apollo graphql server subscription - send only if data differs - graphql

So I created a subscription in apollo server.
i have my data for that request coming from multiple classes, nested in each other, and this works just fine.
But if I would like apollo to only send out the message if the values the client requested actually changed from the last update.
I can't just cache and compare the last sent object, as I don't know which fields this specific connection is interested in.
Is there a hook or something, that would let me compare the current "over the wire" message before it is sent, so I could compare it with the last "over the wire" message and decide if I actually want to send it?
Putting it in example let's say I have a data structure defined:
{
foo: "valueA",
bar: "valueB"
}
If I were to subscribe with one client to foo and another client to bar, and valueB got updatet to valueC, I would want only the client subscribed to bar to get an update, as the message for the client subscribed to foo would be the same as before.
While I could try to manually filter the data to recreate what would actually be sent to the specific client, this would be sort of redundant, as apollo already does this. Much rather I'd like to intercept the JSON message which gets sent over the websocket, compare it with a stored copy of the last message and decide whether I actually want to send it or to silently discard it.
Also I'm not sure how to find out, which fields the client subscribed to, but then again, I really don't want to go down that route recreating functionality apollo already has built in.

I'm not an expert in apollo graphql by any stretch of the imagination, but I had encountered some cases that could help you with this.
What I'd check first though, is wether Apollo server supports hot/cold subscriptions, but the truth is, that doesn't help much in a case of many connections.
What you should look into is a context of a connection. If it works as I think it works you should be able to create a custom resolver that'd do something along the lines of:
export const SomeResolver = {
Subscription: {
subscribeToSomeMessages: {
subscribe: withFilter(
(rootValue, args, context, info) => {
return pubsub.asyncIterator(["YOUR EVENT"])
},
(payload, variables, context, info) => {
{here compare payload with context}
{update context with new value}
return {if values differ}
}
)
},
...
You can then pass this resolver into ApolloServer initialisation block.
Hope that helps and gives you something to start with

Related

How to derive values in apollo client

I have a few react components that mutate my server data and for now I have refetchQueries: [{query:myQuery}]. I need to restructured the data as a map for faster lookup time. How can I accomplish this? In Redux, I would have used reselect and in MobX I would have used #computed. As far as I can tell, apollo doesn't support this functionality yet.
I looked into:
#client directive, but this doesn't work for me since I have to compute the data on the server response.
reactive variables don't work either since I will have to change the variable everywhere I mutate the data, far from ideal.
There seems to be very little information out there about computed/derived values when using Apollo Client, the only reference I found was this one:
Apollo GraphQl Storing derived data
How about if you define in your schema & in your resolver some alternative (union) response structure? (not sure if this would work actually)
type Query{
books(mapBy:String): [Book]|JSON
}
so if you query (using it instead of mutation for simplicity)
query{
books(mapBy:"id")
}
it would return JSON
{
123: { __typename: "Book", name: "Dune"}
}
And if you don't want to return all of the Book fields in JSON, maybe pass extra param which would list actual structure you need.
Didn't encounter such problem myself yet, interesting. But otherwise, it should be done on client side.

Is it possible to map a subscription parameter to an array at the mutation output?

I have a theoretical question. As I know subscription parameters must exist as a field in the returning type of the mutation. This means that the type of parameter must also match the type of the field in the returning object of the mutation. Am I right? Suppose I get an array with channels ids in the mutation response. I only send one channel id as a parameter in the subscription. Is it possible to map a subscription parameter to an array at the mutation output? If the channel id exists in the array (field channelsIds), the subscription must work. Is it possible to write this logic in the scheme itself, or is it technically impossible?
GraphQL schema:
schema {
mutation: Mutation
subscription: Subscription
}
type Mutation {
testMutation(input: TestMutationInput): TestMutationOutput
}
type TestMutationOutput {
channelsIds: [String!]!
userId: String!
userEmail: String
userPhoneNumber: String
}
type Subscription {
watchTestMutation(channelId: String!): TestMutationOutput
#aws_subscribe(mutations: ["testMutation"])
}
If I understand you correctly you want to filter based on if the mutation's returned value is in an array that is passed as an argument to the subscription. Sorry to say that is not possible at this time. Subscription filters only evaluate to true or false and cannot accommodate any logic other than that.
At the end of October 2020, I contacted AWS support for advice on this issue. I think this answer may be useful to someone, so I post their answer.
Please allow me to inform you that the use-case that you have
mentioned in the case is currently not possible via AppSync. I
understand that the lack of the feature may be causing inconvenience.
There is an internal feature request already with the AppSync team to
incorporate this feature and I have added a +1 on your behalf. It is
worth noting, that once this feature request is with the team, it will
be up to the team as to if/when this potential infrastructure feature
is implemented, and because of the limited visibility into the
progress of internal development processes, I won’t be able to provide
an ETA regarding its release. I would request you to keep an eye on
the what's new page or the AWS Blogs as all new feature requests and
enhancements are posted there[1-3].
However we can suggest a couple of workarounds in this case:
Filter the required fields on client side itself after receiving the values on the client-side from AppSync.
If the values to be filtered are very limited we can use a fake mutation made with the help of a resolver mapped to “None” Data
source. In this flow, we would create a lambda function that uses a
DynamoDB stream as the trigger. The Lambda function is triggered
whenever there's an update to the DynamoDB table.

We can then include logic in the Lambda function to filter the
required fields and perform a mutation to AppSync. In AppSync, the
mutation which was called by lambda would configured using a resolver
mapped to a “None” Data source. The None data source type passes the
request mapping template directly to the response mapping template.
And when we subscribe to this mutation, we will directly get the
filtered data from Lambda that was used to call this mutation. Please
refer to [4] for a step-by-step description of this process.
But please note that this workaround is cumbersome and would require a lot of changes if the required field values keep changing. Workaround 1(handling it on the client-side) is usually the preferred way to handle this use-case.
Resources:
[1] https://blogs.amazon.com/
[2] https://aws.amazon.com/new/
[3] https://aws.amazon.com/releasenotes/
[4] https://aws.amazon.com/premiumsupport/knowledge-center/appsync-notify-subscribers-real-time/

How to get the result of a asynchronous action in NGXS?

I want to perform an action based to the result of an asynchronous NGXS action.
In a Angular frontend app I'm using NGXS for state management. Some of the actions involve talking to a backend via REST calls. Those actions are implemented as asynchronous actions, with the reducer functions in my state classes returning an Observable.
What I'm looking for is a way to get hands on the result of the backend call, to be able to perform some action.
One use case I'm trying to implement is navigation to just created objects: Business objects are created in the frontend (Angular) app with a couple of domain properties. They get persisted in the backend, and as a result an ID for this object is created and returned to the frontend, and incorporated into the NGXS store. As a direct response to this, I'd like to navigate to a detail view for the new object. To do so, I need
(a) the information that the call has been returned successful, and
(b) the answer from the backend (the ID in this case).
Another slightly more complicated use case is the assignment of a number of tags to an business object. The tags are entities by themselfes, and have an ID each. In the UI, the user can either pick existing or add new tags. Either way, multiple tags can be added in a single step in the UI, which means I have to
call the backend for each new tag to create the ID
after all missing tags are created, update the business object with the list of tag IDs
In general, there are use cases in the frontend that depend on the result of a backend call, and there is no clean way to find this result in the store (although it's in there)
I know I can subscribe to the Observable returned from the store's dispatch method (as shown in asynchronous actions).
I also know about action handlers. In both cases I can attach code to the event of an action finished, but neither option enables me to get the result of the backend call. In the fist case, the Observable carries the whole store, while in the latter case I get the original Action, which is unfortunately missing the essential information (the ID).
The part you're missing here are selectors. Dispatching actions is not supposed to give you back a result. The only purpose of the Observable returned by store.dispatch() is to tell you when the action's handlers are done.
To get to the data returned by your calls to the backend, you have to patch the state inside your action handler. And then, outside of your state, you can access the data using store.select() or store.selectSnapshot() depending on what you need. Your state class should look somewhat like this (untested):
#State()
export class SampleState {
#Selector(SampleState)
sampleSelector(state) {
return state.sampleObject;
}
#Action(SampleAction)
sampleAction(ctx: StateContext<any>, action: sampleAction) {
return sampleBackendCall(/* ... */).pipe(
tap((result) => {
ctx.patchState({ sampleObject: result });
})
);
}
}
Now you can access this result where ever you need using the Store. For the use case of navigating to an element after its creation, you can combine a subscription to store.dispatch() with a store.selectSnapshot() like this:
store.dispatch(new SampleAction()).subscribe(() => {
navigateTo(store.selectSnapshot(SampleState.sampleSelector));
});
Note that in this easy case a selectSnapshot is perfectly fine, as we only want to get the value we just finished writing into the state. In most cases though, you will want to use store.select() or the #Select() decorator because they return Observables which enable you to also correctly display changes in your state.
That said, I'd like to add that if saving data inside the state is not necessary for you at all, then probably NGXS is the wrong library for you in the first place and you could as well just use an ordinary angular service directly returning the result of the backend call, like suggested in the comments.

Apollo Client strips away additional results from response object

We have implemented our graphql api response like this.
{
data:  {...},
skip: 0,
limit: 10,
total: 100,
hasMore: true
}
If I query our api via graphiql the response looks like expected.
But unfortunately the apollo client in our application strips away all properties from the return object except data.
Is this expected behaviour?
And if so, how can I change it or solve this problem differently.
I need to get the total amount of data to implement pagination accordingly.
I know there is a method with fetchMore but it won't tell me the whole amount of entries in the list.
According to the spec only three top-level keys are expected -- data, errors and extensions. If you include additional keys you're going off-spec -- I would not expect any client to attempt to read them.
At the end of the day, this information should be included in your schema and returned as part of the data in the response. Returning it anywhere else (as additional keys in the response, as response headers, etc.) is a bad idea, if for no other reason than the fact that you could have multiple query fields at the root level, in which case you'd only be able to convey pagination information about one of the fields and it'd be unclear which field the information applied to. The same could be said if you have nested fields that can also be paginated.

How to always return Graphql field?

I would like to always return a field when certain requests are made so that additional actions can be performed by the client.
For example if user registration has been successful I want to show flash message. But it has to be handled by the backend and not the front end.
I could "require" clients to always request specific field that would hold the needed information but it could be easily overlooked so I'd like to return data whenever needed.
Is this possible?
There is nothing in the GraphQL spec that allows particular fields to be required in the same sense that arguments can be required. This can be handled inside the resolver, however. For example, if you have a query called getFoos, and you want to require the clients to always request the field foo when fetching that query, your resolver for getFoos could look like this:
function (obj, args, ctx, info) {
const fields = info.fieldNodes[0].selectionSet.selections.filter(s => s.name.value)
if (!fields.includes('foo')) return Promise.reject(new Error('Missing field foo!'))
// continue resolving the query as normal
}
Each resolver takes as its fourth parameter an info object that contains detailed information about the request, including what fields were requested. So inside your resolver, just verify that the field was included, and if it's missing, return a rejected Promise instead. This will cause your query to fail anytime that particular field is missing.

Resources