Giter Club home page Giter Club logo

aws-mobile-appsync-sdk-js's Introduction

Use AWS AppSync with JavaScript apps · lerna

AWS AppSync

AWS AppSync is a fully managed service that makes it easy to develop GraphQL APIs by handling the heavy lifting of securely connecting to data sources like AWS DynamoDB, Lambda, and more.

You can use any HTTP or GraphQL client to connect to a GraphQL API on AppSync.

For front-end web and mobile development, we recommend using the AWS Amplify library which is optimized to connect to the AppSync backend.

Looking for the AWS AppSync SDK for JavaScript (built on Apollo v2)? AWS AppSync SDK for JavaScript (V2) is now in Maintenance Mode until June 30th, 2024. This means that we will continue to include updates to ensure compatibility with backend services and security. No new features will be introduced in the AWS AppSync SDK for JavaScript (V2). Please review the upgrade guide for recommended next steps.

AWS AppSync Links for Apollo V3

If you would like to use the Apollo JavaScript client version 3 to connect to your AppSync GraphQL API, this repository (on the current stable branch) provides Apollo links to use the different AppSync authorization modes, and to setup subscriptions over web sockets. Please log questions for this client SDK in this repo and questions for the AppSync service in the official AWS AppSync forum .

npm npm

package version
aws-appsync-auth-link npm
aws-appsync-subscription-link npm

Example usage of Apollo V3 links

React / React Native

For more documentation on graphql operations performed by React Apollo see their documentation.

Using Authorization and Subscription links with Apollo Client V3 (No offline support)

For versions of the Apollo client newer than 2.4.6 you can use custom links for Authorization and Subscriptions. Offline support is not available for these newer versions. The packages available are aws-appsync-auth-link and aws-appsync-subscription-link. Below is a sample code snippet that shows how to use it.

import { createAuthLink } from "aws-appsync-auth-link";
import { createSubscriptionHandshakeLink } from "aws-appsync-subscription-link";

import {
  ApolloProvider,
  ApolloClient,
  InMemoryCache,
  HttpLink,
  ApolloLink,
} from "@apollo/client";

import appSyncConfig from "./aws-exports";

/* The HTTPS endpoint of the AWS AppSync API 
(e.g. *https://aaaaaaaaaaaaaaaaaaaaaaaaaa.appsync-api.us-east-1.amazonaws.com/graphql*). 
[Custom domain names](https://docs.aws.amazon.com/appsync/latest/devguide/custom-domain-name.html) can also be supplied here (e.g. *https://api.yourdomain.com/graphql*). 
Custom domain names can have any format, but must end with `/graphql` 
(see https://graphql.org/learn/serving-over-http/#uris-routes). */
const url = appSyncConfig.aws_appsync_graphqlEndpoint;


const region = appSyncConfig.aws_appsync_region;

const auth = {
  type: appSyncConfig.aws_appsync_authenticationType,
  apiKey: appSyncConfig.aws_appsync_apiKey,
  // jwtToken: async () => token, // Required when you use Cognito UserPools OR OpenID Connect. token object is obtained previously
  // credentials: async () => credentials, // Required when you use IAM-based auth.
};

const httpLink = new HttpLink({ uri: url });

const link = ApolloLink.from([
  createAuthLink({ url, region, auth }),
  createSubscriptionHandshakeLink({ url, region, auth }, httpLink),
]);

const client = new ApolloClient({
  link,
  cache: new InMemoryCache(),
});

const ApolloWrapper = ({ children }) => {
  return <ApolloProvider client={client}>{children}</ApolloProvider>;
};

Queries and Subscriptions using Apollo V3

import React, { useState, useEffect } from "react";
import { gql, useSubscription } from "@apollo/client";
import { useMutation, useQuery } from "@apollo/client";
import { v4 as uuidv4 } from "uuid";

const initialState = { name: "", description: "" };

const App = () => {

  const LIST_TODOS = gql`
    query listTodos {
      listTodos {
        items {
          id
          name
          description
        }
      }
    }
  `;

  const {
    loading: listLoading,
    data: listData,
    error: listError,
  } = useQuery(LIST_TODOS);

  const CREATE_TODO = gql`
    mutation createTodo($input: CreateTodoInput!) {
      createTodo(input: $input) {
        id
        name
        description
      }
    }
  `;

  // https://www.apollographql.com/docs/react/data/mutations/
  const [addTodoMutateFunction, { error: createError }] =
    useMutation(CREATE_TODO);

  async function addTodo() {
    try {
      addTodoMutateFunction({ variables: { input: { todo } } });
    } catch (err) {
      console.log("error creating todo:", err);
    }
  }

  const DELETE_TODO = gql`
    mutation deleteTodo($input: DeleteTodoInput!) {
      deleteTodo(input: $input) {
        id
        name
        description
      }
    }
  `;

  const [deleteTodoMutateFunction] = useMutation(DELETE_TODO, {
    refetchQueries: [LIST_TODOS, "listTodos"],
  });

  async function removeTodo(id) {
    try {
      deleteTodoMutateFunction({ variables: { input: { id } } });
    } catch (err) {
      console.log("error deleting todo:", err);
    }
  }

  const CREATE_TODO_SUBSCRIPTION = gql`
    subscription OnCreateTodo {
      onCreateTodo {
        id
        name
        description
      }
    }
  `;

  const { data: createSubData, error: createSubError } = useSubscription(
    CREATE_TODO_SUBSCRIPTION
  );

  return (
    // Render TODOs
  );
};

export default App;

AWS AppSync JavaScript SDK based on Apollo V2 (Maintenance mode)

The aws-appsync and aws-appsync-react packages work with the Apollo client version 2 and provide offline capabilities.

Note: if you do not have any offline requirements in your app, we recommend using the Amplify libraries.

npm

package version
aws-appsync npm
aws-appsync-react npm

Installation

npm

npm install --save aws-appsync

yarn

yarn add aws-appsync

React Native Compatibility

When using this library with React Native, you need to ensure you are using the correct version of the library based on your version of React Native. Take a look at the table below to determine what version to use.

aws-appsync version Required React Native Version
2.x.x >= 0.60
1.x.x <= 0.59

If you are using React Native 0.60 and above, you also need to install @react-native-community/netinfo and @react-native-community/async-storage:

npm install --save @react-native-community/[email protected] @react-native-community/async-storage

or

yarn add @react-native-community/[email protected] @react-native-community/async-storage

If you are using React Native 0.60+ for iOS, run the following command as an additional step:

npx pod-install

Creating a client with AppSync SDK for JavaScript V2 (Maintenance mode)

import AWSAppSyncClient from "aws-appsync";
import AppSyncConfig from "./aws-exports";
import { ApolloProvider } from "react-apollo";
import { Rehydrated } from "aws-appsync-react"; // this needs to also be installed when working with React
import App from "./App";

const client = new AWSAppSyncClient({
  /* The HTTPS endpoint of the AWS AppSync API 
  (e.g. *https://aaaaaaaaaaaaaaaaaaaaaaaaaa.appsync-api.us-east-1.amazonaws.com/graphql*). 
  [Custom domain names](https://docs.aws.amazon.com/appsync/latest/devguide/custom-domain-name.html) can also be supplied here (e.g. *https://api.yourdomain.com/graphql*). 
  Custom domain names can have any format, but must end with `/graphql` 
  (see https://graphql.org/learn/serving-over-http/#uris-routes). */
  url: AppSyncConfig.aws_appsync_graphqlEndpoint,
  region: AppSyncConfig.aws_appsync_region,
  auth: {
    type: AppSyncConfig.aws_appsync_authenticationType,
    apiKey: AppSyncConfig.aws_appsync_apiKey,
    // jwtToken: async () => token, // Required when you use Cognito UserPools OR OpenID Connect. Token object is obtained previously
    // credentials: async () => credentials, // Required when you use IAM-based auth.
  },
});

