Read query from apollo cache with a query that doesn't exist yet, but has all info stored in the cache already - vue.js

I have a graphql endpoint where this query can be entered:
fragment ChildParts {
id
__typename
}
fragment ParentParts {
__typename
id
children {
edges{
node {
...ChildParts
}
}
}
query {
parents {
edges
nodes {
...ParentParts
}
}
}
}
When executed, it returns something like this:
"data": {
"edges": [
"node": {
"id": "<some id for parent>",
"__typename": "ParentNode",
"children": {
"edges": [
node: {
"id": "<some id for child>",
"__typename": "ChildNode"
},
...
]
}
},
...
]
}
Now, with apollo client, after a mutation, I can read this query from the cache, and update / add / delete any ParentNode, and also any ChildNode, but I have to go over the structure returned by this query.
Now, I'm looking for a possibility to get a list of ChildNodes out of the cache (which has those already, as the cache is created as a flat list), to make the update of nested data a bit easier. Is there a possibility of reading a query out of the cache, without having read the same query from the server before?

You can use the client's readFragment method to retrieve any one individual item from the cache. This just requires the id and a fragment string.
const todo = client.readFragment({
id,
fragment: gql`
fragment fooFragment on Foo {
id
bar
qax
}
`,
})
Note that id here is the cache key returned by the dataIdFromObject function -- if you haven't specified a custom function, then (provided the __typename and id or _id fields are present) the default implementation is just:
${result.__typename}:${result.id || result._id}
If you provided your own dataIdFromObject function, you'll need to provide whatever id is returned by that function.
As #Herku pointed out, depending on the use case, it's also possible to use cache redirects to utilize data cached for one query when resolving another one. This is configured as part of setting up your InMemoryCache:
const cache = new InMemoryCache({
cacheRedirects: {
Query: {
book: (_, args, { getCacheKey }) =>
getCacheKey({ __typename: 'Book', id: args.id })
},
},
})
Unfortunately, as of writing this answer, I don't believe there's any method to delete a cached item by id. There's on going discussion here around that point (original issue here).

Related

Calling function in VueApollo after API response

I am using Vue and Apollo and I am making a querie that looks just like the box below.
After I get the API response, I would like to call a method from my methods object. However Vue, doesn't give me acess to it within apollo object.
I would like to know how can I call one of my methods, but only after I am sure I got that response, without having to manually trigger it with a button or something else.
apollo: {
materials: {
query: gql`
query allMaterials($tenantId: ID, $name: String) {
tenantMaterials(tenantId: $tenantId, name: $name) {
edges {
node {
name
materialType {
name
id
}
brand
vendor
size
unit
inventory
createdAt
updatedAt
isActive
updatedBy
id
}
}
totalCount
}
}
`,
variables() {
return {
name: null
};
},
fetchPolicy: "cache-and-network",
update: response => {
return response.tenantMaterials.edges;
//I want to call a function/method after this response
},
skip: false
},
}
Use update(data) or result(result, key)
update(data) {return ...} to customize the value that is set in the
vue property, for example if the field names don't match.
result(ApolloQueryResult, key) is a hook called when a result is
received (see documentation for ApolloQueryResult (opens new window)).
key is the query key in the apollo option.
https://apollo.vuejs.org/api/smart-query.html

GraphQL stitch and union

