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
Show file tree
Hide file tree
Changes from all 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
Expand Up @@ -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))
Expand Down
7 changes: 5 additions & 2 deletions docs/optimistic.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
};
},
},
});
Expand Down
30 changes: 29 additions & 1 deletion docs/resolvers.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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
Expand Up @@ -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.

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

import {
Expand All @@ -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>;
Expand Down Expand Up @@ -149,6 +151,58 @@ const readRootField = (
}
};

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

return null;
}

const select = getSelectionSet(fragment);
const typename = getFragmentTypeName(fragment);
if (typeof entity !== 'string' && !entity.__typename) {
entity.__typename = typename;
}

const entityKey =
typeof entity !== 'string'
? store.keyOfEntity({ __typename: typename, ...entity } as Data)
: entity;

if (!entityKey) {
warning(
false,
"Can't generate a key for readFragment(...).\n" +
'You have to pass an `id` or `_id` field or create a custom `keys` config for `' +
typename +
'`.'
);

return null;
}

const ctx: Context = {
variables: {},
fragments,
partial: false,
store,
schemaPredicates: store.schemaPredicates,
};

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

const readSelection = (
ctx: Context,
entityKey: string,
Expand Down
11 changes: 5 additions & 6 deletions src/operations/write.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,22 +157,21 @@ export const writeFragment = (
}

const select = getSelectionSet(fragment);
const typeName = getFragmentTypeName(fragment);
const writeData = { ...data, __typename: typeName } as Data;

const entityKey = store.keyOfEntity(writeData) as string;
const typename = getFragmentTypeName(fragment);
const writeData = { __typename: typename, ...data } as Data;
const entityKey = store.keyOfEntity(writeData);
if (!entityKey) {
return warning(
false,
"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 +
typename +
'`.'
);
}

const ctx: Context = {
variables: {}, // TODO: Should we support variables?
variables: {},
fragments,
result: { dependencies: getCurrentDependencies() },
store,
Expand Down
52 changes: 51 additions & 1 deletion src/store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,8 +181,9 @@ describe('Store with OptimisticMutationConfig', () => {
expect(store.getRecord('Appointment:1.info')).toBe(undefined);
});

it('should be able to update a fragment', () => {
it('should be able to write a fragment', () => {
initStoreState(0);

store.writeFragment(
gql`
fragment _ on Todo {
Expand Down Expand Up @@ -218,6 +219,32 @@ 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
}
`,
{ id: '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 => ({
Expand Down Expand Up @@ -303,6 +330,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,
Expand Down
17 changes: 15 additions & 2 deletions src/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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>;
Expand Down Expand Up @@ -257,12 +262,20 @@ 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);
}
}

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

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

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