const WithProvider = () => (
  <ApolloProvider client={client}>
    <Rehydrated>
      <App />
    </Rehydrated>
  </ApolloProvider>
);

export default WithProvider;

Queries

import gql from "graphql-tag";
import { graphql } from "react-apollo";

const listPosts = gql`
  query listPosts {
    listPosts {
      items {
        id
        name
      }
    }
  }
`;
class App extends Component {
  render() {
    return (
      <div>
        {this.props.posts.map((post, index) => (
          <h2 key={post.id ? post.id : index}>{post.name}</h2>
        ))}
      </div>
    );
  }
}

export default graphql(listPosts, {
  options: {
    fetchPolicy: "cache-and-network",
  },
  props: (props) => ({
    posts: props.data.listPosts ? props.data.listPosts.items : [],
  }),
})(App);

Mutations & optimistic UI (with graphqlMutation helper)

import gql from "graphql-tag";
import { graphql, compose } from "react-apollo";
import { graphqlMutation } from "aws-appsync-react";

const CreatePost = gql`
  mutation createPost($name: String!) {
    createPost(input: { name: $name }) {
      name
    }
  }
`;

class App extends Component {
  state = { name: "" };
  onChange = (e) => {
    this.setState({ name: e.target.value });
  };
  addTodo = () => this.props.createPost({ name: this.state.name });
  render() {
    return (
      <div>
        <input onChange={this.onChange} placeholder="Todo name" />
        <button onClick={this.addTodo}>Add Todo</button>
        {this.props.posts.map((post, index) => (
          <h2 key={post.id ? post.id : index}>{post.name}</h2>
        ))}
      </div>
    );
  }
}

export default compose(
  graphql(listPosts, {
    options: {
      fetchPolicy: "cache-and-network",
    },
    props: (props) => ({
      posts: props.data.listPosts ? props.data.listPosts.items : [],
    }),
  }),
  graphqlMutation(CreatePost, listPosts, "Post")
)(App);

Mutations & optimistic UI (without graphqlMutation helper)

import gql from "graphql-tag";
import uuidV4 from "uuid/v4";
import { graphql, compose } from "react-apollo";

const CreatePost = gql`
  mutation createPost($name: String!) {
    createPost(input: { name: $name }) {
      name
    }
  }
`;

class App extends Component {
  state = { name: "" };
  onChange = (e) => {
    this.setState({ name: e.target.value });
  };
  addTodo = () => this.props.onAdd({ id: uuidV4(), name: this.state.name });
  render() {
    return (
      <div>
        <input onChange={this.onChange} placeholder="Todo name" />
        <button onClick={this.addTodo}>Add Todo</button>
        {this.props.posts.map((post, index) => (
          <h2 key={post.id ? post.id : index}>{post.name}</h2>
        ))}
      </div>
    );
  }
}

export default compose(
  graphql(listPosts, {
    options: {
      fetchPolicy: "cache-and-network",
    },
    props: (props) => ({
      posts: props.data.listPosts ? props.data.listPosts.items : [],
    }),
  }),
  graphql(CreatePost, {
    options: {
      update: (dataProxy, { data: { createPost } }) => {
        const query = listPosts;
        const data = dataProxy.readQuery({ query });
        data.listPosts.items.push(createPost);
        dataProxy.writeQuery({ query, data });
      },
    },
    props: (props) => ({
      onAdd: (post) => {
        props.mutate({
          variables: post,
          optimisticResponse: () => ({
            createPost: { ...post, __typename: "Post" },
          }),
        });
      },
    }),
  })
)(App);

Subscriptions (with buildSubscription helper)

import gql from "graphql-tag";
import { graphql } from "react-apollo";
import { buildSubscription } from "aws-appsync";

const listPosts = gql`
  query listPosts {
    listPosts {
      items {
        id
        name
      }
    }
  }
`;

const PostSubscription = gql`
  subscription postSubscription {
    onCreatePost {
      id
      name
    }
  }
`;

class App extends React.Component {
  componentDidMount() {
    this.props.data.subscribeToMore(
      buildSubscription(PostSubscription, listPosts)
    );
  }
  render() {
    return (
      <div>
        {this.props.posts.map((post, index) => (
          <h2 key={post.id ? post.id : index}>{post.name}</h2>
        ))}
      </div>
    );
  }
}

export default graphql(listPosts, {
  options: {
    fetchPolicy: "cache-and-network",
  },
  props: (props) => ({
    posts: props.data.listPosts ? props.data.listPosts.items : [],
    data: props.data,
  }),
})(App);

Subscriptions (without buildSubscription helper)

import gql from "graphql-tag";
import { graphql } from "react-apollo";

const listPosts = gql`
  query listPosts {
    listPosts {
      items {
        id
        name
      }
    }
  }
`;

const PostSubscription = gql`
  subscription postSubscription {
    onCreatePost {
      id
      name
    }
  }
`;

class App extends React.Component {
  componentDidMount() {
    this.props.subscribeToNewPosts();
  }
  render() {
    return (
      <div>
        {this.props.posts.map((post, index) => (
          <h2 key={post.id ? post.id : index}>{post.name}</h2>
        ))}
      </div>
    );
  }
}

export default graphql(listPosts, {
  options: {
    fetchPolicy: "cache-and-network",
  },
  props: (props) => ({
    posts: props.data.listPosts ? props.data.listPosts.items : [],
    subscribeToNewPosts: (params) => {
      props.data.subscribeToMore({
        document: PostSubscription,
        updateQuery: (
          prev,
          {
            subscriptionData: {
              data: { onCreatePost },
            },
          }
        ) => ({
          ...prev,
          listPosts: {
            __typename: "PostConnection",
            items: [
              onCreatePost,
              ...prev.listPosts.items.filter(
                (post) => post.id !== onCreatePost.id
              ),
            ],
          },
        }),
      });
    },
  }),
})(App);

Complex objects with AWS AppSync SDK for JavaScript (Maintenance mode)

Many times you might want to create logical objects that have more complex data, such as images or videos, as part of their structure. For example, you might create a Person type with a profile picture or a Post type that has an associated image. With AWS AppSync, you can model these as GraphQL types, referred to as complex objects. If any of your mutations have a variable with bucket, key, region, mimeType and localUri fields, the SDK uploads the file to Amazon S3 for you.

For a complete working example of this feature, see aws-amplify-graphql on GitHub.

If you're using AWS Amplify's GraphQL transformer, then configure your resolvers to write to DynamoDB and point at S3 objects when using the S3Object type. For example, run the following in an Amplify project:

amplify add auth        #Select default configuration
amplify add storage     #Select S3 with read/write access
amplify add api         #Select Cognito User Pool for authorization type

When prompted, use the following schema:

type Todo @model {
  id: ID!
  name: String!
  description: String!
  file: S3Object
}
type S3Object {
  bucket: String!
  key: String!
  region: String!
}
input CreateTodoInput {
  id: ID
  name: String!
  description: String
  file: S3ObjectInput # This input type will be generated for you
}

Save and run amplify push to deploy changes.

To use complex objects you need AWS Identity and Access Management credentials for reading and writing to Amazon S3 which amplify add auth configures in the default setting along with a Cognito user pool. These can be separate from the other auth credentials you use in your AWS AppSync client. Credentials for complex objects are set using the complexObjectsCredentials parameter, which you can use with AWS Amplify and the complex objects feature like so:

const client = new AWSAppSyncClient({
    url: ENDPOINT,
    region: REGION,
    auth: { ... },   //Can be User Pools or API Key
    complexObjectsCredentials: () => Auth.currentCredentials(),
});
(async () => {
  let file;
  if (selectedFile) { // selectedFile is the file to be uploaded, typically comes from an <input type="file" />
    const { name, type: mimeType } = selectedFile;
    const [, , , extension] = /([^.]+)(\.(\w+))?$/.exec(name);
    const bucket = aws_config.aws_user_files_s3_bucket;
    const region = aws_config.aws_user_files_s3_bucket_region;
    const visibility = 'private';
    const { identityId } = await Auth.currentCredentials();
    const key = `${visibility}/${identityId}/${uuid()}${extension && '.'}${extension}`;
    file = {
      bucket,
      key,
      region,
      mimeType,
      localUri: selectedFile,
    };
  }
  const result = await client.mutate({
    mutation: gql(createTodo),
    variables: {
      input: {
        name: 'Upload file',
        description: 'Uses complex objects to upload',
        file: file,
      }
    }
  });
})();

