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

Best way to do forward resolution #5

Closed
lorefnon opened this issue Apr 1, 2018 · 3 comments
Closed

Best way to do forward resolution #5

lorefnon opened this issue Apr 1, 2018 · 3 comments

Comments

@lorefnon
Copy link
Contributor

lorefnon commented Apr 1, 2018

I am trying to prototype an application using typeorm and typegql to evaluate the feasibility of building a query efficient graphql api using this stack.

Given the following schema (Issue & User) :

@ObjectType()
@Entity()
export class Issue {

    @Field({type: () => User})
    @ManyToOne(
        type => User,
        user => user.createdIssues
    )
    @Index()
    creator: Promise<User>;

    // ... other fields
}
@ObjectType()
@Entity()
export class User {

    @OneToMany(
        type => Issue,
        issue => issue.creator
    )
    createdIssues: Promise<Issue[]>;

   // ... other fields
}

I would like that when a user makes a query like:

{
  issues {
    id
  }
}

I perform a select only on the issues table.

And when a user makes a query like:

{
issues {
    id,
    creator {
      name,
      id
    }
}
}

I perform a select query on the join of two tables.

What is the best approach for doing such kind of forward query resolution of find parameters based on user query ?

So far after digging around the source and some experimentation I have arrived at what I need using the @Inject decorator:

@Schema()
export class SuperSchema {

    @Query({
        type: [Issue]
    })
    async issues(
        @Inject((source, args, context, info) => {
            const fieldNodes = info.fieldNodes;
            if (!fieldNodes) return false;
            return fieldNodes.some((fn) => {
                if (!fn.selectionSet) return false;
                return fn.selectionSet.selections.some((s: any) => s && s.name && s.name.value === "creator");
            })
        })
        shouldIncludeCreator: boolean
    ) {
        const repo = getRepository(Issue);
        const relations = [];
        if (shouldIncludeCreator) {
            relations.push("creator");
        }
        const issues = await repo.find({relations});
        return issues;
    }
}

But I find this to be overly verbose for what I see a common use case in my application.

This can perhaps be simplified by extracting some utility functions but it will still be very repetitive when I have a dozen plus models, all exposed through the graphql api.

So I want to know if there is a better solution for this that I am missing out on ?

My familiarity with both typeorm and graphql is very limited (a few days of casual exploration).

@pie6k
Copy link
Collaborator

pie6k commented Apr 3, 2018

Hey @lorefnon

There is great library https://github.com/jakepusateri/graphql-list-fields that is able to give you string[] of needed fields from info variable.

It can be used inside Inject function:

@Schema()
class SuperSchema {
  @Query({ type: [Issue] })
  issues(
    @NeededFields(['creator'])
    relations: string[], 
  ) {
    console.log({ relations });
    // ...
    return repo.find({ relations });
  }
}

I've created function named NeededFields that returns @Inject decorator. I could use @Inject inline, but it'd make schema code less readable. Here is this function definition:

import * as getFieldNames from 'graphql-list-fields';
import { Inject } from 'typegql';

function NeededFields(filterNames: string[] = []) {
  return Inject(({ info }) => {
    const selectionFieldNames: string[] = getFieldNames(info);

    return selectionFieldNames.filter(fieldName => {
      return allowed.includes(fieldName);
    });
  });
}

It takes argument filterNames: string[] so you can filter fields list to only those that are interesting for you (relations in your case).

Let me know if that is helpful for you.

Also, make sure to upgrade typegql to latest version (0.3.0+) as Inject api has changed slightly.

@pie6k
Copy link
Collaborator

pie6k commented Apr 3, 2018

I've also created some example as I think it's quite common use-case:

@pie6k pie6k closed this as completed Apr 3, 2018
@lorefnon
Copy link
Contributor Author

lorefnon commented Apr 3, 2018

This is great! Thanks a lot for your work on this library.

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

No branches or pull requests

2 participants