ioredis-cache
Version:
A promise-based cache package for Nodejs using IORedis
555 lines (554 loc) • 26.4 kB
JavaScript
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
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());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Acquire = exports.ReleaseFailedError = exports.AcquireFailedError = void 0;
var ACQUIRE_FAILED_MESSAGE = "Failed to acquire requested amounts";
var RELEASE_FAILED_MESSAGE = "Failed to release acquired amounts";
var HASH_RELEASE_FAILED_MESSAGE = "Failed to release acquired hash amounts";
var RELEASE_AFTER_CALLBACK_FAILED_MESSAGE = "Failed to release acquired amounts after acquire callback failed";
var HASH_RELEASE_AFTER_CALLBACK_FAILED_MESSAGE = "Failed to release acquired hash amounts after acquire callback failed";
var DEFAULT_PREFIX = "";
var INTEGER_RADIX = 10;
var NON_FINITE_AMOUNT_MESSAGE = "Acquire amounts must be finite numbers";
var AcquireFailedError = /** @class */ (function (_super) {
__extends(AcquireFailedError, _super);
function AcquireFailedError(message, cause) {
if (message === void 0) { message = ACQUIRE_FAILED_MESSAGE; }
var _this = _super.call(this, message) || this;
_this.name = "AcquireFailedError";
_this.cause = cause;
Object.setPrototypeOf(_this, AcquireFailedError.prototype);
return _this;
}
return AcquireFailedError;
}(Error));
exports.AcquireFailedError = AcquireFailedError;
var ReleaseFailedError = /** @class */ (function (_super) {
__extends(ReleaseFailedError, _super);
function ReleaseFailedError(message, cause) {
if (message === void 0) { message = RELEASE_FAILED_MESSAGE; }
var _this = _super.call(this, message) || this;
_this.name = "ReleaseFailedError";
_this.cause = cause;
Object.setPrototypeOf(_this, ReleaseFailedError.prototype);
return _this;
}
return ReleaseFailedError;
}(Error));
exports.ReleaseFailedError = ReleaseFailedError;
var Acquire = /** @class */ (function () {
function Acquire(redis) {
this.redis = redis;
}
Acquire.prototype.acquire = function (key, amount, fn, options) {
return __awaiter(this, void 0, void 0, function () {
var normalizedOptions, float, change, parse, rollbackAmount, newAmount, _a, acquireResult, error_1;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
normalizedOptions = normalizeAcquireOptions(options);
assertFiniteAmount(amount);
assertFiniteLimit(normalizedOptions.limit);
float = normalizedOptions.float === true;
change = this.getChangeCommand(float);
parse = float ? parseFloatValue : parseIntegerValue;
rollbackAmount = amount;
_b.label = 1;
case 1:
_b.trys.push([1, 5, , 7]);
_a = parse;
return [4 /*yield*/, change(key, amount)];
case 2:
newAmount = _a.apply(void 0, [_b.sent()]);
acquireResult = this.getAcquiredResult({
key: String(key),
desireAmount: amount,
limitAmount: normalizedOptions.limit,
}, newAmount);
return [4 /*yield*/, this.releaseSingleLackingAmount(key, acquireResult, change)];
case 3:
_b.sent();
rollbackAmount = acquireResult.acquiredAmount;
return [4 /*yield*/, fn(acquireResult.currentAmount, acquireResult.acquiredAmount, acquireResult.lackingAmount)];
case 4: return [2 /*return*/, _b.sent()];
case 5:
error_1 = _b.sent();
return [4 /*yield*/, change(key, -rollbackAmount)];
case 6:
_b.sent();
throw error_1;
case 7: return [2 /*return*/];
}
});
});
};
Acquire.prototype.hashAcquire = function (key, id, amount, fn, options) {
return __awaiter(this, void 0, void 0, function () {
var normalizedOptions, float, change, parse, rollbackAmount, newAmount, _a, acquireResult, error_2;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
normalizedOptions = normalizeAcquireOptions(options);
assertFiniteAmount(amount);
assertFiniteLimit(normalizedOptions.limit);
float = normalizedOptions.float === true;
change = this.getHashChangeCommand(float);
parse = float ? parseFloatValue : parseIntegerValue;
rollbackAmount = amount;
_b.label = 1;
case 1:
_b.trys.push([1, 5, , 7]);
_a = parse;
return [4 /*yield*/, change(key, id, amount)];
case 2:
newAmount = _a.apply(void 0, [_b.sent()]);
acquireResult = this.getAcquiredResult({
key: String(id),
desireAmount: amount,
limitAmount: normalizedOptions.limit,
}, newAmount);
return [4 /*yield*/, this.releaseSingleHashLackingAmount(key, id, acquireResult, change)];
case 3:
_b.sent();
rollbackAmount = acquireResult.acquiredAmount;
return [4 /*yield*/, fn(acquireResult.currentAmount, acquireResult.acquiredAmount, acquireResult.lackingAmount)];
case 4: return [2 /*return*/, _b.sent()];
case 5:
error_2 = _b.sent();
return [4 /*yield*/, change(key, id, -rollbackAmount)];
case 6:
_b.sent();
throw error_2;
case 7: return [2 /*return*/];
}
});
});
};
Acquire.prototype.acquireMany = function (items, fn, options) {
var _a;
return __awaiter(this, void 0, void 0, function () {
var prefix;
var _this = this;
return __generator(this, function (_b) {
assertFiniteItems(items);
prefix = (_a = options === null || options === void 0 ? void 0 : options.prefix) !== null && _a !== void 0 ? _a : DEFAULT_PREFIX;
return [2 /*return*/, this.acquireBy(items, fn, this.getAcquireCommand(prefix, (options === null || options === void 0 ? void 0 : options.float) === true), function (releaseItems) { return _this.releaseMany(releaseItems, options); }, RELEASE_AFTER_CALLBACK_FAILED_MESSAGE)];
});
});
};
Acquire.prototype.hashAcquireMany = function (hashKey, items, fn, options) {
var _a;
return __awaiter(this, void 0, void 0, function () {
var redisKey;
var _this = this;
return __generator(this, function (_b) {
assertFiniteItems(items);
redisKey = "" + ((_a = options === null || options === void 0 ? void 0 : options.prefix) !== null && _a !== void 0 ? _a : DEFAULT_PREFIX) + hashKey;
return [2 /*return*/, this.acquireBy(items, fn, this.getHashAcquireCommand(redisKey, (options === null || options === void 0 ? void 0 : options.float) === true), function (releaseItems) { return _this.hashReleaseMany(hashKey, releaseItems, options); }, HASH_RELEASE_AFTER_CALLBACK_FAILED_MESSAGE)];
});
});
};
Acquire.prototype.getChangeCommand = function (float) {
var _this = this;
if (float) {
return function (key, amount) { return _this.redis.incrbyfloat(key, amount); };
}
return function (key, amount) { return _this.redis.incrby(key, amount); };
};
Acquire.prototype.getHashChangeCommand = function (float) {
var _this = this;
if (float) {
return function (key, id, amount) {
return _this.redis.hincrbyfloat(key, id, amount);
};
}
return function (key, id, amount) {
return _this.redis.hincrby(key, id, amount);
};
};
Acquire.prototype.getAcquireCommand = function (prefix, float) {
if (float) {
return function (pipeline, item) {
return pipeline.incrbyfloat("" + prefix + item.key, item.desireAmount);
};
}
return function (pipeline, item) {
return pipeline.incrby("" + prefix + item.key, item.desireAmount);
};
};
Acquire.prototype.getHashAcquireCommand = function (redisKey, float) {
if (float) {
return function (pipeline, item) {
return pipeline.hincrbyfloat(redisKey, String(item.key), item.desireAmount);
};
}
return function (pipeline, item) {
return pipeline.hincrby(redisKey, String(item.key), item.desireAmount);
};
};
Acquire.prototype.releaseMany = function (items, options) {
var _a;
return __awaiter(this, void 0, void 0, function () {
var prefix;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
prefix = (_a = options === null || options === void 0 ? void 0 : options.prefix) !== null && _a !== void 0 ? _a : DEFAULT_PREFIX;
return [4 /*yield*/, this.releaseBy(items, this.getReleaseCommand(prefix, (options === null || options === void 0 ? void 0 : options.float) === true), RELEASE_FAILED_MESSAGE)];
case 1:
_b.sent();
return [2 /*return*/];
}
});
});
};
Acquire.prototype.hashReleaseMany = function (hashKey, items, options) {
var _a;
return __awaiter(this, void 0, void 0, function () {
var redisKey;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
redisKey = "" + ((_a = options === null || options === void 0 ? void 0 : options.prefix) !== null && _a !== void 0 ? _a : DEFAULT_PREFIX) + hashKey;
return [4 /*yield*/, this.releaseBy(items, this.getHashReleaseCommand(redisKey, (options === null || options === void 0 ? void 0 : options.float) === true), HASH_RELEASE_FAILED_MESSAGE)];
case 1:
_b.sent();
return [2 /*return*/];
}
});
});
};
Acquire.prototype.getReleaseCommand = function (prefix, float) {
if (float) {
return function (pipeline, item) {
return pipeline.incrbyfloat("" + prefix + item.key, -item.amount);
};
}
return function (pipeline, item) {
return pipeline.decrby("" + prefix + item.key, item.amount);
};
};
Acquire.prototype.getHashReleaseCommand = function (redisKey, float) {
if (float) {
return function (pipeline, item) {
return pipeline.hincrbyfloat(redisKey, String(item.key), -item.amount);
};
}
return function (pipeline, item) {
return pipeline.hincrby(redisKey, String(item.key), -item.amount);
};
};
Acquire.prototype.releaseBy = function (items, releaseCommand, failureMessage) {
return __awaiter(this, void 0, void 0, function () {
var results, error_3;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!items.length) {
return [2 /*return*/];
}
_a.label = 1;
case 1:
_a.trys.push([1, 3, , 4]);
return [4 /*yield*/, items.reduce(releaseCommand, this.redis.pipeline()).exec()];
case 2:
results = _a.sent();
return [3 /*break*/, 4];
case 3:
error_3 = _a.sent();
throw new ReleaseFailedError(failureMessage, error_3);
case 4:
if (!results || results.some(function (result) { return result[0] !== null; })) {
throw new ReleaseFailedError(failureMessage);
}
return [2 /*return*/];
}
});
});
};
Acquire.prototype.acquireBy = function (items, fn, acquireCommand, releaseAcquired, releaseAfterCallbackFailedMessage) {
return __awaiter(this, void 0, void 0, function () {
var results, acquiredResults;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!!items.length) return [3 /*break*/, 2];
return [4 /*yield*/, fn([])];
case 1: return [2 /*return*/, _a.sent()];
case 2: return [4 /*yield*/, this.executeAcquirePipeline(items, acquireCommand)];
case 3:
results = _a.sent();
return [4 /*yield*/, this.releaseAfterAcquireFailure(items, results, releaseAcquired)];
case 4:
_a.sent();
acquiredResults = this.getAcquiredResults(items, results);
return [4 /*yield*/, this.releaseLackingAmounts(acquiredResults, releaseAcquired)];
case 5:
_a.sent();
return [2 /*return*/, this.runAcquireCallback(fn, acquiredResults, releaseAcquired, releaseAfterCallbackFailedMessage)];
}
});
});
};
Acquire.prototype.executeAcquirePipeline = function (items, acquireCommand) {
return __awaiter(this, void 0, void 0, function () {
var results, error_4;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
_a.trys.push([0, 2, , 3]);
return [4 /*yield*/, items
.reduce(acquireCommand, this.redis.pipeline())
.exec()];
case 1:
results = _a.sent();
if (!results) {
throw new AcquireFailedError();
}
return [2 /*return*/, results];
case 2:
error_4 = _a.sent();
if (error_4 instanceof AcquireFailedError) {
throw error_4;
}
throw new AcquireFailedError(ACQUIRE_FAILED_MESSAGE, error_4);
case 3: return [2 /*return*/];
}
});
});
};
Acquire.prototype.releaseAfterAcquireFailure = function (items, results, releaseAcquired) {
return __awaiter(this, void 0, void 0, function () {
var errors;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
errors = this.getFailedIndexes(results);
if (!errors.size) {
return [2 /*return*/];
}
return [4 /*yield*/, releaseAcquired(this.getDesiredReleaseItems(items, errors))];
case 1:
_a.sent();
throw new AcquireFailedError();
}
});
});
};
Acquire.prototype.releaseLackingAmounts = function (acquiredResults, releaseAcquired) {
return __awaiter(this, void 0, void 0, function () {
var lackingItems;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
lackingItems = acquiredResults
.filter(function (item) { return item.lackingAmount > 0; })
.map(function (item) { return ({ key: item.key, amount: item.lackingAmount }); });
return [4 /*yield*/, releaseAcquired(lackingItems)];
case 1:
_a.sent();
return [2 /*return*/];
}
});
});
};
Acquire.prototype.releaseSingleLackingAmount = function (key, acquireResult, change) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (acquireResult.lackingAmount <= 0) {
return [2 /*return*/];
}
return [4 /*yield*/, change(key, -acquireResult.lackingAmount)];
case 1:
_a.sent();
return [2 /*return*/];
}
});
});
};
Acquire.prototype.releaseSingleHashLackingAmount = function (key, id, acquireResult, change) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (acquireResult.lackingAmount <= 0) {
return [2 /*return*/];
}
return [4 /*yield*/, change(key, id, -acquireResult.lackingAmount)];
case 1:
_a.sent();
return [2 /*return*/];
}
});
});
};
Acquire.prototype.runAcquireCallback = function (fn, acquiredResults, releaseAcquired, releaseAfterCallbackFailedMessage) {
return __awaiter(this, void 0, void 0, function () {
var error_5;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
_a.trys.push([0, 2, , 4]);
return [4 /*yield*/, fn(acquiredResults)];
case 1: return [2 /*return*/, _a.sent()];
case 2:
error_5 = _a.sent();
return [4 /*yield*/, this.releaseAfterCallbackFailure(acquiredResults, releaseAcquired, releaseAfterCallbackFailedMessage, error_5)];
case 3:
_a.sent();
throw error_5;
case 4: return [2 /*return*/];
}
});
});
};
Acquire.prototype.releaseAfterCallbackFailure = function (acquiredResults, releaseAcquired, releaseAfterCallbackFailedMessage, acquireError) {
return __awaiter(this, void 0, void 0, function () {
var releaseError_1;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
_a.trys.push([0, 2, , 3]);
return [4 /*yield*/, releaseAcquired(this.getAcquiredReleaseItems(acquiredResults))];
case 1:
_a.sent();
return [3 /*break*/, 3];
case 2:
releaseError_1 = _a.sent();
throw new ReleaseFailedError(releaseAfterCallbackFailedMessage, {
acquireError: acquireError,
releaseError: releaseError_1,
});
case 3: return [2 /*return*/];
}
});
});
};
Acquire.prototype.getFailedIndexes = function (results) {
return results.reduce(function (errors, result, index) {
return result[0] !== null ? errors.add(index) : errors;
}, new Set());
};
Acquire.prototype.getDesiredReleaseItems = function (items, failedIndexes) {
return items
.filter(function (_, index) { return !failedIndexes.has(index); })
.map(function (item) { return ({ key: item.key, amount: item.desireAmount }); });
};
Acquire.prototype.getAcquiredReleaseItems = function (items) {
return items
.filter(function (item) { return item.acquiredAmount > 0; })
.map(function (item) { return ({ key: item.key, amount: item.acquiredAmount }); });
};
Acquire.prototype.getAcquiredResults = function (items, results) {
var _this = this;
return items.map(function (item, index) {
return _this.getAcquiredResult(item, Number(results[index][1]));
});
};
Acquire.prototype.getAcquiredResult = function (item, newAmount) {
if (item.limitAmount === undefined || item.limitAmount === null) {
return {
key: item.key,
currentAmount: newAmount,
acquiredAmount: item.desireAmount,
lackingAmount: 0,
};
}
var prevAmount = newAmount - item.desireAmount;
var availableAmount = Math.max(0, item.limitAmount - prevAmount);
var acquiredAmount = Math.min(item.desireAmount, availableAmount);
return {
key: item.key,
currentAmount: prevAmount + acquiredAmount,
acquiredAmount: acquiredAmount,
lackingAmount: item.desireAmount - acquiredAmount,
};
};
return Acquire;
}());
exports.Acquire = Acquire;
var parseIntegerValue = function (value) {
if (typeof value === "number") {
return value;
}
return parseInt(value, INTEGER_RADIX);
};
var parseFloatValue = function (value) {
if (typeof value === "number") {
return value;
}
return parseFloat(value);
};
var normalizeAcquireOptions = function (options) {
if (typeof options === "boolean") {
return { float: options };
}
return options !== null && options !== void 0 ? options : {};
};
var assertFiniteItems = function (items) {
for (var _i = 0, items_1 = items; _i < items_1.length; _i++) {
var item = items_1[_i];
assertFiniteAmount(item.desireAmount);
assertFiniteLimit(item.limitAmount);
}
};
var assertFiniteAmount = function (amount) {
if (!Number.isFinite(amount)) {
throw new AcquireFailedError(NON_FINITE_AMOUNT_MESSAGE);
}
};
var assertFiniteLimit = function (limit) {
if (limit !== undefined && !Number.isFinite(limit)) {
throw new AcquireFailedError(NON_FINITE_AMOUNT_MESSAGE);
}
};