When you run the above mutation, a record will be in a DynamoDB table for your AppSync API as well as the corresponding file in an S3 bucket.

Offline configuration with AWS AppSync SDK for JavaScript (Maintenance mode)

When using the AWS AppSync SDK offline capabilities (e.g. disableOffline: false), you can provide configurations for the following:

  • Error handling
  • Custom storage engine

Error handling

If a mutation is done while the app was offline, it gets persisted to the platform storage engine. When coming back online, it is sent to the GraphQL endpoint. When a response is returned by the API, the SDK will notify you of the success or error using the callback provided in the offlineConfig param as follows:

const client = new AWSAppSyncClient({
  url: appSyncConfig.graphqlEndpoint,
  region: appSyncConfig.region,
  auth: {
    type: appSyncConfig.authenticationType,
    apiKey: appSyncConfig.apiKey,
  },
  offlineConfig: {
    callback: (err, succ) => {
      if (err) {
        const { mutation, variables } = err;

        console.warn(`ERROR for ${mutation}`, err);
      } else {
        const { mutation, variables } = succ;

        console.info(`SUCCESS for ${mutation}`, succ);
      }
    },
  },
});

Custom storage engine

You can use any custom storage engine from the redux-persist supported engines list.

Configuration is done as follows: (localForage shown in the example)

import * as localForage from "localforage";

const client = new AWSAppSyncClient({
  url: appSyncConfig.graphqlEndpoint,
  region: appSyncConfig.region,
  auth: {
    type: appSyncConfig.authenticationType,
    apiKey: appSyncConfig.apiKey,
  },
  offlineConfig: {
    storage: localForage,
  },
});

Offline helpers

For detailed documentation about the offline helpers, look at the API Definition.

Vue sample with AWS AppSync SDK for JavaScript (Maintenance mode)

For more documentation on Vue Apollo click here.

main.js

import Vue from "vue";
import App from "./App";
import router from "./router";

import AWSAppSyncClient from "aws-appsync";
import VueApollo from "vue-apollo";
import AppSyncConfig from "./aws-exports";

const config = {
  url: AppSyncConfig.graphqlEndpoint,
  region: AppSyncConfig.region,
  auth: {
    type: AppSyncConfig.authType,
    apiKey: AppSyncConfig.apiKey,
  },
};
const options = {
  defaultOptions: {
    watchQuery: {
      fetchPolicy: "cache-and-network",
    },
  },
};

const client = new AWSAppSyncClient(config, options);

const appsyncProvider = new VueApollo({
  defaultClient: client,
});

Vue.use(VueApollo);

new Vue({
  el: "#app",
  router,
  components: { App },
  provide: appsyncProvider.provide(),
  template: "<App/>",
});

App.vue

<template>
  <div id="app" v-if="hydrated">
    <router-view/>
  </div>
</template>

<script>
export default {
  name: 'App',
  data: () => ({ hydrated: false }),
  async mounted() {
    await this.$apollo.provider.defaultClient.hydrated()
    this.hydrated = true
  },
}
</script>

connected component

import gql from "graphql-tag";
import uuidV4 from "uuid/v4";

const CreateTask = gql`
  mutation createTask($id: ID!, $name: String!, $completed: Boolean!) {
    createTask(input: { id: $id, name: $name, completed: $completed }) {
      id
      name
      completed
    }
  }
`;

const DeleteTask = gql`
  mutation deleteTask($id: ID!) {
    deleteTask(input: { id: $id }) {
      id
    }
  }
`;

const ListTasks = gql`
  query listTasks {
    listTasks {
      items {
        id
        name
        completed
      }
    }
  }
`;

const UpdateTask = gql`
  mutation updateTask($id: ID!, $name: String!, $completed: Boolean!) {
    updateTask(input: { id: $id, name: $name, completed: $completed }) {
      id
      name
      completed
    }
  }
`;

// In your component (Examples of queries & mutations)
export default {
  name: "Tasks",
  methods: {
    toggleComplete(task) {
      const updatedTask = {
        ...task,
        completed: !task.completed,
      };
      this.$apollo
        .mutate({
          mutation: UpdateTask,
          variables: updatedTask,
          update: (store, { data: { updateTask } }) => {
            const data = store.readQuery({ query: ListTasks });
            const index = data.listTasks.items.findIndex(
              (item) => item.id === updateTask.id
            );
            data.listTasks.items[index] = updateTask;
            store.writeQuery({ query: ListTasks, data });
          },
          optimisticResponse: {
            __typename: "Mutation",
            updateTask: {
              __typename: "Task",
              ...updatedTask,
            },
          },
        })
        .then((data) => console.log(data))
        .catch((error) => console.error(error));
    },
    deleteTask(task) {
      this.$apollo
        .mutate({
          mutation: DeleteTask,
          variables: {
            id: task.id,
          },
          update: (store, { data: { deleteTask } }) => {
            const data = store.readQuery({ query: ListTasks });
            data.listTasks.items = data.listTasks.items.filter(
              (task) => task.id !== deleteTask.id
            );
            store.writeQuery({ query: ListTasks, data });
          },
          optimisticResponse: {
            __typename: "Mutation",
            deleteTask: {
              __typename: "Task",
              ...task,
            },
          },
        })
        .then((data) => console.log(data))
        .catch((error) => console.error(error));
    },
    createTask() {
      const taskname = this.taskname;
      if (taskname === "") {
        alert("please create a task");
        return;
      }
      this.taskname = "";
      const id = uuidV4();
      const task = {
        name: taskname,
        id,
        completed: false,
      };
      this.$apollo
        .mutate({
          mutation: CreateTask,
          variables: task,
          update: (store, { data: { createTask } }) => {
            const data = store.readQuery({ query: ListTasks });
            data.listTasks.items.push(createTask);
            store.writeQuery({ query: ListTasks, data });
          },
          optimisticResponse: {
            __typename: "Mutation",
            createTask: {
              __typename: "Task",
              ...task,
            },
          },
        })
        .then((data) => console.log(data))
        .catch((error) => console.error("error!!!: ", error));
    },
  },
  data() {
    return {
      taskname: "",
      tasks: [],
    };
  },
  apollo: {
    tasks: {
      query: () => ListTasks,
      update: (data) => data.listTasks.items,
    },
  },
};

Creating an AppSync Project

To create a new AppSync project, go to https://aws.amazon.com/appsync/.

License

This library is licensed under the Apache License 2.0.

aws-mobile-appsync-sdk-js's People

Contributors

alexeygolev avatar ashish-nanda avatar dabit3 avatar david-mcafee avatar dependabot[bot] avatar elorzafe avatar goldenbearkin avatar haverchuck avatar hyandell avatar iartemiev avatar joshbalfour avatar lukasf98 avatar manueliglesias avatar marklawlor avatar matthiasn avatar mlabieniec avatar mohebifar avatar nateous avatar onlybakam avatar palpatim avatar renebrandel avatar sammartinez avatar shawngustaw avatar tgorka avatar thaiphan avatar tombyrer avatar tyler-austin avatar undefobj avatar yuth avatar zaksingh avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

aws-mobile-appsync-sdk-js's Issues

What is the best way to debug requests?

I'm having a pretty big challenge properly debugging what is going on with my requests to/from AppSync. There doesn't seem to be any good way to log/trace AppSync requests correct? Please correct me if any of these are available:

  • There are no detailed request logs for AppSync (nothing I can look at to see each individual requests, who made it, why it might have been rejected, what resolvers were fired, etc.).
  • There is no integration between AppSync and XRay or CloudWatch Logs (but there is CloudWatch Metrics)

