diff --git a/src/types.ts b/src/types.ts index 882724d9..6cc79e06 100644 --- a/src/types.ts +++ b/src/types.ts @@ -20,7 +20,7 @@ export interface UmzugOptions> { /** The storage implementation. By default, `JSONStorage` will be used */ storage?: UmzugStorage; /** An optional context object, which will be passed to each migration function, if defined */ - context?: Ctx | (() => Ctx); + context?: Ctx | (() => Promise | Ctx); /** Options for file creation */ create?: { /** diff --git a/src/umzug.ts b/src/umzug.ts index ceca8f6a..bd6b500c 100644 --- a/src/umzug.ts +++ b/src/umzug.ts @@ -228,10 +228,7 @@ export class Umzug extends emittery(command: string, cb: (commandParams: { context: Ctx }) => Promise): Promise { - const context: Ctx = - typeof this.options.context === 'function' - ? (this.options.context as () => Ctx)() - : ((this.options.context ?? {}) as Ctx); + const context = await this.getContext(); await this.emit('beforeCommand', { command, context }); try { @@ -483,6 +480,11 @@ export class Umzug extends emittery { + const { context = {} } = this.options; + return typeof context === 'function' ? context() : context; + } + /** helper for parsing input migrations into a callback returning a list of ready-to-run migrations */ private getMigrationsResolver( inputMigrations: InputMigrations diff --git a/test/umzug.test.ts b/test/umzug.test.ts index 76a4bfc9..1be01b72 100644 --- a/test/umzug.test.ts +++ b/test/umzug.test.ts @@ -91,6 +91,45 @@ describe('custom context', () => { expect(spy).toHaveBeenCalledTimes(1); }); + + describe(`resolve asynchronous context getter before the migrations run`, () => { + const sleep = async (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); + const getContext = async () => { + // It guarantees the initialization scripts or asynchronous stuff finished their work + // before the actual migrations workflow begins. + // Eg: const externalData = await retrieveExternalData(); + await sleep(100); + return { innerValue: 'text' }; + }; + + test(`context specified as a function`, async () => { + const spy = jest.fn(); + + const umzug = new Umzug({ + migrations: [{ name: 'm2', up: spy }], + context: getContext, + logger: undefined, + }); + + await umzug.up(); + + expect(spy.mock.calls).toMatchObject([[{ context: { innerValue: 'text' } }]]); + }); + + test(`context specified as a function call`, async () => { + const spy = jest.fn(); + + const umzug = new Umzug({ + migrations: [{ name: 'm3', up: spy }], + context: getContext(), + logger: undefined, + }); + + await umzug.up(); + + expect(spy.mock.calls).toMatchObject([[{ context: { innerValue: 'text' } }]]); + }); + }); }); describe('alternate migration inputs', () => {