@andrew_l/mongo-pagination 0.3.22 → 0.4.0

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 (36) hide show
  1. package/dist/_chunks/QueryPaginator.d.mts +198 -0
  2. package/dist/_chunks/QueryPaginator.d.mts.map +1 -0
  3. package/dist/_chunks/withMongoosePagination.mjs +489 -0
  4. package/dist/_chunks/withMongoosePagination.mjs.map +1 -0
  5. package/dist/index.d.mts +13 -14
  6. package/dist/index.d.mts.map +1 -0
  7. package/dist/index.mjs +36 -46
  8. package/dist/index.mjs.map +1 -1
  9. package/dist/mongoose-7.d.mts +9 -12
  10. package/dist/mongoose-7.d.mts.map +1 -0
  11. package/dist/mongoose-7.mjs +14 -27
  12. package/dist/mongoose-7.mjs.map +1 -1
  13. package/dist/mongoose-8.d.mts +9 -12
  14. package/dist/mongoose-8.d.mts.map +1 -0
  15. package/dist/mongoose-8.mjs +14 -27
  16. package/dist/mongoose-8.mjs.map +1 -1
  17. package/package.json +14 -11
  18. package/dist/index.cjs +0 -56
  19. package/dist/index.cjs.map +0 -1
  20. package/dist/index.d.cts +0 -150
  21. package/dist/index.d.ts +0 -150
  22. package/dist/mongoose-7.cjs +0 -36
  23. package/dist/mongoose-7.cjs.map +0 -1
  24. package/dist/mongoose-7.d.cts +0 -16
  25. package/dist/mongoose-7.d.ts +0 -16
  26. package/dist/mongoose-8.cjs +0 -36
  27. package/dist/mongoose-8.cjs.map +0 -1
  28. package/dist/mongoose-8.d.cts +0 -16
  29. package/dist/mongoose-8.d.ts +0 -16
  30. package/dist/shared/mongo-pagination.Ckfb2fG8.mjs +0 -663
  31. package/dist/shared/mongo-pagination.Ckfb2fG8.mjs.map +0 -1
  32. package/dist/shared/mongo-pagination.D7Mb8JCq.d.cts +0 -195
  33. package/dist/shared/mongo-pagination.D7Mb8JCq.d.mts +0 -195
  34. package/dist/shared/mongo-pagination.D7Mb8JCq.d.ts +0 -195
  35. package/dist/shared/mongo-pagination.odJCU2tV.cjs +0 -676
  36. package/dist/shared/mongo-pagination.odJCU2tV.cjs.map +0 -1
