Skip to content
This repository has been archived by the owner on Apr 17, 2023. It is now read-only.

Commit

Permalink
Cache pagination (#524)
Browse files Browse the repository at this point in the history
  • Loading branch information
kingsleyzissou authored Jun 23, 2020
1 parent 5399d59 commit 124d12b
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 38 deletions.
41 changes: 38 additions & 3 deletions docs/ref-cache.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,30 @@ const options = createMutationOptions(mutationOptions);
apolloClient.mutate(options);
```

> NOTE: Cache helpers currently support only GraphQL Queries that return arrays.
> For example `getUsers():[User]`.
> When working with single objects returned by Queries we usually do not need use any helper as Query will be updated automatically on every update
### Pagination and relationships

Offix by default assumes that the object returned by mutations contains just your data. In situations when you need to wrap your data into some container to provide pagination and other information, Offix will require an additional parameter. For example when the API returns TaskPage etc. the actual data might be returned over `items`.

When this query is cached, it may be necessary to update a field within the query and not the entire query, for example with relationships. In order to do this, it is necessary to provide the name of the field being updated with the `returnField` parameter.

```javascript
const mutationOptions = {
mutation: ADD_COMMENT,
variables: {
title: 'comment title'
},
updateQuery: {
query: GET_TASK,
variables: {
filterBy: 'some filter'
}
},
returnType: 'Comment',
operationType: CacheOperation.ADD,
idField: 'id',
returnField: 'comments
};
```
## Subscription Helpers
Expand Down Expand Up @@ -121,6 +142,20 @@ query.subscribeToMore(subscriptionOptions);
The cache will now be kept up to date with automatic data deduplication being performed.
### Pagination and relationships
Similarly to the mutation cache update helpers, it is necessary to provide the `returnField` parameter to specify the name of the field to update within the query.
```javascript
export const editSubscriptionOptions = createSubscriptionOptions({
subscriptionQuery: NEW_COMMENT,
cacheUpdateQuery: GET_TASK,
operationType: CacheOperation.ADD,
idField: 'id',
returnField: 'comments'
});
```
### Multiple Subscriptions
`offix-cache` also provides the ability to automatically call `subscribeToMore` on your `ObservableQuery`. This can be useful in a situation where you may have multiple subscriptions which can affect one single query. For example, if you have a `TaskAdded`, `TaskDeleted` and a `TaskUpdated` subscription you would need three separate `subscribeToMore` function calls. This can become tedious as your number of subscriptions grow. To combat this, we can use the `subscribeToMoreHelper` function from offix-cache to automatically handle this for us by passing it an array of subscriptions and their corresponding queries which need to be updated.
Expand Down
55 changes: 33 additions & 22 deletions packages/offix-cache/src/createMutationOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,14 @@ interface CacheUpdateHelperOptions {
* @default uses `id` field on the type
*/
idField?: string;

/**
* String value for possible nested return type, primarily used
* with pagination or relationships.
*
* @default uses null
*/
returnField?: string | null;
}

/**
Expand Down Expand Up @@ -173,7 +181,7 @@ export const getUpdateFunction = (options: CacheUpdateOptions): MutationUpdaterF
* Generic cache update function that adds an item to a query that contains a list of items
* Might be exported in the future
*/
function addItemToQuery({ mutationName, updateQuery, idField = "id" }: CacheUpdateHelperOptions): MutationUpdaterFn {
function addItemToQuery({ mutationName, updateQuery, idField = "id", returnField = null }: CacheUpdateHelperOptions): MutationUpdaterFn {
return (cache, { data }) => {
const { query, variables } = deconstructQuery(updateQuery);
const queryField = getOperationFieldName(query);
Expand All @@ -186,29 +194,29 @@ function addItemToQuery({ mutationName, updateQuery, idField = "id" }: CacheUpda
queryResult = {};
}

const result = queryResult[queryField];
const result = (returnField)
? queryResult[queryField][returnField]
: queryResult[queryField];

if (result && operationData) {
// FIXME deduplication should happen on subscriptions
// We do that every time no matter if we have subscription
if (result.find) {
if (result instanceof Array) {
const foundItem = !result.find((item: any) => {
return item[idField] === operationData[idField];
});
if (foundItem) {
result.push(operationData);
}

} else if (result.items && result.items.find) {
const foundItem = !result.items.find((item: any) => {
return item[idField] === operationData[idField];
});
if (foundItem) {
result.items.push(operationData);
}
}
} else {
queryResult[queryField] = [operationData];
if (!returnField) {
queryResult[queryField] = [operationData];
} else {
queryResult[queryField][returnField] = [operationData];
}
}

try {
cache.writeQuery({
query,
Expand All @@ -226,7 +234,7 @@ function addItemToQuery({ mutationName, updateQuery, idField = "id" }: CacheUpda
* Generic cache update function that removes an item from a query that contains a list of items
* Might be exported in the future
*/
function deleteItemFromQuery({ mutationName, updateQuery, idField = "id" }: CacheUpdateHelperOptions): MutationUpdaterFn {
function deleteItemFromQuery({ mutationName, updateQuery, idField = "id", returnField = null }: CacheUpdateHelperOptions): MutationUpdaterFn {
return (cache, { data }) => {
const { query, variables } = deconstructQuery(updateQuery);
const queryField = getOperationFieldName(query);
Expand All @@ -242,18 +250,21 @@ function deleteItemFromQuery({ mutationName, updateQuery, idField = "id" }: Cach
toBeRemoved = operationData;
}
let newData: any;
if (typeof queryResult[queryField].filter === "function") {
newData = queryResult[queryField].filter((item: any) => {
return toBeRemoved[idField] !== item[idField];
});
} else if (queryResult[queryField].items) {
const newItems = queryResult[queryField].items.filter((item: any) => {

const prev = (returnField)
? queryResult[queryField][returnField]
: queryResult[queryField];

if (prev instanceof Array) {
newData = prev.filter((item: any) => {
return toBeRemoved[idField] !== item[idField];
});
newData = queryResult[queryField];
newData.items = newItems;
} else {
newData = queryResult[queryField];
if (!returnField) {
newData = queryResult[queryField];
} else {
newData = queryResult[queryField][returnField];
}
}

queryResult[queryField] = newData;
Expand Down
40 changes: 27 additions & 13 deletions packages/offix-cache/src/createSubscriptionOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export interface SubscriptionHelperOptions {
cacheUpdateQuery: CacheUpdatesQuery;
operationType: CacheOperation;
idField?: string;
returnField?: string;
}

/**
Expand All @@ -34,7 +35,8 @@ export const createSubscriptionOptions = (options: SubscriptionHelperOptions): S
subscriptionQuery,
cacheUpdateQuery,
operationType,
idField = "id"
idField = "id",
returnField = null
} = options;
const document = (subscriptionQuery && (subscriptionQuery as QueryWithVariables).query)
|| (subscriptionQuery as DocumentNode);
Expand All @@ -53,12 +55,27 @@ export const createSubscriptionOptions = (options: SubscriptionHelperOptions): S
const mutadedItem = data[key];

const optype = operationType;
const obj = prev[queryField];

// necessary for relationships
// i.e. comments on a task field
const obj = (returnField)
? prev[queryField][returnField]
: prev[queryField];

const updater = getUpdateQueryFunction(optype, idField);
const result = updater(obj, mutadedItem);

if (!returnField) {
return {
[queryField]: result
};
}

return {
[queryField]: result
[queryField]: {
...prev[queryField],
[returnField]: result
}
};
}
};
Expand Down Expand Up @@ -91,12 +108,11 @@ const getUpdateQueryFunction = (operationType: CacheOperation, idField = "id"):
function addSubscriptionItem({ idField }: { idField: string }) {
return (prev: [CacheItem], newItem: CacheItem | undefined) => {
if (!newItem) {
return [...prev];
} else {
return [...prev.filter(item => {
return item[idField] !== newItem[idField];
}), newItem];
return prev;
}
return [...prev.filter(item => {
return item[idField] !== newItem[idField];
}), newItem];
};
};

Expand All @@ -108,9 +124,8 @@ function deleteSubscriptionItem({ idField }: { idField: string }) {
return (prev: [CacheItem], newItem: CacheItem | undefined) => {
if (!newItem) {
return [];
} else {
return prev.filter((item: any) => item[idField] !== newItem[idField]);
}
return prev.filter((item: any) => item[idField] !== newItem[idField]);
};
};

Expand All @@ -121,9 +136,8 @@ function deleteSubscriptionItem({ idField }: { idField: string }) {
function updateSubscriptionItem({ idField }: { idField: string }) {
return (prev: [CacheItem], newItem: CacheItem | undefined) => {
if (!newItem) {
return [...prev];
} else {
return prev.map((item: any) => item[idField] === newItem[idField] ? newItem : item);
return prev;
}
return prev.map((item: any) => item[idField] === newItem[idField] ? newItem : item);
};
};

0 comments on commit 124d12b

Please sign in to comment.