Edwin Veldhuizen 6cba59cc1c
[must-revalidate] properly revalidate based on eTag (#824)
* [must-revalidate] implement logic to properly revalidate requests with must-revalidate cache-control header

* [lint] fix sorting of imports

* [pull 824] apply feedback

---------

Co-authored-by: Edwin Veldhuizen <edwin@pxr.nl>
2024-04-23 13:06:04 -03:00

352 lines
13 KiB
TypeScript

import type { Method } from 'axios';
import type { Deferred } from 'fast-defer';
import type { HeaderInterpreter } from '../header/types.js';
import type { AxiosInterceptor } from '../interceptors/build.js';
import type {
AxiosStorage,
CachedResponse,
CachedStorageValue,
LoadingStorageValue,
StaleStorageValue
} from '../storage/types.js';
import type {
CachePredicate,
CacheUpdater,
KeyGenerator,
StaleIfErrorPredicate
} from '../util/types.js';
import type { CacheAxiosResponse, CacheRequestConfig } from './axios.js';
/**
* @template R The type returned by this response
* @template D The type for the request body
*/
export interface CacheProperties<R = unknown, D = unknown> {
/**
* The time until the cached value is expired in milliseconds.
*
* If a function is used, it will receive the complete response and waits to return a
* TTL value
*
* When using `interpretHeader: true`, this value will only be used if the interpreter
* can't determine their TTL value to override this
*
* @default 1000 * 60 * 5 // 5 Minutes
* @see https://axios-cache-interceptor.js.org/config/request-specifics#cache-ttl
*/
ttl: number | ((response: CacheAxiosResponse<R, D>) => number | Promise<number>);
/**
* If activated, when the response is received, the `ttl` property will be inferred from
* the requests headers. As described in the MDN docs and HTML specification.
*
* See the actual implementation of the
* [`interpretHeader`](https://github.com/arthurfiorette/axios-cache-interceptor/blob/main/src/header/interpreter.ts)
* method for more information.
*
* @default true
* @see https://axios-cache-interceptor.js.org/config/request-specifics#cache-interpretheader
*/
interpretHeader: boolean;
/**
* As most of our cache strategies depends on well known defined HTTP headers, most
* browsers also use those headers to define their own cache strategies and storages.
*
* When your requested routes includes `Cache-Control` in their responses, you may end
* up with we and your browser caching the response, resulting in a **double layer of
* cache**.
*
* This option solves this by including some predefined headers in the request, that
* should tell any client / adapter to not cache the response, thus only we will cache
* it.
*
* _These are headers used in our specific request, it won't affect any other request or
* response that the server may handle._*
*
* Headers included:
*
* - `Cache-Control: no-cache`
* - `Pragma: no-cache`
* - `Expires: 0`
*
* Learn more at
* [#437](https://github.com/arthurfiorette/axios-cache-interceptor/issues/437#issuecomment-1361262194)
* and in this [StackOverflow](https://stackoverflow.com/a/62781874/14681561) answer.
*
* @default true
* @see https://axios-cache-interceptor.js.org/config/request-specifics#cache-cachetakeover
*/
cacheTakeover: boolean;
/**
* Specifies which methods we should handle and cache. This is where you can enable
* caching to `POST`, `PUT`, `DELETE` and other methods, as the default is only `GET`.
*
* We use `methods` in a per-request configuration setup because sometimes you have
* exceptions to the method rule.
*
* @default ['get']
* @see https://axios-cache-interceptor.js.org/config/request-specifics#cache-methods
*/
methods: Lowercase<Method>[];
/**
* An object or function that will be tested against the response to indicate if it can
* be cached.
*
* @default { statusCheck: (status) => [200, 203, 300, 301, 302, 404, 405, 410, 414, 501].includes(status) }
* @see https://axios-cache-interceptor.js.org/config/request-specifics#cache-cachepredicate
*/
cachePredicate: CachePredicate<R, D>;
/**
* Once the request is resolved, this specifies what other responses should change their
* cache. Can be used to update the request or delete other caches. It is a simple
* `Record` with the request id.
*
* Here's an example with some basic login:
*
* Using a function instead of an object is supported but not recommended, as it's
* better to just consume the response normally and write your own code after it. But
* it`s here in case you need it.
*
* @default {{}}
* @see https://axios-cache-interceptor.js.org/config/request-specifics#cache-update
*/
update: CacheUpdater<R, D>;
/**
* If the request should handle
* [`ETag`](https://developer.mozilla.org/pt-BR/docs/Web/HTTP/Headers/ETag) and
* [`If-None-Match
* support`](https://developer.mozilla.org/pt-BR/docs/Web/HTTP/Headers/If-None-Match).
* Use a string to force a custom static value or true to use the previous response
* ETag.
*
* To use `true` (automatic ETag handling), `interpretHeader` option must be set to
* `true`.
*
* @default true
* @see https://axios-cache-interceptor.js.org/config/request-specifics#cache-etag
*/
etag: string | boolean;
/**
* Use
* [`If-Modified-Since`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-Modified-Since)
* header in this request. Use a date to force a custom static value or true to use the
* last cached timestamp.
*
* If never cached before, the header is not set.
*
* If `interpretHeader` is set and a
* [`Last-Modified`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Last-Modified)
* header is sent to us, then value from that header is used, otherwise cache creation
* timestamp will be sent in
* [`If-Modified-Since`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-Modified-Since).
*
* @default false // The opposite of the resulting `etag` option.
* @see https://axios-cache-interceptor.js.org/config/request-specifics#cache-modifiedsince
*/
modifiedSince: Date | boolean;
/**
* Enables cache to be returned if the response comes with an error, either by invalid
* status code, network errors and etc. You can filter the type of error that should be
* stale by using a predicate function.
*
* **If the response is treated as error because of invalid status code _(like when
* using
* [statusCheck](https://axios-cache-interceptor.js.org/config/request-specifics#cache-cachepredicate))_,
* and this ends up `true`, the cache will be preserved over the "invalid" request.**
*
* Types:
*
* - `number` -> the max time (in seconds) that the cache can be reused.
* - `boolean` -> `false` disables and `true` enables with infinite time if no value is
* present on `stale-if-error` in Cache-Control.
* - `function` -> a predicate that can return `number` or `boolean` as described above.
*
* @default true
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#stale-if-error
*/
staleIfError: StaleIfErrorPredicate<R, D>;
/**
* This option bypasses the current cache and always make a new http request. This will
* not delete the current cache, it will just replace the cache when the response
* arrives.
*
* Unlike as `cache: false`, this will not disable the cache, it will just ignore the
* pre-request cache checks before making the request. This way, all post-request
* options are still available and will work as expected.
*
* @default false
* @see https://axios-cache-interceptor.js.org/config/request-specifics#cache-override
*/
override: boolean;
/**
* Asynchronously called when a network request is needed to resolve the data, but an
* older one **and probably expired** cache exists. Its with the current data **BEFORE**
* the network travel starts, so you can use it to temporarily update your UI with
* expired data before the network returns.
*
* Hydrating your components with old data before the network resolves with the newer
* one is better than _flickering_ your entire UI. This is even better when dealing with
* slower networks and persisted cache, like for mobile apps.
*
* If the request can return cached data, as no extensive network travel is needed, the
* hydrate **IS NOT CALLED**, as the axios promise will be resolved instantly.
*
* @default undefined
* @see https://axios-cache-interceptor.js.org/config/request-specifics#cache-hydrate
*/
hydrate:
| undefined
| ((
cache:
| (LoadingStorageValue & {
previous: 'stale' | 'must-revalidate';
})
| CachedStorageValue
| StaleStorageValue
) => void | Promise<void>);
}
/**
* These are properties that are used and shared by the entire application.
*
* ```ts
* const axios = setupCache(axios, OPTIONS);
* ```
*
* The `setupCache` function receives global options and all [request
* specifics](https://axios-cache-interceptor.js.org/config/request-specifics) ones too.
* This way, you can customize the defaults for all requests.
*
* @see https://axios-cache-interceptor.js.org/config/request-specifics
*/
export interface CacheInstance {
/**
* A storage interface is the entity responsible for saving, retrieving and serializing
* data received from network and requested when a axios call is made.
*
* See the [Storages](https://axios-cache-interceptor.js.org/guide/storages) page for
* more information.
*
* @default buildMemoryStorage
* @see https://axios-cache-interceptor.js.org/config#storage
*/
storage: AxiosStorage;
/**
* The function used to create different keys for each request. Defaults to a function
* that priorizes the id, and if not specified, a string is generated using the
* `method`, `baseURL`, `params`, `data` and `url`.
*
* You can learn on how to use them on the [Request
* ID](https://axios-cache-interceptor.js.org/guide/request-id#custom-generator) page.
*
* @default defaultKeyGenerator
* @see https://axios-cache-interceptor.js.org/config#generatekey
*/
generateKey: KeyGenerator;
/**
* A simple object that will hold a promise for each pending request. Used to handle
* concurrent requests.
*
* You'd normally not need to change this, but it is exposed in case you need to use it
* as some sort of listener of know when a request is waiting for other to finish.
*
* @default { }
* @see https://axios-cache-interceptor.js.org/config#waiting
*/
waiting: Record<string, Deferred<CachedResponse>>;
/**
* The function used to interpret all headers from a request and determine a time to
* live (`ttl`) number.
*
* **Many REST backends returns some variation of `Cache-Control: no-cache` or
* `Cache-Control: no-store` headers, which tell us to ignore caching at all. You shall
* disable `headerInterpreter` for those requests.**
*
* **If the debug mode prints `Cache header interpreted as 'dont cache'` this is
* probably the reason.**
*
* The possible returns are:
*
* - `'dont cache'`: the request will not be cached.
* - `'not enough headers'`: the request will find other ways to determine the TTL value.
* - `number`: used as the TTL value.
*
* @default defaultHeaderInterpreter
* @see https://axios-cache-interceptor.js.org/config#headerinterpreter
*/
headerInterpreter: HeaderInterpreter;
/**
* The function that will be used to intercept the request before it is sent to the
* axios adapter.
*
* It is the main function of this library, as it is the bridge between the axios
* request and the cache.
*
* _It wasn't meant to be changed, but if you need to, you can do it by passing a new
* function to this property._*
*
* See its code for more information
* [here](https://github.com/arthurfiorette/axios-cache-interceptor/tree/main/src/interceptors).
*
* @default defaultRequestInterceptor
* @see https://axios-cache-interceptor.js.org/config#requestinterceptor
*/
requestInterceptor: AxiosInterceptor<CacheRequestConfig<unknown, unknown>>;
/**
* The function that will be used to intercept the request after it is returned by the
* axios adapter.
*
* It is the second most important function of this library, as it is the bridge between
* the axios response and the cache.
*
* _It wasn't meant to be changed, but if you need to, you can do it by passing a new
* function to this property._*
*
* See its code for more information
* [here](https://github.com/arthurfiorette/axios-cache-interceptor/tree/main/src/interceptors).
*
* @default defaultResponseInterceptor
* @see https://axios-cache-interceptor.js.org/config#responseinterceptor
*/
responseInterceptor: AxiosInterceptor<CacheAxiosResponse<unknown, unknown>>;
/**
* The debug option will print debug information in the console. It is good if you need
* to trace any undesired behavior or issue. You can enable it by setting `debug` to a
* function that receives an string and returns nothing.
*
* Read the [Debugging](https://axios-cache-interceptor.js.org/guide/debugging) page for
* the complete guide.
*
* @default noop function
* @see https://axios-cache-interceptor.js.org/guide/debugging
*/
debug: (this: void, msg: DebugObject) => void;
}
/**
* An object with any possible type that can be used to log and debug information in
* `development` mode _(a.k.a `__ACI_DEV__ === true`)_
*
* @see https://axios-cache-interceptor.js.org/#/pages/development-mode
*/
export interface DebugObject {
id?: string;
msg?: string;
data?: unknown;
}