I have a need to 'aggregate' multiple graphQl services (with same schema) into single read-only (query only) service exposing data from all services. For example:
---- domain 1 ----
"posts": [
{
"title": "Domain 1 - First post",
"description": "Content of the first post"
},
{
"title": "Domain 1 - Second post",
"description": "Content of the second post"
}
]
---- domain 2 ----
"posts": [
{
"title": "Domain 2 - First post",
"description": "Content of the first post"
},
{
"title": "Domain 2 - Second post",
"description": "Content of the second post"
}
]
I understand that 'stitching' is not meant for UC's like this but more to combine different micro-services into same API. In order to have same types (names) into single API, I implemented 'poor man namespaces' by on-the-fly' appending domain name to all data types. However, I'm able only to make a query with two different types like this:
query {
domain_1_posts {
title
description
}
domain_2_posts {
title
description
}
}
but, it results with data set consist out of two arrays:
{
"data": {
"domain_1_posts": [
{ ...},
],
"domain_2_posts": [
{ ...},
]
}
}
I would like to hear your ideas what I can do to combine it into single dataset containing only posts?
One idea is to add own resolver that can call actual resolvers and combine results into single array (if that is supported at all).
Also, as a plan B, I could live with sending 'domain' param to query and then construct query toward first or second domain (but, to keep initial query 'domain-agnostic', e.g. without using domain namses in query itself?
Thanks in advance for all suggestions...
I manage to find solution for my use-case so, I'll leave it here in case that anyone bump into this thread...
As already mentioned, stitching should be used to compose single endpoint from multiple API segments (microservices). In case that you try to stitch schemas containing same types or queries, your request will be 'routed' to pre-selected instance (so, only one).
As #xadm suggested, key for 'merging' data from multiple schemas into singe data set is in using custom fetch logic for Link used for remote schema, as explained:
1) Define custom fetch function matching your business needs (simplified example):
const customFetch = async (uri, options) => {
// do not merge introspection query results!!!
// for introspection query always use predefined (first?) instance
if( operationType === 'IntrospectionQuery'){
return fetch(services[0].uri, options);
}
// array fecth calls to different endpoints
const calls = [
fetch(services[0].uri, options),
fetch(services[1].uri, options),
fetch(services[2].uri, options),
...
];
// execute calls in parallel
const data = await Promise.all(fetchCalls);
// do whatever you need to merge data according to your needs
const retData = customBusinessLogic();
// return new response containing merged data
return new fetch.Response(JSON.stringify(retData),{ "status" : 200 });
}
2) Define link using custom fetch function. If you are using identical schemas you don't need to create links to each instance, just one should be enough.
const httpLink = new HttpLink(services[0].uri, fetch: customFetch });
3) Use Link to create remote executable schema:
const schema = await introspectSchema(httpLink );
return makeRemoteExecutableSchema({
schema,
link: httpLink,
context: ({ req }) => {
// inject http request headers into context if you need them
return {
headers: {
...req.headers,
}
}
},
})
4) If you want to forward http headers all the way to the fetch function, use apollo ContextLink:
// link for forwarding headers through context
const contextLink = setContext( (request, previousContext) => {
if( previousContext.graphqlContext ){
return {
headers: {
...previousContext.graphqlContext.headers
}
}
}
}).concat(http);
Just to mention, dependencies used for this one:
const { introspectSchema, makeRemoteExecutableSchema, ApolloServer } = require('apollo-server');
const fetch = require('node-fetch');
const { setContext } = require('apollo-link-context');
const { HttpLink } = require('apollo-link-http');
I hope that it will be helfull to someone...

Is smart query custom variable name possible?

I'm using Vue alongside with Apollo in order to query a GraphQL endpoint in my project. Everything's fine but I want to start programming generic components to ease and fasten the development.
The thing is, in most views, I use the Smart Query system.
For instance, I use :
apollo: {
group: {
query: GROUP_QUERY,
variables () { return { id: this.groupId } },
skip () { return this.groupId === undefined },
result ({ data }) {
this.form.name = data.group.name
}
}
}
With the GROUP_QUERY that is :
const GROUP_QUERY = gql`
query groupQuery ($id: ID) {
group (id: $id) {
id
name
usersCount
userIds {
id
username
}
}
}
`
So my group variable in my apollo smart query has the same name as the query itself group (id: $id). It is this mechanism that is quite annoying for what I try to achieve. Is there a way to avoid that default mechanism ?
I'd like for instance to be able to give a generic name such as record, and it would be records for queries that potentially return multiple records.
With that, I would be able to make generic components or mixins that operate either on record or records.
Or have I to rename all my queries to record and records which would be annoying later on in case of troubleshooting with error messages ?
Or maybe there's another way to achieve that and I didn't think about it ?
Thanks in advance.
You can, in fact, rename the variable of Apollo smart queries using the update option, as seen here in the Vue Apollo documentation. Your example would look like:
apollo: {
record: {
query: GROUP_QUERY,
variables () { return { id: this.groupId } },
update: (data) => data.group,
skip () { return this.groupId === undefined },
result ({ data }) {
this.form.name = data.group.name
}
}
}
You should notice that the Apollo object will create a record variable in your component, and the update statement shows where to get the group for the record.
By doing so :
const GROUP_QUERY = gql`
query groupQuery ($id: ID) {
record: group (id: $id) {
id
name
usersCount
userIds {
id
username
}
}
}
`
If the GROUP_QUERY is used at several places, the result will be accessible under the record name, because it is defined as an alias over group.
See documentation for Aliases.

