Mirror: The highly customizable and versatile GraphQL client with which you add on features like normalized caching as you grow.
at main 3.0 kB view raw
1export type FileMap = Map<string, File | Blob>; 2 3const seen: Set<any> = new Set(); 4const cache: WeakMap<any, any> = new WeakMap(); 5 6const stringify = (x: any, includeFiles: boolean): string => { 7 if (x === null || seen.has(x)) { 8 return 'null'; 9 } else if (typeof x !== 'object') { 10 return JSON.stringify(x) || ''; 11 } else if (x.toJSON) { 12 return stringify(x.toJSON(), includeFiles); 13 } else if (Array.isArray(x)) { 14 let out = '['; 15 for (let i = 0, l = x.length; i < l; i++) { 16 if (out.length > 1) out += ','; 17 out += stringify(x[i], includeFiles) || 'null'; 18 } 19 out += ']'; 20 return out; 21 } else if ( 22 !includeFiles && 23 ((FileConstructor !== NoopConstructor && x instanceof FileConstructor) || 24 (BlobConstructor !== NoopConstructor && x instanceof BlobConstructor)) 25 ) { 26 return 'null'; 27 } 28 29 const keys = Object.keys(x).sort(); 30 if ( 31 !keys.length && 32 x.constructor && 33 Object.getPrototypeOf(x).constructor !== Object.prototype.constructor 34 ) { 35 const key = cache.get(x) || Math.random().toString(36).slice(2); 36 cache.set(x, key); 37 return stringify({ __key: key }, includeFiles); 38 } 39 40 seen.add(x); 41 let out = '{'; 42 for (let i = 0, l = keys.length; i < l; i++) { 43 const value = stringify(x[keys[i]], includeFiles); 44 if (value) { 45 if (out.length > 1) out += ','; 46 out += stringify(keys[i], includeFiles) + ':' + value; 47 } 48 } 49 50 seen.delete(x); 51 out += '}'; 52 return out; 53}; 54 55const extract = (map: FileMap, path: string, x: any): void => { 56 if (x == null || typeof x !== 'object' || x.toJSON || seen.has(x)) { 57 /*noop*/ 58 } else if (Array.isArray(x)) { 59 for (let i = 0, l = x.length; i < l; i++) 60 extract(map, `${path}.${i}`, x[i]); 61 } else if (x instanceof FileConstructor || x instanceof BlobConstructor) { 62 map.set(path, x as File | Blob); 63 } else { 64 seen.add(x); 65 for (const key in x) extract(map, `${path}.${key}`, x[key]); 66 } 67}; 68 69/** A stable stringifier for GraphQL variables objects. 70 * 71 * @param x - any JSON-like data. 72 * @return A JSON string. 73 * 74 * @remarks 75 * This utility creates a stable JSON string from any passed data, 76 * and protects itself from throwing. 77 * 78 * The JSON string is stable insofar as objects’ keys are sorted, 79 * and instances of non-plain objects are replaced with random keys 80 * replacing their values, which remain stable for the objects’ 81 * instance. 82 */ 83export const stringifyVariables = (x: any, includeFiles?: boolean): string => { 84 seen.clear(); 85 return stringify(x, includeFiles || false); 86}; 87 88class NoopConstructor {} 89const FileConstructor = typeof File !== 'undefined' ? File : NoopConstructor; 90const BlobConstructor = typeof Blob !== 'undefined' ? Blob : NoopConstructor; 91 92export const extractFiles = (x: any): FileMap => { 93 const map: FileMap = new Map(); 94 if ( 95 FileConstructor !== NoopConstructor || 96 BlobConstructor !== NoopConstructor 97 ) { 98 seen.clear(); 99 extract(map, 'variables', x); 100 } 101 return map; 102};