Include relationship when querying node using Prisma generated wrapper - graphql

I am following the GraphQL Prisma Typescript example provided by Prisma and created a simple data model, generated the code for the Prisma client and resolvers, etc.
My data model includes the following nodes:
type User {
id: ID! #unique
displayName: String!
}
type SystemUserLogin {
id: ID! #unique
username: String! #unique
passwordEnvironmentVariable: String!
user: User!
}
I've seeded with a system user and user.
mutation {
systemUserLogin: createSystemUserLogin({
data: {
username: "SYSTEM",
passwordEnvironmentVariable: "SYSTEM_PASSWORD",
user: {
create: {
displayName: "System User"
}
}
}
})
}
I've created a sample mutation login:
login: async (_parent, { username, password }, ctx) => {
let user
const systemUser = await ctx.db.systemUserLogin({ username })
const valid = systemUser && systemUser.passwordEnvironmentVariable && process.env[systemUser.passwordEnvironmentVariable] &&(process.env[systemUser.passwordEnvironmentVariable] === password)
if (valid) {
user = systemUser.user // this is always undefined!
}
if (!valid || !user) {
throw new Error('Invalid Credentials')
}
const token = jwt.sign({ userId: user.id }, process.env.APP_SECRET)
return {
token,
user: ctx.db.user({ id: user.id }),
}
},
But no matter what I do, systemUser.user is ALWAYS undefined!
This makes sense - how would the client wrapper know how "deep" to recurse into the graph without me telling it?
But how can I tell it that I want to include the User relationship?
Edit: I tried the suggestion below to use prisma-client.
But none of my resolvers ever seem to get called...
export const SystemUserLogin: SystemUserLoginResolvers.Type<TypeMap> = {
id: parent => parent.id,
user: (parent, args, ctx: any) => {
console.log('resolving')
return ctx.db.systemUserLogin({id: parent.id}).user()
},
environmentVariable: parent => parent.environmentVariable,
systemUsername: parent => parent.systemUsername,
createdAt: parent => parent.createdAt,
updatedAt: parent => parent.updatedAt
};
And...
let identity: UserParent;
const systemUserLogins = await context.db.systemUserLogins({
where: {
systemUsername: user,
}
});
const systemUserLogin = (systemUserLogins) ? systemUserLogins[0] : null ;
if (systemUserLogin && systemUserLogin.environmentVariable && process.env[systemUserLogin.environmentVariable] && process.env[systemUserLogin.environmentVariable] === password) {
console.log('should login!')
identity = systemUserLogin.user; // still null
}
Edit 2: Here is the repository
https://github.com/jshin47/annotorious/tree/master/server

There are currently two ways to solve this problem:
Using the Prisma client as OP does at the moment
Using Prisma bindings as was suggested by #User97 in the accepted answer
You can learn more about the difference between Prisma client and Prisma bindings in this forum post.
As OP is currently using Prisma client, I'll use it for this answer as well!
Let's take a look at a statement OP made in the question:
This makes sense - how would the client wrapper know how "deep" to recurse into the graph without me telling it?
OP stated correctly that the Prisma client can't know how to deep to go into the graph and what relationships to fetch. In fact, unless explicitly told otherwise (e.g. using the $fragment API), the client will never fetch any relationships and will always only fetch the scalar values. From the Prisma docs:
Whenever a model is queried using the Prisma client, all scalar fields of that model are fetched. This is true no matter if a single object or a list of objects is queried.
So, how to properly resolve this situation? In fact, the solution is not to make changes to the way how the Prisma client is used, but to implement an additional GraphQL resolver function!
The point about resolvers is that they're fetching the data for specific fields in your schema. In OP's case, there currently is no resolver that would "resolve" the user relation that's defined on the SystemUserLogin type:
type SystemUserLogin {
id: ID! #unique
username: String! #unique
passwordEnvironmentVariable: String!
user: User! # GraphQL doesn't know how to resolve this
}
To resolve this situation, you need to implement a dedicated "type resolver" for it like so:
const resolvers = {
SystemUserLogin: {
user(parent, args, ctx) {
return ctx.db.systemUserLogin({id: parent.id}).user()
}
}
}
Full disclosure: I work at Prisma and we're working on adding better documentation and resources for that use case. Also check out this example where explicit resolvers for the author and posts relation fields are required for the same reason.
Hope that helps!
EDIT: We have also added a slightly more thorough explanation in the Prisma tutorial about Common resolver patterns.

