Unable to use Fragments on GraphQL-yoga with Primsa - graphql

I am using graphql-yoga with Prisma and Prisma-Bindings
I'm trying to add a fragment to my resolver so that a specific field (id in this situation) is always fetched when the user asks for a custom field, costsToDate.
This is so i can make some additional queries needed to build the result for that field, and i need the ID of the object for that.
Unfortunatley i can't seem to get it to work, and the documentations seems a little lacking on the specifics with graphql-yoga and Prisma.
Here is the definition of the type:
type Job {
id: ID!
projectNumber: String!
client: Client!
name: String!
description: String
quoteNumber: String
workshopDaysQuoted: String!
quoted: String!
targetSpend: String!
costs: [JobCost!]!
estimatedCompletion: DateTime
completed: Boolean!
costTotal: String
workshopDaysUsed: String
costsToDate: String
}
And here is the resolver for the query:
const jobs = {
fragment: `fragment description on Jobs { id }`,
resolve: jobsResolver
}
async function jobsResolver(root, args, context, info) {
await validatePermission(args,context,info,['admin','user','appAuth'])
const {showCompleted} = args
const completedFilter = (typeof showCompleted === 'boolean') ? { completed: showCompleted } : {}
const jobIDS = await context.db.query.jobs({ where: completedFilter }, `{ id }`)
//console.log(jobIDS);
let jobs = await context.db.query.jobs({
where: completedFilter
}, info)
return await getAllJobCostsToDateList(jobs)
}
I am applying the the fragmentReplacements as per below.
const fragmentReplacements = extractFragmentReplacements(resolvers)
console.log(fragmentReplacements)
const port = process.env.PORT || 3010
const graphQLServer = new GraphQLServer({
typeDefs: './src/schema.graphql',
resolvers,
resolverValidationOptions: {
requireResolversForResolveType: false
},
context: req => ({
...req,
db: new Prisma({
typeDefs: `src/generated/prisma.graphql`,
fragmentReplacements,
endpoint: PRISMA_ENDPOINT,
secret: PRISMA_KEY,
debug: false
})
})
})
If i console.log the fragmentReplacements object i get the following, so it does seem to be picking up the fragments.
[ { field: 'job', fragment: 'fragment costsToDate on Job { id }' },
{ field: 'jobs',
fragment: 'fragment costsToDate on Jobs { id }' } ]
So my expectation here is that if i make a query against jobs or job that asks for the costsToDate field that it will also fetch the id for the job/each job.
However if i make the following query.
query{
jobs{
description
costsToDate
}
}
But i see no id fetched, and nothing in the root parameter on the resolver function.
Apologies as i am probably barking up completely the wrong tree here, seems like a somewhat simple requirement, but i can't quite work it out. Sure i'm missing something fundamental.
Thanks!
Gareth

A fragment is used to always retrieve given fields on a given type.
It follows the following format:
fragment NameOfYourFragment on YourType { ... }
You currently can't apply a given fragment conditionally as it is always applied.
Moreover, you specified a fragment on Jobs, but the type name used by Prisma is Job (even if you have the job and jobs resolvers)
You probably only need the following resolver:
const job = {
fragment: `fragment JobId on Job { id }`,
resolve: jobsResolver
}

Related

GraphQL Codegen duplicates RegisterDocument with typescript-urql

