Handle import dependency cycle in graphql - javascript

Let's say I declare two GraphQL types like this:
export const ChannelType = new GraphQLObjectType({
name: 'Channel',
fields: {
messages: { type: new GraphQLList(MessageType) },
},
});
export const MessageType = new GraphQLObjectType({
name: 'Message',
fields: {
channel: { type: ChannelType },
text: { type: GraphQLString },
},
});
The channel contains a list of messages, and the message needs to reference its owner channel.
The issue is that, if declared in a single file like this, the ChannelType does not know the Message type exists (because defined after).
And if I split them into separated files importing each other, I end up with an import dependency cycle.
I feel like this is a common issue but I can't get my head around it.
Any help?

Related

NextAuth Credentials, adding more to the user scheme

Nextauth with mysql persisting users.
I'm trying out this NextAuth thing to see if this is something I like. So far so good. There is one thing tho which is buggin me and that would be the user scheme. By default it returns a name, image and the last one I forgot.
I'd like to add more to this scheme and found some ways to do it by looking at google, however those I tried did not work.
One example I found is by extending the model which clearly makes sense...
The issue here is then me, I do not know what to change in the code below to make it work with my NextAuth credentials provider. As shown below, this doesnt work.
projectfolder -> models -> index.js
import User, { UserSchema } from "./User"
export default {
User: {
model: User,
schema: UserSchema
}
}
projectfolder -> models -> user.js
import Adapters from "next-auth/adapters"
// Extend the built-in models using class inheritance
export default class User extends Adapters.TypeORM.Models.User.model {
constructor(name, email, image, emailVerified, roles) {
super(name, email, image, emailVerified)
if (roles) { this.roles = roles}
}
}
export const UserSchema = {
name: "User",
target: User,
columns: {
...Adapters.TypeORM.Models.User.schema.columns,
roles: {
type: "varchar",
nullable: true
},
},
}
In my [...nextauth].js file I have my provider, in this provider i've added an profile() field with the extra fields. This did not solve the issue.
profile(profile) {
return {
name: profile.name,
email: profile.email,
role: profile.role
};
},
Please correct me if I am wrong but if I am using credentials, then I need to replace the "TypeORM" with something else, correct? How about the path for the files, are they correct?
This should clearly be quite easy but am I missing something or am I doing something wrong? I feel like there is a lack of documentation on extending the user model for mysql.
I've doubled checked that the role is being retrieved from the database and then added to the user variable shown here:
async authorize ....
const user = {
name: result.display_name,
role: result.role_name,
email: result.email
}
Although I can see the role being set in the variable with my console.log(), I still cannot access the role and that I suspect is because of the model. How would I resolve this? Thanks a lot in advance.
Any ideas?
----------------------- UPDATES ------------------------
Btw, here is my callback
callbacks: {
async signIn({ user, account, profile, email }) {
console.log("user", user);
return true;
},
},
and this is what it returns (shortened)
token: {
token: { name: 'Firstname Lastname', email: 'test#mail.com' },
user: {
name: 'Firstname Lastname',
role: 'administrator',
email: 'test#mail.com'
},
account: { type: 'credentials', provider: 'credentials' },
isNewUser: false,
iat: 1634193197,
exp: 1636785197
}
I'm new to TypeORM and I am facing the same problems as people here.
What I've done was create a separate Entity which I called users_info to store the other information and retrieve it after signing in.
It looks like this:
import { UserEntity } from './NextAuthEntities';
#Entity({ name: 'users_info' })
export class MemberEntity {
#PrimaryGeneratedColumn('increment')
id!: number;
#OneToOne(() => UserEntity)
#JoinColumn({
name: 'auth_id',
referencedColumnName: 'id',
})
auth_id!: UserEntity;
#Column({ type: 'varchar', nullable: true })
full_name!: string | null;
// etc
}
Then, I created a handshake API route to retrieve users_info if the user is signed-in.
When I added a new #Column on my custom UsersEntity, it threw me an error when I tried to login. It seems like TypeORMLegacyAdapter can't be extended or be different from the default UserEntity.
Hope it helps

What is type "StringQueryOperatorInput"? How can I get rid of this annoying graphql error?