I'm having two issues that I can't get enough information about to diagnose properly. I'm seeing duplication of requests to my Lamba resolvers, and I'm getting 500 errors on my react-native application with no way to understand why.

Any help here would be much appreciated, I'm having a lot of success with AppSync otherwise and with a bit more insight (and a few more features) this will be an amazing enabler.

Angular / NativeScript support

I'm working on a mobile app in NativeScript and Angular and would love to be able to use AppSync, especially for its offline capabilities.

I've been trying to follow a tutorial for Node https://docs.aws.amazon.com/appsync/latest/devguide/building-a-client-app-javascript.html in the hopes that it could work, but haven't gotten a variety of errors and haven't gotten it working yet.

I know you have on your site that you're planning to have an Angular / Ionic demo coming soon. Any idea when that might be?

A NativeScript-specific integration would also be awesome

Fragments on unions and interfaces

There is no way to use union type or interfaces with the current client implementation as apollo requires an instance of IntrospectionFragmentMatcher passed into the InMemoryCache and there is no way to hook into the instantiation of the client at the moment.
First, I assumed that #3 might be the way to go. Yet it's not about additional links, we actually need to provide the fragmentMatcher for InMemoryCache here:

const cache = new InMemoryCache(store);

Mapping template for self referencing object

Schema:

type Query {
	getUserTwitterFeed(handle: String!): User!
}

type Tweet {
	tweet_id: String!
	tweet: String!
	retweeted: Boolean!
	retweet_count: Int
	favorited: Boolean!
	created_at: String!
}

type TweetConnection {
	items: [Tweet!]!
	nextToken: String
}

type User {
	name: String!
	handle: String!
	followers: [User]
	tweets(limit: Int, nextToken: String, keyword: String): TweetConnection
}

schema {
	query: Query
}

Data Source:

Hashkey = handle
sortKey = tweet_id
attributes : name, tweet, tweet_id, retweeted.. 

I have a mapping template associated with User and tweet given below

User:

{
    "version" : "2017-02-28",
    "operation" : "Query",
    "query" : {
        ## Provide a query expression. **
        "expression": "handle = :handle",
        "expressionValues" : {
            ":handle" : {
                "S" : "${context.arguments.handle}"
            }
        }
    }
}

tweets:

{
    "version": "2017-02-28",
    "operation": "Query",
    "query": {
        "expression": "handle = :handle",
        "expressionValues": {
            ":handle": {
                "S": "$context.source.handle"
            }
        }
    },
    "limit": #if($context.arguments.limit) $context.arguments.limit #else 10 #end,
    "nextToken": #if($context.arguments.nextToken) "$context.arguments.nextToken" #else null #end
    #if(${context.arguments.keyword})
    ,"filter": {
        "expression": "begins_with (#tweet, :keyword)",
        "expressionNames" : {
          	"#tweet" : "tweet"
        },
        "expressionValues": {
            ":keyword": { "S": "${context.arguments.keyword}" }
        }
      }
    #end
}

Note: the above filter expression is not working explained here but for this particular question lets not worry about it.

Question:
How do I handle self-referencing User in followers: [User]. Each user can have a list of followers stored as string in the database attribute for example: followers : ["sid", "gupta"]

Now when we query dynamo via GraphQL we fetch data for a given handle but each handle/user can have multiple followers and we might want to get data for each follower as well.

I am trying to look into Batch Get Item. Something like this:

{
    "RequestItems": {
        "users": {
            "Keys": [
                {
                    "handle":{"S":"sid"} -- $context.source.followers[0]
                },
                {
                    "Name":{"S":"gupta"} -- $context.source.followers[1]
                }
            ],
            "ProjectionExpression":"tweet, retweet_count"
        }
    },
    "ReturnConsumedCapacity": "TOTAL"
}

Is this kind of query supported by GraphQL? Not sure how to proceed here.

Is a JWT token for API queries mandatory if you want to use Amazon Cognito User Pool?

For the sake of example I use a simple case.
I have a blog (react-website) who is publicly accessible. Only registered user can create posts and un-registered visitors should be able to just read the blog.

For this case I like to use the Amazon Cognito User Pool as authorization type, so once the user is logged-in I can grant the user acces based on there Cognito Group for example a Editor and Admin group.

It seems I have to provide a valid JWT user token to query the API so I can't show blog posts to users who are not logged-in and registered.
Can I use the Amazon Cognito User Pool as authorization type and still read from the API when a user is not logged-in? If not what would be the recommended way to deal with this I guess quite common use-case?

Catch-22 condition with Cognito auth and setting up Vue Apollo

I'm stuck on something in my Vue app.

In src/main.js, I have this:

const client = new AWSAppSyncClient(
  {
    url: process.env.APPSYNC_API_URL,
    region: 'us-east-1',
    auth: {
      type: AUTH_TYPE.AMAZON_COGNITO_USER_POOLS,
      jwtToken: async () => await // ??? store.state.user.tokens.IdToken,
    },
  },
  {
    defaultOptions: {
      watchQuery: {
        fetchPolicy: 'cache-and-network',
      },
    },
  }
);

The catch here is that the user has to be authenticated via Cognito before I can supply the ID token, but AppSync needs to be set up immediately so the client object can be used in VueApollo and finally included in the Vue app bootstrap:

const apolloProvider = new VueApollo({
  defaultClient: client,
  defaultOptions: {
    $loadingKey: 'loadingCount',
  },
});

new Vue({
  el: '#app',
  apolloProvider,
  i18n,
  router,
  store,
  components: { App },
  template: '<App/>',
});

I can't figure out how to organize this so that once the user is authenticated via Cognito and their tokens are stored in Vuex state, only then can AppSync connect.

How to get up and running with Vue + Apollo?

If I understand correctly, instead of using Apollo's method of connecting to a GraphQL endpoint, are we to use AppSync's instead? Is there any specific way we should set up AppSync for use in a Vue project?

Local state story

Hi I'm a total noob to GraphQL, so I apologize if this is a stupid question, but I'm wondering what the story is for handling local state. I'm used to Redux, which I see is a dependency. However, I've been led to understand that with Apollo 2 comes a discouragement of using Redux alongside for state management, preferring to use https://github.com/apollographql/apollo-link-state. I see #3 exists, the solution to which may be the solution to this issue as well. However, I presume the authors of this library have already considered how users should manage local state, so I'm wondering what the recommended method is. I believe the cache is not to be used for local state, but I could be wrong.

Can't send null values in mutations

If a try to send a null value inside a mutation (though i can sand the same payload in the appsync query console). I get the following error:

Error: Network error: Cannot read property 'bucket' of null
    at new ApolloError (ApolloError.js:34)
    at Object.error (QueryManager.js:118)
    at SubscriptionObserver.error (zen-observable.js:178)
    at SubscriptionObserver.error (zen-observable.js:178)
    at new Subscription (zen-observable.js:124)
    at Observable.subscribe (zen-observable.js:233)
    at offline-link.js:84
    at new Subscription (zen-observable.js:110)
    at Observable.subscribe (zen-observable.js:233)
    at QueryManager.js:89

ES Request Template - Mutation Return Type

Schema:

type Tweet {
  tweet: String
}

type Mutation {
  # Create a tweet for a user
  createUserTweet(
    name: String!,
    screen_name: String!,
    location: String!,
    description: String!,
    followers_count: Int!,
    friends_count: Int!,
    favourites_count: Int!,
    post: String!
  ): Tweets

}

type Tweets {
  name: String!
  screen_name: String!
  location: String!
  description: String!
  followers_count: Int!
  friends_count: Int!
  favourites_count: Int!
  posts: [Tweet]
}

schema {
  mutation: Mutation
}

Request Template:

