I've got a subscription query, MessageFolder_Subscription, that looks like this:
QUERY
const MESSAGEFOLDER_SUBSCRIPTION_QUERY = gql`
subscription ($localUserID: String!){
MessageFolder_Subscription(userID: $localUserID){
id
remoteUserData{
id
name_title
name_first
name_last
[...more fields...]
}
},
}
`;
Here's the schema for it:
SCHEMA
type myUserData {
id: String
gender: String
name_title: String
name_first: String
*[...more fields...]*
}
type messageFolder{
id: String
remoteUserData: myUserData
}
type Subscription {
MessageFolder_Subscription(userID: String!): messageFolder
}
Here's how I'm doing the resolvers:
RESOLVERS
const resolvers = {
//FIELD RESOLVER
MessageFolder_Subscription: {
subscribe: withFilter(
() => pubsub.asyncIterator(MSGFOLDER_ADDED_CHANNEL),
(payload, args) => {
debugger; <=== NEVER FIRES
if (typeof (payload) === 'undefined') {
return false;
}
let result = false;
const userId = Meteor.userId();
// let messageFolder = MessageFolder_Subscription.messageFolder;
result = (userId === args.fromID || args === MSGFOLDERargs.toID);
return result;
}
)
},
//ROOT RESOLVER
*[......more resolvers here.....]*
Subscription: {
MessageFolder_Subscription: {
subscribe: withFilter(
() => pubsub.asyncIterator(MSGFOLDER_ADDED_CHANNEL),
(payload, args) => {
debugger;
if (typeof (payload) === 'undefined') {
return false;
}
let result = false;
const userId = Meteor.userId();
// let messageFolder = MessageFolder_Subscription.messageFolder;
result = (userId === args.fromID || args === MSGFOLDERargs.toID);
return result;
}
)
}
}
When I mutate a related item, the MessageFolder_Subscription query is fired by pubsub as expected. Tracing through, I can see that it returns true.
But for some reason, the field resolver, for the field remoteUserData on MessageFolder_Subscription, never fires.
What am I missing?
Solved. I had to add the __typename: field:
const messageFolder_Subscription = {
__typename: 'messageFolder_Subscription',
id: userID,
}
...to the MessageFolder_Subscription subscription object, when it was created in the mutation resolver, prior to being passed to pubsub.
Related
I am getting this error in graphql playground (image below ) .
I have checked for the validity of objectId in the resolver as well.
// model
const ProposalSchema = new Schema({
cover
Letter: {
type: String,
},
budget: {
type: String,
},
proposals: {
type: mongoose.Schema.Types.ObjectId,
},
_id: {
type: mongoose.Schema.Types.ObjectId,
},
});
//resolver
also checked If the argument is valid using mongoose.isValidObjectId(proposser) it returns true
Query: {
proposals(_, args) {
const { proposser } = args;
return Proposal.findById({
proposser,
});
},
},
// schema
const typeDefs = gql`
type Proposal {
_id: ID!
coverLetter: String
budget: String
proposser: ID!
}
`;
const Proposal = mongoose.model("Proposal", ProposalSchema);
I was using wrong method in resolvers .
findById was being used for field non Id field.
async proposals(_, args) {
const { proposser } = args;
const userProposals = await Proposal.find({
proposser,
});
try {
const result = userProposals;
return result ? result : [];
} catch (err) {
console.log(err);
}
},
Is it possible to add logic in resolvers using GraphQL mutations?
I am trying to create a four-digit string as an alias for a post if the user does not provide it. Then, I would like to check the database to see if the four-digit string exists. If the string exists, I would like to create another four-digit string recursively.
At the moment, I'm exploring adding logic to mutations within resolvers, but I'm not sure if this is doable. I'm using these documents for my foundation: graphql.org sequelize.org
This is my current code block:
Working as of 12/4/2020
const MakeSlug = require("./services/MakeSlug");
const resolvers = {
Query: {
async allLinks(root, args, { models }) {
return models.Link.findAll();
},
async link(root, { id }, { models }) {
return models.Link.findByPk(id);
}
},
Mutation: {
async createLink(root, { slug, description, link }, { models }) {
if (slug !== undefined) {
const foundSlug = await models.Link.findOne({
where: { slug: slug }
});
if (foundSlug === undefined) {
return await models.Link.create({
slug,
description,
link,
shortLink: `https://shink.com/${slug}`
});
} else {
throw new Error(slug + " exists. Try a new short description.");
}
}
if (slug === undefined) {
const MAX_ATTEMPTS = 10;
let attempts = 0;
while (attempts < MAX_ATTEMPTS) {
attempts++;
let madeSlug = MakeSlug(4);
const foundSlug = await models.Link.findOne({
where: { slug: madeSlug }
});
if (foundSlug !== undefined) {
return await models.Link.create({
slug: madeSlug,
description,
link,
shortLink: `https://shink.com/${madeSlug}`
});
}
}
throw new Error("Unable to generate unique alias.");
}
}
}
};
module.exports = resolvers;
This is my full codebase.
Thank you!
A while loop solved the challenge. Thanks xadm.
const MakeSlug = require("./services/MakeSlug");
const resolvers = {
Query: {
async allLinks(root, args, { models }) {
return models.Link.findAll();
},
async link(root, { id }, { models }) {
return models.Link.findByPk(id);
}
},
Mutation: {
async createLink(root, { slug, description, link }, { models }) {
if (slug !== undefined) {
const foundSlug = await models.Link.findOne({
where: { slug: slug }
});
if (foundSlug === undefined) {
return await models.Link.create({
slug,
description,
link,
shortLink: `https://shink.com/${slug}`
});
} else {
throw new Error(slug + " exists. Try a new short description.");
}
}
if (slug === undefined) {
const MAX_ATTEMPTS = 10;
let attempts = 0;
while (attempts < MAX_ATTEMPTS) {
attempts++;
let madeSlug = MakeSlug(4);
const foundSlug = await models.Link.findOne({
where: { slug: madeSlug }
});
if (foundSlug !== undefined) {
return await models.Link.create({
slug: madeSlug,
description,
link,
shortLink: `https://shink.com/${madeSlug}`
});
}
}
throw new Error("Unable to generate unique alias.");
}
}
}
};
module.exports = resolvers;
I want to access the country field from my resolver. The country is being returned by query but since Product is a list I can only access the object inside items return by query. Is there any way I can have access to whole returned data from query or any way to pass it further down as an argument to my resolver function
//schema
type ProductCollectionPage {
items: [Product!]!
}
//resolver
const resolvers = {
Product: {
variants: async (obj: any, args: any, { dataSources }: any): Promise<IProductVariantPage> => {
const { id } = obj;
// want to access country here
return (dataSources.xyz as XyzRepository).retriveProducts(country, id);
}
},
Query: {
products: async (
obj: any,
{ id }: { id: string },
{ dataSources }: any
): Promise<
any
> => {
const locationDetails = await (dataSources.abc as InventoryLocationsRepository).retrieveInventoryLocation(id);
const country = locationDetails.country;
const response = await (dataSources.abc as XyzRepository).retriveProductIds(country);
// response.list === [{id: 1}, {id:2}]
return {
country,
items: response.list
}
}
}
};
As arrays are objects in javascript then you can just assign additional property to response.list:
response.list.country = country;
A request to Apollo Server with default settings works directly, but not when wrapped in "viewer". Does "viewer" need a custom resolver and how does this resolver know to return from "nextUp" resolver?
query {
nextUp(limit: $limit, eventTypes: $eventTypes) {
eventKey
eventType
distanceMetres
eventDateTime
status
__typename
}
}
above returns valid data structure for nextUp.
below returns null for nextUp.
query {
viewer { # <----------- viewer wrapping nextUp
nextUp(limit: $limit, eventTypes: $eventTypes) {
eventKey
eventType
distanceMetres
eventDateTime
status
__typename
}
}
}
typeDefs:
const typeDefs = gql`
type Event {
eventKey: String
eventType: String
distanceMetres: String
eventDateTime: DateTime!
status: String
}
type ViewerModel {
nextUp(
eventTypes: [String] = null
limit: Int = 0
): [Event]
}
type Query {
viewer: ViewerModel
nextUp(
eventTypes: [String] = null
limit: Int = 0
): [Event]
}
`;
and resolvers:
const nextUp= async (_source: any, _args: IGetNextUpAsync, { dataSources }: any) => {
return dataSources.RestDataSource.getNextUpAsync(_args)
};
const resolvers = {
Query: {
viewer: async (_source: any, _args: any, { dataSources }: any, info: any) => {
return nextUp; // ???
},
nextToGo,
}
};
I have written integration tests for graphql-js subscriptions, which are showing weird behavior.
My graphq-js subscription works perfectly in GraphiQL. But when the same subscriptions is called from unit test, it fails.
Ggraphql-Js object, with resolve function and subscribe function
return {
type: outputType,
args: {
input: {type: new GraphQLNonNull(inputType)},
},
resolve(payload, args, context, info) {
const clientSubscriptionId = (payload) ? payload.subscriptionId : null;
const object = (payload) ? payload.object : null;
var where = null;
var type = null;
var target = null;
if (object) {
where = (payload) ? payload.object.where : null;
type = (payload) ? payload.object.type : null;
target = (payload) ? payload.object.target : null;
}
return Promise.resolve(subscribeAndGetPayload(payload, args, context, info))
.then(payload => ({
clientSubscriptionId, where, type, target, object: payload.data,
}));
},
subscribe: withFilter(
() => pubSub.asyncIterator(modelName),
(payload, variables, context, info) => {
const subscriptionPayload = {
clientSubscriptionId: variables.input.clientSubscriptionId,
remove: variables.input.remove,
create: variables.input.create,
update: variables.input.update,
opts: variables.input.options,
};
subscriptionPayload.model = model;
try {
pubSub.subscribe(info.fieldName, null, subscriptionPayload);
} catch (ex) {
console.log(ex);
}
return true;
}
),
};
Subscription query
subscription {
Customer(input: {create: true, clientSubscriptionId: 112}) {
customer {
id
name
age
}
}
}
Mutation query
mutation {
Customer {
CustomerCreate (input:{data:{name:"Atif 50", age:50}}) {
obj {
id
name
}
}
}
}
Integration Test
'use strict';
const ws = require('ws');
const { SubscriptionClient } = require('subscriptions-transport-ws');
const { ApolloClient } = require('apollo-client');
const { HttpLink } = require('apollo-link-http');
const { InMemoryCache } = require('apollo-cache-inmemory');
const Promise = require('bluebird');
const expect = require('chai').expect;
const chai = require('chai').use(require('chai-http'));
const server = require('../server/server');
const gql = require('graphql-tag');
let apollo;
let networkInterface;
const GRAPHQL_ENDPOINT = 'ws://localhost:5000/subscriptions';
describe('Subscription', () => {
before(async () => {
networkInterface = new SubscriptionClient(
GRAPHQL_ENDPOINT, { reconnect: true }, ws);
apollo = new ApolloClient({
networkInterface ,
link: new HttpLink({ uri: 'http://localhost:3000/graphql' }),
cache: new InMemoryCache()
});
});
after(done => {
networkInterface.close() ;
});
it('subscription', async () => {
const client = () => apollo;
// SUBSCRIBE and make a promise
const subscriptionPromise = new Promise((resolve, reject) => {
client().subscribe({
query: gql`
subscription {
Customer(input: {create: true,
clientSubscriptionId: 112,
options: {where: {age: 50}}}) {
customer {
name
}
}
}
`
}).subscribe({
next: resolve,
error: reject
});
});
let execGraphQL;
// MUTATE
await execGraphQL(
`mutation {
Customer {
CustomerCreate (input:{data:{name:"Atif 21", age:50}}) {
obj {
id
name
}
}
}
}`
);
// ASSERT SUBSCRIPTION RECEIVED EVENT
expect(await subscriptionPromise).to.deep.equal({});
});
});
Issue Here
When test in run, payload in the resolve function contains global data, where as it should contain the subscription payload. So the code breaks.