I am attempting to create Gatsby pages programmatically using the Gatsby API createPages and data from Firebase. I've set up everything successfully up to the point where Firebase data is accessible via GraphQL and now I want to query specifict data for each of the new pages that were created using id (which are in string format). However, when I create the template component and try to query the data i get this error:
Variable "$clientId" of type "String!" used in position expecting type "StringQueryOperatorInput".
I have looked everywhere for a reference of this StringQueryOperatorInput and can't find any info on it. Google and graphql docs don't seem to mention the term and this is my first time seeing it. After troubleshooting for an hour I got a different error:
If you're e.g. filtering for specific nodes make sure that you choose the correct field (that has the same type "String!") or adjust the context variable to the type "StringQueryOperatorInput".
File: src/templates/Homeowner/Homeowner.js:24:9
However, I still don't know what a StringQueryOperatorInput is or how to fix this.
Below is my code for this component and my gatsby-node.js, and my gatsby-config.js where i use a plugin to source the Firebase data.
I could really use some help on this, I can't seem to find any reference of this StringQueryOperatorInput.
Everything else works fine, I just can't get this query on the Homeowner.js template to work.
gatsby-node.js
exports.createPages = async ({ graphql, actions }) => {
const { createPage } = actions;
const result = await graphql(`
query {
allClients {
nodes {
firstName
lastName
id
}
}
}
`);
console.log(JSON.stringify(result, null, 4));
result.data.allClients.nodes.forEach(node => {
const slug = `/client/${node.id}`;
createPage({
path: slug,
component: require.resolve(`./src/templates/Homeowner/Homeowner.js`),
context: { clientId: node.id },
});
});
};
src/templates/Homeowner/Homeowner.js
import React from 'react';
import { graphql } from 'gatsby';
import { withFirebase } from '../../components/Firebase';
import { withStyles } from '#material-ui/core/styles';
import Layout from '../../components/layout';
const Homeowner = ({ data }) => {
console.log(data.clients, 'data');
return (
<>
<Layout>
<h1>Home Owner Component</h1>
{/* <h3>{client.firstName}</h3>
<h3>{client.lastName}</h3>
<h3>{client.email}</h3> */}
</Layout>
</>
);
};
export default Homeowner;
export const query = graphql`
query($clientId: String!) {
clients(id: $clientId) {
firstName
lastName
email
}
}
`;
gatsby-config.js
require('dotenv').config({
path: `.env.${process.env.NODE_ENV}`,
});
module.exports = {
siteMetadata: {
title: `SiteTitle`,
siteUrl: `https://www.mysitwe.com`,
description: `YourSite`,
},
plugins: [
`gatsby-plugin-react-helmet`,
`gatsby-plugin-sitemap`,
`gatsby-plugin-styled-components`,
`gatsby-plugin-sharp`,
`gatsby-transformer-sharp`,
{
resolve: `gatsby-source-firebase`,
options: {
credential: require('./firebase-key.json'),
databaseURL: 'https://firebaseurl/',
types: [
{
type: 'Clients',
path: 'clients',
},
{
type: 'Users',
path: 'users',
},
],
},
},
{
resolve: `gatsby-plugin-prefetch-google-fonts`,
options: {
fonts: [
{
family: `Nunito Sans`,
variants: [`400`, `600`, `800`],
},
{
family: `Montserrat`,
variants: [`300`, `400`, `400i`, `500`, `600`],
},
{
family: `Spectral`,
variants: [`400`, `600`, `800`],
},
{
family: `Karla`,
variants: [`400`, `700`],
},
],
},
},
{
resolve: `gatsby-source-filesystem`,
options: {
name: `images`,
path: `${__dirname}/src/images`,
},
},
`gatsby-plugin-offline`,
],
};
THank you in advance if anyone can help me out.
Actually literally right after I posted this question I found the solution. I needed to set up my query like so:
export const query = graphql`
query($clientId: String!) {
clients(id: { eq: $clientId }) {
firstName
lastName
email
}
}
`;
I assume that leaving out the {eq: $clientId} throws that StringQuery error on the GraphQL side. I still do not know what a StringQueryOperatorInput is, however, I have successfully generated the pages with the data from firebase.
StringQueryOperatorInput is the type of the id argument of the clients field. GraphQL includes scalar types like String, ID or Int as well as types that describe more complex data structures like arrays or objects. In this case, StringQueryOperatorInput is an input object type -- it describes objects that can be used as inputs like argument values or variables.
When filtering fields, Gatsby uses an input object like this to enable using a variety of comparison operators to filter the exposed data -- in addition to eq (equals), you can use other operators like ne, regex, in, gt, etc. You can see the full list here. Because not all inputs apply to all scalars (regex makes sense for a String field but lte does not), there's a different input type for each scalar (IntQueryOperatorInput, BooleanQueryOperatorInput, etc.)
Gatsby exposes a GraphiQL endpoint in development. Writing queries using GraphiQL allows you to utilize autocomplete and syntax highlighting so that you can avoid unexpected syntax errors like this. You can also use the "Docs" button to search and browse the entire schema.

How do I properly create a connection in relay & graphql schema?