{
    "version":"2017-02-28",
    "operation":"PUT",
    "path":"/user/twitter/$util.autoId()",
    "params":{
        "body":{
            "name":"$context.arguments.name",
            "screen_name":"$context.arguments.screen_name",
            "location":"$context.arguments.location",
            "description":"$context.arguments.description",
            "followers_count":$context.arguments.followers_count,
            "friends_count":$context.arguments.friends_count,
            "favourites_count":$context.arguments.favourites_count,
            "tweet": "$context.arguments.post"
        }
    }
}

Response Template:

$utils.toJson($context.result.get("_source"))

Query:

screen shot 2018-01-21 at 2 07 14 pm

Question:

  1. Not sure why I am getting output in the first place because ES response doesn't have _source fields.

  2. Because I am getting the response (as pointed in 1) not sure why posts is null?

  3. In this mutation, all I should need is screen_name and tweet but I have to post other fields as well because I don't know a way to first GET the fields for a given screen name and then use those fields in the PUT object in response mapping as pointed in #17

any help would be great.
thanks
Sid

Possible Redux/caching error - renders AppSync unusable

I'm getting this error

Error sending the query '<name>' Error: Can't find field <name> on object (ROOT_QUERY) undefined

I'm not sure what I'm doing wrong, but I did notice appsync in the Redux store is an empty object. Seems like the cache is trying to read from something that isn't there.

Optimistic response isn't being removed on server response [React + Apollo]

I know this probably isn't the best place to put this.. but i'm using AWS-Appsync and I know there was an issue with optimistic update data not being updated with server results and stuff about a month ago.

But my issue now is.. that when I do a mutation.. the optimistic response doesn't get removed after I perform a mution.

Example: https://gyazo.com/b74d295972fac66c6bc8f5268195c22b (It's kinda zoomed out.. but what's happening is I create a new coinflip.. and the optimistic response item isn't removed from the list after the server returns the result.

Here is my code that you can look at to see if u can spot any issues:

createCoinflip = async e => {
    e.stopPropagation();
    e.preventDefault();
 
    const { createCoinflip } = this.props;
    const { side, amount } = this.state;
 
    await createCoinflip({
      amount,
      side
    });
 
    this.setState({
      amount: '',
      side: ''
    });
  };
export default compose(
  connect(mapStateToProps, mapDispatchToProps),
  graphql(ListCoinflipGames, {
    options: {
      fetchPolicy: 'cache-and-network'
    },
    props: props => ({
      coinflipGames: props.data.listActiveCoinflipGames
        ? props.data.listActiveCoinflipGames.items
        : [],
      loading: props.data.loading
    })
  }),
  graphql(CreateCoinflip, {
    options: props => ({
      update: (proxy, { data: { createCoinflip } }) => {
        const query = ListCoinflipGames;
        const data = proxy.readQuery({ query });
 
        data.listActiveCoinflipGames = {
          ...data.listActiveCoinflipGames,
          items: [
            ...data.listActiveCoinflipGames.items.filter(
              c => c.id !== createCoinflip.id
            ),
            {
              ...createCoinflip,
              player2Id: null,
              player2Username: null,
              winner: -1,
              __typename: 'CoinflipGame'
            }
          ]
        };
        proxy.writeQuery({ query, data });
      }
    }),
    props: ({ ownProps, mutate }) => ({
      createCoinflip: coinflip => {
        return mutate({
          variables: coinflip,
          optimisticResponse: {
            createCoinflip: {
              ...coinflip,
              id: Math.round(Math.random() * -1000000),
              createdAt: new Date().toISOString(),
              player1Id: ownProps.auth.id,
              player1Username: ownProps.auth.username,
              player1Side: coinflip.side,
              player2Id: null,
              player2Username: null,
              state: 'waiting',
              status: 'open',
              winner: -1,
              __typename: 'CoinflipGame'
            }
          }
        });
      }
    })
  })
)(Coinflip);

Note: The optimistic response result that I pass to the update function should be removed as soon as the updated item is received (But obviously it's not)

Hopefully someone can help me debug or lmk if there are any ongoing issues with doing optimistic updates with AWS-Appsync. I know there have been some issues previously.. but most likely it's just my code :)

How to properly wait (via Promise) on a JWT token?

I'm using AWS Cognito for authentication, and I'm really confused about how to set it up so AppSync auth waits for Cognito authorization. I have this:

const client = new AWSAppSyncClient(
  {
    url: process.env.APPSYNC_API_URL,
    region: process.env.AWS_REGION,
    auth: {
      type: AUTH_TYPE.AMAZON_COGNITO_USER_POOLS,
      jwtToken: () => store.state.auth.user.idToken,
    },
  },
  {
    defaultOptions: {
      watchQuery: {
        fetchPolicy: 'cache-and-network',
      },
    },
  }
);

This works in most cases, but sometimes there's a race condition when the AppSync client is ready but the Vuex store doesn't have the token just yet. Any advice?

Complex objects - how to use `localUri` to upload a file?

In the complex object documentation, I see that we're supposed to create an object including a string property localUri that points to the file, and this is how AppSync will upload to S3.

The problem is, I don't understand how this could possibly work. First, when using HTML <input type="file">, the local file path is not provided when the user chooses a file (for security reasons). Secondly, the data used to upload a file is a File blob, which one can use FileReader to read the binary data and send to S3 in the Body param. But localUri doesn't accept a File blob.

So I'm confused as to how this works, exactly.

Method to refresh credentials for AWSAppSyncClient

I'm using this library in tandem with aws-amplify and Cognito User Pools / Identity Pools, with the Identity Pool setting IAM credentials. Since the changes in #5, my constructor looks like:

import { Auth } from 'aws-amplify';
import AWSAppSyncClient from 'aws-appsync';
import { AUTH_TYPE } from 'aws-appsync/lib/link/auth-link';
import config from 'config';

const appSyncClient = new AWSAppSyncClient({
      url: config.appsync.URL,
      region: config.appsync.REGION,
      auth: {
        type: AUTH_TYPE.AWS_IAM,
        credentials: () => Auth.currentCredentials() }
    });

An issue that I've encountered is that if a user leaves the browser session open for an hour, the credentials will have expired, and any graphql queries will fail. My solution to this right now (in a React app) is to schedule a refresh when the component mounts via setInterval, like this:

class Home extends Component {
  state = {
    appSyncClient: null,
    timer: null
  }

  setAppSyncClient = () => {
    logger.debug("Creating AppSyncClient");
    const appSyncClient = new AWSAppSyncClient({
      url: config.appsync.URL,
      region: config.appsync.REGION,
      auth: {
        type: AUTH_TYPE.AWS_IAM,
        credentials: () => Auth.currentCredentials() }
    });
    this.setState({appSyncClient})
  }

  componentWillMount = () => {
    this.setAppSyncClient();
    const interval = 50 * 60 * 1000 // 50 minutes
    const timer = setInterval(this.setAppSyncClient, interval);
    this.setState({timer});
  }

  componentWillUnmount = () => {
    clearInterval(this.state.timer);
  }

  render() {
    const {appSyncClient} = this.state;
    if (!appSyncClient) {
      return null
    } else {
      return (
        <ApolloProvider client={appSyncClient}>
          <Rehydrated>
            {/* the rest of my app */}
          </Rehydrated>
        </ApolloProvider>
      )
    }
  }
}

This feels clunky to me --- given that the AWSAppSyncClient can take a function for credentials, it seems like it'd be nice if the refresh trigger could be internal to the client. The Auth class in aws-amplify should ensure that invoking Auth.currentCredentials() refreshes the credentials if they're due to expire, so it seems like all that's required is just something to re-invoke that function. I can see an argument in favor of that being an application concern that this lib shouldn't need to worry about, but it also seems like using short-lived credentials/tokens is likely to be a common concern of consumers of the lib.

Rehydrated should not return <div className> and use something like render(child) prop instead

Now Rehydrate in Rehydrated return div element, what user doesn't expect, because purpose of Rehydrated not layout/styles, but data hydration, same as ApolloProvider doesn't insert any markup. As result Rehydrated breaks layout. This is of course could be fixed by adding styles to 'awsappsync', but this not what lib user wish I think.

Second issue is fixed <span>Loading...</span> that also breaks layout and can't be customized.

Solution can be simple and known as render prop . Rehydrated can take render prop or child ({isRehydrated}) => isRehydrated ? <App/> : <CustomLoading/>

How to create service role using cloudformation

I know appsync currently doesn't support CF which is OK. I wrote code using JS SDK's to create appsync automatically here

When I tried to create service role using CF I still got unauthorized access (i.e appsync couldn't query data sources). Any idea how I can automate creating roles for my data sources because its tedious to create the role using UI and then hardcode it in JS code.

REST wrapper

Is there a way to designate a portion of calls to be wrapped?

I'm trying to get rid of redux entirely (not completely considering the project uses redux-offline), but ideally it would be nice to run Apollo for everything ui. If I have a one off REST call, can I wrap it somehow with something along the lines of https://github.com/apollographql/apollo-link-rest/ without having to create another client and or link?

Mutation with OptimisticResponse doesn't include server response data in second Update.

When using a mutation with an optimisticResponse, the secondary call to the update function contains data from the optimistic response rather than data from the server. This forces the need to use refetchQueries. I have more details and code example in this Stack Overflow thread.

Notice that the console.log kicks out "version: 4" both times while the server responds with "version: 5"

Mutation:

  graphql(MutationUpdateChild, {
    options: {
      // refetchQueries: [{ query: QueryAllChildren }],
      update: (proxy, {data: {updateChild}}) => {
        const query = QueryAllChildren;
        const data = proxy.readQuery({query});
        console.log("Child Version: ", updateChild.version);
        
        // find item to update
        const updateIndex = data.listChildren.items.find(
          child => child.id === updateChild.id
        );

        // splice in update
        data.listChildren.items.splice(updateIndex, 1, updateChild);
        proxy.writeQuery({query, data});
      }
    },
    props: props => ({
      updateChild: child => {
        return props.mutate({
          variables: {...child, expectedVersion: child.version},
          optimisticResponse: () => ({
            updateChild: {
              ...child,
              __typename: "Child"
            }
          })
        });
      }
    })
  }),

Console Output:

Child Version:  4
Child Version:  4

Server Response:

{
  "data": {
    "updateChild": {
      "id": "b6cece1e-b2bf-4d39-9e4e-96f7ac7db3ed",
      "name": "01",
      "description": "01",
      "isActive": true,
      "version": 5,
      "lastModified": "1519531185173",
      "created": "1519527714099",
      "__typename": "Child"
    }
  }
}

Mutation occurring twice despite being called only once when optimisticResponse is not specified

I have a fairly simple React object, and I'm trying to pass it as parameters to a GraphQL mutation (apologies if my terminology is flawed; I'm new to GraphQL).

The following is how I'm using the compose and graphql HOCs on my component CompanyList. In my code, I'm calling the prop createUser one time.

export default compose(
  graphql(MutationCreateCompany, {
    // ...
  }),
  graphql(QueryAllCompanies, {
    // ...
  }),
  graphql(MutationCreateUser, {
    props: props => ({
      createUser: user => {
        console.log('mockingbird');
        return props.mutate({
          variables: user,
          /*optimisticResponse: () => ({
            createUser: { ...user, __typename: 'User' },
          }),*/
        });
      },
    }),
  }),
)(CompanyList);
import gql from 'graphql-tag';

export const MutationCreateUser = gql`
  mutation PutUserMutation(
    $username: String
    $email: String!
    $name: String
    $title: String
  ) {
    putUser(username: $username, email: $email, name: $name, title: $title) {
      username
      email
    }
  }
`;

Intended outcome:

Only one POST request would be made with my mutation.

Actual outcome:

Two POST requests are being made with identical mutations at the same time.

The word "mockingbird" is printed only once.

Only one POST request is made if I provide an optimisticResponse option (if I uncomment it from above)

IAM policy for IAM mode?

Hi there,

I am trying to get IAM authentication to work.

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"appsync:GraphQL"
],
"Resource": [
"arn:aws:appsync:us-east-1:xxxxxxxx:/apis/YourAppName/*"
]
}
]
}

