UNPKG

relay-runtime

Version:

A core runtime for building GraphQL-driven applications.

123 lines (110 loc) 3.14 kB
/** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @flow strict-local * @format * @oncall relay */ 'use strict'; import type {Variables} from '../util/RelayRuntimeTypes'; import type { GraphQLResponse, GraphQLSingularResponse, } from './RelayNetworkTypes'; const {stableCopy} = require('../util/stableCopy'); const invariant = require('invariant'); type Response = { fetchTime: number, payload: GraphQLResponse, ... }; /** * A cache for storing query responses, featuring: * - `get` with TTL * - cache size limiting, with least-recently *updated* entries purged first */ class RelayQueryResponseCache { _responses: Map<string, Response>; _size: number; _ttl: number; constructor({size, ttl}: {size: number, ttl: number, ...}) { invariant( size > 0, 'RelayQueryResponseCache: Expected the max cache size to be > 0, got ' + '`%s`.', size, ); invariant( ttl > 0, 'RelayQueryResponseCache: Expected the max ttl to be > 0, got `%s`.', ttl, ); this._responses = new Map(); this._size = size; this._ttl = ttl; } clear(): void { this._responses.clear(); } get(queryID: string, variables: Variables): ?GraphQLResponse { const cacheKey = getCacheKey(queryID, variables); this._responses.forEach((response, key) => { if (!isCurrent(response.fetchTime, this._ttl)) { this._responses.delete(key); } }); const response = this._responses.get(cacheKey); if (response == null) { return null; } if (Array.isArray(response.payload)) { return response.payload.map( payload => ({ ...payload, extensions: { ...payload.extensions, cacheTimestamp: response.fetchTime, }, }: GraphQLSingularResponse), ); } return ({ ...response.payload, extensions: { ...response.payload.extensions, cacheTimestamp: response.fetchTime, }, }: GraphQLSingularResponse); } set(queryID: string, variables: Variables, payload: GraphQLResponse): void { const fetchTime = Date.now(); const cacheKey = getCacheKey(queryID, variables); this._responses.delete(cacheKey); // deletion resets key ordering this._responses.set(cacheKey, { fetchTime, payload, }); // Purge least-recently updated key when max size reached if (this._responses.size > this._size) { const firstKey = this._responses.keys().next(); if (!firstKey.done) { this._responses.delete(firstKey.value); } } } } function getCacheKey(queryID: string, variables: Variables): string { return JSON.stringify(stableCopy({queryID, variables})); } /** * Determine whether a response fetched at `fetchTime` is still valid given * some `ttl`. */ function isCurrent(fetchTime: number, ttl: number): boolean { return fetchTime + ttl >= Date.now(); } module.exports = RelayQueryResponseCache;