rake-db 2.28.1 → 2.29.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.mjs CHANGED
@@ -1,19 +1,250 @@
1
- import { singleQuote, defaultSchemaConfig, makeColumnTypes, getStackTrace, isRawSQL, escapeForMigration, ArrayColumn, toSnakeCase, toCamelCase, DomainColumn, toArray, EnumColumn, snakeCaseKey, getColumnTypes, parseTableData, emptyObject, escapeString, tableDataMethods, setCurrentColumnName, consumeColumnName, Column, parseTableDataInput, UnknownColumn, setDefaultLanguage, deepCompare, raw, logParamToLogObject, createDbWithAdapter, getImportPath, pathToLog, emptyArray, colors, exhaustive, codeToString, addCode, quoteObjectKey, pushTableDataCode, rawSqlToCode, primaryKeyInnerToCode, indexInnerToCode, excludeInnerToCode, constraintInnerToCode, referencesArgsToCode, backtickQuote, TimestampTZColumn, TimestampColumn, makeColumnsByType, RawSql, CustomTypeColumn, assignDbDataToColumn, PostgisGeographyPointColumn } from 'pqb';
1
+ import { colors, singleQuote, isRawSQL, escapeForMigration, ArrayColumn, toSnakeCase, toCamelCase, DomainColumn, toArray, EnumColumn, defaultSchemaConfig, snakeCaseKey, getColumnTypes, parseTableData, emptyObject, escapeString, tableDataMethods, setCurrentColumnName, consumeColumnName, Column, parseTableDataInput, UnknownColumn, setDefaultLanguage, deepCompare, raw, logParamToLogObject, createDbWithAdapter, getImportPath, pathToLog, emptyArray, exhaustive, codeToString, addCode, quoteObjectKey, pushTableDataCode, rawSqlToCode, primaryKeyInnerToCode, indexInnerToCode, excludeInnerToCode, constraintInnerToCode, referencesArgsToCode, backtickQuote, TimestampTZColumn, TimestampColumn, makeColumnsByType, RawSql, CustomTypeColumn, assignDbDataToColumn, PostgisGeographyPointColumn, makeColumnTypes, getStackTrace } from 'pqb';
2
2
  import path, { join } from 'path';
3
- import { fileURLToPath, pathToFileURL } from 'node:url';
3
+ import { pathToFileURL, fileURLToPath } from 'node:url';
4
4
  import fs, { mkdir, writeFile, readdir, stat, readFile } from 'fs/promises';
5
5
 
