I'm performing this query
query MyQuery($roomId: String, $closetId: String, $caseId: String) {
room(roomId: $roomId) {
closet(closetId: $closetId){
case(caseId: $caseId)
}
}
}
I have a middleware applied to case where i need to perform a test on the first parent's arg roomId
I'm using the library https://www.npmjs.com/package/graphql-middleware
export const globalMiddleware = async (resolve, root, args, context, info) => {
console.log(args.caseId)
console.log(root.closetId)
console.log(grandparent.roomId) // !!!!!!!!!!!!
// ... do something
return await resolve(root, args, context, info)
}
I can access its own arg caseId with args.caseId
I can access its direct parent's arg closetId with root.closetId
Question :
How do I access its first parent's arg roomId ??
PS: the code is ultra simplified, in my real code i have many more resolvers, nested on deeper levels. Which means that passing manually the argument roomId to every reslver is not a practical solution
I could go with accessing only the first parent's args though
It looks like the way it's build, there is no direct way to access resolver's grand-parent/first-parent.
One way to solve this is by passing all the query's params to the context:
const server = new ApolloServer({
schema: schemaWithMiddleware,
context: async ({ req }) => {
return {
variables: req.body.variables
}
}
})
this way you can access all of the params in all resolvers:
export const globalMiddleware = async (resolve, root, args, context, info) => {
console.log(context.variables.roomId)
// ... do something
return await resolve(root, args, context, info)
}
Related
Let's say I have a simple GraphQL type for a user:
type User {
id: ID!
name: String!
}
Query {
user(id:ID!)
}
and a resolver
user = (_, {id}, {api})=> api.getUser(id)
Now I have add a new field to the User called friends and added a new resolver for the User.friends field.
friends = ({id}, _, {api})=> api.getFriends(id)
So now I wonder when we made a query like this, how can I prevent the call to api.getUser but only call api.getFriends.
query {
user(id){
friends {
name
}
}
}
My understanding is that having a resolver defined for the user field in the Query type, it will always call this resolver first and after that all resolvers for fields in the User type.
This is a common problem and there is for example this solution out there: https://github.com/gajus/graphql-lazyloader
Check out the README of the project for a structured description of your problem.
Alternatively, you can implement your own class that contains a cached value making use of how GraphQL.js implements default resolvers:
class User {
constructor(id) {
this.id = id;
}
getInstance({ api }) {
if (!this.instance) {
this.instance = api.getUser(this.id);
}
return this.instance;
}
// notice how id is already a property of this class
name(args, ctx) {
return this.getInstance(ctx).then(instance => instance.name);
}
// do the same for other fields, user will only be fetched once.
friends(args, { api }) {
return api.getFriends(this.id);
}
}
const resolvers = {
Query: {
user: (args) => new User(args.id),
}
}
If you use dataloader you can even do this with even less code thanks to caching in dataloader:
// You probably have this function already somewhere in your apollo server creation
function createContext({ api }) {
return {
api,
loaders: {
user: new Dataloader((ids) => ids.map(id => api.getUser(id))),
},
}
}
const resolvers = {
Query: {
user: (parent, args) => ({ id: args.id }),
},
User: {
name: ({ id }, args, { loaders }) =>
loaders.user.load(id).then(user => user.name),
otherProp: ({ id }, args, { loaders }) =>
loaders.user.load(id).then(user => user.otherProp),
friends: ({ id }, args, { api })=> api.getFriends(id),
}
}
Dataloader will, even when called twice, only reach to the API once. An added benefit is, that it will cache the value. Ideally, you even provide a batch load function in the API to make the loader even more efficient.
Be aware, that user.fields.name now makes calls for every friend to the API. To avoid that, you could check if the property exists:
name: (parent, args, { loaders }) =>
parent.name ?? loaders.user.load(parent.id).then(user => user.name),
Internally in my server my entities are handled using the database's native fields where possible, so the entity's type is keyed with "dgraph.type". My graphql api does not need to know that the database is dgraph, but I don't want to have to change the field name on every resolver. Is it possible to create a Scalar or some other process so that I can send
{
"dgraph.type": "User",
uid: "0x01",
username: "JimNaysium",
}
and have the client receive
{
type: "User",
uid: "0x01",
username: "JimNaysium",
}
If you've found your way to this benighted question, the answer is: There is no built in way to do this. Apollo server's plugins all trigger too early or too late to help and the schema cannot rename properties. I solved this using the following code:
type KeyedList<T = any> = {[key: string]: any};
const getConvertTypesResolver = (
resolver: (parent: any, args: any, context: any, info: any) => Promise<any>
): any => {
return async (parent: any, args: any, context: any, info: any): Promise<any> => {
const result = await resolver(parent, args, context, info);
// Check for serializable data now so the processor does not choke on
// circular references later.
try {
JSON.stringify(result);
} catch (e) {
throw new Error("Resolver results must be serializable.");
}
const nestedKeysArrays: string[][] = [Object.keys(result)];
const path: KeyedList[] = [result];
// Iterate over every nested object in the result body.
while (nestedKeysArrays.length) {
let done = true;
// Iterate over every key of every object.
while (nestedKeysArrays[0].length) {
// Progressively destroy the keys arrays to prevent rework.
const key = nestedKeysArrays[0].shift();
if (!key) {
continue;
}
const current = path[0][key];
// If the current key is an object add it to the beginning of the
// lists and begin processing it now.
if (current && typeof current === "object") {
nestedKeysArrays.unshift(Object.keys(current));
path.unshift(current);
done = false;
break;
}
// Change the "dgraph.type" key to "type". This is where the real
// work is done. Everything else is just navigation.
if (key === "dgraph.type") {
path[0].type = path[0]["dgraph.type"];
delete path[0]["dgraph.type"];
}
}
if (done) {
// Remove the array of keys from the list of keys to be processed.
nestedKeysArrays.shift();
// Return to the previous object.
path.shift();
}
}
return result;
};
};
The function middlewares a resolver. I'm calling it before constructing ApolloServer on every resolver in the project, and using it's result in lieu of the original resolver.
The issue can be more succinctly solved if you don't avoid recursion, but then you would be using recursion. Good luck in your future endeavors.
I have an Apollo GraphQL mutation. This mutation accepts two variables.
I would like to read these variables once the mutation is fired (not as response from the backed).
I need to know these variables because I want to use them inside the onCompleted function.
My code:
const [tripleEntity] = useMutation(TRIPLE_ENTITY, {
context: { clientName: 'redzor' },
refetchQueries: ['listProjects']
})
What I would like:
const [tripleEntity] = useMutation(TRIPLE_ENTITY, {
context: { clientName: 'redzor' },
refetchQueries: ['listsEntities'],
onCompleted(response, variables): {
console.log(variables) // Where "variables" are the input necessary to fire this mutation
myFunction(variables) // This is the function that needs the input variables
}
})
onComplete is only passed the mutation result as a parameter -- nothing else. However, mutate returns a Promise, so using onCompleted is not necessary.
const [mutate] = useMutation(YOUR_MUTATION, {...})
const doSomething = async (options) => {
const result = await mutate(options)
yourFunction(options.variables)
}
I'm trying to mock only a small portion of the Contact type below. My resolvers return data from a REST endpoint for all fields in Contact except for test. For demo purposes, I want to be able to retain the server data for all other fields, but only mock the test field.
I have the following GraphQL schema defined:
const typeDefs = `
type Contact {
id: String,
first_name: String
last_name: String
middle_name: String
date_of_birth: String
test: String
}
type Query {
contacts: [Contact!]!
Contact(id: String!): Contact!
}
`;
I have the following mocks defined:
const mocks = {
Contact: () => ({
test: () => "This data is mocked!"
})
};
And the following resolvers defined:
const resolvers = {
Query: {
contacts: async (parent, args, { dataSources }) =>
dataSources.MYAPI.getAllContacts(),
Contact: async (parent, { id }, { dataSources }) =>
dataSources.MYAPI.getContact(id)
}
};
Then I initialize the server with:
const server = new ApolloServer({
typeDefs,
resolvers,
mocks,
dataSources: () => {
return {
MYAPI: new MYAPI()
};
},
mockEntireSchema: false
});
The above does not work. I added the mockEntireSchema:true configuration which prevented my server response from being overridden, but the test attribute still returns the default String mock of Hello World instead of my attempted mock of This data is mocked!.
I know the mock is set up correctly because I can remove the mockEntireSchema config and my mock data appears correctly.
Is this even possible or does the behavior of mockEntireSchema and mocks in general not support this?
According to the documentation, you want to keep mockEntireSchema as false and create a mocks object that has the components in it that you still WANT to mock. All other resolvers will be used as they exist. Any mocks that you HAVE defined, though, will be used, so the query resolvers that return Contact types will never be used, since you have defined Contact as a mock.
I now believe that this is actually a bug in Apollo https://github.com/apollographql/graphql-tools/issues/1114. When you use preserveResolvers: true or mockEntireSchema: false (they are the same), it will not overwrite existing resolvers, however, I filtered those resolvers out based on my mock configuration, so they are not loaded in the first place.
This makes the partial mocking work in principle. The bug however is that nested () => MockList(100) calls throw an error in the graphql package because graphql interprets the MockList object as "not an iterable". This doesn't happen with preserveResolvers: false
I haven't tried it but to me it seems like it should work (what you describe). But since it doesn't, a possible workaround would be to just add a field resolver for the test field:
const resolvers = {
Contact: {
test: () => "This data is not mocked but the effect is the same!"
},
Query: {
contacts: async (parent, args, { dataSources }) =>
dataSources.MYAPI.getAllContacts(),
Contact: async (parent, { id }, { dataSources }) =>
dataSources.MYAPI.getContact(id)
}
};
I'm new to apollo/graphql and I'm trying to get my authentication done properly in a greenfield project. My authentication provider is AWS cognito. I wrote a cognito helper module to interact with it.
Though I'm not quite sure how to sync my apollo client with my auth state.
export const authenticate = (username: string, password: string) => {
const authDetails = new AuthenticationDetails({
Username: username,
Password: password,
})
const cognitoUser = getCognitoUser(username)
return new Promise((resolve, reject) => {
cognitoUser.authenticateUser(authDetails, {
onSuccess: result => {
resolve(result)
},
onFailure: err => {
reject(err)
},
})
})
}
export const getCurrentUserToken = () => {
return new Promise((resolve, reject) => {
const currentUser = userPool.getCurrentUser()
if (currentUser) {
currentUser.getSession((error, session) => {
if (error) {
reject(error)
}
resolve(session.getIdToken().getJwtToken())
})
} else {
resolve(null)
}
})
}
export const logout = () => {
const currentUser = userPool.getCurrentUser()
if (currentUser) {
currentUser.signOut()
}
}
Right now I'm just using these function to handle my login by calling them in my react component handlers. I configured an apollo-link for adding the auth header. Inject my JWT token data into context at the backend and implemented a currentUser query resolver in the backend.
const resolvers = {
RootQuery: {
currentUser: (obj, args, context) =>
context.tokenData
? {
id: context.tokenData.sub,
name: context.tokenData.name,
email: context.tokenData.email,
username: context.tokenData['cognito:username'],
}
: null,
},
}
In my react App layout i got a component UserPanel which queries that currentUser query.
const CURRENT_USER_QUERY = gql`
query {
currentUser {
name
}
}
`
export default graphql(CURRENT_USER_QUERY)(UserPanel)
When i am logging in now obviously the UserPanel does not update its currentUser query except I'm reloading the page ofc. Though im also having troubles finding a good solution to sync them.
I was thinking about implementing my login via graphql mutation using apollo-link-state to do it locally and watch these to refetch if someone logged in/out. I'm not sure if this is fine since it seems to me that this link cannot resolve async stuff (e.g. promises) in its mutation resolvers.
Another option I was thinking about was to decouple the auth process from the apollo client completely and implement some auth pubsub system maybe with Observables and let the react components refetch the queries if the authentication state changes.
I'm very uncertain how to continue and every solution I'm thinking about doesn't feel like the recommended way to go.
I don't have the full picture with regards to your React setup but here I go. It might be that Apollo-client is caching CURRENT_USER_QUERY locally and is showing you the results of a previous query. You could try the network-only option on the query:
export default graphql(CURRENT_USER_QUERY, { options: {fetchPolicy: 'network-only' }})(UserPanel)
What I have in React is an AppContainer which is my parent component. It checks if the user is logged in:
const loggedInUser = gql`
query loggedInUser{
user {
id
role
}
}`
export default graphql(loggedInUser, { options: {fetchPolicy: 'network-only' }})(AppContainer)
Then on my UserProfile page, I use a data container to fetch the data before passing it down to the UserProfile child component. I think the loggedInUser query automatically updates the user in the apollo store. With it apollo-client realizes that it needs to refetch userQuery. Does that help?
const userQuery = gql`
query userQuery {
user {
id
name
email
role
company
}
}
`
export default graphql(userQuery, {name: 'userQuery'})(UserDataContainer);