Skip to content

Commit

Permalink
enable control of cache behavior for source and query APIs (#33)
Browse files Browse the repository at this point in the history
  • Loading branch information
zbigg authored Dec 11, 2024
1 parent 5463752 commit 5681c7c
Show file tree
Hide file tree
Showing 5 changed files with 65 additions and 5 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# CHANGELOG

## Not released

- add cache control mechanism for sources and query APIs

## 0.4

### 0.4.0
Expand Down
2 changes: 2 additions & 0 deletions src/api/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -52,5 +53,6 @@ export const query = async function (
headers,
errorContext,
maxLengthURL,
localCache,
});
};
38 changes: 34 additions & 4 deletions src/api/request-with-parameters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,29 @@ 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<string, Promise<unknown>>();
const DEFAULT_REQUEST_CACHE = new Map<string, Promise<unknown>>();

export async function requestWithParameters<T = any>({
baseUrl,
parameters = {},
headers: customHeaders = {},
errorContext,
maxLengthURL = DEFAULT_MAX_LENGTH_URL,
localCache,
}: {
baseUrl: string;
parameters?: Record<string, unknown>;
headers?: Record<string, string>;
errorContext: APIErrorContext;
maxLengthURL?: number;
localCache?: LocalCacheOptions;
}): Promise<T> {
// Parameters added to all requests issued with `requestWithParameters()`.
// These parameters override parameters already in the base URL, but not
Expand All @@ -41,7 +44,14 @@ export async function requestWithParameters<T = any>({

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<T>;
}

Expand Down Expand Up @@ -73,14 +83,34 @@ export async function requestWithParameters<T = any>({
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<string, unknown>,
Expand Down
5 changes: 4 additions & 1 deletion src/sources/base-source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export async function baseSource<UrlParameters extends Record<string, unknown>>(
}
}
const baseUrl = buildSourceUrl(mergedOptions);
const {clientId, maxLengthURL, format} = mergedOptions;
const {clientId, maxLengthURL, format, localCache} = mergedOptions;
const headers = {
Authorization: `Bearer ${options.accessToken}`,
...options.headers,
Expand All @@ -65,6 +65,7 @@ export async function baseSource<UrlParameters extends Record<string, unknown>>(
headers,
errorContext,
maxLengthURL,
localCache,
});

const dataUrl = mapInstantiation[format].url[0];
Expand All @@ -82,6 +83,7 @@ export async function baseSource<UrlParameters extends Record<string, unknown>>(
headers,
errorContext,
maxLengthURL,
localCache,
});
if (accessToken) {
json.accessToken = accessToken;
Expand All @@ -94,5 +96,6 @@ export async function baseSource<UrlParameters extends Record<string, unknown>>(
headers,
errorContext,
maxLengthURL,
localCache,
});
}
21 changes: 21 additions & 0 deletions src/sources/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, Promise<unknown>>;

/**
* 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 &
Expand Down

0 comments on commit 5681c7c

Please sign in to comment.