rake-db 2.30.0 → 2.30.1

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
@@ -181,10 +181,6 @@ const promptText = ({
181
181
  };
182
182
 
183
183
  const getMaybeTransactionAdapter = (db) => "$getAdapter" in db ? db.$getAdapter() : db;
184
- const ensureTransaction = (db, fn) => {
185
- const adapter = getMaybeTransactionAdapter(db);
186
- return adapter.isInTransaction() ? fn(adapter) : adapter.transaction(fn);
187
- };
188
184
  const runSqlInSavePoint = async (db, sql, code) => {
189
185
  const adapter = getMaybeTransactionAdapter(db);
190
186
  try {
@@ -315,10 +311,12 @@ const makePopulateEnumQuery = (schema, item) => {
315
311
  const transaction = (adapter, config, fn) => {
316
312
  const searchPath = config.transactionSearchPath;
317
313
  return adapter.transaction(
318
- fn,
319
314
  searchPath ? {
320
- searchPath: typeof searchPath === "function" ? searchPath() : searchPath
321
- } : void 0
315
+ locals: {
316
+ search_path: typeof searchPath === "function" ? searchPath() : searchPath
317
+ }
318
+ } : void 0,
319
+ fn
322
320
  );
323
321
  };
324
322
  const queryLock = (trx) => trx.query(`SELECT pg_advisory_xact_lock('${RAKE_DB_LOCK_KEY}')`);
@@ -792,7 +790,7 @@ const createTable = async (migration, up, tableName, first, second, third) => {
792
790
  shape = tableData = pqb.emptyObject;
793
791
  }
794
792
  const schema = migration.adapter.getSchema();
795
- const ast = makeAst$2(
793
+ const ast = makeAst$3(
796
794
  schema,
797
795
  up,
798
796
  tableName,
@@ -822,7 +820,7 @@ const createTable = async (migration, up, tableName, first, second, third) => {
822
820
  }
823
821
  };
824
822
  };
825
- const makeAst$2 = (schema, up, tableName, shape, tableData, options, noPrimaryKey) => {
823
+ const makeAst$3 = (schema, up, tableName, shape, tableData, options, noPrimaryKey) => {
826
824
  const shapePKeys = [];
827
825
  for (const key in shape) {
828
826
  const column = shape[key];
@@ -1172,7 +1170,7 @@ const changeTable = async (migration, up, tableName, options, fn) => {
1172
1170
  addOrDropChanges.length = 0;
1173
1171
  const changeData = fn?.(tableChanger) || {};
1174
1172
  const schema = migration.adapter.getSchema();
1175
- const ast = makeAst$1(
1173
+ const ast = makeAst$2(
1176
1174
  schema,
1177
1175
  up,
1178
1176
  tableName,
@@ -1186,7 +1184,7 @@ const changeTable = async (migration, up, tableName, options, fn) => {
1186
1184
  query.then?.(result);
1187
1185
  }
1188
1186
  };
1189
- const makeAst$1 = (schema, up, name, changeData, changeTableData2, options) => {
1187
+ const makeAst$2 = (schema, up, name, changeData, changeTableData2, options) => {
1190
1188
  const { comment } = options;
1191
1189
  const shape = {};
1192
1190
  const consumedChanges = {};
@@ -1638,11 +1636,11 @@ const renameColumnSql = (from, to) => {
1638
1636
 
1639
1637
  const createView = async (migration, up, name, options, sql) => {
1640
1638
  const schema = migration.adapter.getSchema();
1641
- const ast = makeAst(schema, up, name, options, sql);
1642
- const query = astToQuery(ast);
1639
+ const ast = makeAst$1(schema, up, name, options, sql);
1640
+ const query = astToQuery$1(ast);
1643
1641
  await migration.adapter.arrays(interpolateSqlValues(query));
1644
1642
  };
1645
- const makeAst = (schema, up, fullName, options, sql) => {
1643
+ const makeAst$1 = (schema, up, fullName, options, sql) => {
1646
1644
  if (typeof sql === "string") {
1647
1645
  sql = pqb.raw({ raw: sql });
1648
1646
  }
@@ -1658,7 +1656,7 @@ const makeAst = (schema, up, fullName, options, sql) => {
1658
1656
  deps: []
1659
1657
  };
1660
1658
  };
1661
- const astToQuery = (ast) => {
1659
+ const astToQuery$1 = (ast) => {
1662
1660
  const values = [];
1663
1661
  const sql = [];
1664
1662
  const { options } = ast;
@@ -1695,6 +1693,130 @@ const astToQuery = (ast) => {
1695
1693
  };
1696
1694
  };
1697
1695
 
1696
+ const serializers = {
1697
+ super: (b) => `${b ? "" : "NO"}SUPERUSER`,
1698
+ inherit: (b) => `${b ? "" : "NO"}INHERIT`,
1699
+ createRole: (b) => `${b ? "" : "NO"}CREATEROLE`,
1700
+ createDb: (b) => `${b ? "" : "NO"}CREATEDB`,
1701
+ canLogin: (b) => `${b ? "" : "NO"}LOGIN`,
1702
+ replication: (b) => `${b ? "" : "NO"}REPLICATION`,
1703
+ bypassRls: (b) => `${b ? "" : "NO"}BYPASSRLS`,
1704
+ connLimit: (value) => `CONNECTION LIMIT ${value === void 0 ? -1 : value}`,
1705
+ validUntil: (value) => `VALID UNTIL '${value === void 0 ? "infinity" : value}'`
1706
+ };
1707
+ const createOrDropRole = async (migration, up, name, params) => {
1708
+ const ast = makeAst(up, name, params);
1709
+ const sql = astToQuery(ast);
1710
+ await migration.adapter.arrays(sql);
1711
+ };
1712
+ const makeAst = (up, name, params) => {
1713
+ return {
1714
+ type: "role",
1715
+ action: up ? "create" : "drop",
1716
+ name,
1717
+ super: false,
1718
+ inherit: false,
1719
+ createRole: false,
1720
+ createDb: false,
1721
+ canLogin: false,
1722
+ replication: false,
1723
+ connLimit: -1,
1724
+ bypassRls: false,
1725
+ ...params
1726
+ };
1727
+ };
1728
+ const astToQuery = (ast) => {
1729
+ const w = [];
1730
+ if (ast.action !== "drop") {
1731
+ for (const key in ast) {
1732
+ if (key in serializers && (key !== "connLimit" || ast[key] !== -1)) {
1733
+ let value = ast[key];
1734
+ if (value instanceof Date) value = value.toISOString();
1735
+ w.push(serializers[key](value));
1736
+ }
1737
+ }
1738
+ }
1739
+ let sql = `${ast.action.toUpperCase()} ROLE "${ast.name}"${w.length ? ` WITH ${w.join(" ")}` : ""}`;
1740
+ if (ast.action !== "drop" && ast.config) {
1741
+ for (const [key, value] of Object.entries(ast.config)) {
1742
+ sql += `;
1743
+ ALTER ROLE "${ast.name}" SET ${key} = '${value}'`;
1744
+ }
1745
+ }
1746
+ return sql;
1747
+ };
1748
+ const changeRole = async (migration, up, name, from, to) => {
1749
+ if (!up) {
1750
+ if (to.name) {
1751
+ from = { ...from, name };
1752
+ name = to.name;
1753
+ }
1754
+ const f = from;
1755
+ from = to;
1756
+ to = { ...f };
1757
+ for (const key in from) {
1758
+ if (!(key in to)) {
1759
+ to[key] = void 0;
1760
+ }
1761
+ }
1762
+ if (from.config) {
1763
+ const config = to.config ?? (to.config = {});
1764
+ for (const key in from.config) {
1765
+ if (!(key in config)) {
1766
+ config[key] = void 0;
1767
+ }
1768
+ }
1769
+ }
1770
+ }
1771
+ const ast = makeChangeAst(name, from, to);
1772
+ const sql = changeAstToQuery(ast);
1773
+ if (sql) await migration.adapter.arrays(sql);
1774
+ };
1775
+ const makeChangeAst = (name, from, to) => {
1776
+ return {
1777
+ type: "changeRole",
1778
+ name,
1779
+ from,
1780
+ to
1781
+ };
1782
+ };
1783
+ const changeAstToQuery = ({ name, from, to }) => {
1784
+ const queries = [];
1785
+ if (to.name && to.name !== name) {
1786
+ queries.push(`ALTER ROLE "${name}" RENAME TO "${to.name}"`);
1787
+ name = to.name;
1788
+ }
1789
+ const w = [];
1790
+ for (const key in to) {
1791
+ let value = to[key];
1792
+ if (key !== "config") {
1793
+ if (value instanceof Date) value = value.toISOString();
1794
+ let other = from[key];
1795
+ if (other instanceof Date) other = other.toISOString();
1796
+ if (value !== other && key in serializers) {
1797
+ w.push(serializers[key](value));
1798
+ }
1799
+ }
1800
+ }
1801
+ if (w.length) {
1802
+ queries.push(`ALTER ROLE "${name}" WITH ${w.join(" ")}`);
1803
+ }
1804
+ const config = to.config;
1805
+ if (config) {
1806
+ const fromConfig = from.config ?? pqb.emptyObject;
1807
+ for (const key in config) {
1808
+ const value = config[key];
1809
+ const other = fromConfig[key];
1810
+ if (value !== other) {
1811
+ queries.push(
1812
+ `ALTER ROLE "${name}" ${value ? `SET ${key} = '${value}'` : `RESET ${key}`}`
1813
+ );
1814
+ }
1815
+ }
1816
+ }
1817
+ return queries.join(";\n");
1818
+ };
1819
+
1698
1820
  const createMigrationInterface = (tx, up, config) => {
1699
1821
  const adapter = Object.create(tx);
1700
1822
  const { query, arrays } = adapter;
@@ -2578,6 +2700,21 @@ class Migration {
2578
2700
  values: [constraintName]
2579
2701
  });
2580
2702
  }
2703
+ createRole(name, params) {
2704
+ return createOrDropRole(this, this.up, name, params);
2705
+ }
2706
+ dropRole(name, params) {
2707
+ return createOrDropRole(this, !this.up, name, params);
2708
+ }
2709
+ changeRole(name, params) {
2710
+ return changeRole(
2711
+ this,
2712
+ this.up,
2713
+ name,
2714
+ params.from || pqb.emptyObject,
2715
+ params.to
2716
+ );
2717
+ }
2581
2718
  }
2582
2719
  const wrapWithLog = async (log, text, values, fn) => {
2583
2720
  if (!log) {
@@ -3381,7 +3518,7 @@ async function renameMigrations(config, trx, versions, renameTo) {
3381
3518
  }
3382
3519
 
3383
3520
  const transactionIfSingle = (adapter, config, fn) => {
3384
- return !adapter.isInTransaction() && config.transaction === "single" ? transaction(adapter, config, fn) : fn(adapter);
3521
+ return config.transaction === "single" ? transaction(adapter, config, fn) : fn(adapter);
3385
3522
  };
3386
3523
  function makeMigrateFn(up, defaultCount, fn) {
3387
3524
  return async (db, config, params) => {
@@ -3434,14 +3571,16 @@ const migrateAndClose = async (db, config, params) => {
3434
3571
  await migrate(adapter, config, params);
3435
3572
  await adapter.close();
3436
3573
  };
3437
- const runMigration = async (db, migration) => {
3438
- await ensureTransaction(db, async (trx) => {
3574
+ async function runMigration(db, ...args) {
3575
+ const [config, migration] = args.length === 1 ? [{}, args[0]] : [args[0], args[1]];
3576
+ const adapter = getMaybeTransactionAdapter(db);
3577
+ await transaction(adapter, config, async (trx) => {
3439
3578
  clearChanges();
3440
3579
  const changes = await getChanges({ load: migration });
3441
- const config = changes[0]?.config;
3442
- await applyMigration(trx, true, changes, config);
3580
+ const config2 = changes[0]?.config;
3581
+ await applyMigration(trx, true, changes, config2);
3443
3582
  });
3444
- };
3583
+ }
3445
3584
  const rollback = makeMigrateFn(
3446
3585
  false,
3447
3586
  1,
@@ -3505,7 +3644,7 @@ const migrateOrRollback = async (trx, config, set, versions, count, up, redo2, f
3505
3644
  }
3506
3645
  let loggedAboutStarting = false;
3507
3646
  let migrations;
3508
- const migrationRunner = trx.isInTransaction() || config.transaction === "single" ? applyMigration : runMigrationInOwnTransaction;
3647
+ const migrationRunner = config.transaction === "single" ? applyMigration : runMigrationInOwnTransaction;
3509
3648
  for (const file of set.migrations) {
3510
3649
  if (up && versionsMap[file.version] || !up && !versionsMap[file.version]) {
3511
3650
  continue;
@@ -3946,6 +4085,10 @@ const astToGenerateItem = (config, ast, currentSchema) => {
3946
4085
  deps.push(tableSchema, `${tableSchema}.${tableName}`);
3947
4086
  break;
3948
4087
  }
4088
+ case "role":
4089
+ case "changeRole": {
4090
+ break;
4091
+ }
3949
4092
  default:
3950
4093
  pqb.exhaustive(ast);
3951
4094
  }
@@ -4526,7 +4669,60 @@ const astEncoders = {
4526
4669
  }
4527
4670
  pqb.addCode(code, ");");
4528
4671
  return code;
4672
+ },
4673
+ role(ast) {
4674
+ const params = ast.action === "create" ? roleParams(ast) : void 0;
4675
+ const arr = [
4676
+ `await db.${ast.action}Role(${pqb.singleQuote(ast.name)}${params?.length ? ", {" : ");"}`
4677
+ ];
4678
+ if (params?.length) {
4679
+ arr.push(params);
4680
+ arr.push("});");
4681
+ }
4682
+ return arr;
4683
+ },
4684
+ changeRole(ast) {
4685
+ const from = roleParams(ast.from, ast.to);
4686
+ const to = roleParams(ast.to, ast.from, true);
4687
+ return [
4688
+ `await db.changeRole(${pqb.singleQuote(ast.name)}, {`,
4689
+ [...from.length ? ["from: {", from, "},"] : [], "to: {", to, "},"],
4690
+ "});"
4691
+ ];
4692
+ }
4693
+ };
4694
+ const roleParams = (ast, compare, to) => {
4695
+ const params = [];
4696
+ for (const key of [
4697
+ "name",
4698
+ "super",
4699
+ "inherit",
4700
+ "createRole",
4701
+ "createDb",
4702
+ "canLogin",
4703
+ "replication",
4704
+ "connLimit",
4705
+ "validUntil",
4706
+ "bypassRls",
4707
+ "config"
4708
+ ]) {
4709
+ if (key === "name" && !to) continue;
4710
+ let value = ast[key];
4711
+ if (!compare && (!value || key === "connLimit" && value === -1)) {
4712
+ continue;
4713
+ }
4714
+ value = value instanceof Date ? `'${value.toISOString()}'` : key === "config" ? JSON.stringify(value) : typeof value === "string" ? pqb.singleQuote(value) : value;
4715
+ if (compare) {
4716
+ const a = value === -1 || value === false ? void 0 : value;
4717
+ const other = compare[key];
4718
+ const b = other === -1 || other === false ? void 0 : other;
4719
+ if (a === b) {
4720
+ continue;
4721
+ }
4722
+ }
4723
+ params.push(`${key}: ${value},`);
4529
4724
  }
4725
+ return params;
4530
4726
  };
4531
4727
  const isTimestamp = (column, type) => {
4532
4728
  if (!column) return false;
@@ -4934,7 +5130,19 @@ const collationsSql = (version) => `SELECT
4934
5130
  FROM pg_collation
4935
5131
  JOIN pg_namespace n on pg_collation.collnamespace = n.oid
4936
5132
  WHERE ${filterSchema("n.nspname")}`;
4937
- const sql = (version) => `SELECT (${schemasSql}) AS "schemas", ${jsonAgg(
5133
+ const roleSql = (params) => `SELECT json_agg(json_build_object(
5134
+ 'name', rolname,
5135
+ 'super', rolsuper,
5136
+ 'inherit', rolinherit,
5137
+ 'createRole', rolcreaterole,
5138
+ 'canLogin', rolcanlogin,
5139
+ 'replication', rolreplication,
5140
+ 'connLimit', rolconnlimit,
5141
+ 'validUntil', rolvaliduntil,
5142
+ 'bypassRls', rolbypassrls,
5143
+ 'config', rolconfig
5144
+ )) FROM pg_roles WHERE ${params.whereSql ?? `name != 'postgres' AND name !~ '^pg_'`}`;
5145
+ const sql = (version, params) => `SELECT (${schemasSql}) AS "schemas", ${jsonAgg(
4938
5146
  tablesSql,
4939
5147
  "tables"
4940
5148
  )}, ${jsonAgg(viewsSql, "views")}, ${jsonAgg(
@@ -4949,13 +5157,13 @@ const sql = (version) => `SELECT (${schemasSql}) AS "schemas", ${jsonAgg(
4949
5157
  )}, ${jsonAgg(domainsSql, "domains")}, ${jsonAgg(
4950
5158
  collationsSql(version),
4951
5159
  "collations"
4952
- )}`;
4953
- async function introspectDbSchema(db) {
5160
+ )}${params?.roles ? `, (${roleSql(params.roles)}) AS "roles"` : ""}`;
5161
+ async function introspectDbSchema(db, params) {
4954
5162
  const {
4955
5163
  rows: [{ version: versionString }]
4956
5164
  } = await db.query("SELECT version()");
4957
5165
  const version = +versionString.match(/\d+/)[0];
4958
- const data = await db.query(sql(version));
5166
+ const data = await db.query(sql(version, params));
4959
5167
  const result = data.rows[0];
4960
5168
  for (const domain of result.domains) {
4961
5169
  domain.checks = domain.checks?.filter((check) => check);
@@ -5034,6 +5242,26 @@ async function introspectDbSchema(db) {
5034
5242
  }
5035
5243
  result.indexes = indexes;
5036
5244
  result.excludes = excludes;
5245
+ if (result.roles) {
5246
+ for (const role of result.roles) {
5247
+ nullsToUndefined(role);
5248
+ if (role.validUntil) role.validUntil = new Date(role.validUntil);
5249
+ if (role.config) {
5250
+ const arr = role.config;
5251
+ role.config = Object.fromEntries(
5252
+ arr.map((item) => {
5253
+ const i = item.indexOf("=");
5254
+ const key = item.slice(0, i);
5255
+ const value = item.slice(i + 1);
5256
+ return [
5257
+ key,
5258
+ value[0] === '"' ? value.slice(1, -1).replaceAll('""', '"') : value
5259
+ ];
5260
+ })
5261
+ );
5262
+ }
5263
+ }
5264
+ }
5037
5265
  return result;
5038
5266
  }
5039
5267
  const nullsToUndefined = (obj) => {
@@ -5132,6 +5360,15 @@ const structureToAst = async (ctx, adapter, config) => {
5132
5360
  for (const view of data.views) {
5133
5361
  ast.push(viewToAst(ctx, data, domains, view));
5134
5362
  }
5363
+ if (data.roles) {
5364
+ for (const role of data.roles) {
5365
+ ast.push({
5366
+ type: "role",
5367
+ action: "create",
5368
+ ...role
5369
+ });
5370
+ }
5371
+ }
5135
5372
  return ast;
5136
5373
  };
5137
5374
  const makeDomainsMap = (ctx, data) => {
@@ -5298,7 +5535,8 @@ const getDbStructureTableData = (data, { name, schemaName }) => {
5298
5535
  } : void 0,
5299
5536
  indexes: data.indexes.filter(filterFn),
5300
5537
  excludes: data.excludes.filter(filterFn),
5301
- constraints
5538
+ constraints,
5539
+ roles: data.roles
5302
5540
  };
5303
5541
  };
5304
5542
  const filterByTableSchema = (tableName, schemaName) => (x) => x.tableName === tableName && x.schemaName === schemaName;