6
- class RakeDbError extends Error {
7
- }
8
- class NoPrimaryKey extends RakeDbError {
9
- }
6
+ const ESC = "\x1B";
7
+ const CSI = `${ESC}[`;
8
+ const cursorShow = `${CSI}?25h`;
9
+ const cursorHide = `${CSI}?25l`;
10
+ const { stdin, stdout } = process;
11
+ const visibleChars = (s) => s.replace(
12
+ // eslint-disable-next-line no-control-regex
13
+ /[\u001B\u009B][[\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\d/#&.:=?%@~_]+)*|[a-zA-Z\d]+(?:;[-a-zA-Z\d/#&.:=?%@~_]*)*)?\u0007)|(?:(?:\d{1,4}(?:;\d{0,4})*)?[\dA-PRZcf-ntqry=><~]))/g,
14
+ ""
15
+ ).length;
16
+ const clear = (text) => {
17
+ const rows = text.split(/\r?\n/).reduce(
18
+ (rows2, line) => rows2 + 1 + Math.floor(Math.max(visibleChars(line) - 1, 0) / stdout.columns),
19
+ 0
20
+ );
21
+ let clear2 = "";
22
+ for (let i = 0; i < rows; i++) {
23
+ clear2 += `${CSI}2K`;
24
+ if (i < rows - 1) {
25
+ clear2 += `${CSI}${i < rows - 1 ? "1A" : "G"}`;
26
+ }
27
+ }
28
+ return clear2;
29
+ };
30
+ const prompt = async ({
31
+ render,
32
+ onKeyPress,
33
+ validate,
34
+ value,
35
+ cursor: showCursor
36
+ }) => {
37
+ stdin.resume();
38
+ if (stdin.isTTY) stdin.setRawMode(true);
39
+ stdin.setEncoding("utf-8");
40
+ if (!showCursor) stdout.write(cursorHide);
41
+ return new Promise((res) => {
42
+ let prevText;
43
+ const ctx = {
44
+ value,
45
+ submitted: false,
46
+ render() {
47
+ let text = (ctx.submitted ? colors.greenBold("\u2714") : colors.yellowBold("?")) + " " + render(ctx);
48
+ if (ctx.submitted) text += "\n";
49
+ stdout.write(prevText ? clear(prevText) + "\r" + text : text);
50
+ prevText = text;
51
+ },
52
+ submit(value2) {
53
+ if (value2 !== void 0) ctx.value = value2;
54
+ if (ctx.value === void 0 || validate && !validate?.(ctx)) return;
55
+ ctx.submitted = true;
56
+ ctx.render();
57
+ close();
58
+ res(ctx.value);
59
+ }
60
+ };
61
+ const close = () => {
62
+ if (!showCursor) stdout.write(cursorShow);
63
+ if (stdin.isTTY) stdin.setRawMode(false);
64
+ stdin.off("data", keypress);
65
+ stdin.pause();
66
+ };
67
+ const keypress = (s) => {
68
+ if (s === "" || s === "") {
69
+ close?.();
70
+ process.exit(0);
71
+ }
72
+ if (s === "\r" || s === "\n" || s === "\r\n") {
73
+ ctx.submit();
74
+ } else {
75
+ onKeyPress(ctx, s);
76
+ }
77
+ };
78
+ stdin.on("data", keypress);
79
+ ctx.render();
80
+ });
81
+ };
82
+ const defaultActive = (s) => `${colors.blueBold("\u276F")} ${s}`;
83
+ const defaultInactive = (s) => ` ${s}`;
84
+ const promptSelect = ({
85
+ message,
86
+ options,
87
+ active = defaultActive,
88
+ inactive = defaultInactive
89
+ }) => prompt({
90
+ value: 0,
91
+ render(ctx) {
92
+ let text = `${message} ${colors.pale(
93
+ "Use arrows or jk. Press enter to submit."
94
+ )}
95
+ `;
96
+ for (let i = 0; i < options.length; i++) {
97
+ text += (ctx.value === i ? active : inactive)(options[i]) + "\n";
98
+ }
99
+ return text;
100
+ },
101
+ onKeyPress(ctx, s) {
102
+ ctx.value = s === "\x1B[H" ? 0 : s === "\x1B[F" ? options.length - 1 : s === "\x1B[A" || s === "k" ? ctx.value === 0 ? options.length - 1 : ctx.value - 1 : s === "\x1B[B" || s === "j" || s === " " ? ctx.value === options.length - 1 ? 0 : ctx.value + 1 : ctx.value;
103
+ ctx.render();
104
+ }
105
+ });
106
+ const promptConfirm = ({
107
+ message
108
+ }) => prompt({
109
+ value: true,
110
+ render(ctx) {
111
+ return `${colors.bright(message)}
112
+ ${ctx.submitted ? `> ${ctx.value ? colors.greenBold("Yes") : colors.yellowBold("No")}` : colors.pale(`> (Y/n)`)}
113
+ `;
114
+ },
115
+ onKeyPress(ctx, s) {
116
+ let ok;
117
+ if (s === "y" || s === "Y") ok = true;
118
+ else if (s === "n" || s === "N") ok = false;
119
+ if (ok !== void 0) {
120
+ ctx.submit(ok);
121
+ }
122
+ }
123
+ });
124
+ const promptText = ({
125
+ message,
126
+ default: def = "",
127
+ password,
128
+ min
129
+ }) => {
130
+ let showDefault = true;
131
+ let x = 0;
132
+ const renderValue = (ctx) => password ? "*".repeat(ctx.value.length) : ctx.value;
133
+ return prompt({
134
+ value: def,
135
+ cursor: true,
136
+ validate: (ctx) => !min || ctx.value.length >= min,
137
+ render(ctx) {
138
+ let text = `${colors.bright(message)}
139
+ > ${ctx.submitted ? renderValue(ctx) : showDefault ? colors.pale(def) + "\b".repeat(def.length) : ctx.value}`;
140
+ if (ctx.submitted) text += "\n";
141
+ return text;
142
+ },
143
+ onKeyPress(ctx, s) {
144
+ let value = showDefault ? "" : ctx.value;
145
+ if (s === "\x1B[D" && x > 0) {
146
+ x--;
147
+ stdout.write("\b");
148
+ } else if (s === "\x1B[C" && x < value.length) {
149
+ stdout.write(value[x]);
150
+ x++;
151
+ }
152
+ if (s !== "\x7F" && s !== "\x1B[3~" && !visibleChars(s)) return;
153
+ if (showDefault) {
154
+ showDefault = false;
155
+ stdout.write(" ".repeat(def.length) + "\b".repeat(def.length));
156
+ }
157
+ const prev = value;
158
+ const prevX = x;
159
+ if (s === "\x7F") {
160
+ if (x > 0) {
161
+ value = value.slice(0, x - 1) + value.slice(x);
162
+ x--;
163
+ }
164
+ } else if (s === "\x1B[3~") {
165
+ if (x < value.length) {
166
+ value = value.slice(0, x) + value.slice(x + 1);
167
+ }
168
+ } else {
169
+ value = value.slice(0, x) + s + value.slice(x);
170
+ x++;
171
+ }
172
+ ctx.value = value;
173
+ const spaces = prev.length - value.length;
174
+ stdout.write(
175
+ "\b".repeat(prevX) + renderValue(ctx) + (spaces > 0 ? " ".repeat(spaces) + "\b".repeat(spaces) : "") + "\b".repeat(value.length - x)
176
+ );
177
+ }
178
+ });
179
+ };
10
180
 
11
- let currentChanges = [];
12
- const clearChanges = () => {
13
- currentChanges = [];
181
+ const getNonTransactionAdapter = (db) => "$adapter" in db ? db.$adapter : db;
182
+ const getMaybeTransactionAdapter = (db) => "$qb" in db ? db.$qb.internal.transactionStorage.getStore()?.adapter || db.$adapter : db;
183
+ const ensureTransaction = (db, fn) => {
184
+ const adapter = getMaybeTransactionAdapter(db);
185
+ return adapter.isInTransaction() ? fn(adapter) : adapter.transaction(void 0, fn);
186
+ };
187
+ const runSqlInSavePoint = async (db, sql, code) => {
188
+ const adapter = getMaybeTransactionAdapter(db);
189
+ try {
190
+ await adapter.query(
191
+ adapter.isInTransaction() ? `SAVEPOINT s; ${sql}; RELEASE SAVEPOINT s` : sql
192
+ );
193
+ return "done";
194
+ } catch (err) {
195
+ if (err.code === code) {
196
+ if (adapter.isInTransaction()) {
197
+ await adapter.query(`ROLLBACK TO SAVEPOINT s`);
198
+ }
199
+ return "already";
200
+ }
201
+ throw err;
202
+ }
14
203
  };
15
- const getCurrentChanges = () => currentChanges;
16
- const pushChange = (change) => currentChanges.push(change);
204
+
205
+ class CreateOrDropError extends Error {
206
+ constructor(message, status, cause) {
207
+ super(message);
208
+ this.status = status;
209
+ this.cause = cause;
210
+ }
211
+ }
212
+ const createDatabase = async (db, {
213
+ database,
214
+ owner
215
+ }) => {
216
+ return createOrDrop(
217
+ db,
218
+ `CREATE DATABASE "${database}"${owner ? ` OWNER "${owner}"` : ""}`
219
+ );
220
+ };
221
+ const dropDatabase = async (db, { database }) => {
222
+ return createOrDrop(db, `DROP DATABASE "${database}"`);
223
+ };
224
+ const createOrDrop = async (db, sql) => {
225
+ try {
226
+ const adapter = getNonTransactionAdapter(db);
227
+ await adapter.query(sql);
228
+ return "done";
229
+ } catch (error) {
230
+ const err = error;
231
+ if (typeof err.message === "string" && err.message.includes("sslmode=require")) {
232
+ throw new CreateOrDropError("SSL required", "ssl-required", err);
233
+ }
234
+ if (err.code === "42P04" || err.code === "3D000") {
235
+ return "already";
236
+ }
237
+ if (err.code === "42501") {
238
+ throw new CreateOrDropError("Insufficient privilege", "forbidden", err);
239
+ }
240
+ if (typeof err.message === "string" && err.message.includes("password authentication failed")) {
241
+ throw new CreateOrDropError("Authentication failed", "auth-failed", err);
242
+ }
243
+ throw err;
244
+ }
245
+ };
246
+ const createSchema$1 = async (db, sql) => runSqlInSavePoint(db, `CREATE SCHEMA ${sql}`, "42P06");
247
+ const createTable$1 = async (db, sql) => runSqlInSavePoint(db, `CREATE TABLE ${sql}`, "42P07");
17
248
 
18
249
  const RAKE_DB_LOCK_KEY = "8582141715823621641";
19
250
  const getFirstWordAndRest = (input) => {
@@ -49,15 +280,18 @@ const quoteWithSchema = ({
49
280
  name
50
281
  }) => quoteTable(schema, name);
51
282
  const quoteTable = (schema, table) => schema ? `"${schema}"."${table}"` : `"${table}"`;
52
- const getSchemaAndTableFromName = (name) => {
283
+ const getSchemaAndTableFromName = (config, name) => {
53
284
  const i = name.indexOf(".");
54
- return i !== -1 ? [name.slice(0, i), name.slice(i + 1)] : [void 0, name];
285
+ return i !== -1 ? [name.slice(0, i), name.slice(i + 1)] : [
286
+ typeof config.schema === "function" ? config.schema() : config.schema,
287
+ name
288
+ ];
55
289
  };
56
- const quoteNameFromString = (string) => {
57
- return quoteTable(...getSchemaAndTableFromName(string));
290
+ const quoteNameFromString = (config, string) => {
291
+ return quoteTable(...getSchemaAndTableFromName(config, string));
58
292
  };
59
- const quoteCustomType = (s) => {
60
- const [schema, type] = getSchemaAndTableFromName(s);
293
+ const quoteCustomType = (config, s) => {
294
+ const [schema, type] = getSchemaAndTableFromName(config, s);
61
295
  return schema ? '"' + schema + '".' + type : type;
62
296
  };
63
297
  const quoteSchemaTable = (arg, excludeCurrentSchema) => {
@@ -69,8 +303,8 @@ const concatSchemaAndName = ({
69
303
  }, excludeCurrentSchema) => {
70
304
  return schema && schema !== excludeCurrentSchema ? `${schema}.${name}` : name;
71
305
  };
72
- const makePopulateEnumQuery = (item) => {
73
- const [schema, name] = getSchemaAndTableFromName(item.enumName);
306
+ const makePopulateEnumQuery = (config, item) => {
307
+ const [schema, name] = getSchemaAndTableFromName(config, item.enumName);
74
308
  return {
75
309
  text: `SELECT unnest(enum_range(NULL::${quoteTable(schema, name)}))::text`,
76
310
  then(result) {
@@ -94,115 +328,35 @@ const getCliParam = (args, name) => {
94
328
  return;
95
329
  };
96
330
 
97
- const migrationConfigDefaults = {
98
- schemaConfig: defaultSchemaConfig,
99
- migrationsPath: path.join("src", "db", "migrations"),
100
- migrationId: { serial: 4 },
101
- migrationsTable: "schemaMigrations",
102
- snakeCase: false,
103
- commands: {},
104
- log: true,
105
- logger: console,
106
- import() {
107
- throw new Error(
108
- "Add `import: (path) => import(path),` setting to `rakeDb` config"
109
- );
110
- }
111
- };
112
- const ensureMigrationsPath = (config) => {
113
- if (!config.migrationsPath) {
114
- config.migrationsPath = migrationConfigDefaults.migrationsPath;
115
- }
116
- if (!path.isAbsolute(config.migrationsPath)) {
117
- config.migrationsPath = path.resolve(
118
- config.basePath,
119
- config.migrationsPath
120
- );
121
- }
122
- return config;
123
- };
124
- const ensureBasePathAndDbScript = (config, intermediateCallers = 0) => {
125
- if (config.basePath && config.dbScript) return config;
126
- let filePath = getStackTrace()?.[3 + intermediateCallers]?.getFileName();
127
- if (!filePath) {
128
- throw new Error(
129
- "Failed to determine path to db script. Please set basePath option of rakeDb"
130
- );
131
- }
132
- if (filePath.startsWith("file://")) {
133
- filePath = fileURLToPath(filePath);
134
- }
135
- const ext = path.extname(filePath);
136
- if (ext !== ".ts" && ext !== ".js" && ext !== ".mjs") {
137
- throw new Error(
138
- `Add a .ts suffix to the "${path.basename(filePath)}" when calling it`
139
- );
140
- }
141
- config.basePath = path.dirname(filePath);
142
- config.dbScript = path.basename(filePath);
143
- return config;
331
+ const makeChange = (config) => (fn) => {
332
+ const change = { fn, config };
333
+ pushChange(change);
334
+ return change;
144
335
  };
145
- const processRakeDbConfig = (config, args) => {
146
- const result = { ...migrationConfigDefaults, ...config };
147
- if (!result.log) {
148
- delete result.logger;
149
- }
150
- ensureBasePathAndDbScript(result, 1);
151
- ensureMigrationsPath(result);
152
- if (!result.recurrentPath) {
153
- result.recurrentPath = path.join(
154
- result.migrationsPath,
155
- "recurrent"
156
- );
157
- }
158
- if ("recurrentPath" in result && !path.isAbsolute(result.recurrentPath)) {
159
- result.recurrentPath = path.resolve(result.basePath, result.recurrentPath);
160
- }
161
- if ("baseTable" in config && config.baseTable) {
162
- const { types, snakeCase, language } = config.baseTable.prototype;
163
- result.columnTypes = types || makeColumnTypes(defaultSchemaConfig);
164
- if (snakeCase) result.snakeCase = true;
165
- if (language) result.language = language;
166
- } else {
167
- const ct = "columnTypes" in config && config.columnTypes;
168
- result.columnTypes = (typeof ct === "function" ? ct(
169
- makeColumnTypes(defaultSchemaConfig)
170
- ) : ct) || makeColumnTypes(defaultSchemaConfig);
171
- }
172
- if (config.migrationId === "serial") {
173
- result.migrationId = { serial: 4 };
174
- }
175
- const transaction = getCliParam(args, "transaction");
176
- if (transaction) {
177
- if (transaction !== "single" && transaction !== "per-migration") {
178
- throw new Error(
179
- `Unsupported transaction param ${transaction}, expected single or per-migration`
180
- );
181
- }
182
- result.transaction = transaction;
183
- } else if (!result.transaction) {
184
- result.transaction = "single";
185
- }
186
- return result;
336
+ let currentChanges = [];
337
+ const clearChanges = () => {
338
+ currentChanges = [];
187
339
  };
340
+ const getCurrentChanges = () => currentChanges;
341
+ const pushChange = (change) => currentChanges.push(change);
188
342
 
189
343
  const versionToString = (config, version) => config.migrationId === "timestamp" ? `${version}` : `${version}`.padStart(config.migrationId.serial, "0");
190
- const columnTypeToSql = (item) => {
191
- return item.data.isOfCustomType ? item instanceof DomainColumn ? quoteNameFromString(item.dataType) : quoteCustomType(item.toSQL()) : item.toSQL();
344
+ const columnTypeToSql = (config, item) => {
345
+ return item.data.isOfCustomType ? item instanceof DomainColumn ? quoteNameFromString(config, item.dataType) : quoteCustomType(config, item.toSQL()) : item.toSQL();
192
346
  };
193
347
  const getColumnName = (item, key, snakeCase) => {
194
348
  return item.data.name || (snakeCase ? toSnakeCase(key) : key);
195
349
  };
196
- const columnToSql = (name, item, values, hasMultiplePrimaryKeys, snakeCase) => {
197
- const line = [`"${name}" ${columnTypeToSql(item)}`];
350
+ const columnToSql = (config, name, item, values, hasMultiplePrimaryKeys, snakeCase) => {
351
+ const line = [`"${name}" ${columnTypeToSql(config, item)}`];
198
352
  if (item.data.compression) {
199
353
  line.push(`COMPRESSION ${item.data.compression}`);
200
354
  }
201
355
  if (item.data.collate) {
202
- line.push(`COLLATE ${quoteNameFromString(item.data.collate)}`);
356
+ line.push(`COLLATE ${quoteNameFromString(config, item.data.collate)}`);
203
357
  }
204
358
  if (item.data.identity) {
205
- line.push(identityToSql(item.data.identity));
359
+ line.push(identityToSql(config, item.data.identity));
206
360
  } else if (item.data.generated) {
207
361
  line.push(
208
362
  `GENERATED ALWAYS AS (${item.data.generated.toSQL({
@@ -236,6 +390,7 @@ const columnToSql = (name, item, values, hasMultiplePrimaryKeys, snakeCase) => {
236
390
  }
237
391
  line.push(
238
392
  referencesToSql(
393
+ config,
239
394
  {
240
395
  columns: [name],
241
396
  ...foreignKey
@@ -259,11 +414,11 @@ const encodeColumnDefault = (def, values, column) => {
259
414
  }
260
415
  return null;
261
416
  };
262
- const identityToSql = (identity) => {
263
- const options = sequenceOptionsToSql(identity);
417
+ const identityToSql = (config, identity) => {
418
+ const options = sequenceOptionsToSql(config, identity);
264
419
  return `GENERATED ${identity.always ? "ALWAYS" : "BY DEFAULT"} AS IDENTITY${options ? ` (${options})` : ""}`;
265
420
  };
266
- const sequenceOptionsToSql = (item) => {
421
+ const sequenceOptionsToSql = (config, item) => {
267
422
  const line = [];
268
423
  if (item.dataType) line.push(`AS ${item.dataType}`);
269
424
  if (item.increment !== void 0) line.push(`INCREMENT BY ${item.increment}`);
@@ -273,7 +428,7 @@ const sequenceOptionsToSql = (item) => {
273
428
  if (item.cache !== void 0) line.push(`CACHE ${item.cache}`);
274
429
  if (item.cycle) line.push(`CYCLE`);
275
430
  if (item.ownedBy) {
276
- const [schema, table] = getSchemaAndTableFromName(item.ownedBy);
431
+ const [schema, table] = getSchemaAndTableFromName(config, item.ownedBy);
277
432
  line.push(`OWNED BY ${quoteTable(schema, table)}`);
278
433
  }
279
434
  return line.join(" ");
@@ -303,9 +458,9 @@ const addColumnComment = (comments, name, item) => {
303
458
  comments.push({ column: name, comment: item.data.comment });
304
459
  }
305
460
  };
306
- const getForeignKeyTable = (fnOrTable) => {
461
+ const getForeignKeyTable = (config, fnOrTable) => {
307
462
  if (typeof fnOrTable === "string") {
308
- return getSchemaAndTableFromName(fnOrTable);
463
+ return getSchemaAndTableFromName(config, fnOrTable);
309
464
  }
310
465
  const item = new (fnOrTable())();
311
466
  return [item.schema, item.table];
@@ -322,7 +477,7 @@ const getConstraintName = (table, constraint, snakeCase) => {
322
477
  if (constraint.identity) return `${table}_identity`;
323
478
  return `${table}_constraint`;
324
479
  };
325
- const constraintToSql = ({ name }, up, constraint, values, snakeCase) => {
480
+ const constraintToSql = (config, { name }, up, constraint, values, snakeCase) => {
326
481
  const constraintName = constraint.name || getConstraintName(name, constraint, snakeCase);
327
482
  if (!up) {
328
483
  const { dropMode } = constraint;
@@ -330,7 +485,7 @@ const constraintToSql = ({ name }, up, constraint, values, snakeCase) => {
330
485
  }
331
486
  const sql = [`CONSTRAINT "${constraintName}"`];
332
487
  if (constraint.references) {
333
- sql.push(foreignKeyToSql(constraint.references, snakeCase));
488
+ sql.push(foreignKeyToSql(config, constraint.references, snakeCase));
334
489
  }
335
490
  if (constraint.check) {
336
491
  sql.push(checkToSql(constraint.check, values));
@@ -340,13 +495,13 @@ const constraintToSql = ({ name }, up, constraint, values, snakeCase) => {
340
495
  const checkToSql = (check, values) => {
341
496
  return `CHECK (${check.toSQL({ values })})`;
342
497
  };
343
- const foreignKeyToSql = (item, snakeCase) => {
498
+ const foreignKeyToSql = (config, item, snakeCase) => {
344
499
  return `FOREIGN KEY (${joinColumns(
345
500
  snakeCase ? item.columns.map(toSnakeCase) : item.columns
346
- )}) ${referencesToSql(item, snakeCase)}`;
501
+ )}) ${referencesToSql(config, item, snakeCase)}`;
347
502
  };
348
- const referencesToSql = (references, snakeCase) => {
349
- const [schema, table] = getForeignKeyTable(references.fnOrTable);
503
+ const referencesToSql = (config, references, snakeCase) => {
504
+ const [schema, table] = getForeignKeyTable(config, references.fnOrTable);
350
505
  const sql = [
351
506
  `REFERENCES ${quoteTable(schema, table)}(${joinColumns(
352
507
  snakeCase ? references.foreignColumns.map(toSnakeCase) : references.foreignColumns
@@ -397,7 +552,7 @@ const getIndexOrExcludeName = (table, columns, suffix) => makeConstraintName(
397
552
  );
398
553
  const getIndexName = (table, columns) => getIndexOrExcludeName(table, columns, "idx");
399
554
  const getExcludeName = (table, columns) => getIndexOrExcludeName(table, columns, "exclude");
400
- const indexesToQuery = (up, { schema, name: tableName }, indexes, snakeCase, language) => {
555
+ const indexesToQuery = (config, up, { schema, name: tableName }, indexes, snakeCase, language) => {
401
556
  return indexes.map((index) => {
402
557
  const { options } = index;
403
558
  const { columns, include, name } = getIndexOrExcludeMainOptions(
@@ -426,7 +581,7 @@ const indexesToQuery = (up, { schema, name: tableName }, indexes, snakeCase, lan
426
581
  const columnsSql = columns.map((column) => {
427
582
  let sql2 = [
428
583
  "expression" in column ? `(${column.expression})` : `"${column.column}"`,
429
- column.collate && `COLLATE ${quoteNameFromString(column.collate)}`,
584
+ column.collate && `COLLATE ${quoteNameFromString(config, column.collate)}`,
430
585
  column.opclass,
431
586
  column.order
432
587
  ].filter((x) => !!x).join(" ");
@@ -470,7 +625,7 @@ const indexesToQuery = (up, { schema, name: tableName }, indexes, snakeCase, lan
470
625
  return { text: sql.join(" "), values };
471
626
  });
472
627
  };
473
- const excludesToQuery = (up, { schema, name: tableName }, excludes, snakeCase) => {
628
+ const excludesToQuery = (config, up, { schema, name: tableName }, excludes, snakeCase) => {
474
629
  return excludes.map((exclude) => {
475
630
  const { options } = exclude;
476
631
  const { columns, include, name } = getIndexOrExcludeMainOptions(
@@ -490,7 +645,7 @@ const excludesToQuery = (up, { schema, name: tableName }, excludes, snakeCase) =
490
645
  const columnList = columns.map(
491
646
  (column) => [
492
647
  "expression" in column ? `(${column.expression})` : `"${column.column}"`,
493
- column.collate && `COLLATE ${quoteNameFromString(column.collate)}`,
648
+ column.collate && `COLLATE ${quoteNameFromString(config, column.collate)}`,
494
649
  column.opclass,
495
650
  column.order,
496
651
  `WITH ${column.with}`
@@ -559,9 +714,16 @@ const cmpRawSql = (a, b) => {
559
714
  };
560
715
  const getMigrationsSchemaAndTable = (config) => {
561
716
  const [tableSchema, table] = getSchemaAndTableFromName(
717
+ config,
562
718
  config.migrationsTable
563
719
  );
564
- const schema = tableSchema || (config.schema && config.schema !== "public" ? config.schema : void 0);
720
+ let schema = tableSchema;
721
+ if (!schema) {
722
+ schema = typeof config.schema === "function" ? config.schema() : config.schema;
723
+ if (schema === "public") {
724
+ schema = void 0;
725
+ }
726
+ }
565
727
  return { schema, table };
566
728
  };
567
729
  const migrationsSchemaTableSql = (config) => {
@@ -580,6 +742,11 @@ const tableMethods = {
580
742
  }
581
743
  };
582
744
 
745
+ class RakeDbError extends Error {
746
+ }
747
+ class NoPrimaryKey extends RakeDbError {
748
+ }
749
+
583
750
  const createTable = async (migration, up, tableName, first, second, third) => {
584
751
  let options;
585
752
  let fn;
@@ -618,6 +785,7 @@ const createTable = async (migration, up, tableName, first, second, third) => {
618
785
  shape = tableData = emptyObject;
619
786
  }
620
787
  const ast = makeAst$2(
788
+ migration.options,
621
789
  up,
622
790
  tableName,
623
791
  shape,
@@ -626,7 +794,7 @@ const createTable = async (migration, up, tableName, first, second, third) => {
626
794
  migration.options.noPrimaryKey
627
795
  );
628
796
  fn && validatePrimaryKey(ast);
629
- const queries = astToQueries$1(ast, snakeCase, language);
797
+ const queries = astToQueries$1(migration.options, ast, snakeCase, language);
630
798
  for (const { then, ...query } of queries) {
631
799
  const result = await migration.adapter.arrays(interpolateSqlValues(query));
632
800
  then?.(result);
@@ -646,7 +814,7 @@ const createTable = async (migration, up, tableName, first, second, third) => {
646
814
  }
647
815
  };
648
816
  };
649
- const makeAst$2 = (up, tableName, shape, tableData, options, noPrimaryKey) => {
817
+ const makeAst$2 = (config, up, tableName, shape, tableData, options, noPrimaryKey) => {
650
818
  const shapePKeys = [];
651
819
  for (const key in shape) {
652
820
  const column = shape[key];
@@ -655,7 +823,7 @@ const makeAst$2 = (up, tableName, shape, tableData, options, noPrimaryKey) => {
655
823
  }
656
824
  }
657
825
  const { primaryKey } = tableData;
658
- const [schema, table] = getSchemaAndTableFromName(tableName);
826
+ const [schema, table] = getSchemaAndTableFromName(config, tableName);
659
827
  return {
660
828
  type: "table",
661
829
  action: up ? "create" : "drop",
@@ -695,13 +863,13 @@ You can suppress this error by setting { noPrimaryKey: true } after a table name
695
863
  }
696
864
  }
697
865
  };
698
- const astToQueries$1 = (ast, snakeCase, language) => {
866
+ const astToQueries$1 = (config, ast, snakeCase, language) => {
699
867
  const queries = [];
700
868
  const { shape } = ast;
701
869
  for (const key in shape) {
702
870
  const item = shape[key];
703
871
  if (!(item instanceof EnumColumn)) continue;
704
- queries.push(makePopulateEnumQuery(item));
872
+ queries.push(makePopulateEnumQuery(config, item));
705
873
  }
706
874
  if (ast.action === "drop") {
707
875
  queries.push({
@@ -722,7 +890,14 @@ const astToQueries$1 = (ast, snakeCase, language) => {
722
890
  addColumnComment(comments, name, item);
723
891
  lines.push(
724
892
  `
725
- ${columnToSql(name, item, values, !!ast.primaryKey, snakeCase)}`
893
+ ${columnToSql(
894
+ config,
895
+ name,
896
+ item,
897
+ values,
898
+ !!ast.primaryKey,
899
+ snakeCase
900
+ )}`
726
901
  );
727
902
  }
728
903
  if (ast.primaryKey) {
@@ -740,6 +915,7 @@ const astToQueries$1 = (ast, snakeCase, language) => {
740
915
  lines.push(
741
916
  `
742
917
  ${constraintToSql(
918
+ config,
743
919
  ast,
744
920
  true,
745
921
  {
@@ -764,8 +940,8 @@ const astToQueries$1 = (ast, snakeCase, language) => {
764
940
  )`,
765
941
  values
766
942
  },
767
- ...indexesToQuery(true, ast, indexes, snakeCase, language),
768
- ...excludesToQuery(true, ast, excludes, snakeCase),
943
+ ...indexesToQuery(config, true, ast, indexes, snakeCase, language),
944
+ ...excludesToQuery(config, true, ast, excludes, snakeCase),
769
945
  ...commentsToQuery(ast, comments)
770
946
  );
771
947
  if (ast.comment) {
@@ -987,14 +1163,21 @@ const changeTable = async (migration, up, tableName, options, fn) => {
987
1163
  tableChanger[snakeCaseKey] = snakeCase;
988
1164
  addOrDropChanges.length = 0;
989
1165
  const changeData = fn?.(tableChanger) || {};
990
- const ast = makeAst$1(up, tableName, changeData, changeTableData, options);
991
- const queries = astToQueries(ast, snakeCase, language);
1166
+ const ast = makeAst$1(
1167
+ migration.options,
1168
+ up,
1169
+ tableName,
1170
+ changeData,
1171
+ changeTableData,
1172
+ options
1173
+ );
1174
+ const queries = astToQueries(migration.options, ast, snakeCase, language);
992
1175
  for (const query of queries) {
993
1176
  const result = await migration.adapter.arrays(interpolateSqlValues(query));
994
1177
  query.then?.(result);
995
1178
  }
996
1179
  };
997
- const makeAst$1 = (up, name, changeData, changeTableData2, options) => {
1180
+ const makeAst$1 = (config, up, name, changeData, changeTableData2, options) => {
998
1181
  const { comment } = options;
999
1182
  const shape = {};
1000
1183
  const consumedChanges = {};
@@ -1039,7 +1222,7 @@ const makeAst$1 = (up, name, changeData, changeTableData2, options) => {
1039
1222
  );
1040
1223
  shape[name2] = arr;
1041
1224
  }
1042
- const [schema, table] = getSchemaAndTableFromName(name);
1225
+ const [schema, table] = getSchemaAndTableFromName(config, name);
1043
1226
  return {
1044
1227
  type: "changeTable",
1045
1228
  schema,
@@ -1049,7 +1232,7 @@ const makeAst$1 = (up, name, changeData, changeTableData2, options) => {
1049
1232
  ...up ? changeTableData2 : { add: changeTableData2.drop, drop: changeTableData2.add }
1050
1233
  };
1051
1234
  };
1052
- const astToQueries = (ast, snakeCase, language) => {
1235
+ const astToQueries = (config, ast, snakeCase, language) => {
1053
1236
  const queries = [];
1054
1237
  if (ast.comment !== void 0) {
1055
1238
  queries.push({
@@ -1069,6 +1252,7 @@ const astToQueries = (ast, snakeCase, language) => {
1069
1252
  if (Array.isArray(item)) {
1070
1253
  for (const it of item) {
1071
1254
  handlePrerequisitesForTableItem(
1255
+ config,
1072
1256
  key,
1073
1257
  it,
1074
1258
  queries,
@@ -1079,6 +1263,7 @@ const astToQueries = (ast, snakeCase, language) => {
1079
1263
  }
1080
1264
  } else {
1081
1265
  handlePrerequisitesForTableItem(
1266
+ config,
1082
1267
  key,
1083
1268
  item,
1084
1269
  queries,
@@ -1117,6 +1302,7 @@ const astToQueries = (ast, snakeCase, language) => {
1117
1302
  if (Array.isArray(item)) {
1118
1303
  for (const it of item) {
1119
1304
  handleTableItemChange(
1305
+ config,
1120
1306
  key,
1121
1307
  it,
1122
1308
  ast,
@@ -1136,6 +1322,7 @@ const astToQueries = (ast, snakeCase, language) => {
1136
1322
  }
1137
1323
  } else {
1138
1324
  handleTableItemChange(
1325
+ config,
1139
1326
  key,
1140
1327
  item,
1141
1328
  ast,
@@ -1168,7 +1355,14 @@ const astToQueries = (ast, snakeCase, language) => {
1168
1355
  prependAlterTable.push(
1169
1356
  ...dropConstraints.map(
1170
1357
  (foreignKey) => `
1171
- DROP ${constraintToSql(ast, false, foreignKey, values, snakeCase)}`
1358
+ DROP ${constraintToSql(
1359
+ config,
1360
+ ast,
1361
+ false,
1362
+ foreignKey,
1363
+ values,
1364
+ snakeCase
1365
+ )}`
1172
1366
  )
1173
1367
  );
1174
1368
  alterTable.unshift(...prependAlterTable);
@@ -1186,7 +1380,14 @@ const astToQueries = (ast, snakeCase, language) => {
1186
1380
  alterTable.push(
1187
1381
  ...addConstraints.map(
1188
1382
  (foreignKey) => `
1189
- ADD ${constraintToSql(ast, true, foreignKey, values, snakeCase)}`
1383
+ ADD ${constraintToSql(
1384
+ config,
1385
+ ast,
1386
+ true,
1387
+ foreignKey,
1388
+ values,
1389
+ snakeCase
1390
+ )}`
1190
1391
  )
1191
1392
  );
1192
1393
  const tableName = quoteWithSchema(ast);
@@ -1202,10 +1403,14 @@ const astToQueries = (ast, snakeCase, language) => {
1202
1403
  if (alterTable.length) {
1203
1404
  queries.push(alterTableSql(tableName, alterTable, values));
1204
1405
  }
1205
- queries.push(...indexesToQuery(false, ast, dropIndexes, snakeCase, language));
1206
- queries.push(...indexesToQuery(true, ast, addIndexes, snakeCase, language));
1207
- queries.push(...excludesToQuery(false, ast, dropExcludes, snakeCase));
1208
- queries.push(...excludesToQuery(true, ast, addExcludes, snakeCase));
1406
+ queries.push(
1407
+ ...indexesToQuery(config, false, ast, dropIndexes, snakeCase, language)
1408
+ );
1409
+ queries.push(
1410
+ ...indexesToQuery(config, true, ast, addIndexes, snakeCase, language)
1411
+ );
1412
+ queries.push(...excludesToQuery(config, false, ast, dropExcludes, snakeCase));
1413
+ queries.push(...excludesToQuery(config, true, ast, addExcludes, snakeCase));
1209
1414
  queries.push(...commentsToQuery(ast, comments));
1210
1415
  return queries;
1211
1416
  };
@@ -1214,11 +1419,11 @@ const alterTableSql = (tableName, lines, values) => ({
1214
1419
  ${lines.join(",\n ")}`,
1215
1420
  values
1216
1421
  });
1217
- const handlePrerequisitesForTableItem = (key, item, queries, addPrimaryKeys, dropPrimaryKeys, snakeCase) => {
1422
+ const handlePrerequisitesForTableItem = (config, key, item, queries, addPrimaryKeys, dropPrimaryKeys, snakeCase) => {
1218
1423
  if ("item" in item) {
1219
1424
  const { item: column } = item;
1220
1425
  if (column instanceof EnumColumn) {
1221
- queries.push(makePopulateEnumQuery(column));
1426
+ queries.push(makePopulateEnumQuery(config, column));
1222
1427
  }
1223
1428
  }
1224
1429
  if (item.type === "add") {
@@ -1231,10 +1436,10 @@ const handlePrerequisitesForTableItem = (key, item, queries, addPrimaryKeys, dro
1231
1436
  }
1232
1437
  } else if (item.type === "change") {
1233
1438
  if (item.from.column instanceof EnumColumn) {
1234
- queries.push(makePopulateEnumQuery(item.from.column));
1439
+ queries.push(makePopulateEnumQuery(config, item.from.column));
1235
1440
  }
1236
1441
  if (item.to.column instanceof EnumColumn) {
1237
- queries.push(makePopulateEnumQuery(item.to.column));
1442
+ queries.push(makePopulateEnumQuery(config, item.to.column));
1238
1443
  }
1239
1444
  if (item.from.primaryKey) {
1240
1445
  dropPrimaryKeys.columns.push(
@@ -1250,7 +1455,7 @@ const handlePrerequisitesForTableItem = (key, item, queries, addPrimaryKeys, dro
1250
1455
  }
1251
1456
  }
1252
1457
  };
1253
- const handleTableItemChange = (key, item, ast, alterTable, renameItems, values, addPrimaryKeys, addIndexes, dropIndexes, addExcludes, dropExcludes, addConstraints, dropConstraints, comments, snakeCase) => {
1458
+ const handleTableItemChange = (config, key, item, ast, alterTable, renameItems, values, addPrimaryKeys, addIndexes, dropIndexes, addExcludes, dropExcludes, addConstraints, dropConstraints, comments, snakeCase) => {
1254
1459
  if (item.type === "add") {
1255
1460
  const column = item.item;
1256
1461
  const name = getColumnName(column, key, snakeCase);
@@ -1259,6 +1464,7 @@ const handleTableItemChange = (key, item, ast, alterTable, renameItems, values,
1259
1464
  addColumnComment(comments, name, column);
1260
1465
  alterTable.push(
1261
1466
  `ADD COLUMN ${columnToSql(
1467
+ config,
1262
1468
  name,
1263
1469
  column,
1264
1470
  values,
@@ -1281,10 +1487,10 @@ const handleTableItemChange = (key, item, ast, alterTable, renameItems, values,
1281
1487
  let changeType = false;
1282
1488
  if (to.type && (from.type !== to.type || from.collate !== to.collate)) {
1283
1489
  changeType = true;
1284
- const type = !to.column || to.column.data.isOfCustomType ? to.column && to.column instanceof DomainColumn ? quoteNameFromString(to.type) : quoteCustomType(to.type) : to.type;
1490
+ const type = !to.column || to.column.data.isOfCustomType ? to.column && to.column instanceof DomainColumn ? quoteNameFromString(config, to.type) : quoteCustomType(config, to.type) : to.type;
1285
1491
  const using = item.using?.usingUp ? ` USING ${item.using.usingUp.toSQL({ values })}` : to.column instanceof EnumColumn ? ` USING "${name}"::text::${type}` : to.column instanceof ArrayColumn ? ` USING "${name}"::text[]::${type}` : "";
1286
1492
  alterTable.push(
1287
- `ALTER COLUMN "${name}" TYPE ${type}${to.collate ? ` COLLATE ${quoteNameFromString(to.collate)}` : ""}${using}`
1493
+ `ALTER COLUMN "${name}" TYPE ${type}${to.collate ? ` COLLATE ${quoteNameFromString(config, to.collate)}` : ""}${using}`
1288
1494
  );
1289
1495
  }
1290
1496
  if (typeof from.identity !== typeof to.identity || !deepCompare(from.identity, to.identity)) {
@@ -1293,7 +1499,7 @@ const handleTableItemChange = (key, item, ast, alterTable, renameItems, values,
1293
1499
  }
1294
1500
  if (to.identity) {
1295
1501
  alterTable.push(
1296
- `ALTER COLUMN "${name}" ADD ${identityToSql(to.identity)}`
1502
+ `ALTER COLUMN "${name}" ADD ${identityToSql(config, to.identity)}`
1297
1503
  );
1298
1504
  }
1299
1505
  }
@@ -1426,15 +1632,15 @@ const renameColumnSql = (from, to) => {
1426
1632
  };
1427
1633
 
1428
1634
  const createView = async (migration, up, name, options, sql) => {
1429
- const ast = makeAst(up, name, options, sql);
1635
+ const ast = makeAst(migration.options, up, name, options, sql);
1430
1636
  const query = astToQuery(ast);
1431
1637
  await migration.adapter.arrays(interpolateSqlValues(query));
1432
1638
  };
1433
- const makeAst = (up, fullName, options, sql) => {
1639
+ const makeAst = (config, up, fullName, options, sql) => {
1434
1640
  if (typeof sql === "string") {
1435
1641
  sql = raw({ raw: sql });
1436
1642
  }
1437
- const [schema, name] = getSchemaAndTableFromName(fullName);
1643
+ const [schema, name] = getSchemaAndTableFromName(config, fullName);
1438
1644
  return {
1439
1645
  type: "view",
1440
1646
  action: up ? "create" : "drop",
@@ -2026,7 +2232,7 @@ class Migration {
2026
2232
  * @param values - object where keys are for old names, values are for new names
2027
2233
  */
2028
2234
  async renameEnumValues(enumName, values) {
2029
- const [schema, name] = getSchemaAndTableFromName(enumName);
2235
+ const [schema, name] = getSchemaAndTableFromName(this.options, enumName);
2030
2236
  const ast = {
2031
2237
  type: "renameEnumValues",
2032
2238
  schema,
@@ -2290,9 +2496,16 @@ class Migration {
2290
2496
  * @param tableName - name of the table
2291
2497
  */
2292
2498
  async tableExists(tableName) {
2499
+ let text = `SELECT 1 FROM "information_schema"."tables" WHERE "table_name" = $1`;
2500
+ const [schema, table] = getSchemaAndTableFromName(this.options, tableName);
2501
+ const values = [table];
2502
+ if (schema) {
2503
+ text += ' AND "table_schema" = $2';
2504
+ values.push(schema);
2505
+ }
2293
2506
  return queryExists(this, {
2294
- text: `SELECT 1 FROM "information_schema"."tables" WHERE "table_name" = $1`,
2295
- values: [tableName]
2507
+ text,
2508
+ values
2296
2509
  });
2297
2510
  }
2298
2511
  /**
@@ -2314,12 +2527,19 @@ class Migration {
2314
2527
  * @param columnName - name of the column
2315
2528
  */
2316
2529
  async columnExists(tableName, columnName) {
2530
+ let text = `SELECT 1 FROM "information_schema"."columns" WHERE "table_name" = $1 AND "column_name" = $2`;
2531
+ const [schema, table] = getSchemaAndTableFromName(this.options, tableName);
2532
+ const values = [
2533
+ table,
2534
+ this.options.snakeCase ? toSnakeCase(columnName) : columnName
2535
+ ];
2536
+ if (schema) {
2537
+ text += ' AND "table_schema" = $3';
2538
+ values.push(schema);
2539
+ }
2317
2540
  return queryExists(this, {
2318
- text: `SELECT 1 FROM "information_schema"."columns" WHERE "table_name" = $1 AND "column_name" = $2`,
2319
- values: [
2320
- tableName,
2321
- this.options.snakeCase ? toSnakeCase(columnName) : columnName
2322
- ]
2541
+ text,
2542
+ values
2323
2543
  });
2324
2544
  }
2325
2545
  /**
@@ -2399,7 +2619,7 @@ const createSchema = async (migration, up, name) => {
2399
2619
  );
2400
2620
  };
2401
2621
  const createExtension = async (migration, up, fullName, options) => {
2402
- const [schema, name] = getSchemaAndTableFromName(fullName);
2622
+ const [schema, name] = getSchemaAndTableFromName(migration.options, fullName);
2403
2623
  const ast = {
2404
2624
  type: "extension",
2405
2625
  action: up ? "create" : "drop",
@@ -2416,7 +2636,7 @@ const createExtension = async (migration, up, fullName, options) => {
2416
2636
  await migration.adapter.query(query);
2417
2637
  };
2418
2638
  const createEnum = async (migration, up, name, values, options = {}) => {
2419
- const [schema, enumName] = getSchemaAndTableFromName(name);
2639
+ const [schema, enumName] = getSchemaAndTableFromName(migration.options, name);
2420
2640
  const ast = {
2421
2641
  type: "enum",
2422
2642
  action: up ? "create" : "drop",
@@ -2435,7 +2655,10 @@ const createEnum = async (migration, up, name, values, options = {}) => {
2435
2655
  await migration.adapter.query(query);
2436
2656
  };
2437
2657
  const createDomain = async (migration, up, name, fn) => {
2438
- const [schema, domainName] = getSchemaAndTableFromName(name);
2658
+ const [schema, domainName] = getSchemaAndTableFromName(
2659
+ migration.options,
2660
+ name
2661
+ );
2439
2662
  const ast = {
2440
2663
  type: "domain",
2441
2664
  action: up ? "create" : "drop",
@@ -2448,7 +2671,10 @@ const createDomain = async (migration, up, name, fn) => {
2448
2671
  const quotedName = quoteWithSchema(ast);
2449
2672
  if (ast.action === "create") {
2450
2673
  const column = ast.baseType;
2451
- query = `CREATE DOMAIN ${quotedName} AS ${columnTypeToSql(column)}${column.data.collate ? `
2674
+ query = `CREATE DOMAIN ${quotedName} AS ${columnTypeToSql(
2675
+ migration.options,
2676
+ column
2677
+ )}${column.data.collate ? `
2452
2678
  COLLATE "${column.data.collate}"` : ""}${column.data.default !== void 0 ? `
2453
2679
  DEFAULT ${encodeColumnDefault(column.data.default, values)}` : ""}${!column.data.isNullable || column.data.checks ? "\n" : ""}${[
2454
2680
  !column.data.isNullable && "NOT NULL",
@@ -2465,7 +2691,10 @@ DEFAULT ${encodeColumnDefault(column.data.default, values)}` : ""}${!column.data
2465
2691
  );
2466
2692
  };
2467
2693
  const createCollation = async (migration, up, name, options) => {
2468
- const [schema, collationName] = getSchemaAndTableFromName(name);
2694
+ const [schema, collationName] = getSchemaAndTableFromName(
2695
+ migration.options,
2696
+ name
2697
+ );
2469
2698
  const ast = {
2470
2699
  type: "collation",
2471
2700
  action: up ? "create" : "drop",
@@ -2478,7 +2707,10 @@ const createCollation = async (migration, up, name, options) => {
2478
2707
  if (ast.action === "create") {
2479
2708
  query = `CREATE COLLATION${ast.createIfNotExists ? " IF NOT EXISTS" : ""} ${quotedName} `;
2480
2709
  if (ast.fromExisting) {
2481
- query += `FROM ${quoteNameFromString(ast.fromExisting)}`;
2710
+ query += `FROM ${quoteNameFromString(
2711
+ migration.options,
2712
+ ast.fromExisting
2713
+ )}`;
2482
2714
  } else {
2483
2715
  const config = [];
2484
2716
  if (ast.locale) config.push(`locale = '${ast.locale}'`);
@@ -2501,8 +2733,14 @@ const queryExists = (db, sql) => {
2501
2733
  return db.adapter.query(sql.text, sql.values).then(({ rowCount }) => rowCount > 0);
2502
2734
  };
2503
2735
  const renameType = async (migration, from, to, kind) => {
2504
- const [fromSchema, f] = getSchemaAndTableFromName(migration.up ? from : to);
2505
- const [toSchema, t] = getSchemaAndTableFromName(migration.up ? to : from);
2736
+ const [fromSchema, f] = getSchemaAndTableFromName(
2737
+ migration.options,
2738
+ migration.up ? from : to
2739
+ );
2740
+ const [toSchema, t] = getSchemaAndTableFromName(
2741
+ migration.options,
2742
+ migration.up ? to : from
2743
+ );
2506
2744
  const ast = {
2507
2745
  type: "renameType",
2508
2746
  kind,
@@ -2523,7 +2761,10 @@ const renameType = async (migration, from, to, kind) => {
2523
2761
  }
2524
2762
  };
2525
2763
  const renameTableItem = async (migration, tableName, from, to, kind) => {
2526
- const [schema, table] = getSchemaAndTableFromName(tableName);
2764
+ const [schema, table] = getSchemaAndTableFromName(
2765
+ migration.options,
2766
+ tableName
2767
+ );
2527
2768
  const [f, t] = migration.up ? [from, to] : [to, from];
2528
2769
  await migration.adapter.query(
2529
2770
  kind === "INDEX" ? `ALTER INDEX ${quoteTable(schema, f)} RENAME TO "${t}"` : `ALTER TABLE ${quoteTable(
@@ -2533,7 +2774,7 @@ const renameTableItem = async (migration, tableName, from, to, kind) => {
2533
2774
  );
2534
2775
  };
2535
2776
  const addOrDropEnumValues = async (migration, up, enumName, values, options) => {
2536
- const [schema, name] = getSchemaAndTableFromName(enumName);
2777
+ const [schema, name] = getSchemaAndTableFromName(migration.options, enumName);
2537
2778
  const quotedName = quoteTable(schema, name);
2538
2779
  const ast = {
2539
2780
  type: "enumValues",
@@ -2569,7 +2810,7 @@ const addOrDropEnumValues = async (migration, up, enumName, values, options) =>
2569
2810
  );
2570
2811
  };
2571
2812
  const changeEnumValues = async (migration, enumName, fromValues, toValues) => {
2572
- const [schema, name] = getSchemaAndTableFromName(enumName);
2813
+ const [schema, name] = getSchemaAndTableFromName(migration.options, enumName);
2573
2814
  if (!migration.up) {
2574
2815
  const values = fromValues;
2575
2816
  fromValues = toValues;
@@ -2592,7 +2833,8 @@ const changeEnumValues = async (migration, enumName, fromValues, toValues) => {
2592
2833
  );
2593
2834
  };
2594
2835
  const recreateEnum = async (migration, { schema, name }, values, errorMessage) => {
2595
- const defaultSchema = migration.options.schema ?? "public";
2836
+ const configSchema = migration.options.schema;
2837
+ const defaultSchema = (typeof configSchema === "function" ? configSchema() : void 0) ?? "public";
2596
2838
  const quotedName = quoteTable(schema, name);
2597
2839
  const relKinds = ["r", "m"];
2598
2840
  const { rows: tables } = await migration.adapter.query(
@@ -2662,8 +2904,7 @@ ${migrationCode}`
2662
2904
  );
2663
2905
  config.logger?.log(`Created ${pathToLog(filePath)}`);
2664
2906
  };
2665
- const newMigration = async (config, [name]) => {
2666
- if (!name) throw new Error("Migration name is missing");
2907
+ const newMigration = async (config, name) => {
2667
2908
  const version = await makeFileVersion({}, config);
2668
2909
  await writeMigrationFile(config, version, name, makeContent(name));
2669
2910
  };
@@ -2719,16 +2960,9 @@ const fileNamesToChangeMigrationId = {
2719
2960
  timestamp: ".rename-to-timestamp.json"
2720
2961
  };
2721
2962
  const fileNamesToChangeMigrationIdMap = Object.fromEntries(
2722
- Object.entries(fileNamesToChangeMigrationId).map(([_, name]) => [name, true])
2963
+ Object.entries(fileNamesToChangeMigrationId).map(([, name]) => [name, true])
2723
2964
  );
2724
- const changeIds = async (adapters, config, [arg, digitsArg]) => {
2725
- if (arg !== "serial" && arg !== "timestamp") {
2726
- throw new Error(
2727
- `Pass "serial" or "timestamp" argument to the "change-ids" command`
2728
- );
2729
- }
2730
- let digits = digitsArg && parseInt(digitsArg);
2731
- if (!digits || isNaN(digits)) digits = 4;
2965
+ const changeIds = async (adapters, config, { format, digits = 4 }) => {
2732
2966
  const data = await getMigrations({}, config, true, false, (_, filePath) => {
2733
2967
  const fileName = path.basename(filePath);
2734
2968
  const match = fileName.match(/^(\d+)\D/);
@@ -2740,9 +2974,9 @@ const changeIds = async (adapters, config, [arg, digitsArg]) => {
2740
2974
  return match[1];
2741
2975
  });
2742
2976
  if (data.renameTo) {
2743
- if (arg === "serial" && typeof data.renameTo.to === "object" && digits === data.renameTo.to.serial || arg === "timestamp" && data.renameTo.to === "timestamp") {
2977
+ if (format === "serial" && typeof data.renameTo.to === "object" && digits === data.renameTo.to.serial || format === "timestamp" && data.renameTo.to === "timestamp") {
2744
2978
  config.logger?.log(
2745
- config.migrations ? "`renameMigrations` setting is already set" : `${fileNamesToChangeMigrationId[arg]} already exists`
2979
+ config.migrations ? "`renameMigrations` setting is already set" : `${fileNamesToChangeMigrationId[format]} already exists`
2746
2980
  );
2747
2981
  return;
2748
2982
  }
@@ -2755,15 +2989,15 @@ const changeIds = async (adapters, config, [arg, digitsArg]) => {
2755
2989
  );
2756
2990
  }
2757
2991
  }
2758
- const version = arg === "timestamp" ? parseInt(generateTimeStamp()) : 1;
2992
+ const version = format === "timestamp" ? parseInt(generateTimeStamp()) : 1;
2759
2993
  const rename = Object.fromEntries(
2760
2994
  data.migrations.map((item, i) => [path.basename(item.path), version + i])
2761
2995
  );
2762
2996
  if (config.migrations) {
2763
- const to = arg === "timestamp" ? `'${arg}'` : `{ serial: ${digits} }`;
2997
+ const to = format === "timestamp" ? `'${format}'` : `{ serial: ${digits} }`;
2764
2998
  config.logger?.log(
2765
2999
  `Save the following settings into your rake-db config under the \`migrations\` setting, it will instruct rake-db to rename migration entries during the next deploy:
2766
- ${arg !== "serial" || digits !== 4 ? `
3000
+ ${format !== "serial" || digits !== 4 ? `
2767
3001
  migrationId: ${to},` : ""}
2768
3002
  renameMigrations: {
2769
3003
  to: ${to},
@@ -2772,14 +3006,14 @@ renameMigrations: {
2772
3006
  );
2773
3007
  } else {
2774
3008
  await fs.writeFile(
2775
- path.join(config.migrationsPath, fileNamesToChangeMigrationId[arg]),
3009
+ path.join(config.migrationsPath, fileNamesToChangeMigrationId[format]),
2776
3010
  JSON.stringify(rename, null, 2)
2777
3011
  );
2778
3012
  }
2779
3013
  const values = data.migrations.map(
2780
3014
  (item, i) => {
2781
3015
  let newVersion = String(version + i);
2782
- if (arg === "serial") newVersion = newVersion.padStart(digits, "0");
3016
+ if (format === "serial") newVersion = newVersion.padStart(digits, "0");
2783
3017
  const name = path.basename(item.path).slice(item.version.length + 1);
2784
3018
  return [item.version, name, newVersion];
2785
3019
  }
@@ -2816,9 +3050,9 @@ After setting \`renameMigrations\` (see above) and renaming the files, run the d
2816
3050
  })
2817
3051
  );
2818
3052
  config.logger?.log(
2819
- `Migration files were renamed, a config file ${fileNamesToChangeMigrationId[arg]} for renaming migrations after deploy was created, and migrations in local db were renamed successfully.
3053
+ `Migration files were renamed, a config file ${fileNamesToChangeMigrationId[format]} for renaming migrations after deploy was created, and migrations in local db were renamed successfully.
2820
3054
 
2821
- ${arg === "timestamp" || digits !== 4 ? `Set \`migrationId\`: ${arg === "timestamp" ? `'timestamp'` : `{ serial: ${digits} }`}` : `Remove \`migrationId\``} setting in the rake-db config`
3055
+ ${format === "timestamp" || digits !== 4 ? `Set \`migrationId\`: ${format === "timestamp" ? `'timestamp'` : `{ serial: ${digits} }`}` : `Remove \`migrationId\``} setting in the rake-db config`
2822
3056
  );
2823
3057
  };
2824
3058
  const renameMigrationVersionsInDb = async (config, adapter, values) => {
@@ -2996,35 +3230,26 @@ const saveMigratedVersion = async (db, version, name, config) => {
2996
3230
  [version, name]
2997
3231
  );
2998
3232
  };
2999
- const createMigrationsTable = async (db, config) => {
3000
- const { schema } = getMigrationsSchemaAndTable(config);
3233
+ const createMigrationsSchemaAndTable = async (adapter, config) => {
3234
+ const { schema, table } = getMigrationsSchemaAndTable(config);
3001
3235
  if (schema) {
3002
- try {
3003
- await db.query(`CREATE SCHEMA "${schema}"`);
3004
- config.logger?.log(`Created schema ${schema}`);
3005
- } catch (err) {
3006
- if (err.code !== "42P06") {
3007
- throw err;
3008
- }
3236
+ const res2 = await createSchema$1(adapter, schema);
3237
+ if (res2 === "done") {
3238
+ config.logger?.log(`Created schema "${schema}"`);
3009
3239
  }
3010
3240
  }
3011
- try {
3012
- await db.query(
3013
- `CREATE TABLE ${migrationsSchemaTableSql(
3014
- config
3015
- )} ( version TEXT NOT NULL, name TEXT NOT NULL )`
3241
+ const res = await createTable$1(
3242
+ adapter,
3243
+ `${schema ? `"${schema}"."${table}"` : `"${table}"`} (version TEXT NOT NULL, name TEXT NOT NULL)`
3244
+ );
3245
+ if (res === "done") {
3246
+ config.logger?.log(
3247
+ `Created migration versions table ${schema ? `"${schema}".` : ""}"${table}"`
3016
3248
  );
3017
- config.logger?.log("Created versions table");
3018
- } catch (err) {
3019
- if (err.code === "42P07") {
3020
- config.logger?.log("Versions table exists");
3021
- } else {
3022
- throw err;
3023
- }
3024
3249
  }
3025
3250
  };
3026
- const deleteMigratedVersion = async (db, version, name, config) => {
3027
- const res = await db.silentArrays(
3251
+ const deleteMigratedVersion = async (adapter, version, name, config) => {
3252
+ const res = await adapter.silentArrays(
3028
3253
  `DELETE FROM ${migrationsSchemaTableSql(
3029
3254
  config
3030
3255
  )} WHERE version = $1 AND name = $2`,
@@ -3114,91 +3339,76 @@ async function renameMigrations(config, trx, versions, renameTo) {
3114
3339
  return updatedVersions;
3115
3340
  }
3116
3341
 
3117
- const transactionIfSingle = (params, fn) => {
3118
- return params.config.transaction === "single" ? transaction(params.adapter, fn) : fn(params.adapter);
3119
- };
3120
- function makeMigrateFn(up, fn) {
3121
- return async (params) => {
3122
- const ctx = params.ctx || {};
3123
- const set = await getMigrations(ctx, params.config, up);
3124
- const count = params.count ?? Infinity;
3125
- const force = params.force ?? false;
3342
+ const transactionIfSingle = (adapter, config, fn) => {
3343
+ return !adapter.isInTransaction() && config.transaction === "single" ? transaction(adapter, fn) : fn(adapter);
3344
+ };
3345
+ function makeMigrateFn(up, defaultCount, fn) {
3346
+ return async (db, config, params) => {
3347
+ const ctx = params?.ctx || {};
3348
+ const set = await getMigrations(ctx, config, up);
3349
+ const count = params?.count ?? defaultCount;
3350
+ const force = params?.force ?? false;
3351
+ const adapter = getMaybeTransactionAdapter(db);
3126
3352
  let migrations;
3127
3353
  try {
3128
- await transactionIfSingle(params, async (trx) => {
3354
+ await transactionIfSingle(adapter, config, async (trx) => {
3129
3355
  const versions = await getMigratedVersionsMap(
3130
3356
  ctx,
3131
3357
  trx,
3132
- params.config,
3358
+ config,
3133
3359
  set.renameTo
3134
3360
  );
3135
- migrations = await fn(trx, params.config, set, versions, count, force);
3361
+ migrations = await fn(trx, config, set, versions, count, force);
3136
3362
  });
3137
3363
  } catch (err) {
3138
3364
  if (err instanceof NoMigrationsTableError) {
3139
- await transactionIfSingle(params, async (trx) => {
3140
- await createMigrationsTable(trx, params.config);
3365
+ await transactionIfSingle(adapter, config, async (trx) => {
3366
+ await createMigrationsSchemaAndTable(trx, config);
3141
3367
  const versions = await getMigratedVersionsMap(
3142
3368
  ctx,
3143
3369
  trx,
3144
- params.config,
3370
+ config,
3145
3371
  set.renameTo
3146
3372
  );
3147
- migrations = await fn(
3148
- trx,
3149
- params.config,
3150
- set,
3151
- versions,
3152
- count,
3153
- force
3154
- );
3373
+ migrations = await fn(trx, config, set, versions, count, force);
3155
3374
  });
3156
3375
  } else {
3157
3376
  throw err;
3158
3377
  }
3159
3378
  }
3160
- params.config.afterChangeCommit?.({
3161
- adapter: params.adapter,
3379
+ config.afterChangeCommit?.({
3380
+ adapter,
3162
3381
  up,
3163
3382
  migrations
3164
3383
  });
3165
3384
  };
3166
3385
  }
3167
- const makeMigrateCommand = (migrateFn, defaultCount) => {
3168
- return async (adapters, config, args) => {
3169
- const arg = args[0];
3170
- let force = arg === "force";
3171
- let count = defaultCount;
3172
- if (arg === "force") {
3173
- force = true;
3174
- } else {
3175
- force = false;
3176
- const num = arg === "all" ? Infinity : parseInt(arg || "");
3177
- if (!isNaN(num)) {
3178
- count = num;
3179
- }
3180
- }
3181
- for (const adapter of adapters) {
3182
- await migrateFn({ ctx: {}, adapter, config, count, force });
3183
- }
3184
- };
3185
- };
3186
3386
  const migrate = makeMigrateFn(
3187
3387
  true,
3388
+ Infinity,
3188
3389
  (trx, config, set, versions, count, force) => migrateOrRollback(trx, config, set, versions, count, true, false, force)
3189
3390
  );
3190
- const migrateAndClose = async (params) => {
3191
- await migrate(params);
3192
- await params.adapter.close();
3391
+ const migrateAndClose = async (db, config, params) => {
3392
+ const adapter = getMaybeTransactionAdapter(db);
3393
+ await migrate(adapter, config, params);
3394
+ await adapter.close();
3395
+ };
3396
+ const runMigration = async (db, migration) => {
3397
+ await ensureTransaction(db, async (trx) => {
3398
+ clearChanges();
3399
+ const changes = await getChanges({ load: migration });
3400
+ const config = changes[0]?.config;
3401
+ await applyMigration(trx, true, changes, config);
3402
+ });
3193
3403
  };
3194
- const migrateCommand = makeMigrateCommand(migrate, Infinity);
3195
3404
  const rollback = makeMigrateFn(
3196
3405
  false,
3406
+ 1,
3197
3407
  (trx, config, set, versions, count, force) => migrateOrRollback(trx, config, set, versions, count, false, false, force)
3198
3408
  );
3199
- const rollbackCommand = makeMigrateCommand(rollback, 1);
3200
3409
  const redo = makeMigrateFn(
3201
3410
  true,
3411
+ 1,
3202
3412
  async (trx, config, set, versions, count, force) => {
3203
3413
  set.migrations.reverse();
3204
3414
  await migrateOrRollback(trx, config, set, versions, count, false, true);
@@ -3216,7 +3426,6 @@ const redo = makeMigrateFn(
3216
3426
  );
3217
3427
  }
3218
3428
  );
3219
- const redoCommand = makeMigrateCommand(redo, 1);
3220
3429
  const getDb = (adapter) => createDbWithAdapter({ adapter });
3221
3430
  const migrateOrRollback = async (trx, config, set, versions, count, up, redo2, force, skipLock) => {
3222
3431
  const { sequence, map: versionsMap } = versions;
@@ -3255,7 +3464,7 @@ const migrateOrRollback = async (trx, config, set, versions, count, up, redo2, f
3255
3464
  }
3256
3465
  let loggedAboutStarting = false;
3257
3466
  let migrations;
3258
- const migrationRunner = config.transaction === "single" ? runMigration : runMigrationInOwnTransaction;
3467
+ const migrationRunner = trx.isInTransaction() || config.transaction === "single" ? applyMigration : runMigrationInOwnTransaction;
3259
3468
  for (const file of set.migrations) {
3260
3469
  if (up && versionsMap[file.version] || !up && !versionsMap[file.version]) {
3261
3470
  continue;
@@ -3323,347 +3532,53 @@ const checkMigrationOrder = (config, set, { sequence, map }, force) => {
3323
3532
  `Cannot migrate ${path.basename(
3324
3533
  file.path
3325
3534
  )} because the higher position ${map[versionToString(config, last)]} was already migrated.
3326
- Run \`**db command** up force\` to rollback the above migrations and migrate all`
3327
- );
3328
- }
3329
- return version;
3330
- }
3331
- }
3332
- return;
3333
- };
3334
- const changeCache = {};
3335
- const getChanges = async (file, config) => {
3336
- clearChanges();
3337
- let changes = file.path ? changeCache[file.path] : void 0;
3338
- if (!changes) {
3339
- const module = await file.load();
3340
- const exported = module?.default && toArray(module.default);
3341
- if (config?.forceDefaultExports && !exported) {
3342
- throw new RakeDbError(
3343
- `Missing a default export in ${file.path} migration`
3344
- );
3345
- }
3346
- changes = exported || getCurrentChanges();
3347
- if (file.path) changeCache[file.path] = changes;
3348
- }
3349
- return changes;
3350
- };
3351
- const runMigrationInOwnTransaction = (adapter, ...rest) => {
3352
- return transaction(adapter, (trx) => runMigration(trx, ...rest));
3353
- };
3354
- const runMigration = async (trx, up, changes, config) => {
3355
- const db = createMigrationInterface(trx, up, config);
3356
- if (changes.length) {
3357
- const from = up ? 0 : changes.length - 1;
3358
- const to = up ? changes.length : -1;
3359
- const step = up ? 1 : -1;
3360
- for (let i = from; i !== to; i += step) {
3361
- await changes[i].fn(db, up);
3362
- }
3363
- }
3364
- return db.adapter;
3365
- };
3366
- const changeMigratedVersion = async (adapter, up, file, config) => {
3367
- await (up ? saveMigratedVersion : deleteMigratedVersion)(
3368
- adapter,
3369
- file.version,
3370
- path.basename(file.path).slice(file.version.length + 1),
3371
- config
3372
- );
3373
- };
3374
-
3375
- const ESC = "\x1B";
3376
- const CSI = `${ESC}[`;
3377
- const cursorShow = `${CSI}?25h`;
3378
- const cursorHide = `${CSI}?25l`;
3379
- const { stdin, stdout } = process;
3380
- const visibleChars = (s) => s.replace(
3381
- // eslint-disable-next-line no-control-regex
3382
- /[\u001B\u009B][[\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\d/#&.:=?%@~_]+)*|[a-zA-Z\d]+(?:;[-a-zA-Z\d/#&.:=?%@~_]*)*)?\u0007)|(?:(?:\d{1,4}(?:;\d{0,4})*)?[\dA-PRZcf-ntqry=><~]))/g,
3383
- ""
3384
- ).length;
3385
- const clear = (text) => {
3386
- const rows = text.split(/\r?\n/).reduce(
3387
- (rows2, line) => rows2 + 1 + Math.floor(Math.max(visibleChars(line) - 1, 0) / stdout.columns),
3388
- 0
3389
- );
3390
- let clear2 = "";
3391
- for (let i = 0; i < rows; i++) {
3392
- clear2 += `${CSI}2K`;
3393
- if (i < rows - 1) {
3394
- clear2 += `${CSI}${i < rows - 1 ? "1A" : "G"}`;
3395
- }
3396
- }
3397
- return clear2;
3398
- };
3399
- const prompt = async ({
3400
- render,
3401
- onKeyPress,
3402
- validate,
3403
- value,
3404
- cursor: showCursor
3405
- }) => {
3406
- stdin.resume();
3407
- if (stdin.isTTY) stdin.setRawMode(true);
3408
- stdin.setEncoding("utf-8");
3409
- if (!showCursor) stdout.write(cursorHide);
3410
- return new Promise((res) => {
3411
- let prevText;
3412
- const ctx = {
3413
- value,
3414
- submitted: false,
3415
- render() {
3416
- let text = (ctx.submitted ? colors.greenBold("\u2714") : colors.yellowBold("?")) + " " + render(ctx);
3417
- if (ctx.submitted) text += "\n";
3418
- stdout.write(prevText ? clear(prevText) + "\r" + text : text);
3419
- prevText = text;
3420
- },
3421
- submit(value2) {
3422
- if (value2 !== void 0) ctx.value = value2;
3423
- if (ctx.value === void 0 || validate && !validate?.(ctx)) return;
3424
- ctx.submitted = true;
3425
- ctx.render();
3426
- close();
3427
- res(ctx.value);
3428
- }
3429
- };
3430
- const close = () => {
3431
- if (!showCursor) stdout.write(cursorShow);
3432
- if (stdin.isTTY) stdin.setRawMode(false);
3433
- stdin.off("data", keypress);
3434
- stdin.pause();
3435
- };
3436
- const keypress = (s) => {
3437
- if (s === "" || s === "") {
3438
- close?.();
3439
- process.exit(0);
3440
- }
3441
- if (s === "\r" || s === "\n" || s === "\r\n") {
3442
- ctx.submit();
3443
- } else {
3444
- onKeyPress(ctx, s);
3445
- }
3446
- };
3447
- stdin.on("data", keypress);
3448
- ctx.render();
3449
- });
3450
- };
3451
- const defaultActive = (s) => `${colors.blueBold("\u276F")} ${s}`;
3452
- const defaultInactive = (s) => ` ${s}`;
3453
- const promptSelect = ({
3454
- message,
3455
- options,
3456
- active = defaultActive,
3457
- inactive = defaultInactive
3458
- }) => prompt({
3459
- value: 0,
3460
- render(ctx) {
3461
- let text = `${message} ${colors.pale(
3462
- "Use arrows or jk. Press enter to submit."
3463
- )}
3464
- `;
3465
- for (let i = 0; i < options.length; i++) {
3466
- text += (ctx.value === i ? active : inactive)(options[i]) + "\n";
3467
- }
3468
- return text;
3469
- },
3470
- onKeyPress(ctx, s) {
3471
- ctx.value = s === "\x1B[H" ? 0 : s === "\x1B[F" ? options.length - 1 : s === "\x1B[A" || s === "k" ? ctx.value === 0 ? options.length - 1 : ctx.value - 1 : s === "\x1B[B" || s === "j" || s === " " ? ctx.value === options.length - 1 ? 0 : ctx.value + 1 : ctx.value;
3472
- ctx.render();
3473
- }
3474
- });
3475
- const promptConfirm = ({
3476
- message
3477
- }) => prompt({
3478
- value: true,
3479
- render(ctx) {
3480
- return `${colors.bright(message)}
3481
- ${ctx.submitted ? `> ${ctx.value ? colors.greenBold("Yes") : colors.yellowBold("No")}` : colors.pale(`> (Y/n)`)}
3482
- `;
3483
- },
3484
- onKeyPress(ctx, s) {
3485
- let ok;
3486
- if (s === "y" || s === "Y") ok = true;
3487
- else if (s === "n" || s === "N") ok = false;
3488
- if (ok !== void 0) {
3489
- ctx.submit(ok);
3490
- }
3491
- }
3492
- });
3493
- const promptText = ({
3494
- message,
3495
- default: def = "",
3496
- password,
3497
- min
3498
- }) => {
3499
- let showDefault = true;
3500
- let x = 0;
3501
- const renderValue = (ctx) => password ? "*".repeat(ctx.value.length) : ctx.value;
3502
- return prompt({
3503
- value: def,
3504
- cursor: true,
3505
- validate: (ctx) => !min || ctx.value.length >= min,
3506
- render(ctx) {
3507
- let text = `${colors.bright(message)}
3508
- > ${ctx.submitted ? renderValue(ctx) : showDefault ? colors.pale(def) + "\b".repeat(def.length) : ctx.value}`;
3509
- if (ctx.submitted) text += "\n";
3510
- return text;
3511
- },
3512
- onKeyPress(ctx, s) {
3513
- let value = showDefault ? "" : ctx.value;
3514
- if (s === "\x1B[D" && x > 0) {
3515
- x--;
3516
- stdout.write("\b");
3517
- } else if (s === "\x1B[C" && x < value.length) {
3518
- stdout.write(value[x]);
3519
- x++;
3520
- }
3521
- if (s !== "\x7F" && s !== "\x1B[3~" && !visibleChars(s)) return;
3522
- if (showDefault) {
3523
- showDefault = false;
3524
- stdout.write(" ".repeat(def.length) + "\b".repeat(def.length));
3525
- }
3526
- const prev = value;
3527
- const prevX = x;
3528
- if (s === "\x7F") {
3529
- if (x > 0) {
3530
- value = value.slice(0, x - 1) + value.slice(x);
3531
- x--;
3532
- }
3533
- } else if (s === "\x1B[3~") {
3534
- if (x < value.length) {
3535
- value = value.slice(0, x) + value.slice(x + 1);
3536
- }
3537
- } else {
3538
- value = value.slice(0, x) + s + value.slice(x);
3539
- x++;
3540
- }
3541
- ctx.value = value;
3542
- const spaces = prev.length - value.length;
3543
- stdout.write(
3544
- "\b".repeat(prevX) + renderValue(ctx) + (spaces > 0 ? " ".repeat(spaces) + "\b".repeat(spaces) : "") + "\b".repeat(value.length - x)
3545
- );
3546
- }
3547
- });
3548
- };
3549
-
3550
- const execute = async (adapter, sql) => {
3551
- try {
3552
- await adapter.query(sql);
3553
- return "ok";
3554
- } catch (error) {
3555
- const err = error;
3556
- if (typeof err.message === "string" && err.message.includes("sslmode=require")) {
3557
- return "ssl required";
3558
- }
3559
- if (err.code === "42P04" || err.code === "3D000") {
3560
- return "already";
3561
- } else if (err.code === "42501" || typeof err.message === "string" && err.message.includes("password authentication failed")) {
3562
- return "forbidden";
3563
- } else {
3564
- return { error };
3565
- }
3566
- } finally {
3567
- await adapter.close();
3568
- }
3569
- };
3570
- const createOrDrop = async (adapter, adminAdapter, config, args) => {
3571
- const params = {
3572
- database: adapter.getDatabase(),
3573
- user: adapter.getUser()
3574
- };
3575
- const result = await execute(
3576
- adminAdapter.reconfigure({ database: "postgres" }),
3577
- args.sql(params)
3578
- );
3579
- if (result === "ok") {
3580
- config.logger?.log(args.successMessage(params));
3581
- } else if (result === "already") {
3582
- config.logger?.log(args.alreadyMessage(params));
3583
- } else if (result === "ssl required") {
3584
- config.logger?.log(
3585
- "SSL is required: append ?ssl=true to the database url string"
3586
- );
3587
- return;
3588
- } else if (result === "forbidden") {
3589
- let message = `Permission denied to ${args.create ? "create" : "drop"} database.`;
3590
- const host = adminAdapter.getHost();
3591
- const isLocal = host === "localhost";
3592
- if (!isLocal) {
3593
- message += `
3594
- Don't use this command for database service providers, only for a local db.`;
3595
- }
3596
- config.logger?.log(message);
3597
- const params2 = await askForAdminCredentials(args.create);
3598
- if (!params2) return;
3599
- await createOrDrop(adapter, adminAdapter.reconfigure(params2), config, args);
3600
- return;
3601
- } else {
3602
- throw result.error;
3603
- }
3604
- if (!args.create) return;
3605
- const newlyConnectedAdapter = adapter.reconfigure({});
3606
- await createMigrationsTable(newlyConnectedAdapter, config);
3607
- await newlyConnectedAdapter.close();
3608
- };
3609
- const createDb = async (adapters, config) => {
3610
- for (const adapter of adapters) {
3611
- await createOrDrop(adapter, adapter, config, {
3612
- sql({ database, user }) {
3613
- return `CREATE DATABASE "${database}"${user ? ` OWNER "${user}"` : ""}`;
3614
- },
3615
- successMessage({ database }) {
3616
- return `Database ${database} successfully created`;
3617
- },
3618
- alreadyMessage({ database }) {
3619
- return `Database ${database} already exists`;
3620
- },
3621
- create: true
3622
- });
3623
- }
3624
- };
3625
- const dropDb = async (adapters, config) => {
3626
- for (const adapter of adapters) {
3627
- await createOrDrop(adapter, adapter, config, {
3628
- sql({ database }) {
3629
- return `DROP DATABASE "${database}"`;
3630
- },
3631
- successMessage({ database }) {
3632
- return `Database ${database} was successfully dropped`;
3633
- },
3634
- alreadyMessage({ database }) {
3635
- return `Database ${database} does not exist`;
3535
+ Run \`**db command** up force\` to rollback the above migrations and migrate all`
3536
+ );
3636
3537
  }
3637
- });
3538
+ return version;
3539
+ }
3638
3540
  }
3541
+ return;
3639
3542
  };
3640
- const resetDb = async (adapters, config) => {
3641
- await dropDb(adapters, config);
3642
- await createDb(adapters, config);
3643
- for (const adapter of adapters) {
3644
- await migrate({ adapter, config });
3543
+ const changeCache = {};
3544
+ const getChanges = async (file, config) => {
3545
+ clearChanges();
3546
+ let changes = file.path ? changeCache[file.path] : void 0;
3547
+ if (!changes) {
3548
+ const module = await file.load();
3549
+ const exported = module?.default && toArray(module.default);
3550
+ if (config?.forceDefaultExports && !exported) {
3551
+ throw new RakeDbError(
3552
+ `Missing a default export in ${file.path} migration`
3553
+ );
3554
+ }
3555
+ changes = exported || getCurrentChanges();
3556
+ if (file.path) changeCache[file.path] = changes;
3645
3557
  }
3558
+ return changes;
3646
3559
  };
3647
- const askForAdminCredentials = async (create) => {
3648
- const ok = await promptConfirm({
3649
- message: `Would you like to share admin credentials to ${create ? "create" : "drop"} a database?`
3650
- });
3651
- if (!ok) {
3652
- return;
3560
+ const runMigrationInOwnTransaction = (adapter, ...rest) => {
3561
+ return transaction(adapter, (trx) => applyMigration(trx, ...rest));
3562
+ };
3563
+ const applyMigration = async (trx, up, changes, config) => {
3564
+ const db = createMigrationInterface(trx, up, config);
3565
+ if (changes.length) {
3566
+ const from = up ? 0 : changes.length - 1;
3567
+ const to = up ? changes.length : -1;
3568
+ const step = up ? 1 : -1;
3569
+ for (let i = from; i !== to; i += step) {
3570
+ await changes[i].fn(db, up);
3571
+ }
3653
3572
  }
3654
- const user = await promptText({
3655
- message: "Enter admin user:",
3656
- default: "postgres",
3657
- min: 1
3658
- });
3659
- const password = await promptText({
3660
- message: "Enter admin password:",
3661
- password: true
3662
- });
3663
- return {
3664
- user,
3665
- password: password || void 0
3666
- };
3573
+ return db.adapter;
3574
+ };
3575
+ const changeMigratedVersion = async (adapter, up, file, config) => {
3576
+ await (up ? saveMigratedVersion : deleteMigratedVersion)(
3577
+ adapter,
3578
+ file.version,
3579
+ path.basename(file.path).slice(file.version.length + 1),
3580
+ config
3581
+ );
3667
3582
  };
3668
3583
 
3669
3584
  const runRecurrentMigrations = async (adapters, config) => {
@@ -3704,6 +3619,131 @@ const readdirRecursive = async (dirPath, cb) => {
3704
3619
  );
3705
3620
  };
3706
3621
 
3622
+ const createDatabaseCommand = (adapters, config, dontClose) => createOrDropDatabase("create", adapters, config, dontClose);
3623
+ const dropDatabaseCommand = (adapters, config) => createOrDropDatabase("drop", adapters, config);
3624
+ const createOrDropDatabase = async (action, adapters, config, dontClose) => {
3625
+ const fn = action === "create" ? createDatabase : dropDatabase;
3626
+ for (const adapter of adapters) {
3627
+ const database = adapter.getDatabase();
3628
+ const owner = adapter.getUser();
3629
+ const res = await run(
3630
+ adapter.reconfigure({ database: "postgres" }),
3631
+ config,
3632
+ {
3633
+ command: (adapter2) => fn(adapter2, {
3634
+ database,
3635
+ owner
3636
+ }),
3637
+ doneMessage: () => `Database ${database} successfully ${action === "create" ? "created" : "dropped"}`,
3638
+ alreadyMessage: () => `Database ${database} ${action === "create" ? "already exists" : "does not exist"}`,
3639
+ deniedMessage: () => `Permission denied to ${action} database.`,
3640
+ askAdminCreds: () => askForAdminCredentials(action === "create")
3641
+ }
3642
+ );
3643
+ if (!res) continue;
3644
+ if (action === "create") {
3645
+ await adapter.transaction(void 0, async (tx) => {
3646
+ if (config.schema) {
3647
+ const quoted = `"${config.schema}"`;
3648
+ const res2 = await createSchema$1(tx, quoted);
3649
+ if (res2 === "done") {
3650
+ config.logger?.log(`Created schema ${quoted}`);
3651
+ }
3652
+ }
3653
+ await createMigrationsSchemaAndTable(tx, config);
3654
+ });
3655
+ if (!dontClose) {
3656
+ await adapter.close();
3657
+ }
3658
+ }
3659
+ }
3660
+ };
3661
+ const resetDatabaseCommand = async (adapters, config) => {
3662
+ await createOrDropDatabase("create", adapters, config);
3663
+ await createOrDropDatabase("drop", adapters, config, true);
3664
+ for (const adapter of adapters) {
3665
+ await migrate(adapter, config);
3666
+ }
3667
+ if (config.recurrentPath) {
3668
+ await runRecurrentMigrations(adapters, config);
3669
+ }
3670
+ await Promise.all(adapters.map((adapter) => adapter.close()));
3671
+ };
3672
+ const run = async (adapter, config, params) => {
3673
+ try {
3674
+ const res = await params.command(adapter);
3675
+ config.logger?.log(
3676
+ res === "done" ? params.doneMessage() : params.alreadyMessage()
3677
+ );
3678
+ await adapter.close();
3679
+ return true;
3680
+ } catch (err) {
3681
+ if (err instanceof CreateOrDropError) {
3682
+ if (err.status === "ssl-required") {
3683
+ config.logger?.log(
3684
+ "SSL is required: append ?ssl=true to the database url string"
3685
+ );
3686
+ return false;
3687
+ }
3688
+ if (err.status === "forbidden" || err.status === "auth-failed") {
3689
+ let message = params.deniedMessage();
3690
+ const host = adapter.getHost();
3691
+ const isLocal = host === "localhost";
3692
+ if (!isLocal) {
3693
+ message += `
3694
+ Don't use this command for database service providers, only for a local db.`;
3695
+ }
3696
+ config.logger?.log(message);
3697
+ const creds = await params.askAdminCreds();
3698
+ if (!creds) return false;
3699
+ return run(adapter.reconfigure(creds), config, params);
3700
+ }
3701
+ }
3702
+ throw err;
3703
+ }
3704
+ };
3705
+ const askForAdminCredentials = async (create) => {
3706
+ const ok = await promptConfirm({
3707
+ message: `Would you like to share admin credentials to ${create ? "create" : "drop"} a database?`
3708
+ });
3709
+ if (!ok) {
3710
+ return;
3711
+ }
3712
+ const user = await promptText({
3713
+ message: "Enter admin user:",
3714
+ default: "postgres",
3715
+ min: 1
3716
+ });
3717
+ const password = await promptText({
3718
+ message: "Enter admin password:",
3719
+ password: true
3720
+ });
3721
+ return {
3722
+ user,
3723
+ password: password || void 0
3724
+ };
3725
+ };
3726
+
3727
+ const makeMigrateOrRollback = (fn) => async (adapters, config, args) => {
3728
+ const arg = args[0];
3729
+ let force;
3730
+ let count;
3731
+ if (arg === "force") {
3732
+ force = true;
3733
+ } else {
3734
+ const num = arg === "all" ? Infinity : parseInt(arg || "");
3735
+ if (!isNaN(num)) {
3736
+ count = num;
3737
+ }
3738
+ }
3739
+ for (const adapter of adapters) {
3740
+ await fn(adapter, config, { ctx: {}, count, force });
3741
+ }
3742
+ };
3743
+ const migrateCommand = makeMigrateOrRollback(migrate);
3744
+ const rollbackCommand = makeMigrateOrRollback(rollback);
3745
+ const redoCommand = makeMigrateOrRollback(redo);
3746
+
3707
3747
  const astToGenerateItems = (config, asts, currentSchema) => {
3708
3748
  return asts.map((ast) => astToGenerateItem(config, ast, currentSchema));
3709
3749
  };
@@ -3715,7 +3755,10 @@ const astToGenerateItem = (config, ast, currentSchema) => {
3715
3755
  const resolveType = (type) => {
3716
3756
  let dep = typeSchemaCache.get(type);
3717
3757
  if (!dep) {
3718
- const [schema = currentSchema, name] = getSchemaAndTableFromName(type);
3758
+ const [schema = currentSchema, name] = getSchemaAndTableFromName(
3759
+ config,
3760
+ type
3761
+ );
3719
3762
  dep = `${schema}.${name}`;
3720
3763
  typeSchemaCache.set(type, dep);
3721
3764
  }
@@ -3905,7 +3948,10 @@ const analyzeTableColumns = (config, currentSchema, schema, table, deps, resolve
3905
3948
  config.snakeCase
3906
3949
  )
3907
3950
  );
3908
- const [s = currentSchema, t] = getForeignKeyTable(fkey.fnOrTable);
3951
+ const [s = currentSchema, t] = getForeignKeyTable(
3952
+ config,
3953
+ fkey.fnOrTable
3954
+ );
3909
3955
  const foreignTable = `${s}.${t}`;
3910
3956
  if (foreignTable !== table) {
3911
3957
  deps.push(foreignTable);
@@ -3950,6 +3996,7 @@ const analyzeTableData = (config, currentSchema, schema, table, keys, deps, data
3950
3996
  );
3951
3997
  if (constraint.references) {
3952
3998
  const [s = currentSchema, t] = getForeignKeyTable(
3999
+ config,
3953
4000
  constraint.references.fnOrTable
3954
4001
  );
3955
4002
  deps.push(`${s}.${t}`);
@@ -5411,36 +5458,239 @@ const checkIfIsOuterRecursiveFkey = (data, table, references) => {
5411
5458
  break;
5412
5459
  }
5413
5460
  }
5414
- return false;
5415
- };
5461
+ return false;
5462
+ };
5463
+
5464
+ const pullDbStructure = async (adapter, config) => {
5465
+ const currentSchema = adapter.searchPath || "public";
5466
+ const ctx = makeStructureToAstCtx(config, currentSchema);
5467
+ const ast = await structureToAst(ctx, adapter, config);
5468
+ const result = astToMigration(currentSchema, config, ast);
5469
+ if (!result) return;
5470
+ const version = await makeFileVersion({}, config);
5471
+ await writeMigrationFile(config, version, "pull", result);
5472
+ const silentQueries = Object.assign(adapter, {
5473
+ silentQuery: adapter.query,
5474
+ silentArrays: adapter.arrays
5475
+ });
5476
+ await saveMigratedVersion(silentQueries, version, "pull", config);
5477
+ const unsupportedEntries = Object.entries(ctx.unsupportedTypes);
5478
+ const len = unsupportedEntries.length;
5479
+ if (len) {
5480
+ let count = 0;
5481
+ config.logger?.warn(
5482
+ `Found unsupported types:
5483
+ ${unsupportedEntries.map(([type, columns]) => {
5484
+ count += columns.length;
5485
+ return `- ${type} is used for column${columns.length > 1 ? "s" : ""} ${columns.join(", ")}`;
5486
+ }).join("\n")}
5487
+ Append \`as\` method manually to ${count > 1 ? "these" : "this"} column${count > 1 ? "s" : ""} to treat ${count > 1 ? "them" : "it"} as other column type`
5488
+ );
5489
+ }
5490
+ config.logger?.log("Database pulled successfully");
5491
+ };
5492
+
5493
+ const migrationConfigDefaults = {
5494
+ schemaConfig: defaultSchemaConfig,
5495
+ migrationsPath: path.join("src", "db", "migrations"),
5496
+ migrationId: { serial: 4 },
5497
+ migrationsTable: "schemaMigrations",
5498
+ snakeCase: false,
5499
+ commands: {},
5500
+ log: true,
5501
+ logger: console,
5502
+ import() {
5503
+ throw new Error(
5504
+ "Add `import: (path) => import(path),` setting to `rakeDb` config"
5505
+ );
5506
+ }
5507
+ };
5508
+ const ensureMigrationsPath = (config) => {
5509
+ if (!config.migrationsPath) {
5510
+ config.migrationsPath = migrationConfigDefaults.migrationsPath;
5511
+ }
5512
+ if (!path.isAbsolute(config.migrationsPath)) {
5513
+ config.migrationsPath = path.resolve(
5514
+ config.basePath,
5515
+ config.migrationsPath
5516
+ );
5517
+ }
5518
+ return config;
5519
+ };
5520
+ const ensureBasePathAndDbScript = (config, intermediateCallers2 = 0) => {
5521
+ if (config.basePath && config.dbScript) return config;
5522
+ let filePath = getStackTrace()?.[3 + intermediateCallers2]?.getFileName();
5523
+ if (!filePath) {
5524
+ throw new Error(
5525
+ "Failed to determine path to db script. Please set basePath option of rakeDb"
5526
+ );
5527
+ }
5528
+ if (filePath.startsWith("file://")) {
5529
+ filePath = fileURLToPath(filePath);
5530
+ }
5531
+ const ext = path.extname(filePath);
5532
+ if (ext !== ".ts" && ext !== ".js" && ext !== ".mjs") {
5533
+ throw new Error(
5534
+ `Add a .ts suffix to the "${path.basename(filePath)}" when calling it`
5535
+ );
5536
+ }
5537
+ config.basePath = path.dirname(filePath);
5538
+ config.dbScript = path.basename(filePath);
5539
+ return config;
5540
+ };
5541
+ let intermediateCallers = 0;
5542
+ const incrementIntermediateCaller = () => {
5543
+ intermediateCallers++;
5544
+ };
5545
+ const makeRakeDbConfig = (config, args) => {
5546
+ const ic = intermediateCallers;
5547
+ intermediateCallers = 0;
5548
+ const result = {
5549
+ ...migrationConfigDefaults,
5550
+ ...config,
5551
+ __rakeDbConfig: true
5552
+ };
5553
+ if (!result.log) {
5554
+ delete result.logger;
5555
+ }
5556
+ ensureBasePathAndDbScript(result, ic);
5557
+ ensureMigrationsPath(result);
5558
+ if (!result.recurrentPath) {
5559
+ result.recurrentPath = path.join(
5560
+ result.migrationsPath,
5561
+ "recurrent"
5562
+ );
5563
+ }
5564
+ if ("recurrentPath" in result && !path.isAbsolute(result.recurrentPath)) {
5565
+ result.recurrentPath = path.resolve(result.basePath, result.recurrentPath);
5566
+ }
5567
+ if ("baseTable" in config && config.baseTable) {
5568
+ const { types, snakeCase, language } = config.baseTable.prototype;
5569
+ result.columnTypes = types || makeColumnTypes(defaultSchemaConfig);
5570
+ if (snakeCase) result.snakeCase = true;
5571
+ if (language) result.language = language;
5572
+ } else {
5573
+ const ct = "columnTypes" in config && config.columnTypes;
5574
+ result.columnTypes = (typeof ct === "function" ? ct(
5575
+ makeColumnTypes(defaultSchemaConfig)
5576
+ ) : ct) || makeColumnTypes(defaultSchemaConfig);
5577
+ }
5578
+ if (config.migrationId === "serial") {
5579
+ result.migrationId = { serial: 4 };
5580
+ }
5581
+ const transaction = getCliParam(args, "transaction");
5582
+ if (transaction) {
5583
+ if (transaction !== "single" && transaction !== "per-migration") {
5584
+ throw new Error(
5585
+ `Unsupported transaction param ${transaction}, expected single or per-migration`
5586
+ );
5587
+ }
5588
+ result.transaction = transaction;
5589
+ } else if (!result.transaction) {
5590
+ result.transaction = "single";
5591
+ }
5592
+ let c = rakeDbCommands;
5593
+ if (config.commands) {
5594
+ c = { ...c };
5595
+ const commands = config.commands;
5596
+ for (const key in commands) {
5597
+ const command = commands[key];
5598
+ c[key] = typeof command === "function" ? { run: command } : command;
5599
+ }
5600
+ }
5601
+ result.commands = c;
5602
+ return result;
5603
+ };
5604
+
5605
+ const listMigrationsStatuses = async (adapters, config, params) => {
5606
+ const ctx = {};
5607
+ const [{ migrations }, ...migrated] = await Promise.all([
5608
+ getMigrations(ctx, config, true),
5609
+ ...adapters.map((adapter) => getMigratedVersionsMap(ctx, adapter, config))
5610
+ ]);
5611
+ const map = {};
5612
+ let maxVersionLength = 12;
5613
+ let maxNameLength = 4;
5614
+ for (let i = 0; i < adapters.length; i++) {
5615
+ const list = migrated[i];
5616
+ const key = Object.entries(list.map).map(([version, up]) => `${version}${up ? "t" : "f"}`).join("");
5617
+ const database = adapters[i].getDatabase();
5618
+ if (map[key]) {
5619
+ map[key].databases.push(database);
5620
+ continue;
5621
+ }
5622
+ map[key] = {
5623
+ databases: [database],
5624
+ migrations: migrations.map((item) => {
5625
+ if (item.version.length > maxVersionLength) {
5626
+ maxVersionLength = item.version.length;
5627
+ }
5628
+ const name = path.parse(item.path).name.slice(item.version.length + 1).replace(
5629
+ /([a-z])([A-Z])/g,
5630
+ (_, a, b) => `${a} ${b.toLocaleLowerCase()}`
5631
+ ).replace(/[-_](.)/g, (_, char) => ` ${char.toLocaleLowerCase()}`).replace(/^\w/, (match) => match.toLocaleUpperCase());
5632
+ if (name.length > maxNameLength) {
5633
+ maxNameLength = name.length;
5634
+ }
5635
+ return {
5636
+ up: !!list.map[item.version],
5637
+ version: item.version,
5638
+ name,
5639
+ url: pathToFileURL(item.path)
5640
+ };
5641
+ })
5642
+ };
5643
+ }
5644
+ const showUrl = params?.showUrl;
5645
+ const asIs = (s) => s;
5646
+ const c = typeof config.log === "object" && config.log.colors === false ? {
5647
+ yellow: asIs,
5648
+ green: asIs,
5649
+ red: asIs,
5650
+ blue: asIs
5651
+ } : colors;
5652
+ const log = Object.values(map).map(({ databases, migrations: migrations2 }) => {
5653
+ let log2 = ` ${c.yellow("Database:")} ${databases.join(", ")}`;
5654
+ if (migrations2.length === 0) {
5655
+ return log2 + `
5416
5656
 
5417
- const pullDbStructure = async (adapter, config) => {
5418
- const currentSchema = adapter.searchPath || "public";
5419
- const ctx = makeStructureToAstCtx(config, currentSchema);
5420
- const ast = await structureToAst(ctx, adapter, config);
5421
- const result = astToMigration(currentSchema, config, ast);
5422
- if (!result) return;
5423
- const version = await makeFileVersion({}, config);
5424
- await writeMigrationFile(config, version, "pull", result);
5425
- const silentQueries = Object.assign(adapter, {
5426
- silentQuery: adapter.query,
5427
- silentArrays: adapter.arrays
5428
- });
5429
- await saveMigratedVersion(silentQueries, version, "pull", config);
5430
- const unsupportedEntries = Object.entries(ctx.unsupportedTypes);
5431
- const len = unsupportedEntries.length;
5432
- if (len) {
5433
- let count = 0;
5434
- config.logger?.warn(
5435
- `Found unsupported types:
5436
- ${unsupportedEntries.map(([type, columns]) => {
5437
- count += columns.length;
5438
- return `- ${type} is used for column${columns.length > 1 ? "s" : ""} ${columns.join(", ")}`;
5439
- }).join("\n")}
5440
- Append \`as\` method manually to ${count > 1 ? "these" : "this"} column${count > 1 ? "s" : ""} to treat ${count > 1 ? "them" : "it"} as other column type`
5657
+ No migrations available`;
5658
+ }
5659
+ const lineSeparator = c.yellow(
5660
+ makeChars(14 + maxVersionLength + maxNameLength, "-")
5661
+ );
5662
+ const columnSeparator = c.yellow("|");
5663
+ log2 += "\n\n " + c.yellow(
5664
+ `Status | Migration ID${makeChars(
5665
+ maxVersionLength - 12,
5666
+ " "
5667
+ )} | Name
5668
+ ${lineSeparator}`
5441
5669
  );
5670
+ for (const migration of migrations2) {
5671
+ log2 += `
5672
+ ${migration.up ? ` ${c.green("Up")} ` : c.red("Down")} ${columnSeparator} ${c.blue(migration.version)}${makeChars(
5673
+ maxVersionLength - migration.version.length,
5674
+ " "
5675
+ )} ${columnSeparator} ${migration.name}`;
5676
+ if (showUrl) {
5677
+ log2 += `
5678
+ ${migration.url}
5679
+ `;
5680
+ }
5681
+ }
5682
+ return log2 += showUrl ? lineSeparator : `
5683
+ ${lineSeparator}`;
5684
+ }).join("\n\n");
5685
+ (config.logger ?? console).log(log);
5686
+ await Promise.all(adapters.map((adapter) => adapter.close()));
5687
+ };
5688
+ const makeChars = (count, char) => {
5689
+ let chars = "";
5690
+ for (let i = 0; i < count; i++) {
5691
+ chars += char;
5442
5692
  }
5443
- config.logger?.log("Database pulled successfully");
5693
+ return chars;
5444
5694
  };
5445
5695
 
5446
5696
  const rebase = async (adapters, config) => {
@@ -5581,11 +5831,7 @@ const rebase = async (adapters, config) => {
5581
5831
  }
5582
5832
  };
5583
5833
  for (const adapter of adapters) {
5584
- await redo({
5585
- ctx,
5586
- adapter,
5587
- config: redoConfig
5588
- });
5834
+ await redo(adapter, redoConfig, { ctx, count: Infinity });
5589
5835
  }
5590
5836
  for (let i = renames.length - 1; i >= 0; i--) {
5591
5837
  const [from, version] = renames[i];
@@ -5600,111 +5846,52 @@ const rebase = async (adapters, config) => {
5600
5846
  }
5601
5847
  };
5602
5848
 
5603
- const listMigrationsStatuses = async (adapters, config, args) => {
5604
- const ctx = {};
5605
- const [{ migrations }, ...migrated] = await Promise.all([
5606
- getMigrations(ctx, config, true),
5607
- ...adapters.map((adapter) => getMigratedVersionsMap(ctx, adapter, config))
5608
- ]);
5609
- const map = {};
5610
- let maxVersionLength = 12;
5611
- let maxNameLength = 4;
5612
- for (let i = 0; i < adapters.length; i++) {
5613
- const list = migrated[i];
5614
- const key = Object.entries(list.map).map(([version, up]) => `${version}${up ? "t" : "f"}`).join("");
5615
- const database = adapters[i].getDatabase();
5616
- if (map[key]) {
5617
- map[key].databases.push(database);
5618
- continue;
5619
- }
5620
- map[key] = {
5621
- databases: [database],
5622
- migrations: migrations.map((item) => {
5623
- if (item.version.length > maxVersionLength) {
5624
- maxVersionLength = item.version.length;
5625
- }
5626
- const name = path.parse(item.path).name.slice(item.version.length + 1).replace(
5627
- /([a-z])([A-Z])/g,
5628
- (_, a, b) => `${a} ${b.toLocaleLowerCase()}`
5629
- ).replace(/[-_](.)/g, (_, char) => ` ${char.toLocaleLowerCase()}`).replace(/^\w/, (match) => match.toLocaleUpperCase());
5630
- if (name.length > maxNameLength) {
5631
- maxNameLength = name.length;
5632
- }
5633
- return {
5634
- up: !!list.map[item.version],
5635
- version: item.version,
5636
- name,
5637
- url: pathToFileURL(item.path)
5638
- };
5639
- })
5640
- };
5641
- }
5642
- const showUrl = args.includes("p") || args.includes("path");
5643
- const asIs = (s) => s;
5644
- const c = typeof config.log === "object" && config.log.colors === false ? {
5645
- yellow: asIs,
5646
- green: asIs,
5647
- red: asIs,
5648
- blue: asIs
5649
- } : colors;
5650
- const log = Object.values(map).map(({ databases, migrations: migrations2 }) => {
5651
- let log2 = ` ${c.yellow("Database:")} ${databases.join(", ")}`;
5652
- if (migrations2.length === 0) {
5653
- return log2 + `
5654
-
5655
- No migrations available`;
5656
- }
5657
- const lineSeparator = c.yellow(
5658
- makeChars(14 + maxVersionLength + maxNameLength, "-")
5659
- );
5660
- const columnSeparator = c.yellow("|");
5661
- log2 += "\n\n " + c.yellow(
5662
- `Status | Migration ID${makeChars(
5663
- maxVersionLength - 12,
5664
- " "
5665
- )} | Name
5666
- ${lineSeparator}`
5667
- );
5668
- for (const migration of migrations2) {
5669
- log2 += `
5670
- ${migration.up ? ` ${c.green("Up")} ` : c.red("Down")} ${columnSeparator} ${c.blue(migration.version)}${makeChars(
5671
- maxVersionLength - migration.version.length,
5672
- " "
5673
- )} ${columnSeparator} ${migration.name}`;
5674
- if (showUrl) {
5675
- log2 += `
5676
- ${migration.url}
5677
- `;
5678
- }
5679
- }
5680
- return log2 += showUrl ? lineSeparator : `
5681
- ${lineSeparator}`;
5682
- }).join("\n\n");
5683
- (config.logger ?? console).log(log);
5684
- await Promise.all(adapters.map((adapter) => adapter.close()));
5685
- };
5686
- const makeChars = (count, char) => {
5687
- let chars = "";
5688
- for (let i = 0; i < count; i++) {
5689
- chars += char;
5690
- }
5691
- return chars;
5692
- };
5693
-
5694
5849
  const rakeDbAliases = {
5695
5850
  migrate: "up",
5696
5851
  rollback: "down",
5697
5852
  s: "status",
5698
5853
  rec: "recurrent"
5699
5854
  };
5700
- const runCommand = async (adapters, config, args = process.argv.slice(2)) => {
5855
+ const rakeDbCliWithAdapter = (inputConfig, args = process.argv.slice(2)) => {
5856
+ let config;
5857
+ if ("__rakeDbConfig" in inputConfig) {
5858
+ config = inputConfig;
5859
+ } else {
5860
+ incrementIntermediateCaller();
5861
+ config = makeRakeDbConfig(inputConfig, args);
5862
+ }
5863
+ return {
5864
+ change: makeChange(config),
5865
+ async run(adapter, runArgs) {
5866
+ const adapters = toArray(adapter);
5867
+ try {
5868
+ await runCommand(adapters, config, runArgs || args);
5869
+ } catch (err) {
5870
+ if (err instanceof RakeDbError) {
5871
+ config.logger?.error(err.message);
5872
+ process.exit(1);
5873
+ }
5874
+ throw err;
5875
+ }
5876
+ }
5877
+ };
5878
+ };
5879
+ const setRakeDbCliRunFn = (rakeDbCli, mapper) => {
5880
+ rakeDbCli.run = (adapter, inputConfig, args) => {
5881
+ const { change, run } = rakeDbCli(inputConfig, args);
5882
+ run(mapper(adapter));
5883
+ return change;
5884
+ };
5885
+ };
5886
+ setRakeDbCliRunFn(rakeDbCliWithAdapter, (x) => x);
5887
+ const runCommand = async (adapters, config, args) => {
5701
5888
  let arg = args[0]?.split(":")[0];
5702
5889
  if (rakeDbAliases[arg]) {
5703
5890
  args = [...args];
5704
5891
  arg = args[0] = rakeDbAliases[arg];
5705
5892
  }
5706
5893
  args.shift();
5707
- const command = rakeDbCommands[arg]?.run ?? config.commands[arg];
5894
+ const command = config.commands[arg]?.run;
5708
5895
  if (command) {
5709
5896
  await command(adapters, config, args);
5710
5897
  } else if (config.logger) {
@@ -5712,8 +5899,8 @@ const runCommand = async (adapters, config, args = process.argv.slice(2)) => {
5712
5899
  let max = 0;
5713
5900
  let maxArgs = 0;
5714
5901
  const addedCommands = /* @__PURE__ */ new Map();
5715
- for (let key in rakeDbCommands) {
5716
- const command2 = rakeDbCommands[key];
5902
+ for (let key in config.commands) {
5903
+ const command2 = config.commands[key];
5717
5904
  const added = addedCommands.get(command2);
5718
5905
  if (added) key = added[0] += `, ${key}`;
5719
5906
  if (key.length > max) max = key.length;
@@ -5724,7 +5911,11 @@ const runCommand = async (adapters, config, args = process.argv.slice(2)) => {
5724
5911
  ...Object.keys(command2.helpArguments).map((key2) => key2.length + 5)
5725
5912
  );
5726
5913
  }
5727
- const helpBlock = [key, command2.help, command2.helpArguments];
5914
+ const helpBlock = [
5915
+ key,
5916
+ command2.help || "undocumented custom command",
5917
+ command2.helpArguments
5918
+ ];
5728
5919
  addedCommands.set(command2, helpBlock);
5729
5920
  if (command2.helpAfter) {
5730
5921
  const i = commandsHelp.findIndex(([key2]) => key2 === command2.helpAfter);
@@ -5760,15 +5951,16 @@ ${Object.entries(helpArguments).map(
5760
5951
  }).join("\n\n")}
5761
5952
  `);
5762
5953
  }
5763
- return {
5764
- adapters,
5765
- config,
5766
- args
5767
- };
5768
5954
  };
5769
5955
  const close = (adapters) => Promise.all(adapters.map((adapter) => adapter.close()));
5956
+ const maybeRunRecurrent = async (adapters, config) => {
5957
+ config.recurrentPath && await runRecurrentMigrations(
5958
+ adapters,
5959
+ config
5960
+ );
5961
+ };
5770
5962
  const upCommand = {
5771
- run: (adapters, config, args) => migrateCommand(adapters, config, args).then(() => runRecurrentMigrations(adapters, config)).then(() => close(adapters)),
5963
+ run: (adapters, config, args) => migrateCommand(adapters, config, args).then(() => maybeRunRecurrent(adapters, config)).then(() => close(adapters)),
5772
5964
  help: "migrate pending migrations",
5773
5965
  helpArguments: {
5774
5966
  "no arguments": "migrate all pending",
@@ -5786,36 +5978,40 @@ const downCommand = {
5786
5978
  }
5787
5979
  };
5788
5980
  const statusCommand = {
5789
- run: listMigrationsStatuses,
5981
+ run(adapters, config, args) {
5982
+ const showUrl = args.includes("p") || args.includes("path");
5983
+ return listMigrationsStatuses(adapters, config, { showUrl });
5984
+ },
5790
5985
  help: "list migrations statuses",
5791
5986
  helpArguments: {
5792
5987
  "no arguments": `does not print file paths`,
5793
5988
  "p, path": "also print file paths"
5794
5989
  }
5795
5990
  };
5796
- const recurrentCommand = {
5797
- run: (adapters, config) => runRecurrentMigrations(adapters, config).then(() => close(adapters)),
5991
+ const recurrent = {
5992
+ async run(adapters, config) {
5993
+ if (!config.recurrentPath) return;
5994
+ await maybeRunRecurrent(adapters, config).then(() => close(adapters));
5995
+ },
5798
5996
  help: "run recurrent migrations"
5799
5997
  };
5800
5998
  const rakeDbCommands = {
5801
5999
  create: {
5802
- run: createDb,
6000
+ run: (adapters, config) => createDatabaseCommand(adapters, config),
5803
6001
  help: "create databases"
5804
6002
  },
5805
6003
  drop: {
5806
- run: dropDb,
6004
+ run: dropDatabaseCommand,
5807
6005
  help: "drop databases"
5808
6006
  },
5809
6007
  reset: {
5810
- run: (adapters, config) => resetDb(adapters, config).then(() => runRecurrentMigrations(adapters, config)).then(() => close(adapters)),
6008
+ run: (adapters, config) => resetDatabaseCommand(adapters, config),
5811
6009
  help: "drop, create and migrate databases"
5812
6010
  },
5813
6011
  up: upCommand,
5814
- migrate: upCommand,
5815
6012
  down: downCommand,
5816
- rollback: downCommand,
5817
6013
  redo: {
5818
- run: (adapters, config, args) => redoCommand(adapters, config, args).then(() => runRecurrentMigrations(adapters, config)).then(() => close(adapters)),
6014
+ run: (adapters, config, args) => redoCommand(adapters, config, args).then(() => maybeRunRecurrent(adapters, config)).then(() => close(adapters)),
5819
6015
  help: "rollback and migrate, run recurrent"
5820
6016
  },
5821
6017
  pull: {
@@ -5823,19 +6019,32 @@ const rakeDbCommands = {
5823
6019
  help: "generate a combined migration for an existing database"
5824
6020
  },
5825
6021
  new: {
5826
- run: (_, config, args) => newMigration(config, args),
6022
+ run(_, config, args) {
6023
+ const [name] = args;
6024
+ if (!name) throw new Error("Migration name is missing");
6025
+ return newMigration(config, name);
6026
+ },
5827
6027
  help: "create new migration file"
5828
6028
  },
5829
- s: statusCommand,
5830
6029
  status: statusCommand,
5831
- rec: recurrentCommand,
5832
- recurrent: recurrentCommand,
6030
+ recurrent,
5833
6031
  rebase: {
5834
6032
  run: (adapters, config) => rebase(adapters, config).then(() => close(adapters)),
5835
6033
  help: "move local migrations below the new ones from upstream"
5836
6034
  },
5837
6035
  "change-ids": {
5838
- run: changeIds,
6036
+ run(adapters, config, [format, digitsArg]) {
6037
+ if (format !== "serial" && format !== "timestamp") {
6038
+ throw new Error(
6039
+ `Pass "serial" or "timestamp" argument to the "change-ids" command`
6040
+ );
6041
+ }
6042
+ const digits = digitsArg ? parseInt(digitsArg) : void 0;
6043
+ if (digits && isNaN(digits)) {
6044
+ throw new Error(`Second argument is optional and must be an integer`);
6045
+ }
6046
+ return changeIds(adapters, config, { format, digits });
6047
+ },
5839
6048
  help: "change migrations ids format",
5840
6049
  helpArguments: {
5841
6050
  serial: "change ids to 4 digit serial",
@@ -5844,68 +6053,10 @@ const rakeDbCommands = {
5844
6053
  }
5845
6054
  }
5846
6055
  };
6056
+ for (const key in rakeDbAliases) {
6057
+ const command = rakeDbAliases[key];
6058
+ if (command) rakeDbCommands[key] = rakeDbCommands[command];
6059
+ }
5847
6060
 
5848
- const rakeDbWithAdapters = (adapters, partialConfig, args = process.argv.slice(2)) => {
5849
- const config = processRakeDbConfig(partialConfig, args);
5850
- const promise = runCommand(
5851
- adapters,
5852
- config,
5853
- args
5854
- ).catch((err) => {
5855
- if (err instanceof RakeDbError) {
5856
- config.logger?.error(err.message);
5857
- process.exit(1);
5858
- }
5859
- throw err;
5860
- });
5861
- return Object.assign(makeChange(config), {
5862
- promise
5863
- });
5864
- };
5865
- rakeDbWithAdapters.lazy = (adapters, partialConfig) => {
5866
- const config = processRakeDbConfig(partialConfig);
5867
- return {
5868
- change: makeChange(config),
5869
- run(args, conf) {
5870
- return runCommand(adapters, conf ? { ...config, ...conf } : config, args);
5871
- }
5872
- };
5873
- };
5874
- const makeChange = (config) => (fn) => {
5875
- const change = { fn, config };
5876
- pushChange(change);
5877
- return change;
5878
- };
5879
-
5880
- const migrateFiles = async (db, files) => {
5881
- const qb = db.$qb;
5882
- await qb.ensureTransaction(async () => {
5883
- const adapter = qb.internal.transactionStorage.getStore()?.adapter;
5884
- for (const load of files) {
5885
- clearChanges();
5886
- const changes = await getChanges({ load });
5887
- const config = changes[0]?.config;
5888
- await runMigration(adapter, true, changes, config);
5889
- }
5890
- });
5891
- };
5892
- const makeMigrateAdapter = (config) => {
5893
- const conf = ensureMigrationsPath(ensureBasePathAndDbScript(config || {}));
5894
- return async (adapter, params) => {
5895
- await migrateAndClose({
5896
- adapter,
5897
- ...params,
5898
- config: {
5899
- ...conf,
5900
- columnTypes: conf.columnTypes || makeColumnTypes(defaultSchemaConfig),
5901
- migrationId: conf.migrationId || migrationConfigDefaults.migrationId,
5902
- migrationsTable: conf.migrationsTable || migrationConfigDefaults.migrationsTable,
5903
- import: conf.import || migrationConfigDefaults.import,
5904
- transaction: conf.transaction || "single"
5905
- }
5906
- });
5907
- };
5908
- };
5909
-
5910
- export { RakeDbError, astToMigration, concatSchemaAndName, createMigrationInterface, dbColumnToAst, encodeColumnDefault, getConstraintName, getDbStructureTableData, getDbTableColumnsChecks, getExcludeName, getIndexName, getMigrationsSchemaAndTable, getSchemaAndTableFromName, instantiateDbColumn, introspectDbSchema, makeChange, makeDomainsMap, makeFileVersion, makeMigrateAdapter, makeStructureToAstCtx, migrate, migrateAndClose, migrateFiles, migrationConfigDefaults, processRakeDbConfig, promptSelect, rakeDbCommands, rakeDbWithAdapters, runCommand, saveMigratedVersion, structureToAst, tableToAst, writeMigrationFile };
6061
+ export { RakeDbError, astToMigration, concatSchemaAndName, createMigrationInterface, dbColumnToAst, encodeColumnDefault, getConstraintName, getDbStructureTableData, getDbTableColumnsChecks, getExcludeName, getIndexName, getMigrationsSchemaAndTable, getSchemaAndTableFromName, incrementIntermediateCaller, instantiateDbColumn, introspectDbSchema, makeDomainsMap, makeFileVersion, makeRakeDbConfig, makeStructureToAstCtx, migrate, migrateAndClose, migrationConfigDefaults, promptSelect, rakeDbCliWithAdapter, rakeDbCommands, redo, rollback, runMigration, saveMigratedVersion, setRakeDbCliRunFn, structureToAst, tableToAst, writeMigrationFile };
5911
6062
  //# sourceMappingURL=index.mjs.map