From 4b9d774316d4274d32bf46b462df5367672e64ab Mon Sep 17 00:00:00 2001 From: Manuel Iglesias Date: Mon, 5 Feb 2018 15:37:21 -0800 Subject: [PATCH] Fix outbox processing (#34) * Change outbox logic * Refactor * Handle missing optimisticResponse * Fix typo. Add comment * Add changelog entry --- packages/aws-appsync/CHANGELOG.md | 5 +- .../aws-appsync/src/cache/offline-cache.js | 7 +- packages/aws-appsync/src/client.js | 40 +++-- packages/aws-appsync/src/link/offline-link.js | 138 +++++++++--------- 4 files changed, 98 insertions(+), 92 deletions(-) diff --git a/packages/aws-appsync/CHANGELOG.md b/packages/aws-appsync/CHANGELOG.md index 812d3b50..dbbaa596 100644 --- a/packages/aws-appsync/CHANGELOG.md +++ b/packages/aws-appsync/CHANGELOG.md @@ -1,4 +1,7 @@ # Changelog +### vNext +- Handle missing optimisticResponse [PR#34](https://github.com/awslabs/aws-mobile-appsync-sdk-js/pull/34) + ### 1.0.7 -- Make offline support optional [PR#33](https://github.com/awslabs/aws-mobile-appsync-sdk-js/pull/33) \ No newline at end of file +- Make offline support optional [PR#33](https://github.com/awslabs/aws-mobile-appsync-sdk-js/pull/33) diff --git a/packages/aws-appsync/src/cache/offline-cache.js b/packages/aws-appsync/src/cache/offline-cache.js index 9622b63d..04449549 100644 --- a/packages/aws-appsync/src/cache/offline-cache.js +++ b/packages/aws-appsync/src/cache/offline-cache.js @@ -48,6 +48,12 @@ export default class MyCache extends InMemoryCache { this.store.dispatch(writeThunk(data)); } + + reset() { + this.store.dispatch(writeThunk({})); + + return super.reset(); + } }; const writeThunk = (payload) => (dispatch) => { @@ -63,7 +69,6 @@ export const reducer = () => ({ switch (type) { case WRITE_ACTION: return { - ...state, ...normCache }; default: diff --git a/packages/aws-appsync/src/client.js b/packages/aws-appsync/src/client.js index 8259627c..888f8d8c 100644 --- a/packages/aws-appsync/src/client.js +++ b/packages/aws-appsync/src/client.js @@ -91,37 +91,31 @@ class AWSAppSyncClient extends ApolloClient { * @param {MutationOptions} options * @returns {Promise} */ - mutate(options, extraOpts = {}) { - const { mutation, variables: mutationVariables, optimisticResponse, context: origContext = {} } = options; - const { AASContext: { ...origASAContext = {} } = {} } = origContext; - - const operationDefinition = getOperationDefinition(mutation); - const varsInOperation = variablesInOperation(operationDefinition); - const variables = Array.from(varsInOperation).reduce((obj, key) => { - obj[key] = mutationVariables[key]; - return obj; - }, {}); - - // refetchQueries left out intentionally when !doIt so we don't run them twice - const { refetchQueries, ...otherOptions } = options; - const { doIt } = origASAContext; + mutate(options) { + const { refetchQueries, context: origContext = {}, ...otherOptions } = options; + const { AASContext: { doIt = false, ...restAASContext } = {} } = origContext; const context = { ...origContext, AASContext: { - ...origASAContext, - mutation, - variables, - optimisticResponse, - refetchQueries, - }, + doIt, + ...restAASContext, + ...(!doIt ? { refetchQueries } : {}), + } }; - return super.mutate({ + const { optimisticResponse, variables } = otherOptions; + const data = optimisticResponse && + (typeof optimisticResponse === 'function' ? { ...optimisticResponse(variables) } : optimisticResponse); + + const newOptions = { ...otherOptions, - refetchQueries: doIt ? refetchQueries : undefined, + optimisticResponse: data, + ...(doIt ? { refetchQueries } : {}), context, - }); + } + + return super.mutate(newOptions); } }; diff --git a/packages/aws-appsync/src/link/offline-link.js b/packages/aws-appsync/src/link/offline-link.js index cb28abe0..ebebfe19 100644 --- a/packages/aws-appsync/src/link/offline-link.js +++ b/packages/aws-appsync/src/link/offline-link.js @@ -7,9 +7,9 @@ * KIND, express or implied. See the License for the specific language governing permissions and limitations under the License. */ import { readQueryFromStore, defaultNormalizedCacheFactory } from "apollo-cache-inmemory"; -import { ApolloLink, Observable } from "apollo-link"; +import { ApolloLink, Observable, Operation } from "apollo-link"; import { getOperationDefinition, getOperationName } from "apollo-utilities"; -import { Store } from 'redux'; +import { Store } from "redux"; import { NORMALIZED_CACHE_KEY } from "../cache"; @@ -46,17 +46,25 @@ export class OfflineLink extends ApolloLink { } if (isMutation) { - const data = processMutation(operation, this.store); + const { cache, optimisticResponse, AASContext: { doIt = false } = {} } = operation.getContext(); - // If we got data, it is the optimisticResponse, we send it to the observer - // Otherwise, we allow the mutation to continue in the link chain - if (data) { - observer.next({ data }); - observer.complete(); + if (!doIt) { + if (!optimisticResponse) { + console.warn('An optimisticResponse was not provided, it is required when using offline capabilities.'); - return () => null; - } else { - // console.log('Processing mutation'); + if (!online) { + throw new Error('Missing optimisticResponse while offline.'); + } + + // offline muation without optimistic response is processed immediately + } else { + const data = enqueueMutation(operation, this.store, observer); + + observer.next({ data }); + observer.complete(); + + return () => null; + } } } @@ -73,6 +81,11 @@ export class OfflineLink extends ApolloLink { } } +/** + * + * @param {Operation} operation + * @param {Store} theStore + */ const processOfflineQuery = (operation, theStore) => { const { [NORMALIZED_CACHE_KEY]: normalizedCache = {} } = theStore.getState(); const { query, variables } = operation; @@ -88,39 +101,56 @@ const processOfflineQuery = (operation, theStore) => { return data; } -const processMutation = (operation, theStore) => { - const { AASContext } = operation.getContext(); - const { mutation, variables, optimisticResponse, refetchQueries, doIt } = AASContext; - - if (doIt) { - return; - } - - const data = optimisticResponse ? - typeof optimisticResponse === 'function' ? - { ...optimisticResponse(variables) } : - optimisticResponse - : null; - - // console.log('Queuing mutation'); - theStore.dispatch({ - type: 'SOME_ACTION', - payload: {}, - meta: { - offline: { - effect: { - mutation, - variables, - refetchQueries, - doIt: true, - }, - commit: { type: 'SOME_ACTION_COMMIT', meta: null }, - rollback: { type: 'SOME_ACTION_ROLLBACK', meta: null }, +/** + * + * @param {Operation} operation + * @param {Store} theStore + */ +const enqueueMutation = (operation, theStore, observer) => { + const { query: mutation, variables, update } = operation; + const { cache, optimisticResponse, AASContext: { doIt = false, refetchQueries } = {} } = operation.getContext(); + + setImmediate(() => { + theStore.dispatch({ + type: 'SOME_ACTION', + payload: {}, + meta: { + offline: { + effect: { + mutation, + variables, + refetchQueries, + update, + optimisticResponse, + }, + commit: { type: 'SOME_ACTION_COMMIT', meta: null }, + rollback: { type: 'SOME_ACTION_ROLLBACK', meta: null }, + } } - } + }); }); - return data; + return optimisticResponse; +} + +/** + * + * @param {*} client + * @param {*} effect + * @param {*} action + */ +export const offlineEffect = (client, effect, action) => { + const doIt = true; + const { ...otherOptions } = effect; + + const context = { AASContext: { doIt } }; + + const options = { + ...otherOptions, + context, + }; + + return client.mutate(options); } export const reducer = () => ({ @@ -145,32 +175,6 @@ export const reducer = () => ({ } }); -/** - * - * @param {*} client - * @param {*} effect - * @param {*} action - */ -export const offlineEffect = (client, effect, action) => { - const { type } = action; - const { mutation, variables, refetchQueries, doIt } = effect; - - const context = { - AASContext: { - doIt, - }, - }; - - const options = { - mutation, - variables, - refetchQueries, - context, - }; - - return client.mutate(options); -} - export const discard = (fn = () => null) => (error, action, retries) => { const { graphQLErrors = [] } = error; const conditionalCheck = graphQLErrors.find(err => err.errorType === 'DynamoDB:ConditionalCheckFailedException');