UNPKG

@irrelon/forerunnerdb-core

Version:

ForerunnerDB core utilities for operating on JSON data.

571 lines (557 loc) 26.3 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports["default"] = void 0; var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator")); var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator")); var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck")); var _assertThisInitialized2 = _interopRequireDefault(require("@babel/runtime/helpers/assertThisInitialized")); var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits")); var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime/helpers/possibleConstructorReturn")); var _getPrototypeOf2 = _interopRequireDefault(require("@babel/runtime/helpers/getPrototypeOf")); var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _path = require("@irrelon/path"); var _CoreClass2 = _interopRequireDefault(require("./CoreClass")); var _objectId = _interopRequireDefault(require("../utils/objectId")); var _IndexHashMap2 = _interopRequireDefault(require("../indexes/IndexHashMap")); var _OperationResult = _interopRequireDefault(require("../operations/OperationResult")); var _OperationFailure = _interopRequireDefault(require("../operations/OperationFailure")); var _OperationSuccess = _interopRequireDefault(require("../operations/OperationSuccess")); var _find = _interopRequireDefault(require("./operation/find")); var _update = _interopRequireDefault(require("./operation/update")); var _remove = _interopRequireDefault(require("./operation/remove")); var _pull = require("../utils/pull"); function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2["default"])(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } function _createSuper(t) { var r = _isNativeReflectConstruct(); return function () { var e, o = (0, _getPrototypeOf2["default"])(t); if (r) { var s = (0, _getPrototypeOf2["default"])(this).constructor; e = Reflect.construct(o, arguments, s); } else e = o.apply(this, arguments); return (0, _possibleConstructorReturn2["default"])(this, e); }; } function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); } /** * @typedef {Object} InsertOptions * @property {Boolean} [$atomic=false] If true, any insert failure will roll back all * documents in the `data` argument. * @property {Boolean} [$ordered=false] If true, inserts will stop at any failure but * previously inserted documents will still remain inserted. * @property {Boolean} [$one=false] If true, inserts will stop after the first document. */ /** * @typedef {Object} InsertResult * @property {Object} operation Describes the operation being carried out. * @property {Boolean} operation.isArray If true, the insert data is an array of * documents. * @property {Boolean} operation.isAtomic True if the operation is atomic. * @property {Boolean} operation.isOrdered True if the operation is ordered. * @property {Object|Array} operation.data The data passed to the operation. * @property {Number} nInserted The number of documents inserted. * @property {Number} nFailed The number of documents that failed to insert. * @property {Array<Object>} inserted Array of documents inserted. * @property {Array<Object>} notInserted Array of documents that failed to insert. * @property {Array<OperationFailure>} failures Array of failed operation results. */ var Collection = /*#__PURE__*/function (_CoreClass) { (0, _inherits2["default"])(Collection, _CoreClass); var _super = _createSuper(Collection); function Collection(name) { var _this; (0, _classCallCheck2["default"])(this, Collection); _this = _super.call(this); (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "_receivers", []); (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "ensurePrimaryKey", function (doc) { if ((0, _path.get)(doc, this._primaryKey) === undefined) { // Assign a primary key automatically return (0, _path.setImmutable)(doc, this._primaryKey, (0, _objectId["default"])()); } return doc; }); (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "_indexInsert", function (doc) { // Return true if we DIDN'T find an error return !_this._index.find(function (indexObj) { // Return true (found an error) if the result was false return indexObj.index.insert(doc) === false; }); }); (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "indexViolationCheck", function (doc) { var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; var indexArray = _this._index; if (options.indexArray) { indexArray = options.indexArray; } // Loop each index and ask it to check if this // document violates any index constraints for (var indexNum = 0; indexNum < indexArray.length; indexNum++) { var indexObj = indexArray[indexNum]; // Check if the index has a unique flag, if not it cannot violate // so early exit if (!indexObj.index.isUnique()) continue; var hash = indexObj.index.hash(doc); var wouldBeViolated = indexObj.index.willViolateByHash(hash); if (wouldBeViolated) { return new _OperationFailure["default"]({ "type": "INDEX_VIOLATION_CHECK_FAILURE", "meta": { "stage": "preFlight", "indexName": indexObj.name, hash: hash }, "data": doc }); } } return new _OperationSuccess["default"]({ "type": "INDEX_VIOLATION_CHECK_SUCCESS", "data": doc }); }); (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "operation", function (docOrArr, func) { var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; var opResult = new _OperationResult["default"](); var isArray = Array.isArray(docOrArr); var data = docOrArr; if (!isArray) { data = [docOrArr]; } for (var currentIndex = 0; currentIndex < data.length; currentIndex++) { var doc = data[currentIndex]; var result = func(doc, options); if (!result) { continue; } result.atIndex = currentIndex; opResult.addResult(result); if (options.breakOnFailure && result instanceof _OperationFailure["default"]) { // The result was a failure, break now break; } } return opResult; }); (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "pushData", function (doc) { _this._indexInsert(doc); _this._data.push(doc); }); (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "_insertUnordered", /*#__PURE__*/function () { var _ref = (0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator["default"].mark(function _callee(data) { var insertResult, promiseArr, promiseResultArr; return _regenerator["default"].wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: insertResult = { "inserted": [], "notInserted": [], "failures": [] }; // Loop the array of data and fire off an insert operation for each // document, collating the result of each insert into an insert result promiseArr = []; data.forEach(function (doc) { return promiseArr.push(_this._insertDocument(doc)); }); _context.next = 5; return Promise.all(promiseArr); case 5: promiseResultArr = _context.sent; promiseResultArr.forEach(function (result) { if (result instanceof _OperationFailure["default"]) { insertResult.failures.push(result); insertResult.notInserted.push(result.data); return; } insertResult.inserted.push(result.data); }); return _context.abrupt("return", insertResult); case 8: case "end": return _context.stop(); } } }, _callee); })); return function (_x) { return _ref.apply(this, arguments); }; }()); (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "_insertOrdered", /*#__PURE__*/function () { var _ref2 = (0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator["default"].mark(function _callee2(data) { var insertResult, dataIndex, insertDocumentResult; return _regenerator["default"].wrap(function _callee2$(_context2) { while (1) { switch (_context2.prev = _context2.next) { case 0: insertResult = { "inserted": [], "notInserted": [], "failures": [] }; // Loop the array of data and fire off an insert operation for each // document, collating the result of each insert into an insert result dataIndex = 0; case 2: if (!(dataIndex < data.length)) { _context2.next = 14; break; } _context2.next = 5; return _this._insertDocument(data[dataIndex]); case 5: insertDocumentResult = _context2.sent; if (!(insertDocumentResult instanceof _OperationFailure["default"])) { _context2.next = 10; break; } // This doc failed to insert, return operation result now insertResult.notInserted.push(insertDocumentResult.data); insertResult.failures.push(insertDocumentResult); return _context2.abrupt("return", insertResult); case 10: insertResult.inserted.push(insertDocumentResult.data); case 11: dataIndex++; _context2.next = 2; break; case 14: return _context2.abrupt("return", insertResult); case 15: case "end": return _context2.stop(); } } }, _callee2); })); return function (_x2) { return _ref2.apply(this, arguments); }; }()); (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "_insertDocument", /*#__PURE__*/function () { var _ref3 = (0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator["default"].mark(function _callee3(doc) { var newDoc, indexViolationResult; return _regenerator["default"].wrap(function _callee3$(_context3) { while (1) { switch (_context3.prev = _context3.next) { case 0: // 1. Ensure primary key newDoc = _this.ensurePrimaryKey(doc); // 2. Check for index violation indexViolationResult = _this.indexViolationCheck(newDoc); if (!(indexViolationResult instanceof _OperationFailure["default"])) { _context3.next = 4; break; } return _context3.abrupt("return", indexViolationResult); case 4: // 3. Insert into internal data array _this._data.push(newDoc); // 4. Insert into indexes _this._indexInsert(newDoc); // 5. Return a successful operation return _context3.abrupt("return", new _OperationSuccess["default"]({ data: newDoc })); case 7: case "end": return _context3.stop(); } } }, _callee3); })); return function (_x3) { return _ref3.apply(this, arguments); }; }()); (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "insert", /*#__PURE__*/function () { var _ref4 = (0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator["default"].mark(function _callee4(data) { var options, isArray, isAtomic, isOrdered, insertResult, insertOperationResult, _args4 = arguments; return _regenerator["default"].wrap(function _callee4$(_context4) { while (1) { switch (_context4.prev = _context4.next) { case 0: options = _args4.length > 1 && _args4[1] !== undefined ? _args4[1] : { "$atomic": false, "$ordered": false }; isArray = Array.isArray(data); isAtomic = options.$atomic === true; isOrdered = options.$ordered === true; // Make sure the data is an array if (!isArray) { data = [data]; } insertResult = { "operation": { isArray: isArray, isAtomic: isAtomic, isOrdered: isOrdered, data: data }, "nInserted": 0, "nFailed": 0, "inserted": [], "notInserted": [], "failures": [] }; if (!isOrdered) { _context4.next = 12; break; } _context4.next = 9; return _this._insertOrdered(data); case 9: insertOperationResult = _context4.sent; _context4.next = 21; break; case 12: if (!isAtomic) { _context4.next = 18; break; } _context4.next = 15; return _this._insertUnordered(data); case 15: insertOperationResult = _context4.sent; _context4.next = 21; break; case 18: _context4.next = 20; return _this._insertUnordered(data); case 20: insertOperationResult = _context4.sent; case 21: if (!(_this._cap && _this._data.length > _this._cap)) { _context4.next = 24; break; } _context4.next = 24; return _this.removeById((0, _path.get)(_this._data[0], _this._primaryKey)); case 24: _this.emit("insert", { insertResult: insertResult, insertOperationResult: insertOperationResult, data: data }); // 5 Return result return _context4.abrupt("return", _objectSpread(_objectSpread(_objectSpread({}, insertResult), insertOperationResult), {}, { nInserted: insertOperationResult.inserted.length, nFailed: insertOperationResult.notInserted.length })); case 26: case "end": return _context4.stop(); } } }, _callee4); })); return function (_x4) { return _ref4.apply(this, arguments); }; }()); (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "insertOne", /*#__PURE__*/function () { var _ref5 = (0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator["default"].mark(function _callee5(data) { var options, _args5 = arguments; return _regenerator["default"].wrap(function _callee5$(_context5) { while (1) { switch (_context5.prev = _context5.next) { case 0: options = _args5.length > 1 && _args5[1] !== undefined ? _args5[1] : { "$atomic": false, "$ordered": false }; return _context5.abrupt("return", _this.insert(data, _objectSpread(_objectSpread({}, options), {}, { "$one": true }))); case 2: case "end": return _context5.stop(); } } }, _callee5); })); return function (_x5) { return _ref5.apply(this, arguments); }; }()); (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "insertMany", /*#__PURE__*/function () { var _ref6 = (0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator["default"].mark(function _callee6(data) { var options, _args6 = arguments; return _regenerator["default"].wrap(function _callee6$(_context6) { while (1) { switch (_context6.prev = _context6.next) { case 0: options = _args6.length > 1 && _args6[1] !== undefined ? _args6[1] : { "$atomic": false, "$ordered": false }; return _context6.abrupt("return", _this.insert(data, options)); case 2: case "end": return _context6.stop(); } } }, _callee6); })); return function (_x6) { return _ref6.apply(this, arguments); }; }()); (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "find", function () { var queryObj = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; return (0, _find["default"])(_this._data, queryObj); }); (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "findOne", function () { var queryObj = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; return _this.find(queryObj, options)[0]; }); (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "findMany", function () { var queryObj = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; return _this.find(queryObj, options); }); (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "update", function (queryObj, updateObj) { var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; // TODO: Add option to run a sanity check on each match before and update // is performed so we can check if an index violation would occur var resultArr = (0, _update["default"])(_this._data, queryObj, updateObj, options); _this.emit("update", { resultArr: resultArr, queryObj: queryObj, updateObj: updateObj, options: options }); // TODO: Now loop the result array and check if any fields that are in the // update object match fields that are in the index. If they are, remove each // document from the index and re-index them return resultArr; }); (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "updateOne", function (queryObj, update) { var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; return _this.update(queryObj, update, _objectSpread(_objectSpread({}, options), {}, { "$one": true })); }); (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "updateMany", function (queryObj, update) { var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; return _this.update(queryObj, update, options); }); (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "remove", function () { var queryObj = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; var resultArr = (0, _remove["default"])(_this._data, queryObj, options); _this.emit("remove", { resultArr: resultArr, queryObj: queryObj, options: options }); // TODO: Now loop the result array and check if any fields that are in the // remove array match objects that are in the index. If they are, remove each // document from the index return resultArr; }); (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "removeOne", function (queryObj) { var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; return _this.remove(queryObj, _objectSpread(_objectSpread({}, options), {}, { "$one": true })); }); (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "removeMany", function (queryObj) { var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; return _this.remove(queryObj, options); }); (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "removeById", function (id) { return _this.removeOne((0, _defineProperty2["default"])({}, _this._primaryKey, id)); }); (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "pipe", function (receiver) { _this._receivers.push(receiver); _this.on("insert", function (args) {}); }); (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "unPipe", function (receiver) { (0, _pull.pull)(_this._receivers, receiver); }); (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "virtual", /*#__PURE__*/function () { var _ref7 = (0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator["default"].mark(function _callee7(path) { var newCollection, initialData; return _regenerator["default"].wrap(function _callee7$(_context7) { while (1) { switch (_context7.prev = _context7.next) { case 0: newCollection = new Collection(); // Set initial data // TODO: Make path selection with .$. work _context7.next = 3; return _this.find(path); case 3: initialData = _context7.sent; _context7.next = 6; return newCollection.insert(initialData); case 6: _this.pipe(newCollection); return _context7.abrupt("return", newCollection); case 8: case "end": return _context7.stop(); } } }, _callee7); })); return function (_x7) { return _ref7.apply(this, arguments); }; }()); _this._name = name; _this._cap = 0; _this._primaryKey = "_id"; _this._data = []; _this._index = [{ "name": "primaryKey", "index": new _IndexHashMap2["default"]((0, _defineProperty2["default"])({}, _this._primaryKey, 1), { "unique": true }) }]; return _this; } /** * Checks for a primary key on the document and assigns one if none * currently exists. * @param {Object} doc The document to check a primary key against. * @returns {Object} The document passed in `obj`. * @private */ /** * Inserts a document into the indexes currently defined in the collection. * @param {Object} doc The document to insert. * @returns {boolean} True if successful, false if unsuccessful. * @private */ /** * Scans the collection indexes and checks that the passed doc does * not violate any index constraints. * @param {Object} doc The document to check. * @param {Object} [options] An options object. * @returns {OperationSuccess|OperationFailure} An operation result. */ /** * Run a single operation on a single or multiple data items. * @param {object|Array<object>} docOrArr An array of data items or * a single data item object. * @param {function} func The operation to run on each data item. * @param {object} [options={}] Optional options object. * @returns {OperationResult} The result of the operation(s). */ /** * Insert a document or array of documents into the collection. * @param {Object|Array} data The document or array of documents to insert. * @param {InsertOptions} [options={$atomic: false, $ordered: false}] Options object. * @returns {Promise<InsertResult>} The result of the insert operation. */ /** * * @param path * @returns {Promise<Collection>} */ return Collection; }(_CoreClass2["default"]); var _default = exports["default"] = Collection;