with-simple-caching
Version:
A wrapper that makes it simple to add caching to any function
104 lines • 6.58 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.withSimpleCachingAsync = exports.getOutputCacheOptionFromCacheInput = void 0;
const simple_in_memory_cache_1 = require("simple-in-memory-cache");
const type_fns_1 = require("type-fns");
const getCacheFromCacheOption_1 = require("../options/getCacheFromCacheOption");
const defaults_1 = require("../serde/defaults");
const withExtendableCaching_1 = require("./withExtendableCaching");
/**
* method to get the output cache option chosen by the user from the cache input
*/
const getOutputCacheOptionFromCacheInput = (cacheInput) => 'output' in cacheInput ? cacheInput.output : cacheInput;
exports.getOutputCacheOptionFromCacheInput = getOutputCacheOptionFromCacheInput;
/**
* method to get the output cache option chosen by the user from the cache input
*/
const getDeduplicationCacheOptionFromCacheInput = (cacheInput) => 'deduplication' in cacheInput
? cacheInput.deduplication
: (0, simple_in_memory_cache_1.createCache)({
expiration: { minutes: 15 }, // support deduplicating requests that take up to 15 minutes to resolve, by default (note: we remove the promise as soon as it resolves through "serialize" method below)
});
/**
* a wrapper which adds asynchronous caching to asynchronous logic
*
* note
* - utilizes an additional in-memory synchronous cache under the hood to prevent duplicate requests (otherwise, while async cache is resolving, a duplicate parallel request may have be made)
* - can be given a synchronous cache, since what you can do on an asynchronous cache you can do on a synchronous cache, but not the other way around
*/
const withSimpleCachingAsync = (logic, { cache: cacheOption, serialize: { key: serializeKey = defaults_1.defaultKeySerializationMethod, // default serialize key to JSON.stringify
value: serializeValue = defaults_1.defaultValueSerializationMethod, // default serialize value to noOp
} = {}, deserialize: { value: deserializeValue = defaults_1.noOp } = {}, expiration, bypass = {
get: defaults_1.defaultShouldBypassGetMethod,
set: defaults_1.defaultShouldBypassSetMethod,
}, }) => {
// add async caching to the logic
const logicWithAsyncCaching = ((...args) => __awaiter(void 0, void 0, void 0, function* () {
var _a, _b;
// define key based on args the function was invoked with
const key = serializeKey({ forInput: args });
// define cache based on options
const cache = (0, getCacheFromCacheOption_1.getCacheFromCacheOption)({
forInput: args,
cacheOption: (0, exports.getOutputCacheOptionFromCacheInput)(cacheOption),
});
// see if its already cached
const cachedValue = ((_a = bypass.get) === null || _a === void 0 ? void 0 : _a.call(bypass, args))
? undefined
: yield cache.get(key);
if ((0, type_fns_1.isNotUndefined)(cachedValue))
return deserializeValue(cachedValue); // if already cached, return it immediately
// if its not, grab the output from the logic
const output = yield logic(...args);
// if was asked to bypass cache.set, we can return the output now
if ((_b = bypass.set) === null || _b === void 0 ? void 0 : _b.call(bypass, args))
return output;
// set the output to the cache
const serializedOutput = serializeValue(output);
yield cache.set(key, serializedOutput, { expiration });
// if the output was undefined, we can just return here - no deserialization needed
if (output === undefined)
return output;
// and now re-get from the cache, to ensure that output on first response === output on second response
const cachedValueNow = yield cache.get(key);
if ((0, type_fns_1.isNotUndefined)(cachedValueNow))
return deserializeValue(cachedValueNow);
// otherwise, somehow, get-after-set returned undefined. warn about this and return output
// eslint-disable-next-line no-console
console.warn(
// warn about this because it should never occur
'withSimpleCachingAsync encountered a situation which should not occur: cache.get returned undefined immediately after having been set. returning the output directly to prevent irrecoverable failure.', { key });
return output;
}));
// wrap the logic with extended sync caching, to ensure that duplicate requests resolve the same promise from in-memory (rather than each getting a promise to check the async cache + operate separately)
const { execute, invalidate } = (0, withExtendableCaching_1.withExtendableCaching)(logicWithAsyncCaching, {
cache: getDeduplicationCacheOptionFromCacheInput(cacheOption),
serialize: {
key: serializeKey,
},
});
// define a function which the user will run which kicks off the result + invalidates the in-memory cache promise as soon as it finishes
const logicWithAsyncCachingAndInMemoryRequestDeduplication = (...args) => __awaiter(void 0, void 0, void 0, function* () {
// start executing the request w/ async caching + sync caching
const promiseResult = execute(...args);
// ensure that after the promise resolves, we remove it from the cache (so that unique subsequent requests can still be made)
const promiseResultAfterInvalidation = promiseResult
.finally(() => invalidate({ forInput: args }))
.then(() => promiseResult);
// return the result after invalidation
return promiseResultAfterInvalidation;
});
// return the function w/ async caching and sync-in-memory-request-deduplication
return logicWithAsyncCachingAndInMemoryRequestDeduplication;
};
exports.withSimpleCachingAsync = withSimpleCachingAsync;
//# sourceMappingURL=withSimpleCachingAsync.js.map