UNPKG

mongo-relay-connection

Version:

Helper for building relay connection from mongoose. Support dynamic collection, but only for single (unique or non-unique) field sorting.

265 lines (204 loc) 9.04 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _last2 = _interopRequireDefault(require("lodash/last")); var _isEmpty = _interopRequireDefault(require("lodash/isEmpty")); var _isNumber = _interopRequireDefault(require("lodash/isNumber")); var _leaf = _interopRequireDefault(require("./leaf")); var _defaultFromCursor = _interopRequireDefault(require("./defaultFromCursor")); var _defaultToCursor = _interopRequireDefault(require("./defaultToCursor")); var _reverse = _interopRequireDefault(require("lodash/reverse")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); } function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } function _iterableToArrayLimit(arr, i) { if (!(Symbol.iterator in Object(arr) || Object.prototype.toString.call(arr) === "[object Arguments]")) { return; } var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; } function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } } function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; } /** * Query and resolve according to the pagination algorithm. * ref: https://facebook.github.io/relay/graphql/connections.htm#sec-Pagination-algorithm * @param {object} args Arguments from parent value. * @param {object} model Mongoose model * @param {object} query Mongo query to get all documents. * @param {string} cursorField Unique field used in sorting and constructing the cursor. * @param {number} direction 1 to sort ascendingly, -1 to sort decendingly. * @param {string} populate Mongoose field(s) to be populated. * note: if both first and last are given, then last is ignored */ function mrResolve(_x, _x2) { return _mrResolve.apply(this, arguments); } function _mrResolve() { _mrResolve = _asyncToGenerator(function* (args, model, query = {}, { cursorField = '_id', direction = 1, toCursor = _defaultToCursor.default, fromCursor = _defaultFromCursor.default, mapNode = x => x, populate = '', totalCount = null } = {}) { if (!(0, _isNumber.default)(direction)) { direction = 1; } const after = args.after, first = args.first, before = args.before; let last = args.last; // if both first and last are given, last is ignored if (first && last) { last = null; } const sort = { [cursorField]: direction }; let idSort = 1; if (cursorField === '_id') { idSort = direction; } let afterQuery = {}; let beforeQuery = {}; if (after) { const _fromCursor = fromCursor(after), field = _fromCursor.field, id = _fromCursor.id; // Let afterEdge be the edge in edges whose cursor is equal to the after argument. // if field is found, if it is unique, then count is 1, otherwise larger than 1. const afterEdgeCount = yield model.countDocuments(_objectSpread({}, query, { [cursorField]: field })); // Remove all elements of edges before and including afterEdge. if (direction === 1) { afterQuery[cursorField] = { $gt: field }; } else { afterQuery[cursorField] = { $lt: field }; } // non unique case, need to fetch back the tie-ing docs too if (afterEdgeCount > 1) { const tie = { // ...query, [cursorField]: field, _id: { $gt: id } }; afterQuery = { $or: [tie, afterQuery] }; } } if (before) { const _fromCursor2 = fromCursor(before), field = _fromCursor2.field, id = _fromCursor2.id; // Let beforeEdge be the edge in edges whose cursor is equal to the before argument. const beforeEdgeCount = yield model.countDocuments(_objectSpread({}, query, { [cursorField]: field })); // Remove all elements of edges after and including beforeEdge. if (direction === 1) { beforeQuery[cursorField] = { $lt: field }; } else { beforeQuery[cursorField] = { $gt: field }; } if (beforeEdgeCount > 1) { const tie = { // ...query, [cursorField]: field, _id: { $lt: id } }; beforeQuery = { $or: [tie, beforeQuery] }; } } // in case cursorField is not unique const multiSort = [[cursorField, sort[cursorField]], ['_id', idSort]]; if (last) { multiSort[0][1] = direction * -1; multiSort[1][1] = -1; } const joinQuery = [query, afterQuery, beforeQuery].filter(x => !(0, _isEmpty.default)(x)); let finalQuery = {}; if (joinQuery.length > 1) { finalQuery = { $and: joinQuery }; } else if (joinQuery.length === 1) { finalQuery = joinQuery[0]; } if (first && first < 0) { throw new Error(`first(${first}) could not be negative`); } if (last && last < 0) { throw new Error(`last(${last}) could not be negative`); } const limit = first || last; // special optimization: // if limit (from first or last) is specified, // then edgesCount could be limited by a bigger number than limit, // instead of counting all docs, to infer if more pages is available. let cntLimit; if (limit) { cntLimit = limit + 1; } const pNodes = model.find(finalQuery).limit(cntLimit).sort(multiSort).populate(populate); // if totalCount is given, skip the count operation let pTotalCnt = totalCount; if (pTotalCnt === null) { pTotalCnt = model.find(query).countDocuments(); } const _ref = yield Promise.all([pNodes, pTotalCnt]), _ref2 = _slicedToArray(_ref, 2), nodes = _ref2[0], tc = _ref2[1]; const edgesCount = nodes.length; let edges = nodes.map(node => { return { node: mapNode(node), cursor: toCursor((0, _leaf.default)(node, cursorField), node.id) }; }); if (limit && edgesCount > limit) { edges.pop(); } if (last) { edges = (0, _reverse.default)(edges); } let hasPreviousPage = false; if (last && edgesCount > last) { hasPreviousPage = true; } let hasNextPage = false; if (first && edgesCount > first) { hasNextPage = true; } let startCursor = ''; let endCursor = ''; if (edges.length > 0) { startCursor = edges[0].cursor; endCursor = (0, _last2.default)(edges).cursor; } const pageInfo = { hasNextPage, hasPreviousPage, startCursor, endCursor }; return { pageInfo, edges, totalCount: tc }; }); return _mrResolve.apply(this, arguments); } var _default = mrResolve; exports.default = _default;