tempest-db-js 0.2.0 → 0.3.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.
package/dist/index.d.cts CHANGED
@@ -380,14 +380,14 @@ declare function del<C extends ModelClass>(model: C): DeleteBuilder<InferModel<C
380
380
  * detection, so URLs copied from a Python service still work here.
381
381
  */
382
382
  /** A database dialect tempest-db-js can target. */
383
- type Dialect = "sqlite" | "postgresql";
383
+ type Dialect = "sqlite" | "postgresql" | "mysql";
384
384
  /** A parsed database URL, dialect-neutral. */
385
385
  interface ParsedDatabaseUrl {
386
386
  /** The detected dialect. */
387
387
  readonly dialect: Dialect;
388
388
  /** Driver after the `+` in the scheme (e.g. `better-sqlite3`), or `null`. */
389
389
  readonly driver: string | null;
390
- /** Host (PostgreSQL), or `null` for SQLite. */
390
+ /** Host (PostgreSQL/MySQL), or `null` for SQLite. */
391
391
  readonly host: string | null;
392
392
  /** Port, or `null`. */
393
393
  readonly port: number | null;
@@ -610,7 +610,7 @@ type QueryNode = SelectNode | InsertNode | UpdateNode | DeleteNode | JoinNode;
610
610
  * actually differs between databases (placeholder syntax, `ILIKE` support).
611
611
  */
612
612
  declare abstract class BaseDialect {
613
- abstract readonly name: "sqlite" | "postgresql";
613
+ abstract readonly name: Dialect;
614
614
  /**
615
615
  * INSERT SQL templates keyed by structure (dialect|table|columns|rowCount|
616
616
  * returning). Shared across dialect instances — the key namespaces by dialect
@@ -647,10 +647,19 @@ declare abstract class BaseDialect {
647
647
  * So a per-row insert loop compiles the string once and reuses it every row.
648
648
  */
649
649
  private insertTemplate;
650
+ /**
651
+ * Render the conflict-handling clause. Standard SQL (SQLite/PostgreSQL) uses
652
+ * `ON CONFLICT (...) DO NOTHING | DO UPDATE SET ...`; MySQL overrides this.
653
+ *
654
+ * @param onConflict The conflict clause from the node.
655
+ * @param conflictCols The columns to overwrite on `DO UPDATE` (empty for nothing).
656
+ * @param nextPlaceholder Yields the next positional placeholder (advances the count).
657
+ */
658
+ protected renderConflict(onConflict: NonNullable<InsertNode["onConflict"]>, conflictCols: readonly string[], nextPlaceholder: () => string): string;
650
659
  private compileUpdate;
651
660
  private compileDelete;
652
661
  private compileJoin;
653
- private compileReturning;
662
+ protected compileReturning(returning: readonly string[] | "*" | null): string;
654
663
  /**
655
664
  * Compile a condition tree (fields / and / or / not) to SQL. `idFor` renders a
656
665
  * key to a quoted identifier — `quoteId` for single-table, `qualify` for joins —
@@ -672,8 +681,21 @@ declare class PostgresDialect extends BaseDialect {
672
681
  protected placeholder(index: number): string;
673
682
  protected ilike(column: string, param: string): string;
674
683
  }
684
+ /**
685
+ * MySQL dialect: `?` placeholders, backtick identifiers, `ON DUPLICATE KEY
686
+ * UPDATE` for upsert, and case-insensitive `LIKE` (default collation). MySQL has
687
+ * no `RETURNING`, so requesting it throws.
688
+ */
689
+ declare class MysqlDialect extends BaseDialect {
690
+ readonly name: "mysql";
691
+ protected placeholder(): string;
692
+ protected ilike(column: string, param: string): string;
693
+ protected quoteId(name: string): string;
694
+ protected renderConflict(onConflict: NonNullable<InsertNode["onConflict"]>, conflictCols: readonly string[], nextPlaceholder: () => string): string;
695
+ protected compileReturning(returning: readonly string[] | "*" | null): string;
696
+ }
675
697
  /** Get a dialect instance by name. */
676
- declare function getDialect(name: "sqlite" | "postgresql"): BaseDialect;
698
+ declare function getDialect(name: Dialect): BaseDialect;
677
699
 
678
700
  /** The outcome of running one statement. */
679
701
  interface DriverResult {
@@ -1364,4 +1386,4 @@ type InferInsert<C extends ModelClass> = Simplify<{
1364
1386
  [K in Exclude<ColumnKeys<InstanceType<C>>, OptionalInsertKeys<InstanceType<C>>>]: ColValue<InstanceType<C>[K]>;
1365
1387
  }>;
1366
1388
 
1367
- export { ActiveRecord, type ActiveRecordManager, Agg, type AggregateTerm, type AsyncDriver, AsyncEngine, AsyncResult, AsyncSession, BaseDialect, BaseRepository, type BelongsTo, type ColRef, type ColType, Column, type ColumnFlags, type ColumnType, type ColumnTypeKind, type ColumnTypeMeta, type CompiledQuery, type CondNode, type Condition, type DefaultValue, DeleteBuilder, type DeleteNode, type Dialect, type DriverResult, type EngineOptions, type Executable, type HasMany, type InferInsert, type InferModel, InsertBuilder, type InsertNode, InvalidDatabaseUrl, JoinBuilder, type JoinClause, type JoinNode, type JoinOn, type JoinSelection, type JoinWhereInput, Model, type ModelClass, NoResultError, NodeSqliteDriver, OPERATORS, type OnConflict, type Operator, type OperatorsFor, type OrderTerm, type PaginationFilter, type PaginationResult, type ParsedDatabaseUrl, type PoolOptions, type PortableExpression, PostgresDialect, QueryExecutionError, type QueryLogger, type QueryNode, RecordNotFound, type Relation, type RelationValue, type ReservedAsyncDriver, type Returning, type RowOf, SelectBuilder, type SelectNode, type SortDirection, type Sources, SqliteDialect, type SyncDriver, SyncEngine, SyncResult, SyncSession, UpdateBuilder, type UpdateNode, ValidationError, type WhereArg, type WhereInput, type WithRelations, activeRecord, and, avg, belongsTo, column, columnsOf, count, createEngine, createSyncEngine, del, detectDialect, fromDict, getDialect, hasMany, insert, isCondition, join, loadRelations, max, min, not, or, parse, parseDatabaseUrl, select, sql, stringify, sum, toCondNode, toDict, toJSON, update };
1389
+ export { ActiveRecord, type ActiveRecordManager, Agg, type AggregateTerm, type AsyncDriver, AsyncEngine, AsyncResult, AsyncSession, BaseDialect, BaseRepository, type BelongsTo, type ColRef, type ColType, Column, type ColumnFlags, type ColumnType, type ColumnTypeKind, type ColumnTypeMeta, type CompiledQuery, type CondNode, type Condition, type DefaultValue, DeleteBuilder, type DeleteNode, type Dialect, type DriverResult, type EngineOptions, type Executable, type HasMany, type InferInsert, type InferModel, InsertBuilder, type InsertNode, InvalidDatabaseUrl, JoinBuilder, type JoinClause, type JoinNode, type JoinOn, type JoinSelection, type JoinWhereInput, Model, type ModelClass, MysqlDialect, NoResultError, NodeSqliteDriver, OPERATORS, type OnConflict, type Operator, type OperatorsFor, type OrderTerm, type PaginationFilter, type PaginationResult, type ParsedDatabaseUrl, type PoolOptions, type PortableExpression, PostgresDialect, QueryExecutionError, type QueryLogger, type QueryNode, RecordNotFound, type Relation, type RelationValue, type ReservedAsyncDriver, type Returning, type RowOf, SelectBuilder, type SelectNode, type SortDirection, type Sources, SqliteDialect, type SyncDriver, SyncEngine, SyncResult, SyncSession, UpdateBuilder, type UpdateNode, ValidationError, type WhereArg, type WhereInput, type WithRelations, activeRecord, and, avg, belongsTo, column, columnsOf, count, createEngine, createSyncEngine, del, detectDialect, fromDict, getDialect, hasMany, insert, isCondition, join, loadRelations, max, min, not, or, parse, parseDatabaseUrl, select, sql, stringify, sum, toCondNode, toDict, toJSON, update };
package/dist/index.d.ts CHANGED
@@ -380,14 +380,14 @@ declare function del<C extends ModelClass>(model: C): DeleteBuilder<InferModel<C
380
380
  * detection, so URLs copied from a Python service still work here.
381
381
  */
382
382
  /** A database dialect tempest-db-js can target. */
383
- type Dialect = "sqlite" | "postgresql";
383
+ type Dialect = "sqlite" | "postgresql" | "mysql";
384
384
  /** A parsed database URL, dialect-neutral. */
385
385
  interface ParsedDatabaseUrl {
386
386
  /** The detected dialect. */
387
387
  readonly dialect: Dialect;
388
388
  /** Driver after the `+` in the scheme (e.g. `better-sqlite3`), or `null`. */
389
389
  readonly driver: string | null;
390
- /** Host (PostgreSQL), or `null` for SQLite. */
390
+ /** Host (PostgreSQL/MySQL), or `null` for SQLite. */
391
391
  readonly host: string | null;
392
392
  /** Port, or `null`. */
393
393
  readonly port: number | null;
@@ -610,7 +610,7 @@ type QueryNode = SelectNode | InsertNode | UpdateNode | DeleteNode | JoinNode;
610
610
  * actually differs between databases (placeholder syntax, `ILIKE` support).
611
611
  */
612
612
  declare abstract class BaseDialect {
613
- abstract readonly name: "sqlite" | "postgresql";
613
+ abstract readonly name: Dialect;
614
614
  /**
615
615
  * INSERT SQL templates keyed by structure (dialect|table|columns|rowCount|
616
616
  * returning). Shared across dialect instances — the key namespaces by dialect
@@ -647,10 +647,19 @@ declare abstract class BaseDialect {
647
647
  * So a per-row insert loop compiles the string once and reuses it every row.
648
648
  */
649
649
  private insertTemplate;
650
+ /**
651
+ * Render the conflict-handling clause. Standard SQL (SQLite/PostgreSQL) uses
652
+ * `ON CONFLICT (...) DO NOTHING | DO UPDATE SET ...`; MySQL overrides this.
653
+ *
654
+ * @param onConflict The conflict clause from the node.
655
+ * @param conflictCols The columns to overwrite on `DO UPDATE` (empty for nothing).
656
+ * @param nextPlaceholder Yields the next positional placeholder (advances the count).
657
+ */
658
+ protected renderConflict(onConflict: NonNullable<InsertNode["onConflict"]>, conflictCols: readonly string[], nextPlaceholder: () => string): string;
650
659
  private compileUpdate;
651
660
  private compileDelete;
652
661
  private compileJoin;
653
- private compileReturning;
662
+ protected compileReturning(returning: readonly string[] | "*" | null): string;
654
663
  /**
655
664
  * Compile a condition tree (fields / and / or / not) to SQL. `idFor` renders a
656
665
  * key to a quoted identifier — `quoteId` for single-table, `qualify` for joins —
@@ -672,8 +681,21 @@ declare class PostgresDialect extends BaseDialect {
672
681
  protected placeholder(index: number): string;
673
682
  protected ilike(column: string, param: string): string;
674
683
  }
684
+ /**
685
+ * MySQL dialect: `?` placeholders, backtick identifiers, `ON DUPLICATE KEY
686
+ * UPDATE` for upsert, and case-insensitive `LIKE` (default collation). MySQL has
687
+ * no `RETURNING`, so requesting it throws.
688
+ */
689
+ declare class MysqlDialect extends BaseDialect {
690
+ readonly name: "mysql";
691
+ protected placeholder(): string;
692
+ protected ilike(column: string, param: string): string;
693
+ protected quoteId(name: string): string;
694
+ protected renderConflict(onConflict: NonNullable<InsertNode["onConflict"]>, conflictCols: readonly string[], nextPlaceholder: () => string): string;
695
+ protected compileReturning(returning: readonly string[] | "*" | null): string;
696
+ }
675
697
  /** Get a dialect instance by name. */
676
- declare function getDialect(name: "sqlite" | "postgresql"): BaseDialect;
698
+ declare function getDialect(name: Dialect): BaseDialect;
677
699
 
678
700
  /** The outcome of running one statement. */
679
701
  interface DriverResult {
@@ -1364,4 +1386,4 @@ type InferInsert<C extends ModelClass> = Simplify<{
1364
1386
  [K in Exclude<ColumnKeys<InstanceType<C>>, OptionalInsertKeys<InstanceType<C>>>]: ColValue<InstanceType<C>[K]>;
1365
1387
  }>;
1366
1388
 
1367
- export { ActiveRecord, type ActiveRecordManager, Agg, type AggregateTerm, type AsyncDriver, AsyncEngine, AsyncResult, AsyncSession, BaseDialect, BaseRepository, type BelongsTo, type ColRef, type ColType, Column, type ColumnFlags, type ColumnType, type ColumnTypeKind, type ColumnTypeMeta, type CompiledQuery, type CondNode, type Condition, type DefaultValue, DeleteBuilder, type DeleteNode, type Dialect, type DriverResult, type EngineOptions, type Executable, type HasMany, type InferInsert, type InferModel, InsertBuilder, type InsertNode, InvalidDatabaseUrl, JoinBuilder, type JoinClause, type JoinNode, type JoinOn, type JoinSelection, type JoinWhereInput, Model, type ModelClass, NoResultError, NodeSqliteDriver, OPERATORS, type OnConflict, type Operator, type OperatorsFor, type OrderTerm, type PaginationFilter, type PaginationResult, type ParsedDatabaseUrl, type PoolOptions, type PortableExpression, PostgresDialect, QueryExecutionError, type QueryLogger, type QueryNode, RecordNotFound, type Relation, type RelationValue, type ReservedAsyncDriver, type Returning, type RowOf, SelectBuilder, type SelectNode, type SortDirection, type Sources, SqliteDialect, type SyncDriver, SyncEngine, SyncResult, SyncSession, UpdateBuilder, type UpdateNode, ValidationError, type WhereArg, type WhereInput, type WithRelations, activeRecord, and, avg, belongsTo, column, columnsOf, count, createEngine, createSyncEngine, del, detectDialect, fromDict, getDialect, hasMany, insert, isCondition, join, loadRelations, max, min, not, or, parse, parseDatabaseUrl, select, sql, stringify, sum, toCondNode, toDict, toJSON, update };
1389
+ export { ActiveRecord, type ActiveRecordManager, Agg, type AggregateTerm, type AsyncDriver, AsyncEngine, AsyncResult, AsyncSession, BaseDialect, BaseRepository, type BelongsTo, type ColRef, type ColType, Column, type ColumnFlags, type ColumnType, type ColumnTypeKind, type ColumnTypeMeta, type CompiledQuery, type CondNode, type Condition, type DefaultValue, DeleteBuilder, type DeleteNode, type Dialect, type DriverResult, type EngineOptions, type Executable, type HasMany, type InferInsert, type InferModel, InsertBuilder, type InsertNode, InvalidDatabaseUrl, JoinBuilder, type JoinClause, type JoinNode, type JoinOn, type JoinSelection, type JoinWhereInput, Model, type ModelClass, MysqlDialect, NoResultError, NodeSqliteDriver, OPERATORS, type OnConflict, type Operator, type OperatorsFor, type OrderTerm, type PaginationFilter, type PaginationResult, type ParsedDatabaseUrl, type PoolOptions, type PortableExpression, PostgresDialect, QueryExecutionError, type QueryLogger, type QueryNode, RecordNotFound, type Relation, type RelationValue, type ReservedAsyncDriver, type Returning, type RowOf, SelectBuilder, type SelectNode, type SortDirection, type Sources, SqliteDialect, type SyncDriver, SyncEngine, SyncResult, SyncSession, UpdateBuilder, type UpdateNode, ValidationError, type WhereArg, type WhereInput, type WithRelations, activeRecord, and, avg, belongsTo, column, columnsOf, count, createEngine, createSyncEngine, del, detectDialect, fromDict, getDialect, hasMany, insert, isCondition, join, loadRelations, max, min, not, or, parse, parseDatabaseUrl, select, sql, stringify, sum, toCondNode, toDict, toJSON, update };
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
- export { ActiveRecord, Agg, AsyncEngine, AsyncResult, AsyncSession, BaseDialect, BaseRepository, Column, DeleteBuilder, InsertBuilder, InvalidDatabaseUrl, JoinBuilder, Model, NoResultError, NodeSqliteDriver, OPERATORS, PostgresDialect, QueryExecutionError, RecordNotFound, SelectBuilder, SqliteDialect, SyncEngine, SyncResult, SyncSession, UpdateBuilder, ValidationError, activeRecord, and, avg, belongsTo, column, columnsOf, count, createEngine, createSyncEngine, del, detectDialect, fromDict, getDialect, hasMany, insert, isCondition, join, loadRelations, max, min, not, or, parse, parseDatabaseUrl, select, sql, stringify, sum, toCondNode, toDict, toJSON, update } from './chunk-AGDD7K3F.js';
1
+ export { ActiveRecord, Agg, AsyncEngine, AsyncResult, AsyncSession, BaseDialect, BaseRepository, Column, DeleteBuilder, InsertBuilder, InvalidDatabaseUrl, JoinBuilder, Model, MysqlDialect, NoResultError, NodeSqliteDriver, OPERATORS, PostgresDialect, QueryExecutionError, RecordNotFound, SelectBuilder, SqliteDialect, SyncEngine, SyncResult, SyncSession, UpdateBuilder, ValidationError, activeRecord, and, avg, belongsTo, column, columnsOf, count, createEngine, createSyncEngine, del, detectDialect, fromDict, getDialect, hasMany, insert, isCondition, join, loadRelations, max, min, not, or, parse, parseDatabaseUrl, select, sql, stringify, sum, toCondNode, toDict, toJSON, update } from './chunk-Q32CBI2A.js';
2
2
  //# sourceMappingURL=index.js.map
3
3
  //# sourceMappingURL=index.js.map
@@ -140,8 +140,8 @@ function invertAll(ops) {
140
140
  }
141
141
 
142
142
  // src/migrations/ddl.ts
143
- function quoteId(name) {
144
- return `"${name.replace(/"/g, '""')}"`;
143
+ function quoteId(name, dialect) {
144
+ return dialect === "mysql" ? `\`${name.replace(/`/g, "``")}\`` : `"${name.replace(/"/g, '""')}"`;
145
145
  }
146
146
  function quoteLiteral(value) {
147
147
  return `'${value.replace(/'/g, "''")}'`;
@@ -166,6 +166,45 @@ function renderColumnType(type, dialect) {
166
166
  return "TEXT";
167
167
  }
168
168
  }
169
+ if (dialect === "mysql") {
170
+ switch (kind) {
171
+ case "smallint":
172
+ return "SMALLINT";
173
+ case "integer":
174
+ return "INT";
175
+ case "bigint":
176
+ return "BIGINT";
177
+ case "numeric":
178
+ return meta.precision !== void 0 ? `DECIMAL(${meta.precision}${meta.scale !== void 0 ? `, ${meta.scale}` : ""})` : "DECIMAL";
179
+ case "real":
180
+ return "FLOAT";
181
+ case "double":
182
+ return "DOUBLE";
183
+ case "varchar":
184
+ return `VARCHAR(${meta.length ?? 255})`;
185
+ case "char":
186
+ return `CHAR(${meta.length ?? 255})`;
187
+ case "text":
188
+ return "TEXT";
189
+ case "boolean":
190
+ return "TINYINT(1)";
191
+ case "date":
192
+ return "DATE";
193
+ case "time":
194
+ return "TIME";
195
+ case "datetime":
196
+ case "timestamp":
197
+ return "DATETIME";
198
+ case "blob":
199
+ return "BLOB";
200
+ case "json":
201
+ return "JSON";
202
+ case "uuid":
203
+ return "CHAR(36)";
204
+ case "enum":
205
+ return `ENUM(${(meta.values ?? []).map(quoteLiteral).join(", ")})`;
206
+ }
207
+ }
169
208
  switch (kind) {
170
209
  case "smallint":
171
210
  return "SMALLINT";
@@ -210,19 +249,21 @@ function renderDefault(def, dialect) {
210
249
  if (typeof expr === "object") return expr.raw;
211
250
  switch (expr) {
212
251
  case "now":
213
- return dialect === "sqlite" ? "CURRENT_TIMESTAMP" : "now()";
252
+ return dialect === "postgresql" ? "now()" : "CURRENT_TIMESTAMP";
214
253
  case "current_date":
215
254
  return "CURRENT_DATE";
216
255
  case "current_time":
217
256
  return "CURRENT_TIME";
218
257
  case "uuidv4":
219
- return dialect === "sqlite" ? "(lower(hex(randomblob(16))))" : "gen_random_uuid()";
258
+ if (dialect === "postgresql") return "gen_random_uuid()";
259
+ if (dialect === "mysql") return "(UUID())";
260
+ return "(lower(hex(randomblob(16))))";
220
261
  }
221
262
  }
222
263
  const value = def.value;
223
264
  if (value === null) return "NULL";
224
265
  if (typeof value === "boolean") {
225
- return dialect === "sqlite" ? value ? "1" : "0" : value ? "TRUE" : "FALSE";
266
+ return dialect === "postgresql" ? value ? "TRUE" : "FALSE" : value ? "1" : "0";
226
267
  }
227
268
  if (typeof value === "number" || typeof value === "bigint") return String(value);
228
269
  if (value instanceof Date) return quoteLiteral(value.toISOString());
@@ -230,7 +271,7 @@ function renderDefault(def, dialect) {
230
271
  return quoteLiteral(String(value));
231
272
  }
232
273
  function renderColumnDef(col, dialect) {
233
- let sql = `${quoteId(col.name)} ${renderColumnType(col.type, dialect)}`;
274
+ let sql = `${quoteId(col.name, dialect)} ${renderColumnType(col.type, dialect)}`;
234
275
  if (col.notNull) sql += " NOT NULL";
235
276
  if (col.default !== null) sql += ` DEFAULT ${renderDefault(col.default, dialect)}`;
236
277
  return sql;
@@ -252,23 +293,28 @@ function renderCreateTable(table, dialect) {
252
293
  if (dialect === "postgresql" && c.type.kind === "enum") {
253
294
  const typeName = enumTypeName(table.name, c.name);
254
295
  const values = (c.type.meta.values ?? []).map(quoteLiteral).join(", ");
255
- typeStmts.push(`CREATE TYPE ${quoteId(typeName)} AS ENUM (${values})`);
256
- let def = `${quoteId(c.name)} ${quoteId(typeName)}`;
296
+ typeStmts.push(`CREATE TYPE ${quoteId(typeName, dialect)} AS ENUM (${values})`);
297
+ let def = `${quoteId(c.name, dialect)} ${quoteId(typeName, dialect)}`;
257
298
  if (c.notNull) def += " NOT NULL";
258
299
  if (c.default !== null) def += ` DEFAULT ${renderDefault(c.default, dialect)}`;
259
300
  return def;
260
301
  }
261
302
  if (dialect === "postgresql" && isAutoIncrementPk(table, c)) {
262
- return `${quoteId(c.name)} ${postgresSerialType(c.type.kind)}`;
303
+ return `${quoteId(c.name, dialect)} ${postgresSerialType(c.type.kind)}`;
304
+ }
305
+ if (dialect === "mysql" && isAutoIncrementPk(table, c)) {
306
+ return `${quoteId(c.name, dialect)} ${renderColumnType(c.type, dialect)} NOT NULL AUTO_INCREMENT`;
263
307
  }
264
308
  return renderColumnDef(c, dialect);
265
309
  });
266
310
  if (table.primaryKey.length > 0) {
267
- cols.push(`PRIMARY KEY (${table.primaryKey.map(quoteId).join(", ")})`);
311
+ cols.push(
312
+ `PRIMARY KEY (${table.primaryKey.map((c) => quoteId(c, dialect)).join(", ")})`
313
+ );
268
314
  }
269
315
  return [
270
316
  ...typeStmts,
271
- `CREATE TABLE ${quoteId(table.name)} (
317
+ `CREATE TABLE ${quoteId(table.name, dialect)} (
272
318
  ${cols.join(",\n ")}
273
319
  )`
274
320
  ];
@@ -278,23 +324,27 @@ function renderOperation(op, dialect) {
278
324
  case "create_table":
279
325
  return renderCreateTable(op.table, dialect);
280
326
  case "drop_table":
281
- return [`DROP TABLE ${quoteId(op.table.name)}`];
327
+ return [`DROP TABLE ${quoteId(op.table.name, dialect)}`];
282
328
  case "rename_table":
283
- return [`ALTER TABLE ${quoteId(op.from)} RENAME TO ${quoteId(op.to)}`];
329
+ return dialect === "mysql" ? [`RENAME TABLE ${quoteId(op.from, dialect)} TO ${quoteId(op.to, dialect)}`] : [
330
+ `ALTER TABLE ${quoteId(op.from, dialect)} RENAME TO ${quoteId(op.to, dialect)}`
331
+ ];
284
332
  case "add_column":
285
333
  return [
286
- `ALTER TABLE ${quoteId(op.table)} ADD COLUMN ${renderColumnDef(op.column, dialect)}`
334
+ `ALTER TABLE ${quoteId(op.table, dialect)} ADD COLUMN ${renderColumnDef(op.column, dialect)}`
287
335
  ];
288
336
  case "drop_column":
289
- return [`ALTER TABLE ${quoteId(op.table)} DROP COLUMN ${quoteId(op.column.name)}`];
337
+ return [
338
+ `ALTER TABLE ${quoteId(op.table, dialect)} DROP COLUMN ${quoteId(op.column.name, dialect)}`
339
+ ];
290
340
  case "rename_column":
291
341
  return [
292
- `ALTER TABLE ${quoteId(op.table)} RENAME COLUMN ${quoteId(op.from)} TO ${quoteId(op.to)}`
342
+ `ALTER TABLE ${quoteId(op.table, dialect)} RENAME COLUMN ${quoteId(op.from, dialect)} TO ${quoteId(op.to, dialect)}`
293
343
  ];
294
344
  case "alter_column":
295
345
  return renderAlterColumn(op.table, op.to, dialect);
296
346
  case "recreate_table":
297
- return dialect === "sqlite" ? renderSqliteRebuild(op.from, op.to) : renderPostgresTableDiff(op.from, op.to);
347
+ return dialect === "sqlite" ? renderSqliteRebuild(op.from, op.to) : renderTableDiff(op.from, op.to, dialect);
298
348
  case "execute":
299
349
  return [op.up];
300
350
  }
@@ -304,34 +354,38 @@ function renderSqliteRebuild(from, to) {
304
354
  const common = Object.keys(to.columns).filter((c) => c in from.columns);
305
355
  const cols = Object.values(to.columns).map((c) => renderColumnDef(c, "sqlite"));
306
356
  if (to.primaryKey.length > 0) {
307
- cols.push(`PRIMARY KEY (${to.primaryKey.map(quoteId).join(", ")})`);
357
+ cols.push(
358
+ `PRIMARY KEY (${to.primaryKey.map((c) => quoteId(c, "sqlite")).join(", ")})`
359
+ );
308
360
  }
309
- const commonSql = common.map(quoteId).join(", ");
361
+ const commonSql = common.map((c) => quoteId(c, "sqlite")).join(", ");
310
362
  return [
311
363
  "PRAGMA foreign_keys=off",
312
- `CREATE TABLE ${quoteId(tmp)} (
364
+ `CREATE TABLE ${quoteId(tmp, "sqlite")} (
313
365
  ${cols.join(",\n ")}
314
366
  )`,
315
- common.length > 0 ? `INSERT INTO ${quoteId(tmp)} (${commonSql}) SELECT ${commonSql} FROM ${quoteId(from.name)}` : `-- no common columns to copy from ${from.name}`,
316
- `DROP TABLE ${quoteId(from.name)}`,
317
- `ALTER TABLE ${quoteId(tmp)} RENAME TO ${quoteId(to.name)}`,
367
+ common.length > 0 ? `INSERT INTO ${quoteId(tmp, "sqlite")} (${commonSql}) SELECT ${commonSql} FROM ${quoteId(from.name, "sqlite")}` : `-- no common columns to copy from ${from.name}`,
368
+ `DROP TABLE ${quoteId(from.name, "sqlite")}`,
369
+ `ALTER TABLE ${quoteId(tmp, "sqlite")} RENAME TO ${quoteId(to.name, "sqlite")}`,
318
370
  "PRAGMA foreign_keys=on"
319
371
  ];
320
372
  }
321
- function renderPostgresTableDiff(from, to) {
373
+ function renderTableDiff(from, to, dialect) {
322
374
  const stmts = [];
323
375
  for (const [name, col] of Object.entries(to.columns)) {
324
376
  if (!(name in from.columns)) {
325
377
  stmts.push(
326
- `ALTER TABLE ${quoteId(to.name)} ADD COLUMN ${renderColumnDef(col, "postgresql")}`
378
+ `ALTER TABLE ${quoteId(to.name, dialect)} ADD COLUMN ${renderColumnDef(col, dialect)}`
327
379
  );
328
380
  } else {
329
- stmts.push(...renderAlterColumn(to.name, col, "postgresql"));
381
+ stmts.push(...renderAlterColumn(to.name, col, dialect));
330
382
  }
331
383
  }
332
384
  for (const name of Object.keys(from.columns)) {
333
385
  if (!(name in to.columns)) {
334
- stmts.push(`ALTER TABLE ${quoteId(to.name)} DROP COLUMN ${quoteId(name)}`);
386
+ stmts.push(
387
+ `ALTER TABLE ${quoteId(to.name, dialect)} DROP COLUMN ${quoteId(name, dialect)}`
388
+ );
335
389
  }
336
390
  }
337
391
  return stmts;
@@ -339,11 +393,16 @@ function renderPostgresTableDiff(from, to) {
339
393
  function renderAlterColumn(table, to, dialect) {
340
394
  if (dialect === "sqlite") {
341
395
  throw new Error(
342
- `alter_column on SQLite needs batch/table-rebuild (Phase 6e); column ${table}.${to.name}`
396
+ `alter_column on SQLite needs a table-rebuild (recreate_table); column ${table}.${to.name}`
343
397
  );
344
398
  }
345
- const t = quoteId(table);
346
- const c = quoteId(to.name);
399
+ if (dialect === "mysql") {
400
+ return [
401
+ `ALTER TABLE ${quoteId(table, dialect)} MODIFY COLUMN ${renderColumnDef(to, dialect)}`
402
+ ];
403
+ }
404
+ const t = quoteId(table, dialect);
405
+ const c = quoteId(to.name, dialect);
347
406
  const stmts = [
348
407
  `ALTER TABLE ${t} ALTER COLUMN ${c} TYPE ${renderColumnType(to.type, dialect)}`
349
408
  ];
@@ -634,6 +693,112 @@ var MigrationRunner = class {
634
693
  return reverted;
635
694
  }
636
695
  };
696
+ function quoteIdent(name, dialect) {
697
+ return dialect === "mysql" ? `\`${name.replace(/`/g, "``")}\`` : `"${name.replace(/"/g, '""')}"`;
698
+ }
699
+ function placeholder(index, dialect) {
700
+ return dialect === "postgresql" ? `$${index}` : "?";
701
+ }
702
+ var AsyncMigrationRunner = class {
703
+ constructor(driver, dialect) {
704
+ this.driver = driver;
705
+ this.dialect = dialect;
706
+ this.vt = quoteIdent(VERSION_TABLE, dialect);
707
+ }
708
+ driver;
709
+ dialect;
710
+ vt;
711
+ /** Create the version-tracking table if it does not exist. */
712
+ async ensureVersionTable() {
713
+ await this.driver.execute(
714
+ `CREATE TABLE IF NOT EXISTS ${this.vt} (revision ${this.textType()} PRIMARY KEY, applied_at ${this.textType()} NOT NULL, down_revision ${this.textType()} NOT NULL)`,
715
+ []
716
+ );
717
+ }
718
+ /** A portable "text" column type for the version table. */
719
+ textType() {
720
+ return this.dialect === "mysql" ? "VARCHAR(255)" : "TEXT";
721
+ }
722
+ /** The set of applied revision ids. */
723
+ async applied() {
724
+ await this.ensureVersionTable();
725
+ const { rows } = await this.driver.execute(`SELECT revision FROM ${this.vt}`, []);
726
+ return new Set(rows.map((r) => String(r.revision)));
727
+ }
728
+ async runOps(ops) {
729
+ for (const op of ops) {
730
+ for (const stmt of renderOperation(op, this.dialect)) {
731
+ const trimmed = stmt.trim();
732
+ if (trimmed.length === 0 || trimmed.startsWith("--")) continue;
733
+ await this.driver.execute(stmt, []);
734
+ }
735
+ }
736
+ }
737
+ async record(migration, appliedAt) {
738
+ const p = (i) => placeholder(i, this.dialect);
739
+ await this.driver.execute(
740
+ `INSERT INTO ${this.vt} (revision, applied_at, down_revision) VALUES (${p(1)}, ${p(2)}, ${p(3)})`,
741
+ [migration.revision, appliedAt, migration.downRevision.join(",")]
742
+ );
743
+ }
744
+ async forget(revision) {
745
+ await this.driver.execute(
746
+ `DELETE FROM ${this.vt} WHERE revision = ${placeholder(1, this.dialect)}`,
747
+ [revision]
748
+ );
749
+ }
750
+ /**
751
+ * Apply all pending migrations up to the head(s), in DAG order.
752
+ *
753
+ * @param migrations All known migrations.
754
+ * @param appliedAt Timestamp string to stamp on each applied revision.
755
+ * @returns The revision ids that were applied this run.
756
+ */
757
+ async upgrade(migrations, appliedAt) {
758
+ const done = await this.applied();
759
+ const ordered = topoOrder(migrations);
760
+ const ran = [];
761
+ for (const migration of ordered) {
762
+ if (done.has(migration.revision)) continue;
763
+ const op = new Op();
764
+ migration.up(op);
765
+ await this.runOps(op.operations);
766
+ await this.record(migration, appliedAt);
767
+ ran.push(migration.revision);
768
+ }
769
+ return ran;
770
+ }
771
+ /**
772
+ * Revert the last `steps` applied migrations (default 1), newest first.
773
+ *
774
+ * @param migrations All known migrations.
775
+ * @param steps How many applied revisions to roll back.
776
+ * @returns The revision ids that were reverted.
777
+ */
778
+ async downgrade(migrations, steps = 1) {
779
+ const done = await this.applied();
780
+ const ordered = topoOrder(migrations).filter((m) => done.has(m.revision));
781
+ const toRevert = ordered.slice(-steps).reverse();
782
+ const reverted = [];
783
+ for (const migration of toRevert) {
784
+ const op = new Op();
785
+ try {
786
+ migration.down(op);
787
+ } catch {
788
+ op.operations.length = 0;
789
+ }
790
+ if (op.operations.length === 0) {
791
+ const upOp = new Op();
792
+ migration.up(upOp);
793
+ op.operations.push(...invertAll(upOp.operations));
794
+ }
795
+ await this.runOps(op.operations);
796
+ await this.forget(migration.revision);
797
+ reverted.push(migration.revision);
798
+ }
799
+ return reverted;
800
+ }
801
+ };
637
802
 
638
803
  // src/migrations/introspect.ts
639
804
  function sqliteAffinity(declared) {
@@ -1123,6 +1288,7 @@ function runMigrationCli(argv, config) {
1123
1288
  }
1124
1289
  }
1125
1290
 
1291
+ exports.AsyncMigrationRunner = AsyncMigrationRunner;
1126
1292
  exports.CyclicMigrationGraph = CyclicMigrationGraph;
1127
1293
  exports.IrreversibleMigration = IrreversibleMigration;
1128
1294
  exports.MigrationRunner = MigrationRunner;