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