UNPKG

@lastlight/typeorm-cursor-pagination

Version:
187 lines 6.89 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Order = void 0; const typeorm_1 = require("typeorm"); const utils_1 = require("./utils"); var Order; (function (Order) { Order["ASC"] = "ASC"; Order["DESC"] = "DESC"; })(Order = exports.Order || (exports.Order = {})); class CursorPaginator { constructor(entity, paginationKeys) { this.entity = entity; this.paginationKeys = paginationKeys; this.afterCursor = null; this.beforeCursor = null; this.nextAfterCursor = null; this.nextBeforeCursor = null; this.alias = (0, utils_1.pascalToUnderscore)(this.entity.name); this.limit = 100; this.order = Order.DESC; this.paginationOrderForKeys = null; } setAlias(alias) { this.alias = alias; } setAfterCursor(cursor) { this.afterCursor = cursor; } setBeforeCursor(cursor) { this.beforeCursor = cursor; } setLimit(limit) { this.limit = limit; } setOrder(order) { this.order = order; } setFindOptions(options) { this.findOptions = options; } setPaginationKeys(keys) { this.paginationKeys = keys; } setPaginationOrderByKeys(paginationOrder) { this.paginationOrderForKeys = paginationOrder; } paginate(builder) { return __awaiter(this, void 0, void 0, function* () { if (this.findOptions) { builder.setFindOptions(this.findOptions); } const pagingQueryBuilder = this.appendPagingQuery(builder); const entities = yield pagingQueryBuilder.getMany(); const hasMore = entities.length > this.limit; const count = yield builder.getCount(); if (hasMore) { entities.splice(entities.length - 1, 1); } if (entities.length === 0) { return this.toPagingResult(entities, count); } if (!this.hasAfterCursor() && this.hasBeforeCursor()) { entities.reverse(); } if (this.hasBeforeCursor() || hasMore) { this.nextAfterCursor = this.encode(entities[entities.length - 1]); } if (this.hasAfterCursor() || (hasMore && this.hasBeforeCursor())) { this.nextBeforeCursor = this.encode(entities[0]); } return this.toPagingResult(entities, count); }); } getCursor() { return { afterCursor: this.nextAfterCursor, beforeCursor: this.nextBeforeCursor, }; } appendPagingQuery(builder) { const cursors = {}; const clonedBuilder = new typeorm_1.SelectQueryBuilder(builder); if (this.hasAfterCursor()) { Object.assign(cursors, this.decode(this.afterCursor)); } else if (this.hasBeforeCursor()) { Object.assign(cursors, this.decode(this.beforeCursor)); } if (Object.keys(cursors).length > 0) { clonedBuilder.andWhere(new typeorm_1.Brackets((where) => this.buildCursorQuery(where, cursors))); } clonedBuilder.take(this.limit + 1); const paginationKeyOrders = this.buildOrder(); Object.keys(paginationKeyOrders).forEach((orderKey) => { clonedBuilder.addOrderBy(orderKey, paginationKeyOrders[orderKey] === 'ASC' ? 'ASC' : 'DESC'); }); return clonedBuilder; } buildCursorQuery(where, cursors) { let operator = this.getOperator(); const params = {}; let query = ''; this.paginationKeys.forEach((key) => { params[key] = cursors[key]; if (this.paginationOrderForKeys) { operator = this.paginationOrderForKeys[key] === 'ASC' ? '>' : '<'; } if (params[key]) { where.orWhere(`${query}${this.alias}.${key} ${operator} :${key}`, params); query = `${query}${this.alias}.${key} = :${key} AND `; } }); } getOperator() { if (this.hasAfterCursor()) { return this.order === Order.ASC ? '>' : '<'; } if (this.hasBeforeCursor()) { return this.order === Order.ASC ? '<' : '>'; } return '='; } buildOrder() { let { order } = this; if (!this.hasAfterCursor() && this.hasBeforeCursor()) { order = this.flipOrder(order); } const orderByCondition = {}; this.paginationKeys.forEach((key) => { if (this.paginationOrderForKeys) { order = this.paginationOrderForKeys[key]; } orderByCondition[`${this.alias}.${key}`] = order; }); return orderByCondition; } hasAfterCursor() { return this.afterCursor !== null; } hasBeforeCursor() { return this.beforeCursor !== null; } encode(entity) { const payload = this.paginationKeys .map((key) => { const type = this.getEntityPropertyType(key); const value = (0, utils_1.encodeByType)(type, entity[key]); return `${key}:${value}`; }) .join(','); return (0, utils_1.btoa)(payload); } decode(cursor) { const cursors = {}; const columns = (0, utils_1.atob)(cursor).split(','); columns.forEach((column) => { const [key, raw] = column.split(':'); if (raw !== 'null') { const type = this.getEntityPropertyType(key); const value = (0, utils_1.decodeByType)(type, raw); cursors[key] = value; } }); return cursors; } getEntityPropertyType(key) { return Reflect.getMetadata('design:type', this.entity.prototype, key).name.toLowerCase(); } flipOrder(order) { return order === Order.ASC ? Order.DESC : Order.ASC; } toPagingResult(entities, count) { return Object.assign(Object.assign({ items: entities }, this.getCursor()), { count }); } } exports.default = CursorPaginator; //# sourceMappingURL=Paginator.js.map