@@ -0,0 +1,198 @@
1
+ import { Sort } from "mongodb";
2
+ import internal, { Readable } from "node:stream";
3
+ interface TokenOptions {
4
+ /**
5
+ * The schema version of the token.
6
+ */
7
+ schemaVersion: number;
8
+ /**
9
+ * The name of the model the token is related to.
10
+ */
11
+ modelName?: string;
12
+ /**
13
+ * The sorting direction used to building pagination query.
14
+ */
15
+ sortDirection?: Record<string, any>;
16
+ /**
17
+ * The values associated with sorting.
18
+ * Used in conjunction with `sortDirection` to building range query.
19
+ */
20
+ sortValues?: Record<string, any>;
21
+ /**
22
+ * The payload of the token. Contains additional data or metadata (can be `null`).
23
+ */
24
+ payload: Record<string, any> | null;
25
+ }
26
+ /**
27
+ * Represents a token with pagination and metadata information.
28
+ * This class holds the token data, including sorting details, and any payload.
29
+ */
30
+ declare class Token {
31
+ /**
32
+ * The CRC of the model name, used for verify the model.
33
+ */
34
+ modelNameCRC: number;
35
+ /**
36
+ * The sorting direction used for pagination building pagination query.
37
+ */
38
+ sortDirection: Record<string, any>;
39
+ /**
40
+ * The sorting values associated with the pagination.
41
+ */
42
+ sortValues: Record<string, any>;
43
+ /**
44
+ * The payload of the token, which can contain any associated metadata or data (may be `null`).
45
+ */
46
+ payload: Record<string, any> | null;
47
+ constructor(options?: Partial<TokenOptions>);
48
+ /**
49
+ * Retrieves the string representation of token.
50
+ */
51
+ stringify(): string;
52
+ /**
53
+ * Retrieves binary buffer representation of token.
54
+ */
55
+ buffer(): Uint8Array;
56
+ /**
57
+ * Encode token from provided value
58
+ */
59
+ static from(value: string | Buffer | Uint8Array): Token;
60
+ }
61
+ /**
62
+ * Parses a token from provided value.
63
+ * @group Utils
64
+ */
65
+ declare function parseToken(value: string | Buffer): Token;
66
+ /**
67
+ * Create a token with pagination and metadata information.
68
+ * This holds the token data, including sorting details, and any payload.
69
+ *
70
+ * @group Utils
71
+ */
72
+ declare function createToken(options?: Partial<TokenOptions>): Token;
73
+ declare namespace QueryAdapter {
74
+ type Options = {
75
+ sort?: Sort;
76
+ limit?: number;
77
+ };
78
+ type Filter = Record<string, any>;
79
+ interface QueryAdapter<T = any> {
80
+ getModelName(): string;
81
+ getOptions(): Options;
82
+ getFilter(): Filter;
83
+ setOptions(options: Options): void;
84
+ setFilter(filter: Filter): void;
85
+ toArray(): Promise<T[]>;
86
+ stream(): Readable;
87
+ }
88
+ }
89
+ interface QueryPaginatorOptions {
90
+ /**
91
+ * An optional list of fields used for building pagination query.
92
+ */
93
+ paginationFields?: string[];
94
+ /**
95
+ * The token to start the pagination from, if available.
96
+ */
97
+ next?: string | Buffer | null;
98
+ /**
99
+ * A callback function executed before the query is performed.
100
+ */
101
+ preQuery?: (this: QueryPaginator) => void;
102
+ /**
103
+ * A callback function executed after the query is performed.
104
+ */
105
+ postQuery?: (this: QueryPaginator) => void;
106
+ /** @internal */
107
+ debugQuery?: boolean;
108
+ }
109
+ interface QueryPaginatorMeta {
110
+ /**
111
+ * Indicates whether there is a next page available.
112
+ */
113
+ hasNext: boolean;
114
+ /**
115
+ * The token for the next page, or `null` if there is no next page.
116
+ */
117
+ next: string | null;
118
+ }
119
+ interface QueryPaginatorResult<T> {
120
+ /**
121
+ * The list of items for the current page.
122
+ */
123
+ items: T[];
124
+ /**
125
+ * Metadata providing information about pagination, such as whether more pages are available.
126
+ */
127
+ metadata: QueryPaginatorMeta;
128
+ }
129
+ /**
130
+ * A generic class for handling query pagination.
131
+ *
132
+ * Look at `withMongoPagination` and `withMongoosePagination`
133
+ *
134
+ * @group Main
135
+ */
136
+ declare class QueryPaginator<T = any> {
137
+ private query;
138
+ private hooks;
139
+ private next;
140
+ private previous?;
141
+ private resHasNext;
142
+ private resLastItem;
143
+ private paginationFields;
144
+ private tweaked;
145
+ private debugQuery;
146
+ getMetadata: () => QueryPaginatorMeta;
147
+ constructor(query: QueryAdapter.QueryAdapter, {
148
+ paginationFields,
149
+ postQuery,
150
+ preQuery,
151
+ next: previousToken,
152
+ debugQuery
153
+ }?: QueryPaginatorOptions);
154
+ /**
155
+ * Retrieves metadata about the current state of pagination.
156
+ */
157
+ get metadata(): QueryPaginatorMeta;
158
+ /**
159
+ * Indicates whether there is a next page available.
160
+ */
161
+ get hasNext(): boolean;
162
+ /**
163
+ * Retrieves the token for the next page.
164
+ */
165
+ get nextToken(): Token;
166
+ /**
167
+ * Retrieves the string representation of the next page token.
168
+ * If there is no next page, returns `null`.
169
+ */
170
+ get nextTokenString(): string | null;
171
+ /**
172
+ * Retrieves the token for the previous page.
173
+ */
174
+ get prevToken(): Token | null;
175
+ /**
176
+ * Retrieves the string representation of the previous page token.
177
+ */
178
+ get prevTokenString(): string | null;
179
+ getQuery(): QueryAdapter.QueryAdapter<any>;
180
+ pre(hookName: string, fn: Function): this;
181
+ post(hookName: string, fn: Function): this;
182
+ stream(): Promise<internal.Transform>;
183
+ /** @internal */
184
+ maybeTweak(): this;
185
+ /** @internal */
186
+ tweak(): this;
187
+ /** @internal */
188
+ _handleResult(items: any[]): QueryPaginatorResult<any>;
189
+ /**
190
+ * Executes the query and returns the results along with pagination metadata.
191
+ */
192
+ exec(): Promise<QueryPaginatorResult<T>>;
193
+ then: Promise<QueryPaginatorResult<T>>["then"];
194
+ cath: Promise<QueryPaginatorResult<T>>["catch"];
195
+ finally: Promise<QueryPaginatorResult<T>>["finally"];
196
+ }
197
+ export { QueryAdapter, QueryPaginator, QueryPaginatorMeta, QueryPaginatorOptions, QueryPaginatorResult, Token, TokenOptions, createToken, parseToken };
198
+ //# sourceMappingURL=QueryPaginator.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"QueryPaginator.d.mts","names":["schemaVersion","modelName","sortDirection","Record","sortValues","payload","modelNameCRC","constructor","Partial","TokenOptions","options","stringify","buffer","Uint8Array","from","Buffer","value","Token","Options","sort","Sort","limit","Filter","Record","QueryAdapter","T","getModelName","getOptions","getFilter","setOptions","options","setFilter","filter","toArray","Promise","stream","Readable","paginationFields","next","Buffer","preQuery","QueryPaginator","this","postQuery","debugQuery","hasNext","T","items","metadata","QueryPaginatorMeta","query","hooks","previous","resHasNext","resLastItem","tweaked","getMetadata","constructor","QueryAdapter","previousToken","QueryPaginatorOptions","nextToken","Token","nextTokenString","prevToken","prevTokenString","getQuery","pre","hookName","Function","fn","post","stream","Promise","internal","Transform","maybeTweak","tweak","_handleResult","QueryPaginatorResult","exec","then","cath","finally"],"sources":["../../src/Token.d.ts","../../src/QueryAdapter.d.ts","../../src/QueryPaginator.d.ts"],"mappings":"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"}
@@ -0,0 +1,489 @@
1
+ import { CORE_TYPES, createExtension, defineStructure } from "@andrew_l/tl-pack";
2
+ import { base62Fast, cleanEmpty, crc32, deepClone, intersection, isEmpty, isFunction, isNumber, isObject, pick, toError } from "@andrew_l/toolkit";
3
+ import { MongoInvalidArgumentError, ObjectId } from "mongodb";
4
+ import d from "debug";
5
+ import Kareem from "kareem";
6
+ import { Transform } from "node:stream";
7
+ const extensions = [createExtension(100, {
8
+ encode(value) {
9
+ if (value?._bsontype?.toLowerCase() === "objectid") this.writeBytes(value.id);
10
+ },
11
+ decode() {
12
+ return new ObjectId(this.readBytes());
13
+ }
14
+ })];
15
+ const TokenStructure = defineStructure({
16
+ name: "PaginationToken",
17
+ version: 1,
18
+ checksum: true,
19
+ properties: {
20
+ modelNameCrc: {
21
+ type: CORE_TYPES.Int32,
22
+ required: true
23
+ },
24
+ keys: {
25
+ type: [String],
26
+ required: true
27
+ },
28
+ sortDirection: {
29
+ type: [null],
30
+ required: true
31
+ },
32
+ sortValues: {
33
+ type: [null],
34
+ required: true
35
+ },
36
+ payload: { type: Object }
37
+ }
38
+ });
39
+ var Token = class Token {
40
+ modelNameCRC = 0;
41
+ sortDirection = {};
42
+ sortValues = {};
43
+ payload = null;
44
+ constructor(options) {
45
+ if (options) {
46
+ Object.assign(this, {
47
+ ...options,
48
+ modelName: void 0
49
+ });
50
+ if (options.modelName) this.modelNameCRC = crc32(options.modelName);
51
+ }
52
+ }
53
+ stringify() {
54
+ return base62Fast.encode(this.buffer());
55
+ }
56
+ buffer() {
57
+ return new TokenStructure({
58
+ modelNameCrc: this.modelNameCRC,
59
+ sortDirection: Object.values(this.sortDirection),
60
+ sortValues: Object.values(this.sortValues),
61
+ keys: Object.keys(this.sortValues),
62
+ payload: this.payload
63
+ }).toBuffer({ extensions });
64
+ }
65
+ static from(value) {
66
+ const buffer = Buffer.isBuffer(value) || value instanceof Uint8Array ? value : base62Fast.decode(value);
67
+ const struct = TokenStructure.fromBuffer(buffer, { extensions });
68
+ const token = new Token();
69
+ token.modelNameCRC = struct.modelNameCrc;
70
+ token.sortDirection = {};
71
+ token.sortValues = {};
72
+ token.payload = struct.payload ?? {};
73
+ for (let idx = 0; idx < struct.keys.length; idx++) {
74
+ token.sortValues[struct.keys[idx]] = struct.sortValues[idx];
75
+ token.sortDirection[struct.keys[idx]] = struct.sortDirection[idx];
76
+ }
77
+ return token;
78
+ }
79
+ };
80
+ function parseToken(value) {
81
+ return Token.from(value);
82
+ }
83
+ function createToken(options) {
84
+ return new Token(options);
85
+ }
86
+ const debug = {
87
+ init: d("mongo-pagination:init"),
88
+ tweak: d("mongo-pagination:tweak"),
89
+ exec: d("mongo-pagination:exec"),
90
+ stream: d("mongo-pagination:query:stream")
91
+ };
92
+ function isEmptyObject(obj) {
93
+ return !isObject(obj) || isEmpty(obj);
94
+ }
95
+ function sameKeys(...args) {
96
+ return intersection(...args.map((v) => Object.keys(v)));
97
+ }
98
+ function defineGetter(obj, key, getterFn) {
99
+ delete obj[key];
100
+ Object.defineProperty(obj, key, {
101
+ get: getterFn,
102
+ enumerable: true,
103
+ configurable: true
104
+ });
105
+ }
106
+ function isLastKeys(value, keys, strictOrder) {
107
+ const lastKeys = (isObject(value) ? Object.keys(value) : []).slice(-keys.length);
108
+ if (lastKeys.length !== keys.length) return false;
109
+ for (let idx = 0; idx < keys.length; idx++) if (strictOrder) {
110
+ if (lastKeys[idx] !== keys[idx]) return false;
111
+ } else if (!lastKeys.includes(keys[idx])) return false;
112
+ return true;
113
+ }
114
+ function toObject(value) {
115
+ return isFunction(value?.toObject) ? value?.toObject() : value;
116
+ }
117
+ function prepareDirection(direction = 1) {
118
+ const value = `${direction}`.toLowerCase();
119
+ if (isMeta(direction)) return direction;
120
+ switch (value) {
121
+ case "ascending":
122
+ case "asc":
123
+ case "1": return 1;
124
+ case "descending":
125
+ case "desc":
126
+ case "-1": return -1;
127
+ default: throw new MongoInvalidArgumentError(`Invalid sort direction: ${JSON.stringify(direction)}`);
128
+ }
129
+ }
130
+ function isMeta(t) {
131
+ return typeof t === "object" && t != null && "$meta" in t && typeof t.$meta === "string";
132
+ }
133
+ function isPair(t) {
134
+ if (Array.isArray(t) && t.length === 2) try {
135
+ prepareDirection(t[1]);
136
+ return true;
137
+ } catch {
138
+ return false;
139
+ }
140
+ return false;
141
+ }
142
+ function isDeep(t) {
143
+ return Array.isArray(t) && Array.isArray(t[0]);
144
+ }
145
+ function isMap(t) {
146
+ return t instanceof Map && t.size > 0;
147
+ }
148
+ function pairToMap(v) {
149
+ return /* @__PURE__ */ new Map([[`${v[0]}`, prepareDirection([v[1]])]]);
150
+ }
151
+ function deepToMap(t) {
152
+ const sortEntries = t.map(([k, v]) => [`${k}`, prepareDirection(v)]);
153
+ return new Map(sortEntries);
154
+ }
155
+ function stringsToMap(t) {
156
+ const sortEntries = t.map((key) => [`${key}`, 1]);
157
+ return new Map(sortEntries);
158
+ }
159
+ function objectToMap(t) {
160
+ const sortEntries = Object.entries(t).map(([k, v]) => [`${k}`, prepareDirection(v)]);
161
+ return new Map(sortEntries);
162
+ }
163
+ function mapToMap(t) {
164
+ const sortEntries = Array.from(t).map(([k, v]) => [`${k}`, prepareDirection(v)]);
165
+ return new Map(sortEntries);
166
+ }
167
+ function formatSort(sort, direction) {
168
+ if (sort == null) return void 0;
169
+ if (typeof sort === "string") return /* @__PURE__ */ new Map([[sort, prepareDirection(direction)]]);
170
+ if (typeof sort !== "object") throw new MongoInvalidArgumentError(`Invalid sort format: ${JSON.stringify(sort)} Sort must be a valid object`);
171
+ if (!Array.isArray(sort)) return isMap(sort) ? mapToMap(sort) : Object.keys(sort).length ? objectToMap(sort) : void 0;
172
+ if (!sort.length) return void 0;
173
+ if (isDeep(sort)) return deepToMap(sort);
174
+ if (isPair(sort)) return pairToMap(sort);
175
+ return stringsToMap(sort);
176
+ }
177
+ const DEF_LIMIT = 10;
178
+ function createRangeFilter(sortDirection, sortValues) {
179
+ const sortCmd = formatSort(sortDirection) ?? {};
180
+ const keys = [];
181
+ for (const [sortKey, sortValue] of sortCmd.entries()) {
182
+ if (sortValues[sortKey] === void 0) continue;
183
+ if (!isNumber(sortValue)) continue;
184
+ keys.push(sortKey);
185
+ }
186
+ const op = (sortKey) => {
187
+ return sortCmd.get(sortKey) === 1 ? "$gt" : "$lt";
188
+ };
189
+ const val = (sortKey) => sortValues[sortKey];
190
+ if (keys.length === 0) return {};
191
+ else if (keys.length === 1) {
192
+ const sortKey = keys[0];
193
+ return { [sortKey]: { [op(sortKey)]: val(sortKey) } };
194
+ }
195
+ const $or = [];
196
+ const keysAmount = keys.length;
197
+ for (let i = 0; i < keysAmount; i++) {
198
+ const sortKey = keys[i];
199
+ if (i === 0) $or.push({ [sortKey]: { [op(sortKey)]: val(sortKey) } });
200
+ else {
201
+ const filter = {};
202
+ keys.slice(0, i).map((sortKey) => {
203
+ filter[sortKey] = { $eq: val(sortKey) };
204
+ });
205
+ const sortKey = keys[i];
206
+ filter[sortKey] = { [op(sortKey)]: val(sortKey) };
207
+ $or.push(filter);
208
+ }
209
+ }
210
+ if ($or.length === 1) return $or[0];
211
+ return { $or };
212
+ }
213
+ function mergeFilters(first, second) {
214
+ switch (true) {
215
+ case isEmptyObject(first): return deepClone(second);
216
+ case isEmptyObject(second): return deepClone(first);
217
+ case sameKeys(first, second).length === 0: return Object.assign(deepClone(first), deepClone(second));
218
+ case Array.isArray(first.$and):
219
+ first = deepClone(first);
220
+ second = deepClone(second);
221
+ if (Object.keys(second).length === 1 && second.$and) first.$and.push(...second.$and);
222
+ else first.$and.push(second);
223
+ return first;
224
+ default: return { $and: [deepClone(first), deepClone(second)] };
225
+ }
226
+ }
227
+ function tweakQuery({ paginationFields, token, query }) {
228
+ let queryOptions = deepClone(query.getOptions());
229
+ let queryFilter = deepClone(query.getFilter());
230
+ paginationFields = deepClone(paginationFields);
231
+ if (queryOptions.sort) {
232
+ const sortMap = formatSort(queryOptions.sort);
233
+ if (sortMap) queryOptions.sort = Object.fromEntries(sortMap.entries());
234
+ }
235
+ if (token) {
236
+ if (!isEmptyObject(token.sortDirection)) {
237
+ debug.tweak("set from token [queryOptions.sort] = %o", token.sortDirection);
238
+ queryOptions.sort = token.sortDirection;
239
+ }
240
+ if (!isEmptyObject(token.sortValues)) {
241
+ const rangeFilter = createRangeFilter(queryOptions.sort, token.sortValues);
242
+ debug.tweak("range conditions:\n sortDirection = %o\n sortValues = %o\n result = %o", queryOptions.sort, token.sortValues, rangeFilter);
243
+ queryFilter = mergeFilters(queryFilter, rangeFilter);
244
+ }
245
+ }
246
+ if (isEmptyObject(queryOptions.sort)) {
247
+ queryOptions.sort = { _id: -1 };
248
+ debug.tweak("set default [queryOptions.sort] = %o", queryOptions.sort);
249
+ }
250
+ if (!Array.isArray(paginationFields) || !paginationFields.length) {
251
+ paginationFields = Object.keys(queryOptions.sort);
252
+ debug.tweak("set default [paginationFields] = %o", paginationFields);
253
+ }
254
+ if (!isLastKeys(queryOptions.sort, paginationFields)) throw new MongoInvalidArgumentError(`The query sort keys must ends with "${paginationFields.join(", ")}".`);
255
+ if (!queryOptions.limit) {
256
+ queryOptions.limit = 11;
257
+ debug.tweak("set default [queryOptions.limit] = %d", queryOptions.limit);
258
+ } else queryOptions.limit = Math.max(queryOptions.limit || DEF_LIMIT, 1) + 1;
259
+ query.setOptions(queryOptions);
260
+ query.setFilter(queryFilter);
261
+ return paginationFields;
262
+ }
263
+ var QueryAdapterMongoose = class {
264
+ query;
265
+ constructor(query) {
266
+ this.query = query;
267
+ }
268
+ getModelName() {
269
+ return this.query.model.modelName;
270
+ }
271
+ getOptions() {
272
+ const options = this.query.getOptions();
273
+ return {
274
+ limit: options.limit,
275
+ sort: options.sort
276
+ };
277
+ }
278
+ getFilter() {
279
+ return this.query.getFilter();
280
+ }
281
+ setOptions({ limit, sort }) {
282
+ this.query.setOptions(cleanEmpty({
283
+ limit,
284
+ sort
285
+ }));
286
+ }
287
+ setFilter(filter) {
288
+ this.query.setQuery(filter);
289
+ }
290
+ toArray() {
291
+ return this.query.exec();
292
+ }
293
+ stream() {
294
+ return this.query.cursor();
295
+ }
296
+ };
297
+ var Filter = class extends Transform {
298
+ has;
299
+ _count = 0;
300
+ constructor(has, options) {
301
+ super({
302
+ autoDestroy: true,
303
+ objectMode: true,
304
+ ...options,
305
+ readableObjectMode: true,
306
+ writableObjectMode: true,
307
+ highWaterMark: 16
308
+ });
309
+ this.has = has;
310
+ this._count = -1;
311
+ }
312
+ _transform(chunk, encoding, next) {
313
+ const result = this.has(chunk, ++this._count);
314
+ if (typeof result === "boolean") if (result) return next(null, chunk);
315
+ else return next();
316
+ if ("then" in result) {
317
+ result.then((r) => {
318
+ if (r) next(null, chunk);
319
+ }).catch(next);
320
+ return;
321
+ }
322
+ next();
323
+ }
324
+ };
325
+ function streamFilter(fn, options) {
326
+ return new Filter(fn, options);
327
+ }
328
+ var QueryPaginator = class {
329
+ query;
330
+ hooks = new Kareem();
331
+ next;
332
+ previous;
333
+ resHasNext = false;
334
+ resLastItem = null;
335
+ paginationFields;
336
+ tweaked = false;
337
+ debugQuery = false;
338
+ getMetadata = () => this.metadata;
339
+ constructor(query, { paginationFields = [], postQuery, preQuery, next: previousToken, debugQuery = false } = {}) {
340
+ this.query = query;
341
+ this.paginationFields = paginationFields;
342
+ this.debugQuery = debugQuery;
343
+ this.next = createToken({
344
+ modelName: query.getModelName(),
345
+ sortDirection: {},
346
+ sortValues: {},
347
+ payload: {}
348
+ });
349
+ if (previousToken) {
350
+ try {
351
+ this.previous = Token.from(previousToken);
352
+ this.paginationFields = Object.keys(this.previous.sortValues);
353
+ debug.init("parse previous = %o", this.previous);
354
+ debug.init("set from previous [paginationFields] = %o", this.paginationFields);
355
+ } catch (orignErr) {
356
+ throw new MongoInvalidArgumentError("Failed to parse next pagination cursor.", { cause: toError(orignErr) });
357
+ }
358
+ const modelNameCRC = crc32(query.getModelName());
359
+ debug.init("compare model name: previous = %d, query = %d", this.previous.modelNameCRC, modelNameCRC);
360
+ if (modelNameCRC !== this.previous.modelNameCRC) throw new MongoInvalidArgumentError("The model name of next cursor token is not equal with query model name. Expected: " + this.query.getModelName());
361
+ }
362
+ if (preQuery) this.pre("query", preQuery);
363
+ if (postQuery) this.post("query", postQuery);
364
+ defineGetter(this.next, "sortDirection", () => {
365
+ const sortMap = formatSort(this.query.getOptions().sort);
366
+ return sortMap ? Object.fromEntries(sortMap.entries()) : {};
367
+ });
368
+ defineGetter(this.next, "sortValues", () => {
369
+ if (!this.resLastItem) return {};
370
+ return pick(this.resLastItem, this.paginationFields);
371
+ });
372
+ debug.init("with options = %o", arguments[1]);
373
+ }
374
+ get metadata() {
375
+ return {
376
+ hasNext: this.hasNext,
377
+ next: this.nextTokenString
378
+ };
379
+ }
380
+ get hasNext() {
381
+ return this.resHasNext;
382
+ }
383
+ get nextToken() {
384
+ return this.next;
385
+ }
386
+ get nextTokenString() {
387
+ if (!this.resHasNext) return null;
388
+ return this.next.stringify();
389
+ }
390
+ get prevToken() {
391
+ return this.previous || null;
392
+ }
393
+ get prevTokenString() {
394
+ return this.previous ? this.previous.stringify() : null;
395
+ }
396
+ getQuery() {
397
+ return this.query;
398
+ }
399
+ pre(hookName, fn) {
400
+ this.hooks.pre(hookName, fn);
401
+ return this;
402
+ }
403
+ post(hookName, fn) {
404
+ this.hooks.post(hookName, fn);
405
+ return this;
406
+ }
407
+ stream() {
408
+ return new Promise((resolve, reject) => {
409
+ this.hooks.execPre("query", this, [], (error) => {
410
+ if (error) return reject(error);
411
+ try {
412
+ this.maybeTweak();
413
+ } catch (err) {
414
+ return reject(err);
415
+ }
416
+ const limit = this.query.getOptions().limit;
417
+ const filter = streamFilter((item, index) => {
418
+ if (index + 1 === limit) {
419
+ this.resHasNext = true;
420
+ debug.stream("has next:\n cutoff_idx = %d\n next = %j", index, this.next);
421
+ return false;
422
+ } else this.resLastItem = toObject(item);
423
+ return true;
424
+ });
425
+ resolve(this.query.stream().pipe(filter));
426
+ });
427
+ });
428
+ }
429
+ maybeTweak() {
430
+ if (!this.tweaked) this.tweak();
431
+ return this;
432
+ }
433
+ tweak() {
434
+ this.paginationFields = tweakQuery({
435
+ query: this.query,
436
+ paginationFields: this.paginationFields,
437
+ token: this.previous
438
+ });
439
+ this.tweaked = true;
440
+ return this;
441
+ }
442
+ _handleResult(items) {
443
+ const limit = this.query.getOptions().limit;
444
+ if (items.length === limit) {
445
+ items.pop();
446
+ this.resHasNext = true;
447
+ }
448
+ this.resLastItem = toObject(items.at(-1));
449
+ if (this.resHasNext) debug.exec("has next:\n cutoff_idx = %d\n next = %j", limit - 1, this.next);
450
+ return {
451
+ metadata: this.getMetadata(),
452
+ items
453
+ };
454
+ }
455
+ exec() {
456
+ return new Promise((resolve, reject) => {
457
+ this.hooks.execPre("query", this, [], (preHookErr) => {
458
+ if (preHookErr) return reject(preHookErr);
459
+ if (this.debugQuery) console.info("pre tweak", {
460
+ queryFilter: this.query.getFilter(),
461
+ getOptions: this.query.getOptions(),
462
+ prevToken: this.prevToken
463
+ });
464
+ this.maybeTweak();
465
+ if (this.debugQuery) console.info("post tweak", {
466
+ queryFilter: this.query.getFilter(),
467
+ getOptions: this.query.getOptions(),
468
+ prevToken: this.prevToken
469
+ });
470
+ this.query.toArray().then((items) => {
471
+ const result = this._handleResult(items);
472
+ this.hooks.execPost("query", this, [], (postHookErr) => {
473
+ if (postHookErr) return reject(postHookErr);
474
+ return resolve(result);
475
+ });
476
+ }).catch(reject);
477
+ });
478
+ });
479
+ }
480
+ then = ((onfulfilled, onrejected) => this.exec().then(onfulfilled, onrejected));
481
+ cath = ((onrejected) => this.exec().catch(onrejected));
482
+ finally = ((onfinally) => this.exec().finally(onfinally));
483
+ };
484
+ function withMongoosePagination(query, options) {
485
+ return new QueryPaginator(new QueryAdapterMongoose(query), options);
486
+ }
487
+ export { QueryPaginator, createRangeFilter, createToken, mergeFilters, parseToken, tweakQuery, withMongoosePagination };
488
+
489
+ //# sourceMappingURL=withMongoosePagination.mjs.map