Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to retrieve context asynchronously before the migrations run #453

Merged

Conversation

alefi
Copy link
Contributor

@alefi alefi commented Apr 6, 2021

It often needs that we should do some preparation prior to context could be provided to migrations modules. It could be the initialization of database backend storages, etc. The old behavior keeps untouched, but this PR adds the advantage to use the asynchronous functions to retrieve context.

@alefi
Copy link
Contributor Author

alefi commented Apr 21, 2021

Hi,
Could someone clarify what approximate time needed to approve or reject this PR? I opened it about 2 weeks ago, and there are no reactions till now. Thanks.

src/umzug.ts Outdated Show resolved Hide resolved
@alefi
Copy link
Contributor Author

alefi commented May 12, 2021

Hi, is there any chance that this PR would merge in near future? Thanks.

Copy link
Contributor

@mmkal mmkal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the slow response here. I am 👍 on the idea but I don't think we should be checking if the return value of the context-getter is a function promise.

src/umzug.ts Outdated
@@ -483,6 +480,23 @@ export class Umzug<Ctx extends object = object> extends emittery<UmzugEvents<Ctx
});
}

private async getContext(): Promise<Ctx> {
const isPromise = (_ctx: object | Function) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this? Can't we just check if it's a function and await context() if it is?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, Promise isn't a function and this comparison is required. We could use different mechanics to detect promise, such either typeof _ctx.constructor === 'function'; or Boolean(typeof value.then === 'function');, but in my opinion, current implementation Is better way to do that. WDYT?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mostly mean that you can await a non-promise too. So we can keep lines 231-234 basically the same but put an await before each ternary statement option, then I don't think we'd even need this function.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The context retrieving action is a significant one, and grouping it into the method getContext increases code readability. As for rest, if we would add await before each ternary, the Promise branch won't execute at all.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's true. I'm fine with keepinh getContext, encapsulating the logic in a function, as long as we get rid of this toString based promise checking. It's brittle (likely won't work with non-native promises like bluebird, etc.) and it doesn't seem necessary.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed the Promise detection implementation. Please review.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any updates on it? :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left a suggestion to explain what I mean. Please add a failing test if you think my suggestion doesn't cover everything.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I simplified the method according to your suggestion. Thanks!

src/umzug.ts Outdated
Comment on lines 484 to 496
const isPromise = (_ctx: object | Function | PromiseLike<unknown>) => {
return 'then' in _ctx && typeof _ctx.then === 'function';
};

let { context = {} as Ctx }: UmzugOptions<Ctx> = this.options;

if (isPromise(context)) {
context = await Promise.resolve(context as Promise<Ctx>);
} else if (typeof context === 'function') {
context = await (context as () => Promise<Ctx> | Ctx)();
}

return context;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I clearly haven't been explaining this very well. We do not need to check if the context is a promise. The fact that this function is async will ensure it's one anyway, and a the js engine will flatten promises for us. The implementation of this method can and should be much simpler and have less risk of gotchas.

Suggested change
const isPromise = (_ctx: object | Function | PromiseLike<unknown>) => {
return 'then' in _ctx && typeof _ctx.then === 'function';
};
let { context = {} as Ctx }: UmzugOptions<Ctx> = this.options;
if (isPromise(context)) {
context = await Promise.resolve(context as Promise<Ctx>);
} else if (typeof context === 'function') {
context = await (context as () => Promise<Ctx> | Ctx)();
}
return context;
const { context = {} } = this.options;
return typeof context === 'function' ? context() : context;

The tests you added are good - they should pass if you make this change. Assuming they do, I'm happy to merge.

Comment on lines 103 to 106
return {
// Eg: externalData,
innerValue: 'text',
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this comment is confusing/not necessary - the one above explaining the use case you're trying to replicate is, but no need for the extra comment here:

Suggested change
return {
// Eg: externalData,
innerValue: 'text',
};
return { innerValue: 'text' };

@mmkal mmkal merged commit ea214fd into sequelize:master May 23, 2021
@mmkal
Copy link
Contributor

mmkal commented May 23, 2021

Released as 3.0.0-beta.16

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants