···11+---
22+'@urql/exchange-graphcache': patch
33+---
44+55+Prevent reusal of incoming API data in Graphcache’s produced (“owned”) data. This prevents us from copying the `__typename` and other superfluous fields.
···11import { gql } from '@urql/core';
22import { it, expect } from 'vitest';
33-import { query, write } from '../operations';
33+import { __initAnd_query as query } from '../operations/query';
44+import { __initAnd_write as write } from '../operations/write';
45import { Store } from '../store';
56import { relayPagination } from './relayPagination';
67
···11import { gql } from '@urql/core';
22import { it, expect } from 'vitest';
33-import { query, write } from '../operations';
33+import { __initAnd_query as query } from '../operations/query';
44+import { __initAnd_write as write } from '../operations/write';
45import { Store } from '../store';
56import { simplePagination } from './simplePagination';
67
+1-2
exchanges/graphcache/src/index.ts
···11export * from './types';
22-export { query, write } from './operations';
33-export { Store } from './store';
22+export type { Store } from './store';
43export { cacheExchange } from './cacheExchange';
54export { offlineExchange } from './offlineExchange';
···6868/** Reads a GraphQL query from the cache.
6969 * @internal
7070 */
7171-export const query = (
7171+export const __initAnd_query = (
7272 store: Store,
7373 request: OperationRequest,
7474 data?: Data | null | undefined,
···7676 key?: number
7777): QueryResult => {
7878 initDataState('read', store.data, key);
7979- const result = read(store, request, data, error);
7979+ const result = _query(store, request, data, error);
8080 clearDataState();
8181 return result;
8282};
83838484-export const read = (
8484+/** Reads a GraphQL query from the cache.
8585+ * @internal
8686+ */
8787+export const _query = (
8588 store: Store,
8689 request: OperationRequest,
8790 input?: Data | null | undefined,
···105108 pushDebugNode(rootKey, operation);
106109 }
107110108108- if (!input) input = makeData();
109111 // NOTE: This may reuse "previous result data" as indicated by the
110112 // `originalData` argument in readRoot(). This behaviour isn't used
111113 // for readSelection() however, which always produces results from
112114 // scratch
113115 const data =
114116 rootKey !== ctx.store.rootFields['query']
115115- ? readRoot(ctx, rootKey, rootSelect, input)
116116- : readSelection(ctx, rootKey, rootSelect, input);
117117+ ? readRoot(ctx, rootKey, rootSelect, input || makeData())
118118+ : readSelection(ctx, rootKey, rootSelect, input || makeData());
117119118120 if (process.env.NODE_ENV !== 'production') {
119121 popDebugNode();
···143145 const iterate = makeSelectionIterator(entityKey, entityKey, select, ctx);
144146145147 let node: FieldNode | void;
146146- let hasChanged = false;
148148+ let hasChanged = InMemoryData.currentForeignData;
147149 const output = makeData(input);
148150 while ((node = iterate())) {
149151 const fieldAlias = getFieldAlias(node);
···181183): Link<Data> => {
182184 if (Array.isArray(originalData)) {
183185 const newData = new Array(originalData.length);
184184- let hasChanged = false;
186186+ let hasChanged = InMemoryData.currentForeignData;
185187 for (let i = 0, l = originalData.length; i < l; i++) {
186188 // Add the current index to the walked path before reading the field's value
187189 ctx.__internal.path.push(i);
···208210 }
209211};
210212211211-export const readFragment = (
213213+export const _queryFragment = (
212214 store: Store,
213215 query: DocumentNode,
214216 entity: Partial<Data> | string,
···337339 let hasFields = false;
338340 let hasPartials = false;
339341 let hasNext = false;
340340- let hasChanged = typename !== input.__typename;
342342+ let hasChanged = InMemoryData.currentForeignData;
341343 let node: FieldNode | void;
342344 const output = makeData(input);
343345 while ((node = iterate()) !== undefined) {
···456458 // Now that dataFieldValue has been retrieved it'll be set on data
457459 // If it's uncached (undefined) but nullable we can continue assembling
458460 // a partial query result
459459- if (dataFieldValue === undefined && deferRef.current) {
461461+ if (dataFieldValue === undefined && deferRef) {
460462 // The field is undelivered and uncached, but is included in a deferred fragment
461463 hasNext = true;
462464 } else if (
···500502 select: SelectionSet,
501503 prevData: void | null | Data | Data[],
502504 result: void | DataField,
503503- skipNull: boolean
505505+ isOwnedData: boolean
504506): DataField | void => {
505507 if (Array.isArray(result)) {
506508 const { store } = ctx;
···511513 : false;
512514 const data = new Array(result.length);
513515 let hasChanged =
514514- !Array.isArray(prevData) || result.length !== prevData.length;
516516+ !isOwnedData ||
517517+ !Array.isArray(prevData) ||
518518+ result.length !== prevData.length;
515519 for (let i = 0, l = result.length; i < l; i++) {
516520 // Add the current index to the walked path before reading the field's value
517521 ctx.__internal.path.push(i);
···524528 select,
525529 prevData != null ? prevData[i] : undefined,
526530 result[i],
527527- skipNull
531531+ isOwnedData
528532 );
529533 // After processing the field, remove the current index from the path
530534 ctx.__internal.path.pop();
···542546 return hasChanged ? data : prevData;
543547 } else if (result === null || result === undefined) {
544548 return result;
545545- } else if (skipNull && prevData === null) {
549549+ } else if (!isOwnedData && prevData === null) {
546550 return null;
547551 } else if (isDataOrKey(result)) {
548552 const data = (prevData || makeData()) as Data;
···569573 fieldName: string,
570574 select: SelectionSet,
571575 prevData: void | null | Data | Data[],
572572- skipNull: boolean
576576+ isOwnedData: boolean
573577): DataField | undefined => {
574578 if (Array.isArray(link)) {
575579 const { store } = ctx;
···578582 : false;
579583 const newLink = new Array(link.length);
580584 let hasChanged =
581581- !Array.isArray(prevData) || newLink.length !== prevData.length;
585585+ !isOwnedData ||
586586+ !Array.isArray(prevData) ||
587587+ newLink.length !== prevData.length;
582588 for (let i = 0, l = link.length; i < l; i++) {
583589 // Add the current index to the walked path before reading the field's value
584590 ctx.__internal.path.push(i);
···590596 fieldName,
591597 select,
592598 prevData != null ? prevData[i] : undefined,
593593- skipNull
599599+ isOwnedData
594600 );
595601 // After processing the field, remove the current index from the path
596602 ctx.__internal.path.pop();
···606612 }
607613608614 return hasChanged ? newLink : (prevData as Data[]);
609609- } else if (link === null || (prevData === null && skipNull)) {
615615+ } else if (link === null || (prevData === null && isOwnedData)) {
610616 return null;
611617 }
612618
+5-6
exchanges/graphcache/src/operations/shared.ts
···5151 };
5252}
53535454-export const contextRef: { current: Context | null } = { current: null };
5555-export const deferRef: { current: boolean } = { current: false };
5454+export let contextRef: Context | null = null;
5555+export let deferRef = false;
56565757// Checks whether the current data field is a cache miss because of a GraphQLError
5858export const getFieldError = (ctx: Context): ErrorLike | undefined =>
···110110 fieldKey: string,
111111 fieldName: string
112112) => {
113113- contextRef.current = ctx;
113113+ contextRef = ctx;
114114 ctx.parent = data;
115115 ctx.parentTypeName = typename;
116116 ctx.parentKey = entityKey;
···168168 let index = 0;
169169170170 return function next() {
171171- if (!deferRef.current && childDeferred) deferRef.current = childDeferred;
171171+ if (!deferRef && childDeferred) deferRef = childDeferred;
172172173173 if (childIterator) {
174174 const node = childIterator();
···208208 }
209209210210 childDeferred = !!isDeferred(node, ctx.variables);
211211- if (!deferRef.current && childDeferred)
212212- deferRef.current = childDeferred;
211211+ if (!deferRef && childDeferred) deferRef = childDeferred;
213212214213 return (childIterator = makeSelectionIterator(
215214 typename,
+1-1
exchanges/graphcache/src/operations/write.test.ts
···44import { minifyIntrospectionQuery } from '@urql/introspection';
55import { vi, expect, it, beforeEach, describe, beforeAll } from 'vitest';
6677-import { write } from './write';
77+import { __initAnd_write as write } from './write';
88import * as InMemoryData from '../store/data';
99import { Store } from '../store';
1010
···6363let currentData: null | InMemoryData = null;
6464let currentDependencies: null | Dependencies = null;
6565let currentOptimisticKey: null | number = null;
6666-let currentOptimistic = false;
6666+export let currentForeignData = false;
6767+export let currentOptimistic = false;
67686869/** Creates a new data object unless it's been created in this data run */
6970export const makeData = (data?: Data): Data => {
7071 let newData: Data;
7172 if (data) {
7273 if (currentOwnership!.has(data)) return data;
7373- newData = currentDataMapping!.get(data) || ({ ...data } as Data);
7474+ newData = currentDataMapping!.get(data) || ({} as Data);
7475 currentDataMapping!.set(data, newData);
7576 } else {
7677 newData = {} as Data;
···9091 operationType: OperationType,
9192 data: InMemoryData,
9293 layerKey?: number | null,
9393- isOptimistic?: boolean
9494+ isOptimistic?: boolean,
9595+ isForeignData?: boolean
9496) => {
9597 currentOwnership = new WeakSet();
9698 currentDataMapping = new WeakMap();
···98100 currentData = data;
99101 currentDependencies = new Set();
100102 currentOptimistic = !!isOptimistic;
103103+ currentForeignData = !!isForeignData;
101104 if (process.env.NODE_ENV !== 'production') {
102105 currentDebugStack.length = 0;
103106 }
+6-2
exchanges/graphcache/src/store/store.test.ts
···12121313import { Data, StorageAdapter } from '../types';
1414import { makeContext, updateContext } from '../operations/shared';
1515-import { query } from '../operations/query';
1616-import { write, writeOptimistic } from '../operations/write';
1715import * as InMemoryData from './data';
1816import { Store } from './store';
1917import { noop } from '../test-utils/utils';
1818+1919+import { __initAnd_query as query } from '../operations/query';
2020+import {
2121+ __initAnd_write as write,
2222+ __initAnd_writeOptimistic as writeOptimistic,
2323+} from '../operations/write';
20242125const mocked = (x: any): any => x;
2226
+7-8
exchanges/graphcache/src/store/store.ts
···19192020import { invariant } from '../helpers/help';
2121import { contextRef, ensureLink } from '../operations/shared';
2222-import { read, readFragment } from '../operations/query';
2323-import { writeFragment, startWrite } from '../operations/write';
2222+import { _query, _queryFragment } from '../operations/query';
2323+import { _write, _writeFragment } from '../operations/write';
2424import { invalidateEntity } from '../operations/invalidate';
2525import { keyOfField } from './keys';
2626import * as InMemoryData from './data';
···107107 // In resolvers and updaters we may have a specific parent
108108 // object available that can be used to skip to a specific parent
109109 // key directly without looking at its incomplete properties
110110- if (contextRef.current && data === contextRef.current.parent)
111111- return contextRef.current!.parentKey;
110110+ if (contextRef && data === contextRef.parent) return contextRef.parentKey;
112111113112 if (data == null || typeof data === 'string') return data || null;
114113 if (!data.__typename) return null;
···167166 request.query = formatDocument(request.query);
168167 const output = updater(this.readQuery(request));
169168 if (output !== null) {
170170- startWrite(this, request, output as any);
169169+ _write(this, request, output as any, undefined);
171170 }
172171 }
173172174173 readQuery<T = Data, V = Variables>(input: QueryInput<T, V>): T | null {
175174 const request = createRequest(input.query, input.variables!);
176175 request.query = formatDocument(request.query);
177177- return read(this, request).data as T | null;
176176+ return _query(this, request, undefined, undefined).data as T | null;
178177 }
179178180179 readFragment<T = Data, V = Variables>(
···183182 variables?: V,
184183 fragmentName?: string
185184 ): T | null {
186186- return readFragment(
185185+ return _queryFragment(
187186 this,
188187 formatDocument(fragment),
189188 entity as Data,
···198197 variables?: V,
199198 fragmentName?: string
200199 ): void {
201201- writeFragment(
200200+ _writeFragment(
202201 this,
203202 formatDocument(fragment),
204203 data as Data,
···11import { gql } from '@urql/core';
22import { it, expect, afterEach } from 'vitest';
33-import { query, write, writeOptimistic } from '../operations';
33+import { __initAnd_query as query } from '../operations/query';
44+import {
55+ __initAnd_write as write,
66+ __initAnd_writeOptimistic as writeOptimistic,
77+} from '../operations/write';
48import * as InMemoryData from '../store/data';
59import { Store } from '../store';
610import { Data } from '../types';