@entria/graphql-mongoose-loader
Version:
GraphQL Mongoose Loader helpers
141 lines (140 loc) • 5.49 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.getPageInfo = exports.calculateOffsets = exports.getTotalCount = exports.offsetToCursor = exports.getOffsetWithDefault = exports.cursorToOffset = exports.unbase64 = exports.base64 = exports.PREFIX = void 0;
exports.PREFIX = 'mongo:';
const base64 = (str) => Buffer.from(str, 'ascii').toString('base64');
exports.base64 = base64;
const unbase64 = (b64) => Buffer.from(b64, 'base64').toString('ascii');
exports.unbase64 = unbase64;
/**
* Rederives the offset from the cursor string
*/
const cursorToOffset = (cursor) => parseInt((0, exports.unbase64)(cursor).substring(exports.PREFIX.length), 10);
exports.cursorToOffset = cursorToOffset;
/**
* Given an optional cursor and a default offset, returns the offset to use;
* if the cursor contains a valid offset, that will be used, otherwise it will
* be the default.
*/
const getOffsetWithDefault = (cursor, defaultOffset) => {
if (cursor === undefined || cursor === null) {
return defaultOffset;
}
const offset = (0, exports.cursorToOffset)(cursor);
return isNaN(offset) ? defaultOffset : offset;
};
exports.getOffsetWithDefault = getOffsetWithDefault;
/**
* Creates the cursor string from an offset.
*/
const offsetToCursor = (offset) => (0, exports.base64)(exports.PREFIX + offset);
exports.offsetToCursor = offsetToCursor;
const getTotalCount = async ({ cursor, useEstimatedCount = false, lean = true }) => {
// @ts-ignore
const clonedCursor = lean ? cursor.model.find().lean().merge(cursor) : cursor.model.find().merge(cursor);
return useEstimatedCount ? clonedCursor.estimatedDocumentCount() : clonedCursor.countDocuments();
};
exports.getTotalCount = getTotalCount;
const calculateOffsets = ({ args, totalCount }) => {
const { after, before } = args;
let { first, last } = args;
// Limit the maximum number of elements in a query
if (!first && !last)
first = 10;
if (first && first > 1000)
first = 1000;
if (last && last > 1000)
last = 1000;
const beforeOffset = (0, exports.getOffsetWithDefault)(before || null, totalCount);
const afterOffset = (0, exports.getOffsetWithDefault)(after || null, -1);
let startOffset = Math.max(-1, afterOffset) + 1;
let endOffset = Math.min(totalCount, beforeOffset);
if (first !== undefined && first !== null) {
endOffset = Math.min(endOffset, startOffset + first);
}
if (last !== undefined && last !== null) {
startOffset = Math.max(startOffset, endOffset - (last || 0));
}
const skip = Math.max(startOffset, 0);
const safeLimit = Math.max(endOffset - startOffset, 1);
const limitOffset = Math.max(endOffset - startOffset, 0);
return {
first: first || null,
last: last || null,
before: before || null,
after: after || null,
skip,
limit: safeLimit,
beforeOffset,
afterOffset,
startOffset,
endOffset,
startCursorOffset: skip,
endCursorOffset: limitOffset + skip,
};
};
exports.calculateOffsets = calculateOffsets;
function getPageInfo({ edges,
// before,
// after,
// first,
// last,
// afterOffset,
// beforeOffset,
// startOffset,
// endOffset,
totalCount, startCursorOffset, endCursorOffset, }) {
const firstEdge = edges[0];
const lastEdge = edges[edges.length - 1];
// const lowerBound = after ? afterOffset + 1 : 0;
// const upperBound = before ? Math.min(beforeOffset, totalCount) : totalCount;
return {
startCursor: firstEdge ? firstEdge.cursor : null,
endCursor: lastEdge ? lastEdge.cursor : null,
hasPreviousPage: startCursorOffset > 0,
hasNextPage: endCursorOffset < totalCount,
// hasPreviousPage: last !== null ? startOffset > lowerBound : false,
// hasNextPage: first !== null ? endOffset < upperBound : false,
};
}
exports.getPageInfo = getPageInfo;
async function connectionFromMongoCursor({ cursor, context, args = {}, loader, raw = false, useEstimatedCount = false, lean = true, }) {
// @ts-ignore
const clonedCursor = lean ? cursor.model.find().lean().merge(cursor) : cursor.model.find().merge(cursor);
const totalCount = await (0, exports.getTotalCount)({
cursor: clonedCursor,
useEstimatedCount,
lean,
});
const { first, last, before, after, skip, limit, beforeOffset, afterOffset, startOffset, endOffset, startCursorOffset, endCursorOffset, } = (0, exports.calculateOffsets)({ args, totalCount });
// If supplied slice is too large, trim it down before mapping over it.
clonedCursor.skip(skip);
clonedCursor.limit(limit);
// avoid large objects retrieval from collection
const slice = await clonedCursor.select(raw ? {} : { _id: 1 }).exec();
const edges = slice.map((value, index) => ({
cursor: (0, exports.offsetToCursor)(startOffset + index),
node: loader(context, raw ? value : value._id),
}));
return {
edges,
count: totalCount,
endCursorOffset,
startCursorOffset,
pageInfo: getPageInfo({
edges,
before,
after,
first,
last,
afterOffset,
beforeOffset,
startOffset,
endOffset,
totalCount,
startCursorOffset,
endCursorOffset,
}),
};
}
exports.default = connectionFromMongoCursor;