Add a new element in each array of objects where array may have different length in mongodb

I have a following shema.
{
id:week
output:{
headerValues:[
{startDate:"0707",headers:"ID|week"},
{startDate:"0715",headers:"ID1|week1"},
{startDate:"0722",headers:"ID2|week2"}
]
}
}
I have to add a new field into headerValues array like this:
{
id:week
output:{
headerValues[
{startDate:"0707",headers:"ID|week",types:"used"},
{startDate:"0715",headers:"ID1|week1",types:"used"},
{startDate:"0722",headers:"ID2|week2",types:"used"}
]
}
}
I tried different approaches like this:
1)
db.CollectionName.find({}).forEach(function(data){
for(var i=0;i<data.output.headerValues.length;i++) {
db.CollectionName.update({
"_id": data._id, "output.headerValues.startDate":data.output.headerValues[i].startDate
},
{
"$set": {
"output.headerValues.$.types":"used"
}
},true,true
);
}
})
So, In this approach it is executing script and then failing. It is updating result with failed statement.
2)
Another approach I have followed using this link:
https://jira.mongodb.org/browse/SERVER-1243
db.collectionName.update({"_id":"week"},
{ "$set": { "output.headerValues.$[].types":"used" }
})
But it fails with error:
cannot use the part (headerValues of output.headerValues.$[].types) to
traverse the element ({headerValues: [ { startDate: "0707", headers:
"Id|week" } ]}) WriteError#src/mongo/shell/bulk_api.js:469:48
Bulk/mergeBatchResults#src/mongo/shell/bulk_api.js:836:49
Bulk/executeBatch#src/mongo/shell/bulk_api.js:906:13
Bulk/this.execute#src/mongo/shell/bulk_api.js:1150:21
DBCollection.prototype.updateOne#src/mongo/shell/crud_api.js:550:17
#(shell):1:1
I have searched with many different ways which can update different arrays object by adding new field to each object but no success. Can anybody please suggest that what am I doing wrong?
Your query is {"_id" : "week"} but in your data id field is week
So you can change {"_id" : "week"} to {"id" : "week"} and also update your mongodb latest version
db.collectionName.update({"id":"week"},
{ "$set": { "output.headerValues.$[].types":"used" }
})

Graphql query only not null objects

im trying to perform a query like this:
{
people{
pet{
name
}
}
}
result:
{
"people": {
"pet": null
}
},
{
"people": {
"pet": {
name: "steve"
}
}
}
What i want is to get only people that contains a pet, is there any way to achieve this not coding on my resolver ?
Actually, it is possible with the filter: { pet: {ne: null} } filtering:
query allPeople(filter: { people: { pet: {ne: null} } }) {
people {
pet
}
}
This is not possible the way you describe. GraphQL will call resolve functions to fetch the data. If you don't want certain data in your response, you have to filter it somewhere on the server. The only thing you have control over is the query, the schema and the resolve functions.
There is no way to express your requirement purely in the query. If you put it in the schema, you would no longer be able to query for people without pets. So the only way to do it is to write it in your resolve function. You could for example add a boolean argument called hasPet to your people field, and do this in the resolver:
people(root, { hasPet }){
// get allPeople
if (typeof hasPet === 'undefined'){
return allPeople
}
return allPeople.filter((person) => person.hasPet() === hasPet)
}
The unfortunate thing is that this will require you to 'look ahead' and figure out if a person has a pet in the first place, but if you cache backend or DB requests with something like DataLoader, this isn't actually costly, because you would have to fetch the pet anyway. This way you just fetch it a bit earlier.
If you're fetching your people from a database, it would of course make sense to already filter them there, and not in the resolve function.