The codegen.ts config below results in duplicating the RegisterDocument entries.
codegen.ts:
const config: CodegenConfig = {
overwrite: true,
schema: "http://localhost:4000/graphql",
documents: "src/graphql/**/*.graphql",
generates: {
"src/generated/graphql": {
preset: "client",
plugins: [
"typescript-urql"
],
config: {
documentVariableSuffix: 'test2'
}
}
}
};
the output:
export const RegisterDocument = {"kind":"Document", ...}
export const RegisterDocument = gql`
mutation Register($username: String!, $password: String!) {
register(options: {username: $username, password: $password}) {
errors {
field
message
}
user {
id
username
createdAt
}
}
}
`;
export function useRegisterMutation() {
return Urql.useMutation<RegisterMutation, RegisterMutationVariables>(RegisterDocument);
};
Seemingly either the documentVariableSuffix param didn't affect the output const naming or it was a wrong param. The use of the typescript-operations or/and typescript packages only led to more duplicates.
What is the way to have typescript-urql register the mutation differently?
UP. The register mutation I need typings for:
const registerMutationDocument = graphql(`
mutation Register($username: String!, $password: String!) {
register(options: { username: $username, password: $password }) {
errors {
field
message
}
user {
id
username
createdAt
}
}
}
`)
I'm Charly, from The Guild, working on GraphQL Code Generator.
The preset: "client" is not meant to be used in combination with other plugins.
You must use either the client-preset or typescript-urql-plugin which provides 2 different ways to get typed GraphQL Operations.
The typescript-urql-plugin generates React hooks while, the client-preset generated typed GraphQL documents that can be used with URQL's native useQuery() and useMutation().
We now recommend using the client-preset that provides a better developer experience and smaller generated code for the same result.
You will find a guide to setup the client-preset with URQL here: https://the-guild.dev/graphql/codegen/docs/guides/react-vue
After a few attempts with various code pieces it seems I've got it to work. Thank you mr. Poly for the hints.
Here's the take.
First, the present iteration of graphql-codegen watches for .ts/.tsx documents not *.graphql ones. Second the only plugins needed are the ones listed in the docs.
const config: CodegenConfig = {
overwrite: true,
schema: "http://localhost:4000/graphql",
documents: "src/graphql/mutations/*.ts",
generates: {
"src/generated/graphql/": {
preset: "client",
plugins: []
}
}
};
Third the way to put the mutations etc. to a dedicated folder that I used was to create one at src/graphql/mutations. The register.ts holding the mutation had the following code:
import { graphql } from '../../generated/graphql';
export const registerMutationDocument = graphql(`
mutation Register($username: String!, $password: String!) {
register(options: { username: $username, password: $password }) {
errors {
field
message
}
user {
id
username
createdAt
}
}
}
`);
The only problem for me was if I tried to add a field to it that didn't exist on the model the editor froze.
The usage within the component:
import { registerMutationDocument } from '../graphql/mutations/register';
...
const [, register] = useMutation(registerMutationDocument);

How to implement mutations with optional arguments in GraphQL?

I am learning about graphql, and went through the https://www.howtographql.com/graphql-js/3-a-simple-mutation/ tutorial, and was interested in what the implementation of the updateLink mutation as follows would look like.
type Query {
# Fetch a single link by its `id`
link(id: ID!): Link
}
type Mutation {
# Update a link
updateLink(id: ID!, url: String, description: String): Link
}
The reason I am asking this is that every other mutation implementation I have seen uses only NON-optional parameters. I am curious if there is a community-agreed-upon pattern for extracting and applying only the provided non-null arguments(url, description) from the given context and applying them to relevant the database record.
I have considered checking if each variable is null as follows, but this approach looks way messier than I would expect compared to the rest of the 'magic' and simplicity that Graphql provides.
updateLink(root, args, context) {
if (args.url == null && args.description == null){
return null
} else if (args.url == null) {
return context.prisma.updateLink({
id: args.id,
description: args.description
})
} else {
return context.prisma.updateLink({
id: args.id,
url: args.url
})
}
}
Please let me know if you found a cleaner way to extract and apply the optional arguments(url, description).
Another consideration I had was to make two separate update mutations as follows.
type Query {
# Fetch a single link by its `id`
link(id: ID!): Link
}
type Mutation {
# Update a link
updateLinkURL(id: ID!, url: String!): Link
updateLinkDescription(id: ID!, description: String!): Link
}
The thinking here was with limited arguments and a declarative mutation name, one could force the arguments to be Non-Null. The main issue here is that one can have many update methods for tables with many columns, this would also start to look messy.
FYI I am using prisma as my ORM.
const resolvers = {
Query: {
info: () => `This is the API of a Hackernews Clone`,
feed: () => links,
link: (parent, args) => {
// console.log(args)
return links.find((link) => link.id === args.id)
}
},
Link: {
id: (parent) => parent.id,
description: (parent) => parent.description,
url: (parent) => parent.url,
},
}

how can I fetch data from graphql in my resolver

