From 5681c7c96a676b1047aaa04f0363a7d8ce853a2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20Zag=C3=B3rski?= <1507542+zbigg@users.noreply.github.com> Date: Wed, 11 Dec 2024 16:29:43 +0100 Subject: [PATCH] enable control of cache behavior for source and query APIs (#33) --- CHANGELOG.md | 4 ++++ src/api/query.ts | 2 ++ src/api/request-with-parameters.ts | 38 ++++++++++++++++++++++++++---- src/sources/base-source.ts | 5 +++- src/sources/types.ts | 21 +++++++++++++++++ 5 files changed, 65 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e75851e..fa9dd2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # CHANGELOG +## Not released + +- add cache control mechanism for sources and query APIs + ## 0.4 ### 0.4.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..4261d17 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); + + if (canReadCache && REQUEST_CACHE.has(key)) { return REQUEST_CACHE.get(key) as Promise; } @@ -73,14 +83,34 @@ 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) { + const canReadCache = localCache?.cacheControl?.includes('no-cache') + ? false + : true; + const canStoreInCache = localCache?.cacheControl?.includes('no-store') + ? false + : true; + 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..ac5878e 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]; @@ -82,6 +83,7 @@ export async function baseSource>( headers, errorContext, maxLengthURL, + localCache, }); if (accessToken) { json.accessToken = accessToken; @@ -94,5 +96,6 @@ export async function baseSource>( headers, errorContext, maxLengthURL, + localCache, }); } diff --git a/src/sources/types.ts b/src/sources/types.ts index 50ec91c..f61ec7d 100644 --- a/src/sources/types.ts +++ b/src/sources/types.ts @@ -47,6 +47,27 @@ export type SourceOptionalOptions = { * @default {@link DEFAULT_MAX_LENGTH_URL} */ maxLengthURL?: number; + + /** + * By default, local in-memory caching is enabled. + */ + localCache?: LocalCacheOptions; +}; + +export type LocalCacheOptions = { + /** + * Map that stores requests and their responses. + */ + cache?: Map>; + + /** + * Cache control + * * `no-cache`: If present, the source will always fetch from original source. + * * `no-store`: If present, source will not store result in cache (for later reuse). + * + * See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#directives + */ + cacheControl?: ('no-cache' | 'no-store')[]; }; export type SourceOptions = SourceRequiredOptions &