@classytic/mongokit 2.1.0 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +35 -4
  2. package/dist/actions/index.d.ts +2 -2
  3. package/dist/actions/index.js +0 -2
  4. package/dist/{index-CgOJ2pqz.d.ts → index-CKy3H2SY.d.ts} +1 -1
  5. package/dist/index.d.ts +10 -6
  6. package/dist/index.js +183 -6
  7. package/dist/{memory-cache-DG2oSSbx.d.ts → memory-cache-tn3v1xgG.d.ts} +1 -1
  8. package/dist/pagination/PaginationEngine.d.ts +1 -1
  9. package/dist/pagination/PaginationEngine.js +0 -2
  10. package/dist/plugins/index.d.ts +37 -2
  11. package/dist/plugins/index.js +173 -3
  12. package/dist/{types-Nxhmi1aI.d.cts → types-vDtcOhyx.d.ts} +19 -1
  13. package/dist/utils/index.d.ts +2 -2
  14. package/dist/utils/index.js +0 -2
  15. package/package.json +6 -12
  16. package/dist/actions/index.cjs +0 -479
  17. package/dist/actions/index.cjs.map +0 -1
  18. package/dist/actions/index.d.cts +0 -3
  19. package/dist/actions/index.js.map +0 -1
  20. package/dist/index-BfVJZF-3.d.cts +0 -337
  21. package/dist/index.cjs +0 -2142
  22. package/dist/index.cjs.map +0 -1
  23. package/dist/index.d.cts +0 -239
  24. package/dist/index.js.map +0 -1
  25. package/dist/memory-cache-DqfFfKes.d.cts +0 -142
  26. package/dist/pagination/PaginationEngine.cjs +0 -375
  27. package/dist/pagination/PaginationEngine.cjs.map +0 -1
  28. package/dist/pagination/PaginationEngine.d.cts +0 -117
  29. package/dist/pagination/PaginationEngine.js.map +0 -1
  30. package/dist/plugins/index.cjs +0 -874
  31. package/dist/plugins/index.cjs.map +0 -1
  32. package/dist/plugins/index.d.cts +0 -275
  33. package/dist/plugins/index.js.map +0 -1
  34. package/dist/types-Nxhmi1aI.d.ts +0 -510
  35. package/dist/utils/index.cjs +0 -667
  36. package/dist/utils/index.cjs.map +0 -1
  37. package/dist/utils/index.d.cts +0 -189
  38. package/dist/utils/index.js.map +0 -1
