Skip to content
This repository was archived by the owner on Sep 6, 2021. It is now read-only.

Commit aef866c

Browse files
committed
update intro docs
1 parent bb506a7 commit aef866c

14 files changed

+595
-122
lines changed

docs/cache-configuration.md

+157
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
---
2+
id: cache-configuration
3+
title: Configuring the cache
4+
sidebar_label: Configuration
5+
---
6+
7+
The Ferry cache normalizes incoming data for your GraphQL operations and stores it using a `Store`. This allows the client to respond to future queries for the same data without sending unnecessary network requests.
8+
9+
## Data Normalization
10+
11+
Normalization involves the following steps:
12+
13+
1. The cache [generates a unique ID](#generating-unique-identifiers) for every identifiable object included in the response.
14+
2. The cache stores the objects by ID in a flat lookup table.
15+
3. Whenever an incoming object is stored with the same ID as an _existing_ object, the fields of those objects are _merged_.
16+
- If the incoming object and the existing object share any fields, the incoming object _overwrites_ the cached values for those fields.
17+
- Fields that appear in _only_ the existing object or _only_ the incoming object are preserved.
18+
19+
Normalization constructs a partial copy of your data graph on your client, in a format that's optimized for reading and updating the graph as your application changes state.
20+
21+
### Generating Unique Identifiers
22+
23+
By default, the cache generates a unique identifier using an object's `__typename` and any uniquely identifying fields, called `keyFields`. By default, the `id` or `_id` field (whichever is defined) is used as a `keyField`, but this behavior can be [customized](#customizing-identifier-generation-by-type). These two values are separated by a colon (`:`).
24+
25+
For example, an object with a `__typename` of `Task` and an `id` of `14` is assigned a default identifier of `Task:14`.
26+
27+
#### Customizing Identifier Generation by Type
28+
29+
If one of your types defines its primary key with a field _besides_ `id` or `_id`, you can customize how the cache generates unique identifiers for that type. To do so, you define `TypePolicy` for the type and include a Map of all your `typePolicies` when instantiating the cache.
30+
31+
Include a `keyFields` field in relevant `TypePolicy` objects, like so:
32+
33+
```dart
34+
final cache = Cache(
35+
typePolicies: {
36+
'Product': TypePolicy(
37+
// In most inventory management systems, a single UPC code uniquely
38+
// identifies any product.
39+
keyFields: {
40+
'upc': true,
41+
},
42+
),
43+
'Person': TypePolicy(
44+
// In some user account systems, names or emails alone do not have to
45+
// be unique, but the combination of a person's name and email is
46+
// uniquely identifying.
47+
keyFields: {
48+
'name': true,
49+
'email': true,
50+
},
51+
),
52+
'Book': TypePolicy(
53+
// If one of the keyFields is an object with fields of its own, you can
54+
// include those nested keyFields by using a nested array of strings:
55+
keyFields: {
56+
'title': true,
57+
'author': {
58+
'name': true,
59+
},
60+
},
61+
),
62+
},
63+
);
64+
```
65+
66+
This example shows three `typePolicies`: one for a `Product` type, one for a `Person` type, and one for a `Book` type. Each `TypePolicy`'s `keyFields` array defines which fields on the type _together_ represent the type's primary key.
67+
68+
The `Book` type above uses a _subfield_ as part of its primary key. The `Book`'s `author` field must be an object that includes a `name` field for this to be valid.
69+
70+
In the example above, the resulting identifier string for a `Book` object has the following structure:
71+
72+
```
73+
Book:{"author":{"name":"Ray Bradbury"}, "title":"Fahrenheit 451"}
74+
```
75+
76+
An object's primary key fields are always listed alphabetically to ensure uniqueness.
77+
78+
Note that these `keyFields` strings always refer to the actual field names as defined in your schema, meaning the ID computation is not sensitive to field aliases.
79+
80+
### Disabling Normalization
81+
82+
You can instruct Ferry _not_ to normalize objects of a certain type. This can be useful for metrics and other transient data that's identified by a timestamp and never receives updates.
83+
84+
To disable normalization for a type, define a `TypePolicy` for the type (as shown in [Customizing Identifier Generation by Type](#customizing-identifier-generation-by-type)) and set the policy's `keyFields` field to an empty `Map`.
85+
86+
Objects that are not normalized are instead embedded within their _parent_ object in the cache. You can't access these objects directly, but you can access them via their parent.
87+
88+
## `TypePolicy` Fields
89+
90+
To customize how the cache interacts with specific types in your schema, you can provide an object mapping `__typename` strings to `TypePolicy` objects when you create a new `Cache`.
91+
92+
A `TypePolicy` object can include the following fields:
93+
94+
````dart
95+
class TypePolicy {
96+
/// Allows defining the primary key fields for this type.
97+
///
98+
/// Pass a `true` value for any fields you wish to use as key fields. You can
99+
/// also use child fields.
100+
///
101+
/// ```dart
102+
/// final bookTypePolicy = TypePolicy(
103+
/// keyFields: {
104+
/// 'title': true,
105+
/// 'author': {
106+
/// 'name': true,
107+
/// }
108+
/// },
109+
/// );
110+
/// ```
111+
///
112+
/// If you don't wish to normalize this type, simply pass an empty `Map`. In
113+
/// that case, we won't normalize this type and it will be reachable from its
114+
/// parent.
115+
Map<String, dynamic> keyFields;
116+
117+
/// Set to `true` if this type is the root Query in your schema.
118+
bool queryType;
119+
120+
/// Set to `true` if this type is the root Mutation in your schema.
121+
bool mutationType;
122+
123+
/// Set to `true` if this type is the root Subscription in your schema.
124+
bool subscriptionType;
125+
126+
/// Allows defining [FieldPolicy]s for this type.
127+
Map<String, FieldPolicy> fields;
128+
129+
TypePolicy({
130+
this.keyFields,
131+
this.queryType = false,
132+
this.mutationType = false,
133+
this.subscriptionType = false,
134+
this.fields = const {},
135+
});
136+
}
137+
````
138+
139+
### Overriding Root Operation Types (Uncommon)
140+
141+
In addition to `keyFields`, a `TypePolicy` can indicate that it represents the root query, mutation, or subscription type by setting `queryType`, `mutationType`, or `subscriptionType` as `true`:
142+
143+
```dart
144+
final cache = Cache(
145+
typePolicies: {
146+
'UnconventionalRootQuery': TypePolicy(
147+
queryType: true,
148+
),
149+
},
150+
);
151+
```
152+
153+
The cache normally obtains `__typename` information by adding the `__typename` field to every GraphQL operation selection set it sends to the server. The `__typename` of the root query or mutation is almost always simply `"Query"` or `"Mutation"`, so the cache assumes those common defaults unless instructed otherwise in a `TypePolicy`.
154+
155+
### The `fields` Property
156+
157+
The final property within `TypePolicy` is the `fields` property, which is a map from string field names to `FieldPolicy` objects. For more information on this field, see [Customizing the behavior of cached fields](field-policies.md).

docs/cache-interaction.md

+137
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
---
2+
id: cache-interaction
3+
title: Reading and Writing Data to the Cache
4+
sidebar_label: Reading and Writing
5+
---
6+
7+
Ferry provides the following methods for reading and writing data to the cache:
8+
9+
- [`readQuery`](#readquery) and [`readFragment`](#readfragment)
10+
- [`writeQuery` and `writeFragment`](#writequery-and-writefragment)
11+
12+
These methods are described in detail below.
13+
14+
All code samples below assume that you have initialized an instance of `Cache`.
15+
16+
## `readQuery`
17+
18+
The `readQuery` method enables you to run a GraphQL query directly on your cache.
19+
20+
1. If your cache contains all of the data necessary to fulfill a specified query, `readQuery` returns a data object in the shape of that query, just like a GraphQL server does.
21+
22+
2. If your cache _doesn't_ contain all of the data necessary to fulfill a specified query, `readQuery` returns null. It _never_ attempts to fetch data from a remote server.
23+
24+
Let's assume we've created the following `reviews.graphql` file and [generated](codegen.md) our GraphQL classes.
25+
26+
```graphql
27+
query Reviews($first: Int, $offset: Int) {
28+
reviews(first: $first, offset: $offset) {
29+
id
30+
stars
31+
commentary
32+
}
33+
}
34+
```
35+
36+
We can then read our Query like so:
37+
38+
```dart
39+
final reviewsReq = GReviewsReq();
40+
41+
final reviewData = cache.readQuery(reviewsReq);
42+
```
43+
44+
You can provide GraphQL variables to `readQuery` like so:
45+
46+
```dart
47+
final reviewsReq = GReviewsReq(
48+
(b) => b
49+
..vars.first = 5
50+
..vars.offset = 0,
51+
);
52+
53+
final reviewData = cache.readQuery(reviewsReq);
54+
```
55+
56+
## `readFragment`
57+
58+
The `readFragment` method enables you to read data from _any_ normalized cache object that was stored as part of _any_ query result. Unlike with `readQuery`, calls to `readFragment` do not need to conform to the structure of one of your data graph's supported queries.
59+
60+
For example, if we have the following `hero_data.graphql` file:
61+
62+
```graphql
63+
fragment ReviewFragment on Review {
64+
stars
65+
commentary
66+
}
67+
```
68+
69+
We could read the generated fragment like so:
70+
71+
```dart
72+
final reviewFragmentReq = GReviewFragmentReq(
73+
(b) => b..idFields = {'id': '123'},
74+
);
75+
76+
final reviewFragmentData = cache.readFragment(reviewFragmentReq);
77+
```
78+
79+
The `idFields` argument is the set of [unique identifiers](cache-configuration.md#generating-unique-identifiers) and their values for the object you want to read from the cache. By default, this is the value of the object's `id` field, but you can [customize this behavior](cache-configuration.md#generating-unique-identifiers).
80+
81+
In the example above:
82+
83+
- If a `Review` object with an `id` of `123` is _not_ in the cache,
84+
`readFragment` returns `null`.
85+
- If the `Review` object _is_ in the cache but it's
86+
missing either a `stars` or `commentary` field, `readFragment` returns `null`.
87+
88+
## `writeQuery` and `writeFragment`
89+
90+
In addition to reading arbitrary data from the cache, you can _write_ arbitrary data to the cache with the `writeQuery` and `writeFragment` methods.
91+
92+
:::note
93+
Any changes you make to cached data with `writeQuery` and `writeFragment` are not pushed to your GraphQL server.
94+
:::
95+
96+
These methods have the same signature as their `read` counterparts, except they require an additional `data` variable.
97+
98+
For example, the following call to `writeFragment` _locally_ updates the `Review` object with an `id` of `123`:
99+
100+
```dart
101+
final reviewFragmentReq = GReviewFragmentReq(
102+
(b) => b..idFields = {'id': '123'},
103+
);
104+
105+
final reviewFragmentData = GReviewFragmentData(
106+
(b) => b
107+
..stars = 5
108+
..commentary = 'I watched it again and loved it',
109+
);
110+
111+
cache.writeFragment(reviewFragmentReq, reviewFragmentData);
112+
```
113+
114+
Any `operationRequest` streams that are listening to this data will be updated accordingly.
115+
116+
## Combining reads and writes
117+
118+
Combine `readQuery` and `writeQuery` to fetch currently cached data and make selective modifications to it.
119+
120+
:::note
121+
Since Ferry's generated classes are based on `built_value`, we can easily create modified copies of them using the `rebuild` method. Check out [this post](https://medium.com/dartlang/darts-built-value-for-immutable-object-models-83e2497922d4) to learn more about `built_value` objects.
122+
:::
123+
124+
The example below updates the star rating for the cached `Review`. Remember, this addition is _not_ sent to your remote server.
125+
126+
```dart
127+
final reviewFragmentReq = GReviewFragmentReq(
128+
(b) => b..idFields = {'id': review.id},
129+
);
130+
131+
final data = cache.readFragment(reviewFragmentReq);
132+
133+
cache.writeFragment(
134+
reviewFragmentReq,
135+
data.rebuild((b) => b..stars = 4),
136+
);
137+
```

docs/custom-plugins.md

-49
This file was deleted.

0 commit comments

Comments
 (0)