I'm struggling to see where I can easily find the ARN for the graphql endpoint. I'm trying it as follows:

xxxxxxxxxx - the AWS account id (xxxx-xxxx-xxxx)

Then the YourAppName - is that the name of the AppSync endpoint in the console, or is it it the https://xxxxxxx.appsync-api.us-east-1.amazonaws.com/graphql part in the endpoint URL?

Thanks

Passing a federated identity into AWSAppSyncClient?

Here's what im trying to do essentially in React.

  1. User logs in via a login mutation with appsync and is returned an identityId and JWT token from the response.
const cognitoCredentials = new AWS.CognitoIdentityCredentials({
  IdentityPoolId: 'IdentityPool goes here',
  IdentityId: 'IdentityId here',
  Logins: {
     'cognito-identity.amazonaws.com': 'JWT Token here'
  }
});
  1. I pass the credentials with the users identityId And JWT token to Appsync to authenticate them..
const client = new AWSAppSyncClient({
  url: appSyncConfig.graphqlEndpoint,
  region: appSyncConfig.region,
  auth: {
    type: AUTH_TYPE.AWS_IAM,
    credentials: cognitoCredentials
  }
});

My question is... is this the approach I should be taking or is there an easier way of doing this? I don't want to use user pools because it will end up being too expensive for me; so i'm going the federated identity approach. I will create a user...and save them as a federated identity using their database user id.

Sorry if this is the wrong section, but maybe someone knows!

Implementing pagination with DynamoDB

I've decided to make a pagination in my app. But I've troubles with the backward paginating.

My resolvers are connected to the DynamoDB. When you want to get the previous "page" of your data you will need specify "scanIndexForward": false in your query to switch scan direction throw the data table. This attribute works only with "Query" operation not with "Scan". And the troubles become here because in "expression" attribute you'll need set expression with the following condition:

The partition key equality test is required, and must be specified in the following format:

partitionKeyName = :partitionkeyval

If you also want to provide a condition for the sort key, it must be combined using AND with the condition for the sort key. Following is an example, using the = comparison operator for the sort key:

partitionKeyName = :partitionkeyval AND sortKeyName = :sortkeyval

as says DynamoDB reference. So when I'm specifying partitionKeyName = :partitionkeyval I'll getting only one item and I can't get multiple items because of "=" operator in "expression". Operation "Scan" returns multiple items but it doesn't support "scanIndexForward" option.

Am I doing all right? Or is there another way to implement prev pagination?

And is it possible to implement pagination with the numbered pages not only previous/next ones?

Warning: Failed context type: Invalid context `client` of type `AWSAppSyncClient` supplied to `Rehydrated`, expected instance of `AWSAppSyncClient`

with new upgrade in packages I get warning

index.js:2178 Warning: Failed context type: Invalid context `client` of type `AWSAppSyncClient` supplied to `Rehydrated`, expected instance of `AWSAppSyncClient`.
    in Rehydrated (at App.js:36)
    in QueryRecyclerProvider (created by ApolloProvider)
    in ApolloProvider (at App.js:35)
    in Router (created by BrowserRouter)
    in BrowserRouter (at App.js:34)
    in WithProvider (created by _class)
    in div (created by _class)
    in _class (at index.js:7)

Pagination with Filtering in DynamoDB Template

Schema:

type Query {
	getUserTwitterFeed(handle: String!): User
}

type Tweet {
	tweet_id: String!
	tweet: String!
	retweeted: Boolean!
	retweet_count: Int
	favorited: Boolean!
}

type TweetConnection {
	items: [Tweet]
	nextToken: String
}

type User {
	name: String!
	handle: String!
	location: String!
	description: String!
	followers_count: Int!
	friends_count: Int!
	favourites_count: Int!
	followers: [String]
	tweets(limit: Int, nextToken: String): TweetConnection
}

schema {
	query: Query
}

Request Mapping Template:

