UNPKG

@apollo/client

Version:

A fully-featured caching GraphQL client.

113 lines (112 loc) 5.38 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.canonicalStringify = void 0; const environment_1 = require("@apollo/client/utilities/environment"); const sizes_js_1 = require("../caching/sizes.cjs"); const caches_js_1 = require("./caches.cjs"); const getMemoryInternals_js_1 = require("./getMemoryInternals.cjs"); /** * Serializes a value to JSON with object keys in a consistent, sorted order. * * @remarks * * Unlike `JSON.stringify()`, this function ensures that object keys are always * serialized in the same alphabetical order, regardless of their original order. * This makes it suitable for creating consistent cache keys from objects, * comparing objects by their serialized representation, or generating * deterministic hashes of objects. * * To achieve performant sorting, this function uses a `Map` from JSON-serialized * arrays of keys (in any order) to sorted arrays of the same keys, with a * single sorted array reference shared by all permutations of the keys. * * As a drawback, this function will add a little more memory for every object * encountered that has different (more, less, a different order of) keys than * in the past. * * In a typical application, this extra memory usage should not play a * significant role, as `canonicalStringify` will be called for only a limited * number of object shapes, and the cache will not grow beyond a certain point. * But in some edge cases, this could be a problem. Use canonicalStringify.reset() * as a way to clear the memoization cache. * * @param value - The value to stringify * @returns JSON string with consistently ordered object keys * * @example * * ```ts * import { canonicalStringify } from "@apollo/client/utilities"; * * const obj1 = { b: 2, a: 1 }; * const obj2 = { a: 1, b: 2 }; * * console.log(canonicalStringify(obj1)); // '{"a":1,"b":2}' * console.log(canonicalStringify(obj2)); // '{"a":1,"b":2}' * ``` */ exports.canonicalStringify = Object.assign(function canonicalStringify(value) { return JSON.stringify(value, stableObjectReplacer); }, { reset() { // Clearing the sortingMap will reclaim all cached memory, without // affecting the logical results of canonicalStringify, but potentially // sacrificing performance until the cache is refilled. sortingMap = new caches_js_1.AutoCleanedStrongCache(sizes_js_1.cacheSizes.canonicalStringify || 1000 /* defaultCacheSizes.canonicalStringify */); }, }); if (environment_1.__DEV__) { (0, getMemoryInternals_js_1.registerGlobalCache)("canonicalStringify", () => sortingMap.size); } // Values are JSON-serialized arrays of object keys (in any order), and values // are sorted arrays of the same keys. let sortingMap; exports.canonicalStringify.reset(); // The JSON.stringify function takes an optional second argument called a // replacer function. This function is called for each key-value pair in the // object being stringified, and its return value is used instead of the // original value. If the replacer function returns a new value, that value is // stringified as JSON instead of the original value of the property. // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#the_replacer_parameter function stableObjectReplacer(key, value) { if (value && typeof value === "object") { const proto = Object.getPrototypeOf(value); // We don't want to mess with objects that are not "plain" objects, which // means their prototype is either Object.prototype or null. This check also // prevents needlessly rearranging the indices of arrays. if (proto === Object.prototype || proto === null) { const keys = Object.keys(value); // If keys is already sorted, let JSON.stringify serialize the original // value instead of creating a new object with keys in the same order. if (keys.every(everyKeyInOrder)) return value; const unsortedKey = JSON.stringify(keys); let sortedKeys = sortingMap.get(unsortedKey); if (!sortedKeys) { keys.sort(); const sortedKey = JSON.stringify(keys); // Checking for sortedKey in the sortingMap allows us to share the same // sorted array reference for all permutations of the same set of keys. sortedKeys = sortingMap.get(sortedKey) || keys; sortingMap.set(unsortedKey, sortedKeys); sortingMap.set(sortedKey, sortedKeys); } const sortedObject = Object.create(proto); // Reassigning the keys in sorted order will cause JSON.stringify to // serialize them in sorted order. sortedKeys.forEach((key) => { sortedObject[key] = value[key]; }); return sortedObject; } } return value; } // Since everything that happens in stableObjectReplacer benefits from being as // efficient as possible, we use a static function as the callback for // keys.every in order to test if the provided keys are already sorted without // allocating extra memory for a callback. function everyKeyInOrder(key, i, keys) { return i === 0 || keys[i - 1] <= key; } //# sourceMappingURL=canonicalStringify.cjs.map