Skip to content

Commit

Permalink
fix(triggers): trigger updates and invaludations even on mutation error
Browse files Browse the repository at this point in the history
mutations may throw an error even after mutating the remote state, so
allow users to trigger invalidations/updates even on error
  • Loading branch information
uladkasach committed Dec 29, 2022
1 parent 90f673c commit b77d127
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 14 deletions.
39 changes: 37 additions & 2 deletions src/RemoteStateQueryCachingOptions.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
import { WithSimpleCachingOptions } from 'with-simple-caching';
import { MutationWithRemoteStateRegistration } from './createRemoteStateCachingContext';

export enum MutationExecutionStatus {
/**
* the mutation successfully executed and resolved a value
*/
RESOLVED = 'RESOLVED',

/**
* the mutation threw an error and rejected
*/
REJECTED = 'REJECTED',
}

/**
* an invalidation trigger for the cache of a remote-state-query
* - allows the user to specify which keys become invalid when a specific mutation fires
Expand All @@ -24,8 +36,24 @@ export interface RemoteStateQueryInvalidationTrigger<Q extends (...args: any) =>
* - provides the list of all currently cached query strings
*/
affects: (args: {
/**
* the input the triggering mutation was invoked with
*/
mutationInput: Parameters<M>;
mutationOutput: ReturnType<M>;
/**
* the output the triggering mutation produced
*
* note
* - this may be null if the mutation threw an error
*/
mutationOutput: ReturnType<M> | null;
/**
* the status of the execution
*/
mutationStatus: MutationExecutionStatus;
/**
* specifies all of the keys that are currently cached
*/
cachedQueryKeys: string[];
}) => {
inputs?: Parameters<Q>[];
Expand Down Expand Up @@ -69,8 +97,15 @@ export interface RemoteStateQueryUpdateTrigger<Q extends (...args: any) => any,
mutationInput: Parameters<M>;
/**
* the output the triggering mutation produced
*
* note
* - this may be null if the mutation threw an error
*/
mutationOutput: ReturnType<M> | null;
/**
* the status of the execution
*/
mutationOutput: ReturnType<M>;
mutationStatus: MutationExecutionStatus;
};
}) => ReturnType<Q>;
}
Expand Down
15 changes: 8 additions & 7 deletions src/createRemoteStateCachingContext.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ describe('createRemoteStateCachingContext', () => {
expect(result6.length).toEqual(0); // should no longer have any results, since our updatedBy trigger should have removed the recipe by uuid
expect(apiCalls.length).toEqual(3); // should not have had another api call, since we updated the cache, not invalidated it
});
it('should be not trigger invalidations nor updates if the mutation threw an error', async () => {
it('should still trigger invalidations and updates if the mutation threw an error', async () => {
// start the context
const { withRemoteStateQueryCaching, withRemoteStateMutationRegistration } = createRemoteStateCachingContext({ cache: createCache() });

Expand Down Expand Up @@ -377,10 +377,10 @@ describe('createRemoteStateCachingContext', () => {
expect(error.message).toEqual('surprise!');
}

// prove that we did not invalidate the request, since the mutation failed
// prove that we invalidated the request
const result5 = await queryGetRecipes.execute({ searchFor: 'steak' });
expect(result5).toEqual(result1); // should have gotten the same result after cache invalidation
expect(apiCalls.length).toEqual(2); // and should not have called the api after cache invalidation
expect(result5).not.toEqual(result1); // should have gotten a different result after cache invalidation
expect(apiCalls.length).toEqual(3); // and should have called the api after cache invalidation

// execute mutation to delete a recipe we've previously found for smoothie
try {
Expand All @@ -391,10 +391,11 @@ describe('createRemoteStateCachingContext', () => {
expect(error.message).toEqual('surprise!');
}

// prove that we did not update the cached value for that request
// prove that we updated the cached value for that request
const result6 = await queryGetRecipes.execute({ searchFor: 'smoothie' });
expect(result6).toEqual(result2);
expect(apiCalls.length).toEqual(2); // should not have had another api call, since still would have been cached
expect(result2.length).toEqual(1);
expect(result6.length).toEqual(0); // should no longer have any results, since our updatedBy trigger should have removed the recipe by uuid
expect(apiCalls.length).toEqual(3); // should not have had another api call, since we updated the cache, not invalidated it
});
});

Expand Down
20 changes: 15 additions & 5 deletions src/createRemoteStateCachingContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
WithSimpleCachingAsyncOptions,
} from 'with-simple-caching';
import { RemoteStateCacheContext, RemoteStateCacheContextQueryRegistration } from './RemoteStateCacheContext';
import { RemoteStateQueryInvalidationTrigger, RemoteStateQueryUpdateTrigger } from './RemoteStateQueryCachingOptions';
import { MutationExecutionStatus, RemoteStateQueryInvalidationTrigger, RemoteStateQueryUpdateTrigger } from './RemoteStateQueryCachingOptions';
import { BadRequestError } from './errors/BadRequestError';
import { RemoteStateCache } from './RemoteStateCache';
import { defaultKeySerializationMethod, defaultValueDeserializationMethod, defaultValueSerializationMethod } from './defaults';
Expand Down Expand Up @@ -270,10 +270,12 @@ export const createRemoteStateCachingContext = <
mutationName,
mutationInput,
mutationOutput,
mutationStatus,
}: {
mutationName: string;
mutationInput: Parameters<M>;
mutationOutput: ReturnType<M>;
mutationOutput: ReturnType<M> | null;
mutationStatus: MutationExecutionStatus;
}) => {
const registrations = Object.values(context.registered.queries);

Expand All @@ -299,6 +301,7 @@ export const createRemoteStateCachingContext = <
const invalidate = invalidatedByThisMutationDefinition.affects({
mutationInput,
mutationOutput,
mutationStatus,
cachedQueryKeys,
});

Expand All @@ -325,6 +328,7 @@ export const createRemoteStateCachingContext = <
const affected = updatedByThisMutationDefinition.affects({
mutationInput,
mutationOutput,
mutationStatus,
cachedQueryKeys,
});

Expand All @@ -338,6 +342,7 @@ export const createRemoteStateCachingContext = <
with: {
mutationInput,
mutationOutput,
mutationStatus,
},
})
: undefined;
Expand All @@ -364,9 +369,14 @@ export const createRemoteStateCachingContext = <

// define the execute function, with triggers onMutationOutput
const execute: L = (async (...args: Parameters<L>): Promise<ReturnType<L>> => {
const result = (await logic(...args)) as ReturnType<L>;
await onMutationOutput({ mutationName, mutationInput: args, mutationOutput: result });
return result;
try {
const result = (await logic(...args)) as ReturnType<L>;
await onMutationOutput({ mutationName, mutationInput: args, mutationOutput: result, mutationStatus: MutationExecutionStatus.RESOLVED });
return result;
} catch (error) {
await onMutationOutput({ mutationName, mutationInput: args, mutationOutput: null, mutationStatus: MutationExecutionStatus.REJECTED });
throw error;
}
}) as L;

// return the extended logic
Expand Down

0 comments on commit b77d127

Please sign in to comment.