Conversation
|
I know it's strange to work like this, because the purpose of decorators is precisely to... decorate... |
metcoder95
left a comment
There was a problem hiding this comment.
I think I shared same thought on a similar PR, but I'm having a little bit of troubles to see the real value of the feature.
As per this example:
const { sendFile } = fastify.getDecorators<StaticReplyDecorators>('sendFile')
fastify.get('/', (request, reply) => {
return sendFile.call(reply, 'hello.html')
})It really seems a decorator would just fit better right there.
Is there any particular use-case you've in mind to support with the feature?
|
It does fit better on a Vanilla JS project, but not with typescript imo. The goal is not to create a new design, but to tell the users that we are not forcing them to use decorators with module augmentation and there is an alternative if they prefer. |
|
|
Eomm
left a comment
There was a problem hiding this comment.
We could export the decorators from core plugins instead of just augmenting the global instance and request/reply:
What about decorator with a dynamic name?
The title says allow to access decorators before start, but this is already possibile, so: are we solving a TS issue?
const { sendFile } = fastify.getDecorators<StaticReplyDecorators>('sendFile')
fastify.get('/', (request, reply) => {
return sendFile.call(reply, 'hello.html')
})
isn't this example already doable?
const sendFile = fastify.sendFile as StaticReplyDecorators
fastify.get('/', (request, reply) => {
return sendFile.call(reply, 'hello.html')
})
I think we need more use cases that this feat is solving to get it merged.
Not request and reply decorators, at least not in a formal way, we could use key symbols
const sendFile = fastify.sendFile as StaticReplyDecorators
fastify.get('/', (request, reply) => {
return sendFile.call(reply, 'hello.html')
})Unless the plugin has decorated the fastify instance with a function that depends on But yes, we could allow via an option to set decorators to the current instance instead of I also realize thanks to your comment that my implementation is wrong, because two decorators fastify.decorate('a', 'from_instance')
fastify.decorateRequest('a', 'from_request')
fastify.getDecorators('a') // from_request is never foundSo, we should have |
mcollina
left a comment
There was a problem hiding this comment.
I think we should just expose:
getRequestDecoratorsgetReplyDecorators
as I don't think we should mingle them together in a single API. There is no need to have this API for the fastify instance because one could always do instance[name].
|
Yes, I agree. |
There's an advantage to access it via |
|
I understand the skepticism about giving access to Request/Reply decorators, and there's no hurry, I can work on convincing you more. This might not be used directly by users, but by a plugin offering a slightly different architecture to work with Fastify (I need to think/read about it). But for this to happen, it would be great to be able to offer adapters capable of wrapping the many features of Core plugins on Request/Reply as changing/duplicate an entire ecosystem is not an option. This my main concern and the reason for this PR. In the meantime, here are a few examples. A logger plugin that decorate instance: export interface FastifyLoggingDecorators {
log(message: string): void;
}
export const FASTIFY_LOGGING_DECORATOR_NAMES = ['log'];
const fastifyLoggingPlugin = fp(async (fastify: FastifyInstance) => {
fastify.decorate('log', (message: string) => {
// implement the logic
});
});
export default fastifyLoggingPlugin;An auth plugin that decorate request: export interface RequestAuthDecorators {
getAuthenticatedUser(): { id: string; name: string } | null;
}
export const REQUEST_AUTH_DECORATOR_NAMES = ['getAuthenticatedUser'];
const requestAuthPlugin = fp(async (fastify: FastifyInstance) => {
fastify.decorateRequest('getAuthenticatedUser', function (this: FastifyRequest) {
// Implement the logic here
return null;
});
});
export default requestAuthPlugin;A static file handler plugin that decorate reply: export interface ReplyStaticDecorators {
sendFile(filename: string, rootPath?: string): FastifyReply;
}
export const REPLY_STATIC_DECORATOR_NAMES = ['sendFile'];
const replyFormatPlugin = fp(async (fastify: FastifyInstance) => {
fastify.decorateReply('sendFile', function (this: FastifyReply, filename: string, rootPath: string = './') {
// Implement the logic here
return this;
});
});
export default replyFormatPlugin;Then, a plugin to configure routes: const routePlugin = fp(async (fastify: FastifyInstance) => {
// Get all the necessary dependencies with compilation/runtime check
const { log } = fastify.getFastifyDecorators<FastifyLoggingDecorators>(FASTIFY_LOGGING_DECORATOR_NAMES);
const { getAuthenticatedUser } = fastify.getRequestDecorators<RequestAuthDecorators>(REQUEST_AUTH_DECORATOR_NAMES);
const { sendFile } = fastify.getReplyDecorators<ReplyStaticDecorators>(REPLY_STATIC_DECORATOR_NAMES);
fastify.get('/user', (request, reply) => {
const user = getAuthenticatedUser.call(request);
if (!user) {
log('Unauthenticated access attempt');
return reply.send({ success: false, message: 'Unauthorized' });
}
log(`User profile accessed by ${user.id}`);
return sendFile.call(reply, 'user-profile.html', '/views');
});
});
export default routePlugin; |
|
Not to be a hard opposer of the feature, but I'm still having hard times seeing use cases for these new APIs. If e.g. there's plugin A that depends on B decorating the It might overlap already to what Besides that, I'm having some troubles seeing more complex use case around it. |
|
If the solution in this PR is right and applicable to all the core plugins, I might be happy enough to close this PR by myself. Can you take a look? |
|
My recommendation has not been implemented yet: #5768 (review). I think we could have: const { sendFile } = reply.getDecorator(..)In this way: |
|
#5672 is likely going to be semver-major. |
|
I've started looking into Node DI containers, and I think it's possible to create something really satisfying with Fastify for people who prefer this way of developing an application. Closing for #6019. |
|
Sorry, I reopen it 😛 Reading the comment of jsummers on the last mentioned issue, I think we still can allow access to decorators to simplify typing and dependency check, for business logic too. IoC container is an other topic. No need for module augmentation here: fastify.getDecorator<SomeFeature>('someFeature') // great for dependency check at registration
request.getDecorator<SomeFeature>('someFeature')
reply.getDecorator<SomeFeature>('someFeature')I am gonna implement it the way Matteo recommend it. |
|
I think, we should add a method fastify.decorateRequest('sessionData', null)
fastify.addHook('onRequest', async (req, reply) => {
req.setDecorator<ISession>('sessionData', { user: 'Alice' })
}Cleaner than using (req as typeof req & { someProp: string }).someProp = 'hello' |
67e6cb7 to
b8e929e
Compare
|
In order to explain the evolution of my thinking, I initially wanted to propose something that would allow more control over the typing and runtime safety of both business logic and installed core plugins. But I think it's possible to decouple core plugin utilities if desired by the users of the framework. I've explained my reasoning a little further in the updated doc, I'll read your feedback carefully. |
|
This pull request has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. |
New proposal
Allow to access decorators on fastify, request and reply thanks to accessor
getDecorator<T>(decoratorName):The goal is to avoid module augmentation for TypeScript users, because augmentation type the decorators globally on instances. It causes issues if developers want to instantiate several Fastify servers or to leverage encapsulation for their business logic.
Some users might also use this for Early Plugin Dependency Validation and better error handling for missing decorators. Refer to the doc update.
This proposal is mostly obsolete
I added tests for typing, encapsulation, and different kind of decorator values (function, getter/setter, primitive) for instance, request and reply.
We currently leverage module augmentation for decorators, that doesn't give any information about the level of encapsulation, worse, it does pollute the whole module. I think this is bad, doesn't bring a lot of type safety and is inconvenient.
Maybe we can think about decorators as dependencies in some way, nice features added by plugins. We should be sure that these decorators are available before the application even start. This is possible to do this check with
fastifyPlugin:But currently does not give any typing information, and I get headaches just imagining trying to educate TypeScript about it...
There is a simple solution to that imo, and I would like your feedback.
We could export the decorators from core plugins instead of just augmenting the global instance and request/reply:
We could use this feature this way at plugin registration:
More realist use case:
Yes, we have to leverage
call, but it does work, doesn't break anything and causes much less issues than module augmentation imho. There is just exports to create if we want to provide appropriate typing, I can work on it.EDIT:
Maybe leveraging
.callis slightly less performant? There are probably solutions to this. We can also return a slightly different decorator that takerequestorreplyas first argument:sendFile(reply)Do you see any value in this feature?