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
JavaScript
;
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;