import type { StoreLink } from './IsographEnvironment'; import type { ReaderAst, ReaderLinkedField, ReaderScalarField } from './reader'; function mergeUsingReaderAst( field: ReaderScalarField | ReaderLinkedField, oldItem: unknown, newItem: unknown, ): unknown { if (newItem == null || oldItem == null) { return newItem; } if (Array.isArray(newItem)) { if (!Array.isArray(oldItem)) { return newItem; } return mergeArraysUsingReaderAst(field, oldItem, newItem); } switch (field.kind) { case 'Scalar': return oldItem === newItem ? oldItem : newItem; case 'Linked': if (oldItem == null) { return newItem; } return mergeObjectsUsingReaderAst(field.selections, oldItem, newItem); } } function mergeArraysUsingReaderAst( field: ReaderScalarField | ReaderLinkedField, oldItems: ReadonlyArray, newItems: Array, ): ReadonlyArray { if (newItems.length !== oldItems.length) { return newItems; } let canRecycle = true; for (let i = 0; i < newItems.length; i++) { const mergedItem = mergeUsingReaderAst(field, oldItems[i], newItems[i]); if (mergedItem !== oldItems[i]) { canRecycle = false; } else { newItems[i] = mergedItem; } } return canRecycle ? oldItems : newItems; } export function mergeObjectsUsingReaderAst( ast: ReaderAst, oldItemObject: object, newItemObject: object, ): object { let canRecycle = true; for (const field of ast) { switch (field.kind) { case 'Scalar': case 'Linked': if (field.kind === 'Linked' && field.refetchQueryIndex != null) { // client pointers are functions, so we can't merge them canRecycle = false; break; } const key = field.alias ?? field.fieldName; // @ts-expect-error const oldValue = oldItemObject[key]; // @ts-expect-error const newValue = newItemObject[key]; const mergedValue = mergeUsingReaderAst(field, oldValue, newValue); if (mergedValue !== oldValue) { canRecycle = false; } else { // @ts-expect-error newItemObject[key] = mergedValue; } break; case 'Resolver': { const key = field.alias; // @ts-expect-error const oldValue = oldItemObject[key]; // @ts-expect-error const newValue = newItemObject[key]; if (oldValue !== newValue) { canRecycle = false; } break; } case 'Link': { const key = field.alias; // @ts-expect-error const oldValue: StoreLink = oldItemObject[key]; // @ts-expect-error const newValue: StoreLink = newItemObject[key]; if ( oldValue.__link !== newValue.__link || oldValue.__typename !== newValue.__typename ) { canRecycle = false; } else { // @ts-expect-error newItemObject[key] = oldValue; } break; } case 'ImperativelyLoadedField': case 'LoadablySelectedField': break; } } return canRecycle ? oldItemObject : newItemObject; }