@andrew_l/mongo-pagination 0.0.2
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.
- package/LICENSE +21 -0
- package/README.md +101 -0
- package/dist/index.cjs +55 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +150 -0
- package/dist/index.d.mts +150 -0
- package/dist/index.d.ts +150 -0
- package/dist/index.mjs +49 -0
- package/dist/index.mjs.map +1 -0
- package/dist/mongoose-7.cjs +36 -0
- package/dist/mongoose-7.cjs.map +1 -0
- package/dist/mongoose-7.d.cts +16 -0
- package/dist/mongoose-7.d.mts +16 -0
- package/dist/mongoose-7.d.ts +16 -0
- package/dist/mongoose-7.mjs +34 -0
- package/dist/mongoose-7.mjs.map +1 -0
- package/dist/mongoose-8.cjs +36 -0
- package/dist/mongoose-8.cjs.map +1 -0
- package/dist/mongoose-8.d.cts +16 -0
- package/dist/mongoose-8.d.mts +16 -0
- package/dist/mongoose-8.d.ts +16 -0
- package/dist/mongoose-8.mjs +34 -0
- package/dist/mongoose-8.mjs.map +1 -0
- package/dist/shared/mongo-pagination.BU3-d0QB.d.cts +194 -0
- package/dist/shared/mongo-pagination.BU3-d0QB.d.mts +194 -0
- package/dist/shared/mongo-pagination.BU3-d0QB.d.ts +194 -0
- package/dist/shared/mongo-pagination.CHC5agt7.cjs +666 -0
- package/dist/shared/mongo-pagination.CHC5agt7.cjs.map +1 -0
- package/dist/shared/mongo-pagination.YG1DNE27.mjs +654 -0
- package/dist/shared/mongo-pagination.YG1DNE27.mjs.map +1 -0
- package/package.json +61 -0
|
@@ -0,0 +1,666 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const toolkit = require('@andrew_l/toolkit');
|
|
4
|
+
const Kareem = require('kareem');
|
|
5
|
+
const mongodb = require('mongodb');
|
|
6
|
+
const tlPack = require('@andrew_l/tl-pack');
|
|
7
|
+
const d = require('debug');
|
|
8
|
+
const node_stream = require('node:stream');
|
|
9
|
+
|
|
10
|
+
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
|
|
11
|
+
|
|
12
|
+
const Kareem__default = /*#__PURE__*/_interopDefaultCompat(Kareem);
|
|
13
|
+
const d__default = /*#__PURE__*/_interopDefaultCompat(d);
|
|
14
|
+
|
|
15
|
+
const SCHEMA_VERSION = 1;
|
|
16
|
+
const OBJECT_ID_TOKEN = 100;
|
|
17
|
+
const extensions = [
|
|
18
|
+
tlPack.createExtension(OBJECT_ID_TOKEN, {
|
|
19
|
+
encode(value) {
|
|
20
|
+
if (value?._bsontype?.toLowerCase() === "objectid") {
|
|
21
|
+
this.writeBytes(value.id);
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
decode() {
|
|
25
|
+
const bytes = this.readBytes();
|
|
26
|
+
return new mongodb.ObjectId(bytes);
|
|
27
|
+
}
|
|
28
|
+
})
|
|
29
|
+
];
|
|
30
|
+
class Token {
|
|
31
|
+
/**
|
|
32
|
+
* The schema version of the token.
|
|
33
|
+
*/
|
|
34
|
+
schemaVersion = SCHEMA_VERSION;
|
|
35
|
+
/**
|
|
36
|
+
* The CRC of the model name, used for verify the model.
|
|
37
|
+
*/
|
|
38
|
+
modelNameCRC = 0;
|
|
39
|
+
/**
|
|
40
|
+
* The sorting direction used for pagination building pagination query.
|
|
41
|
+
*/
|
|
42
|
+
sortDirection = {};
|
|
43
|
+
/**
|
|
44
|
+
* The sorting values associated with the pagination.
|
|
45
|
+
*/
|
|
46
|
+
sortValues = {};
|
|
47
|
+
/**
|
|
48
|
+
* The payload of the token, which can contain any associated metadata or data (may be `null`).
|
|
49
|
+
*/
|
|
50
|
+
payload = null;
|
|
51
|
+
constructor(options) {
|
|
52
|
+
if (options) {
|
|
53
|
+
Object.assign(this, { ...options, modelName: void 0 });
|
|
54
|
+
if (options.modelName) {
|
|
55
|
+
this.modelNameCRC = toolkit.crc32(options.modelName);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Retrieves the string representation of token.
|
|
61
|
+
*/
|
|
62
|
+
stringify() {
|
|
63
|
+
return this.buffer().toString("base64url");
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Retrieves binary buffer representation of token.
|
|
67
|
+
*/
|
|
68
|
+
buffer() {
|
|
69
|
+
const writer = new tlPack.BinaryWriter({
|
|
70
|
+
extensions
|
|
71
|
+
});
|
|
72
|
+
writer.writeInt8(this.schemaVersion, false);
|
|
73
|
+
writer.writeObject(this.modelNameCRC);
|
|
74
|
+
writer.writeMap(this.sortDirection || {});
|
|
75
|
+
writer.writeMap(this.sortValues || {});
|
|
76
|
+
writer.writeObject(this.payload || null);
|
|
77
|
+
return Buffer.from(writer.getBuffer());
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Encode token from provided value
|
|
81
|
+
*/
|
|
82
|
+
static from(value) {
|
|
83
|
+
const buffer = Buffer.isBuffer(value) ? value : Buffer.from(value, "base64url");
|
|
84
|
+
const reader = new tlPack.BinaryReader(buffer, { extensions });
|
|
85
|
+
const schemaVersion = reader.readInt8(false);
|
|
86
|
+
if (schemaVersion !== SCHEMA_VERSION) {
|
|
87
|
+
throw new TypeError(`Unexpected schema version: ${schemaVersion}`);
|
|
88
|
+
}
|
|
89
|
+
const token = new Token();
|
|
90
|
+
token.modelNameCRC = reader.readObject();
|
|
91
|
+
token.sortDirection = reader.readMap(false);
|
|
92
|
+
token.sortValues = reader.readMap(false);
|
|
93
|
+
token.payload = reader.readObject();
|
|
94
|
+
return token;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function createToken(options) {
|
|
98
|
+
return new Token(options);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const debug = {
|
|
102
|
+
init: d__default("mongo-pagination:init"),
|
|
103
|
+
tweak: d__default("mongo-pagination:tweak"),
|
|
104
|
+
exec: d__default("mongo-pagination:exec"),
|
|
105
|
+
stream: d__default("mongo-pagination:query:stream")
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
function isEmptyObject(obj) {
|
|
109
|
+
return !toolkit.isObject(obj) || toolkit.isEmpty(obj);
|
|
110
|
+
}
|
|
111
|
+
function sameKeys(...args) {
|
|
112
|
+
return toolkit.intersection(...args.map((v) => Object.keys(v)));
|
|
113
|
+
}
|
|
114
|
+
function defineGetter(obj, key, getterFn) {
|
|
115
|
+
delete obj[key];
|
|
116
|
+
Object.defineProperty(obj, key, {
|
|
117
|
+
get: getterFn,
|
|
118
|
+
enumerable: true,
|
|
119
|
+
configurable: true
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
function isLastKeys(value, keys, strictOrder) {
|
|
123
|
+
const objKeys = toolkit.isObject(value) ? Object.keys(value) : [];
|
|
124
|
+
const lastKeys = objKeys.slice(-keys.length);
|
|
125
|
+
if (lastKeys.length !== keys.length) {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
for (let idx = 0; idx < keys.length; idx++) {
|
|
129
|
+
{
|
|
130
|
+
if (!lastKeys.includes(keys[idx])) {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
function toObject(value) {
|
|
138
|
+
return toolkit.isFunction(value?.toObject) ? value?.toObject() : value;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function prepareDirection(direction = 1) {
|
|
142
|
+
const value = `${direction}`.toLowerCase();
|
|
143
|
+
if (isMeta(direction)) return direction;
|
|
144
|
+
switch (value) {
|
|
145
|
+
case "ascending":
|
|
146
|
+
case "asc":
|
|
147
|
+
case "1":
|
|
148
|
+
return 1;
|
|
149
|
+
case "descending":
|
|
150
|
+
case "desc":
|
|
151
|
+
case "-1":
|
|
152
|
+
return -1;
|
|
153
|
+
default:
|
|
154
|
+
throw new mongodb.MongoInvalidArgumentError(
|
|
155
|
+
`Invalid sort direction: ${JSON.stringify(direction)}`
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
function isMeta(t) {
|
|
160
|
+
return typeof t === "object" && t != null && "$meta" in t && typeof t.$meta === "string";
|
|
161
|
+
}
|
|
162
|
+
function isPair(t) {
|
|
163
|
+
if (Array.isArray(t) && t.length === 2) {
|
|
164
|
+
try {
|
|
165
|
+
prepareDirection(t[1]);
|
|
166
|
+
return true;
|
|
167
|
+
} catch {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
function isDeep(t) {
|
|
174
|
+
return Array.isArray(t) && Array.isArray(t[0]);
|
|
175
|
+
}
|
|
176
|
+
function isMap(t) {
|
|
177
|
+
return t instanceof Map && t.size > 0;
|
|
178
|
+
}
|
|
179
|
+
function pairToMap(v) {
|
|
180
|
+
return /* @__PURE__ */ new Map([[`${v[0]}`, prepareDirection([v[1]])]]);
|
|
181
|
+
}
|
|
182
|
+
function deepToMap(t) {
|
|
183
|
+
const sortEntries = t.map(([k, v]) => [
|
|
184
|
+
`${k}`,
|
|
185
|
+
prepareDirection(v)
|
|
186
|
+
]);
|
|
187
|
+
return new Map(sortEntries);
|
|
188
|
+
}
|
|
189
|
+
function stringsToMap(t) {
|
|
190
|
+
const sortEntries = t.map((key) => [`${key}`, 1]);
|
|
191
|
+
return new Map(sortEntries);
|
|
192
|
+
}
|
|
193
|
+
function objectToMap(t) {
|
|
194
|
+
const sortEntries = Object.entries(t).map(([k, v]) => [
|
|
195
|
+
`${k}`,
|
|
196
|
+
prepareDirection(v)
|
|
197
|
+
]);
|
|
198
|
+
return new Map(sortEntries);
|
|
199
|
+
}
|
|
200
|
+
function mapToMap(t) {
|
|
201
|
+
const sortEntries = Array.from(t).map(([k, v]) => [
|
|
202
|
+
`${k}`,
|
|
203
|
+
prepareDirection(v)
|
|
204
|
+
]);
|
|
205
|
+
return new Map(sortEntries);
|
|
206
|
+
}
|
|
207
|
+
function formatSort(sort, direction) {
|
|
208
|
+
if (sort == null) return void 0;
|
|
209
|
+
if (typeof sort === "string")
|
|
210
|
+
return /* @__PURE__ */ new Map([[sort, prepareDirection(direction)]]);
|
|
211
|
+
if (typeof sort !== "object") {
|
|
212
|
+
throw new mongodb.MongoInvalidArgumentError(
|
|
213
|
+
`Invalid sort format: ${JSON.stringify(sort)} Sort must be a valid object`
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
if (!Array.isArray(sort)) {
|
|
217
|
+
return isMap(sort) ? mapToMap(sort) : Object.keys(sort).length ? objectToMap(sort) : void 0;
|
|
218
|
+
}
|
|
219
|
+
if (!sort.length) return void 0;
|
|
220
|
+
if (isDeep(sort)) return deepToMap(sort);
|
|
221
|
+
if (isPair(sort)) return pairToMap(sort);
|
|
222
|
+
return stringsToMap(sort);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const DEF_LIMIT = 10;
|
|
226
|
+
function createRangeFilter(sortDirection, sortValues) {
|
|
227
|
+
const sortCmd = formatSort(sortDirection) ?? {};
|
|
228
|
+
const keys = [];
|
|
229
|
+
for (const [sortKey, sortValue] of sortCmd.entries()) {
|
|
230
|
+
if (sortValues[sortKey] === void 0) continue;
|
|
231
|
+
if (!toolkit.isNumber(sortValue)) continue;
|
|
232
|
+
keys.push(sortKey);
|
|
233
|
+
}
|
|
234
|
+
const op = (sortKey) => {
|
|
235
|
+
const direction = sortCmd.get(sortKey);
|
|
236
|
+
return direction === 1 ? "$gt" : "$lt";
|
|
237
|
+
};
|
|
238
|
+
const val = (sortKey) => sortValues[sortKey];
|
|
239
|
+
if (keys.length === 0) {
|
|
240
|
+
return {};
|
|
241
|
+
} else if (keys.length === 1) {
|
|
242
|
+
const sortKey = keys[0];
|
|
243
|
+
return {
|
|
244
|
+
[sortKey]: {
|
|
245
|
+
[op(sortKey)]: val(sortKey)
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
const $or = [];
|
|
250
|
+
const keysAmount = keys.length;
|
|
251
|
+
for (let i = 0; i < keysAmount; i++) {
|
|
252
|
+
const sortKey = keys[i];
|
|
253
|
+
if (i === 0) {
|
|
254
|
+
$or.push({
|
|
255
|
+
[sortKey]: { [op(sortKey)]: val(sortKey) }
|
|
256
|
+
});
|
|
257
|
+
} else {
|
|
258
|
+
const filter = {};
|
|
259
|
+
keys.slice(0, i).map((sortKey3) => {
|
|
260
|
+
filter[sortKey3] = { $eq: val(sortKey3) };
|
|
261
|
+
});
|
|
262
|
+
const sortKey2 = keys[i];
|
|
263
|
+
filter[sortKey2] = {
|
|
264
|
+
[op(sortKey2)]: val(sortKey2)
|
|
265
|
+
};
|
|
266
|
+
$or.push(filter);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
if ($or.length === 1) return $or[0];
|
|
270
|
+
return { $or };
|
|
271
|
+
}
|
|
272
|
+
function mergeFilters(first, second) {
|
|
273
|
+
switch (true) {
|
|
274
|
+
// Just return second
|
|
275
|
+
case isEmptyObject(first):
|
|
276
|
+
return toolkit.deepClone(second);
|
|
277
|
+
// Just return first
|
|
278
|
+
case isEmptyObject(second):
|
|
279
|
+
return toolkit.deepClone(first);
|
|
280
|
+
// Just assign when no conflicts
|
|
281
|
+
case sameKeys(first, second).length === 0:
|
|
282
|
+
return Object.assign(toolkit.deepClone(first), toolkit.deepClone(second));
|
|
283
|
+
// Merge with $and way
|
|
284
|
+
case Array.isArray(first.$and):
|
|
285
|
+
first = toolkit.deepClone(first);
|
|
286
|
+
second = toolkit.deepClone(second);
|
|
287
|
+
if (Object.keys(second).length === 1 && second.$and) {
|
|
288
|
+
first.$and.push(...second.$and);
|
|
289
|
+
} else {
|
|
290
|
+
first.$and.push(second);
|
|
291
|
+
}
|
|
292
|
+
return first;
|
|
293
|
+
// Regular merge via $and wrapping
|
|
294
|
+
default:
|
|
295
|
+
return {
|
|
296
|
+
$and: [toolkit.deepClone(first), toolkit.deepClone(second)]
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
function tweakQuery({
|
|
301
|
+
paginationFields,
|
|
302
|
+
token,
|
|
303
|
+
query
|
|
304
|
+
}) {
|
|
305
|
+
let queryOptions = toolkit.deepClone(query.getOptions());
|
|
306
|
+
let queryFilter = toolkit.deepClone(query.getFilter());
|
|
307
|
+
paginationFields = toolkit.deepClone(paginationFields);
|
|
308
|
+
if (queryOptions.sort) {
|
|
309
|
+
const sortMap = formatSort(queryOptions.sort);
|
|
310
|
+
if (sortMap) {
|
|
311
|
+
queryOptions.sort = Object.fromEntries(sortMap.entries());
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
if (token) {
|
|
315
|
+
if (!isEmptyObject(token.sortDirection)) {
|
|
316
|
+
debug.tweak(
|
|
317
|
+
"set from token [queryOptions.sort] = %o",
|
|
318
|
+
token.sortDirection
|
|
319
|
+
);
|
|
320
|
+
queryOptions.sort = token.sortDirection;
|
|
321
|
+
}
|
|
322
|
+
if (!isEmptyObject(token.sortValues)) {
|
|
323
|
+
const rangeFilter = createRangeFilter(
|
|
324
|
+
queryOptions.sort,
|
|
325
|
+
token.sortValues
|
|
326
|
+
);
|
|
327
|
+
debug.tweak(
|
|
328
|
+
"range conditions:\n sortDirection = %o\n sortValues = %o\n result = %o",
|
|
329
|
+
queryOptions.sort,
|
|
330
|
+
token.sortValues,
|
|
331
|
+
rangeFilter
|
|
332
|
+
);
|
|
333
|
+
queryFilter = mergeFilters(queryFilter, rangeFilter);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
if (isEmptyObject(queryOptions.sort)) {
|
|
337
|
+
queryOptions.sort = { _id: -1 };
|
|
338
|
+
debug.tweak("set default [queryOptions.sort] = %o", queryOptions.sort);
|
|
339
|
+
}
|
|
340
|
+
if (!Array.isArray(paginationFields) || !paginationFields.length) {
|
|
341
|
+
paginationFields = Object.keys(queryOptions.sort);
|
|
342
|
+
debug.tweak("set default [paginationFields] = %o", paginationFields);
|
|
343
|
+
}
|
|
344
|
+
if (!isLastKeys(queryOptions.sort, paginationFields)) {
|
|
345
|
+
const expectedKeys = paginationFields.join(", ");
|
|
346
|
+
throw new mongodb.MongoInvalidArgumentError(
|
|
347
|
+
`The query sort keys must ends with "${expectedKeys}".`
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
if (!queryOptions.limit) {
|
|
351
|
+
queryOptions.limit = DEF_LIMIT + 1;
|
|
352
|
+
debug.tweak("set default [queryOptions.limit] = %d", queryOptions.limit);
|
|
353
|
+
} else {
|
|
354
|
+
queryOptions.limit = Math.max(queryOptions.limit || DEF_LIMIT, 1) + 1;
|
|
355
|
+
}
|
|
356
|
+
query.setOptions(queryOptions);
|
|
357
|
+
query.setFilter(queryFilter);
|
|
358
|
+
return paginationFields;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
class QueryAdapterMongoose {
|
|
362
|
+
constructor(query) {
|
|
363
|
+
this.query = query;
|
|
364
|
+
}
|
|
365
|
+
getModelName() {
|
|
366
|
+
return this.query.model.modelName;
|
|
367
|
+
}
|
|
368
|
+
getOptions() {
|
|
369
|
+
const options = this.query.getOptions();
|
|
370
|
+
return {
|
|
371
|
+
limit: options.limit,
|
|
372
|
+
sort: options.sort
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
getFilter() {
|
|
376
|
+
return this.query.getFilter();
|
|
377
|
+
}
|
|
378
|
+
setOptions({ limit, sort }) {
|
|
379
|
+
this.query.setOptions(toolkit.cleanEmpty({ limit, sort }));
|
|
380
|
+
}
|
|
381
|
+
setFilter(filter) {
|
|
382
|
+
this.query.setQuery(filter);
|
|
383
|
+
}
|
|
384
|
+
toArray() {
|
|
385
|
+
return this.query.exec();
|
|
386
|
+
}
|
|
387
|
+
stream() {
|
|
388
|
+
return this.query.cursor();
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
class Filter extends node_stream.Transform {
|
|
393
|
+
constructor(has, options) {
|
|
394
|
+
super({
|
|
395
|
+
autoDestroy: true,
|
|
396
|
+
objectMode: true,
|
|
397
|
+
...options,
|
|
398
|
+
readableObjectMode: true,
|
|
399
|
+
writableObjectMode: true,
|
|
400
|
+
highWaterMark: 16
|
|
401
|
+
});
|
|
402
|
+
this.has = has;
|
|
403
|
+
this._count = -1;
|
|
404
|
+
}
|
|
405
|
+
_count = 0;
|
|
406
|
+
_transform(chunk, encoding, next) {
|
|
407
|
+
const result = this.has(chunk, ++this._count);
|
|
408
|
+
if (typeof result === "boolean") {
|
|
409
|
+
if (result) {
|
|
410
|
+
return next(null, chunk);
|
|
411
|
+
} else {
|
|
412
|
+
return next();
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
if ("then" in result) {
|
|
416
|
+
return void result.then((r) => {
|
|
417
|
+
if (r) next(null, chunk);
|
|
418
|
+
}).catch(next);
|
|
419
|
+
}
|
|
420
|
+
next();
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
function streamFilter(fn, options) {
|
|
424
|
+
return new Filter(fn, options);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
class QueryPaginator {
|
|
428
|
+
constructor(query, {
|
|
429
|
+
paginationFields = [],
|
|
430
|
+
postQuery,
|
|
431
|
+
preQuery,
|
|
432
|
+
next: previousToken,
|
|
433
|
+
debugQuery = false
|
|
434
|
+
} = {}) {
|
|
435
|
+
this.query = query;
|
|
436
|
+
this.paginationFields = paginationFields;
|
|
437
|
+
this.debugQuery = debugQuery;
|
|
438
|
+
this.next = createToken({
|
|
439
|
+
modelName: query.getModelName(),
|
|
440
|
+
sortDirection: {},
|
|
441
|
+
sortValues: {},
|
|
442
|
+
payload: {}
|
|
443
|
+
});
|
|
444
|
+
if (previousToken) {
|
|
445
|
+
try {
|
|
446
|
+
this.previous = Token.from(previousToken);
|
|
447
|
+
this.paginationFields = Object.keys(this.previous.sortValues);
|
|
448
|
+
debug.init("parse previous = %o", this.previous);
|
|
449
|
+
debug.init(
|
|
450
|
+
"set from previous [paginationFields] = %o",
|
|
451
|
+
this.paginationFields
|
|
452
|
+
);
|
|
453
|
+
} catch (orignErr) {
|
|
454
|
+
const err = new mongodb.MongoInvalidArgumentError(
|
|
455
|
+
"Failed to parse next pagination cursor.",
|
|
456
|
+
{ cause: toolkit.toError(orignErr) }
|
|
457
|
+
);
|
|
458
|
+
throw err;
|
|
459
|
+
}
|
|
460
|
+
const modelNameCRC = toolkit.crc32(query.getModelName());
|
|
461
|
+
debug.init(
|
|
462
|
+
"compare model name: previous = %d, query = %d",
|
|
463
|
+
this.previous.modelNameCRC,
|
|
464
|
+
modelNameCRC
|
|
465
|
+
);
|
|
466
|
+
if (modelNameCRC !== this.previous.modelNameCRC) {
|
|
467
|
+
throw new mongodb.MongoInvalidArgumentError(
|
|
468
|
+
"The model name of next cursor token is not equal with query model name. Expected: " + this.query.getModelName()
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
if (preQuery) this.pre("query", preQuery);
|
|
473
|
+
if (postQuery) this.post("query", postQuery);
|
|
474
|
+
defineGetter(this.next, "sortDirection", () => {
|
|
475
|
+
const sortMap = formatSort(this.query.getOptions().sort);
|
|
476
|
+
return sortMap ? Object.fromEntries(sortMap.entries()) : {};
|
|
477
|
+
});
|
|
478
|
+
defineGetter(this.next, "sortValues", () => {
|
|
479
|
+
if (!this.resLastItem) {
|
|
480
|
+
return {};
|
|
481
|
+
}
|
|
482
|
+
return toolkit.pick(this.resLastItem, this.paginationFields);
|
|
483
|
+
});
|
|
484
|
+
debug.init("with options = %o", arguments[1]);
|
|
485
|
+
}
|
|
486
|
+
hooks = new Kareem__default();
|
|
487
|
+
next;
|
|
488
|
+
previous;
|
|
489
|
+
resHasNext = false;
|
|
490
|
+
resLastItem = null;
|
|
491
|
+
paginationFields;
|
|
492
|
+
tweaked = false;
|
|
493
|
+
debugQuery = false;
|
|
494
|
+
getMetadata = () => this.metadata;
|
|
495
|
+
/**
|
|
496
|
+
* Retrieves metadata about the current state of pagination.
|
|
497
|
+
*/
|
|
498
|
+
get metadata() {
|
|
499
|
+
return {
|
|
500
|
+
hasNext: this.hasNext,
|
|
501
|
+
next: this.nextTokenString
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* Indicates whether there is a next page available.
|
|
506
|
+
*/
|
|
507
|
+
get hasNext() {
|
|
508
|
+
return this.resHasNext;
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Retrieves the token for the next page.
|
|
512
|
+
*/
|
|
513
|
+
get nextToken() {
|
|
514
|
+
return this.next;
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* Retrieves the string representation of the next page token.
|
|
518
|
+
* If there is no next page, returns `null`.
|
|
519
|
+
*/
|
|
520
|
+
get nextTokenString() {
|
|
521
|
+
if (!this.resHasNext) {
|
|
522
|
+
return null;
|
|
523
|
+
}
|
|
524
|
+
return this.next.stringify();
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* Retrieves the token for the previous page.
|
|
528
|
+
*/
|
|
529
|
+
get prevToken() {
|
|
530
|
+
return this.previous || null;
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Retrieves the string representation of the previous page token.
|
|
534
|
+
*/
|
|
535
|
+
get prevTokenString() {
|
|
536
|
+
return this.previous ? this.previous.stringify() : null;
|
|
537
|
+
}
|
|
538
|
+
getQuery() {
|
|
539
|
+
return this.query;
|
|
540
|
+
}
|
|
541
|
+
pre(hookName, fn) {
|
|
542
|
+
this.hooks.pre(hookName, fn);
|
|
543
|
+
return this;
|
|
544
|
+
}
|
|
545
|
+
post(hookName, fn) {
|
|
546
|
+
this.hooks.post(hookName, fn);
|
|
547
|
+
return this;
|
|
548
|
+
}
|
|
549
|
+
stream() {
|
|
550
|
+
return new Promise((resolve, reject) => {
|
|
551
|
+
this.hooks.execPre("query", this, [], (error) => {
|
|
552
|
+
if (error) return reject(error);
|
|
553
|
+
try {
|
|
554
|
+
this.maybeTweak();
|
|
555
|
+
} catch (err) {
|
|
556
|
+
return reject(err);
|
|
557
|
+
}
|
|
558
|
+
const limit = this.query.getOptions().limit;
|
|
559
|
+
const filter = streamFilter((item, index) => {
|
|
560
|
+
if (index + 1 === limit) {
|
|
561
|
+
this.resHasNext = true;
|
|
562
|
+
debug.stream(
|
|
563
|
+
"has next:\n cutoff_idx = %d\n next = %j",
|
|
564
|
+
index,
|
|
565
|
+
this.next
|
|
566
|
+
);
|
|
567
|
+
return false;
|
|
568
|
+
} else {
|
|
569
|
+
this.resLastItem = toObject(item);
|
|
570
|
+
}
|
|
571
|
+
return true;
|
|
572
|
+
});
|
|
573
|
+
const stream = this.query.stream().pipe(filter);
|
|
574
|
+
resolve(stream);
|
|
575
|
+
});
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
/** @internal */
|
|
579
|
+
maybeTweak() {
|
|
580
|
+
if (!this.tweaked) {
|
|
581
|
+
this.tweak();
|
|
582
|
+
}
|
|
583
|
+
return this;
|
|
584
|
+
}
|
|
585
|
+
/** @internal */
|
|
586
|
+
tweak() {
|
|
587
|
+
this.paginationFields = tweakQuery({
|
|
588
|
+
query: this.query,
|
|
589
|
+
paginationFields: this.paginationFields,
|
|
590
|
+
token: this.previous
|
|
591
|
+
});
|
|
592
|
+
this.tweaked = true;
|
|
593
|
+
return this;
|
|
594
|
+
}
|
|
595
|
+
/** @internal */
|
|
596
|
+
_handleResult(items) {
|
|
597
|
+
const limit = this.query.getOptions().limit;
|
|
598
|
+
if (items.length === limit) {
|
|
599
|
+
items.pop();
|
|
600
|
+
this.resHasNext = true;
|
|
601
|
+
}
|
|
602
|
+
this.resLastItem = toObject(items.at(-1));
|
|
603
|
+
if (this.resHasNext) {
|
|
604
|
+
debug.exec(
|
|
605
|
+
"has next:\n cutoff_idx = %d\n next = %j",
|
|
606
|
+
limit - 1,
|
|
607
|
+
this.next
|
|
608
|
+
);
|
|
609
|
+
}
|
|
610
|
+
const metadata = this.getMetadata();
|
|
611
|
+
return { metadata, items };
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* Executes the query and returns the results along with pagination metadata.
|
|
615
|
+
*/
|
|
616
|
+
exec() {
|
|
617
|
+
return new Promise((resolve, reject) => {
|
|
618
|
+
this.hooks.execPre("query", this, [], (preHookErr) => {
|
|
619
|
+
if (preHookErr) {
|
|
620
|
+
return reject(preHookErr);
|
|
621
|
+
}
|
|
622
|
+
if (this.debugQuery) {
|
|
623
|
+
console.info("pre tweak", {
|
|
624
|
+
queryFilter: this.query.getFilter(),
|
|
625
|
+
getOptions: this.query.getOptions(),
|
|
626
|
+
prevToken: this.prevToken
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
this.maybeTweak();
|
|
630
|
+
if (this.debugQuery) {
|
|
631
|
+
console.info("post tweak", {
|
|
632
|
+
queryFilter: this.query.getFilter(),
|
|
633
|
+
getOptions: this.query.getOptions(),
|
|
634
|
+
prevToken: this.prevToken
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
this.query.toArray().then((items) => {
|
|
638
|
+
const result = this._handleResult(items);
|
|
639
|
+
this.hooks.execPost("query", this, [], (postHookErr) => {
|
|
640
|
+
if (postHookErr) {
|
|
641
|
+
return reject(postHookErr);
|
|
642
|
+
}
|
|
643
|
+
return resolve(result);
|
|
644
|
+
});
|
|
645
|
+
}).catch(reject);
|
|
646
|
+
});
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
then = (onfulfilled, onrejected) => this.exec().then(onfulfilled, onrejected);
|
|
650
|
+
cath = (onrejected) => this.exec().catch(onrejected);
|
|
651
|
+
finally = (onfinally) => this.exec().finally(onfinally);
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
function withMongoosePagination(query, options) {
|
|
655
|
+
const adapter = new QueryAdapterMongoose(query);
|
|
656
|
+
const pagin = new QueryPaginator(adapter, options);
|
|
657
|
+
return pagin;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
exports.QueryPaginator = QueryPaginator;
|
|
661
|
+
exports.createRangeFilter = createRangeFilter;
|
|
662
|
+
exports.createToken = createToken;
|
|
663
|
+
exports.mergeFilters = mergeFilters;
|
|
664
|
+
exports.tweakQuery = tweakQuery;
|
|
665
|
+
exports.withMongoosePagination = withMongoosePagination;
|
|
666
|
+
//# sourceMappingURL=mongo-pagination.CHC5agt7.cjs.map
|