Skip to content
This repository has been archived by the owner on Jul 6, 2020. It is now read-only.

(chore) - improve api surface consistency #73

Merged
merged 14 commits into from
Sep 12, 2019
Merged
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -8,6 +8,10 @@ If a change is missing an attribution, it may have been made by a Core Contribut

_The format is based on [Keep a Changelog](http://keepachangelog.com/)._

## vNext

- ⚠ rename `writeFragment` to `updateFragment` and introduce `readQuery` and `readFragment` (see [#73](https://github.com/FormidableLabs/urql-exchange-graphcache/pull/73))

## [v1.0.0-rc.7](https://github.com/FormidableLabs/urql-exchange-graphcache/compare/v1.0.0-rc.6...v1.0.0-rc.7)

- ⚠ Fix reexecuted operations due to dependencies not using `cache-first` (see [0bd58f6](https://github.com/FormidableLabs/urql-exchange-graphcache/commit/0bd58f6))
7 changes: 5 additions & 2 deletions docs/optimistic.md
Original file line number Diff line number Diff line change
@@ -20,8 +20,11 @@ Let's see an example.
const myGraphCache = cacheExchange({
optimistic: {
addTodo: (variables, cache, info) => {
console.log(variables); // { id: '1', text: 'optimistic', __typename: 'Todo' }
return variables;
console.log(variables); // { id: '1', text: 'optimistic' }
return {
...variables
__typename: 'Todo', // We still have to let the cache know what entity we are on.
};
},
},
});
30 changes: 29 additions & 1 deletion docs/resolvers.md
Original file line number Diff line number Diff line change
@@ -23,7 +23,7 @@ A `resolver` gets four arguments:
- `parent` – The original entity in the cache. In the example above, this
would be the full `Todo` object.
- `arguments` – The arguments used in this field.
- `cache` – This is the normalized cache. The cache provides us with a `resolve` method;
- `cache` – This is the normalized cache. The cache provides us with `resolve`, `readQuery` and `readFragment` methods;
see more about this [below](#cache.resolve).
- `info` – This contains the fragments used in the query and the field arguments in the query.

@@ -82,4 +82,32 @@ console.log(name); // 'Bar'
This can help solve practical use cases like date formatting,
where you would query the date and then convert it in your resolver.

## `cache.readQuery`

Another method the cache allows is to let you read a full query, this method
accepts an object of `query` and optionally `variables`.

```js
const data = cache.readQuery({ query: Todos, variables: { from: 0, limit: 10 } })`
```

This way we'll get the stored data for the `TodosQuery` with given variables.

## `cache.readFragment`

The store allows the user to also read a fragment for a certain entity, this function
accepts a `fragment` and an `id`. This looks like the following.

```js
const data = cache.readFragment(gql`
fragment _ on Todo {
id
text
}
`, '1'`;
```

This way we'll get the Todo with id 1 and the relevant data we are askng for in the
fragment.

[Back](../README.md)
4 changes: 4 additions & 0 deletions docs/updates.md
Original file line number Diff line number Diff line change
@@ -8,6 +8,10 @@ That's where updates come into play. Analogous to our `resolvers`,
`updates` get arguments, but instead of the `parent` argument we get the
result given from the server due to a subscription trigger or a mutation.

> Note that this result will look like result.data, this means that in
> the example of us adding a todo by means of `addTodo` it will look like
> `{ addTodo: addedTodo }`.

Let's look at three additional methods provided by the cache to enable
updates.

43 changes: 43 additions & 0 deletions src/operations/query.ts
Original file line number Diff line number Diff line change
@@ -7,6 +7,7 @@ import {
getName,
getFieldArguments,
getFieldAlias,
getFragmentTypeName,
} from '../ast';

import {
@@ -31,6 +32,7 @@ import {
import { SelectionIterator, isScalar } from './shared';
import { joinKeys, keyOfField } from '../helpers';
import { SchemaPredicates } from '../ast/schemaPredicates';
import { DocumentNode, FragmentDefinitionNode } from 'graphql';

export interface QueryResult {
dependencies: Set<string>;
@@ -149,6 +151,47 @@ const readRootField = (
}
};

export const readFragment = (
store: Store,
query: DocumentNode,
id: string
): Data | null | void => {
const fragments = getFragments(query);
const names = Object.keys(fragments);
const fragment = fragments[names[0]] as FragmentDefinitionNode;
if (fragment === undefined) {
return warning(
false,
'readFragment (...) was called with an empty fragment.\n' +
'You have to call it with at least one fragment in your GraphQL document.'
);
}

const select = getSelectionSet(fragment);
const typeName = getFragmentTypeName(fragment);
const entityKey = store.keyOfEntity({ __typename: typeName, id }) as string;
JoviDeCroock marked this conversation as resolved.
Show resolved Hide resolved

if (!entityKey) {
kitten marked this conversation as resolved.
Show resolved Hide resolved
return warning(
store.keys[typeName],
"Can't generate a key for readFragment (...).\n" +
'You have to pass an `id` or create a custom `keys` config for `' +
typeName +
'`.'
);
}

const ctx: Context = {
variables: {}, // TODO: Should we support variables?
JoviDeCroock marked this conversation as resolved.
Show resolved Hide resolved
fragments,
partial: false,
store,
schemaPredicates: store.schemaPredicates,
};

return readSelection(ctx, entityKey, select, Object.create(null));
};

const readSelection = (
ctx: Context,
entityKey: string,
2 changes: 1 addition & 1 deletion src/operations/write.ts
Original file line number Diff line number Diff line change
@@ -163,7 +163,7 @@ export const writeFragment = (
const entityKey = store.keyOfEntity(writeData) as string;
if (!entityKey) {
return warning(
false,
store.keys[typeName],
"Can't generate a key for writeFragment(...) data.\n" +
'You have to pass an `id` or `_id` field or create a custom `keys` config for `' +
typeName +
50 changes: 49 additions & 1 deletion src/store.test.ts
Original file line number Diff line number Diff line change
@@ -183,7 +183,7 @@ describe('Store with OptimisticMutationConfig', () => {

it('should be able to update a fragment', () => {
initStoreState(0);
store.writeFragment(
store.updateFragment(
gql`
fragment _ on Todo {
id
@@ -218,6 +218,31 @@ describe('Store with OptimisticMutationConfig', () => {
clearStoreState();
});

it('should be able to read a fragment', () => {
initStoreState(0);
const result = store.readFragment(
gql`
fragment _ on Todo {
id
text
complete
}
`,
'0'
);

const deps = getCurrentDependencies();
expect(deps).toEqual(new Set(['Todo:0']));

expect(result).toEqual({
id: '0',
text: 'Go to the shops',
complete: false,
__typename: 'Todo',
});
clearStoreState();
});

it('should be able to update a query', () => {
initStoreState(0);
store.updateQuery({ query: Todos }, data => ({
@@ -303,6 +328,29 @@ describe('Store with OptimisticMutationConfig', () => {
clearStoreState();
});

it('should be able to read a query', () => {
initStoreState(0);
const result = store.readQuery({ query: Todos });

const deps = getCurrentDependencies();
expect(deps).toEqual(
new Set([
'Query.todos',
'Todo:0',
'Todo:1',
'Todo:2',
'Author:0',
'Author:1',
])
);

expect(result).toEqual({
__typename: 'Query',
todos: todosData.todos,
});
clearStoreState();
});

it('should be able to optimistically mutate', () => {
const { dependencies } = writeOptimistic(
store,
19 changes: 16 additions & 3 deletions src/store.ts
Original file line number Diff line number Diff line change
@@ -16,7 +16,7 @@ import {
} from './types';

import { joinKeys, keyOfField } from './helpers';
import { read } from './operations/query';
import { read, readFragment } from './operations/query';
import { writeFragment, startWrite } from './operations/write';
import { invalidate } from './operations/invalidate';
import { SchemaPredicates } from './ast/schemaPredicates';
@@ -74,6 +74,11 @@ const mapRemove = <T>(map: Pessimism.Map<T>, key: string) => {

type RootField = 'query' | 'mutation' | 'subscription';

interface QueryInput {
query: string | DocumentNode;
variables?: Variables;
}

export class Store {
records: Pessimism.Map<EntityField>;
links: Pessimism.Map<Link>;
@@ -257,13 +262,21 @@ export class Store {
updater: (data: Data | null) => null | Data
): void {
const request = createRequest(input.query, input.variables);
const output = updater(read(this, request).data);
const output = updater(this.readQuery(request as QueryInput));
if (output !== null) {
startWrite(this, request, output);
}
}

writeFragment(dataFragment: DocumentNode, data: Data): void {
readQuery(input: QueryInput): Data | null {
return read(this, createRequest(input.query, input.variables)).data;
}

updateFragment(dataFragment: DocumentNode, data: Data): void {
writeFragment(this, dataFragment, data);
}

readFragment(dataFragment: DocumentNode, id: string): Data | null {
return readFragment(this, dataFragment, id) || null;
}
}