{
    "version": "2017-02-28",
    "operation": "Query",
    "query": {
        "expression": "handle = :handle",
        "expressionValues": {
            ":handle": {
                "S": "$context.source.handle"
            }
        }
    },    
    "limit": #if($context.arguments.limit) $context.arguments.limit #else 10 #end,
    "nextToken": #if($context.arguments.nextToken) "$context.arguments.nextToken" #else null #end
    #if(${context.arguments.keyword})
    ,"filter": {
        "expression": "contains (#tweet, :keyword)",
        "expressionNames" : {
          	"#tweet" : "tweet"
        },        
        "expressionValues": {
            ":keyword": { "S": "${context.arguments.keyword}" }
        }
      }
    #end   
}

Response Mapping Template:

{
    "items": $util.toJson($context.result.items),
    "nextToken": $util.toJson($context.result.nextToken)
}

Query:

screen shot 2018-01-21 at 6 38 24 pm

I am trying to write a mapping template to paginate tweets with a given filter.
My dynamodb table has simple structure: hash key = handle, sort key = tweet_id

When I don't pass keyword parameter, everything runs fine. But now I added another condition to the mapping template i.e if the keyword is an input then filter tweets by keyword. But I see weird error in the above screenshot. Took reference to add Query Filters from here

Any idea what is happening? Thanks!

Intermittent error retrieving data on a physical phone

Seeing a weird problem that should be easy to repro using the AppSync React starter app.

  1. Create the AppSync API using the sample schema and API key auth
  2. Download the AppSync.json file from the AppSync console
  3. Clone the https://github.com/aws-samples/aws-mobile-appsync-events-starter-react repo
  4. Drop in AppSync.json in the /src folder
  5. npm install
  6. Deploy to an S3 folder with a CloudFront distro. I'm doing this via the AWS CLI like so:
npm run build && aws s3 sync build/ s3://<BUCKET> && aws s3 cp --cache-control max-age=0 ./build/index.html s3://<BUCKET> && aws s3 cp --cache-control max-age=0 ./build/service-worker.js s3://<BUCKET>
  1. On a computer, add a couple of events using the CloudFront URL. Refresh, clear browser cache, etc and things work like a charm.
  2. On a physical phone try the same thing. You should intermittently see app crashes where no data will appear (no other clues on the console). I'm also able to repro via the XCode simulator in private browsing mode.

I posted this pull request with some updates to the AppSync client to get more clues via the Apollo Error Link.

#21

After those changes are in, I'm able to trigger the following error via a combination of browser refreshes and clearing the browser cache on the phone.
screen shot 2018-01-22 at 8 01 18 pm

My guess is that this issue is related to what's described in this Apollo client bug.

apollographql/apollo-client#1804

Specifically this statement caught my attention:

If any of the data that readStore tries to read is not there, the function will throw an error

Forgive me as I'm still learning about how the local cache works, but my wild guess is since this is happening intermittently is that there's some kind of race condition where the local cache isn't initialized properly which results in an error being thrown.

Am more than happy to try to help, but would really appreciate any guidance or ideas on things to try.

Appsync Authentication - AWS Cognito User Pools with AWS Amplify

To pass the jwtToken in auth object https://www.youtube.com/watch?v=vAjf3lyjf8c&feature=youtu.be&t=3410 talks about passing it like:

screen shot 2018-01-06 at 1 48 07 pm

but Auth.currentSession returns a promise (not sure if above approach is correct) so I followed this approach:

screen shot 2018-01-06 at 1 49 09 pm

Now jwtToken is successfully generated but client auth object doesn't wait for credentials to get generated.

What is the right way to generate token on the client side for cognito user pools?

Reference Implementation: serverless/serverless-graphql#227

Amplify.configure({
  Auth: {
    region: process.env.REACT_APP_AWS_AUTH_REGION, // REQUIRED - Amazon Cognito Region
    userPoolId: process.env.REACT_APP_USER_POOL_ID, //OPTIONAL - Amazon Cognito User Pool ID
    userPoolWebClientId: process.env.REACT_APP_CLIENT_APP_ID, //User Pool App Client ID
  },
});

const getCreds = async () => {
  return await Auth.currentSession()
    .then(data => {
      return data.idToken.jwtToken;
    })
    .catch(err => console.log(err));
};

