@apollo/client 4.1.6 → 4.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/__cjs/link/ws/index.cjs +9 -1
  3. package/__cjs/link/ws/index.cjs.map +1 -1
  4. package/__cjs/link/ws/index.d.cts +1 -1
  5. package/__cjs/react/hooks/useSubscription.cjs +1 -1
  6. package/__cjs/react/hooks/useSubscription.cjs.map +1 -1
  7. package/__cjs/react/hooks/useSubscription.d.cts +2 -2
  8. package/__cjs/version.cjs +1 -1
  9. package/link/ws/index.d.ts +1 -1
  10. package/link/ws/index.js +9 -1
  11. package/link/ws/index.js.map +1 -1
  12. package/package.json +3 -2
  13. package/react/hooks/useSubscription.d.ts +2 -2
  14. package/react/hooks/useSubscription.js +1 -1
  15. package/react/hooks/useSubscription.js.map +1 -1
  16. package/react/hooks-compiled/useSubscription.d.ts +2 -2
  17. package/react/hooks-compiled/useSubscription.js +1 -1
  18. package/react/hooks-compiled/useSubscription.js.map +1 -1
  19. package/skills/apollo-client/SKILL.md +168 -0
  20. package/skills/apollo-client/references/caching.md +560 -0
  21. package/skills/apollo-client/references/error-handling.md +344 -0
  22. package/skills/apollo-client/references/fragments.md +804 -0
  23. package/skills/apollo-client/references/integration-client.md +340 -0
  24. package/skills/apollo-client/references/integration-nextjs.md +325 -0
  25. package/skills/apollo-client/references/integration-react-router.md +256 -0
  26. package/skills/apollo-client/references/integration-tanstack-start.md +378 -0
  27. package/skills/apollo-client/references/mutations.md +549 -0
  28. package/skills/apollo-client/references/queries.md +416 -0
  29. package/skills/apollo-client/references/state-management.md +428 -0
  30. package/skills/apollo-client/references/suspense-hooks.md +773 -0
  31. package/skills/apollo-client/references/troubleshooting.md +487 -0
  32. package/skills/apollo-client/references/typescript-codegen.md +133 -0
  33. package/version.js +1 -1
@@ -0,0 +1,560 @@
1
+ # Caching Reference
2
+
3
+ ## Table of Contents
4
+
5
+ - [InMemoryCache Setup](#inmemorycache-setup)
6
+ - [Cache Normalization](#cache-normalization)
7
+ - [Type Policies](#type-policies)
8
+ - [Field Policies](#field-policies)
9
+ - [Pagination](#pagination)
10
+ - [Cache Manipulation](#cache-manipulation)
11
+ - [Garbage Collection](#garbage-collection)
12
+
13
+ ## InMemoryCache Setup
14
+
15
+ ### Basic Configuration
16
+
17
+ ```typescript
18
+ import { InMemoryCache } from "@apollo/client";
19
+
20
+ const cache = new InMemoryCache({
21
+ // Custom type policies
22
+ typePolicies: {
23
+ Query: {
24
+ fields: {
25
+ // Query-level field policies
26
+ },
27
+ },
28
+ User: {
29
+ keyFields: ["id"],
30
+ fields: {
31
+ // User-level field policies
32
+ },
33
+ },
34
+ },
35
+
36
+ // Custom type name handling (rare)
37
+ possibleTypes: {
38
+ Character: ["Human", "Droid"],
39
+ Node: ["User", "Post", "Comment"],
40
+ },
41
+ });
42
+ ```
43
+
44
+ ### Constructor Options
45
+
46
+ ```typescript
47
+ new InMemoryCache({
48
+ // Define how types are identified in cache
49
+ typePolicies: {
50
+ /* ... */
51
+ },
52
+
53
+ // Interface/union type mappings between supertypes and their subtypes
54
+ possibleTypes: {
55
+ /* ... */
56
+ },
57
+
58
+ // Custom function to generate cache IDs (rare)
59
+ dataIdFromObject: (object) => {
60
+ if (object.__typename === "Book") {
61
+ return `Book:${object.isbn}`;
62
+ }
63
+ return defaultDataIdFromObject(object);
64
+ },
65
+ });
66
+ ```
67
+
68
+ ## Cache Normalization
69
+
70
+ Apollo Client normalizes data by splitting query results into individual objects and storing them by unique identifier.
71
+
72
+ ### How Normalization Works
73
+
74
+ ```graphql
75
+ # Query
76
+ query GetPost {
77
+ post(id: "1") {
78
+ id
79
+ title
80
+ author {
81
+ id
82
+ name
83
+ }
84
+ }
85
+ }
86
+ ```
87
+
88
+ ```typescript
89
+ // Normalized cache structure
90
+ {
91
+ 'Post:1': {
92
+ __typename: 'Post',
93
+ id: '1',
94
+ title: 'Hello World',
95
+ author: { __ref: 'User:42' }
96
+ },
97
+ 'User:42': {
98
+ __typename: 'User',
99
+ id: '42',
100
+ name: 'John'
101
+ },
102
+ ROOT_QUERY: {
103
+ 'post({"id":"1"})': { __ref: 'Post:1' }
104
+ }
105
+ }
106
+ ```
107
+
108
+ ### Benefits of Normalization
109
+
110
+ 1. **Automatic updates**: When `User:42` is updated anywhere, all components showing that user update
111
+ 2. **Deduplication**: Same objects aren't stored multiple times
112
+ 3. **Efficient updates**: Only changed objects trigger re-renders
113
+
114
+ ## Type Policies
115
+
116
+ ### keyFields
117
+
118
+ Customize how objects are identified in the cache.
119
+
120
+ ```typescript
121
+ const cache = new InMemoryCache({
122
+ typePolicies: {
123
+ // Use ISBN instead of id for books
124
+ Book: {
125
+ keyFields: ["isbn"],
126
+ },
127
+
128
+ // Composite key
129
+ UserSession: {
130
+ keyFields: ["userId", "deviceId"],
131
+ },
132
+
133
+ // Nested key
134
+ Review: {
135
+ keyFields: ["book", ["isbn"], "reviewer", ["id"]],
136
+ },
137
+
138
+ // No key fields (singleton, only one object in cache per type)
139
+ AppSettings: {
140
+ keyFields: [],
141
+ },
142
+
143
+ // Disable normalization (objects of this type will be stored with their
144
+ // parent entity. The same object might end up multiple times in the cache
145
+ // and run out of sync. Use with caution, only if this object really relates
146
+ // to a property of their parent entity and cannot exist on its own.)
147
+ Address: {
148
+ keyFields: false,
149
+ },
150
+ },
151
+ });
152
+ ```
153
+
154
+ ### merge Functions
155
+
156
+ Control how incoming data merges with existing data.
157
+
158
+ ```typescript
159
+ const cache = new InMemoryCache({
160
+ typePolicies: {
161
+ User: {
162
+ fields: {
163
+ // Deep merge profile object
164
+ profile: {
165
+ merge: true, // Shorthand for deep merge
166
+ },
167
+
168
+ // Custom merge logic
169
+ notifications: {
170
+ merge(existing = [], incoming, { mergeObjects }) {
171
+ // Prepend new notifications
172
+ return [...incoming, ...existing];
173
+ },
174
+ },
175
+ },
176
+ },
177
+ },
178
+ });
179
+ ```
180
+
181
+ ## Field Policies
182
+
183
+ ### read Function
184
+
185
+ Transform cached data when reading.
186
+
187
+ ```typescript
188
+ const cache = new InMemoryCache({
189
+ typePolicies: {
190
+ User: {
191
+ fields: {
192
+ // Computed field
193
+ fullName: {
194
+ read(_, { readField }) {
195
+ const firstName = readField("firstName");
196
+ const lastName = readField("lastName");
197
+ return `${firstName} ${lastName}`;
198
+ },
199
+ },
200
+
201
+ // Transform existing field
202
+ birthDate: {
203
+ read(existing) {
204
+ return existing ? new Date(existing) : null;
205
+ },
206
+ },
207
+
208
+ // Default value
209
+ role: {
210
+ read(existing = "USER") {
211
+ return existing;
212
+ },
213
+ },
214
+ },
215
+ },
216
+ },
217
+ });
218
+ ```
219
+
220
+ ### merge Function
221
+
222
+ Control how incoming data is stored.
223
+
224
+ ```typescript
225
+ const cache = new InMemoryCache({
226
+ typePolicies: {
227
+ User: {
228
+ fields: {
229
+ // Accumulate items instead of replacing
230
+ friends: {
231
+ merge(existing = [], incoming) {
232
+ return [...existing, ...incoming];
233
+ },
234
+ },
235
+
236
+ // Merge objects deeply
237
+ settings: {
238
+ merge(existing, incoming, { mergeObjects }) {
239
+ return mergeObjects(existing, incoming);
240
+ },
241
+ },
242
+ },
243
+ },
244
+
245
+ Query: {
246
+ fields: {
247
+ // Merge paginated results
248
+ posts: {
249
+ keyArgs: ["category"], // Only category affects cache key
250
+ merge(existing = { items: [] }, incoming) {
251
+ return {
252
+ ...incoming,
253
+ items: [...existing.items, ...incoming.items],
254
+ };
255
+ },
256
+ },
257
+ },
258
+ },
259
+ },
260
+ });
261
+ ```
262
+
263
+ ### keyArgs
264
+
265
+ Control which arguments affect cache storage.
266
+
267
+ ```typescript
268
+ const cache = new InMemoryCache({
269
+ typePolicies: {
270
+ Query: {
271
+ fields: {
272
+ // Different cache entry per userId only
273
+ // (limit, offset don't create new entries)
274
+ userPosts: {
275
+ keyArgs: ["userId"],
276
+ },
277
+
278
+ // No arguments affect cache key
279
+ // (useful for pagination)
280
+ feed: {
281
+ keyArgs: false,
282
+ },
283
+
284
+ // Nested argument
285
+ search: {
286
+ keyArgs: ["filter", ["category", "status"]],
287
+ },
288
+ },
289
+ },
290
+ },
291
+ });
292
+ ```
293
+
294
+ ## Pagination
295
+
296
+ ### Offset-Based Pagination
297
+
298
+ ```typescript
299
+ import { offsetLimitPagination } from "@apollo/client/utilities";
300
+
301
+ const cache = new InMemoryCache({
302
+ typePolicies: {
303
+ Query: {
304
+ fields: {
305
+ posts: offsetLimitPagination(),
306
+
307
+ // With key arguments
308
+ userPosts: offsetLimitPagination(["userId"]),
309
+ },
310
+ },
311
+ },
312
+ });
313
+ ```
314
+
315
+ ### Cursor-Based Pagination (Relay Style)
316
+
317
+ ```typescript
318
+ import { relayStylePagination } from "@apollo/client/utilities";
319
+
320
+ const cache = new InMemoryCache({
321
+ typePolicies: {
322
+ Query: {
323
+ fields: {
324
+ posts: relayStylePagination(),
325
+
326
+ // With key arguments
327
+ userPosts: relayStylePagination(["userId"]),
328
+ },
329
+ },
330
+ },
331
+ });
332
+ ```
333
+
334
+ ### Custom Pagination
335
+
336
+ ```typescript
337
+ const cache = new InMemoryCache({
338
+ typePolicies: {
339
+ Query: {
340
+ fields: {
341
+ feed: {
342
+ keyArgs: false,
343
+
344
+ merge(existing, incoming, { args }) {
345
+ const merged = existing ? existing.slice(0) : [];
346
+ const offset = args?.offset ?? 0;
347
+
348
+ for (let i = 0; i < incoming.length; i++) {
349
+ merged[offset + i] = incoming[i];
350
+ }
351
+
352
+ return merged;
353
+ },
354
+
355
+ read(existing, { args }) {
356
+ const offset = args?.offset ?? 0;
357
+ const limit = args?.limit ?? existing?.length ?? 0;
358
+ return existing?.slice(offset, offset + limit);
359
+ },
360
+ },
361
+ },
362
+ },
363
+ },
364
+ });
365
+ ```
366
+
367
+ ### fetchMore for Pagination
368
+
369
+ ```tsx
370
+ function PostList() {
371
+ const { data, fetchMore, loading } = useQuery(GET_POSTS, {
372
+ variables: { offset: 0, limit: 10 },
373
+ });
374
+
375
+ const loadMore = () => {
376
+ fetchMore({
377
+ variables: {
378
+ offset: data.posts.length,
379
+ },
380
+ // With proper type policies, no updateQuery needed
381
+ });
382
+ };
383
+
384
+ return (
385
+ <div>
386
+ {data?.posts.map((post) => <PostCard key={post.id} post={post} />)}
387
+ <button onClick={loadMore} disabled={loading}>
388
+ Load More
389
+ </button>
390
+ </div>
391
+ );
392
+ }
393
+ ```
394
+
395
+ ## Cache Manipulation
396
+
397
+ ### cache.readQuery
398
+
399
+ ```typescript
400
+ // Read data from cache
401
+ const data = cache.readQuery({
402
+ query: GET_TODOS,
403
+ });
404
+
405
+ // With variables
406
+ const userData = cache.readQuery({
407
+ query: GET_USER,
408
+ variables: { id: "1" },
409
+ });
410
+ ```
411
+
412
+ ### cache.writeQuery
413
+
414
+ ```typescript
415
+ // Write data to cache
416
+ cache.writeQuery({
417
+ query: GET_TODOS,
418
+ data: {
419
+ todos: [
420
+ { __typename: "Todo", id: "1", text: "Buy milk", completed: false },
421
+ ],
422
+ },
423
+ });
424
+
425
+ // With variables
426
+ cache.writeQuery({
427
+ query: GET_USER,
428
+ variables: { id: "1" },
429
+ data: {
430
+ user: { __typename: "User", id: "1", name: "John" },
431
+ },
432
+ });
433
+ ```
434
+
435
+ ### cache.readFragment / cache.writeFragment
436
+
437
+ ```typescript
438
+ // Read a specific object - use cache.identify for safety
439
+ const user = cache.readFragment({
440
+ id: cache.identify({ __typename: "User", id: "1" }),
441
+ fragment: gql`
442
+ fragment UserFragment on User {
443
+ id
444
+ name
445
+ email
446
+ }
447
+ `,
448
+ });
449
+
450
+ // Apollo Client 4.1+: Use 'from' parameter (recommended)
451
+ const user = cache.readFragment({
452
+ from: { __typename: "User", id: "1" },
453
+ fragment: gql`
454
+ fragment UserFragment on User {
455
+ id
456
+ name
457
+ email
458
+ }
459
+ `,
460
+ });
461
+
462
+ // Update a specific object
463
+ cache.writeFragment({
464
+ id: cache.identify({ __typename: "User", id: "1" }),
465
+ fragment: gql`
466
+ fragment UpdateUser on User {
467
+ name
468
+ }
469
+ `,
470
+ data: {
471
+ name: "Jane",
472
+ },
473
+ });
474
+
475
+ // Apollo Client 4.1+: Use 'from' parameter (recommended)
476
+ cache.writeFragment({
477
+ from: { __typename: "User", id: "1" },
478
+ fragment: gql`
479
+ fragment UpdateUser on User {
480
+ name
481
+ }
482
+ `,
483
+ data: {
484
+ name: "Jane",
485
+ },
486
+ });
487
+ ```
488
+
489
+ ### cache.modify
490
+
491
+ ```typescript
492
+ // Modify fields directly
493
+ cache.modify({
494
+ id: cache.identify(user),
495
+ fields: {
496
+ // Set new value
497
+ name: () => "New Name",
498
+
499
+ // Transform existing value
500
+ postCount: (existing) => existing + 1,
501
+
502
+ // Delete field
503
+ temporaryField: (_, { DELETE }) => DELETE,
504
+
505
+ // Add to array
506
+ friends: (existing, { toReference }) => [
507
+ ...existing,
508
+ toReference({ __typename: "User", id: "2" }),
509
+ ],
510
+ },
511
+ });
512
+ ```
513
+
514
+ ### cache.evict
515
+
516
+ ```typescript
517
+ // Remove object from cache
518
+ cache.evict({ id: "User:1" });
519
+
520
+ // Remove specific field
521
+ cache.evict({ id: "User:1", fieldName: "friends" });
522
+
523
+ // Remove with broadcast (trigger re-renders)
524
+ cache.evict({ id: "User:1", broadcast: true });
525
+ ```
526
+
527
+ ## Garbage Collection
528
+
529
+ ### Manual Garbage Collection
530
+
531
+ ```typescript
532
+ // After evicting objects, clean up dangling references
533
+ cache.evict({ id: "User:1" });
534
+ cache.gc();
535
+ ```
536
+
537
+ ### Retaining Objects
538
+
539
+ ```typescript
540
+ // Prevent objects from being garbage collected
541
+ const release = cache.retain("User:1");
542
+
543
+ // Later, allow GC
544
+ release();
545
+ cache.gc();
546
+ ```
547
+
548
+ ### Inspecting Cache
549
+
550
+ ```typescript
551
+ // Get all cached data
552
+ const cacheContents = cache.extract();
553
+
554
+ // Restore cache state
555
+ cache.restore(previousCacheContents);
556
+
557
+ // Get identified object cache key
558
+ const userId = cache.identify({ __typename: "User", id: "1" });
559
+ // Returns: 'User:1'
560
+ ```