diff --git a/src/index.ts b/src/index.ts index 34053e4a..fa995cd0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,6 @@ import runner from './runner' import { Migration } from './migration' -import { RunnerOption, MigrationBuilder, PgType } from './types' +import { RunnerOption, MigrationBuilder, MigrationBuilderActions, PgType } from './types' import PgLiteral from './operations/PgLiteral' import { PgLiteralValue, @@ -119,10 +119,11 @@ import { } from './operations/viewsMaterializedTypes' export { + PgType, PgLiteral, Migration, - PgType, MigrationBuilder, + MigrationBuilderActions, RunnerOption, PgLiteralValue, Value, diff --git a/src/runner.ts b/src/runner.ts index 307a0c51..33c69dca 100644 --- a/src/runner.ts +++ b/src/runner.ts @@ -7,6 +7,7 @@ import { MigrationDirection, RunnerOptionClient, RunnerOptionUrl, + RunnerOptionMigrations, RunnerOption, Logger, } from './types' @@ -20,19 +21,41 @@ const idColumn = 'id' const nameColumn = 'name' const runOnColumn = 'run_on' +const isMigrationsOptions = (options: unknown): options is RunnerOptionMigrations => + !!(options as RunnerOptionMigrations).migrations + +const importMigration = async ( + filePath: string, + module: string | MigrationBuilderActions | (() => MigrationBuilderActions | Promise), +): Promise => { + if (typeof module === 'string') { + // eslint-disable-next-line global-require,import/no-dynamic-require,security/detect-non-literal-require + return require(path.relative(__dirname, filePath)) + } + + if (typeof module === 'function') { + // eslint-disable-next-line no-return-await + return await module() + } + + return module +} + const loadMigrations = async (db: DBConnection, options: RunnerOption, logger: Logger) => { try { let shorthands: ColumnDefinitions = {} - const files = await loadMigrationFiles(options.dir, options.ignorePattern) + + const files = isMigrationsOptions(options) + ? options.migrations + : Object.fromEntries( + (await loadMigrationFiles(options.dir, options.ignorePattern)).map((f) => [`${options.dir}/${f}`, f]), + ) + return ( await Promise.all( - files.map(async (file) => { - const filePath = `${options.dir}/${file}` - const actions: MigrationBuilderActions = - path.extname(filePath) === '.sql' - ? await migrateSqlFile(filePath) - : // eslint-disable-next-line global-require,import/no-dynamic-require,security/detect-non-literal-require - require(path.relative(__dirname, filePath)) + Object.entries(files).map(async ([filePath, module]) => { + const actions = + path.extname(filePath) === '.sql' ? await migrateSqlFile(filePath) : await importMigration(filePath, module) shorthands = { ...shorthands, ...actions.shorthands } return new Migration( db, diff --git a/src/types.ts b/src/types.ts index 6a1968b8..7bb7167f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -215,10 +215,6 @@ export interface RunnerOptionConfig { * @default 'public' */ schema?: string | string[] - /** - * The directory containing your migration files. - */ - dir: string /** * Check order of migrations before running them. */ @@ -235,10 +231,6 @@ export interface RunnerOptionConfig { * Treats `count` as timestamp. */ timestamp?: boolean - /** - * Regex pattern for file names to ignore (ignores files starting with `.` by default). - */ - ignorePattern?: string /** * Run only migration with this name. */ @@ -284,6 +276,29 @@ export interface RunnerOptionConfig { verbose?: boolean } +export interface RunnerOptionDir { + /** + * The directory containing your migration files. + */ + dir: string + /** + * Regex pattern for file names to ignore (ignores files starting with `.` by default). + */ + ignorePattern?: string +} + +export interface RunnerOptionMigrations { + /** + * A dictionary containing migrations to run. + * + * The object can follow th + */ + migrations: Record< + string, + string | MigrationBuilderActions | (() => MigrationBuilderActions | Promise) + > +} + export interface RunnerOptionUrl { /** * Connection string or client config which is passed to [new pg.Client](https://node-postgres.com/api/client#constructor) @@ -300,4 +315,6 @@ export interface RunnerOptionClient { dbClient: ClientBase } -export type RunnerOption = RunnerOptionConfig & (RunnerOptionClient | RunnerOptionUrl) +export type RunnerOption = RunnerOptionConfig & + (RunnerOptionClient | RunnerOptionUrl) & + (RunnerOptionDir | RunnerOptionMigrations) diff --git a/test/ts/customRunner.ts b/test/ts/customRunner.ts index 10a60ce3..9bedab3d 100644 --- a/test/ts/customRunner.ts +++ b/test/ts/customRunner.ts @@ -1,6 +1,7 @@ import { resolve } from 'path' import { Client } from 'pg' import runner, { RunnerOption } from '../../dist' +import { RunnerOptionDir, RunnerOptionMigrations } from '../../dist/types' type TestOptions = { count?: number @@ -13,7 +14,7 @@ type Options = ({ databaseUrl: string } & TestOptions) | ({ dbClient: Client } & /* eslint-disable no-console */ // eslint-disable-next-line import/prefer-default-export export const run = async (options: Options): Promise => { - const opts: Omit & Options = { + const opts: Omit & Options & (RunnerOptionDir | RunnerOptionMigrations) = { migrationsTable: 'migrations', dir: resolve(__dirname, 'migrations'), expectedUpLength: 2,