Mirror: The highly customizable and versatile GraphQL client with which you add on features like normalized caching as you grow.
at main 4.1 kB view raw
1import { stringifyVariables } from '@urql/core'; 2import type { Resolver, Variables, NullArray } from '../types'; 3 4export type MergeMode = 'before' | 'after'; 5 6/** Input parameters for the {@link simplePagination} factory. */ 7export interface PaginationParams { 8 /** The name of the field argument used to define the page’s offset. */ 9 offsetArgument?: string; 10 /** The name of the field argument used to define the page’s length. */ 11 limitArgument?: string; 12 /** Flip between forward and backwards pagination. 13 * 14 * @remarks 15 * When set to `'after'`, its default, pages are merged forwards and in order. 16 * When set to `'before'`, pages are merged in reverse, putting later pages 17 * in front of earlier ones. 18 */ 19 mergeMode?: MergeMode; 20} 21 22/** Creates a {@link Resolver} that combines pages of a primitive pagination field. 23 * 24 * @param options - A {@link PaginationParams} configuration object. 25 * @returns the created pagination {@link Resolver}. 26 * 27 * @remarks 28 * `simplePagination` is a factory that creates a {@link Resolver} that can combine 29 * multiple lists on a paginated field into a single, combined list for infinite 30 * scrolling. 31 * 32 * Hint: It's not recommended to use this when you can handle infinite scrolling 33 * in your UI code instead. 34 * 35 * @see {@link https://urql.dev/goto/docs/graphcache/local-resolvers#simple-pagination} for more information. 36 * @see {@link https://urql.dev/goto/docs/basics/ui-patterns/#infinite-scrolling} for an alternate approach. 37 */ 38export const simplePagination = ({ 39 offsetArgument = 'skip', 40 limitArgument = 'limit', 41 mergeMode = 'after', 42}: PaginationParams = {}): Resolver<any, any, any> => { 43 const compareArgs = ( 44 fieldArgs: Variables, 45 connectionArgs: Variables 46 ): boolean => { 47 for (const key in connectionArgs) { 48 if (key === offsetArgument || key === limitArgument) { 49 continue; 50 } else if (!(key in fieldArgs)) { 51 return false; 52 } 53 54 const argA = fieldArgs[key]; 55 const argB = connectionArgs[key]; 56 57 if ( 58 typeof argA !== typeof argB || typeof argA !== 'object' 59 ? argA !== argB 60 : stringifyVariables(argA) !== stringifyVariables(argB) 61 ) { 62 return false; 63 } 64 } 65 66 for (const key in fieldArgs) { 67 if (key === offsetArgument || key === limitArgument) { 68 continue; 69 } 70 if (!(key in connectionArgs)) return false; 71 } 72 73 return true; 74 }; 75 76 return (_parent, fieldArgs, cache, info) => { 77 const { parentKey: entityKey, fieldName } = info; 78 79 const allFields = cache.inspectFields(entityKey); 80 const fieldInfos = allFields.filter(info => info.fieldName === fieldName); 81 const size = fieldInfos.length; 82 if (size === 0) { 83 return undefined; 84 } 85 86 const visited = new Set(); 87 let result: NullArray<string> = []; 88 let prevOffset: number | null = null; 89 90 for (let i = 0; i < size; i++) { 91 const { fieldKey, arguments: args } = fieldInfos[i]; 92 if (args === null || !compareArgs(fieldArgs, args)) { 93 continue; 94 } 95 96 const links = cache.resolve(entityKey, fieldKey) as string[]; 97 const currentOffset = args[offsetArgument]; 98 99 if ( 100 links === null || 101 links.length === 0 || 102 typeof currentOffset !== 'number' 103 ) { 104 continue; 105 } 106 107 const tempResult: NullArray<string> = []; 108 109 for (let j = 0; j < links.length; j++) { 110 const link = links[j]; 111 if (visited.has(link)) continue; 112 tempResult.push(link); 113 visited.add(link); 114 } 115 116 if ( 117 (!prevOffset || currentOffset > prevOffset) === 118 (mergeMode === 'after') 119 ) { 120 result = [...result, ...tempResult]; 121 } else { 122 result = [...tempResult, ...result]; 123 } 124 125 prevOffset = currentOffset; 126 } 127 128 const hasCurrentPage = cache.resolve(entityKey, fieldName, fieldArgs); 129 if (hasCurrentPage) { 130 return result; 131 } else if (!(info as any).store.schema) { 132 return undefined; 133 } else { 134 info.partial = true; 135 return result; 136 } 137 }; 138};