scrivito
Version:
Scrivito is a professional, yet easy to use SaaS Enterprise Content Management Service, built for digital agencies and medium to large businesses. It is completely maintenance-free, cost-effective, and has unprecedented performance and security.
81 lines (71 loc) • 2.88 kB
text/typescript
import {
LoadableState,
createLoadableData,
isCurrentlyCapturing,
} from 'scrivito_sdk/loadable';
import { dejitterStateStream } from 'scrivito_sdk/loadable/dejitter_state_stream';
import { observeAndLoad } from 'scrivito_sdk/loadable/observe_and_load';
import { createStateContainer } from 'scrivito_sdk/state';
interface FunctionState<T> {
[serializedArgs: string]: LoadableState<T>;
}
/** Wrap the given function so that it uses a "loadable" cache.
*
* If the function is called with a loadable context (e.g. inside load or connect),
* caching will automatically be used.
*
* Outside of a loadable context, the function will be called without caching,
* i.e. as a normal JS function.
*
* Caching has several effects:
* * The function is computed asynchronously ("in the background").
* * The function is cached, i.e. several consumers can use the function with
* the same input, but only one computation takes place for each unique input.
* * Once completely loaded, the function does not "jitter",
* i.e. no temporary incomplete results are shown.
*
* While the function is loading, the 'defaultValue' is returned.
*
* If the function has parameters, an 'argsToString' function must be provided,
* which maps each unique input to a unique string (which is used as the cache key).
*
* It is recommended to use primitive parameters, e.g. an obj ID instead of a `BasicObj` instance.
*/
export function loadableFunction<Args extends unknown[], Return>(
defaultValue: Return,
argsToString: (...args: Args) => string,
fn: (...args: Args) => Return
): (...args: Args) => Return;
export function loadableFunction<Return>(
defaultValue: Return,
zeroArgsFn?: () => Return
): () => Return;
export function loadableFunction<Args extends unknown[], Return>(
defaultValue: Return,
argsToStringOrZeroArgsFn:
| ((...args: Args) => string)
| ((...args: Args) => Return),
fn?: (...args: Args) => Return
): (...args: Args) => Return {
if (!fn) {
const zeroArgsFn = argsToStringOrZeroArgsFn as (...args: Args) => Return;
return createLoadableFunction(defaultValue, () => '', zeroArgsFn);
}
const argsToString = argsToStringOrZeroArgsFn as (...args: Args) => string;
return createLoadableFunction(defaultValue, argsToString, fn);
}
function createLoadableFunction<Args extends unknown[], Return>(
defaultValue: Return,
argsToString: (...args: Args) => string,
fn: (...args: Args) => Return
): (...args: Args) => Return {
const functionState = createStateContainer<FunctionState<Return>>();
return (...args: Args) => {
if (!isCurrentlyCapturing()) return fn(...args);
const data = createLoadableData({
state: functionState.subState(argsToString(...args)),
loadableStream: dejitterStateStream(observeAndLoad(() => fn(...args))),
});
return data.getWithDefault(defaultValue);
};
}