UNPKG

relational-redis-store

Version:
839 lines 48.9 kB
"use strict"; var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; 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 }; } }; var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) { if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { if (ar || !(i in from)) { if (!ar) ar = Array.prototype.slice.call(from, 0, i); ar[i] = from[i]; } } return to.concat(ar || Array.prototype.slice.call(from)); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Store = void 0; var flatten_1 = __importDefault(require("flatten")); var deep_equal_1 = __importDefault(require("deep-equal")); var deepmerge_1 = __importDefault(require("deepmerge")); var ts_async_results_1 = require("ts-async-results"); var ts_results_1 = require("ts-results"); var json_stable_stringify_1 = __importDefault(require("json-stable-stringify")); var util_1 = require("./util"); var redis_lock_1 = __importDefault(require("redis-lock")); var util_2 = require("util"); var Store = /** @class */ (function () { function Store(redis, config) { var _this = this; this.redis = redis; this.toNamespacedCollection = function (collection) { return "".concat(_this.namespace).concat(collection); }; this.logger = (config === null || config === void 0 ? void 0 : config.logger) || console; this.redisClient = this.redis; this.redis.redis.on('connect', function () { _this.logger.info('[Store] Redis Connected', { connection: _this.redis.redis.connection_id, }); }); // TODO: Make sure this works! this.redis.redis.off('connect', function () { _this.logger.info('[Store] Redis Disonnected', { connection: _this.redis.redis.connection_id, }); }); this.redisLock = (0, util_2.promisify)((0, redis_lock_1.default)(redis.redis)); this.namespace = (config === null || config === void 0 ? void 0 : config.namespace) ? "".concat(config === null || config === void 0 ? void 0 : config.namespace, "::") : ''; } Store.prototype.lockCollection = function (collection) { var nameSpacedCollection = this.toNamespacedCollection(collection); return this.redisLock("locked:".concat(nameSpacedCollection)); }; Store.prototype.lockCollectionItem = function (collection, id) { var nameSpacedCollection = this.toNamespacedCollection(collection); return this.redisLock("locked:".concat(nameSpacedCollection, ":").concat(id)); }; Store.prototype.addItemToCollection = function (collection, val, id, opts) { var _this = this; if (opts === void 0) { opts = { foreignKeys: {}, }; } var nameSpacedCollection = this.toNamespacedCollection(collection); return new ts_async_results_1.AsyncResultWrapper(function () { return __awaiter(_this, void 0, void 0, function () { var unlock, resolvedId, _a, field, item, transactions, res, parsedResItem; var _b; return __generator(this, function (_c) { switch (_c.label) { case 0: return [4 /*yield*/, this.lockCollection(collection)]; case 1: unlock = _c.sent(); if (!id) return [3 /*break*/, 2]; _a = id; return [3 /*break*/, 4]; case 2: return [4 /*yield*/, this.redis .hget(nameSpacedCollection, '_index') .then(function (v) { return (v !== null ? String(Number(v) + 1) : '1'); })]; case 3: _a = _c.sent(); _c.label = 4; case 4: resolvedId = _a; field = (0, util_1.toCollectionId)(nameSpacedCollection, resolvedId); item = __assign(__assign({ val: val, id: resolvedId }, (opts.foreignKeys && Object.keys(opts.foreignKeys).length > 0 && { foreignKeys: opts.foreignKeys, })), (opts.indexBy && opts.indexBy.length > 0 && { indexedIn: opts.indexBy.reduce(function (prev, byField) { var _a; return (__assign(__assign({}, prev), (_a = {}, _a[(0, util_1.toIndexedCollectionName)(nameSpacedCollection, String(byField))] = val[byField], _a))); }, {}), })); transactions = this.redis .multi() .hset(nameSpacedCollection, [field, JSON.stringify(item)]) .hincrby(nameSpacedCollection, '_index', 1) .hlen(nameSpacedCollection) .hget(nameSpacedCollection, field); // If there is an indexBy, create the indexBy hashMaps (_b = opts.indexBy) === null || _b === void 0 ? void 0 : _b.forEach(function (key) { transactions = transactions.hset((0, util_1.toIndexedCollectionName)(nameSpacedCollection, String(key)), "".concat(val[key]), resolvedId); }); return [4 /*yield*/, this.redis.execMulti(transactions)]; case 5: res = _c.sent(); if (res === null) { unlock(); return [2 /*return*/, new ts_results_1.Err('CollectionFieldInexistent')]; } parsedResItem = JSON.parse(res[3]); return [4 /*yield*/, this.getItemInCollection(collection, parsedResItem.id) .map(function (item) { return ({ index: Number(res[1]), length: Number(res[2]) - 1, item: item, }); }) .resolve() .finally(function () { return unlock(); })]; case 6: return [2 /*return*/, _c.sent()]; } }); }); }).map(ts_async_results_1.AsyncResult.passThrough(function (next) { _this.logger.info('[Store] Item Added', { collection: collection, id: next.item.id, length: next.index, }); })); }; Store.prototype.getCollectionIndex = function (collection) { var _this = this; var nameSpacedCollection = this.toNamespacedCollection(collection); return new ts_async_results_1.AsyncResultWrapper(function () { return __awaiter(_this, void 0, void 0, function () { var v; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.redis.hget(nameSpacedCollection, '_index')]; case 1: v = _a.sent(); if (v === null) { // If the index to a collection doesnt exist it means it's not instantiated yet return [2 /*return*/, new ts_results_1.Ok(0)]; } return [2 /*return*/, new ts_results_1.Ok(Number(v))]; } }); }); }); }; Store.prototype.getCollectionLength = function (collection) { var _this = this; var nameSpacedCollection = this.toNamespacedCollection(collection); return new ts_async_results_1.AsyncResultWrapper(function () { return __awaiter(_this, void 0, void 0, function () { var v, error_1; return __generator(this, function (_a) { switch (_a.label) { case 0: _a.trys.push([0, 2, , 3]); return [4 /*yield*/, this.redis.hlen(nameSpacedCollection)]; case 1: v = _a.sent(); return [2 /*return*/, new ts_results_1.Ok(Number(v))]; case 2: error_1 = _a.sent(); this.logger.error('[Store] Get Collection Length', { collection: collection, error: error_1, }); return [2 /*return*/, new ts_results_1.Err('GenericRedisFailure')]; case 3: return [2 /*return*/]; } }); }); }); }; Store.prototype.compactAllForeignKeys = function (itemsMetadata) { var allFKsToFIdsMap = itemsMetadata.reduce(function (prev, itemMetadata) { var foreignKeysList = Object.keys(itemMetadata.foreignKeys || {}); var valuesMap = foreignKeysList.reduce(function (p, k) { var _a, _b; var foreignKeyObject = itemMetadata.foreignKeys[k]; return __assign(__assign({}, p), (_a = {}, _a[foreignKeyObject.collection] = __assign(__assign({}, p[k]), (foreignKeyObject.type === 'oneToMany' ? itemMetadata.val[k] : (_b = {}, _b[itemMetadata.val[k]] = null, _b))), _a)); }, {}); return (0, deepmerge_1.default)(prev, valuesMap); }, {}); var orderedAllFKs = Object.keys(allFKsToFIdsMap); return orderedAllFKs.reduce(function (prev, next) { var _a; return (__assign(__assign({}, prev), (_a = {}, _a[next] = Object.keys(allFKsToFIdsMap[next]), _a))); }, {}); }; Store.prototype.resolveForeignItems = function (itemsMetadata) { var _this = this; return new ts_async_results_1.AsyncResultWrapper(function () { return __awaiter(_this, void 0, void 0, function () { var allForeignKeysByCollection, foreignCollectionsList, foreignKeysWithValuesZip, redisCollectionAndTransactionsGetteriZip, redisTransactions, redisReply, allResults; var _this = this; return __generator(this, function (_a) { switch (_a.label) { case 0: allForeignKeysByCollection = this.compactAllForeignKeys(itemsMetadata); foreignCollectionsList = Object.keys(allForeignKeysByCollection); foreignKeysWithValuesZip = foreignCollectionsList.reduce(function (prev, foreignCollection) { var fids = allForeignKeysByCollection[foreignCollection].map(function (fid) { return (0, util_1.toCollectionId)(foreignCollection, fid); }); return __spreadArray(__spreadArray([], prev, true), [[foreignCollection, fids]], false); }, []); redisCollectionAndTransactionsGetteriZip = foreignKeysWithValuesZip.reduce(function (prev, _a) { var foreignCollection = _a[0], fids = _a[1]; if (fids.length === 0) { return prev; } return __spreadArray(__spreadArray([], prev, true), [ { collection: foreignCollection, getTransaction: function (redis) { return redis.hmget.apply(redis, __spreadArray([_this.toNamespacedCollection(foreignCollection)], fids, false)); }, }, ], false); }, []); // Return Early if no Foreign Keys or no Foreign Values if (redisCollectionAndTransactionsGetteriZip.length === 0) { return [2 /*return*/, new ts_results_1.Ok(itemsMetadata.map(function (md) { return (__assign(__assign({}, md), { foreignItems: {} })); }))]; } redisTransactions = redisCollectionAndTransactionsGetteriZip.reduce(function (prev, _a) { var getTransaction = _a.getTransaction; return getTransaction(prev); }, this.redis.multi()); return [4 /*yield*/, this.redis.execMulti(redisTransactions)]; case 1: redisReply = _a.sent(); allResults = redisReply.map(function (resultArrayPerForeignCollection, collectionIndex) { var results = resultArrayPerForeignCollection.map(function (v) { if (v === null || v === undefined) { return new ts_async_results_1.AsyncErr('CollectionFieldInexistent'); } return new ts_async_results_1.AsyncOk({ collection: redisCollectionAndTransactionsGetteriZip[collectionIndex] .collection, itemMetadata: JSON.parse(v), }); }); return ts_async_results_1.AsyncResult.all.apply(ts_async_results_1.AsyncResult, results); }); return [4 /*yield*/, ts_async_results_1.AsyncResult.all.apply(ts_async_results_1.AsyncResult, allResults).flatMap(function (resultsNestedArrayPerForeignCollection) { return new ts_results_1.Ok((0, flatten_1.default)(resultsNestedArrayPerForeignCollection)); }) .flatMap(function (flattenResults) { return _this.resolveForeignItems(flattenResults.map(function (fr) { return fr.itemMetadata; })).map(function (resolvedResults) { return resolvedResults.map(function (resolvedMetadata, i) { return ({ collection: flattenResults[i].collection, itemMetadata: resolvedMetadata, }); }); }); }) .flatMap(function (flattenResults) { var res = flattenResults.reduce(function (prev, next) { var _a, _b; return __assign(__assign({}, prev), (_a = {}, _a[next.collection] = __assign(__assign({}, prev[next.collection]), (_b = {}, _b[next.itemMetadata.id] = next.itemMetadata, _b)), _a)); }, {}); return new ts_results_1.Ok(res); }) .map(function (foreignItemsMetadataByFCollectionAndFId) { return itemsMetadata.map(function (itemMetadata) { var itemForeignKeys = Object.keys(itemMetadata.foreignKeys || {}); var foreignItems = itemForeignKeys.reduce(function (prev, nextFk) { var _a, _b; var foreignKeyObj = itemMetadata.foreignKeys[nextFk]; if (foreignKeyObj.type === 'oneToMany') { var fids = Object.keys(itemMetadata.val[nextFk]); var foreignItemsByIdInCollection = fids.reduce(function (p, fid) { var _a; return __assign(__assign({}, p), (_a = {}, _a[fid] = foreignItemsMetadataByFCollectionAndFId[foreignKeyObj.collection][fid], _a)); }, {}); return __assign(__assign({}, prev), { oneToMany: __assign(__assign({}, prev.oneToMany), (_a = {}, _a[nextFk] = foreignItemsByIdInCollection, _a)) }); } var fid = itemMetadata.val[nextFk]; return __assign(__assign({}, prev), { oneToOne: __assign(__assign({}, prev.oneToOne), (_b = {}, _b[nextFk] = foreignItemsMetadataByFCollectionAndFId[foreignKeyObj.collection][fid], _b)) }); }, {}); return __assign(__assign({}, itemMetadata), { foreignItems: foreignItems }); }); }) .resolve()]; case 2: return [2 /*return*/, _a.sent()]; } }); }); }); }; Store.prototype.getItemsInCollectionWithMetadata = function (collection, ids) { var _this = this; return this.getShallowItemsInCollectionWithMetadata(collection, ids) .flatMap(function (itemsMetadata) { return _this.resolveForeignItems(itemsMetadata); }) .mapErr(ts_async_results_1.AsyncResult.passThrough(function (error) { _this.logger.error("[Store] getItemsInCollectionWithMetadata", { collection: collection, error: error, }); })); }; Store.prototype.getShallowItemsInCollectionWithMetadata = function (collection, ids) { var _this = this; if (ids.length === 0) { return new ts_async_results_1.AsyncOk([]); } var nameSpacedCollection = this.toNamespacedCollection(collection); return new ts_async_results_1.AsyncResultWrapper(function () { return __awaiter(_this, void 0, void 0, function () { var redisReplies, itemsMetadataResults; var _a; return __generator(this, function (_b) { switch (_b.label) { case 0: return [4 /*yield*/, (_a = this.redis).hmget.apply(_a, __spreadArray([nameSpacedCollection], ids.map(function (id) { return (0, util_1.toCollectionId)(nameSpacedCollection, id); }), false))]; case 1: redisReplies = _b.sent(); itemsMetadataResults = redisReplies.map(function (reply) { if (reply === null || reply === undefined) { return new ts_async_results_1.AsyncErr('CollectionFieldInexistent'); } var metadata = JSON.parse(reply); return new ts_async_results_1.AsyncOk(metadata); }); return [4 /*yield*/, ts_async_results_1.AsyncResult.all.apply(ts_async_results_1.AsyncResult, itemsMetadataResults).resolve()]; case 2: return [2 /*return*/, (_b.sent())]; } }); }); }).mapErr(ts_async_results_1.AsyncResult.passThrough(function (error) { _this.logger.error('[Store] getShallowItemsInCollectionWithMetadata Collection:', { collection: collection, ids: ids, error: error, }); })); }; Store.prototype.getItemInCollection = function (collection, id) { var _this = this; return this.getItemsInCollectionWithMetadata(collection, [id]).map(function (_a) { var m = _a[0]; return _this.metadataReplyToCollectionItem(m); }); }; Store.prototype.getItemInCollectionBy = function (collection, byKey, keyVal) { var _this = this; var nameSpacedCollection = this.toNamespacedCollection(collection); return new ts_async_results_1.AsyncResultWrapper(function () { return __awaiter(_this, void 0, void 0, function () { var referencedId; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.redis.hget((0, util_1.toIndexedCollectionName)(nameSpacedCollection, String(byKey)), String(keyVal))]; case 1: referencedId = _a.sent(); if (referencedId === null) { return [2 /*return*/, new ts_results_1.Err('CollectionFieldInexistent')]; } return [4 /*yield*/, this.getItemInCollection(collection, referencedId).resolve()]; case 2: return [2 /*return*/, (_a.sent())]; } }); }); }); }; Store.prototype.getIndexedItemReference = function (collection, byKey, keyVal) { var _this = this; var nameSpacedCollection = this.toNamespacedCollection(collection); return new ts_async_results_1.AsyncResultWrapper(function () { return __awaiter(_this, void 0, void 0, function () { var referencedId; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.redis.hget((0, util_1.toIndexedCollectionName)(nameSpacedCollection, String(byKey)), String(keyVal))]; case 1: referencedId = _a.sent(); if (referencedId === null) { return [2 /*return*/, ts_results_1.Err.EMPTY]; } return [2 /*return*/, new ts_results_1.Ok(referencedId)]; } }); }); }); }; Store.prototype.getItemsInCollection = function (collection, ids) { var _this = this; return this.getItemsInCollectionWithMetadata(collection, ids).map(function (metadatas) { return metadatas.map(function (m) { return _this.metadataReplyToCollectionItem(m); }); }); }; Store.prototype.metadataReplyToCollectionItem = function (metadata) { var _this = this; return __assign(__assign(__assign(__assign({}, metadata.val), Object.keys(metadata.foreignItems.oneToMany || {}).reduce(function (prev, fk) { var _a; return __assign(__assign({}, prev), (_a = {}, _a[fk] = Object.keys((metadata.foreignItems.oneToMany || {})[fk]).reduce(function (p, fid) { var _a; return (__assign(__assign({}, p), (_a = {}, _a[fid] = _this.metadataReplyToCollectionItem((metadata.foreignItems.oneToMany || {})[fk][fid]), _a))); }, {}), _a)); }, {})), Object.keys(metadata.foreignItems.oneToOne || {}).reduce(function (prev, fk) { var _a; return __assign(__assign({}, prev), (_a = {}, _a[fk] = _this.metadataReplyToCollectionItem((metadata.foreignItems.oneToOne || {})[fk]), _a)); }, {})), { id: metadata.id }); }; Store.prototype.getAllItemsInCollection = function (collection) { var _this = this; var nameSpacedCollection = this.toNamespacedCollection(collection); return new ts_async_results_1.AsyncResultWrapper(function () { return __awaiter(_this, void 0, void 0, function () { var resultHash, itemsMetadata; var _this = this; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.redis.hgetall(nameSpacedCollection)]; case 1: resultHash = _a.sent(); if (!resultHash) { return [2 /*return*/, new ts_results_1.Ok([])]; } itemsMetadata = Object.keys(resultHash) .filter(function (k) { return k[0] !== '_'; }) .map(function (collectionId) { return JSON.parse(resultHash[collectionId]); }); return [4 /*yield*/, this.resolveForeignItems(itemsMetadata) .map(function (allMetadatas) { return allMetadatas.map(function (m) { return _this.metadataReplyToCollectionItem(m); }); }) .resolve()]; case 2: return [2 /*return*/, (_a.sent())]; } }); }); }); }; // getItemInCollectionBy< // K extends CollectionKey, // T extends CollectionMap[K], // F extends OnlyKeysOfType<string | number, UnidentifiableModel<T>> // >(collection: K, byKey: F, keyVal: string | number): AsyncResult<T, StoreErrors> { // return new AsyncResultWrapper(async () => { // const referencedId = await this.redis.hget( // toIndexedCollectionName(collection, String(byKey)), // String(keyVal) // ); // if (referencedId === null) { // return new Err('CollectionOrFieldInexistent'); // } // return (await this.getItemInCollection(collection, referencedId).resolve()) as Result< // T, // StoreErrors // >; // }); // } // getAllItemsInCollectionBy< // K extends CollectionKey, // T extends CollectionMap[K], // F extends OnlyKeysOfType<string | number, UnidentifiableModel<T>> // >( // collection: K, // byKey: F // // keyVal: string | number // ): AsyncResult<T[], StoreErrors> { // return new AsyncResultWrapper(async () => { // console.debug('getAllItemsInCollectionBy started'); // const indexCollection = toIndexedCollectionName(collection, String(byKey)); // const referencedIds = await this.redis.hgetall( // indexCollection // ); // console.debug('indexCollection', indexCollection); // console.debug('referencedIds', referencedIds); // if (referencedIds === null) { // return new Err('CollectionOrFieldInexistent'); // } // return new Ok(referencedIds); // // if (referencedId === null) { // // return new Err('CollectionOrFieldInexistent'); // // } // // return (await this.getItemInCollection(collection, referencedId).resolve()) as Result< // // T, // // StoreErrors // // >; // }); // // return this.getAllItemsInCollection('') // // return this.getItemsInCollectionWithMetadata<K, T>(collection, ids).map((metadatas) => // // metadatas.map((m) => this.metadataReplyToCollectionItem(m)) // // ); // } Store.prototype.isItemInCollection = function (collection, id) { return this.getShallowItemsInCollectionWithMetadata(collection, [ id, ]) .map(function () { return true; }) .flatMapErr(function () { return new ts_results_1.Ok(false); }); }; Store.prototype.isItemInCollectionBy = function (collection, byKey, keyVal) { var _this = this; var nameSpacedCollection = this.toNamespacedCollection(collection); return new ts_async_results_1.AsyncResultWrapper(function () { return __awaiter(_this, void 0, void 0, function () { var referencedId; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.redis.hget((0, util_1.toIndexedCollectionName)(nameSpacedCollection, String(byKey)), String(keyVal))]; case 1: referencedId = _a.sent(); if (referencedId === null) { return [2 /*return*/, ts_results_1.Err.EMPTY]; } return [2 /*return*/, new ts_results_1.Ok(referencedId)]; } }); }); }) .flatMap(function (id) { return _this.isItemInCollection(collection, id); }) .flatMapErr(function () { return new ts_results_1.Ok(false); }); }; Store.prototype.updateItemInCollection = function (collection, id, itemModelGetter, opts) { var _this = this; var nameSpacedCollection = this.toNamespacedCollection(collection); return new ts_async_results_1.AsyncResultWrapper(function () { return __awaiter(_this, void 0, void 0, function () { var unlock; var _this = this; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.lockCollectionItem(collection, id)]; case 1: unlock = _a.sent(); return [2 /*return*/, (this.getShallowItemsInCollectionWithMetadata(collection, [id]) .flatMap(function (_a) { var prev = _a[0]; if (!(0, deep_equal_1.default)(opts.foreignKeys || {}, prev.foreignKeys || {})) { _this.logger.error('[Store] UpdateItemInCollection ForeignKeys Mismatch Error', { forCollection: collection, itemId: id, prevForeignKeys: prev.foreignKeys, nextForeignKeys: opts.foreignKeys, }); return new ts_results_1.Err('CollectionUpdateFailure:MismatchingForeignKeys'); } return new ts_results_1.Ok(prev); }) .flatMap(function (prev) { return new ts_async_results_1.AsyncResultWrapper(function () { return __awaiter(_this, void 0, void 0, function () { var unresolvedItemModel, itemModelAsAsyncResult, itemModelResult, itemModel, _a, removedId, itemModelWithoutId, nextItem, transactions, indexByCollectionWithUpdatedValueRecords, nextItemWithMetadata, payload, field, res; return __generator(this, function (_b) { switch (_b.label) { case 0: unresolvedItemModel = typeof itemModelGetter === 'function' ? itemModelGetter(prev.val) : itemModelGetter; itemModelAsAsyncResult = ts_async_results_1.AsyncResult.isAsyncResult(unresolvedItemModel) ? unresolvedItemModel : new ts_async_results_1.AsyncOk(unresolvedItemModel); return [4 /*yield*/, itemModelAsAsyncResult.resolve()]; case 1: itemModelResult = _b.sent(); if (!itemModelResult.ok) { return [2 /*return*/, new ts_results_1.Err('CollectionUpdateFailure')]; } itemModel = itemModelResult.val; _a = itemModel, removedId = _a.id, itemModelWithoutId = __rest(_a, ["id"]); nextItem = __assign(__assign({}, prev.val), itemModelWithoutId); transactions = this.redis.multi(); indexByCollectionWithUpdatedValueRecords = this.getIndexedInValueRecords(prev, nextItem); if (indexByCollectionWithUpdatedValueRecords.length > 0) { // If the indexBy value changed in this update, update the indexBy Collections as well // by removing the old and adding the new transactions = indexByCollectionWithUpdatedValueRecords.reduce(function (prev, record) { return prev .hset(record.indexedInCollection, [ record.nextValue, id, ]) .hdel(record.indexedInCollection, record.prevValue); }, transactions); } nextItemWithMetadata = __assign(__assign({ val: nextItem, id: prev.id }, (prev.foreignKeys && { foreignKeys: prev.foreignKeys, })), (prev.indexedIn && { indexedIn: indexByCollectionWithUpdatedValueRecords.reduce(function (accum, nextRecord) { var _a; return (__assign(__assign({}, accum), (_a = {}, _a[nextRecord.indexedInCollection] = nextRecord.nextValue, _a))); }, prev.indexedIn), })); payload = JSON.stringify(nextItemWithMetadata); field = (0, util_1.toCollectionId)(nameSpacedCollection, id); transactions = transactions.hset(nameSpacedCollection, [ field, payload, ]); return [4 /*yield*/, this.redis.execMulti(transactions)]; case 2: res = _b.sent(); if (res === null) { return [2 /*return*/, new ts_results_1.Err('CollectionUpdateFailure')]; } return [4 /*yield*/, this.getItemInCollection(collection, id).resolve()]; case 3: // TODO: Add an optimization to only run another query if there are foreign keys // or if the foregin keys have been updated not if there are no modification to that // since this could be pretty expensive // But on the other hand it could also be ok since data will be always fresh! // if (nextItemWithMetadata.foreignKeys && ) {} // Run another query so the all the foreign references work return [2 /*return*/, _b.sent()]; } }); }); }); }) .map(ts_async_results_1.AsyncResult.passThrough(function (nextItem) { _this.logger.info('[Store] Item Updated', { collection: collection, id: nextItem.id, }); })) .resolve() // Finally Unlock the resource .finally(unlock))]; } }); }); }); }; Store.prototype.getIndexedInValueRecords = function (prevItemWithMetadata, nextItem) { var indexedInHash = prevItemWithMetadata.indexedIn || {}; var keysOfIndexedIn = (0, util_1.objectKeys)(indexedInHash); return keysOfIndexedIn.reduce(function (accum, indexedInCollection) { var indexedByField = (0, util_1.getByFieldNameFromIndexedCollection)(indexedInCollection); var prevIndexedValue = indexedInHash[indexedInCollection]; var nextIndexedValue = nextItem[indexedByField]; if (prevIndexedValue === nextIndexedValue) { return accum; } return __spreadArray(__spreadArray([], accum, true), [ { indexedInCollection: indexedInCollection, indexedByField: indexedByField, nextValue: nextIndexedValue, prevValue: prevIndexedValue, }, ], false); }, []); }; Store.prototype.removeCollection = function (collection) { var _this = this; var nameSpacedCollection = this.toNamespacedCollection(collection); return new ts_async_results_1.AsyncResultWrapper(function () { return __awaiter(_this, void 0, void 0, function () { var e_1; return __generator(this, function (_a) { switch (_a.label) { case 0: _a.trys.push([0, 2, , 3]); return [4 /*yield*/, this.redis.del(nameSpacedCollection)]; case 1: _a.sent(); return [2 /*return*/, ts_results_1.Ok.EMPTY]; case 2: e_1 = _a.sent(); return [2 /*return*/, new ts_results_1.Err('CollectionDeletionFailure')]; case 3: return [2 /*return*/]; } }); }); }); }; Store.prototype.removeItemInCollection = function (collection, id) { var _this = this; var nameSpacedCollection = this.toNamespacedCollection(collection); return new ts_async_results_1.AsyncResultWrapper(function () { return __awaiter(_this, void 0, void 0, function () { var itemBeforeRemoval, field, transactions, res, parsedRemovedItem, indexByCollectionWithValuesZip, indexByRemovalTransactions, next; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.getShallowItemsInCollectionWithMetadata(collection, [ id, ]).resolve()]; case 1: itemBeforeRemoval = _a.sent(); if (!itemBeforeRemoval.ok) { return [2 /*return*/, new ts_results_1.Err('CollectionFieldInexistent')]; } field = (0, util_1.toCollectionId)(nameSpacedCollection, id); transactions = this.redis .multi() .hdel(nameSpacedCollection, field) .hget(nameSpacedCollection, '_index') .hlen(nameSpacedCollection); return [4 /*yield*/, this.redis.execMulti(transactions)]; case 2: res = _a.sent(); if (res === null) { return [2 /*return*/, new ts_results_1.Err('CollectionDeletionFailure')]; } parsedRemovedItem = itemBeforeRemoval.val[0]; indexByCollectionWithValuesZip = Object.keys(parsedRemovedItem.indexedIn || {}).reduce(function (prev, nextIndexedInCollection) { var _a; var indexedByField = (_a = parsedRemovedItem.indexedIn) === null || _a === void 0 ? void 0 : _a[nextIndexedInCollection]; if (!indexedByField) { return prev; } return __spreadArray(__spreadArray([], prev, true), [[nextIndexedInCollection, indexedByField]], false); }, []); if (!(indexByCollectionWithValuesZip.length > 0)) return [3 /*break*/, 4]; indexByRemovalTransactions = indexByCollectionWithValuesZip.reduce(function (prev, _a) { var indexedInCollection = _a[0], indexByField = _a[1]; return prev.hdel(indexedInCollection, indexByField); }, this.redis.multi()); return [4 /*yield*/, this.redis.execMulti(indexByRemovalTransactions)]; case 3: _a.sent(); _a.label = 4; case 4: next = { index: Number(res[1]), length: Number(res[2]) - 1, item: undefined, }; return [2 /*return*/, new ts_results_1.Ok(next)]; } }); }); }).map(ts_async_results_1.AsyncResult.passThrough(function (next) { _this.logger.info('[Store] Item Removed', { collection: collection, id: id, length: next.length, }); })); }; Store.prototype.removeItemInCollectionBy = function (collection, byKey, keyVal) { var _this = this; return this.getIndexedItemReference(collection, byKey, keyVal) .flatMap(function (id) { return _this.removeItemInCollection(collection, id); }) .flatMapErr(function () { return new ts_results_1.Err('CollectionFieldInexistent'); }); }; Store.prototype.enqueue = function (q, item) { var _this = this; return new ts_async_results_1.AsyncResultWrapper(function () { return _this.redis .rpush((0, util_1.toQueueName)(q), (0, json_stable_stringify_1.default)(item)) .then(function () { return ts_results_1.Ok.EMPTY; }) .catch(function () { return new ts_results_1.Err('GenericRedisFailure'); }); }); }; Store.prototype.dequeue = function (q) { var _this = this; return new ts_async_results_1.AsyncResultWrapper(function () { return _this.redis .lpop((0, util_1.toQueueName)(q)) .then(function (v) { if (v !== null) { return new ts_results_1.Ok(JSON.parse(v)); } return ts_results_1.Ok.EMPTY; }) .catch(function () { return new ts_results_1.Err('GenericRedisFailure'); }); }); }; Store.prototype.removeFromQueue = function (q, item) { var _this = this; return new ts_async_results_1.AsyncResultWrapper(function () { return _this.redis .lrem((0, util_1.toQueueName)(q), 0, (0, json_stable_stringify_1.default)(item)) .then(function (v) { if (v > 0) { return ts_results_1.Ok.EMPTY; } return new ts_results_1.Err('QueueItemNotFound'); }) .catch(function () { return new ts_results_1.Err('GenericRedisFailure'); }); }); }; Store.prototype.removeFromQueueIfExists = function (q, item) { return this.removeFromQueue(q, item).flatMapErr(function () { return ts_async_results_1.AsyncOk.EMPTY; }); }; Store.prototype.getQueueSize = function (q) { var _this = this; return new ts_async_results_1.AsyncResultWrapper(function () { return _this.redis .llen((0, util_1.toQueueName)(q)) .then(function (v) { return new ts_results_1.Ok(v); }) .catch(function () { return new ts_results_1.Err('GenericRedisFailure'); }); }); }; Store.prototype.flush = function () { var _this = this; return new ts_async_results_1.AsyncResultWrapper(function () { return new Promise(function () { _this.redis.redis.flushall(); // Ensure this return ts_results_1.Ok.EMPTY; }); }); }; return Store; }()); exports.Store = Store; //# sourceMappingURL=Store.js.map