How to add a field to a GraphQL document? - javascript

Let's say this is my graphql query:
mutation Test ($input: UpdateUserAccountInput!) {
updateUserAccount(input: $input) {
... on UpdateUserAccountPayload {
userAccount {
name
}
}
}
}
I want to modify to have the following fragment:
... on Error {
message
}
I was able to figure out that I can get AST using parse from graphql package, i.e.
import {
parse,
gql,
} from 'graphql';
parse(gql`
mutation Test ($input: UpdateUserAccountInput!) {
updateUserAccount(input: $input) {
... on UpdateUserAccountPayload {
userAccount {
name
}
}
}
}
`)
Now I am trying to figure out how to add ... on Error { message } to this query.
The problem that I am trying to solve is that my tests sometimes quietly fail because mutation returns an error that I did not capturing. I am extending my GraphQL test client to automatically request errors for every mutation and throw if error is returned.
I assume there exists some utilities that allow me to inject fields into AST, but so far I was not able to find them.

I think I figured it out.
Here is my solution:
const appendErrorFieldToMutation = (query: string) => {
const inlineErrorFragmentTemplate = {
kind: 'InlineFragment',
selectionSet: {
kind: 'SelectionSet',
selections: [
{
kind: 'Field',
name: {
kind: 'Name',
value: 'message',
},
},
],
},
typeCondition: {
kind: 'NamedType',
name: {
kind: 'Name',
value: 'Error',
},
},
};
const document = parseGraphQL(query);
if (document.definitions.length !== 1) {
throw new Error('Expected only one definition');
}
const definition = document.definitions[0] as OperationDefinitionNode;
if (definition.operation !== 'mutation') {
return query;
}
if (definition.selectionSet.selections.length !== 1) {
throw new Error('Expected only one document selection');
}
const documentSelection = definition.selectionSet.selections[0] as InlineFragmentNode;
const errorField = documentSelection.selectionSet.selections.find((selection) => {
return (selection as InlineFragmentNode).typeCondition?.name.value === 'Error';
});
if (!errorField) {
// #ts-expect-error – Intentionally mutating the AST.
documentSelection.selectionSet.selections.unshift(inlineErrorFragmentTemplate);
}
return printGraphQL(document);
};
I've not used any utilities, so perhaps there is a smarter way to do the same.

Related

Apollo resolver chain returning undefined? Cannot read properties of undefined

I'm new to Apollo (back-end in general) so your patience is appreciated; I've looked through Apollo's docs and I'm not sure where I've gone wrong.
The data I'm receiving from my REST API call is as follows - I'm trying to return the ids and titles and based on my trials, I'm fairly certain the issue is my resolvers? The error I'm receiving is: "Cannot read properties of undefined (reading: 'session')"
{
"selection": {
...other data...
"options": [
{
id: 1
title: "Option 1 Title"
},
{
id: 2
title: "Option 2 Title"
},
]
}
}
My schema:
type Query {
session: Selection
}
type: Selection {
...other data...
optionListing: [Options]
}
type: Options {
id: Int
title: String
}
My resolvers:
{
Query: {
session: async (parent, args, {tokenAuth}) => {
...token auth code....
return tokenAuth;
};
}
Selection: {
optionListing: async ({tokenAuth}) => {
...this is the resolver that triggers the API call...
return optionData;
}
}
Options: {
id: async(parent) => {
const tempID = await parent;
return tempID.id;
}
title: async(parent) => {
const tempTitle = await parent;
return tempTitle.title;
}
}
}

Reactive queries Apollo graphql in Vue 3

