@nodeboot/starter-persistence
Version:
Nodeboot starter package for persistence. Supports data access layer auto-configuration providing features like database initialization, consistency check, entity mapping, repository pattern, transactions, paging, migrations, persistence listeners, persis
132 lines • 5.68 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.MongoPagingAndSortingRepository = void 0;
const typeorm_1 = require("typeorm");
const core_1 = require("@nodeboot/core");
const mongodb_1 = require("mongodb");
/**
* A generic MongoDB repository that provides both offset-based and cursor-based pagination.
* This can be extended by application repositories to support pagination out of the box.
*
* @template Entity - The database entity type (e.g., User, Product, Post)
*
* @example Offset-based pagination
* ```ts
* class UserRepository extends MongoPagingAndSortingRepository<User> {}
*
* const page = await userRepository.findPaginated({}, { page: 1, pageSize: 20 });
* ```
*
* @example Cursor-based pagination
* ```ts
* class UserRepository extends MongoPagingAndSortingRepository<User> {}
*
* const cursorPage = await userRepository.findCursorPaginated({}, { pageSize: 10, lastId: "..." });
* ```
*
* @author Manuel Santos <https://github.com/manusant>
*/
class MongoPagingAndSortingRepository extends typeorm_1.MongoRepository {
/**
* Finds a single entity by its ID.
*
* @param id - The ID of the entity to find. Can be a string, number, ObjectId, or Uint8Array.
* @returns A promise that resolves to the found entity or null if not found.
*/
async findById(id) {
return this.findOneBy({ _id: new mongodb_1.ObjectId(id) });
}
/**
* Offset-based pagination (Traditional Pagination)
*
* This method retrieves paginated results using `LIMIT` and `SKIP`.
* It is useful when you need **random access to pages** (e.g., "Jump to page 3").
*
* However, for large datasets, this method can be **inefficient** because SKIP requires scanning records before fetching.
*
* @param filter - MongoDB-like filter object `{ field: value }`
* @param options - Pagination options containing:
* - page - The page number (starting from 1, default: 1)
* - pageSize - Number of items per page (default: 10)
* - sortField - The field used for sorting (default: `_id`)
* - sortOrder - The sort direction (`ASC` for ascending, `DESC` for descending, default: `DESC`)
*
* @returns An object containing:
* - `page`: Current page number
* - `pageSize`: Number of records per page
* - `totalItems`: Total number of records
* - `totalPages`: Total pages based on total items
* - `items`: Array of paginated records
*/
async findPaginated(filter = {}, options) {
const { page = 1, pageSize = 10, sortField = "_id", sortOrder = core_1.SortOrder.DESC } = options;
// Ensure valid page and pageSize
const validPage = Math.max(1, page);
const validPageSize = Math.max(1, pageSize);
// Calculate number of records to skip
const skip = (validPage - 1) * validPageSize;
// Fetch paginated data and count total items
const [items, totalItems] = await Promise.all([
this.find({
where: filter,
order: { [sortField]: sortOrder === core_1.SortOrder.ASC ? 1 : -1 },
skip,
take: validPageSize,
}),
this.count(filter),
]);
return {
page: validPage,
pageSize: validPageSize,
totalItems,
items,
totalPages: Math.ceil(totalItems / pageSize),
};
}
/**
* Cursor-based pagination (Efficient Pagination)
*
* Instead of using SKIP, this method uses a **cursor** (`_id` or another indexed field) to fetch the next page.
* This is **more efficient for large datasets** because it avoids skipping records.
*
* This method is ideal for **infinite scrolling** or **continuous data loading** (e.g., social media feeds).
*
* @param filter - MongoDB-like filter object `{ field: value }`
* @param options - Pagination options containing:
* - pageSize - Number of items per page (default: 10)
* - lastId - The `_id` of the last item from the previous page (optional)
* - sortField - The field used for sorting (default: `_id`)
* - sortOrder - The sort direction (`ASC` for ascending, `DESC` for descending, default: `ASC`)
*
* @returns An object containing:
* - `pageSize`: Number of records per page
* - `lastId`: The `_id` of the last record from the current page (used as a cursor for the next page)
* - `items`: Array of paginated records
*/
async findCursorPaginated(filter = {}, options) {
const { pageSize = 10, lastId, sortField = "_id", sortOrder = core_1.SortOrder.ASC } = options;
const query = { ...filter };
// Add cursor condition (_id > lastId for forward pagination)
if (lastId) {
query._id =
sortOrder === core_1.SortOrder.ASC
? { $gt: lastId } // Next page (ascending)
: { $lt: lastId }; // Previous page (descending)
}
// Fetch paginated data
const items = await this.find({
where: query,
order: { [sortField]: sortOrder === core_1.SortOrder.ASC ? 1 : -1 },
take: pageSize,
});
// Get the last _id from the current page
const newLastId = items.length > 0 ? items[items.length - 1]?.["_id"].toString() : null;
return {
pageSize,
items,
lastId: newLastId,
};
}
}
exports.MongoPagingAndSortingRepository = MongoPagingAndSortingRepository;
//# sourceMappingURL=MongoPagingAndSortingRepository.js.map