1.7.3 • Published 5 years ago

reams-aws-appsync v1.7.3

Weekly downloads
25
License
SEE LICENSE IN LI...
Repository
github
Last release
5 years ago

AWS AppSync

AWS AppSync JavaScript SDK

This SDK can be used with the Apollo JavaScript client found here. Please log questions for this client SDK in this repo and questions for the AppSync service in the official AWS AppSync forum.

lerna npm

packageversion
reams-aws-appsyncnpm
reams-aws-appsync-reactnpm

Installation

npm

npm install --save reams-aws-appsync

yarn

yarn add reams-aws-appsync

Why is it needed?

When user is offline and mutations are happened, the aws-appsync package stores snapshot to local storage. And that snapshot is restored when user becomes online. This makes users confused about their works and they can think that they lost their works.

How to update it?

aws-appsync is maintained by lerna so we should create our own package to use updated aws-appsync. We called it reams-aws-appsync This repo will be removed when aws-appsync fixed above issue.

What we have changed?

We have disabled the restoring snapshot code Disable Snapshot Restore

Usage

Please visit the documentation with the Amplify Framework for detailed instructions.

React / React Native

Vue
Angular
Creating a new AWS AppSync API
License

React / React Native

For more documentation on graphql operations performed by React Apollo click here.

Creating a client

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

import App from './App'

const client = new AWSAppSyncClient({
  url: AppSyncConfig.graphqlEndpoint,
  region: AppSyncConfig.region,
  auth: {
    type: AppSyncConfig.authenticationType,
    apiKey: AppSyncConfig.apiKey,
    // jwtToken: async () => token, // Required when you use Cognito UserPools OR OpenID Connect. token object is obtained previously
  }
})

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={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 'reams-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={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={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={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={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)

Offline configuration

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

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
    }
  },
}

Angular / Ionic examples coming soon

Creating an AppSync Project

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

License

This library is licensed under the Amazon Software License.