From 338a08dc019f9fdf9cf5e4d732dc42720c7711e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20Zag=C3=B3rski?= Date: Thu, 28 Nov 2024 15:45:48 +0100 Subject: [PATCH] source: enable control of cache behavior for source and query APIs --- CHANGELOG.md | 4 +++ src/api/query.ts | 2 ++ src/api/request-with-parameters.ts | 42 +++++++++++++++++++++++++++--- src/sources/base-source.ts | 3 ++- src/sources/types.ts | 18 +++++++++++++ 5 files changed, 64 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5450ff2..7414b67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # CHANGELOG +## Not released + +- add cache control mechanism for sources and query APIs + ## 0.3 ### 0.3.0 diff --git a/src/api/query.ts b/src/api/query.ts index 3678038..b21b1b8 100644 --- a/src/api/query.ts +++ b/src/api/query.ts @@ -23,6 +23,7 @@ export const query = async function ( apiBaseUrl = SOURCE_DEFAULTS.apiBaseUrl, clientId = SOURCE_DEFAULTS.clientId, maxLengthURL = SOURCE_DEFAULTS.maxLengthURL, + localCache, connectionName, sqlQuery, queryParameters, @@ -52,5 +53,6 @@ export const query = async function ( headers, errorContext, maxLengthURL, + localCache, }); }; diff --git a/src/api/request-with-parameters.ts b/src/api/request-with-parameters.ts index 0cffe1a..1ae4514 100644 --- a/src/api/request-with-parameters.ts +++ b/src/api/request-with-parameters.ts @@ -7,13 +7,14 @@ import {CartoAPIError, APIErrorContext} from './carto-api-error'; import {V3_MINOR_VERSION} from '../constants-internal'; import {DEFAULT_MAX_LENGTH_URL} from '../constants-internal'; import {getClient} from '../client'; +import {LocalCacheOptions} from '../sources/types'; const DEFAULT_HEADERS = { Accept: 'application/json', 'Content-Type': 'application/json', }; -const REQUEST_CACHE = new Map>(); +const DEFAULT_REQUEST_CACHE = new Map>(); export async function requestWithParameters({ baseUrl, @@ -21,12 +22,14 @@ export async function requestWithParameters({ headers: customHeaders = {}, errorContext, maxLengthURL = DEFAULT_MAX_LENGTH_URL, + localCache, }: { baseUrl: string; parameters?: Record; headers?: Record; errorContext: APIErrorContext; maxLengthURL?: number; + localCache?: LocalCacheOptions; }): Promise { // Parameters added to all requests issued with `requestWithParameters()`. // These parameters override parameters already in the base URL, but not @@ -41,7 +44,14 @@ export async function requestWithParameters({ baseUrl = excludeURLParameters(baseUrl, Object.keys(parameters)); const key = createCacheKey(baseUrl, parameters, customHeaders); - if (REQUEST_CACHE.has(key)) { + + const { + cache: REQUEST_CACHE, + canReadCache, + canStoreInCache, + } = getCacheSettings(localCache, customHeaders); + + if (canReadCache && REQUEST_CACHE.has(key)) { return REQUEST_CACHE.get(key) as Promise; } @@ -73,14 +83,38 @@ export async function requestWithParameters({ return json; }) .catch((error: Error) => { - REQUEST_CACHE.delete(key); + if (canStoreInCache) { + REQUEST_CACHE.delete(key); + } throw new CartoAPIError(error, errorContext, response, responseJson); }); - REQUEST_CACHE.set(key, jsonPromise); + if (canStoreInCache) { + REQUEST_CACHE.set(key, jsonPromise); + } return jsonPromise; } +function getCacheSettings( + localCache: LocalCacheOptions | undefined, + headers: Record +) { + const cacheControl = headers['Cache-Control']; + const canReadCache = localCache + ? localCache.canReadCache + : !cacheControl?.includes('no-cache'); + const canStoreInCache = localCache + ? localCache.canStoreInCache + : !cacheControl?.includes('no-store'); + const cache = localCache?.cache || DEFAULT_REQUEST_CACHE; + + return { + cache, + canReadCache, + canStoreInCache, + }; +} + function createCacheKey( baseUrl: string, parameters: Record, diff --git a/src/sources/base-source.ts b/src/sources/base-source.ts index 3b2f530..380208a 100644 --- a/src/sources/base-source.ts +++ b/src/sources/base-source.ts @@ -45,7 +45,7 @@ export async function baseSource>( } } const baseUrl = buildSourceUrl(mergedOptions); - const {clientId, maxLengthURL, format} = mergedOptions; + const {clientId, maxLengthURL, format, localCache} = mergedOptions; const headers = { Authorization: `Bearer ${options.accessToken}`, ...options.headers, @@ -65,6 +65,7 @@ export async function baseSource>( headers, errorContext, maxLengthURL, + localCache, }); const dataUrl = mapInstantiation[format].url[0]; diff --git a/src/sources/types.ts b/src/sources/types.ts index b039d18..af58e81 100644 --- a/src/sources/types.ts +++ b/src/sources/types.ts @@ -47,6 +47,24 @@ export type SourceOptionalOptions = { * @default {@link DEFAULT_MAX_LENGTH_URL} */ maxLengthURL?: number; + + /** + * Local cache options. + * * `canReadCache`: If `true`, the source will try to read from the local memory cache. + * * `canStoreInCache`: If `true`, the source will store the response in the local memory cache. + * * `cache`: A map of promises that are used to store the responses. + * + * If not provided, source will try to detect `CacheControl: no-cache or no-store` headers in the response and disable respective caching modes. + * + * By default, local in-memory caching is enabled + */ + localCache?: LocalCacheOptions; +}; + +export type LocalCacheOptions = { + canReadCache?: boolean; + canStoreInCache?: boolean; + cache?: Map>; }; export type SourceOptions = SourceRequiredOptions &