Within my resolver I seem to be unable to fetch connected data
this works in the graphql playground (prisma) but I am unsure of the syntax about how to form a resolver in apollo server
// my typedef for activity is
type Activity {
id: ID! #id
ActivityType: ActivityType!
title: String!
date: DateTime
user: User!
distance: Float!
distance_unit: Unit!
duration: Float!
elevation: Float
elevation_unit: Unit
createdAt: DateTime! #createdAt
updatedAt: DateTime! #updatedAt
// and my resolver currently looks like this
async activity(parent, args, ctx, info) {
const foundActivity = await ctx.db.query.activity({
where: {
id: args.id
}
});
// todo fetch user data from query
console.log(foundActivity);
}
// where db has been placed onto the ctx (context)
// the CL gives all of the data for Activity apart from the user
// in the playground I would do something like this
query activity {
activity(where: {
id: "cjxxow7c8si4o0b5314f9ibek"
}){
title
user {
id
name
}
}
}
// but I do not know how to specify what is returned in my resolver.
console.log(foundActivity) gives:
{ id: 'cjxxpuh1bsq750b53psd2c77d',
ActivityType: 'CYCLING',
title: 'Test Activity',
date: '2019-07-10T20:21:27.681Z',
distance: 13.4,
distance_unit: 'KM',
duration: 90030,
elevation: 930,
elevation_unit: 'METERS',
createdAt: '2019-07-10T20:48:50.879Z',
updatedAt: '2019-07-10T20:48:50.879Z' }
Prisma is the DB ORM and then I have an Apollo-Server 2 server running on top of that. Unfortunately, stack overflow also thinks that there is too much code on this post so I will have to waffle on about inconsequential gibberish due to the fact that their system can't handle it.
You will have to implement a resolver for Activity.user. Unfortunately your entity does not seem to contain a reference to the user. First, add the user connection to your Prisma data model. Then implement a resolver for Activity.user. I am not very familiar with Prisma 1 but this naive implementation should already do what you want:
let resolvers = {
Query: {
// ...
},
Activity: {
user(parent, args, ctx) {
return ctx.db.query.activity({ id: parent.id }).user();
}
}
}
Find out more about resolving relations in Prisma here
So the answer was incredibly simple:
I just add a second argument to the query (after the "where" with a gql tag of the data shape to be returned so my code now looks like:
const foundActivity = await ctx.db.query.activity(
{
where: {
id: args.id
}
},
`{id title user { id name }}`
);

How get rid of redundant wrapper object of a mutation result?

When I'm making a request to my backend through a mutation like that:
mutation{
resetPasswordByToken(token:"my-token"){
id
}
}
I'm getting a response in such format:
{
"data": {
"resetPasswordByToken": {
"id": 3
}
}
}
And that wrapper object named the same as the mutation seems somewhat awkward (and at least redundant) to me. Is there a way to get rid of that wrapper to make the returning result a bit cleaner?
This is how I define the mutation now:
export const ResetPasswordByTokenMutation = {
type: UserType,
description: 'Sets a new password and sends an informing email with the password generated',
args: {
token: { type: new GraphQLNonNull(GraphQLString) },
captcha: { type: GraphQLString },
},
resolve: async (root, args, request) => {
const ip = getRequestIp(request);
const user = await Auth.resetPasswordByToken(ip, args);
return user.toJSON();
}
};
In a word: No.
resetPasswordByToken is not a "wrapper object", but simply a field you've defined in your schema that resolves to an object (in this case, a UserType). While it's common to request just one field on your mutation type at a time, it's possible to request any number of fields:
mutation {
resetPasswordByToken(token:"my-token"){
id
}
someOtherMutation {
# some fields here
}
andYetAnotherMutation {
# some other fields here
}
}
If we were to flatten the structure of the response like you suggest, we would not be able to distinguish between the data returned by one mutation from another. We likewise need to nest all of this inside data to keep our actual data separate from any returned errors (which appear in a separate errors entry).

Enumerating all fields from a GraphQL query

Given a GraphQL schema and resolvers for Apollo Server, and a GraphQL query, is there a way to create a collection of all requested fields (in an Object or a Map) in the resolver function?
For a simple query, it's easy to recreate this collection from the info argument of the resolver.
Given a schema:
type User {
id: Int!
username: String!
roles: [Role!]!
}
type Role {
id: Int!
name: String!
description: String
}
schema {
query: Query
}
type Query {
getUser(id: Int!): User!
}
and a resolver:
Query: {
getUser: (root, args, context, info) => {
console.log(infoParser(info))
return db.Users.findOne({ id: args.id })
}
}
with a simple recursive infoParser function like this:
function infoParser (info) {
const fields = {}
info.fieldNodes.forEach(node => {
parseSelectionSet(node.selectionSet.selections, fields)
})
return fields
}
function parseSelectionSet (selections, fields) {
selections.forEach(selection => {
const name = selection.name.value
fields[name] = selection.selectionSet
? parseSelectionSet(selection.selectionSet.selections, {})
: true
})
return fields
}
The following query results in this log:
{
getUser(id: 1) {
id
username
roles {
name
}
}
}
=> { id: true, username: true, roles: { name: true } }
Things get pretty ugly pretty soon, for example when you use fragments in the query:
fragment UserInfo on User {
id
username
roles {
name
}
}
{
getUser(id: 1) {
...UserInfo
username
roles {
description
}
}
}
GraphQL engine correctly ignores duplicates, (deeply) merges etc. queried fields on execution, but it is not reflected in the info argument. When you add unions and inline fragments it just gets hairier.
Is there a way to construct a collection of all fields requested in a query, taking in account advanced querying capabilities of GraphQL?
Info about the info argument can be found on the Apollo docs site and in the graphql-js Github repo.
I know it has been a while but in case anyone ends up here, there is an npm package called graphql-list-fields by Jake Pusareti that does this. It handles fragments and skip and include directives.
you can also check the code here.

Resources