const client = new AWSAppSyncClient({
  url: process.env.REACT_APP_GRAPHQL_ENDPOINT,
  region: process.env.REACT_APP_AWS_CLIENT_REGION,
  auth: {
    type: AUTH_TYPE.AMAZON_COGNITO_USER_POOLS,
    jwtToken: getCreds(),
  },
});```
  

Better example for handling complex objects

Hey

I am trying to understand how to use complex objects as described here.

Is there a better example to show how to write mapping templates for complex objects? For instance, let's say user profile info (picture, video etc) is stored in S3 bucket and I want to Query that info using GraphQL endpoint, how would I do that.

thanks
Sid

Need more details on how complex objects work

Regarding:
https://docs.aws.amazon.com/appsync/latest/devguide/building-a-client-app-react.html#complex-objects

How does this work exactly? I don't understand what value localUri should be. When you select files to upload, FileList contains a list of File objects, which do not expose the full path of the file, just the names. It would make more sense to pass the File object to AWS AppSync which would then use FileReader to get an array buffer to pass to s3.upload.

Could someone give more details on how this works? Thanks!

TypeScript definitions for aws-appsync

It would be really helpful to have Typescript definitions for aws-appsync. There seem to be three options.

  1. Maintain them separately in DefinitelyTyped.
  2. Manually maintain them (I'll submit a PR to do this for now)
  3. Convert the package to TypeScript and automatically generate them. This might be an option because TypeScript is already being used to compile the JavaScript so the tooling is in place.

Log intermediate outputs of mapping templates

Is there way for better logging to debug mapping templates?

I am finding it hard to debug errors in mapping template.

For example:

I am trying to debug the following mapping template. Each tweet record is a document in ES. I want to search all tweets by a given keyword.

Data:

screen shot 2018-01-20 at 3 09 39 pm

Schema:

type Query {
	searchAllTweets(keyword: String!): [Tweets]
}

type Tweet {
	tweet: String
}

type Tweets {
	screen_name: String!
	posts: [Tweet]
}

schema {
	query: Query
}

Request Mapping Template:

    "version":"2017-02-28",
    "operation":"GET",
    "path":"/user/twitter/_search",
    "params":{
        "body":{
            "from":0,
            "size":50,
            "query" : {
                "bool" : {
                    "must" : [
                        {"match" : { "tweet" : "$context.arguments.keyword" }}
                    ]
                }
            }
        }
    }
}

Response Mapping Template:

[
  #set($hitcount = $context.result.hits.total)
    #set($tweetMap = {})
    #set($result = [])
  #if($hitcount > 0)
      #foreach($entry in $context.result.hits.hits)
      	#if($tweetMap.get("$entry.get('_source')['screen_name']"))
        	#set($tweetList = $tweetMap.get("$entry.get('_source')['screen_name']"))
        	#set($element = ${tweetList.add({ "tweet" : "$entry.get('_source')['tweet']" })})
            #set($tweetMap["$entry.get('_source')['screen_name']"] = {
            	"screen_name" : "$entry.get('_source')['screen_name']",
                "posts" : $tweetList
            }
            )
        #else
        	#set($tweetList = [])
            #set($element = ${tweetList.add({ "tweet" : "$entry.get('_source')['tweet']" })})
            #set($tweetMap["$entry.get('_source')['screen_name']"] = {
            	"screen_name" : "$entry.get('_source')['screen_name']",
                "posts" : $tweetList
            }
            )
        #end    
      #end
	  #foreach( $key in $tweetMap.keySet() )
    	$tweetMap.get($key)
	  #end
  #end
]

Output:

screen shot 2018-01-20 at 3 07 59 pm

Multiple Operations in the same appsync request template

Something I have been trying to figure out:

Is it possible to do multiple dynamodb or ES operations in the same request template?

For example:

Use Case 1: I want to fetch the key from dynamodb table A and then get value on this key from dynamodb table B.

Use Case 2: I want to first GET data from ES, parse the output and use it to PUT item.

Not sure if appsync supports such use cases

Pagination with Lambda example

It will be EXTREMELY helpful to get an example on how to do pagination using Lambda integration.

One use case can be Lambda integrates with REST API or any other backend which handles pagination (which is easier because you can then have your backend handle pagination) but would be great to see how to handle pagination using appsync when backend doesn't have built-in support for the same.

thanks
Sid

cleanup cache after user logs out

trying to figure out how can we clean up cached data at client end once user logs out (aws-amplify).

I found that if the fetchPolicy is cache and network and multiple users log-in then the data at the client end remains static (from the cache). One solution is to use 'network only' but not the best solution I guess?

Maybe some RehydratedProps member types are wrong

Current RehydratedProps definition is below.

export interface RehydratedProps {
    render?: ((props: { rehydrated: boolean }) => React.ReactNode);
    children?: React.ReactChildren;
    loading?: React.ComponentType<any>;
    style?: any;
}

But Rehydrated.propTypes defines each as PropTypes.node.
Also, render () returns the passed values ​​of children and loading.
Since render () expects ReactNode to be returned, it does not work as expected with the type defined by PropTypes.
If implementation is correct,
I think children and loading type should be React.ReactNode.

IAM auth isn't working?

I've been able to get the API key and Cognito user pool auth working without any issues. Please note that I'm using a Cognito identity pool for sign in via the aws-amplify package.

I switched the AppSync configuration (in the AWS AppSync console and in the aws-appsync client config) to IAM auth and am hitting issues.

I am going this route given that federated identities like Facebook don't seem to work with the Cognito user pool auth method since there's no jwt.

Here are the problems that I'm seeing:

  1. Mutation error
Error: Can't find field listEvents on object (ROOT_QUERY) undefined.
    at readStoreResolver (http://localhost:3000/static/js/bundle.js:6315:19)
    at executeField (http://localhost:3000/static/js/bundle.js:56196:18)
    at http://localhost:3000/static/js/bundle.js:56153:31
    at Array.forEach (<anonymous>)
    at executeSelectionSet (http://localhost:3000/static/js/bundle.js:56148:29)
    at graphql (http://localhost:3000/static/js/bundle.js:56143:12)
    at diffQueryAgainstStore (http://localhost:3000/static/js/bundle.js:6347:91)
    at readQueryFromStore (http://localhost:3000/static/js/bundle.js:6290:12)
    at MyCache../node_modules/apollo-cache-inmemory/lib/inMemoryCache.js.InMemoryCache.read (http://localhost:3000/static/js/bundle.js:6033:98)
    at MyCache../node_modules/apollo-cache-inmemory/lib/inMemoryCache.js.InMemoryCache.readQuery (http://localhost:3000/static/js/bundle.js:6123:21)
    at update (http://localhost:3000/static/js/bundle.js:139224:30)
    at http://localhost:3000/static/js/bundle.js:8787:122
    at tryFunctionOrLogError (http://localhost:3000/static/js/bundle.js:10581:16)
    at http://localhost:3000/static/js/bundle.js:8787:100
    at MyCache../node_modules/apollo-cache-inmemory/lib/inMemoryCache.js.InMemoryCache.performTransaction (http://localhost:3000/static/js/bundle.js:6093:9)
    at DataStore../node_modules/apollo-client/data/store.js.DataStore.markMutationResult (http://localhost:3000/static/js/bundle.js:8786:28)
    at changeFn_1 (http://localhost:3000/static/js/bundle.js:8719:23)
    at http://localhost:3000/static/js/bundle.js:8732:21
    at MyCache../node_modules/apollo-cache-inmemory/lib/inMemoryCache.js.InMemoryCache.performTransaction (http://localhost:3000/static/js/bundle.js:6093:9)
    at http://localhost:3000/static/js/bundle.js:6105:19
    at RecordingCache../node_modules/apollo-cache-inmemory/lib/recordingCache.js.RecordingCache.record (http://localhost:3000/static/js/bundle.js:6443:9)
    at record (http://localhost:3000/static/js/bundle.js:6479:27)
    at MyCache../node_modules/apollo-cache-inmemory/lib/inMemoryCache.js.InMemoryCache.recordOptimisticTransaction (http://localhost:3000/static/js/bundle.js:6102:92)
    at DataStore../node_modules/apollo-client/data/store.js.DataStore.markMutationInit (http://localhost:3000/static/js/bundle.js:8728:24)
    at QueryManager../node_modules/apollo-client/core/QueryManager.js.QueryManager.mutate (http://localhost:3000/static/js/bundle.js:7732:24)
    at AWSAppSyncClient../node_modules/apollo-client/ApolloClient.js.ApolloClient.mutate (http://localhost:3000/static/js/bundle.js:7174:34)
    at AWSAppSyncClient../node_modules/aws-appsync/lib/client.js.AWSAppSyncClient.mutate (http://localhost:3000/static/js/bundle.js:22593:40)
    at GraphQL.dataForChildViaMutation (http://localhost:3000/static/js/bundle.js:97593:51)
    at createEvent (http://localhost:3000/static/js/bundle.js:139234:31)
    at NewEvent._callee$ (http://localhost:3000/static/js/bundle.js:138978:40)
    at tryCatch (http://localhost:3000/static/js/bundle.js:127527:40)
    at Generator.invoke [as _invoke] (http://localhost:3000/static/js/bundle.js:127761:22)
    at Generator.prototype.(anonymous function) [as next] (http://localhost:3000/static/js/bundle.js:127579:21)
    at step (http://localhost:3000/static/js/bundle.js:138920:191)
    at http://localhost:3000/static/js/bundle.js:138920:437
    at new Promise (<anonymous>)
    at http://localhost:3000/static/js/bundle.js:138920:99
    at http://localhost:3000/static/js/bundle.js:138993:30
    at HTMLUnknownElement.callCallback (http://localhost:3000/static/js/bundle.js:101391:14)
    at Object.invokeGuardedCallbackDev (http://localhost:3000/static/js/bundle.js:101430:16)
    at Object.invokeGuardedCallback (http://localhost:3000/static/js/bundle.js:101287:27)
    at Object.invokeGuardedCallbackAndCatchFirstError (http://localhost:3000/static/js/bundle.js:101301:43)
    at executeDispatch (http://localhost:3000/static/js/bundle.js:101685:19)
    at executeDispatchesInOrder (http://localhost:3000/static/js/bundle.js:101707:5)
    at executeDispatchesAndRelease (http://localhost:3000/static/js/bundle.js:101805:5)
    at executeDispatchesAndReleaseTopLevel (http://localhost:3000/static/js/bundle.js:101816:10)
    at Array.forEach (<anonymous>)
    at forEachAccumulated (http://localhost:3000/static/js/bundle.js:101784:9)
    at processEventQueue (http://localhost:3000/static/js/bundle.js:101961:5)
    at runEventQueueInBatch (http://localhost:3000/static/js/bundle.js:104456:3)
  1. Subscription error
auth-link.js:114 Uncaught (in promise) TypeError: Cannot read property 'getPromise' of undefined
    at Object.<anonymous> (auth-link.js:114)
    at step (auth-link.js:50)
    at Object.next (auth-link.js:31)
    at auth-link.js:25
    at new Promise (<anonymous>)
    at ./node_modules/aws-appsync/lib/link/auth-link.js.__awaiter (auth-link.js:21)
    at iamBasedAuth (auth-link.js:107)
    at auth-link.js:138
    at new Subscription (zen-observable.js:103)
    at Observable.subscribe (zen-observable.js:229)
    at complex-object-link.js:74
    at <anonymous>

I've tried combing through various docs and looking at the code, however, a little uncertain about what's going sideways. Would really appreciate any guidance you can provide to try to chase it down.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.