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