In Relay.js, what is the `Client Mutation Identifier`? - graphql

In the relay documentation here, it says that:
Relay uses a common pattern for mutations, where there are root fields on the mutation type with a single argument, input, and where the input and output both contain a client mutation identifier used to reconcile requests and responses.
But in the example they provided, the input and output looked like this respectively:
// IntroducedShipInput
{
"input": {
"shipName": "B-Wing",
"factionId": "1"
}
}
// IntroducedShipPayload
{
"introduceShip": {
"ship": {
"id": "U2hpcDo5",
"name": "B-Wing"
},
"faction": {
"name": "Alliance to Restore the Republic"
}
}
}
So what is the client mutation identifier? And why, and how does it get used to reconcile requests and responses?

I'm still not 100% sure what exactly happened to the "client mutation identifier," but having done some research, it appears to have been a requirement in previous versions of Relay. This PR apparently removed the requirement by replacing it with some other mechanism, but it's not clear to me what that other mechanism does. I left a comment requesting more clarification around the documentation, which appears to be out of date.
At any rate, the client mutation identifier appears to have been related to some assumptions about mutation idempotency in Facebook's implementation of GraphQL.

Related

How to return both errors and data with Apollo server?

I know Apollo can return a combination of errors and data, the client documentation even covers this: and I believe this can happen automatically when field resolvers run into issues, but how can I manually return mixed payloads? I have use cases in my project where users will send large amounts of data to mutate in the form of an input array. Some members of the array might be malformed and I want to throw validation errors for those but I still want the other changes to go through. An example pseudo request and response pair would be:
// input
{
mutateEntities: [
entityAMutationInput,
entityBMutationInput,
entityCMutationInput,
entityDMutationInput,
]
}
// response
{
data: {
mutateEntities: [
entityAMutationPayload,
entityBMutationPayload,
]
},
// members of the error array seem to refer to individual GQL operations in Apollo so
// there would only be one here
errors: [{
...errorMetadata
extensions: {
validationErrors: [
entityCValidationError,
entityDValidationError
]
}
}]
}
I realise that I have the option of defining errors as a GQL type and including them as part of the payload data but our FE team already has error handling that expects errors to appear in payload.errors and not, for example, payload.data.validationErrors or some other custom field. I want my errors to appear in payload.errors.extensions.validationErrors to minimise the impact on the front end. I know how to do this if I throw a custom error but, if I throw errors, then I won't have any data under payload.data.mutateEntities.

Best practices for writing a PUT endpoint for a REST API

I am building a basic CRUD service with some business logic under the hood, and I'm about to start working on the PUT (update) endpoint. I have already fully written+tested GET (read) and POST (create) for my data object. The data store for my documents is an ElasticSearch instance on AWS.
I have some decisions to make about how I want to architect the PUT, namely, how I want to determine a valid request. My goal is to make it so the POST is only for the creation of new assets, and PUT will only update existing documents. (At the moment, I am POSTing to elastic with /_doc/, however the intent is to move to /_create/ as part of this work)
What I'm a little hung-up on is the "right" way to check that a document exists before making the API call to Elastic to update.
When a user submits a document to PUT, should I first GET from Elastic with the document ID to make sure the document already exists? Or should I simply try to "update" the resource and if it doesn't exists, one is created?
Obviously there are trade-offs to each strategy. With the latter, PUTing a document that doesn't exist almost completely negates the need for a POST at all, so I'd be more inclined to go with the former - despite the additional REST call - to maintain the integrity of the basic REST definition.
Thoughts?
The consideration whether to update a doc (with versioning) or create a new one with some shared ID related to all previous versions depends on your use case -- either of them are 'correct' but there's too little information to advise on that right now.
With regards to the document-exists strategies -- there are essentially 2 types of IDs in ES -- what I call:
internal ids (_id)
external ids (doc_values-provided ids)
Create an index & a doc:
PUT myindex
PUT myindex/_doc/internal_id_1
{
"external_id": "1"
}
Internal ID check
GET myindex/_doc/internal_id_1
or
GET myindex/_count
{
"query": {
"ids": {
"values": [
"internal_id_1"
]
}
}
}
or
GET myindex/_count
{
"query": {
"term": {
"_id": {
"value": "internal_id_1"
}
}
}
}
External ID check
GET myindex/_count
{
"query": {
"term": {
"external_id": {
"value": "1"
}
}
}
}
and many others (terms, match (for partial matches etc), ...)
Note that I've used the _count endpoint instead of _search -- it's slightly faster.
If you intend to check the _version of a given doc before you proceed to update it, replace _count with _search?version=true and the _version attribute will become available:
{
"_index":"myindex",
"_type":"_doc",
"_id":"internal_id_1",
"_version":2, <---
"_score":1.0,
"_source":{
"external_id":"1"
}
}

What is query_hash in instagram?

