-
-
Notifications
You must be signed in to change notification settings - Fork 674
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
Accessing to resolver metadata in middleware #123
Comments
Here is a quick snippet with my vision of the API: const setValues: MiddlewareFn<Cntx> = ({ context, args, resolverMetadata }, next) => {
// if resolverMetadata is defined, the next in chain is a resolver
if (resolverMetadata) {
// clone original args data object
const customArgs = { ...args };
for (const argMetadata of resolverMetadata.args) {
// if arg is an input
if (argMetadata.isInput) {
// load custom metadata value from custom storage using InputType class
const unlessAdminMetadatas = loadMetadataFromCustomStorage(argMetadata.classType);
// do the logic - replace with the value when not admin
for (const metadata of unlessAdminMetadatas) {
if (!context.user.roles.include("admin")) {
customArgs[argMetadata.argName][metadata.fieldName] = metadata.value;
}
}
}
}
// call the next middleware (resolver) with replaced args object
return next({ args: customArgs });
}
// if not, normally call next
return next();
}; It's just an idea how to implement it exposing the |
I am revisiting this issue and I am thinking about a different way as injecting built metadata into middlewares is not so easy. What if: @ValueUnlessAdmin("member") // Creates metadata
level: string; would use #124 to add some metadata to the GraphQLObject/InputType field?
Because this should be available in an @erencay What do you think? Would it solve your use case? 🤔 |
After a small research, it looks like #124 will allow to attach the custom data to the export const SampleMiddleware: MiddlewareFn = async ({ info }, next) => {
const resolverMetadata = info.parentType.getFields()[info.fieldName];
// here we can access `resolverMetadata.complexity` or other metadata
return next();
}; I will add this recipe to the docs describing the metadata feature, maybe in the future there will be another argument of middleware function with injected parsed data like this. |
My current use case is generating a TypeORM query builder, where I need access to the entity property name when given a schema name. For example, if the schema name is It might also be useful to have access to the object type class, to check if the object implements a Connection (like Relay) interface. Is there no "supported" method of accessing this data? Should I assume that relying on: import {getMetadataStorage} from "type-graphql/dist/metadata/getMetadataStorage"; ... is Really Bad™? Regarding your vision of the API, would it not make sense to leave ({ context, args }, next, resolverMetadata) => {} I wasn't initially planning on using middleware, or a custom decorator, because I don't think that it's possible to do dependency injection into a decorator? The usage of my decorator currently looks like this: @Query(() => User)
public async user(
@Arg("id") _id: string,
@QueryBuilder(User, (qb, {args}) => qb.where({id: args.id}))
qb: SelectQueryBuilder<User>
): Promise<User> {
const user = await qb.getOne();
if (!user) {
throw new Error("User not found.");
}
return user;
} The unused I was initially just using a function within the body of the resolver, like this: const qb = generateQueryBuilder(User, info, {id}); But even with this issue resolved, I would have no access to the resolver metadata. At the moment the code uses: import {getRepository} from "typeorm"; ... but I wonder if the entity manager should instead be dependency-injected (which I don't think is possible when using a decorator). Maybe it's possible to inject the entity manager into the resolver, and then pass that as an argument to the decorator, like: @QueryBuilder(this.entityManager, User, (qb, {args}) => qb.where({id: args.id})) Or: @QueryBuilder(this.entityManager.getRepository(User), (qb, {args}) => qb.where({id: args.id})) (untested) I'm just a bit concerned that too much is happening within the decorator usage, as opposed to within the resolver body. |
It's the best solution, much more readable, less error-prone, easier to understand as no magic behind, which I can't say about that super weird: @QueryBuilder(this.entityManager.getRepository(User), (qb, {args}) => qb.where({id: args.id}))
What exactly do you want to magically auto generate? Only select optimizations? |
The 2nd argument is just an optional initialization callback that allows you to set up the query builder (in this example, adding the I guess one benefit to this is not having to always pull out the But ya, I wasn't sure if it was the right approach, but you made it sound like middleware would be the only option (although I suppose a simple [global?] middleware could place the resolver metadata in an argument or in the context).
I'm doing this with graphql-parse-resolve-info. |
No, that's why I didn't propose to
But to make them part of the
You don't have schema to class property name mapping applied there.
I would rather use Warthog to have them auto generated under the hood or some common generated/generic args/input types to just pass them to the custom repository in resolver. |
I know, that's why I need the resolver metadata.
I'm trying to avoid two things:
I'm not really sure what you mean by this. |
Generic input or args, like |
I think I'd prefer to have more specific input and argument types, similar to Prisma and Warthog. I guess this means that I'll also have to have a code generation step (similar to Warthog). However, regardless of this aspect, I still need access to TypeGraphQL metadata, in at least two places: A CLI/code-gen step, and in middleware/decorators in order to access GraphQL -> class property mappings (among other things, potentially) in resolvers. Which of these options do you suggest that I use?:
|
I think the point nr 1 is the answer. You provide your own decorators, you translate its options into orm/graphql layer options, store additional info in your metadata storage (own class or |
I'm not convinced that option 1 is ideal, because:
Could you please consider providing a formal API for accessing TypeGraphQL metadata (globally and via middleware)? (BTW: This doesn't really seem like the same request as #123 or #134 – should I create a new issue for this?) |
This won't happen in the nearest time as I'm working on vNext with a complete redesign of metadata and pipeline. I have to make the internals stable first before exposing them to the outside world.
|
All I ask is that you keep this request in the back of your mind while working on vNext. 🙂
Ya, I forgot to mention that alternative. The other points still apply though. |
Providing metadata/resolver information into a middleware operation might have some benefits for extended use cases.
Example Use Case:
In this example I wanted to change
level
field to a fixed value if user is not an admin.Decorator example;
InputType example;
Middleware example;
Here's a suggested workaround by @19majkel94
This workaround did the trick. That being said, it could be easier to realize such intentions and may be more if metadata/resolver information can be accessed in the middleware.
The text was updated successfully, but these errors were encountered: