@apollo/client 4.3.0-alpha.3 → 4.3.0-alpha.4

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 (118) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/__cjs/cache/core/types/Cache.d.cts +25 -0
  3. package/__cjs/cache/index.cjs.map +1 -1
  4. package/__cjs/cache/index.d.cts +1 -1
  5. package/__cjs/cache/inmemory/inMemoryCache.cjs +4 -0
  6. package/__cjs/cache/inmemory/inMemoryCache.cjs.map +1 -1
  7. package/__cjs/cache/inmemory/inMemoryCache.d.cts +7 -3
  8. package/__cjs/cache/inmemory/readFromStore.cjs +453 -42
  9. package/__cjs/cache/inmemory/readFromStore.cjs.map +1 -1
  10. package/__cjs/cache/inmemory/readFromStore.d.cts +11 -2
  11. package/__cjs/cache/inmemory/types.d.cts +11 -1
  12. package/__cjs/cache/inmemory/writeToStore.cjs +1 -5
  13. package/__cjs/cache/inmemory/writeToStore.cjs.map +1 -1
  14. package/__cjs/core/QueryInfo.cjs +86 -172
  15. package/__cjs/core/QueryInfo.cjs.map +1 -1
  16. package/__cjs/core/QueryInfo.d.cts +8 -2
  17. package/__cjs/core/QueryManager.cjs +3 -1
  18. package/__cjs/core/QueryManager.cjs.map +1 -1
  19. package/__cjs/incremental/handlers/defer20220824.cjs.map +1 -1
  20. package/__cjs/incremental/handlers/defer20220824.d.cts +2 -4
  21. package/__cjs/incremental/handlers/graphql17Alpha9.cjs +20 -12
  22. package/__cjs/incremental/handlers/graphql17Alpha9.cjs.map +1 -1
  23. package/__cjs/incremental/handlers/graphql17Alpha9.d.cts +5 -5
  24. package/__cjs/incremental/types.d.cts +16 -3
  25. package/__cjs/utilities/caching/sizes.cjs.map +1 -1
  26. package/__cjs/utilities/caching/sizes.d.cts +39 -1
  27. package/__cjs/utilities/internal/StreamArrayState.cjs +27 -0
  28. package/__cjs/utilities/internal/StreamArrayState.cjs.map +1 -0
  29. package/__cjs/utilities/internal/StreamArrayState.d.cts +12 -0
  30. package/__cjs/utilities/internal/constants.cjs +20 -1
  31. package/__cjs/utilities/internal/constants.cjs.map +1 -1
  32. package/__cjs/utilities/internal/constants.d.cts +19 -0
  33. package/__cjs/utilities/internal/getFragmentFromSelection.cjs +1 -1
  34. package/__cjs/utilities/internal/getFragmentFromSelection.cjs.map +1 -1
  35. package/__cjs/utilities/internal/getMemoryInternals.cjs +6 -0
  36. package/__cjs/utilities/internal/getMemoryInternals.cjs.map +1 -1
  37. package/__cjs/utilities/internal/getMemoryInternals.d.cts +4 -0
  38. package/__cjs/utilities/internal/index.cjs +9 -4
  39. package/__cjs/utilities/internal/index.cjs.map +1 -1
  40. package/__cjs/utilities/internal/index.d.cts +5 -3
  41. package/__cjs/utilities/internal/isDeferredFragment.cjs +19 -4
  42. package/__cjs/utilities/internal/isDeferredFragment.cjs.map +1 -1
  43. package/__cjs/utilities/internal/isDeferredFragment.d.cts +6 -1
  44. package/__cjs/utilities/internal/isStreamField.cjs +37 -0
  45. package/__cjs/utilities/internal/isStreamField.cjs.map +1 -0
  46. package/__cjs/utilities/internal/isStreamField.d.cts +9 -0
  47. package/__cjs/utilities/internal/makeStreamInfoTrie.cjs +17 -0
  48. package/__cjs/utilities/internal/makeStreamInfoTrie.cjs.map +1 -0
  49. package/__cjs/utilities/internal/makeStreamInfoTrie.d.cts +8 -0
  50. package/__cjs/utilities/internal/types/DeferInfo.cjs +3 -0
  51. package/__cjs/utilities/internal/types/DeferInfo.cjs.map +1 -0
  52. package/__cjs/utilities/internal/types/DeferInfo.d.cts +10 -0
  53. package/__cjs/utilities/internal/types/StreamInfoTrie.d.cts +2 -0
  54. package/__cjs/version.cjs +1 -1
  55. package/cache/core/types/Cache.d.ts +25 -0
  56. package/cache/core/types/Cache.js.map +1 -1
  57. package/cache/index.d.ts +1 -1
  58. package/cache/index.js.map +1 -1
  59. package/cache/inmemory/inMemoryCache.d.ts +7 -3
  60. package/cache/inmemory/inMemoryCache.js +5 -1
  61. package/cache/inmemory/inMemoryCache.js.map +1 -1
  62. package/cache/inmemory/readFromStore.d.ts +11 -2
  63. package/cache/inmemory/readFromStore.js +454 -43
  64. package/cache/inmemory/readFromStore.js.map +1 -1
  65. package/cache/inmemory/types.d.ts +12 -2
  66. package/cache/inmemory/types.js.map +1 -1
  67. package/cache/inmemory/writeToStore.js +2 -6
  68. package/cache/inmemory/writeToStore.js.map +1 -1
  69. package/core/QueryInfo.d.ts +8 -2
  70. package/core/QueryInfo.js +87 -173
  71. package/core/QueryInfo.js.map +1 -1
  72. package/core/QueryManager.js +3 -1
  73. package/core/QueryManager.js.map +1 -1
  74. package/incremental/handlers/defer20220824.d.ts +2 -4
  75. package/incremental/handlers/defer20220824.js.map +1 -1
  76. package/incremental/handlers/graphql17Alpha9.d.ts +5 -5
  77. package/incremental/handlers/graphql17Alpha9.js +21 -13
  78. package/incremental/handlers/graphql17Alpha9.js.map +1 -1
  79. package/incremental/types.d.ts +16 -3
  80. package/incremental/types.js.map +1 -1
  81. package/package.json +1 -1
  82. package/utilities/caching/sizes.d.ts +39 -1
  83. package/utilities/caching/sizes.js.map +1 -1
  84. package/utilities/internal/StreamArrayState.d.ts +12 -0
  85. package/utilities/internal/StreamArrayState.js +23 -0
  86. package/utilities/internal/StreamArrayState.js.map +1 -0
  87. package/utilities/internal/constants.d.ts +19 -0
  88. package/utilities/internal/constants.js +19 -0
  89. package/utilities/internal/constants.js.map +1 -1
  90. package/utilities/internal/getFragmentFromSelection.js +1 -1
  91. package/utilities/internal/getFragmentFromSelection.js.map +1 -1
  92. package/utilities/internal/getMemoryInternals.d.ts +4 -0
  93. package/utilities/internal/getMemoryInternals.js +6 -0
  94. package/utilities/internal/getMemoryInternals.js.map +1 -1
  95. package/utilities/internal/index.d.ts +5 -3
  96. package/utilities/internal/index.js +4 -2
  97. package/utilities/internal/index.js.map +1 -1
  98. package/utilities/internal/isDeferredFragment.d.ts +7 -2
  99. package/utilities/internal/isDeferredFragment.js +19 -4
  100. package/utilities/internal/isDeferredFragment.js.map +1 -1
  101. package/utilities/internal/isStreamField.d.ts +9 -0
  102. package/utilities/internal/isStreamField.js +34 -0
  103. package/utilities/internal/isStreamField.js.map +1 -0
  104. package/utilities/internal/makeStreamInfoTrie.d.ts +8 -0
  105. package/utilities/internal/makeStreamInfoTrie.js +14 -0
  106. package/utilities/internal/makeStreamInfoTrie.js.map +1 -0
  107. package/utilities/internal/types/DeferInfo.d.ts +10 -0
  108. package/utilities/internal/types/DeferInfo.js +2 -0
  109. package/utilities/internal/types/DeferInfo.js.map +1 -0
  110. package/utilities/internal/types/StreamInfoTrie.d.ts +2 -0
  111. package/utilities/internal/types/StreamInfoTrie.js.map +1 -1
  112. package/version.js +1 -1
  113. package/__cjs/utilities/internal/collectSiblingFields.cjs +0 -61
  114. package/__cjs/utilities/internal/collectSiblingFields.cjs.map +0 -1
  115. package/__cjs/utilities/internal/collectSiblingFields.d.cts +0 -21
  116. package/utilities/internal/collectSiblingFields.d.ts +0 -21
  117. package/utilities/internal/collectSiblingFields.js +0 -58
  118. package/utilities/internal/collectSiblingFields.js.map +0 -1
@@ -5,6 +5,8 @@ const {
5
5
 
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
7
  exports.StoreReader = void 0;
8
+ const equality_1 = require("@wry/equality");
9
+ const trie_1 = require("@wry/trie");
8
10
  const graphql_1 = require("graphql");
9
11
  const optimism_1 = require("optimism");
10
12
  const utilities_1 = require("@apollo/client/utilities");
@@ -22,8 +24,11 @@ class StoreReader {
22
24
  executeSelectionSet;
23
25
  // cached version of executeSubSelectedArray
24
26
  executeSubSelectedArray;
27
+ prunePartialStreamArray;
28
+ prunePartialBoundaries;
25
29
  config;
26
30
  knownResults = new WeakMap();
31
+ keyMaker = new trie_1.Trie();
27
32
  constructor(config) {
28
33
  this.config = config;
29
34
  // memoized functions in this class will be "garbage-collected"
@@ -66,47 +71,143 @@ class StoreReader {
66
71
  }
67
72
  },
68
73
  });
74
+ this.prunePartialBoundaries = (0, optimism_1.wrap)((options) => this.prunePartialBoundariesImpl(options), {
75
+ max: utilities_1.cacheSizes["inMemoryCache.prunePartialBoundaries"] ||
76
+ 20000 /* defaultCacheSizes["inMemoryCache.prunePartialBoundaries"] */,
77
+ makeCacheKey: ({ boundaries, context, selectionSet }) => {
78
+ if ((0, entityStore_js_1.supportsResultCaching)(context.store)) {
79
+ return this.keyMaker.lookup(selectionSet, boundaries, context.streamInfo, context.deferInfo);
80
+ }
81
+ },
82
+ });
83
+ this.prunePartialStreamArray = (0, optimism_1.wrap)((options) => {
84
+ const { field, context, path } = options;
85
+ if ((0, internal_1.isStreamField)(field, context.variables)) {
86
+ context.streamInfo?.lookupArray(path).state.depend();
87
+ }
88
+ return this.prunePartialStreamArrayImpl(options);
89
+ }, {
90
+ max: utilities_1.cacheSizes["inMemoryCache.prunePartialStreamArray"] ||
91
+ 20000 /* defaultCacheSizes["inMemoryCache.prunePartialStreamArray"] */,
92
+ makeCacheKey: ({ field, context, boundaries }) => {
93
+ if ((0, entityStore_js_1.supportsResultCaching)(context.store)) {
94
+ return this.keyMaker.lookup(field, boundaries, context.streamInfo, context.deferInfo);
95
+ }
96
+ },
97
+ });
69
98
  }
70
- /**
71
- * Given a store and a query, return as much of the result as possible and
72
- * identify if any data was missing from the store.
73
- */
74
- diffQueryAgainstStore({ store, query, rootId = "ROOT_QUERY", variables, returnPartialData = true, }) {
99
+ diffQueryAgainstStore({ store, query, rootId = "ROOT_QUERY", variables, returnPartialData = true, [internal_1.handleIncrementalSymbol]: handleIncremental, }) {
75
100
  const policies = this.config.cache.policies;
76
101
  variables = {
77
102
  ...(0, internal_1.getDefaultValues)((0, internal_1.getQueryDefinition)(query)),
78
103
  ...variables,
79
104
  };
80
105
  const rootRef = (0, internal_1.makeReference)(rootId);
81
- const execResult = this.executeSelectionSet({
106
+ const context = {
107
+ store,
108
+ query,
109
+ policies,
110
+ variables,
111
+ varString: (0, utilities_1.canonicalStringify)(variables),
112
+ ...(0, helpers_js_1.extractFragmentContext)(query, this.config.fragments),
113
+ ...(typeof handleIncremental === "object" ? handleIncremental : (undefined)),
114
+ };
115
+ let execResult = this.executeSelectionSet({
82
116
  selectionSet: (0, internal_1.getMainDefinition)(query).selectionSet,
83
117
  objectOrReference: rootRef,
84
118
  enclosingRef: rootRef,
85
- context: {
86
- store,
87
- query,
88
- policies,
89
- variables,
90
- varString: (0, utilities_1.canonicalStringify)(variables),
91
- ...(0, helpers_js_1.extractFragmentContext)(query, this.config.fragments),
92
- },
119
+ context,
93
120
  });
94
- let missing;
95
- if (execResult.missing) {
96
- missing = new common_js_1.MissingFieldError(firstMissing(execResult.missing), execResult.missing, query, variables);
121
+ // Since executeSelectionSet doesn't know about returnPartialData, we need
122
+ // to perform a 2nd pass over the result to prune any fields inside
123
+ // partial defer boundaries. The "deferPartial" data state tells us that the
124
+ // only part of the result that contributed to its partiality is data inside
125
+ // a defer boundary.
126
+ if (handleIncremental &&
127
+ (execResult.dataState === "deferPartial" ||
128
+ execResult.dataState === "streamPartial" ||
129
+ // If the last cache write repaired a partial @stream array to a
130
+ // complete array, the stream array might contain stale entries after
131
+ // the last written value. We only want to deliver the results up to
132
+ // the index the network wrote so we need to prune it too.
133
+ context.streamInfo ||
134
+ // The network hasn't delivered these @defer boundaries yet, so prune
135
+ // the (possibly complete) cached data sitting at them.
136
+ context.deferInfo)) {
137
+ const pruned = this.prunePartialBoundaries({
138
+ selectionSet: (0, internal_1.getMainDefinition)(query).selectionSet,
139
+ data: execResult.result,
140
+ boundaries: execResult.partialBoundaries,
141
+ context,
142
+ path: [],
143
+ });
144
+ const changed = execResult.result !== pruned;
145
+ // It's possible that pruning didn't actually change the result which can
146
+ // happen if a defer boundary is misclassified as "deferPartial" instead
147
+ // of "streaming" (sibling defer boundaries with overlapping selection
148
+ // sets, one of which is complete). In this case, pruning corrects the
149
+ // dataState to streaming instead of leaving it as partial. If we tolerate
150
+ // partial results and pruning changed the result by dropping fields, then
151
+ // we want to keep the original execResult which contains the partial
152
+ // data.
153
+ if (!changed || !returnPartialData) {
154
+ const { dataState } = execResult;
155
+ // Omit `missing` property since pruning puts it in a state that doesn't
156
+ // report missing fields.
157
+ execResult = {
158
+ result: pruned,
159
+ partialBoundaries: execResult.partialBoundaries,
160
+ dataState: dataState === "deferPartial" ? "streaming"
161
+ : dataState === "streamPartial" ? "complete"
162
+ // A cached @defer boundary the network hasn't delivered was
163
+ // pruned from an otherwise complete result, so we're still
164
+ // streaming.
165
+ : context.deferInfo && changed ? "streaming"
166
+ : dataState,
167
+ };
168
+ }
97
169
  }
98
- const complete = !missing;
99
- const { result } = execResult;
100
- return {
101
- result: complete ? result
102
- : returnPartialData ?
103
- Object.keys(result).length === 0 ?
104
- null
105
- : result
106
- : null,
170
+ let { result, dataState, missing } = execResult;
171
+ // Evaluate this condition before we start mucking with dataState for the
172
+ // publicly returned value
173
+ const includeMissing = !!missing &&
174
+ // We don't need to report missing fields inside defer boundaries since
175
+ // the "streaming" dataState tells us that the only missing fields in
176
+ // the object is inside a defer boundary.
177
+ (dataState !== "streaming" || !handleIncremental);
178
+ // If we get all root @defer boundaries with an empty result, report it as
179
+ // empty instead of streaming.
180
+ if (dataState === "streaming" && Object.keys(result).length === 0) {
181
+ dataState = "empty";
182
+ }
183
+ let missingError;
184
+ if (dataState === "deferPartial" ||
185
+ dataState === "streamPartial" ||
186
+ (dataState === "streaming" && !handleIncremental)) {
187
+ dataState = "partial";
188
+ }
189
+ if (dataState === "partial" && !returnPartialData) {
190
+ dataState = "empty";
191
+ }
192
+ const complete = dataState === "complete";
193
+ const keepResult = complete ||
194
+ dataState === "streaming" ||
195
+ (returnPartialData && Object.keys(result).length);
196
+ const diffResult = {
197
+ result: keepResult ? result : null,
107
198
  complete,
108
- missing,
199
+ get missing() {
200
+ if (includeMissing) {
201
+ missingError ||= new common_js_1.MissingFieldError(firstMissing(missing), missing, query, variables);
202
+ }
203
+ return missingError;
204
+ },
109
205
  };
206
+ if (handleIncremental) {
207
+ diffResult.dataState =
208
+ dataState;
209
+ }
210
+ return diffResult;
110
211
  }
111
212
  isFresh(result, parent, selectionSet, context) {
112
213
  if ((0, entityStore_js_1.supportsResultCaching)(context.store) &&
@@ -125,14 +226,18 @@ class StoreReader {
125
226
  !context.store.has(objectOrReference.__ref)) {
126
227
  return {
127
228
  result: {},
229
+ dataState: "empty",
128
230
  missing: `Dangling reference to missing ${objectOrReference.__ref} object`,
231
+ partialBoundaries: new PartialBoundaries(),
129
232
  };
130
233
  }
131
234
  const { variables, policies, store } = context;
132
235
  const typename = store.getFieldValue(objectOrReference, "__typename");
133
236
  const objectsToMerge = [];
237
+ let dataState;
134
238
  let missing;
135
239
  const missingMerger = new internal_1.DeepMerger();
240
+ const partialBoundaries = new PartialBoundaries();
136
241
  if (typeof typename === "string" && !policies.rootIdsByTypename[typename]) {
137
242
  // Ensure we always include a default value for the __typename
138
243
  // field, if we have one. Note that this field can be overridden by other
@@ -145,7 +250,7 @@ class StoreReader {
145
250
  [resultName]: result.missing,
146
251
  });
147
252
  }
148
- return result.result;
253
+ return result;
149
254
  }
150
255
  const workSet = new Set(selectionSet.selections);
151
256
  workSet.forEach((selection) => {
@@ -163,25 +268,39 @@ class StoreReader {
163
268
  const resultName = (0, internal_1.resultKeyNameFromField)(selection);
164
269
  if (fieldValue === void 0) {
165
270
  if (!utilities_1.addTypenameToDocument.added(selection)) {
271
+ const id = (0, utilities_1.isReference)(objectOrReference) ? objectOrReference.__ref
272
+ : objectOrReference ? policies.identify(objectOrReference)[0]
273
+ : undefined;
166
274
  missing = missingMerger.merge(missing, {
167
- [resultName]: `Can't find field '${selection.name.value}' on ${(0, utilities_1.isReference)(objectOrReference) ?
168
- objectOrReference.__ref + " object"
169
- : "object " + JSON.stringify(objectOrReference, null, 2)}`,
275
+ [resultName]: `Can't find field '${selection.name.value}' on ${id ?
276
+ `${id} object`
277
+ : `object ${JSON.stringify(objectOrReference || {}, null, 2)}`}`,
170
278
  });
279
+ dataState = mergeDataState(dataState, "empty");
171
280
  }
172
281
  }
173
282
  else if ((0, internal_1.isArray)(fieldValue)) {
174
283
  if (fieldValue.length > 0) {
175
- fieldValue = handleMissing(this.executeSubSelectedArray({
284
+ const execResult = handleMissing(this.executeSubSelectedArray({
176
285
  field: selection,
177
286
  array: fieldValue,
178
287
  enclosingRef,
179
288
  context,
180
289
  }), resultName);
290
+ fieldValue = execResult.result;
291
+ dataState = mergeDataState(dataState, execResult.dataState);
292
+ partialBoundaries.set(resultName, execResult.partialBoundaries);
293
+ }
294
+ else {
295
+ dataState = mergeDataState(dataState, "complete");
181
296
  }
182
297
  }
183
298
  else if (!selection.selectionSet) {
184
- // do nothing
299
+ // Auto-inserted __typename should not affect dataState (see empty
300
+ // @defer boundaries, which must stay "empty" → parent "streaming").
301
+ if (!utilities_1.addTypenameToDocument.added(selection)) {
302
+ dataState = mergeDataState(dataState, "complete");
303
+ }
185
304
  }
186
305
  else if (fieldValue != null) {
187
306
  if (environment_1.__DEV__) {
@@ -196,12 +315,28 @@ class StoreReader {
196
315
  // In this case, because we know the field has a selection set,
197
316
  // it must be trying to query a GraphQLObjectType, which is why
198
317
  // fieldValue must be != null.
199
- fieldValue = handleMissing(this.executeSelectionSet({
318
+ const execResult = handleMissing(this.executeSelectionSet({
200
319
  selectionSet: selection.selectionSet,
201
320
  objectOrReference: fieldValue,
202
321
  enclosingRef: (0, utilities_1.isReference)(fieldValue) ? fieldValue : enclosingRef,
203
322
  context,
204
323
  }), resultName);
324
+ fieldValue = execResult.result;
325
+ partialBoundaries.set(resultName, execResult.partialBoundaries);
326
+ // If the object's fields resolved to an "empty" dataState (e.g. no
327
+ // field resolved with a non-undefined value), but the fieldValue
328
+ // object itself is present, this object should be considered
329
+ // partial instead of empty. This also ensures defer boundaries that
330
+ // select this object remain as partial defer boundaries rather than
331
+ // mistakenly get reported as streaming. This is especially necessary
332
+ // when combined with GraphQL Codegen which generates its type and
333
+ // relies on the outer object to be absent when its fields haven't
334
+ // streamed in. Reporting the defer boundary as "streaming" instead of
335
+ // "partial" would otherwise have the potential to cause runtime
336
+ // crashes since the runtime values and types would not line up
337
+ // properly (types expect object to be undefined, but its instead
338
+ // present without its fields)
339
+ dataState = mergeDataState(dataState, execResult.dataState === "empty" ? "partial" : execResult.dataState);
205
340
  }
206
341
  if (fieldValue !== void 0) {
207
342
  objectsToMerge.push({ [resultName]: fieldValue });
@@ -213,12 +348,56 @@ class StoreReader {
213
348
  throw (0, invariant_1.newInvariantError)(113, selection.name.value);
214
349
  }
215
350
  if (fragment && policies.fragmentMatches(fragment, typename)) {
216
- fragment.selectionSet.selections.forEach(workSet.add, workSet);
351
+ const isDeferBoundary = (0, internal_1.isDeferredFragment)(selection, context.variables);
352
+ // Prior to 4.3, this branch just flattened the fragment's
353
+ // selectionSet into the existing workSet so that it continued
354
+ // iterating as if the fragment didn't exist. The cache is
355
+ // incremental aware as of 4.3 and as such, we need to resolve the
356
+ // per-fragment selection set so that we can properly strip partial
357
+ // defer fragment data when returnPartialData is false. We need to
358
+ // call execSelectionSetImpl directly (non-cached version) so that we
359
+ // scope the dataState correctly for its fields. Using the cached
360
+ // executeSelectionSet can result in cache poisoning when combined
361
+ // with the fragment registry where it might cache either a) an error
362
+ // thrown when a registered fragment references a named fragment that
363
+ // the query is expected to supply and doesn't or b) resolve to the
364
+ // wrong data result when combined with queries that provide different
365
+ // implementations of the same fragment (see inmemory/fragmentRegistry and
366
+ // cache.diff/incremental tests which provide guards against this
367
+ // behavior).
368
+ const execResult = this.execSelectionSetImpl({
369
+ selectionSet: fragment.selectionSet,
370
+ objectOrReference,
371
+ enclosingRef,
372
+ context,
373
+ });
374
+ const { result, dataState: nextDataState } = execResult;
375
+ partialBoundaries.merge(execResult.partialBoundaries);
376
+ if (result !== void 0) {
377
+ objectsToMerge.push(result);
378
+ }
379
+ if (execResult.missing) {
380
+ missing = missingMerger.merge(missing, execResult.missing);
381
+ }
382
+ if (isDeferBoundary && nextDataState === "partial") {
383
+ partialBoundaries.add(selection);
384
+ }
385
+ dataState = mergeDataState(dataState, isDeferBoundary ?
386
+ nextDataState === "empty" ? "streaming"
387
+ : nextDataState === "partial" ? "deferPartial"
388
+ : nextDataState
389
+ : nextDataState);
217
390
  }
218
391
  }
219
392
  });
393
+ dataState ||= "complete";
220
394
  const result = (0, internal_1.mergeDeepArray)(objectsToMerge);
221
- const finalResult = { result, missing };
395
+ const finalResult = {
396
+ result,
397
+ missing,
398
+ dataState,
399
+ partialBoundaries,
400
+ };
222
401
  const frozen = (0, internal_1.maybeDeepFreeze)(finalResult);
223
402
  // Store this result with its selection set so that we can quickly
224
403
  // recognize it again in the StoreReader#isFresh method.
@@ -229,8 +408,11 @@ class StoreReader {
229
408
  }
230
409
  // Uncached version of executeSubSelectedArray.
231
410
  execSubSelectedArrayImpl({ field, array, enclosingRef, context, }) {
411
+ let dataState = "complete";
232
412
  let missing;
233
413
  let missingMerger = new internal_1.DeepMerger();
414
+ const partialBoundaries = new PartialBoundaries();
415
+ const isStreamed = (0, internal_1.isStreamField)(field, context.variables);
234
416
  function handleMissing(childResult, i) {
235
417
  if (childResult.missing) {
236
418
  missing = missingMerger.merge(missing, { [i]: childResult.missing });
@@ -245,23 +427,36 @@ class StoreReader {
245
427
  if (item === null) {
246
428
  return null;
247
429
  }
430
+ let execResult;
248
431
  // This is a nested array, recurse
249
432
  if ((0, internal_1.isArray)(item)) {
250
- return handleMissing(this.executeSubSelectedArray({
433
+ execResult = this.executeSubSelectedArray({
251
434
  field,
252
435
  array: item,
253
436
  enclosingRef,
254
437
  context,
255
- }), i);
438
+ });
256
439
  }
257
- // This is an object, run the selection set on it
258
- if (field.selectionSet) {
259
- return handleMissing(this.executeSelectionSet({
440
+ else if (field.selectionSet) {
441
+ execResult = this.executeSelectionSet({
260
442
  selectionSet: field.selectionSet,
261
443
  objectOrReference: item,
262
444
  enclosingRef: (0, utilities_1.isReference)(item) ? item : enclosingRef,
263
445
  context,
264
- }), i);
446
+ });
447
+ }
448
+ if (execResult) {
449
+ const { dataState: nextDataState } = execResult;
450
+ partialBoundaries.set(i, nextDataState === "partial" ?
451
+ // avoid mutating the execResult partialBoundaries object
452
+ execResult.partialBoundaries.clone().add(field)
453
+ : execResult.partialBoundaries);
454
+ dataState = mergeDataState(dataState, isStreamed ?
455
+ nextDataState === "partial" ?
456
+ "streamPartial"
457
+ : nextDataState
458
+ : nextDataState);
459
+ return handleMissing(execResult, i);
265
460
  }
266
461
  if (environment_1.__DEV__) {
267
462
  assertSelectionSetForIdValue(context.store, field, item);
@@ -270,9 +465,144 @@ class StoreReader {
270
465
  });
271
466
  return {
272
467
  result: array,
468
+ dataState,
273
469
  missing,
470
+ partialBoundaries,
274
471
  };
275
472
  }
473
+ prunePartialBoundariesImpl({ boundaries, context, data, path, selectionSet, }) {
474
+ const { variables, lookupFragment, policies } = context;
475
+ if (data == null || !boundaries)
476
+ return data;
477
+ const merger = new internal_1.DeepMerger();
478
+ let changed = false;
479
+ const result = {};
480
+ // __typename might not be part of the selection set, so preserve it when
481
+ // available, otherwise it gets removed since it's never visited when
482
+ // iterating the selection set.
483
+ if (Object.hasOwn(data, "__typename")) {
484
+ result.__typename = data.__typename;
485
+ }
486
+ const workSet = new Set(selectionSet.selections);
487
+ workSet.forEach((selection) => {
488
+ if (!(0, internal_1.shouldInclude)(selection, variables))
489
+ return;
490
+ if ((0, internal_1.isField)(selection)) {
491
+ const resultName = (0, internal_1.resultKeyNameFromField)(selection);
492
+ if (!Object.hasOwn(data, resultName)) {
493
+ return;
494
+ }
495
+ const fieldValue = data[resultName];
496
+ if (Array.isArray(fieldValue)) {
497
+ const pruned = this.prunePartialStreamArray({
498
+ field: selection,
499
+ array: fieldValue,
500
+ boundaries: boundaries.getChild(resultName),
501
+ context,
502
+ path: path.concat(resultName),
503
+ });
504
+ changed ||= pruned !== fieldValue;
505
+ result[resultName] = pruned;
506
+ }
507
+ else if (!selection.selectionSet) {
508
+ result[resultName] = fieldValue;
509
+ }
510
+ else {
511
+ const pruned = this.prunePartialBoundaries({
512
+ data: fieldValue,
513
+ selectionSet: selection.selectionSet,
514
+ boundaries: boundaries.getChild(resultName),
515
+ context,
516
+ path: path.concat(resultName),
517
+ });
518
+ changed ||= pruned !== fieldValue;
519
+ // A response key can be selected by more than one selection (e.g. a
520
+ // field and an overlapping fragment), so merge their kept fields.
521
+ result[resultName] =
522
+ Object.hasOwn(result, resultName) ?
523
+ merger.merge(result[resultName], pruned)
524
+ : pruned;
525
+ }
526
+ return;
527
+ }
528
+ // Note: we do NOT set `changed` to true anywhere in this branch of the
529
+ // conditional, despite the fact that we might have encountered a
530
+ // partial @defer boundary. Dropping a fragment does not guarantee keys
531
+ // are actually dropped which can happen when overlapping sibling
532
+ // selections contribute to the construction of the object. The final
533
+ // Object.keys(result).length check actually detects whether keys were
534
+ // dropped or not.
535
+ const fragment = (0, internal_1.getFragmentFromSelection)(selection, lookupFragment);
536
+ if (fragment &&
537
+ policies.fragmentMatches(fragment, data.__typename) &&
538
+ !boundaries.has(selection) &&
539
+ !context.deferInfo?.peekArray(path)) {
540
+ fragment.selectionSet.selections.forEach(workSet.add, workSet);
541
+ }
542
+ });
543
+ if (Object.keys(result).length !== Object.keys(data).length) {
544
+ changed = true;
545
+ }
546
+ else if (changed && boundaries.hasSelections()) {
547
+ // Overlapping siblings may rebuild the same fields under a new object
548
+ // identity (e.g. changed === true) after a partial @defer is skipped.
549
+ // We perform a deep equality check to verify whether anything was
550
+ // actually dropped by the partial @defer fragment.
551
+ changed = !(0, equality_1.equal)(result, data);
552
+ }
553
+ return changed ? result : data;
554
+ }
555
+ prunePartialStreamArrayImpl({ field, array, boundaries, context, path, }) {
556
+ if (!boundaries)
557
+ return array;
558
+ let changed = false;
559
+ let pruned = [];
560
+ const state = context.streamInfo?.peekArray(path)?.state;
561
+ const length = Math.min(array.length, state?.truncate ? state.streamPosition : Number.MAX_SAFE_INTEGER);
562
+ for (let i = 0; i < length; i++) {
563
+ const item = array[i];
564
+ let prunedItem = item;
565
+ const boundary = boundaries.getChild(i);
566
+ if (boundary?.has(field)) {
567
+ // The presence of streamInfo determines how we truncate partial
568
+ // stream arrays. Stream info is only given to cache.diff during
569
+ // in-flight requests so we want keep items in the array equal to the
570
+ // total that have streamed in (this is represented by streamPosition
571
+ // above). For all other cache reads, partial stream boundaries are
572
+ // pruned back to an empty array.
573
+ if (state) {
574
+ state.truncate = true;
575
+ pruned = pruned.slice(0, state.streamPosition);
576
+ }
577
+ else {
578
+ pruned = [];
579
+ }
580
+ break;
581
+ }
582
+ if (Array.isArray(item)) {
583
+ prunedItem = this.prunePartialStreamArray({
584
+ field,
585
+ array: item,
586
+ boundaries: boundaries.getChild(i),
587
+ context,
588
+ path: path.concat(i),
589
+ });
590
+ }
591
+ else if (field.selectionSet) {
592
+ prunedItem = this.prunePartialBoundaries({
593
+ data: item,
594
+ selectionSet: field.selectionSet,
595
+ boundaries: boundaries.getChild(i),
596
+ context,
597
+ path: path.concat(i),
598
+ });
599
+ }
600
+ pruned.push(prunedItem);
601
+ changed ||= prunedItem !== item;
602
+ }
603
+ changed ||= pruned.length !== array.length;
604
+ return changed ? pruned : array;
605
+ }
276
606
  }
277
607
  exports.StoreReader = StoreReader;
278
608
  function firstMissing(tree) {
@@ -303,4 +633,85 @@ function assertSelectionSetForIdValue(store, field, fieldValue) {
303
633
  });
304
634
  }
305
635
  }
636
+ // We deliberately leave `returnPartialData` out of `executeSelectionSet`'s
637
+ // cache key. `isFresh` runs during writes and cannot provide a reliable value
638
+ // for this option, so including it would prevent a reliable cache hit.
639
+ //
640
+ // When `returnPartialData` is false, `diffQueryAgainstStore` prunes data from
641
+ // partial @defer boundaries after reading the cached result. `PartialBoundaries`
642
+ // records the selection paths needed for that pass, including empty nodes along
643
+ // a path. Overlapping non-deferred selections must still be rebuilt so fields
644
+ // contributed only by a partial deferred sibling are removed. The prune pass
645
+ // can then skip unrelated result branches.
646
+ class PartialBoundaries {
647
+ selections = new Set();
648
+ children = new Map();
649
+ add(selection) {
650
+ this.selections.add(selection);
651
+ return this;
652
+ }
653
+ has(selection) {
654
+ return this.selections.has(selection);
655
+ }
656
+ hasSelections() {
657
+ return this.selections.size > 0;
658
+ }
659
+ getChild(key) {
660
+ return this.children.get(key);
661
+ }
662
+ clone() {
663
+ return new PartialBoundaries().merge(this);
664
+ }
665
+ set(key, boundary) {
666
+ const child = this.getChild(key);
667
+ this.children.set(key,
668
+ // Create a new PartialBoundaries instance to avoid mutating any cached
669
+ // execResult partialBoundaries objects
670
+ child ? child.clone().merge(boundary) : boundary);
671
+ }
672
+ merge(boundaries) {
673
+ boundaries.selections.forEach((selection) => this.add(selection));
674
+ boundaries.children.forEach((child, key) => this.set(key, child));
675
+ return this;
676
+ }
677
+ }
678
+ // Describes the data state transitions that change the running state when it's
679
+ // combined with the next data state. Omitted object values represent
680
+ // "impossible" merges where the data state should remain the same.
681
+ const DATA_STATE_MERGES = {
682
+ empty: {
683
+ complete: "partial",
684
+ deferPartial: "partial",
685
+ streaming: "partial",
686
+ streamPartial: "partial",
687
+ },
688
+ deferPartial: {
689
+ empty: "partial",
690
+ },
691
+ streamPartial: {
692
+ deferPartial: "deferPartial",
693
+ empty: "partial",
694
+ },
695
+ streaming: {
696
+ deferPartial: "deferPartial",
697
+ empty: "partial",
698
+ streamPartial: "streamPartial",
699
+ },
700
+ complete: {
701
+ deferPartial: "deferPartial",
702
+ streaming: "streaming",
703
+ streamPartial: "streamPartial",
704
+ empty: "partial",
705
+ },
706
+ partial: {},
707
+ };
708
+ function mergeDataState(current, next) {
709
+ if (next === "partial") {
710
+ return "partial";
711
+ }
712
+ if (!current || current === next) {
713
+ return next;
714
+ }
715
+ return DATA_STATE_MERGES[current][next] || current;
716
+ }
306
717
  //# sourceMappingURL=readFromStore.cjs.map