I was working for the first time on graphql, and I saw that Instagram hash their queries.
I searched something, but I don't know if it is correct. The hash is like a persistedquery stored in a cache memory?
Or am I wrong?
Example: this is my request payload
{
"operationName":"user",
"variables":{},
"query":"query user {\n users {\n username\n createdAt\n _id\n }\n}\n"
}
this is instagram:
query_hash: 60b755363b5c230111347a7a4e242001
variables: %7B%22only_stories%22%3Atrue%7D
(it is in urlencode mode).
Now, how could I hash my query? I'm using NodeJS as backend and react js as frontend.
I would like to understand how it works x)! Thank you guys!
The persisted query is used to improve GraphQL network performance by reducing the request size.
Instead of sending a full query which could be very long, you send a hash to the GraphQL server which will retrieve the full query from the key-value store using the hash as the key.
The key value store can be memcached, redis, etc
The Apollo Server comes with automated persisted queries out of the box. I recommended gives it a try. They have publish a blog about it. https://blog.apollographql.com/automatic-persisted-queries-and-cdn-caching-with-apollo-server-2-0-bf42b3a313de
If you want to build your own solution, you can use this package to do the hashing yourself https://www.npmjs.com/package/hash.js
query_hash (or query_id) does not hash the variables or the parameters, it hashes the payload.
Lets say your actual path is /graphql and your payload is
{
"user": {
"profile": [
"username",
"user_id",
"profile_picture"
],
"feed": {
"posts": {
"data": [
"image_url"
],
"page_size": "{{variables.max_count}}"
}
}
}
}
Then this graphql payload will be hashed and it becomes d4d88dc1500312af6f937f7b804c68c3. Now instead of doing that on /graphql you do /graphql/query/?query_hash=d4d88dc1500312af6f937f7b804c68c3. This way you hashed the payload, as in you hashed the "keys" that are required from the graphql. So when you pass variables as a param then the payload does not actually change, because the variables are constant as well, and you are changing them on the backend, and not in the payload.

Confused about elasticsearch query

POST http://localhost:9200/test2/drug?pretty
{
"title": "I can do this"
}
get test2/drug/_search
{
"query" : {
"match": {
"title": "cancer"
}
}
}
The mappings are:
{
"test2": {
"mappings": {
"drug": {
"properties": {
"title": {
"type": "string"
}
}
}
}
}
}
Running the above query returns the document. I want to understand what elastic is doing behind the scenes? From looking at the output of the default analyzer it does not tokenize cancer such that it returns "can" so why is a document with the word "can" being returned and what is causing this to be returned? In other words, what other processing is happening to the search query "cancer".
Updated
Is there a command I can run on my box that will clear all indexes and everything so I have a clean slate? I ran delete /* which succeeded but still getting a match.
The problem with your test is, if you are using Sense, the get request. In Sense it should be GET (capital letters).
The explanation is related to GET vs. POST http methods.
Behind the scene Sense actually converts a GET request to a HTTP POST (given that many browsers do not support HTTP GET requests with a request body). This means that, even if you write GET, the actual http request is a POST.
Because Sense has the autocomplete that forces upper case letters for request methods, it uses the same upper case letters when deciding if it's a GET (and not a lowercase get) request together with a request body. If it is, then that request is transformed to a POST one. If it compares the request method and decides is not a GET it sends the request as is, meaning with a get method and with a body. Since the body is ignored, what reaches Elasticsearch will be a test2/drug/_search which is basically a match_all.
I guess that you configured in your index mappings an NGram filter or tokenizer. Let's suppose (I hope you'll confirm my hypothesis) an Edge NGram is configured. You can check it with:
GET test2/_mapping
Then the document is tokenized: i,c,ca,can,d,do,t,th,thi,this. As a result, in the index, the token can points to the document I can do this
When you're searching cancer, the tokens c,ca,can,canc,cance,cancer are produced by the same analysis chain, and then looked for in the index. As a result your document is found.
With the NGram filter, you often need to configure a different analyzer for search than for indexing, for instance:
index_analyzer/analyzer: standard + edge ngram
search_analyzer: stardand along
Then if you search can you'll find documents containing can,cancer,candy... But if you search cancer, you'll only find documents containing cancer,cancerology... and so on.

Mailchimp API 2.0 lists/subscribe responding with error 250 List_MergeFieldRequired MMERGE4 is required

I'm attempting to use the lists/subscribe Mailchimp API 2.0 endpoint to subscribe an email to a list, but I keep getting a puzzling error. My request looks like:
{
"apikey":"myapikey-us5",
"id":"listid",
"email":{"email":"my#email.com"},
"double_optin":false,
"send_welcome":true
}
I'm sending this to https://us5.api.mailchimp.com/2.0/lists/subscribe.json and getting this response:
{
"status": "error",
"code": 250,
"name": "List_MergeFieldRequired",
"error": "MMERGE4 must be provided - Please enter a value"
}
And if I specify "merge_vars": {} I still get the same error. What am I missing here?
It means you need to provide a value for MMERGE4 merge field. How you do this depends on what type of merge field MMERGE4 is, but It would be like this:
{
"apikey":"myapikey-us5",
"id":"listid",
"email":{"email":"my#email.com"},
"double_optin":false,
"send_welcome":true,
"merge_vars": {
"MMERGE4": "something"
}
}
But you should look and see what type of data that is, otherwise you might cause issues for whomever set that up as a required field.

Resources