-
Notifications
You must be signed in to change notification settings - Fork 81
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 support for user provided context in handlers #835
Conversation
I love the way this keeps the type information associated to the context id, very cool. One question I have is if we want to expose the context values to interceptors as well? |
We don't have server side interceptors yet, but if we finalize this API I will send a follow up to add this to the clients. The server side interceptors should include them when implemented. |
This may be a contrived example, but what if the user wants to add something to context that doesn't involve the request object? I suppose they could do: await server.register(fastifyConnectPlugin, {
routes,
contextValues: () => createContextValues().set(kUser, {"name": "Krishna"}),
}); Does it make sense then to make the |
@smaye81 Your example will work without any changes, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I imagine one might want to console.log context values or inspect them in a debugger.
Should we improve behavior with an optional description string for the symbol, and by storing values as properties instead of a captured variable?
Would it be helpful to consider a solution that introduces typesafe constraints between the route handlers and the context builder? Here is a rough example of the types I'm talking about: // context now has typesafety
type Context<V={}> = { values: V };
// example of how a route handler can assert that it needs some data out of context
type RouterHandler<I, O, V={}> = (input: I, context?: Context<V>) => O;
// then we can force the injection of some data in context
class Server<C extends Context> {
addContextValue<K extends LiteralString, T>(lambda: (req: Request) => T): Server<Context<C["values"] & { K: T}>>;
} |
Thank you for the suggestion @jeffsu! Could you give us a simple e2e example of how this will work? I am unable to see how this can guarantee the presence of a value. In the current design, users have to pass a default value. One other thing to note is that we want to add context values to interceptors on both the client and server side (not implemented yet). Values can be added via the interceptors as well which means the values are not always available at the request start. |
47bbd47
to
00b5e34
Compare
Hey @jeffsu to illustrate what I meant by adding them to interceptors I just opened #841 please take a look and let me know what you think. One thing I noticed with the current implementation is that Current behavior: const interceptor = (next) => {
return (req) => {
const user = req.values(kUser);
const res = next(req); // This can modify req.values
// req.values(kUser) !== user
}
} |
This level of mutability doesn't really bother me. If we made a version of context that could be deep cloned, you could do something like: const interceptor = (next) => {
return (req) => {
const user = req.values(kUser);
const res = next({ ...req, values: req.values.clone() }); // This can modify req.values
// req.values(kUser) === user
}
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's true that handlers cannot declare a dependency on a context value with this, as Jeff noted. I think this is a nice step forwards though. I say LGTM.
Is there an example of how to use this new system to get the logger inside the connect service? E.g
|
It is the same as the example in the PR description, instead of And then can get the value and use it:
|
Why was the implementation shipped this way? We have no async support and we can't pass a global type for contextValues. Our use case is to check authentication in this handler. Do you accept a PR? |
@StarpTech Typically if you're using something like Fastify/Express you will have an auth library that does the authentication checks and adds the subject info to the incoming request, and you can add the that to the context.
This is actually error prone and could have conflicts. Using a unique key lookup helps us guarantee type safety and always provide a default value. |
I see your points but why should we limit the implementation to only synchronous work? I don't see the value in it.
I'm quite on the opposite side. We have to specify a shared ContextKey Type and use the right type everywhere we access it instead of maintaining the relation once This would be more conenvient (example, could be specified somewhere else) (router: ConnectRouter) => {
router.service<MyContextValuesType>(MyService, MyServiceServiceImpl(opts), handlerOptions);
}; Access ctx.values.get(Symbol('foo')) // return the correct type, typesafe access out of the box Another thing we find odd is, why is it necessary to specify the the default value when retrieving a value? If the value can be undefined, it is the dev responsibility to deal with it. // Error: Property defaultValue is missing in type { id: symbol; } but required in type ContextKey<unknown>
ctx.values.get({
id: Symbol('subgraph'),
}); |
This is how it can be used today, instead of creating a
Default is not needed when retrieving a value, it is only needed when creating the key. If We just updated the docs with an example. Hopefully that helps clarify the usage a little bit. This is fairly a common pattern in the JS ecosystem, a popular mechanic that works like this is the React Context. |
Was the API updated? This is not possible in
This doesn't work for me. Property defaultValue is missing in type { id: symbol; } but required in type ContextKey<string | undefined>
ctx.values.get<string | undefined>({
id: Symbol('test'),
});
There you made an example to authenticate within |
The API is the same, you have to create a unique key using console.log(Symbol('foo') === Symbol('foo'));
// Expected output: false |
Ah, ok |
Add support for user provided context in handlers. With this change handlers can depend on user provided values in the context.
Closes #818, #586, and #708
Related: #550
Example:
Create a context key with a default value:
Use the
contextValues
option to provide the context values for each request:Use the context value in the handler: