I'm developing an application that will allow users to search for other users by ordering them by geo location.
According to the information found on the internet, to do this on amplify I have to perform the following steps:
create an elastic search mapping, indicating the type 'geo_point' on the lastPosition attribute (I would like to define it once in the project file and not at each build in the elastic search console)
create a custom query and a custom vtl resolver and set the sort by lastPosition (of type '_geo_distance') in it.
But I don't understand how to do these 2 steps, so I need some suggestion :(.
// my schema.graphql
type User
#model
#auth(rules: [{allow: owner}, {allow: private, operations: [read]}])
#searchable {
// ...other fields
id: ID!
lastPosition: Position
positionUpdatedAt: AWSDateTime
}
type Position {
lat: Float!
lon: Float!
}
// my custom searchUsers query in graphql/custom_queries.ts
export const searchUsersNearby = /* GraphQL */ `
query SearchUsersNearby(
$filter: SearchableUserFilterInput
$sort: [SearchableUserSortInput]
$location: PositionInput // current user position that i need to use in vtl resolver to sort users by distance,
$limit: Int
$nextToken: String
$from: Int
$aggregates: [SearchableUserAggregationInput]
) {
searchUsersNearby(
filter: $filter
sort: $sort
limit: $limit
nextToken: $nextToken
from: $from
aggregates: $aggregates
) {
items {
id
// ...other fields
lastPosition
positionUpdatedAt
createdAt
updatedAt
owner
}
nextToken
total
aggregateItems {
name
result {
... on SearchableAggregateScalarResult {
value
}
... on SearchableAggregateBucketResult {
buckets {
key
doc_count
}
}
}
}
}
}
`;
I use amplify v8.1.0 with transformer v2.
Thank you guys
I'm using DynamoDB of AWS and i'm trying to insert item with relations (hasMany).
When i'm trying to create item with relations it returns error:
"The variables input contains a field name 'contacts' that is not defined for input object type 'CreateBookingInput' "
Here is the definition of my models where you can see the relations
type Traineeship #model #auth(rules:[{allow:public}]){
traineeshipID: ID! #primaryKey
}
type Booking #model #auth(rules:[{allow:public}]){
bookingID: ID! #primaryKey
traineeship: Traineeship #hasOne
contacts:[BookingContact] #hasMany
}
type BookingContact #model #auth(rules:[{allow:public}]){
bookingContactID: ID! #primaryKey
}
And here is the mutation use to insert
export const createBooking = /* GraphQL */ `
mutation CreateBooking(
$input: CreateBookingInput!
$condition: ModelBookingConditionInput
) {
createBooking(input: $input, condition: $condition) {
bookingID
traineeship {
traineeshipID
title
description
fulldesc
place
from
to
price
img
horaire
garderie
age
full
createdAt
updatedAt
}
email
tel
firstname
name
birthdate
address
zipcode
city
Remark
submitedDate
validatedDate
validated
contacts {
items {
bookingContactID
firstname
name
phone
parent
createdAt
updatedAt
bookingContactsId
}
nextToken
}
createdAt
updatedAt
bookingTraineeshipId
}
}
`;
As you can see contacts is define in createBooking. If i remove this property it works correctly.
THanks for your help
I'm performing a query to get PowerMeter details in which contains another type inside called Project. I write the query this way:
query getPowerMeter($powerMeterId: ID!) {
powerMeter: powerMeter(powerMeterId: $powerMeterId) {
id
name
registry
project {
id
name
}
}
}
When I perform the query for the first time, project is successfully returned. The problem is that when I perform subsequent queries with the same parameters and default fetchPolicy (cache-first), project isn't returned anymore.
How may I solve this problem?
Also, I call readFragment to check how powerMeter is saved in the cache and the response shows that powerMeter has project saved.
const frag = client.readFragment({
fragment: gql`
fragment P on PowerMeter {
id
name
registry
project {
id
name
}
}
`,
id: 'PowerMeter:' + powerMeterId,
});
Power Meter returned first time
{
"powerMeter":{
"id":"7168adb4-4198-443e-ab76-db0725be2b18",
"name":"asd123123",
"registry":"as23",
"project":{
"id":"41d8e71b-d1e9-41af-af96-5b4ae9e492c1",
"name":"ProjectName",
"__typename":"Project"
},
"__typename":"PowerMeter"
}
}
Fragment after calling power meter first time
{
"id":"7168adb4-4198-443e-ab76-db0725be2b18",
"name":"asd123123",
"registry":"as23",
"project":{
"id":"41d8e71b-d1e9-41af-af96-5b4ae9e492c1",
"name":"ProjectName",
"__typename":"Project"
},
"__typename":"PowerMeter"
}
Power Meter returned second time
{
"powerMeter":{
"id":"7168adb4-4198-443e-ab76-db0725be2b18",
"name":"asd123123",
"registry":"as23",
"__typename":"PowerMeter"
}
}
Fragment after calling power meter second time
{
"id":"7168adb4-4198-443e-ab76-db0725be2b18",
"name":"asd123123",
"registry":"as23",
"project":{
"id":"41d8e71b-d1e9-41af-af96-5b4ae9e492c1",
"name":"ProjectName",
"__typename":"Project"
},
"__typename":"PowerMeter"
}
Edit 1: Fetching Query
The code below is how I'm fetching data. I'm using useApolloClient and not a query hook because I'm using AWS AppSync and it doesn't support query hook yet.
import { useApolloClient } from '#apollo/react-hooks';
import gql from 'graphql-tag';
import { useEffect, useState } from 'react';
export const getPowerMeterQuery = gql`
query getPowerMeter($powerMeterId: ID!) {
powerMeter: powerMeter(powerMeterId: $powerMeterId) {
id
name
registry
project {
id
name
}
}
}
`;
export const useGetPowerMeter = (powerMeterId?: string) => {
const client = useApolloClient();
const [state, setState] = useState<{
loading: boolean;
powerMeter?: PowerMeter;
error?: string;
}>({
loading: true,
});
useEffect(() => {
if (!powerMeterId) {
return setState({ loading: false });
}
client
.query<GetPowerMeterQueryResponse, GetPowerMeterQueryVariables>({
query: getPowerMeterQuery,
variables: {
powerMeterId,
},
})
.then(({ data, errors }) => {
if (errors) {
setState({ loading: false, error: errors[0].message });
}
console.log(JSON.stringify(data));
const frag = client.readFragment({
fragment: gql`
fragment P on PowerMeter {
id
name
registry
project {
id
name
}
}
`,
id: 'PowerMeter:' + powerMeterId,
});
console.log(JSON.stringify(frag));
setState({
loading: false,
powerMeter: data.powerMeter,
});
})
.catch(err => setState({ loading: false, error: err.message }));
}, [powerMeterId]);
return state;
};
Edit 2: Fetching Policy Details
When I use fetchPolice equals cache-first or network-only, the error persists. When I use no-cache, I don't get the error.
I think this might have been the solution:
https://github.com/apollographql/apollo-client/issues/7050
Probably way too late, but it could help people coming to this issue in the future.
When using apollo client's InMemoryCache it seems you need to provide a list of possible types so the fragment matching can be done correctly when using the InMemoryCache.
You can do that manually when having few union types and a pretty stable API which doesn't change very often.
Or you automatically generate these types into a json file, which you can use directly in the InMemoryCache's possibleTypes config directly.
Visit this link to the official docs to find out how to do it.
Cheers.
I created this question in case anyone was curious on how to add union / Polymorphic types in Apollo. Hopefully this will make it easier for them.
In this example I wanted the response to either be a Worksheet or ApiError
// typedefs.js
export default [`
schema {
query: Query
}
type Query {
worksheet(id: String!): Worksheet | Error
}
type Worksheet {
id: String!
name String
}
type ApiError {
code: String!
message: String!
}
`];
// resolvers.js
export default {
Query: {
worksheet(_, args, { loaders }) {
return loaders.worksheet.get(args.id).catch(() => {
// ApiError
return {
code: '1',
message: 'test'
}
});
}
}
};
// Express Server
import { graphqlExpress } from 'apollo-server-express';
import { makeExecutableSchema } from 'graphql-tools';
import typeDefs from './typedefs';
import resolvers from './resolvers';
...
app.post(
'/graphql',
graphqlExpress(req => ({
makeExecutableSchema({ typeDefs, resolvers }),
context: mkRequestContext(req.ctx, req.log),
formatError: formatGraphQLError(req.ctx, req.log)
}))
);
In GraphQL to add a union type in the typedefs you have to define the union
i.e union WorksheetOrError = Worksheet | ApiError
// typedefs.js
export default [
`
schema {
query: Query
}
type Query {
worksheet(id: String!): WorksheetOrError
}
union WorksheetOrError = Worksheet | ApiError
type Worksheet {
id: String!
name String
}
type ApiError {
code: String!
message: String!
}
`];
In the resolvers you have to define a resolver for the union type that has the property __resolveType. This will help tell the GraphQL executor which type the result is.
// resolvers.js
export default {
Query: {
worksheet() {
...
}
},
WorksheetOrError: {
__resolveType(obj) {
if (obj.id) {
return 'Worksheet';
}
if (obj.code) {
return 'ApiError';
}
return null;
}
},
};
To create a GraphQL Query in Apollo Client
// Your application code.
// This is my Worksheet Query in my React code.
const WorksheetQuery = gql`
query GetWorksheet($worksheetId: String!) {
worksheet(id: $worksheetId) {
... on Worksheet {
id
name
}
... on ApiError {
code
message
}
}
}
Now you can check the __typename to check what type is in the response.
Note: For those who are wondering why I'm not using GraphQL errors. It's because Apollo doesn't seem to handle errors well when it encounters a graphQL error. So for a work around I'm trying to return a custom ApiError in my response.
There a few reasons why using a union with an error type is nice.
Currently if you wanted a partial response with GraphQLError. Apollo does not cache the errors so if you wanted to re-use the cached response later you wouldn't have the complete response since the errors are removed. (Now you can't display the proper UI with errors)
Getting GraphQLError back in Apollo would return a flat list of errors with the path to where the error is in the data. So you would need to verify that which part of your schema did the error occur in. However if you follow the instructions above you would have the error within the schema already. That way you already know which part of the schema the error happened.
I am trying to query all entities from my apollo local cache (InMemoryCache) but without success.
Here is how I proceed.
query EntityList($limit: Int!, $offset: Int!) {
entities(
limit: $limit,
offset: $offset
) {
__typename
EntityId
}
}
With this query : no problem.
But later, I would like to query all entities from the cache and without any params.
query LocalEntityList {
entities {
EntityId
}
}
This simple code triggers an error
Can't find field entities on object (ROOT_QUERY)....
From the documentation site, I understand that I need to use cacheResolvers options on the InMemoryCache object.
But there is no example without passing an id as argument.
I think what you want is a #connection directive. This will allow you to cache your entities query without the limit/offset arguments.
query EntityList($limit: Int!, $offset: Int!) {
entities (
limit: $limit,
offset: $offset
) #connection(key: "EntityList_entities") {
__typename
EntityId
}
}
Making a successive call with the same connection should return all the entities from that key.
query LocalEntityList {
entities #connection(key: "EntityList_entities") {
EntityId
}
}