Trying to make a reactive query as per https://v4.apollo.vuejs.org/guide-option/queries.html#reactive-query-definition, but I can't get them to work.
Error:
Uncaught (in promise) Error: Invalid AST Node: { query: { kind: "Document", definitions: [Array], loc: [Object] }, loadingKey: "loading" }.
Query inside export default (checked is a reactive boolean v-model'ed to a button defined in data()):
apollo: {
getTags: getTags,
getPhotos: {
query() {
if (this.checked)
return {
query: getPhotos,
loadingKey: "loading",
}
else {
return {
query: getPhotosByTag,
loadingKey: "loading",
}
}
},
update: (data) => data.getPhotos || data.getPhotos,
},
},
GQL getPhotosByTag:
const getPhotosByTag = gql`
query getPhotosByTag {
getPhotos: findTagByID(id: 326962542206255296) {
photos {
data {
name
description
_id
}
}
}
}
`
GQL getPhotos:
const getPhotos = gql`
query getPhotos {
getPhotos: getPhotos(_size: 50) {
data {
name
description
_id
}
}
}
`
If I take them out into separate queries and try to instead update use skip() via checked and !checked in the query definition, only the initial load delivers a query, if I click the button new query doesn't launch. Queries work by themselves fine.
apollo: {
getPhotos: {
query() {
return this.checked ? getPhotos : getPhotosByTag
},
loadingKey: "loading",
update: (data) => data.getPhotos || data.getPhotos,
},
},
Loading key has to be on the same level as query

Construct MongoDB query from GraphQL request

Let's say we query the server with this request, we only want to get the following user's Email, My current implementation requests the whole User object from the MongoDB, which I can imagine is extremely inefficient.
GQL
{
user(id:"34567345637456") {
email
}
}
How would you go about creating a MongoDB filter that would only return those Specified Fields? E.g,
JS object
{
"email": 1
}
My current server is running Node.js, Fastify and Mercurius
which I can imagine is extremely inefficient.
Doing this task is an advanced feature with many pitfalls. I would suggest starting building a simple extraction that read all the fields. This solution works and does not return any additional field to the client.
The pitfalls are:
nested queries
complex object composition
aliasing
multiple queries into one request
Here an example that does what you are looking for.
It manages aliasing and multiple queries.
const Fastify = require('fastify')
const mercurius = require('mercurius')
const app = Fastify({ logger: true })
const schema = `
type Query {
select: Foo
}
type Foo {
a: String
b: String
}
`
const resolvers = {
Query: {
select: async (parent, args, context, info) => {
const currentQueryName = info.path.key
// search the input query AST node
const selection = info.operation.selectionSet.selections.find(
(selection) => {
return (
selection.name.value === currentQueryName ||
selection.alias.value === currentQueryName
)
}
)
// grab the fields requested by the user
const project = selection.selectionSet.selections.map((selection) => {
return selection.name.value
})
// do the query using the projection
const result = {}
project.forEach((fieldName) => {
result[fieldName] = fieldName
})
return result
},
},
}
app.register(mercurius, {
schema,
resolvers,
graphiql: true,
})
app.listen(3000)
Call it using:
query {
one: select {
a
}
two: select {
a
aliasMe:b
}
}
Returns
{
"data": {
"one": {
"a": "a"
},
"two": {
"a": "a",
"aliasMe": "b"
}
}
}
Expanding from #Manuel Spigolon original answer, where he stated that one of the pitfalls of his implementation is that it doesn't work on nested queries and 'multiple queries into one request' which this implementation seeks to fix.
function formFilter(context:any) {
let filter:any = {};
let getValues = (selection:any, parentObj?:string[]) => {
//selection = labelSelection(selection);
selection.map((selection:any) => {
// Check if the parentObj is defined
if(parentObj)
// Merge the two objects
_.merge(filter, [...parentObj, null].reduceRight((obj, next) => {
if(next === null) return ({[selection.name?.value]: 1});
return ({[next]: obj});
}, {}));
// Check for a nested selection set
if(selection.selectionSet?.selections !== undefined){
// If the selection has a selection set, then we need to recurse
if(!parentObj) getValues(selection.selectionSet?.selections, [selection.name.value]);
// If the selection is nested
else getValues(selection.selectionSet?.selections, [...parentObj, selection.name.value]);
}
});
}
// Start the recursive function
getValues(context.operation.selectionSet.selections);
return filter;
}
Input
{
role(id: "61f1ccc79623d445bd2f677f") {
name
users {
user_name
_id
permissions {
roles
}
}
permissions
}
}
Output (JSON.stringify)
{
"role":{
"name":1,
"users":{
"user_name":1,
"_id":1,
"permissions":{
"roles":1
}
},
"permissions":1
}
}

Encountered error while updating pages in a database with Notion API

I recently made an integration between Todoist and Notion. The main idea is that every time I do something with a task on Todoist, the changes are reflected on Notion. I'm encountering a problem with the notion.pages.update() function: every time I modify a task, it returns this error:
body failed validation. Fix one: body.properties.body.id should be defined, instead was undefined. body.properties.body.name should be defined, instead was undefined. body.properties.body.start should be defined, instead was undefined.
I don't even have a body property in my database.
Here's my update function, keep in mind that this is a function inside a module I made:
my.updateTask = async function (notion_page_id, name, todoist_project_id, do_date, priority, in_discord) {
var req_body = {
page_id: notion_page_id,
properties: {
Name: {
type: "title",
title: [
{
type: "rich_text",
rich_text: {
content: name
}
}
],
},
Project: {
type: "relation",
relation: [
{
database_id: my.getProject("todoist_id", todoist_project_id).notion_id
}
]
},
Priority: {
number: priority
},
isOnDiscord: {
checkbox: in_discord
}
}
}
if (typeof do_date !== 'undefined' && do_date !== null) {
start_dt = new Date(do_date.date);
end_dt = new Date(do_date.date);
end_dt.setHours(end_dt.getHours() + 24);
req_body.properties.DoDate = {
date: {
start: start_dt.toISOString().replace(/Z$/, '+02:00'),
end: end_dt.toISOString().replace(/Z$/, '+02:00')
}
}
}
const response = await my.api.pages.update(req_body);
return response !== {};
}
Any help is highly appreciated!
I think your Name property should look like this:
Name: {
title: [
{
text: { content: name }
}
]
}
You can see that here in the docs: https://developers.notion.com/reference/patch-page
I solved it, the problem was actually in another function (the getProject function)

GraphQL-js mutation optional arguments

how would I achieve a GraphQL Mutation in nodeJS that has arguments which are optional?
My current mutation has an args field, but all the arguments are mandatory. Since I couldn't find anything in the documentation, I don't know how or if this is possible.
This is my current code:
const fakeDB = {
count: 0
};
const schema = new GraphQLSchema({
query: //...
mutation: new GraphQLObjectType({
name: 'adsF',
fields: {
updateCount: {
type: GraphQLInt,
args: {
count: { type: GraphQLInt } // I want to make this argument optional
},
resolve: (value, { count }) => {
// Catch if count is null or undefined
if (count == null) {
// If so just update with default
fakeDB.count = 5;
} else {
fakeDB.count = count
}
return fakeDB.count;
})
}
}
})
});
Thanks for Your help!
Types in GraphQL are nullable by default. That means that the way you have specified the mutation at the moment makes the count optional. If you wanted a field to be mandatory you need to mark it as non null

Categories

Resources