@@ -1,117 +0,0 @@
1
- import { Model } from 'mongoose';
2
- import { A as AnyDocument, P as PaginationConfig, O as OffsetPaginationOptions, a as OffsetPaginationResult, K as KeysetPaginationOptions, b as KeysetPaginationResult, c as AggregatePaginationOptions, d as AggregatePaginationResult } from '../types-Nxhmi1aI.cjs';
3
-
4
- /**
5
- * Pagination Engine
6
- *
7
- * Production-grade pagination for MongoDB with support for:
8
- * - Offset pagination (page-based) - Best for small datasets, random page access
9
- * - Keyset pagination (cursor-based) - Best for large datasets, infinite scroll
10
- * - Aggregate pagination - Best for complex queries requiring aggregation
11
- *
12
- * @example
13
- * ```typescript
14
- * const engine = new PaginationEngine(UserModel, {
15
- * defaultLimit: 20,
16
- * maxLimit: 100,
17
- * useEstimatedCount: true
18
- * });
19
- *
20
- * // Offset pagination
21
- * const page1 = await engine.paginate({ page: 1, limit: 20 });
22
- *
23
- * // Keyset pagination (better for large datasets)
24
- * const stream1 = await engine.stream({ sort: { createdAt: -1 }, limit: 20 });
25
- * const stream2 = await engine.stream({ sort: { createdAt: -1 }, after: stream1.next });
26
- * ```
27
- */
28
-
29
- /**
30
- * Internal pagination config with required values
31
- */
32
- interface ResolvedPaginationConfig {
33
- defaultLimit: number;
34
- maxLimit: number;
35
- maxPage: number;
36
- deepPageThreshold: number;
37
- cursorVersion: number;
38
- useEstimatedCount: boolean;
39
- }
40
- /**
41
- * Production-grade pagination engine for MongoDB
42
- * Supports offset, keyset (cursor), and aggregate pagination
43
- */
44
- declare class PaginationEngine<TDoc = AnyDocument> {
45
- readonly Model: Model<TDoc>;
46
- readonly config: ResolvedPaginationConfig;
47
- /**
48
- * Create a new pagination engine
49
- *
50
- * @param Model - Mongoose model to paginate
51
- * @param config - Pagination configuration
52
- */
53
- constructor(Model: Model<TDoc>, config?: PaginationConfig);
54
- /**
55
- * Offset-based pagination using skip/limit
56
- * Best for small datasets and when users need random page access
57
- * O(n) performance - slower for deep pages
58
- *
59
- * @param options - Pagination options
60
- * @returns Pagination result with total count
61
- *
62
- * @example
63
- * const result = await engine.paginate({
64
- * filters: { status: 'active' },
65
- * sort: { createdAt: -1 },
66
- * page: 1,
67
- * limit: 20
68
- * });
69
- * console.log(result.docs, result.total, result.hasNext);
70
- */
71
- paginate(options?: OffsetPaginationOptions): Promise<OffsetPaginationResult<TDoc>>;
72
- /**
73
- * Keyset (cursor-based) pagination for high-performance streaming
74
- * Best for large datasets, infinite scroll, real-time feeds
75
- * O(1) performance - consistent speed regardless of position
76
- *
77
- * @param options - Pagination options (sort is required)
78
- * @returns Pagination result with next cursor
79
- *
80
- * @example
81
- * // First page
82
- * const page1 = await engine.stream({
83
- * sort: { createdAt: -1 },
84
- * limit: 20
85
- * });
86
- *
87
- * // Next page using cursor
88
- * const page2 = await engine.stream({
89
- * sort: { createdAt: -1 },
90
- * after: page1.next,
91
- * limit: 20
92
- * });
93
- */
94
- stream(options: KeysetPaginationOptions): Promise<KeysetPaginationResult<TDoc>>;
95
- /**
96
- * Aggregate pipeline with pagination
97
- * Best for complex queries requiring aggregation stages
98
- * Uses $facet to combine results and count in single query
99
- *
100
- * @param options - Aggregation options
101
- * @returns Pagination result with total count
102
- *
103
- * @example
104
- * const result = await engine.aggregatePaginate({
105
- * pipeline: [
106
- * { $match: { status: 'active' } },
107
- * { $group: { _id: '$category', count: { $sum: 1 } } },
108
- * { $sort: { count: -1 } }
109
- * ],
110
- * page: 1,
111
- * limit: 20
112
- * });
113
- */
114
- aggregatePaginate(options?: AggregatePaginationOptions): Promise<AggregatePaginationResult<TDoc>>;
115
- }
116
-
117
- export { PaginationEngine };
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../src/pagination/utils/cursor.ts","../../src/pagination/utils/sort.ts","../../src/pagination/utils/filter.ts","../../src/pagination/utils/limits.ts","../../src/utils/error.ts","../../src/pagination/PaginationEngine.ts"],"names":[],"mappings":"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","file":"PaginationEngine.js","sourcesContent":["/**\r\n * Cursor Utilities\r\n * \r\n * Encoding and decoding of cursor tokens for keyset pagination.\r\n * Cursors are base64-encoded JSON containing position data and metadata.\r\n */\r\n\r\nimport mongoose from 'mongoose';\r\nimport type { SortSpec, DecodedCursor, ObjectId, CursorPayload, ValueType } from '../../types.js';\r\n\r\n/**\r\n * Encodes document values and sort metadata into a base64 cursor token\r\n *\r\n * @param doc - Document to extract cursor values from\r\n * @param primaryField - Primary sort field name\r\n * @param sort - Normalized sort specification\r\n * @param version - Cursor version for forward compatibility\r\n * @returns Base64-encoded cursor token\r\n */\r\nexport function encodeCursor(\r\n doc: Record<string, unknown>,\r\n primaryField: string,\r\n sort: SortSpec,\r\n version: number = 1\r\n): string {\r\n const primaryValue = doc[primaryField];\r\n const idValue = doc._id;\r\n\r\n const payload: CursorPayload = {\r\n v: serializeValue(primaryValue),\r\n t: getValueType(primaryValue),\r\n id: serializeValue(idValue) as string,\r\n idType: getValueType(idValue),\r\n sort,\r\n ver: version,\r\n };\r\n\r\n return Buffer.from(JSON.stringify(payload)).toString('base64');\r\n}\r\n\r\n/**\r\n * Decodes a cursor token back into document values and sort metadata\r\n *\r\n * @param token - Base64-encoded cursor token\r\n * @returns Decoded cursor data\r\n * @throws Error if token is invalid or malformed\r\n */\r\nexport function decodeCursor(token: string): DecodedCursor {\r\n try {\r\n const json = Buffer.from(token, 'base64').toString('utf-8');\r\n const payload = JSON.parse(json) as CursorPayload;\r\n\r\n return {\r\n value: rehydrateValue(payload.v, payload.t),\r\n id: rehydrateValue(payload.id, payload.idType) as ObjectId | string,\r\n sort: payload.sort,\r\n version: payload.ver,\r\n };\r\n } catch {\r\n throw new Error('Invalid cursor token');\r\n }\r\n}\r\n\r\n/**\r\n * Validates that cursor sort matches current query sort\r\n *\r\n * @param cursorSort - Sort specification from cursor\r\n * @param currentSort - Sort specification from query\r\n * @throws Error if sorts don't match\r\n */\r\nexport function validateCursorSort(cursorSort: SortSpec, currentSort: SortSpec): void {\r\n const cursorSortStr = JSON.stringify(cursorSort);\r\n const currentSortStr = JSON.stringify(currentSort);\r\n\r\n if (cursorSortStr !== currentSortStr) {\r\n throw new Error('Cursor sort does not match current query sort');\r\n }\r\n}\r\n\r\n/**\r\n * Validates cursor version matches expected version\r\n *\r\n * @param cursorVersion - Version from cursor\r\n * @param expectedVersion - Expected version from config\r\n * @throws Error if versions don't match\r\n */\r\nexport function validateCursorVersion(cursorVersion: number, expectedVersion: number): void {\r\n if (cursorVersion !== expectedVersion) {\r\n throw new Error(`Cursor version ${cursorVersion} does not match expected version ${expectedVersion}`);\r\n }\r\n}\r\n\r\n/**\r\n * Serializes a value for cursor storage\r\n */\r\nfunction serializeValue(value: unknown): string | number | boolean {\r\n if (value instanceof Date) return value.toISOString();\r\n if (value instanceof mongoose.Types.ObjectId) return value.toString();\r\n return value as string | number | boolean;\r\n}\r\n\r\n/**\r\n * Gets the type identifier for a value\r\n */\r\nfunction getValueType(value: unknown): ValueType {\r\n if (value instanceof Date) return 'date';\r\n if (value instanceof mongoose.Types.ObjectId) return 'objectid';\r\n if (typeof value === 'boolean') return 'boolean';\r\n if (typeof value === 'number') return 'number';\r\n if (typeof value === 'string') return 'string';\r\n return 'unknown';\r\n}\r\n\r\n/**\r\n * Rehydrates a serialized value back to its original type\r\n */\r\nfunction rehydrateValue(serialized: unknown, type: ValueType): unknown {\r\n switch (type) {\r\n case 'date':\r\n return new Date(serialized as string);\r\n case 'objectid':\r\n return new mongoose.Types.ObjectId(serialized as string);\r\n case 'boolean':\r\n return Boolean(serialized);\r\n case 'number':\r\n return Number(serialized);\r\n default:\r\n return serialized;\r\n }\r\n}\r\n","/**\r\n * Sort Utilities\r\n * \r\n * Normalization and validation of sort specifications for pagination.\r\n */\r\n\r\nimport type { SortSpec, SortDirection } from '../../types.js';\r\n\r\n/**\r\n * Normalizes sort object to ensure stable key order\r\n * Primary fields first, _id last (not alphabetical)\r\n *\r\n * @param sort - Sort specification\r\n * @returns Normalized sort with stable key order\r\n */\r\nexport function normalizeSort(sort: SortSpec): SortSpec {\r\n const normalized: SortSpec = {};\r\n\r\n Object.keys(sort).forEach(key => {\r\n if (key !== '_id') normalized[key] = sort[key];\r\n });\r\n\r\n if (sort._id !== undefined) {\r\n normalized._id = sort._id;\r\n }\r\n\r\n return normalized;\r\n}\r\n\r\n/**\r\n * Validates and normalizes sort for keyset pagination\r\n * Auto-adds _id tie-breaker if needed\r\n * Ensures _id direction matches primary field\r\n *\r\n * @param sort - Sort specification\r\n * @returns Validated and normalized sort\r\n * @throws Error if sort is invalid for keyset pagination\r\n */\r\nexport function validateKeysetSort(sort: SortSpec): SortSpec {\r\n const keys = Object.keys(sort);\r\n\r\n if (keys.length === 1 && keys[0] !== '_id') {\r\n const field = keys[0];\r\n const direction = sort[field];\r\n return normalizeSort({ [field]: direction, _id: direction });\r\n }\r\n\r\n if (keys.length === 1 && keys[0] === '_id') {\r\n return normalizeSort(sort);\r\n }\r\n\r\n if (keys.length === 2) {\r\n if (!keys.includes('_id')) {\r\n throw new Error('Keyset pagination requires _id as tie-breaker');\r\n }\r\n\r\n const primaryField = keys.find(k => k !== '_id')!;\r\n const primaryDirection = sort[primaryField];\r\n const idDirection = sort._id;\r\n\r\n if (primaryDirection !== idDirection) {\r\n throw new Error('_id direction must match primary field direction');\r\n }\r\n\r\n return normalizeSort(sort);\r\n }\r\n\r\n throw new Error('Keyset pagination only supports single field + _id');\r\n}\r\n\r\n/**\r\n * Inverts sort directions (1 becomes -1, -1 becomes 1)\r\n *\r\n * @param sort - Sort specification\r\n * @returns Inverted sort\r\n */\r\nexport function invertSort(sort: SortSpec): SortSpec {\r\n const inverted: SortSpec = {};\r\n\r\n Object.keys(sort).forEach(key => {\r\n inverted[key] = (sort[key] === 1 ? -1 : 1) as SortDirection;\r\n });\r\n\r\n return inverted;\r\n}\r\n\r\n/**\r\n * Extracts primary sort field (first non-_id field)\r\n *\r\n * @param sort - Sort specification\r\n * @returns Primary field name\r\n */\r\nexport function getPrimaryField(sort: SortSpec): string {\r\n const keys = Object.keys(sort);\r\n return keys.find(k => k !== '_id') || '_id';\r\n}\r\n\r\n/**\r\n * Gets sort direction for a specific field\r\n *\r\n * @param sort - Sort specification\r\n * @param field - Field name\r\n * @returns Sort direction\r\n */\r\nexport function getDirection(sort: SortSpec, field: string): SortDirection | undefined {\r\n return sort[field];\r\n}\r\n","/**\r\n * Filter Utilities\r\n * \r\n * Build MongoDB filters for keyset pagination with proper cursor positioning.\r\n */\r\n\r\nimport type { SortSpec, FilterQuery, AnyDocument, ObjectId } from '../../types.js';\r\n\r\n/**\r\n * Builds MongoDB filter for keyset pagination\r\n * Creates compound $or condition for proper cursor-based filtering\r\n *\r\n * @param baseFilters - Existing query filters\r\n * @param sort - Normalized sort specification\r\n * @param cursorValue - Primary field value from cursor\r\n * @param cursorId - _id value from cursor\r\n * @returns MongoDB filter with keyset condition\r\n *\r\n * @example\r\n * buildKeysetFilter(\r\n * { status: 'active' },\r\n * { createdAt: -1, _id: -1 },\r\n * new Date('2024-01-01'),\r\n * new ObjectId('...')\r\n * )\r\n * // Returns:\r\n * // {\r\n * // status: 'active',\r\n * // $or: [\r\n * // { createdAt: { $lt: Date('2024-01-01') } },\r\n * // { createdAt: Date('2024-01-01'), _id: { $lt: ObjectId('...') } }\r\n * // ]\r\n * // }\r\n */\r\nexport function buildKeysetFilter(\r\n baseFilters: FilterQuery<AnyDocument>,\r\n sort: SortSpec,\r\n cursorValue: unknown,\r\n cursorId: ObjectId | string\r\n): FilterQuery<AnyDocument> {\r\n const primaryField = Object.keys(sort).find(k => k !== '_id') || '_id';\r\n const direction = sort[primaryField];\r\n const operator = direction === 1 ? '$gt' : '$lt';\r\n\r\n return {\r\n ...baseFilters,\r\n $or: [\r\n { [primaryField]: { [operator]: cursorValue } },\r\n {\r\n [primaryField]: cursorValue,\r\n _id: { [operator]: cursorId },\r\n },\r\n ],\r\n } as FilterQuery<AnyDocument>;\r\n}\r\n","/**\r\n * Limit Utilities\r\n * \r\n * Validation and calculation helpers for pagination limits and pages.\r\n */\r\n\r\nimport type { PaginationConfig } from '../../types.js';\r\n\r\n/**\r\n * Validates and sanitizes limit value\r\n * Parses strings to numbers and prevents NaN bugs\r\n *\r\n * @param limit - Requested limit\r\n * @param config - Pagination configuration\r\n * @returns Sanitized limit between 1 and maxLimit\r\n */\r\nexport function validateLimit(limit: number | string, config: PaginationConfig): number {\r\n const parsed = Number(limit);\r\n\r\n if (!Number.isFinite(parsed) || parsed < 1) {\r\n return config.defaultLimit || 10;\r\n }\r\n\r\n return Math.min(Math.floor(parsed), config.maxLimit || 100);\r\n}\r\n\r\n/**\r\n * Validates and sanitizes page number\r\n * Parses strings to numbers and prevents NaN bugs\r\n *\r\n * @param page - Requested page (1-indexed)\r\n * @param config - Pagination configuration\r\n * @returns Sanitized page number >= 1\r\n * @throws Error if page exceeds maxPage\r\n */\r\nexport function validatePage(page: number | string, config: PaginationConfig): number {\r\n const parsed = Number(page);\r\n\r\n if (!Number.isFinite(parsed) || parsed < 1) {\r\n return 1;\r\n }\r\n\r\n const sanitized = Math.floor(parsed);\r\n\r\n if (sanitized > (config.maxPage || 10000)) {\r\n throw new Error(`Page ${sanitized} exceeds maximum ${config.maxPage || 10000}`);\r\n }\r\n\r\n return sanitized;\r\n}\r\n\r\n/**\r\n * Checks if page number should trigger deep pagination warning\r\n *\r\n * @param page - Current page number\r\n * @param threshold - Warning threshold\r\n * @returns True if warning should be shown\r\n */\r\nexport function shouldWarnDeepPagination(page: number, threshold: number): boolean {\r\n return page > threshold;\r\n}\r\n\r\n/**\r\n * Calculates number of documents to skip for offset pagination\r\n *\r\n * @param page - Page number (1-indexed)\r\n * @param limit - Documents per page\r\n * @returns Number of documents to skip\r\n */\r\nexport function calculateSkip(page: number, limit: number): number {\r\n return (page - 1) * limit;\r\n}\r\n\r\n/**\r\n * Calculates total number of pages\r\n *\r\n * @param total - Total document count\r\n * @param limit - Documents per page\r\n * @returns Total number of pages\r\n */\r\nexport function calculateTotalPages(total: number, limit: number): number {\r\n return Math.ceil(total / limit);\r\n}\r\n","/**\r\n * Error Utilities\r\n * \r\n * HTTP-compatible error creation for repository operations\r\n */\r\n\r\nimport type { HttpError } from '../types.js';\r\n\r\n/**\r\n * Creates an error with HTTP status code\r\n *\r\n * @param status - HTTP status code\r\n * @param message - Error message\r\n * @returns Error with status property\r\n * \r\n * @example\r\n * throw createError(404, 'Document not found');\r\n * throw createError(400, 'Invalid input');\r\n * throw createError(403, 'Access denied');\r\n */\r\nexport function createError(status: number, message: string): HttpError {\r\n const error = new Error(message) as HttpError;\r\n error.status = status;\r\n return error;\r\n}\r\n","/**\r\n * Pagination Engine\r\n * \r\n * Production-grade pagination for MongoDB with support for:\r\n * - Offset pagination (page-based) - Best for small datasets, random page access\r\n * - Keyset pagination (cursor-based) - Best for large datasets, infinite scroll\r\n * - Aggregate pagination - Best for complex queries requiring aggregation\r\n * \r\n * @example\r\n * ```typescript\r\n * const engine = new PaginationEngine(UserModel, {\r\n * defaultLimit: 20,\r\n * maxLimit: 100,\r\n * useEstimatedCount: true\r\n * });\r\n *\r\n * // Offset pagination\r\n * const page1 = await engine.paginate({ page: 1, limit: 20 });\r\n *\r\n * // Keyset pagination (better for large datasets)\r\n * const stream1 = await engine.stream({ sort: { createdAt: -1 }, limit: 20 });\r\n * const stream2 = await engine.stream({ sort: { createdAt: -1 }, after: stream1.next });\r\n * ```\r\n */\r\n\r\nimport type { Model } from 'mongoose';\r\nimport { encodeCursor, decodeCursor, validateCursorSort, validateCursorVersion } from './utils/cursor.js';\r\nimport { validateKeysetSort, getPrimaryField } from './utils/sort.js';\r\nimport { buildKeysetFilter } from './utils/filter.js';\r\nimport {\r\n validateLimit,\r\n validatePage,\r\n shouldWarnDeepPagination,\r\n calculateSkip,\r\n calculateTotalPages,\r\n} from './utils/limits.js';\r\nimport { createError } from '../utils/error.js';\r\nimport type {\r\n PaginationConfig,\r\n OffsetPaginationOptions,\r\n KeysetPaginationOptions,\r\n AggregatePaginationOptions,\r\n OffsetPaginationResult,\r\n KeysetPaginationResult,\r\n AggregatePaginationResult,\r\n AnyDocument,\r\n} from '../types.js';\r\n\r\n/**\r\n * Internal pagination config with required values\r\n */\r\ninterface ResolvedPaginationConfig {\r\n defaultLimit: number;\r\n maxLimit: number;\r\n maxPage: number;\r\n deepPageThreshold: number;\r\n cursorVersion: number;\r\n useEstimatedCount: boolean;\r\n}\r\n\r\n/**\r\n * Production-grade pagination engine for MongoDB\r\n * Supports offset, keyset (cursor), and aggregate pagination\r\n */\r\nexport class PaginationEngine<TDoc = AnyDocument> {\r\n public readonly Model: Model<TDoc>;\r\n public readonly config: ResolvedPaginationConfig;\r\n\r\n /**\r\n * Create a new pagination engine\r\n *\r\n * @param Model - Mongoose model to paginate\r\n * @param config - Pagination configuration\r\n */\r\n constructor(Model: Model<TDoc>, config: PaginationConfig = {}) {\r\n this.Model = Model;\r\n this.config = {\r\n defaultLimit: config.defaultLimit || 10,\r\n maxLimit: config.maxLimit || 100,\r\n maxPage: config.maxPage || 10000,\r\n deepPageThreshold: config.deepPageThreshold || 100,\r\n cursorVersion: config.cursorVersion || 1,\r\n useEstimatedCount: config.useEstimatedCount || false,\r\n };\r\n }\r\n\r\n /**\r\n * Offset-based pagination using skip/limit\r\n * Best for small datasets and when users need random page access\r\n * O(n) performance - slower for deep pages\r\n *\r\n * @param options - Pagination options\r\n * @returns Pagination result with total count\r\n *\r\n * @example\r\n * const result = await engine.paginate({\r\n * filters: { status: 'active' },\r\n * sort: { createdAt: -1 },\r\n * page: 1,\r\n * limit: 20\r\n * });\r\n * console.log(result.docs, result.total, result.hasNext);\r\n */\r\n async paginate(options: OffsetPaginationOptions = {}): Promise<OffsetPaginationResult<TDoc>> {\r\n const {\r\n filters = {},\r\n sort = { _id: -1 },\r\n page = 1,\r\n limit = this.config.defaultLimit,\r\n select,\r\n populate = [],\r\n lean = true,\r\n session,\r\n } = options;\r\n\r\n const sanitizedPage = validatePage(page, this.config);\r\n const sanitizedLimit = validateLimit(limit, this.config);\r\n const skip = calculateSkip(sanitizedPage, sanitizedLimit);\r\n\r\n let query = this.Model.find(filters as Record<string, unknown>);\r\n if (select) query = query.select(select);\r\n if (populate && (Array.isArray(populate) ? populate.length : populate)) {\r\n query = query.populate(populate as string | string[]);\r\n }\r\n query = query.sort(sort).skip(skip).limit(sanitizedLimit).lean(lean);\r\n if (session) query = query.session(session);\r\n\r\n const hasFilters = Object.keys(filters).length > 0;\r\n const useEstimated = this.config.useEstimatedCount && !hasFilters;\r\n\r\n // Note: estimatedDocumentCount() doesn't support sessions or filters\r\n // It reads collection metadata (O(1) instant), not actual documents\r\n // Falls back to countDocuments() when filters are present\r\n const [docs, total] = await Promise.all([\r\n query.exec(),\r\n useEstimated\r\n ? this.Model.estimatedDocumentCount()\r\n : this.Model.countDocuments(filters as Record<string, unknown>).session(session ?? null),\r\n ]);\r\n\r\n const totalPages = calculateTotalPages(total, sanitizedLimit);\r\n const warning = shouldWarnDeepPagination(sanitizedPage, this.config.deepPageThreshold)\r\n ? `Deep pagination (page ${sanitizedPage}). Consider getAll({ after, sort, limit }) for better performance.`\r\n : undefined;\r\n\r\n return {\r\n method: 'offset',\r\n docs: docs as TDoc[],\r\n page: sanitizedPage,\r\n limit: sanitizedLimit,\r\n total,\r\n pages: totalPages,\r\n hasNext: sanitizedPage < totalPages,\r\n hasPrev: sanitizedPage > 1,\r\n ...(warning && { warning }),\r\n };\r\n }\r\n\r\n /**\r\n * Keyset (cursor-based) pagination for high-performance streaming\r\n * Best for large datasets, infinite scroll, real-time feeds\r\n * O(1) performance - consistent speed regardless of position\r\n *\r\n * @param options - Pagination options (sort is required)\r\n * @returns Pagination result with next cursor\r\n *\r\n * @example\r\n * // First page\r\n * const page1 = await engine.stream({\r\n * sort: { createdAt: -1 },\r\n * limit: 20\r\n * });\r\n *\r\n * // Next page using cursor\r\n * const page2 = await engine.stream({\r\n * sort: { createdAt: -1 },\r\n * after: page1.next,\r\n * limit: 20\r\n * });\r\n */\r\n async stream(options: KeysetPaginationOptions): Promise<KeysetPaginationResult<TDoc>> {\r\n const {\r\n filters = {},\r\n sort,\r\n after,\r\n limit = this.config.defaultLimit,\r\n select,\r\n populate = [],\r\n lean = true,\r\n session,\r\n } = options;\r\n\r\n if (!sort) {\r\n throw createError(400, 'sort is required for keyset pagination');\r\n }\r\n\r\n const sanitizedLimit = validateLimit(limit, this.config);\r\n const normalizedSort = validateKeysetSort(sort);\r\n\r\n let query: Record<string, unknown> = { ...filters };\r\n\r\n if (after) {\r\n const cursor = decodeCursor(after);\r\n validateCursorVersion(cursor.version, this.config.cursorVersion);\r\n validateCursorSort(cursor.sort, normalizedSort);\r\n query = buildKeysetFilter(query, normalizedSort, cursor.value, cursor.id);\r\n }\r\n\r\n let mongoQuery = this.Model.find(query);\r\n if (select) mongoQuery = mongoQuery.select(select);\r\n if (populate && (Array.isArray(populate) ? populate.length : populate)) {\r\n mongoQuery = mongoQuery.populate(populate as string | string[]);\r\n }\r\n mongoQuery = mongoQuery.sort(normalizedSort).limit(sanitizedLimit + 1).lean(lean);\r\n if (session) mongoQuery = mongoQuery.session(session);\r\n\r\n const docs = await mongoQuery.exec() as (TDoc & Record<string, unknown>)[];\r\n\r\n const hasMore = docs.length > sanitizedLimit;\r\n if (hasMore) docs.pop();\r\n\r\n const primaryField = getPrimaryField(normalizedSort);\r\n const nextCursor = hasMore && docs.length > 0\r\n ? encodeCursor(docs[docs.length - 1], primaryField, normalizedSort, this.config.cursorVersion)\r\n : null;\r\n\r\n return {\r\n method: 'keyset',\r\n docs,\r\n limit: sanitizedLimit,\r\n hasMore,\r\n next: nextCursor,\r\n };\r\n }\r\n\r\n /**\r\n * Aggregate pipeline with pagination\r\n * Best for complex queries requiring aggregation stages\r\n * Uses $facet to combine results and count in single query\r\n *\r\n * @param options - Aggregation options\r\n * @returns Pagination result with total count\r\n *\r\n * @example\r\n * const result = await engine.aggregatePaginate({\r\n * pipeline: [\r\n * { $match: { status: 'active' } },\r\n * { $group: { _id: '$category', count: { $sum: 1 } } },\r\n * { $sort: { count: -1 } }\r\n * ],\r\n * page: 1,\r\n * limit: 20\r\n * });\r\n */\r\n async aggregatePaginate(options: AggregatePaginationOptions = {}): Promise<AggregatePaginationResult<TDoc>> {\r\n const {\r\n pipeline = [],\r\n page = 1,\r\n limit = this.config.defaultLimit,\r\n session,\r\n } = options;\r\n\r\n const sanitizedPage = validatePage(page, this.config);\r\n const sanitizedLimit = validateLimit(limit, this.config);\r\n const skip = calculateSkip(sanitizedPage, sanitizedLimit);\r\n\r\n const facetPipeline = [\r\n ...pipeline,\r\n {\r\n $facet: {\r\n docs: [{ $skip: skip }, { $limit: sanitizedLimit }],\r\n total: [{ $count: 'count' }],\r\n },\r\n },\r\n ];\r\n\r\n const aggregation = this.Model.aggregate(facetPipeline);\r\n if (session) aggregation.session(session);\r\n\r\n const [result] = await aggregation.exec() as [{ docs: TDoc[]; total: { count: number }[] }];\r\n const docs = result.docs;\r\n const total = result.total[0]?.count || 0;\r\n const totalPages = calculateTotalPages(total, sanitizedLimit);\r\n\r\n const warning = shouldWarnDeepPagination(sanitizedPage, this.config.deepPageThreshold)\r\n ? `Deep pagination in aggregate (page ${sanitizedPage}). Uses $skip internally.`\r\n : undefined;\r\n\r\n return {\r\n method: 'aggregate',\r\n docs,\r\n page: sanitizedPage,\r\n limit: sanitizedLimit,\r\n total,\r\n pages: totalPages,\r\n hasNext: sanitizedPage < totalPages,\r\n hasPrev: sanitizedPage > 1,\r\n ...(warning && { warning }),\r\n };\r\n }\r\n}\r\n"]}