Mirror: The highly customizable and versatile GraphQL client with which you add on features like normalized caching as you grow.
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

(graphcache) - Reimplement default-storage serializer/deserializer (#866)

* Prevent collecting optimistic writes on persist Set

* Implement custom persistence serializer

* Simplify deserializer implementation

* Bump idbName fallback in default-storage to v4

* Add changeset

* Add missing mode reset and invalid EOF handling

authored by

Phil Plückthun and committed by
GitHub
147e6281 c3dbd2a7

+49 -19
+5
.changeset/thick-bees-decide.md
··· 1 + --- 2 + '@urql/exchange-graphcache': patch 3 + --- 4 + 5 + Fix storage implementation not preserving deleted values correctly or erroneously checking optimistically written entries for changes. This is fixed by adding a new default serializer to the `@urql/exchange-graphcache/default-storage` implementation, which will be incompatible with the old one.
+32 -9
exchanges/graphcache/src/default-storage/index.ts
··· 1 - import { stringifyVariables } from '@urql/core'; 2 1 import { SerializedEntries, SerializedRequest, StorageAdapter } from '../types'; 3 2 4 3 const getRequestPromise = <T>(request: IDBRequest<T>): Promise<T> => { ··· 35 34 export const makeDefaultStorage = (opts?: StorageOptions): DefaultStorage => { 36 35 if (!opts) opts = {}; 37 36 38 - const DB_NAME = opts.idbName || 'graphcache-v3'; 37 + const DB_NAME = opts.idbName || 'graphcache-v4'; 39 38 const ENTRIES_STORE_NAME = 'entries'; 40 39 const METADATA_STORE_NAME = 'metadata'; 41 40 ··· 51 50 req.result.createObjectStore(METADATA_STORE_NAME); 52 51 }; 53 52 53 + const serializeEntry = (entry: string): string => entry.replace(/:/g, '%3a'); 54 + 55 + const deserializeEntry = (entry: string): string => 56 + entry.replace(/%3a/g, ':'); 57 + 54 58 const serializeBatch = (): string => { 55 59 let data = ''; 56 60 for (const key in batch) { 57 61 const value = batch[key]; 58 - data += `${stringifyVariables(key)}:${ 59 - value !== undefined ? stringifyVariables(value) : 'null' 60 - },`; 62 + data += serializeEntry(key); 63 + data += ':'; 64 + if (value) data += serializeEntry(value); 65 + data += ':'; 61 66 } 62 67 63 68 return data; 64 69 }; 65 70 66 71 const deserializeBatch = (input: string) => { 67 - try { 68 - return JSON.parse(`{${input.slice(0, -1)}}`); 69 - } catch (_error) { 70 - return {}; 72 + const data = {}; 73 + let char = '', 74 + key = '', 75 + entry = '', 76 + mode = 0, 77 + index = 0; 78 + 79 + while (index < input.length) { 80 + entry = ''; 81 + while ((char = input[index++]) !== ':' && char) { 82 + entry += char; 83 + } 84 + 85 + if (mode) { 86 + data[key] = deserializeEntry(entry) || undefined; 87 + mode = 0; 88 + } else { 89 + key = deserializeEntry(entry); 90 + mode = 1; 91 + } 71 92 } 93 + 94 + return data; 72 95 }; 73 96 74 97 return {
+12 -10
exchanges/graphcache/src/store/data.ts
··· 75 75 currentOperation = operationType; 76 76 currentData = data; 77 77 currentDependencies = makeDict(); 78 - currentIgnoreOptimistic = false; 78 + currentIgnoreOptimistic = !!isOptimistic; 79 79 if (process.env.NODE_ENV !== 'production') { 80 80 currentDebugStack.length = 0; 81 81 } ··· 116 116 117 117 const data = currentData!; 118 118 const layerKey = currentOptimisticKey; 119 + currentIgnoreOptimistic = false; 119 120 currentOptimisticKey = null; 120 121 121 122 // Determine whether the current operation has been a commutative layer ··· 132 133 } 133 134 } 134 135 136 + currentOperation = null; 137 + currentData = null; 138 + currentDependencies = null; 139 + if (process.env.NODE_ENV !== 'production') { 140 + currentDebugStack.length = 0; 141 + } 142 + 135 143 // Schedule deferred tasks if we haven't already 136 144 if (process.env.NODE_ENV !== 'test' && !data.defer) { 137 145 data.defer = true; ··· 143 151 data.defer = false; 144 152 }); 145 153 } 146 - 147 - currentOperation = null; 148 - currentData = null; 149 - currentDependencies = null; 150 - if (process.env.NODE_ENV !== 'production') { 151 - currentDebugStack.length = 0; 152 - } 153 154 }; 154 155 155 156 /** Initialises then resets the data state, which may squash this layer if necessary */ ··· 382 383 }; 383 384 384 385 const updatePersist = (entityKey: string, fieldKey: string) => { 385 - if (currentData!.storage) 386 + if (!currentIgnoreOptimistic && currentData!.storage) { 386 387 currentData!.persist.add(serializeKeys(entityKey, fieldKey)); 388 + } 387 389 }; 388 390 389 391 /** Reads an entity's field (a "record") from data */ ··· 555 557 556 558 export const persistData = () => { 557 559 if (currentData!.storage) { 558 - const entries: SerializedEntries = makeDict(); 559 560 currentIgnoreOptimistic = true; 561 + const entries: SerializedEntries = makeDict(); 560 562 currentData!.persist.forEach(key => { 561 563 const { entityKey, fieldKey } = deserializeKeyInfo(key); 562 564 let x: void | Link | EntityField;