Skip to content

Commit bcb8aad

Browse files
BoDmartinbonnin
andauthored
Pagination: use "field key" instead of "field name" (#5898)
* Use "field key" instead of "field name" * Add a few cache related terms to the glossary * Apply suggestions from code review Co-authored-by: Martin Bonnin <[email protected]> --------- Co-authored-by: Martin Bonnin <[email protected]>
1 parent 9ba3e55 commit bcb8aad

File tree

22 files changed

+280
-224
lines changed

22 files changed

+280
-224
lines changed

design-docs/Glossary.md

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
1-
# Codegen Glossary
2-
3-
## Glossary
4-
5-
A small Glossary of the terms used during codegen. The [GraphQL Spec](https://spec.graphql.org/draft/) does a nice job of defining the common terms like `Field`, `SelectionSet`, etc... so I'm not adding these terms here. But it misses some concepts that we bumped into during codegen and that I'm trying to clarify here.
1+
# Glossary
62

3+
The [GraphQL Spec](https://spec.graphql.org/draft/) does a nice job of defining common terms like `Field`, `SelectionSet`, etc. but here are a few other concepts that the library deals with, and their definition.
74

85
### Response shape
96

@@ -105,3 +102,26 @@ Example:
105102
### Polymorphic field
106103

107104
A field that can take several shapes
105+
106+
### Record
107+
108+
A shallow map of a response object. Nested objects in the map values are replaced by a cache reference to another Record.
109+
110+
### Cache key
111+
112+
A unique identifier for a Record.
113+
By default it is the path formed by all the field keys from the root of the query to the field referencing the Record.
114+
To avoid duplication the Cache key can also be computed from the Record contents, usually using its key fields.
115+
116+
### Field key
117+
118+
A key that uniquely identifies a field within a Record. By default composed of the field name and the arguments passed to it.
119+
120+
### Key fields
121+
122+
Fields that are used to compute a Cache key for an object.
123+
124+
### Pagination arguments
125+
126+
Field arguments that control pagination, e.g. `first`, `after`, etc. They should be omitted when computing a field key so different pages can be merged into the same field.
127+

design-docs/Normalized cache overview.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ An example:
9393

9494
<table>
9595
<tr>
96-
<th>Key</th>
96+
<th>Cache key</th>
9797
<th>Record</th>
9898
</tr>
9999

@@ -217,7 +217,7 @@ An instance is created when building the `ApolloClient` and held by the `ApolloC
217217

218218
## Record merging
219219

220-
When a `Record` is stored in the cache, it is _merged_ with the existing one at the same key (if any):
220+
When a `Record` is stored in the cache, it is _merged_ with the existing one at the same cache key (if any):
221221
- existing fields are replaced with the new value
222222
- new fields are added
223223

design-docs/Normalized cache pagination.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ If your schema uses a different pagination style, you can still use the paginati
210210

211211
#### Pagination arguments
212212

213-
The `@fieldPolicy` directive has a `paginationArgs` argument that can be used to specify the arguments that should be omitted from the field name.
213+
The `@fieldPolicy` directive has a `paginationArgs` argument that can be used to specify the arguments that should be omitted from the field key.
214214

215215
Going back to the example above with `usersPage`:
216216

@@ -221,7 +221,7 @@ extend type Query
221221
```
222222

223223
> [!NOTE]
224-
> This can also be done programmatically by configuring the `ApolloStore` with a `FieldNameGenerator` implementation.
224+
> This can also be done programmatically by configuring the `ApolloStore` with a `FieldKeyGenerator` implementation.
225225

226226
With that in place, after fetching the first page, the cache will look like this:
227227

@@ -231,7 +231,7 @@ With that in place, after fetching the first page, the cache will look like this
231231
| user:1 | id: 1, name: John Smith |
232232
| user:2 | id: 2, name: Jane Doe |
233233

234-
The field name no longer includes the `page` argument, which means watching `UsersPage(page = 1)` or any page will observe the same list.
234+
The field key no longer includes the `page` argument, which means watching `UsersPage(page = 1)` or any page will observe the same list.
235235

236236
Here's what happens when fetching the second page:
237237

@@ -245,7 +245,7 @@ Here's what happens when fetching the second page:
245245

246246
The field containing the first page was overwritten by the second page.
247247

248-
This is because the field name is now the same for all pages and the default merging strategy is to overwrite existing fields with the new value.
248+
This is because the field key is now the same for all pages and the default merging strategy is to overwrite existing fields with the new value.
249249

250250
#### Record merging
251251

intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/normalizedcache/NormalizedCache.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ data class NormalizedCache(
1010
)
1111

1212
data class Field(
13-
val name: String,
13+
val key: String,
1414
val value: FieldValue,
1515
)
1616

intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/normalizedcache/provider/DatabaseNormalizedCacheProvider.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ class DatabaseNormalizedCacheProvider : NormalizedCacheProvider<File> {
3939
apolloRecords.map { (key, apolloRecord) ->
4040
NormalizedCache.Record(
4141
key = key,
42-
fields = apolloRecord.map { (fieldName, fieldValue) ->
43-
Field(fieldName, fieldValue.toFieldValue())
42+
fields = apolloRecord.map { (fieldKey, fieldValue) ->
43+
Field(fieldKey, fieldValue.toFieldValue())
4444
},
4545
sizeInBytes = apolloRecord.sizeInBytes
4646
)

intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/normalizedcache/ui/FieldTreeTable.kt

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,14 @@ class FieldTreeTable(selectRecord: (String) -> Unit) : JBTreeTable(FieldTreeTabl
4040
is NormalizedCache.FieldValue.StringValue -> append("\"${v.value}\"")
4141
is NormalizedCache.FieldValue.NumberValue -> append(v.value.toString())
4242
is NormalizedCache.FieldValue.BooleanValue -> append(v.value.toString())
43-
is NormalizedCache.FieldValue.ListValue -> append(when (val size = v.value.size) {
44-
0 -> ApolloBundle.message("normalizedCacheViewer.fields.list.empty")
45-
1 -> ApolloBundle.message("normalizedCacheViewer.fields.list.single")
46-
else -> ApolloBundle.message("normalizedCacheViewer.fields.list.multiple", size)
47-
}, SimpleTextAttributes.GRAY_ITALIC_ATTRIBUTES)
43+
is NormalizedCache.FieldValue.ListValue -> append(
44+
when (val size = v.value.size) {
45+
0 -> ApolloBundle.message("normalizedCacheViewer.fields.list.empty")
46+
1 -> ApolloBundle.message("normalizedCacheViewer.fields.list.single")
47+
else -> ApolloBundle.message("normalizedCacheViewer.fields.list.multiple", size)
48+
},
49+
SimpleTextAttributes.GRAY_ITALIC_ATTRIBUTES
50+
)
4851

4952
is NormalizedCache.FieldValue.CompositeValue -> append("{...}", SimpleTextAttributes.GRAY_ITALIC_ATTRIBUTES)
5053
NormalizedCache.FieldValue.Null -> append("null")

intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/normalizedcache/ui/FieldTreeTableModel.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ class FieldTreeTableModel : ListTreeTableModel(
1414
override fun getColumnClass() = TreeTableModel::class.java
1515
override fun valueOf(item: Unit) = Unit
1616
},
17-
object : ColumnInfo<NormalizedCacheFieldTreeNode, NormalizedCache.Field>(ApolloBundle.message("normalizedCacheViewer.fields.column.value")) {
17+
object :
18+
ColumnInfo<NormalizedCacheFieldTreeNode, NormalizedCache.Field>(ApolloBundle.message("normalizedCacheViewer.fields.column.value")) {
1819
override fun getColumnClass() = NormalizedCache.Field::class.java
1920
override fun valueOf(item: NormalizedCacheFieldTreeNode) = item.field
2021
},
@@ -42,7 +43,7 @@ class FieldTreeTableModel : ListTreeTableModel(
4243

4344
class NormalizedCacheFieldTreeNode(val field: NormalizedCache.Field) : DefaultMutableTreeNode() {
4445
init {
45-
userObject = field.name
46+
userObject = field.key
4647
}
4748
}
4849
}

intellij-plugin/src/main/resources/messages/ApolloBundle.properties

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ errorReport.actionText=Open GitHub Issue
191191
toolwindow.stripe.NormalizedCacheViewer=Apollo Normalized Cache
192192
normalizedCacheViewer.newTab=New Tab
193193
normalizedCacheViewer.tabName.empty=Empty
194-
normalizedCacheViewer.fields.column.key=Key
194+
normalizedCacheViewer.fields.column.key=Field Key
195195
normalizedCacheViewer.fields.column.value=Value
196196
normalizedCacheViewer.toolbar.expandAll=Expand all keys
197197
normalizedCacheViewer.toolbar.collapseAll=Collapse all keys
@@ -201,7 +201,7 @@ normalizedCacheViewer.toolbar.refresh=Refresh
201201
normalizedCacheViewer.empty.message=Open or drag and drop a normalized cache .db file.
202202
normalizedCacheViewer.empty.openFile=Open file...
203203
normalizedCacheViewer.empty.pullFromDevice=Pull from device
204-
normalizedCacheViewer.records.table.key=Key
204+
normalizedCacheViewer.records.table.key=Cache Key
205205
normalizedCacheViewer.records.table.size=Size
206206
normalizedCacheViewer.fields.list.empty=[empty]
207207
normalizedCacheViewer.fields.list.single=[1 item]

libraries/apollo-api/src/commonMain/kotlin/com/apollographql/apollo3/api/CompiledGraphQL.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ class CompiledField internal constructor(
6868

6969
val value = argument.value.getOrThrow()
7070
return if (value is CompiledVariable) {
71-
if (variables.valueMap.containsKey(value.name)) {
71+
if (variables.valueMap.containsKey(value.name)) {
7272
Optional.present(variables.valueMap[value.name])
7373
} else {
7474
// this argument has a variable value that is absent
@@ -100,7 +100,7 @@ class CompiledField internal constructor(
100100
/**
101101
* Returns a String containing the name of this field as well as encoded arguments. For an example:
102102
* `hero({"episode": "Jedi"})`
103-
* This is mostly used internally to compute records.
103+
* This is mostly used internally to compute field keys / cache keys.
104104
*
105105
* ## Note1:
106106
* The argument defaultValues are not added to the name. If the schema changes from:

libraries/apollo-api/src/commonMain/kotlin/com/apollographql/apollo3/exception/Exceptions.kt

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@ sealed class ApolloException(message: String? = null, cause: Throwable? = null)
1717
/**
1818
* A generic exception used when there is no additional context besides the message.
1919
*/
20-
class DefaultApolloException(message: String? = null, cause: Throwable? = null): ApolloException(message, cause)
20+
class DefaultApolloException(message: String? = null, cause: Throwable? = null) : ApolloException(message, cause)
2121

2222
/**
2323
* No data was found
2424
*/
25-
class NoDataException(cause: Throwable?): ApolloException("No data was found", cause)
25+
class NoDataException(cause: Throwable?) : ApolloException("No data was found", cause)
2626

2727
/**
2828
* An I/O error happened: socket closed, DNS issue, TLS problem, file not found, etc...
@@ -118,7 +118,7 @@ class JsonDataException(message: String) : ApolloException(message)
118118
*
119119
* Due to the way the parsers work, it is not possible to distinguish between both cases.
120120
*/
121-
class NullOrMissingField(message: String): ApolloException(message)
121+
class NullOrMissingField(message: String) : ApolloException(message)
122122

123123
/**
124124
* The response could not be parsed because of an I/O exception.
@@ -132,8 +132,8 @@ class NullOrMissingField(message: String): ApolloException(message)
132132
@Deprecated("ApolloParseException was only used for I/O exceptions and is now mapped to ApolloNetworkException.")
133133
class ApolloParseException(message: String? = null, cause: Throwable? = null) : ApolloException(message = message, cause = cause)
134134

135-
class ApolloGraphQLException(val error: Error): ApolloException("GraphQL error: '${error.message}'") {
136-
constructor(errors: List<Error>): this(errors.first())
135+
class ApolloGraphQLException(val error: Error) : ApolloException("GraphQL error: '${error.message}'") {
136+
constructor(errors: List<Error>) : this(errors.first())
137137

138138
@Deprecated("Use error instead", level = DeprecationLevel.ERROR)
139139
val errors: List<Error> = listOf(error)
@@ -143,10 +143,17 @@ class ApolloGraphQLException(val error: Error): ApolloException("GraphQL error:
143143
* An object/field was missing in the cache
144144
* If [fieldName] is null, it means a reference to an object could not be resolved
145145
*/
146-
147146
class CacheMissException @ApolloInternal constructor(
147+
/**
148+
* The cache key to the missing object, or to the parent of the missing field if [fieldName] is not null.
149+
*/
148150
val key: String,
151+
152+
/**
153+
* The field key that was missing. If null, it means the object referenced by [key] was missing.
154+
*/
149155
val fieldName: String? = null,
156+
150157
stale: Boolean = false,
151158
) : ApolloException(message = message(key, fieldName, stale)) {
152159

@@ -156,14 +163,14 @@ class CacheMissException @ApolloInternal constructor(
156163
constructor(key: String, fieldName: String?) : this(key, fieldName, false)
157164

158165
companion object {
159-
internal fun message(key: String?, fieldName: String?, stale: Boolean): String {
160-
return if (fieldName == null) {
161-
"Object '$key' not found"
166+
internal fun message(cacheKey: String?, fieldKey: String?, stale: Boolean): String {
167+
return if (fieldKey == null) {
168+
"Object '$cacheKey' not found"
162169
} else {
163170
if (stale) {
164-
"Field '$fieldName' on object '$key' is stale"
171+
"Field '$fieldKey' on object '$cacheKey' is stale"
165172
} else {
166-
"Object '$key' has no field named '$fieldName'"
173+
"Object '$cacheKey' has no field named '$fieldKey'"
167174
}
168175
}
169176
}

0 commit comments

Comments
 (0)