UNPKG

ioredis-cache

Version:

A promise-based cache package for Nodejs using IORedis

555 lines (554 loc) 26.4 kB
"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); } };