relational-redis-store
Version:
A Relational Redis Store
839 lines • 48.9 kB
JavaScript
"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