How to catch and modify apollo response globally? - graphql

Is there a way to catch and modify response globally on the fly? I can do this for one query like below, but I want to do it for all queries.
apollo: {
post: {
query: Post,
update(data) {
return data.map(item => Object.assign(item, {foo: 'bar'})
}
}
}
It's simplified for this question, but under the hood I'd like to apply a constructor (class) to all objects...
I'm using nuxt-apollo. I searched for a way to do that in clientConfig or elsewhere so the solution may be related to apollo...
Thanks for your advice!
edit:
OK, I found to do that with apollo-link, but I can't modify response. Here the code:
const constructorMiddleware = new ApolloLink((operation, forward) => {
return forward(operation).map(response => {
Object.keys(response.data).map(key => {
if (!Array.isArray(response.data[key])) return;
const newResponse = response.data[key].map(item => {
return item.__typename === 'post'
? Object.assign(item, { foo: 'bar' })
: item
})
console.log(newResponse)
response.data[key] = newResponse
})
return response
})
})
I can see foo: bar in the newResponse, but the graphql returning by nuxt-apollo doesn't contains this newResponse, only original.
Do ApolloLink override response? Does apollo cache change this?
edit 2:
I tried to chain links and the newResponse of the constructorMiddleware is well in the next link. So the problem seems come from nuxt-apollo, or more vue-apollo...

Related

error Policy in Apollo Client React does'nt work

I have aproblem when test Apollo.When I try query with apollo and graphql, i want response return error and partical data, so I set property errorPolicy:'all'. But its not work. I don't no why? Help please!
Here my code:
query { animal {
name
age }, school {
name
numberfd } } `
const { loading,data,error} = useQuery(GET_DASHBOARD_DATA, {
errorPolicy:'all',
onCompleted: (res) => {console.log("complete",res)},
onError : (res,data) => {console.log("ERRRR",res,data)},
})
and i want to receive:
{
error:[...], data:[animal:[...]] }
but its only response error.Here is Apollo's doc: https://www.apollographql.com/docs/react/data/error-handling/
onError type is onError?: (error: ApolloError) => void;. You don't have data inside onError callback.
After useQuery you can add:
console.log('data', data)
console.log('error', error)
I faced the same issue with errorPolicy: 'all', I only received the partial result inside onCompleted callback of useQuery, but no errors.
I created an ErrorLink like this:
private createErrorLink = () => {
return new ApolloLink((operation, forward) => {
return forward(operation).map((response) => {
// filter out errors you don't want to display
const errors = filterSomeErrors(response.errors);
if (errors && response?.data) {
response.data.errors = errors;
}
return response;
});
});
};
Now inside my onCompleted callback I get my data as well as errors. You will have to tweak your types a bit, because seems there is no errors field on response.data by default.
Mind that if you use onError from Apollo and return something from the link, it will retry your request containing errors!

`RxJS` throws error on subcribe when do request

I am making a request using observable. and trying to subcribe the value. But getting error on typescript. any on help me?
I like to do this:
public getCountry(lat,lan):Observable<any>{
return this.http.get(this.googleApi+lat+','+lan+'&sensor=false').subscribe(data => {
return this.genertedData(data);
} );
}
But getting error as follows:
UPDATES
public getCountry(lat,lan):Observable<any>{
return this.http.get(this.googleApi+lat+','+lan+'&sensor=false').map( data => {
data.results.map( array => {
let details = array.address_components.find( obj => obj.types.includes("country") );
this.countryDetails.countryLongName = details.long_name;
this.countryDetails.countryShortName = details.short_name;
})
return this.countryDetails;
})
}
The problem is that your return type states Observable<any>, where as you actually return whatever this.genertedData(data) returns (Hint: Sounds like a typo in your function. Guess it should be called generatedData ?).
Best practice would be to move the http call into a service and subscribe to its returned Observable within your component.
So to speak:
// => service.ts
public getCountryObservable(lat,lan):Observable<any> {
return this.http.get(this.googleApi+lat+','+lan+'&sensor=false');
}
Your component would look something like:
// => component.ts
export class YourComponent {
constructor(public yourService: YourService) {}
getCountry(lat, lan): whateverDataTypeYourGeneratedDataReturns {
this.yourService.getCountryObservable(lat, lan).subscribe((data) => {
this.data = this.generatedData(data);
});
}
}
Since the return type of the function is Observable<any>, I guess it should just return this.http.get(this.googleApi+lat+','+lan+'&sensor=false')

How to ignore url querystring from cached urls when using workbox?

Is there a way to ignore query string "?screenSize=" from below registered route using workbox! If I can use regex how would i write it in below scenario? Basically, I am looking to match the cache no matter what is the screenSize querystring.
workboxSW.router.registerRoute('https://example.com/data/image?screenSize=980',
workboxSW.strategies.cacheFirst({
cacheName: 'mycache',
cacheExpiration: {
maxEntries: 50
},
cacheableResponse: {statuses: [0, 200]}
})
);
After trying the cachedResponseWillBeUsed plugin:
I do not see the plugin is applied:
Update: As of Workbox v4.2.0, the new cacheKeyWillBeUsed lifecycle callback can help override the default cache key for both read and write operations: https://github.com/GoogleChrome/workbox/releases/tag/v4.2.0
Original response:
You should be able to do this by writing a cachedResponseWillBeUsed plugin that you pass in when you configure the strategy:
// See https://workboxjs.org/reference-docs/latest/module-workbox-runtime-caching.RequestWrapper.html#.cachedResponseWillBeUsed
const cachedResponseWillBeUsed = ({cache, request, cachedResponse}) => {
// If there's already a match against the request URL, return it.
if (cachedResponse) {
return cachedResponse;
}
// Otherwise, return a match for a specific URL:
const urlToMatch = 'https://example.com/data/generic/image.jpg';
return caches.match(urlToMatch);
};
const imageCachingStrategy = workboxSW.strategies.cacheFirst({
cacheName: 'mycache',
cacheExpiration: {
maxEntries: 50
},
cacheableResponse: {statuses: [0, 200]},
plugins: [{cachedResponseWillBeUsed}]
});
workboxSW.router.registerRoute(
new RegExp('^https://example\.com/data/'),
imageCachingStrategy
);
To build on the other answer, caches.match has an option ignoreSearch, so we can simply try again with the same url:
cachedResponseWillBeUsed = ({cache, request, cachedResponse}) => {
if (cachedResponse) {
return cachedResponse;
}
// this will match same url/diff query string where the original failed
return caches.match(request.url, { ignoreSearch: true });
};
As of v5, building on aw04's answer, the code should read as follows:
const ignoreQueryStringPlugin = {
cachedResponseWillBeUsed: async({cacheName, request, matchOptions, cachedResponse, event}) => {
console.log(request.url);
if (cachedResponse) {
return cachedResponse;
}
// this will match same url/diff query string where the original failed
return caches.match(request.url, {ignoreSearch: true});
}
};
registerRoute(
new RegExp('...'),
new NetworkFirst({
cacheName: 'cache',
plugins: [
ignoreQueryStringPlugin
],
})
);
You can use the cacheKeyWillBeUsed simply, modifying the saved cache key to ignore the query at all, and matching for every response to the url with any query.
const ignoreQueryStringPlugin = {
cacheKeyWillBeUsed: async ({request, mode, params, event, state}) => {
//here you can extract the fix part of the url you want to cache without the query
curl = new URL(request.url);
return curl.pathname;
}
};
and add it to the strategy
workbox.routing.registerRoute(/\/(\?.+)?/,new
workbox.strategies.StaleWhileRevalidate({
matchOptions: {
ignoreSearch: true,
},
plugins: [
ignoreQueryStringPlugin
],
}));
ignoreURLParametersMatching parameter worked for me:
https://developers.google.com/web/tools/workbox/modules/workbox-precaching#ignore_url_parameters

Using graphql-tools to mock a GraphQL server seems broken

I've followed the documentation about using graphql-tools to mock a GraphQL server, however this throws an error for custom types, such as:
Expected a value of type "JSON" but received: [object Object]
The graphql-tools documentation about mocking explicitly states that they support custom types, and even provide an example of using the GraphQLJSON custom type from the graphql-type-json project.
I've provided a demo of a solution on github which uses graphql-tools to successfully mock a GraphQL server, but this relies on monkey-patching the built schema:
// Here we Monkey-patch the schema, as otherwise it will fall back
// to the default serialize which simply returns null.
schema._typeMap.JSON._scalarConfig.serialize = () => {
return { result: 'mocking JSON monkey-patched' }
}
schema._typeMap.MyCustomScalar._scalarConfig.serialize = () => {
return mocks.MyCustomScalar()
}
Possibly I'm doing something wrong in my demo, but without the monkey-patched code above I get the error regarding custom types mentioned above.
Does anyone have a better solution than my demo, or any clues as to what I might be doing wrong, and how I can change the code so that the demo works without monkey-patching the schema?
The relevant code in the demo index.js is as follows:
/*
** As per:
** http://dev.apollodata.com/tools/graphql-tools/mocking.html
** Note that there are references on the web to graphql-tools.mockServer,
** but these seem to be out of date.
*/
const { graphql, GraphQLScalarType } = require('graphql');
const { makeExecutableSchema, addMockFunctionsToSchema } = require('graphql-tools');
const GraphQLJSON = require('graphql-type-json');
const myCustomScalarType = new GraphQLScalarType({
name: 'MyCustomScalar',
description: 'Description of my custom scalar type',
serialize(value) {
let result;
// Implement your own behavior here by setting the 'result' variable
result = value || "I am the results of myCustomScalarType.serialize";
return result;
},
parseValue(value) {
let result;
// Implement your own behavior here by setting the 'result' variable
result = value || "I am the results of myCustomScalarType.parseValue";
return result;
},
parseLiteral(ast) {
switch (ast.kind) {
// Implement your own behavior here by returning what suits your needs
// depending on ast.kind
}
}
});
const schemaString = `
scalar MyCustomScalar
scalar JSON
type Foo {
aField: MyCustomScalar
bField: JSON
cField: String
}
type Query {
foo: Foo
}
`;
const resolverFunctions = {
Query: {
foo: {
aField: () => {
return 'I am the result of resolverFunctions.Query.foo.aField'
},
bField: () => ({ result: 'of resolverFunctions.Query.foo.bField' }),
cField: () => {
return 'I am the result of resolverFunctions.Query.foo.cField'
}
},
},
};
const mocks = {
Foo: () => ({
// aField: () => mocks.MyCustomScalar(),
// bField: () => ({ result: 'of mocks.foo.bField' }),
cField: () => {
return 'I am the result of mocks.foo.cField'
}
}),
cField: () => {
return 'mocking cField'
},
MyCustomScalar: () => {
return 'mocking MyCustomScalar'
},
JSON: () => {
return { result: 'mocking JSON'}
}
}
const query = `
{
foo {
aField
bField
cField
}
}
`;
const schema = makeExecutableSchema({
typeDefs: schemaString,
resolvers: resolverFunctions
})
addMockFunctionsToSchema({
schema,
mocks
});
// Here we Monkey-patch the schema, as otherwise it will fall back
// to the default serialize which simply returns null.
schema._typeMap.JSON._scalarConfig.serialize = () => {
return { result: 'mocking JSON monkey-patched' }
}
schema._typeMap.MyCustomScalar._scalarConfig.serialize = () => {
return mocks.MyCustomScalar()
}
graphql(schema, query).then((result) => console.log('Got result', JSON.stringify(result, null, 4)));
I and a few others are seeing a similar issue with live data sources (in my case MongoDB/Mongoose). I suspect it is something internal to the graphql-tools makeExecutableSchema and the way it ingests text-based schemas with custom types.
Here's another post on the issue: How to use graphql-type-json package with GraphQl
I haven't tried the suggestion to build the schema in code, so can't confirm whether it works or not.
My current workaround is to stringify the JSON fields (in the connector) when serving them to the client (and parsing on the client side) and vice-versa. A little clunky but I'm not really using GraphQL to query and/or selectively extract the properties within the JSON object. This wouldn't be optimal for large JSON objects I suspect.
If anyone else comes here from Google results, the solution for me was to add the JSON resolver as parameter to the makeExecutableSchema call. It's described here:
https://github.com/apollographql/apollo-test-utils/issues/28#issuecomment-377794825
That made the mocking work for me.

fieldASTs on GraphQL resolve

I have been going crazy over this for GraphQL. I have seen a lot of resources referring to this last fieldASTs param for the selectionSet. However, it doesn't seem to be there. I haven't found any solid evidence that it is, but it has been brought up in github issues and tutorials. I am a little confused by this. Is there or is there not a 4th param?
I have also tested the other params to see if I can pull it off of those.
const SomeType = new GraphQLObjectType({
name: 'SomeObject',
fields: () => ({
someItems : {
type: new GraphQLList(SomeCustomType),
resolve: (someItems, params, source, fieldASTs) => {
const projections = getProjection(fieldASTs);
return SomeModel.find({}, projections);
}
}
});
with current version (0.7.0), now it is in the forth argument, third argument is for context.
the following observation from this blog post may help.
http://pcarion.com/2015/09/27/GraphQLResolveInfo/
Welp, I found it. I dove through the changelogs and it was changed to be part of the third param. However, it isn't structured the same way
resolve: (item, params, info, fieldASTs) => {
//used to be
fieldASTs.selectionMap.selection.reduce(someLogic);
//now its simply
fieldASTs.reduce(someLogic);
}
I'm using graphql#0.4.14, and found it here:
info //third argument
.fieldASTs[0]
.selectionSet
.selections
//.reduce...
I guess they are still changing everything, so I've added try/catch to getProjection()
Yes the fourth param comprises of fieldASTs but its hooked under the object as an array
const SomeType = new GraphQLObjectType({
name: 'SomeObject',
fields: () => ({
someItems : {
type: new GraphQLList(SomeCustomType),
resolve: (someItems, params, source, options) => {
const projections = getProjection(options.fieldASTs[0]);
return SomeModel.find({}, projections);
}
}
});
This solved for me.
In express-graphql being graphql v0.8.1 I had to do it like this (note the use of mongoose too):
function getProjection (fieldASTs) {
return fieldASTs.fieldNodes[0].selectionSet.selections.reduce((projections, selection) => {
projections[selection.name.value] = 1;
return projections;
}, {});
}
resolve (root, params, info, fieldASTs) {
let filter = {};
if(params._id){
filter._id = params._id;
}
var projections = getProjection(fieldASTs);
return grocerModel.find(filter).select(projections).exec();
}
I am new to graphql, but this resolved it for me and I think something may have changed in the versions as fieldASTs did not exist on the options (4th param) object, but fieldNodes does that has the necessary child objects to perform the projection.
export default {
type: new GraphQLList(teamType),
args: {
name: {
type: GraphQLString
}
},
resolve(someItems, params, source, options) {
const projection = getProjection(options.fieldNodes[0])
return TeamModel
.find()
.select(projection)
.exec()
}
}
There are 2 competing Open Source libraries to achieve what topic starter was asking for:
graphql-fields-list
graphql-list-fields
They both try to solve the same problem, but the former seem to have some more features, including TypeScript support out of the box.

Resources