UNPKG

@hirosystems/api-toolkit

Version:
134 lines 5.38 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.runMigrations = runMigrations; exports.cycleMigrations = cycleMigrations; exports.databaseHasData = databaseHasData; exports.dangerousDropAllTables = dangerousDropAllTables; const node_pg_migrate_1 = require("node-pg-migrate"); const logger_1 = require("../logger"); const connection_1 = require("./connection"); const values_1 = require("../helpers/values"); /** * Run migrations in one direction. * @param dir - Migrations directory * @param direction - Migration direction (`'down'` or `'up'`) * @param connectionArgs - Postgres connection args * @param opts - Migration options */ async function runMigrations(dir, direction, connectionArgs, opts) { if (!opts?.dangerousAllowDataLoss && direction !== 'up' && !values_1.isTestEnv && !values_1.isDevEnv) { throw new Error('Whoa there! This is a testing function that will drop all data from PG. ' + 'Set NODE_ENV to "test" or "development" to enable migration testing.'); } const args = (0, connection_1.standardizedConnectionArgs)(connectionArgs, 'migrations'); await (0, node_pg_migrate_1.default)({ dir, direction, count: Infinity, ignorePattern: '.*map', databaseUrl: typeof args === 'string' ? args : { host: args.host, port: args.port, user: args.user, password: args.password, database: args.database, }, migrationsTable: opts?.migrationsTable ?? 'pgmigrations', schema: typeof args === 'string' ? 'public' : args.schema, logger: opts?.logger ?? { info: msg => (opts?.logMigrations === true ? logger_1.logger.info(msg) : {}), warn: msg => logger_1.logger.warn(msg), error: msg => logger_1.logger.error(msg), }, }); } /** * Cycle migrations down and up. * @param dir - Migrations directory * @param connectionArgs - Postgres connection args * @param opts - Migration options */ async function cycleMigrations(dir, connectionArgs, opts) { await runMigrations(dir, 'down', connectionArgs, opts); if (opts?.checkForEmptyData && (await databaseHasData(connectionArgs, { ignoreMigrationTables: true, migrationsTable: opts.migrationsTable, }))) { throw new Error('Migration down process did not completely remove DB tables'); } await runMigrations(dir, 'up', connectionArgs, opts); } /** * Check the `pg_class` table for any data structures contained in the database. We will consider * any and all results here as "data" contained in the DB, since anything that is not a completely * empty DB could lead to strange errors when running the API. See: * https://www.postgresql.org/docs/current/catalog-pg-class.html * @returns `boolean` if the DB has data */ async function databaseHasData(connectionArgs, opts) { const sql = await (0, connection_1.connectPostgres)({ usageName: 'contains-data-check', connectionArgs: (0, connection_1.standardizedConnectionArgs)(connectionArgs, 'contains-data-check'), }); try { const ignoreMigrationTables = opts?.ignoreMigrationTables ?? false; const tableName = opts?.migrationsTable ?? 'pgmigrations'; const result = await sql ` SELECT COUNT(*) FROM pg_class c JOIN pg_namespace s ON s.oid = c.relnamespace WHERE s.nspname = ${sql.options.connection.search_path} ${ignoreMigrationTables ? sql `AND c.relname NOT LIKE ${tableName}::text || '%'` : sql ``} `; return result.count > 0 && result[0].count > 0; } catch (error) { if (error.message?.includes('does not exist')) { return false; } throw error; } finally { await sql.end(); } } /** * Drops all tables from the Postgres DB. DANGEROUS!!! */ async function dangerousDropAllTables(connectionArgs, opts) { if (opts?.acknowledgePotentialCatastrophicConsequences !== 'yes') { throw new Error('Dangerous usage error.'); } const sql = await (0, connection_1.connectPostgres)({ usageName: 'dangerous-drop-all-tables', connectionArgs: (0, connection_1.standardizedConnectionArgs)(connectionArgs, 'dangerous-drop-all-tables'), }); const schema = sql.options.connection.search_path; try { await sql.begin(async (sql) => { const relNamesQuery = async (kind) => sql ` SELECT relname FROM pg_class c JOIN pg_namespace s ON s.oid = c.relnamespace WHERE s.nspname = ${schema} AND c.relkind = ${kind} `; // Remove materialized views first and tables second. // Using CASCADE in these DROP statements also removes associated indexes and constraints. const views = await relNamesQuery('m'); for (const view of views) { await sql `DROP MATERIALIZED VIEW IF EXISTS ${sql(view.relname)} CASCADE`; } const tables = await relNamesQuery('r'); for (const table of tables) { await sql `DROP TABLE IF EXISTS ${sql(table.relname)} CASCADE`; } }); } finally { await sql.end(); } } //# sourceMappingURL=migrations.js.map