|
1 | 1 | import objectPath from 'object-path';
|
2 | 2 | import bsonUrlEncoding from './bsonUrlEncoding';
|
3 | 3 |
|
| 4 | +export type PaginationToken = { _id: string; [key: string]: any } | string | [any, unknown]; |
| 5 | + |
4 | 6 | export type PaginationParams = {
|
5 | 7 | paginatedField?: string;
|
6 | 8 | sortCaseInsensitive?: boolean;
|
7 | 9 | sortAscending?: boolean;
|
8 |
| - previous?: string | [unknown, unknown]; |
9 |
| - next?: string | [unknown, unknown]; |
| 10 | + previous?: PaginationToken; |
| 11 | + next?: PaginationToken; |
10 | 12 | limit?: number;
|
11 |
| - after?: string | [unknown, unknown]; |
| 13 | + after?: PaginationToken; |
12 | 14 | hint?: string;
|
13 | 15 | before?: string;
|
14 | 16 | };
|
15 | 17 |
|
16 | 18 | export type PaginationResponse<T> = {
|
17 | 19 | results: T[];
|
18 |
| - previous: string | null; |
| 20 | + previous: PaginationToken; // Updated to reflect a more specific type |
19 | 21 | hasPrevious: boolean;
|
20 |
| - next: string | null; |
| 22 | + next: PaginationToken; // Updated to reflect a more specific type |
21 | 23 | hasNext: boolean;
|
22 | 24 | };
|
23 | 25 |
|
24 |
| -type SortObject = Record<string, 1 | -1>; |
25 |
| - |
26 |
| -type CursorQuery = Record<string, any>; |
27 |
| - |
28 | 26 | /**
|
29 | 27 | * Helper function to encode pagination tokens.
|
30 | 28 | *
|
31 | 29 | * NOTE: this function modifies the passed-in `response` argument directly.
|
32 | 30 | *
|
33 |
| - * @param params - Pagination parameters |
34 |
| - * @param response - The response object to modify |
| 31 | + * @param {Object} params |
| 32 | + * @param {String} paginatedField |
| 33 | + * @param {boolean} sortCaseInsensitive |
| 34 | + * |
| 35 | + * @param {Object} response The response |
| 36 | + * @param {String?} previous |
| 37 | + * @param {String?} next |
| 38 | + * |
| 39 | + * @returns void |
35 | 40 | */
|
36 |
| -export function encodePaginationTokens<T>( |
37 |
| - params: PaginationParams, |
38 |
| - response: PaginationResponse<T>, |
39 |
| - previous: T | null, |
40 |
| - next: T | null |
41 |
| -): void { |
| 41 | +function encodePaginationTokens(params: PaginationParams, response: PaginationResponse<any>): void { |
42 | 42 | const shouldSecondarySortOnId = params.paginatedField !== '_id';
|
43 | 43 |
|
44 |
| - if (previous) { |
45 |
| - let previousPaginatedField = objectPath.get(previous, params.paginatedField); |
| 44 | + if (response.previous) { |
| 45 | + let previousPaginatedField = objectPath.get(response.previous, params.paginatedField); |
46 | 46 | if (params.sortCaseInsensitive) {
|
47 | 47 | previousPaginatedField = previousPaginatedField?.toLowerCase?.() ?? '';
|
48 | 48 | }
|
49 |
| - response.previous = shouldSecondarySortOnId |
50 |
| - ? bsonUrlEncoding.encode([previousPaginatedField, (previous as any)._id]) |
51 |
| - : bsonUrlEncoding.encode(previousPaginatedField); |
| 49 | + if (shouldSecondarySortOnId) { |
| 50 | + if ( |
| 51 | + typeof response.previous === 'object' && |
| 52 | + response.previous !== null && |
| 53 | + '_id' in response.previous |
| 54 | + ) { |
| 55 | + response.previous = bsonUrlEncoding.encode([previousPaginatedField, response.previous._id]); |
| 56 | + } |
| 57 | + } else { |
| 58 | + response.previous = bsonUrlEncoding.encode(previousPaginatedField); |
| 59 | + } |
52 | 60 | }
|
53 |
| - |
54 |
| - if (next) { |
55 |
| - let nextPaginatedField = objectPath.get(next, params.paginatedField); |
| 61 | + if (response.next) { |
| 62 | + let nextPaginatedField = objectPath.get(response.next, params.paginatedField); |
56 | 63 | if (params.sortCaseInsensitive) {
|
57 | 64 | nextPaginatedField = nextPaginatedField?.toLowerCase?.() ?? '';
|
58 | 65 | }
|
59 |
| - response.next = shouldSecondarySortOnId |
60 |
| - ? bsonUrlEncoding.encode([nextPaginatedField, (next as any)._id]) |
61 |
| - : bsonUrlEncoding.encode(nextPaginatedField); |
| 66 | + if (shouldSecondarySortOnId) { |
| 67 | + if (typeof response.next === 'object' && response.next !== null && '_id' in response.next) { |
| 68 | + response.next = bsonUrlEncoding.encode([nextPaginatedField, response.next._id]); |
| 69 | + } |
| 70 | + } else { |
| 71 | + response.next = bsonUrlEncoding.encode(nextPaginatedField); |
| 72 | + } |
62 | 73 | }
|
63 | 74 | }
|
64 | 75 |
|
65 | 76 | /**
|
66 | 77 | * Parses the raw results from a find or aggregate query and generates a response object that
|
67 |
| - * contains various pagination properties. |
| 78 | + * contain the various pagination properties |
| 79 | + * |
| 80 | + * @param {Object[]} results the results from a query |
| 81 | + * @param {Object} params The params originally passed to `find` or `aggregate` |
68 | 82 | *
|
69 |
| - * @param results - The results from a query |
70 |
| - * @param params - The parameters originally passed to `find` or `aggregate` |
71 |
| - * @returns The object containing pagination properties |
| 83 | + * @return {Object} The object containing pagination properties |
72 | 84 | */
|
73 |
| -export function prepareResponse<T>(results: T[], params: PaginationParams): PaginationResponse<T> { |
| 85 | +function prepareResponse(results: any[], params: any): any { |
74 | 86 | const hasMore = results.length > params.limit;
|
75 |
| - |
| 87 | + // Remove the extra element that we added to 'peek' to see if there were more entries. |
76 | 88 | if (hasMore) results.pop();
|
77 | 89 |
|
78 | 90 | const hasPrevious = !!params.next || !!(params.previous && hasMore);
|
79 | 91 | const hasNext = !!params.previous || hasMore;
|
80 | 92 |
|
| 93 | + // If we sorted reverse to get the previous page, correct the sort order. |
81 | 94 | if (params.previous) results = results.reverse();
|
82 | 95 |
|
83 |
| - const response: PaginationResponse<T> = { |
| 96 | + const response = { |
84 | 97 | results,
|
| 98 | + previous: results[0], |
85 | 99 | hasPrevious,
|
| 100 | + next: results[results.length - 1], |
86 | 101 | hasNext,
|
87 |
| - previous: null, |
88 |
| - next: null, |
89 | 102 | };
|
90 | 103 |
|
91 |
| - const previous = results[0] || null; |
92 |
| - const next = results[results.length - 1] || null; |
93 |
| - |
94 |
| - encodePaginationTokens(params, response, previous, next); |
| 104 | + encodePaginationTokens(params, response); |
95 | 105 |
|
96 | 106 | return response;
|
97 | 107 | }
|
98 | 108 |
|
99 | 109 | /**
|
100 |
| - * Generates a `$sort` object given the parameters. |
| 110 | + * Generates a `$sort` object given the parameters |
| 111 | + * |
| 112 | + * @param {Object} params The params originally passed to `find` or `aggregate` |
101 | 113 | *
|
102 |
| - * @param params - The parameters originally passed to `find` or `aggregate` |
103 |
| - * @returns A sort object |
| 114 | + * @return {Object} a sort object |
104 | 115 | */
|
105 |
| -export function generateSort(params: PaginationParams): SortObject { |
| 116 | +function generateSort(params: any): any { |
106 | 117 | const sortAsc =
|
107 | 118 | (!params.sortAscending && params.previous) || (params.sortAscending && !params.previous);
|
108 | 119 | const sortDir = sortAsc ? 1 : -1;
|
109 | 120 |
|
110 |
| - if (params.paginatedField === '_id') { |
111 |
| - return { _id: sortDir }; |
| 121 | + if (params.paginatedField == '_id') { |
| 122 | + return { |
| 123 | + _id: sortDir, |
| 124 | + }; |
112 | 125 | } else {
|
113 | 126 | const field = params.sortCaseInsensitive ? '__lc' : params.paginatedField;
|
114 |
| - return { [field]: sortDir, _id: sortDir }; |
| 127 | + return { |
| 128 | + [field]: sortDir, |
| 129 | + _id: sortDir, |
| 130 | + }; |
115 | 131 | }
|
116 | 132 | }
|
117 | 133 |
|
118 |
| -/** |
119 |
| - * Generates a cursor query that provides the offset capabilities. |
| 134 | +function /** |
| 135 | + * Generates a cursor query that provides the offset capabilities |
120 | 136 | *
|
121 |
| - * @param params - The parameters originally passed to `find` or `aggregate` |
122 |
| - * @returns A cursor offset query |
| 137 | + * @param {Object} params The params originally passed to `find` or `aggregate` |
| 138 | + * |
| 139 | + * @return {Object} a cursor offset query |
123 | 140 | */
|
124 |
| -export function generateCursorQuery(params: PaginationParams): CursorQuery { |
| 141 | +generateCursorQuery(params: any): any { |
125 | 142 | if (!params.next && !params.previous) return {};
|
126 | 143 |
|
127 | 144 | const sortAsc =
|
@@ -213,9 +230,4 @@ export function generateCursorQuery(params: PaginationParams): CursorQuery {
|
213 | 230 | }
|
214 | 231 | }
|
215 | 232 |
|
216 |
| -export default { |
217 |
| - prepareResponse, |
218 |
| - encodePaginationTokens, |
219 |
| - generateSort, |
220 |
| - generateCursorQuery, |
221 |
| -}; |
| 233 | +export { encodePaginationTokens, prepareResponse, generateSort, generateCursorQuery }; |
0 commit comments