UNPKG

@entria/graphql-mongoose-loader

Version:
141 lines (140 loc) 5.49 kB
"use strict"; 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;