Second parameter of prisma binding functions accept GraphQL query string. Changing following line from
const systemUser = await ctx.db.query.systemUserLogin({ username })
to
const systemUser = await ctx.db.query.systemUserLogin({ username }, `{id username user {id displayName}}`)
will give you the data of user.
Prisma binding will return only direct properties of model in case second parameter is not passed to it.

Related

Using Apollo to mix local and remote fields using the #client directive

I'm using apollo-client#2.6.10 in a VueJS application.
I have a user type that has (until recently) used local-only fields. Here is the local query and corresponding mutation:
query currentUserQuery {
user #client {
id
email
emailVerified
displayName
}
}
mutation setCurrentUser($id: ID!, $email: String!, $emailVerified: Boolean!, $displayName: String) {
update_current_user(
id: $id,
email: $email,
emailVerified: $emailVerified,
displayName: $displayName
) #client
}
And I also have a corresponding resolver for the mutation:
resolvers = {
Mutation: {
update_current_user: (_, { id, email, emailVerified, displayName }, { cache }) => {
const data = cache.readQuery({ query: currentUserQuery })
const user = {
__typename: 'User',
id,
email,
emailVerified,
displayName
}
data.user = user
cache.writeData({ data })
return user
}
}
So far, so good!
What I'd like to do now is mix in some additional fields from the remote api. So, I modified the query as such:
query currentUserQuery {
user {
id #client
email #client
emailVerified #client
displayName #client
tier {
id
name
}
}
}
I also updated the initial cache object written cache.writeQuery to include placeholder tier data.
But no matter how I try to wrangle this, the tier is always the initialised null value, and after inspecting network traffic I can see that the remote fields are not being fetched.
Should I be doing something different with resolvers? I've tried using local query resolvers for the local fields (instead of relying on direct fetches from the cache) to no avail.
After reading through the v2 docs and trying a variety of different approaches I'm at a loss to understand what's not working here.
Help greatly appreciated!

Apollo Graphql Subscriptions: Different subscription resolvers for create, update & delete or a single resolver?

I am using Typegrpahql for creating my graphql server with express. I want to clarify whether I should create separate subscription resolvers for create, update and delete operations of an entity? Or should I create one resolver since the return type of the data will always be the same?
Suppose I have an entity like this
type Board {
createdAt: DateTime!
id: ID!
members: [User!]!
name: String!
owner: User!
stacks: [Stack!]!
updatedAt: DateTime
}
And I have a mutation resolver class like this
#Resolver(Board)
export class BoardMuations {
#Authorized()
#Mutation(() => Board)
async createBoard(
#PubSub() pubSub: PubSubEngine,
#Arg("name") name: string,
#Ctx() { uid }: MyContext
): Promise<Board> {
// perform create opration
}
#Authorized()
#Mutation(() => Board)
async updateBoard(
#PubSub() pubSub: PubSubEngine,
#Arg("boardId") boardId: string,
#Arg("name") name: string
): Promise<Board | undefined> {
// perform update operation
}
#Authorized()
#Mutation(() => DeleteResponse)
async deleteBoard(#PubSub() pubSub: PubSubEngine, #Arg("boardId") boardId: string): Promise<DeleteResponse> {
//perform delete operation
}
}
So should I create a subscription resolver like this
#Subscription({ topics: "CUD_BOARD" })
newBoard(#Root() data: Board): Board {
return data;
}
Or should I create different resolvers for every operation? what is this topics array?
I could also provide different topic strings as an array as I understand. What difference would it make to create different topics strings and publish the event using them?
I am currently publishing the event like this await pubSub.publish("CUD_BOARD", board);

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,
},
}

Unable to use Fragments on GraphQL-yoga with Primsa

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
}

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