rake-db 2.0.0 → 2.0.2

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.esm.js CHANGED
@@ -1,27 +1,28 @@
1
- import { toArray, Adapter } from 'pqb';
1
+ import { toArray, Adapter, quote, isRaw, getRaw, getColumnTypes, columnTypes, getTableData, ColumnType, Operators, resetTableData, emptyObject, TransactionAdapter, logParamToLogObject } from 'pqb';
2
2
  import Enquirer from 'enquirer';
3
3
  import path from 'path';
4
- import 'fs/promises';
4
+ import { readdir, mkdir, writeFile } from 'fs/promises';
5
+ import { singular } from 'pluralize';
5
6
 
6
- var __defProp = Object.defineProperty;
7
- var __defProps = Object.defineProperties;
8
- var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
9
- var __getOwnPropSymbols = Object.getOwnPropertySymbols;
10
- var __hasOwnProp = Object.prototype.hasOwnProperty;
11
- var __propIsEnum = Object.prototype.propertyIsEnumerable;
12
- var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
13
- var __spreadValues = (a, b) => {
7
+ var __defProp$3 = Object.defineProperty;
8
+ var __defProps$3 = Object.defineProperties;
9
+ var __getOwnPropDescs$3 = Object.getOwnPropertyDescriptors;
10
+ var __getOwnPropSymbols$3 = Object.getOwnPropertySymbols;
11
+ var __hasOwnProp$3 = Object.prototype.hasOwnProperty;
12
+ var __propIsEnum$3 = Object.prototype.propertyIsEnumerable;
13
+ var __defNormalProp$3 = (obj, key, value) => key in obj ? __defProp$3(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
14
+ var __spreadValues$3 = (a, b) => {
14
15
  for (var prop in b || (b = {}))
15
- if (__hasOwnProp.call(b, prop))
16
- __defNormalProp(a, prop, b[prop]);
17
- if (__getOwnPropSymbols)
18
- for (var prop of __getOwnPropSymbols(b)) {
19
- if (__propIsEnum.call(b, prop))
20
- __defNormalProp(a, prop, b[prop]);
16
+ if (__hasOwnProp$3.call(b, prop))
17
+ __defNormalProp$3(a, prop, b[prop]);
18
+ if (__getOwnPropSymbols$3)
19
+ for (var prop of __getOwnPropSymbols$3(b)) {
20
+ if (__propIsEnum$3.call(b, prop))
21
+ __defNormalProp$3(a, prop, b[prop]);
21
22
  }
22
23
  return a;
23
24
  };
24
- var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
25
+ var __spreadProps$3 = (a, b) => __defProps$3(a, __getOwnPropDescs$3(b));
25
26
  const migrationConfigDefaults = {
26
27
  migrationsPath: path.resolve(process.cwd(), "src", "migrations"),
27
28
  migrationsTable: "schemaMigrations",
@@ -30,7 +31,12 @@ const migrationConfigDefaults = {
30
31
  require("ts-node").register({ compilerOptions: { module: "CommonJS" } });
31
32
  }
32
33
  require(path2);
33
- }
34
+ },
35
+ log: true,
36
+ logger: console
37
+ };
38
+ const getMigrationConfigWithDefaults = (config) => {
39
+ return __spreadValues$3(__spreadValues$3({}, migrationConfigDefaults), config);
34
40
  };
35
41
  const getDatabaseAndUserFromOptions = (options) => {
36
42
  if (options.connectionString) {
@@ -58,9 +64,9 @@ const setAdapterOptions = (options, set) => {
58
64
  if (set.password !== void 0) {
59
65
  url.password = set.password;
60
66
  }
61
- return __spreadProps(__spreadValues({}, options), { connectionString: url.toString() });
67
+ return __spreadProps$3(__spreadValues$3({}, options), { connectionString: url.toString() });
62
68
  } else {
63
- return __spreadValues(__spreadValues({}, options), set);
69
+ return __spreadValues$3(__spreadValues$3({}, options), set);
64
70
  }
65
71
  };
66
72
  const askAdminCredentials = async () => {
@@ -93,7 +99,9 @@ const setAdminCredentialsToOptions = async (options) => {
93
99
  const createSchemaMigrations = async (db, config) => {
94
100
  try {
95
101
  await db.query(
96
- `CREATE TABLE "${config.migrationsTable}" ( version TEXT NOT NULL )`
102
+ `CREATE TABLE ${quoteTable(
103
+ config.migrationsTable
104
+ )} ( version TEXT NOT NULL )`
97
105
  );
98
106
  console.log("Created versions table");
99
107
  } catch (err) {
@@ -104,6 +112,79 @@ const createSchemaMigrations = async (db, config) => {
104
112
  }
105
113
  }
106
114
  };
115
+ const getFirstWordAndRest = (input) => {
116
+ const index = input.search(/(?=[A-Z])|[-_]/);
117
+ if (index !== -1) {
118
+ const restStart = input[index] === "-" || input[index] === "_" ? index + 1 : index;
119
+ const rest = input.slice(restStart);
120
+ return [input.slice(0, index), rest[0].toLowerCase() + rest.slice(1)];
121
+ } else {
122
+ return [input];
123
+ }
124
+ };
125
+ const getTextAfterRegExp = (input, regex, length) => {
126
+ let index = input.search(regex);
127
+ if (index === -1)
128
+ return;
129
+ if (input[index] === "-" || input[index] === "_")
130
+ index++;
131
+ index += length;
132
+ const start = input[index] == "-" || input[index] === "_" ? index + 1 : index;
133
+ const text = input.slice(start);
134
+ return text[0].toLowerCase() + text.slice(1);
135
+ };
136
+ const getTextAfterTo = (input) => {
137
+ return getTextAfterRegExp(input, /(To|-to|_to)[A-Z-_]/, 2);
138
+ };
139
+ const getTextAfterFrom = (input) => {
140
+ return getTextAfterRegExp(input, /(From|-from|_from)[A-Z-_]/, 4);
141
+ };
142
+ const getMigrationFiles = async (config, up) => {
143
+ const { migrationsPath } = config;
144
+ let files;
145
+ try {
146
+ files = await readdir(migrationsPath);
147
+ } catch (_) {
148
+ return [];
149
+ }
150
+ const sort = up ? sortAsc : sortDesc;
151
+ return sort(files).map((file) => {
152
+ if (!file.endsWith(".ts")) {
153
+ throw new Error(
154
+ `Only .ts files are supported for migration, received: ${file}`
155
+ );
156
+ }
157
+ const timestampMatch = file.match(/^(\d{14})\D/);
158
+ if (!timestampMatch) {
159
+ throw new Error(
160
+ `Migration file name should start with 14 digit version, received ${file}`
161
+ );
162
+ }
163
+ return {
164
+ path: path.join(migrationsPath, file),
165
+ version: timestampMatch[1]
166
+ };
167
+ });
168
+ };
169
+ const sortAsc = (arr) => arr.sort();
170
+ const sortDesc = (arr) => arr.sort((a, b) => a > b ? -1 : 1);
171
+ const joinWords = (...words) => {
172
+ return words.slice(1).reduce(
173
+ (acc, word) => acc + word[0].toUpperCase() + word.slice(1),
174
+ words[0]
175
+ );
176
+ };
177
+ const joinColumns = (columns) => {
178
+ return columns.map((column) => `"${column}"`).join(", ");
179
+ };
180
+ const quoteTable = (table) => {
181
+ const index = table.indexOf(".");
182
+ if (index !== -1) {
183
+ return `"${table.slice(0, index)}"."${table.slice(index + 1)}"`;
184
+ } else {
185
+ return `"${table}"`;
186
+ }
187
+ };
107
188
 
108
189
  const execute = async (options, sql) => {
109
190
  const db = new Adapter(options);
@@ -182,9 +263,898 @@ const dropDb = async (arg) => {
182
263
  }
183
264
  };
184
265
 
266
+ const generate = async (config, args) => {
267
+ const name = args[0];
268
+ if (!name)
269
+ throw new Error("Migration name is missing");
270
+ await mkdir(config.migrationsPath, { recursive: true });
271
+ const filePath = path.resolve(
272
+ config.migrationsPath,
273
+ `${makeFileTimeStamp()}_${name}.ts`
274
+ );
275
+ await writeFile(filePath, makeContent(name, args.slice(1)));
276
+ console.log(`Created ${filePath}`);
277
+ };
278
+ const makeFileTimeStamp = () => {
279
+ const now = new Date();
280
+ return [
281
+ now.getUTCFullYear(),
282
+ now.getUTCMonth() + 1,
283
+ now.getUTCDate(),
284
+ now.getUTCHours(),
285
+ now.getUTCMinutes(),
286
+ now.getUTCSeconds()
287
+ ].map((value) => value < 10 ? `0${value}` : value).join("");
288
+ };
289
+ const makeContent = (name, args) => {
290
+ let content = `import { change } from 'rake-db';
291
+
292
+ change(async (db) => {`;
293
+ const [first, rest] = getFirstWordAndRest(name);
294
+ if (rest) {
295
+ if (first === "create" || first === "drop") {
296
+ content += `
297
+ await db.${first === "create" ? "createTable" : "dropTable"}('${rest}', (t) => ({`;
298
+ content += makeColumnsContent(args);
299
+ content += "\n }));";
300
+ } else if (first === "change") {
301
+ content += `
302
+ await db.changeTable('${rest}', (t) => ({`;
303
+ content += "\n }));";
304
+ } else if (first === "add" || first === "remove") {
305
+ const table = first === "add" ? getTextAfterTo(rest) : getTextAfterFrom(rest);
306
+ content += `
307
+ await db.changeTable(${table ? `'${table}'` : "tableName"}, (t) => ({`;
308
+ content += makeColumnsContent(args, first);
309
+ content += "\n }));";
310
+ }
311
+ }
312
+ return content + "\n});\n";
313
+ };
314
+ const makeColumnsContent = (args, method) => {
315
+ let content = "";
316
+ const prepend = method ? `t.${method}(` : "";
317
+ const append = method ? ")" : "";
318
+ for (const arg of args) {
319
+ const [name, def] = arg.split(":");
320
+ if (!def) {
321
+ throw new Error(
322
+ `Column argument should be similar to name:type, name:type.method1.method2, name:type(arg).method(arg). Example: name:varchar(20).nullable. Received: ${arg}`
323
+ );
324
+ }
325
+ const methods = def.split(".").map((method2) => method2.endsWith(")") ? `.${method2}` : `.${method2}()`);
326
+ content += `
327
+ ${name}: ${prepend}t${methods.join("")}${append},`;
328
+ }
329
+ return content;
330
+ };
331
+
332
+ let currentMigration;
333
+ let currentPromise;
334
+ let currentUp = true;
185
335
  const change = (fn) => {
186
- throw new Error("Database instance is not set");
336
+ if (!currentMigration)
337
+ throw new Error("Database instance is not set");
338
+ currentPromise = fn(currentMigration, currentUp);
339
+ };
340
+ const setCurrentMigration = (db) => {
341
+ currentMigration = db;
342
+ };
343
+ const setCurrentMigrationUp = (up) => {
344
+ currentUp = up;
345
+ };
346
+ const getCurrentPromise = () => currentPromise;
347
+
348
+ var __defProp$2 = Object.defineProperty;
349
+ var __defProps$2 = Object.defineProperties;
350
+ var __getOwnPropDescs$2 = Object.getOwnPropertyDescriptors;
351
+ var __getOwnPropSymbols$2 = Object.getOwnPropertySymbols;
352
+ var __hasOwnProp$2 = Object.prototype.hasOwnProperty;
353
+ var __propIsEnum$2 = Object.prototype.propertyIsEnumerable;
354
+ var __defNormalProp$2 = (obj, key, value) => key in obj ? __defProp$2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
355
+ var __spreadValues$2 = (a, b) => {
356
+ for (var prop in b || (b = {}))
357
+ if (__hasOwnProp$2.call(b, prop))
358
+ __defNormalProp$2(a, prop, b[prop]);
359
+ if (__getOwnPropSymbols$2)
360
+ for (var prop of __getOwnPropSymbols$2(b)) {
361
+ if (__propIsEnum$2.call(b, prop))
362
+ __defNormalProp$2(a, prop, b[prop]);
363
+ }
364
+ return a;
365
+ };
366
+ var __spreadProps$2 = (a, b) => __defProps$2(a, __getOwnPropDescs$2(b));
367
+ const columnToSql = (key, item, { values }) => {
368
+ const line = [`"${key}" ${item.toSQL()}`];
369
+ if (item.data.compression) {
370
+ line.push(`COMPRESSION ${item.data.compression}`);
371
+ }
372
+ if (item.data.collate) {
373
+ line.push(`COLLATE ${quote(item.data.collate)}`);
374
+ }
375
+ if (item.isPrimaryKey) {
376
+ line.push("PRIMARY KEY");
377
+ } else if (!item.isNullable) {
378
+ line.push("NOT NULL");
379
+ }
380
+ if (item.data.default !== void 0) {
381
+ if (typeof item.data.default === "object" && item.data.default && isRaw(item.data.default)) {
382
+ line.push(`DEFAULT ${getRaw(item.data.default, values)}`);
383
+ } else {
384
+ line.push(`DEFAULT ${quote(item.data.default)}`);
385
+ }
386
+ }
387
+ const { foreignKey } = item.data;
388
+ if (foreignKey) {
389
+ const table = getForeignKeyTable(
390
+ "fn" in foreignKey ? foreignKey.fn : foreignKey.table
391
+ );
392
+ if (foreignKey.name) {
393
+ line.push(`CONSTRAINT "${foreignKey.name}"`);
394
+ }
395
+ line.push(referencesToSql(table, foreignKey.columns, foreignKey));
396
+ }
397
+ return line.join(" ");
398
+ };
399
+ const addColumnIndex = (indexes, key, item) => {
400
+ if (item.data) {
401
+ if (item.data.index) {
402
+ indexes.push({
403
+ columns: [__spreadProps$2(__spreadValues$2({}, item.data.index), { column: key })],
404
+ options: item.data.index
405
+ });
406
+ }
407
+ }
408
+ };
409
+ const addColumnComment = (comments, key, item) => {
410
+ if (item.data.comment) {
411
+ comments.push({ column: key, comment: item.data.comment });
412
+ }
413
+ };
414
+ const getForeignKeyTable = (fnOrTable) => {
415
+ if (typeof fnOrTable === "string") {
416
+ return fnOrTable;
417
+ }
418
+ const klass = fnOrTable();
419
+ return new klass().table;
420
+ };
421
+ const constraintToSql = (tableName, up, foreignKey) => {
422
+ const table = getForeignKeyTable(foreignKey.fnOrTable);
423
+ const constraintName = foreignKey.options.name || joinWords(tableName, "to", table);
424
+ if (!up) {
425
+ const { dropMode } = foreignKey.options;
426
+ return `CONSTRAINT "${constraintName}"${dropMode ? ` ${dropMode}` : ""}`;
427
+ }
428
+ return `CONSTRAINT "${constraintName}" FOREIGN KEY (${joinColumns(
429
+ foreignKey.columns
430
+ )}) ${referencesToSql(table, foreignKey.foreignColumns, foreignKey.options)}`;
431
+ };
432
+ const referencesToSql = (table, columns, foreignKey) => {
433
+ const sql = [
434
+ `REFERENCES ${quoteTable(table)}(${joinColumns(columns)})`
435
+ ];
436
+ if (foreignKey.match) {
437
+ sql.push(`MATCH ${foreignKey.match.toUpperCase()}`);
438
+ }
439
+ if (foreignKey.onDelete) {
440
+ sql.push(`ON DELETE ${foreignKey.onDelete.toUpperCase()}`);
441
+ }
442
+ if (foreignKey.onUpdate) {
443
+ sql.push(`ON UPDATE ${foreignKey.onUpdate.toUpperCase()}`);
444
+ }
445
+ return sql.join(" ");
446
+ };
447
+ const migrateIndexes = async (state, indexes, up) => {
448
+ for (const item of indexes) {
449
+ await migrateIndex(state, up, item);
450
+ }
451
+ };
452
+ const migrateIndex = (state, up, { columns, options }) => {
453
+ const indexName = options.name || joinWords(state.tableName, ...columns.map(({ column }) => column), "index");
454
+ if (!up) {
455
+ return state.migration.query(
456
+ `DROP INDEX "${indexName}"${options.dropMode ? ` ${options.dropMode}` : ""}`
457
+ );
458
+ }
459
+ const values = [];
460
+ const sql = ["CREATE"];
461
+ if (options.unique) {
462
+ sql.push("UNIQUE");
463
+ }
464
+ sql.push(`INDEX "${indexName}" ON ${quoteTable(state.tableName)}`);
465
+ if (options.using) {
466
+ sql.push(`USING ${options.using}`);
467
+ }
468
+ const columnsSql = [];
469
+ columns.forEach((column) => {
470
+ const columnSql = [
471
+ `"${column.column}"${column.expression ? `(${column.expression})` : ""}`
472
+ ];
473
+ if (column.collate) {
474
+ columnSql.push(`COLLATE '${column.collate}'`);
475
+ }
476
+ if (column.operator) {
477
+ columnSql.push(column.operator);
478
+ }
479
+ if (column.order) {
480
+ columnSql.push(column.order);
481
+ }
482
+ columnsSql.push(columnSql.join(" "));
483
+ });
484
+ sql.push(`(${columnsSql.join(", ")})`);
485
+ if (options.include) {
486
+ sql.push(
487
+ `INCLUDE (${toArray(options.include).map((column) => `"${column}"`).join(", ")})`
488
+ );
489
+ }
490
+ if (options.with) {
491
+ sql.push(`WITH (${options.with})`);
492
+ }
493
+ if (options.tablespace) {
494
+ sql.push(`TABLESPACE ${options.tablespace}`);
495
+ }
496
+ if (options.where) {
497
+ sql.push(
498
+ `WHERE ${typeof options.where === "object" && options.where && isRaw(options.where) ? getRaw(options.where, values) : options.where}`
499
+ );
500
+ }
501
+ return state.migration.query({ text: sql.join(" "), values });
502
+ };
503
+ const migrateComments = async (state, comments) => {
504
+ for (const { column, comment } of comments) {
505
+ await state.migration.query(
506
+ `COMMENT ON COLUMN ${quoteTable(state.tableName)}."${column}" IS ${quote(
507
+ comment
508
+ )}`
509
+ );
510
+ }
511
+ };
512
+ const primaryKeyToSql = (primaryKey) => {
513
+ var _a;
514
+ const name = (_a = primaryKey.options) == null ? void 0 : _a.name;
515
+ return `${name ? `CONSTRAINT "${name}" ` : ""}PRIMARY KEY (${joinColumns(
516
+ primaryKey.columns
517
+ )})`;
518
+ };
519
+ const getPrimaryKeysOfTable = async (db, tableName) => {
520
+ const { rows } = await db.query(
521
+ {
522
+ text: `SELECT
523
+ pg_attribute.attname AS name,
524
+ format_type(pg_attribute.atttypid, pg_attribute.atttypmod) AS type
525
+ FROM pg_index, pg_class, pg_attribute, pg_namespace
526
+ WHERE
527
+ pg_class.oid = $1::regclass AND
528
+ indrelid = pg_class.oid AND
529
+ nspname = 'public' AND
530
+ pg_class.relnamespace = pg_namespace.oid AND
531
+ pg_attribute.attrelid = pg_class.oid AND
532
+ pg_attribute.attnum = any(pg_index.indkey) AND
533
+ indisprimary`,
534
+ values: [tableName]
535
+ },
536
+ db.types,
537
+ void 0
538
+ );
539
+ return rows;
540
+ };
541
+
542
+ var __defProp$1 = Object.defineProperty;
543
+ var __defProps$1 = Object.defineProperties;
544
+ var __getOwnPropDescs$1 = Object.getOwnPropertyDescriptors;
545
+ var __getOwnPropSymbols$1 = Object.getOwnPropertySymbols;
546
+ var __hasOwnProp$1 = Object.prototype.hasOwnProperty;
547
+ var __propIsEnum$1 = Object.prototype.propertyIsEnumerable;
548
+ var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
549
+ var __spreadValues$1 = (a, b) => {
550
+ for (var prop in b || (b = {}))
551
+ if (__hasOwnProp$1.call(b, prop))
552
+ __defNormalProp$1(a, prop, b[prop]);
553
+ if (__getOwnPropSymbols$1)
554
+ for (var prop of __getOwnPropSymbols$1(b)) {
555
+ if (__propIsEnum$1.call(b, prop))
556
+ __defNormalProp$1(a, prop, b[prop]);
557
+ }
558
+ return a;
559
+ };
560
+ var __spreadProps$1 = (a, b) => __defProps$1(a, __getOwnPropDescs$1(b));
561
+ class UnknownColumn extends ColumnType {
562
+ constructor(dataType) {
563
+ super();
564
+ this.dataType = dataType;
565
+ this.operators = Operators.any;
566
+ }
567
+ }
568
+ const createJoinTable = async (migration, up, tables, options, fn) => {
569
+ const tableName = options.tableName || joinWords(...tables);
570
+ if (!up) {
571
+ return createTable(migration, up, tableName, options, () => ({}));
572
+ }
573
+ const tablesWithPrimaryKeys = await Promise.all(
574
+ tables.map(async (table) => {
575
+ const primaryKeys = await getPrimaryKeysOfTable(migration, table).then(
576
+ (items) => items.map((item) => __spreadProps$1(__spreadValues$1({}, item), {
577
+ joinedName: joinWords(singular(table), item.name)
578
+ }))
579
+ );
580
+ if (!primaryKeys.length) {
581
+ throw new Error(
582
+ `Primary key for table ${quoteTable(table)} is not defined`
583
+ );
584
+ }
585
+ return [table, primaryKeys];
586
+ })
587
+ );
588
+ return createTable(migration, up, tableName, options, (t) => {
589
+ const result = {};
590
+ tablesWithPrimaryKeys.forEach(([table, primaryKeys]) => {
591
+ if (primaryKeys.length === 1) {
592
+ const [{ type, joinedName, name }] = primaryKeys;
593
+ const column = new UnknownColumn(type);
594
+ result[joinedName] = column.foreignKey(table, name);
595
+ return;
596
+ }
597
+ primaryKeys.forEach(({ joinedName, type }) => {
598
+ result[joinedName] = new UnknownColumn(type);
599
+ });
600
+ t.foreignKey(
601
+ primaryKeys.map((key) => key.joinedName),
602
+ table,
603
+ primaryKeys.map((key) => key.name)
604
+ );
605
+ });
606
+ if (fn) {
607
+ Object.assign(result, fn(t));
608
+ }
609
+ t.primaryKey(
610
+ tablesWithPrimaryKeys.flatMap(
611
+ ([, primaryKeys]) => primaryKeys.map((item) => item.joinedName)
612
+ )
613
+ );
614
+ return result;
615
+ });
616
+ };
617
+ const createTable = async (migration, up, tableName, options, fn) => {
618
+ const shape = getColumnTypes(columnTypes, fn);
619
+ if (!up) {
620
+ const { dropMode } = options;
621
+ await migration.query(
622
+ `DROP TABLE ${quoteTable(tableName)}${dropMode ? ` ${dropMode}` : ""}`
623
+ );
624
+ return;
625
+ }
626
+ const lines = [];
627
+ const state = {
628
+ migration,
629
+ tableName,
630
+ values: [],
631
+ indexes: [],
632
+ comments: []
633
+ };
634
+ for (const key in shape) {
635
+ const item = shape[key];
636
+ addColumnIndex(state.indexes, key, item);
637
+ addColumnComment(state.comments, key, item);
638
+ lines.push(`
639
+ ${columnToSql(key, item, state)}`);
640
+ }
641
+ const tableData = getTableData();
642
+ if (tableData.primaryKey) {
643
+ lines.push(`
644
+ ${primaryKeyToSql(tableData.primaryKey)}`);
645
+ }
646
+ tableData.foreignKeys.forEach((foreignKey) => {
647
+ lines.push(`
648
+ ${constraintToSql(state.tableName, up, foreignKey)}`);
649
+ });
650
+ await migration.query({
651
+ text: `CREATE TABLE ${quoteTable(tableName)} (${lines.join(",")}
652
+ )`,
653
+ values: state.values
654
+ });
655
+ state.indexes.push(...tableData.indexes);
656
+ await migrateIndexes(state, state.indexes, up);
657
+ await migrateComments(state, state.comments);
658
+ if (options.comment) {
659
+ await migration.query(
660
+ `COMMENT ON TABLE ${quoteTable(tableName)} IS ${quote(options.comment)}`
661
+ );
662
+ }
663
+ };
664
+
665
+ const newChangeTableData = () => ({
666
+ add: [],
667
+ drop: []
668
+ });
669
+ let changeTableData = newChangeTableData();
670
+ const resetChangeTableData = () => {
671
+ changeTableData = newChangeTableData();
672
+ };
673
+ function add(item, options) {
674
+ if (item instanceof ColumnType) {
675
+ return ["add", item, options];
676
+ } else if (item === emptyObject) {
677
+ changeTableData.add.push(getTableData());
678
+ resetTableData();
679
+ return emptyObject;
680
+ } else {
681
+ const result = {};
682
+ for (const key in item) {
683
+ result[key] = ["add", item[key], options];
684
+ }
685
+ return result;
686
+ }
687
+ }
688
+ const drop = (item, options) => {
689
+ if (item instanceof ColumnType) {
690
+ return ["drop", item, options];
691
+ } else if (item === emptyObject) {
692
+ changeTableData.drop.push(getTableData());
693
+ resetTableData();
694
+ return emptyObject;
695
+ } else {
696
+ const result = {};
697
+ for (const key in item) {
698
+ result[key] = [
699
+ "drop",
700
+ item[key],
701
+ options
702
+ ];
703
+ }
704
+ return result;
705
+ }
706
+ };
707
+ const tableChangeMethods = {
708
+ add,
709
+ drop,
710
+ change(from, to, options) {
711
+ return ["change", from, to, options];
712
+ },
713
+ default(value) {
714
+ return ["default", value];
715
+ },
716
+ nullable() {
717
+ return ["nullable", true];
718
+ },
719
+ nonNullable() {
720
+ return ["nullable", false];
721
+ },
722
+ comment(name) {
723
+ return ["comment", name];
724
+ },
725
+ rename(name) {
726
+ return ["rename", name];
727
+ }
728
+ };
729
+ const changeTable = async (migration, up, tableName, options, fn) => {
730
+ resetTableData();
731
+ resetChangeTableData();
732
+ const tableChanger = Object.create(columnTypes);
733
+ Object.assign(tableChanger, tableChangeMethods);
734
+ const changeData = (fn == null ? void 0 : fn(tableChanger)) || {};
735
+ const state = {
736
+ migration,
737
+ up,
738
+ tableName,
739
+ alterTable: [],
740
+ values: [],
741
+ indexes: [],
742
+ dropIndexes: [],
743
+ comments: []
744
+ };
745
+ if (options.comment !== void 0) {
746
+ await changeActions.tableComment(state, tableName, options.comment);
747
+ }
748
+ for (const key in changeData) {
749
+ const result = changeData[key];
750
+ if (Array.isArray(result)) {
751
+ const [action] = result;
752
+ if (action === "change") {
753
+ const [, from, to, options2] = result;
754
+ changeActions.change(state, up, key, from, to, options2);
755
+ } else if (action === "rename") {
756
+ const [, name] = result;
757
+ changeActions.rename(state, up, key, name);
758
+ } else {
759
+ const [action2, item, options2] = result;
760
+ changeActions[action2](state, up, key, item, options2);
761
+ }
762
+ }
763
+ }
764
+ changeTableData.add.forEach((tableData) => {
765
+ handleTableData(state, up, tableName, tableData);
766
+ });
767
+ changeTableData.drop.forEach((tableData) => {
768
+ handleTableData(state, !up, tableName, tableData);
769
+ });
770
+ if (state.alterTable.length) {
771
+ await migration.query(
772
+ `ALTER TABLE ${quoteTable(tableName)}
773
+ ${state.alterTable.join(",\n ")}`
774
+ );
775
+ }
776
+ const createIndexes = up ? state.indexes : state.dropIndexes;
777
+ const dropIndexes = up ? state.dropIndexes : state.indexes;
778
+ await migrateIndexes(state, createIndexes, up);
779
+ await migrateIndexes(state, dropIndexes, !up);
780
+ await migrateComments(state, state.comments);
781
+ };
782
+ const changeActions = {
783
+ tableComment({ migration, up }, tableName, comment) {
784
+ let value;
785
+ if (up) {
786
+ value = Array.isArray(comment) ? comment[1] : comment;
787
+ } else {
788
+ value = Array.isArray(comment) ? comment[0] : null;
789
+ }
790
+ return migration.query(
791
+ `COMMENT ON TABLE ${quoteTable(tableName)} IS ${quote(value)}`
792
+ );
793
+ },
794
+ add(state, up, key, item, options) {
795
+ addColumnIndex(state[up ? "indexes" : "dropIndexes"], key, item);
796
+ if (up) {
797
+ addColumnComment(state.comments, key, item);
798
+ }
799
+ if (up) {
800
+ state.alterTable.push(`ADD COLUMN ${columnToSql(key, item, state)}`);
801
+ } else {
802
+ state.alterTable.push(
803
+ `DROP COLUMN "${key}"${(options == null ? void 0 : options.dropMode) ? ` ${options.dropMode}` : ""}`
804
+ );
805
+ }
806
+ },
807
+ drop(state, up, key, item, options) {
808
+ this.add(state, !up, key, item, options);
809
+ },
810
+ change(state, up, key, first, second, options) {
811
+ const [fromItem, toItem] = up ? [first, second] : [second, first];
812
+ const from = getChangeProperties(fromItem);
813
+ const to = getChangeProperties(toItem);
814
+ if (from.type !== to.type || from.collate !== to.collate) {
815
+ const using = up ? options == null ? void 0 : options.usingUp : options == null ? void 0 : options.usingDown;
816
+ state.alterTable.push(
817
+ `ALTER COLUMN "${key}" TYPE ${to.type}${to.collate ? ` COLLATE ${quote(to.collate)}` : ""}${using ? ` USING ${getRaw(using, state.values)}` : ""}`
818
+ );
819
+ }
820
+ if (from.default !== to.default) {
821
+ const value = getRawOrValue(to.default, state.values);
822
+ const expr = value === void 0 ? `DROP DEFAULT` : `SET DEFAULT ${value}`;
823
+ state.alterTable.push(`ALTER COLUMN "${key}" ${expr}`);
824
+ }
825
+ if (from.nullable !== to.nullable) {
826
+ state.alterTable.push(
827
+ `ALTER COLUMN "${key}" ${to.nullable ? "DROP" : "SET"} NOT NULL`
828
+ );
829
+ }
830
+ if (from.comment !== to.comment) {
831
+ state.comments.push({ column: key, comment: to.comment || null });
832
+ }
833
+ },
834
+ rename(state, up, key, name) {
835
+ const [from, to] = up ? [key, name] : [name, key];
836
+ state.alterTable.push(`RENAME COLUMN "${from}" TO "${to}"`);
837
+ }
838
+ };
839
+ const getChangeProperties = (item) => {
840
+ if (item instanceof ColumnType) {
841
+ return {
842
+ type: item.toSQL(),
843
+ collate: item.data.collate,
844
+ default: item.data.default,
845
+ nullable: item.isNullable,
846
+ comment: item.data.comment
847
+ };
848
+ } else {
849
+ return {
850
+ type: void 0,
851
+ collate: void 0,
852
+ default: item[0] === "default" ? item[1] : void 0,
853
+ nullable: item[0] === "nullable" ? item[1] : void 0,
854
+ comment: item[0] === "comment" ? item[1] : void 0
855
+ };
856
+ }
857
+ };
858
+ const handleTableData = (state, up, tableName, tableData) => {
859
+ var _a;
860
+ if (tableData.primaryKey) {
861
+ if (up) {
862
+ state.alterTable.push(`ADD ${primaryKeyToSql(tableData.primaryKey)}`);
863
+ } else {
864
+ const name = ((_a = tableData.primaryKey.options) == null ? void 0 : _a.name) || `${tableName}_pkey`;
865
+ state.alterTable.push(`DROP CONSTRAINT "${name}"`);
866
+ }
867
+ }
868
+ if (tableData.indexes.length) {
869
+ state[up ? "indexes" : "dropIndexes"].push(...tableData.indexes);
870
+ }
871
+ if (tableData.foreignKeys.length) {
872
+ tableData.foreignKeys.forEach((foreignKey) => {
873
+ const action = up ? "ADD" : "DROP";
874
+ state.alterTable.push(
875
+ `
876
+ ${action} ${constraintToSql(state.tableName, up, foreignKey)}`
877
+ );
878
+ });
879
+ }
880
+ };
881
+ const getRawOrValue = (item, values) => {
882
+ return typeof item === "object" && item && isRaw(item) ? getRaw(item, values) : quote(item);
883
+ };
884
+
885
+ var __defProp = Object.defineProperty;
886
+ var __defProps = Object.defineProperties;
887
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
888
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
889
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
890
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
891
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
892
+ var __spreadValues = (a, b) => {
893
+ for (var prop in b || (b = {}))
894
+ if (__hasOwnProp.call(b, prop))
895
+ __defNormalProp(a, prop, b[prop]);
896
+ if (__getOwnPropSymbols)
897
+ for (var prop of __getOwnPropSymbols(b)) {
898
+ if (__propIsEnum.call(b, prop))
899
+ __defNormalProp(a, prop, b[prop]);
900
+ }
901
+ return a;
902
+ };
903
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
904
+ class Migration extends TransactionAdapter {
905
+ constructor(tx, up, options) {
906
+ super(tx.pool, tx.client, tx.types);
907
+ this.up = up;
908
+ this.log = logParamToLogObject(options.logger || console, options.log);
909
+ }
910
+ async query(query, types = this.types, log = this.log) {
911
+ return wrapWithLog(log, query, () => super.query(query, types));
912
+ }
913
+ async arrays(query, types = this.types, log = this.log) {
914
+ return wrapWithLog(log, query, () => super.arrays(query, types));
915
+ }
916
+ createTable(tableName, cbOrOptions, cb) {
917
+ const options = typeof cbOrOptions === "function" ? {} : cbOrOptions;
918
+ const fn = cb || cbOrOptions;
919
+ return createTable(this, this.up, tableName, options, fn);
920
+ }
921
+ dropTable(tableName, cbOrOptions, cb) {
922
+ const options = typeof cbOrOptions === "function" ? {} : cbOrOptions;
923
+ const fn = cb || cbOrOptions;
924
+ return createTable(this, !this.up, tableName, options, fn);
925
+ }
926
+ async createJoinTable(tables, cbOrOptions, cb) {
927
+ const options = typeof cbOrOptions === "function" ? {} : cbOrOptions || {};
928
+ const fn = cb || cbOrOptions;
929
+ return createJoinTable(this, this.up, tables, options, fn);
930
+ }
931
+ async dropJoinTable(tables, cbOrOptions, cb) {
932
+ const options = typeof cbOrOptions === "function" ? {} : cbOrOptions || {};
933
+ const fn = cb || cbOrOptions;
934
+ return createJoinTable(this, !this.up, tables, options, fn);
935
+ }
936
+ changeTable(tableName, cbOrOptions, cb) {
937
+ const [fn, options] = typeof cbOrOptions === "function" ? [cbOrOptions, {}] : [cb, cbOrOptions];
938
+ return changeTable(this, this.up, tableName, options, fn);
939
+ }
940
+ async renameTable(from, to) {
941
+ const [table, newName] = this.up ? [from, to] : [to, from];
942
+ await this.query(`ALTER TABLE ${quoteTable(table)} RENAME TO "${newName}"`);
943
+ }
944
+ addColumn(tableName, columnName, fn) {
945
+ return addColumn(this, this.up, tableName, columnName, fn);
946
+ }
947
+ dropColumn(tableName, columnName, fn) {
948
+ return addColumn(this, !this.up, tableName, columnName, fn);
949
+ }
950
+ addIndex(tableName, columns, options) {
951
+ return addIndex(this, this.up, tableName, columns, options);
952
+ }
953
+ dropIndex(tableName, columns, options) {
954
+ return addIndex(this, !this.up, tableName, columns, options);
955
+ }
956
+ addForeignKey(tableName, columns, foreignTable, foreignColumns, options) {
957
+ return addForeignKey(
958
+ this,
959
+ this.up,
960
+ tableName,
961
+ columns,
962
+ foreignTable,
963
+ foreignColumns,
964
+ options
965
+ );
966
+ }
967
+ dropForeignKey(tableName, columns, foreignTable, foreignColumns, options) {
968
+ return addForeignKey(
969
+ this,
970
+ !this.up,
971
+ tableName,
972
+ columns,
973
+ foreignTable,
974
+ foreignColumns,
975
+ options
976
+ );
977
+ }
978
+ addPrimaryKey(tableName, columns, options) {
979
+ return addPrimaryKey(this, this.up, tableName, columns, options);
980
+ }
981
+ dropPrimaryKey(tableName, columns, options) {
982
+ return addPrimaryKey(this, !this.up, tableName, columns, options);
983
+ }
984
+ renameColumn(tableName, from, to) {
985
+ return this.changeTable(tableName, (t) => ({
986
+ [from]: t.rename(to)
987
+ }));
988
+ }
989
+ createSchema(schemaName) {
990
+ return createSchema(this, this.up, schemaName);
991
+ }
992
+ dropSchema(schemaName) {
993
+ return createSchema(this, !this.up, schemaName);
994
+ }
995
+ createExtension(name, options = {}) {
996
+ return createExtension(this, this.up, name, __spreadProps(__spreadValues({}, options), {
997
+ checkExists: options.ifNotExists
998
+ }));
999
+ }
1000
+ dropExtension(name, options = {}) {
1001
+ return createExtension(this, !this.up, name, __spreadProps(__spreadValues({}, options), {
1002
+ checkExists: options.ifExists
1003
+ }));
1004
+ }
1005
+ async tableExists(tableName) {
1006
+ return queryExists(this, {
1007
+ text: `SELECT 1 FROM "information_schema"."tables" WHERE "table_name" = $1`,
1008
+ values: [tableName]
1009
+ });
1010
+ }
1011
+ async columnExists(tableName, columnName) {
1012
+ return queryExists(this, {
1013
+ text: `SELECT 1 FROM "information_schema"."columns" WHERE "table_name" = $1 AND "column_name" = $2`,
1014
+ values: [tableName, columnName]
1015
+ });
1016
+ }
1017
+ async constraintExists(constraintName) {
1018
+ return queryExists(this, {
1019
+ text: `SELECT 1 FROM "information_schema"."table_constraints" WHERE "constraint_name" = $1`,
1020
+ values: [constraintName]
1021
+ });
1022
+ }
1023
+ }
1024
+ const wrapWithLog = async (log, query, fn) => {
1025
+ if (!log) {
1026
+ return fn();
1027
+ } else {
1028
+ const sql = typeof query === "string" ? { text: query, values: [] } : query.values ? query : __spreadProps(__spreadValues({}, query), { values: [] });
1029
+ const logData = log.beforeQuery(sql);
1030
+ try {
1031
+ const result = await fn();
1032
+ log.afterQuery(sql, logData);
1033
+ return result;
1034
+ } catch (err) {
1035
+ log.onError(err, sql, logData);
1036
+ throw err;
1037
+ }
1038
+ }
1039
+ };
1040
+ const addColumn = (migration, up, tableName, columnName, fn) => {
1041
+ return changeTable(migration, up, tableName, {}, (t) => ({
1042
+ [columnName]: t.add(fn(t))
1043
+ }));
1044
+ };
1045
+ const addIndex = (migration, up, tableName, columns, options) => {
1046
+ return changeTable(migration, up, tableName, {}, (t) => __spreadValues({}, t.add(t.index(columns, options))));
1047
+ };
1048
+ const addForeignKey = (migration, up, tableName, columns, foreignTable, foreignColumns, options) => {
1049
+ return changeTable(migration, up, tableName, {}, (t) => __spreadValues({}, t.add(t.foreignKey(columns, foreignTable, foreignColumns, options))));
1050
+ };
1051
+ const addPrimaryKey = (migration, up, tableName, columns, options) => {
1052
+ return changeTable(migration, up, tableName, {}, (t) => __spreadValues({}, t.add(t.primaryKey(columns, options))));
1053
+ };
1054
+ const createSchema = (migration, up, schemaName) => {
1055
+ if (up) {
1056
+ return migration.query(`CREATE SCHEMA "${schemaName}"`);
1057
+ } else {
1058
+ return migration.query(`DROP SCHEMA "${schemaName}"`);
1059
+ }
1060
+ };
1061
+ const createExtension = (migration, up, name, options) => {
1062
+ if (!up) {
1063
+ return migration.query(
1064
+ `DROP EXTENSION${options.checkExists ? " IF EXISTS" : ""} "${name}"${options.cascade ? " CASCADE" : ""}`
1065
+ );
1066
+ }
1067
+ return migration.query(
1068
+ `CREATE EXTENSION${options.checkExists ? " IF NOT EXISTS" : ""} "${name}"${options.schema ? ` SCHEMA "${options.schema}"` : ""}${options.version ? ` VERSION '${options.version}'` : ""}${options.cascade ? " CASCADE" : ""}`
1069
+ );
1070
+ };
1071
+ const queryExists = (db, sql) => {
1072
+ return db.query(sql).then(({ rowCount }) => rowCount > 0);
1073
+ };
1074
+
1075
+ const migrateOrRollback = async (options, config, args, up) => {
1076
+ var _a;
1077
+ const files = await getMigrationFiles(config, up);
1078
+ const argCount = parseInt(args[0]);
1079
+ let count = isNaN(argCount) ? up ? Infinity : 1 : argCount;
1080
+ for (const opts of toArray(options)) {
1081
+ const db = new Adapter(opts);
1082
+ const migratedVersions = await getMigratedVersionsMap(db, config);
1083
+ try {
1084
+ for (const file of files) {
1085
+ if (up && migratedVersions[file.version] || !up && !migratedVersions[file.version]) {
1086
+ continue;
1087
+ }
1088
+ if (count-- <= 0)
1089
+ break;
1090
+ await processMigration(db, up, file, config);
1091
+ (_a = config.logger) == null ? void 0 : _a.log(`${file.path} ${up ? "migrated" : "rolled back"}`);
1092
+ }
1093
+ } finally {
1094
+ await db.destroy();
1095
+ }
1096
+ }
1097
+ };
1098
+ const processMigration = async (db, up, file, config) => {
1099
+ await db.transaction(async (tx) => {
1100
+ const db2 = new Migration(tx, up, config);
1101
+ setCurrentMigration(db2);
1102
+ setCurrentMigrationUp(up);
1103
+ config.requireTs(file.path);
1104
+ await getCurrentPromise();
1105
+ await (up ? saveMigratedVersion : removeMigratedVersion)(
1106
+ db2,
1107
+ file.version,
1108
+ config
1109
+ );
1110
+ });
1111
+ };
1112
+ const saveMigratedVersion = async (db, version, config) => {
1113
+ await db.query(
1114
+ `INSERT INTO ${quoteTable(config.migrationsTable)} VALUES ('${version}')`
1115
+ );
1116
+ };
1117
+ const removeMigratedVersion = async (db, version, config) => {
1118
+ await db.query(
1119
+ `DELETE FROM ${quoteTable(
1120
+ config.migrationsTable
1121
+ )} WHERE version = '${version}'`
1122
+ );
1123
+ };
1124
+ const getMigratedVersionsMap = async (db, config) => {
1125
+ try {
1126
+ const result = await db.arrays(
1127
+ `SELECT * FROM ${quoteTable(config.migrationsTable)}`
1128
+ );
1129
+ return Object.fromEntries(result.rows.map((row) => [row[0], true]));
1130
+ } catch (err) {
1131
+ if (err.code === "42P01") {
1132
+ await createSchemaMigrations(db, config);
1133
+ return {};
1134
+ }
1135
+ throw err;
1136
+ }
1137
+ };
1138
+ const migrate = (options, config, args) => migrateOrRollback(options, config, args, true);
1139
+ const rollback = (options, config, args) => migrateOrRollback(options, config, args, false);
1140
+
1141
+ const rakeDb = async (options, partialConfig = {}, args = process.argv.slice(2)) => {
1142
+ const config = getMigrationConfigWithDefaults(partialConfig);
1143
+ const command = args[0].split(":")[0];
1144
+ if (command === "create") {
1145
+ await createDb(options, config);
1146
+ } else if (command === "drop") {
1147
+ await dropDb(options);
1148
+ } else if (command === "migrate") {
1149
+ await migrate(options, config, args.slice(1));
1150
+ } else if (command === "rollback") {
1151
+ await rollback(options, config, args.slice(1));
1152
+ } else if (command === "g" || command === "generate") {
1153
+ await generate(config, args.slice(1));
1154
+ } else {
1155
+ console.log(`Usage: rake-db [command] [arguments]`);
1156
+ }
187
1157
  };
188
1158
 
189
- export { change, createDb, dropDb };
1159
+ export { Migration, change, createDb, dropDb, generate, migrate, rakeDb, rollback };
190
1160
  //# sourceMappingURL=index.esm.js.map