@mysten/utils
Version:
Shared utilities for @mysten/* packages
319 lines (316 loc) • 9.99 kB
JavaScript
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var dataloader_exports = {};
__export(dataloader_exports, {
DataLoader: () => DataLoader
});
module.exports = __toCommonJS(dataloader_exports);
class DataLoader {
constructor(batchLoadFn, options) {
if (typeof batchLoadFn !== "function") {
throw new TypeError(
`DataLoader must be constructed with a function which accepts Array<key> and returns Promise<Array<value>>, but got: ${batchLoadFn}.`
);
}
this._batchLoadFn = batchLoadFn;
this._maxBatchSize = getValidMaxBatchSize(options);
this._batchScheduleFn = getValidBatchScheduleFn(options);
this._cacheKeyFn = getValidCacheKeyFn(options);
this._cacheMap = getValidCacheMap(options);
this._batch = null;
this.name = getValidName(options);
}
/**
* Loads a key, returning a `Promise` for the value represented by that key.
*/
load(key) {
if (key === null || key === void 0) {
throw new TypeError(
`The loader.load() function must be called with a value, but got: ${String(key)}.`
);
}
const batch = getCurrentBatch(this);
const cacheMap = this._cacheMap;
let cacheKey;
if (cacheMap) {
cacheKey = this._cacheKeyFn(key);
const cachedPromise = cacheMap.get(cacheKey);
if (cachedPromise) {
const cacheHits = batch.cacheHits || (batch.cacheHits = []);
return new Promise((resolve) => {
cacheHits.push(() => {
resolve(cachedPromise);
});
});
}
}
batch.keys.push(key);
const promise = new Promise((resolve, reject) => {
batch.callbacks.push({ resolve, reject });
});
if (cacheMap) {
cacheMap.set(cacheKey, promise);
}
return promise;
}
/**
* Loads multiple keys, promising an array of values:
*
* var [ a, b ] = await myLoader.loadMany([ 'a', 'b' ]);
*
* This is similar to the more verbose:
*
* var [ a, b ] = await Promise.all([
* myLoader.load('a'),
* myLoader.load('b')
* ]);
*
* However it is different in the case where any load fails. Where
* Promise.all() would reject, loadMany() always resolves, however each result
* is either a value or an Error instance.
*
* var [ a, b, c ] = await myLoader.loadMany([ 'a', 'b', 'badkey' ]);
* // c instanceof Error
*
*/
loadMany(keys) {
if (!isArrayLike(keys)) {
throw new TypeError(
`The loader.loadMany() function must be called with Array<key>, but got: ${keys}.`
);
}
const loadPromises = [];
for (let i = 0; i < keys.length; i++) {
loadPromises.push(this.load(keys[i]).catch((error) => error));
}
return Promise.all(loadPromises);
}
/**
* Clears the value at `key` from the cache, if it exists. Returns itself for
* method chaining.
*/
clear(key) {
const cacheMap = this._cacheMap;
if (cacheMap) {
const cacheKey = this._cacheKeyFn(key);
cacheMap.delete(cacheKey);
}
return this;
}
/**
* Clears the entire cache. To be used when some event results in unknown
* invalidations across this particular `DataLoader`. Returns itself for
* method chaining.
*/
clearAll() {
const cacheMap = this._cacheMap;
if (cacheMap) {
cacheMap.clear();
}
return this;
}
/**
* Adds the provided key and value to the cache. If the key already
* exists, no change is made. Returns itself for method chaining.
*
* To prime the cache with an error at a key, provide an Error instance.
*/
prime(key, value) {
const cacheMap = this._cacheMap;
if (cacheMap) {
const cacheKey = this._cacheKeyFn(key);
if (cacheMap.get(cacheKey) === void 0) {
let promise;
if (value instanceof Error) {
promise = Promise.reject(value);
promise.catch(() => {
});
} else {
promise = Promise.resolve(value);
}
cacheMap.set(cacheKey, promise);
}
}
return this;
}
}
const enqueuePostPromiseJob = (
/** @ts-ignore */
typeof process === "object" && typeof process.nextTick === "function" ? function(fn) {
if (!resolvedPromise) {
resolvedPromise = Promise.resolve();
}
resolvedPromise.then(() => {
process.nextTick(fn);
});
} : (
// @ts-ignore
typeof setImmediate === "function" ? function(fn) {
setImmediate(fn);
} : function(fn) {
setTimeout(fn);
}
)
);
let resolvedPromise;
function getCurrentBatch(loader) {
const existingBatch = loader._batch;
if (existingBatch !== null && !existingBatch.hasDispatched && existingBatch.keys.length < loader._maxBatchSize) {
return existingBatch;
}
const newBatch = { hasDispatched: false, keys: [], callbacks: [] };
loader._batch = newBatch;
loader._batchScheduleFn(() => {
dispatchBatch(loader, newBatch);
});
return newBatch;
}
function dispatchBatch(loader, batch) {
batch.hasDispatched = true;
if (batch.keys.length === 0) {
resolveCacheHits(batch);
return;
}
let batchPromise;
try {
batchPromise = loader._batchLoadFn(batch.keys);
} catch (e) {
return failedDispatch(
loader,
batch,
new TypeError(
`DataLoader must be constructed with a function which accepts Array<key> and returns Promise<Array<value>>, but the function errored synchronously: ${String(e)}.`
)
);
}
if (!batchPromise || typeof batchPromise.then !== "function") {
return failedDispatch(
loader,
batch,
new TypeError(
`DataLoader must be constructed with a function which accepts Array<key> and returns Promise<Array<value>>, but the function did not return a Promise: ${String(batchPromise)}.`
)
);
}
Promise.resolve(batchPromise).then((values) => {
if (!isArrayLike(values)) {
throw new TypeError(
`DataLoader must be constructed with a function which accepts Array<key> and returns Promise<Array<value>>, but the function did not return a Promise of an Array: ${String(values)}.`
);
}
if (values.length !== batch.keys.length) {
throw new TypeError(
`DataLoader must be constructed with a function which accepts Array<key> and returns Promise<Array<value>>, but the function did not return a Promise of an Array of the same length as the Array of keys.
Keys:
${String(batch.keys)}
Values:
${String(values)}`
);
}
resolveCacheHits(batch);
for (let i = 0; i < batch.callbacks.length; i++) {
const value = values[i];
if (value instanceof Error) {
batch.callbacks[i].reject(value);
} else {
batch.callbacks[i].resolve(value);
}
}
}).catch((error) => {
failedDispatch(loader, batch, error);
});
}
function failedDispatch(loader, batch, error) {
resolveCacheHits(batch);
for (let i = 0; i < batch.keys.length; i++) {
loader.clear(batch.keys[i]);
batch.callbacks[i].reject(error);
}
}
function resolveCacheHits(batch) {
if (batch.cacheHits) {
for (let i = 0; i < batch.cacheHits.length; i++) {
batch.cacheHits[i]();
}
}
}
function getValidMaxBatchSize(options) {
const shouldBatch = !options || options.batch !== false;
if (!shouldBatch) {
return 1;
}
const maxBatchSize = options && options.maxBatchSize;
if (maxBatchSize === void 0) {
return Infinity;
}
if (typeof maxBatchSize !== "number" || maxBatchSize < 1) {
throw new TypeError(`maxBatchSize must be a positive number: ${maxBatchSize}`);
}
return maxBatchSize;
}
function getValidBatchScheduleFn(options) {
const batchScheduleFn = options && options.batchScheduleFn;
if (batchScheduleFn === void 0) {
return enqueuePostPromiseJob;
}
if (typeof batchScheduleFn !== "function") {
throw new TypeError(`batchScheduleFn must be a function: ${batchScheduleFn}`);
}
return batchScheduleFn;
}
function getValidCacheKeyFn(options) {
const cacheKeyFn = options && options.cacheKeyFn;
if (cacheKeyFn === void 0) {
return (key) => key;
}
if (typeof cacheKeyFn !== "function") {
throw new TypeError(`cacheKeyFn must be a function: ${cacheKeyFn}`);
}
return cacheKeyFn;
}
function getValidCacheMap(options) {
const shouldCache = !options || options.cache !== false;
if (!shouldCache) {
return null;
}
const cacheMap = options && options.cacheMap;
if (cacheMap === void 0) {
return /* @__PURE__ */ new Map();
}
if (cacheMap !== null) {
const cacheFunctions = ["get", "set", "delete", "clear"];
const missingFunctions = cacheFunctions.filter(
(fnName) => cacheMap && typeof cacheMap[fnName] !== "function"
);
if (missingFunctions.length !== 0) {
throw new TypeError("Custom cacheMap missing methods: " + missingFunctions.join(", "));
}
}
return cacheMap;
}
function getValidName(options) {
if (options && options.name) {
return options.name;
}
return null;
}
function isArrayLike(x) {
return typeof x === "object" && x !== null && "length" in x && typeof x.length === "number" && (x.length === 0 || x.length > 0 && Object.prototype.hasOwnProperty.call(x, x.length - 1));
}
//# sourceMappingURL=dataloader.js.map
;