import { types as T } from "./mod.ts"; import { EmVer } from "./emver-lite/mod.ts"; import { matches } from "./dependencies.ts"; export type MigrationFn = ( effects: T.Effects, ) => Promise & { _type: type; _version: version }; export function migrationFn( fn: ( effects: T.Effects, ) => Promise, ): MigrationFn { return fn as MigrationFn; } export interface Migration { up: MigrationFn; down: MigrationFn; } export type MigrationMapping = { [version in versions]: Migration; }; export function fromMapping( migrations: MigrationMapping, currentVersion: string, ): T.ExpectedExports.migration { const directionShape = matches.literals("from", "to"); return async ( effects: T.Effects, version: string, direction?: unknown, ) => { if (!directionShape.test(direction)) { return { error: 'Must specify arg "from" or "to".' }; } let configured = true; const current = EmVer.parse(currentVersion); const other = EmVer.parse(version); const filteredMigrations = (Object.entries(migrations) as [ keyof MigrationMapping, Migration, ][]) .map(([version, migration]) => ({ version: EmVer.parse(version), migration, })).filter(({ version }) => version.greaterThan(other) && version.lessThanOrEqual(current) ); const migrationsToRun = matches.matches(direction) .when("from", () => filteredMigrations .sort((a, b) => a.version.compareForSort(b.version)) // low to high .map(({ migration }) => migration.up)) .when("to", () => filteredMigrations .sort((a, b) => b.version.compareForSort(a.version)) // high to low .map(({ migration }) => migration.down)) .unwrap(); for (const migration of migrationsToRun) { configured = (await migration(effects)).configured && configured; } return { result: { configured } }; }; }