I am trying to create a pagination in relay & graphql in my schema. This is my code:
const GraphQLFriendByUser = new GraphQLObjectType({
name: 'FriendByUser',
fields: {
id: globalIdField('FriendByUser'),
_id: {
type: GraphQLString,
resolve: (root) => root._id,
},
email: {
type: GraphQLBoolean,
resolve: (root) => root.email,
},
fullName: {
type: GraphQLString,
resolve: (root) => {
return root.fullName;
},
},
},
interfaces: [nodeInterface],
});
const {
connectionType: FriendsByUserConnection,
edgeType: GraphQLFriendByUserEdge,
} = connectionDefinitions({
name: 'FriendByUser',
nodeType: GraphQLFriendByUser,
});
I created this similarly looking at sample in todo-modern, In my TodoListHome component, this is my graphql query:
friendsByUserContext( first: 200 ) #connection(key: "TodoListHome_friendsByUserContext") {
id
_id
fullName
email
}
there's no problem when I update the schema, but when I try to build, this is my error:
Invariant Violation: RelayParser: Unknown field id on type
FriendByUserConnection. Source: document TodoListHome_viewer file:
C:\Users\PE60\Desktop\socialnetworking-todoapp\js\components\TodoListHome.js.
The execution doesn't even reach on resolve, so I cannot log. but I believe the error is not from there. Help?
Since you're using graphql-js, you probably have graphiql hosted on your /graphql endpoint. That's a good place to debug queries and see the structure of your schema. There you'll see that the connection you're talking about has the field edges which has the field node which has the fields you're looking for.

Model not associated with Model Sequelize

I'm trying to esatblish a One-To-Many relationship between the tables: Exam and Exam_Questions, using Sequelize.
Even though the tables are created properly and I can see them in PhpMyAdmin, I keep getting the following error in console:
Error: exam_question is not associated to exam!
exam.js
...
const ExamQuestion = require('./exam-question');
...
const Exam = sequelizeInstance.define("exam", {
name: { type: Sequelize.STRING },
date: { type: Sequelize.DATE }
});
// Build the model relations
Exam.hasMany(ExamQuestion, { as: "Questions" });
exam-question.js
const ExamQuestion = Sequelize.db.define("exam_question", {
correct_answer: {
type: Sequelize.STRING
},
text: {
type: Sequelize.STRING
}
});
module.exports = ExamQuestion;
To solve the error, I tried:
ExamQuestion.belongsTo(Exam);
But that doesn't change anything.
The query is:
Exam.findAll({
include: [ExamQuestion]
})
How to fix this problem and get the Exam objects including their questions?
TL;DR
For some very non-intuitive reason this seems to be happening because of the as property. To fix the problem, simply remove the as property:
Exam.hasMany(ExamQuestion);
Fixing the methods
By default, after removing the as property, Sequelize will automagically add the following methods: getExam_questions, addExam_question and so on.
They look quite bad: camel and snake cases mixed up together.
To solve that, we can easily define the singular and plural names in the ExamQuestion model options (the third argument):
const ExamQuestion = Sequelize.db.define("exam_question", {
correct_answer: {
type: Sequelize.STRING
},
text: {
type: Sequelize.STRING
}
}, {
name: {
singular: "question",
plural: "questions"
}
});
This will dictate Sequelize to create methods such as getQuestions and addQuestion instead of getExam_questions and addExam_question.

How to create a list of custom objects in GraphQL

I am currently playing around with a bunch of new technology of Facebook.
I have a little problem with GraphQL schemas.
I have this model of an object:
{
id: '1',
participants: ['A', 'B'],
messages: [
{
content: 'Hi there',
sender: 'A'
},
{
content: 'Hey! How are you doing?',
sender: 'B'
},
{
content: 'Pretty good and you?',
sender: 'A'
},
];
}
Now I want to create a GraphQL model for this. I did this:
var theadType = new GraphQLObjectType({
name: 'Thread',
description: 'A Thread',
fields: () => ({
id: {
type: new GraphQLNonNull(GraphQLString),
description: 'id of the thread'
},
participants: {
type: new GraphQLList(GraphQLString),
description: 'Participants of thread'
},
messages: {
type: new GraphQLList(),
description: 'Messages in thread'
}
})
});
I know there are more elegant ways to structure the data in the first place. But for the sake of experimenting, I wanted to try it like this.
Everything works fine, besides my messages array, since I do not specify the Array type. I have to specify what kind of data goes into that array. But since it is an custom object, I don't know what to pass into the GraphQLList().
Any idea how to resolve this besides creating an own type for messages?
You can define your own custom messageType the same way you defined theadType, and then you do new GraphQLList(messageType) to specify the type of your list of messages.
I don't think you can do this in GraphQL. Think that it's a bit against GraphQL philosophy of asking for the fields "you need" in each component against asking for "them all".
When the app scales, your approach will provoque higher loads of data. I know that for the purpose of testing the library looks a bit too much but it seems this is how it is designed. Types allowed in current GraphQL library (0.2.6) are:
GraphQLSchema
GraphQLScalarType
GraphQLObjectType
GraphQLInterfaceType
GraphQLUnionType
GraphQLEnumType
GraphQLInputObjectType
GraphQLList
GraphQLNonNull
GraphQLInt
GraphQLFloat
GraphQLString
GraphQLBoolean
GraphQLID

Categories