Skip to content

Commit 8f0bc8c

Browse files
committedJul 4, 2022
Code restructuring for subDocs pagination.
1 parent d0f24bf commit 8f0bc8c

File tree

3 files changed

+210
-208
lines changed

3 files changed

+210
-208
lines changed
 

‎src/index.js

+3-195
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
* @returns {Promise}
2222
*/
2323
const PaginationParametersHelper = require('./pagination-parameters');
24+
const paginateSubDocsHelper = require('./pagination-subdocs');
2425

2526
const defaultOptions = {
2627
customLabels: {
@@ -283,207 +284,14 @@ function paginate(query, options, callback) {
283284
});
284285
}
285286

286-
/**
287-
* Pagination process for sub-documents
288-
* internally, it would call `query.findOne`, return only one document
289-
*
290-
* @param {Object} query
291-
* @param {Object} options
292-
* @param {Function} callback
293-
*/
294-
function paginateSubDocs(query, options, callback) {
295-
/**
296-
* Populate sub documents with pagination fields
297-
*
298-
* @param {Object} query
299-
* @param {Object} populate origin populate option
300-
* @param {Object} option
301-
*/
302-
function getSubDocsPopulate(option) {
303-
// options properties for sub-documents pagination
304-
let { populate, page = 1, limit = 10 } = option;
305-
306-
if (!populate) {
307-
throw new Error('populate is required');
308-
}
309-
310-
const offset = (page - 1) * limit;
311-
option.offset = offset;
312-
const pagination = {
313-
skip: offset,
314-
limit: limit,
315-
};
316-
317-
if (typeof populate === 'string') {
318-
populate = {
319-
path: populate,
320-
...pagination,
321-
};
322-
} else if (typeof populate === 'object' && !Array.isArray(populate)) {
323-
populate = Object.assign(populate, pagination);
324-
}
325-
option.populate = populate;
326-
327-
return populate;
328-
}
329-
330-
function populateResult(result, populate, callback) {
331-
return result.populate(populate, callback);
332-
}
333-
334-
/**
335-
* Convert result of sub-docs list to pagination like docs
336-
*
337-
* @param {Object} result query result
338-
* @param {Object} option pagination option
339-
*/
340-
function constructDocs(paginatedResult, option) {
341-
let { populate, offset = 0, page = 1, limit = 10 } = option;
342-
343-
const path = populate.path;
344-
const count = option.count;
345-
const paginatedDocs = paginatedResult[path];
346-
347-
if (!paginatedDocs) {
348-
throw new Error(
349-
`Parse error! Cannot find key on result with path ${path}`
350-
);
351-
}
352-
353-
page = Math.ceil((offset + 1) / limit);
354-
355-
// set default meta
356-
const meta = {
357-
docs: paginatedDocs,
358-
totalDocs: count || 1,
359-
limit: limit,
360-
page: page,
361-
prevPage: null,
362-
nextPage: null,
363-
hasPrevPage: false,
364-
hasNextPage: false,
365-
};
366-
367-
const totalPages = limit > 0 ? Math.ceil(count / limit) || 1 : null;
368-
meta.totalPages = totalPages;
369-
meta.pagingCounter = (page - 1) * limit + 1;
370-
371-
// Set prev page
372-
if (page > 1) {
373-
meta.hasPrevPage = true;
374-
meta.prevPage = page - 1;
375-
} else if (page == 1 && offset !== 0) {
376-
meta.hasPrevPage = true;
377-
meta.prevPage = 1;
378-
}
379-
380-
// Set next page
381-
if (page < totalPages) {
382-
meta.hasNextPage = true;
383-
meta.nextPage = page + 1;
384-
}
385-
386-
if (limit == 0) {
387-
meta.limit = 0;
388-
meta.totalPages = 1;
389-
meta.page = 1;
390-
meta.pagingCounter = 1;
391-
}
392-
393-
Object.defineProperty(paginatedResult, path, {
394-
value: meta,
395-
writable: false,
396-
});
397-
}
398-
399-
options = Object.assign(options, {
400-
customLabels: defaultOptions.customLabels,
401-
});
402-
403-
// options properties for main document query
404-
const {
405-
populate,
406-
read = {},
407-
select = '',
408-
pagination = true,
409-
pagingOptions,
410-
} = options;
411-
412-
const mQuery = this.findOne(query, options.projection);
413-
414-
if (read && read.pref) {
415-
/**
416-
* Determines the MongoDB nodes from which to read.
417-
* @param read.pref one of the listed preference options or aliases
418-
* @param read.tags optional tags for this query
419-
*/
420-
mQuery.read(read.pref, read.tags);
421-
}
422-
423-
if (select) {
424-
mQuery.select(select);
425-
}
426-
427-
return new Promise((resolve, reject) => {
428-
mQuery
429-
.exec()
430-
.then((result) => {
431-
let newPopulate = [];
432-
433-
if (populate) {
434-
newPopulate.push(newPopulate);
435-
}
436-
437-
if (pagination && pagingOptions) {
438-
if (Array.isArray(pagingOptions)) {
439-
pagingOptions.forEach((option) => {
440-
let populate = getSubDocsPopulate(option);
441-
option.count = result[populate.path].length;
442-
newPopulate.push(populate);
443-
});
444-
} else {
445-
let populate = getSubDocsPopulate(pagingOptions);
446-
pagingOptions.count = result[populate.path].length;
447-
newPopulate.push(populate);
448-
}
449-
}
450-
451-
populateResult(result, newPopulate, (err, paginatedResult) => {
452-
if (err) {
453-
callback?.(err, null);
454-
reject(err);
455-
return;
456-
}
457-
// convert paginatedResult to pagination docs
458-
if (pagination && pagingOptions) {
459-
if (Array.isArray(pagingOptions)) {
460-
pagingOptions.forEach((option) => {
461-
constructDocs(paginatedResult, option);
462-
});
463-
} else {
464-
constructDocs(paginatedResult, pagingOptions);
465-
}
466-
}
467-
468-
callback?.(null, paginatedResult);
469-
resolve(paginatedResult);
470-
});
471-
})
472-
.catch((err) => {
473-
console.error(err.message);
474-
callback?.(err, null);
475-
});
476-
});
477-
}
478-
479287
/**
480288
* @param {Schema} schema
481289
*/
482290
module.exports = (schema) => {
483291
schema.statics.paginate = paginate;
484-
schema.statics.paginateSubDocs = paginateSubDocs;
292+
schema.statics.paginateSubDocs = paginateSubDocsHelper;
485293
};
486294

487295
module.exports.PaginationParameters = PaginationParametersHelper;
296+
module.exports.paginateSubDocs = paginateSubDocsHelper;
488297
module.exports.paginate = paginate;
489-
module.exports.paginateSubDocs = paginateSubDocs;

‎src/pagination-subdocs.js

+194
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
/**
2+
* Pagination process for sub-documents
3+
* internally, it would call `query.findOne`, return only one document
4+
*
5+
* @param {Object} query
6+
* @param {Object} options
7+
* @param {Function} callback
8+
*/
9+
function paginateSubDocs(query, options, callback) {
10+
/**
11+
* Populate sub documents with pagination fields
12+
*
13+
* @param {Object} query
14+
* @param {Object} populate origin populate option
15+
* @param {Object} option
16+
*/
17+
function getSubDocsPopulate(option) {
18+
// options properties for sub-documents pagination
19+
let { populate, page = 1, limit = 10 } = option;
20+
21+
if (!populate) {
22+
throw new Error('populate is required');
23+
}
24+
25+
const offset = (page - 1) * limit;
26+
option.offset = offset;
27+
const pagination = {
28+
skip: offset,
29+
limit: limit,
30+
};
31+
32+
if (typeof populate === 'string') {
33+
populate = {
34+
path: populate,
35+
...pagination,
36+
};
37+
} else if (typeof populate === 'object' && !Array.isArray(populate)) {
38+
populate = Object.assign(populate, pagination);
39+
}
40+
option.populate = populate;
41+
42+
return populate;
43+
}
44+
45+
function populateResult(result, populate, callback) {
46+
return result.populate(populate, callback);
47+
}
48+
49+
/**
50+
* Convert result of sub-docs list to pagination like docs
51+
*
52+
* @param {Object} result query result
53+
* @param {Object} option pagination option
54+
*/
55+
function constructDocs(paginatedResult, option) {
56+
let { populate, offset = 0, page = 1, limit = 10 } = option;
57+
58+
const path = populate.path;
59+
const count = option.count;
60+
const paginatedDocs = paginatedResult[path];
61+
62+
if (!paginatedDocs) {
63+
throw new Error(
64+
`Parse error! Cannot find key on result with path ${path}`
65+
);
66+
}
67+
68+
page = Math.ceil((offset + 1) / limit);
69+
70+
// set default meta
71+
const meta = {
72+
docs: paginatedDocs,
73+
totalDocs: count || 1,
74+
limit: limit,
75+
page: page,
76+
prevPage: null,
77+
nextPage: null,
78+
hasPrevPage: false,
79+
hasNextPage: false,
80+
};
81+
82+
const totalPages = limit > 0 ? Math.ceil(count / limit) || 1 : null;
83+
meta.totalPages = totalPages;
84+
meta.pagingCounter = (page - 1) * limit + 1;
85+
86+
// Set prev page
87+
if (page > 1) {
88+
meta.hasPrevPage = true;
89+
meta.prevPage = page - 1;
90+
} else if (page == 1 && offset !== 0) {
91+
meta.hasPrevPage = true;
92+
meta.prevPage = 1;
93+
}
94+
95+
// Set next page
96+
if (page < totalPages) {
97+
meta.hasNextPage = true;
98+
meta.nextPage = page + 1;
99+
}
100+
101+
if (limit == 0) {
102+
meta.limit = 0;
103+
meta.totalPages = 1;
104+
meta.page = 1;
105+
meta.pagingCounter = 1;
106+
}
107+
108+
Object.defineProperty(paginatedResult, path, {
109+
value: meta,
110+
writable: false,
111+
});
112+
}
113+
114+
// options = Object.assign(options, {
115+
// customLabels: defaultOptions.customLabels,
116+
// });
117+
118+
// options properties for main document query
119+
const {
120+
populate,
121+
read = {},
122+
select = '',
123+
pagination = true,
124+
pagingOptions,
125+
} = options;
126+
127+
const mQuery = this.findOne(query, options.projection);
128+
129+
if (read && read.pref) {
130+
/**
131+
* Determines the MongoDB nodes from which to read.
132+
* @param read.pref one of the listed preference options or aliases
133+
* @param read.tags optional tags for this query
134+
*/
135+
mQuery.read(read.pref, read.tags);
136+
}
137+
138+
if (select) {
139+
mQuery.select(select);
140+
}
141+
142+
return new Promise((resolve, reject) => {
143+
mQuery
144+
.exec()
145+
.then((result) => {
146+
let newPopulate = [];
147+
148+
if (populate) {
149+
newPopulate.push(newPopulate);
150+
}
151+
152+
if (pagination && pagingOptions) {
153+
if (Array.isArray(pagingOptions)) {
154+
pagingOptions.forEach((option) => {
155+
let populate = getSubDocsPopulate(option);
156+
option.count = result[populate.path].length;
157+
newPopulate.push(populate);
158+
});
159+
} else {
160+
let populate = getSubDocsPopulate(pagingOptions);
161+
pagingOptions.count = result[populate.path].length;
162+
newPopulate.push(populate);
163+
}
164+
}
165+
166+
populateResult(result, newPopulate, (err, paginatedResult) => {
167+
if (err) {
168+
callback(err, null);
169+
reject(err);
170+
return;
171+
}
172+
// convert paginatedResult to pagination docs
173+
if (pagination && pagingOptions) {
174+
if (Array.isArray(pagingOptions)) {
175+
pagingOptions.forEach((option) => {
176+
constructDocs(paginatedResult, option);
177+
});
178+
} else {
179+
constructDocs(paginatedResult, pagingOptions);
180+
}
181+
}
182+
183+
callback && callback(null, paginatedResult);
184+
resolve(paginatedResult);
185+
});
186+
})
187+
.catch((err) => {
188+
console.error(err.message);
189+
callback && callback(err, null);
190+
});
191+
});
192+
}
193+
194+
module.exports = paginateSubDocs;

‎tests/index.js

+13-13
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ let BookSchema = new mongoose.Schema({
2929
type: mongoose.Schema.ObjectId,
3030
ref: 'Author',
3131
},
32-
used: [
32+
user: [
3333
{
3434
type: mongoose.Schema.ObjectId,
3535
ref: 'User',
@@ -128,7 +128,7 @@ describe('mongoose-paginate', function () {
128128
title: 'Book #' + i,
129129
date: new Date(date.getTime() + i),
130130
author: author._id,
131-
used: users,
131+
user: users,
132132
loc: {
133133
type: 'Point',
134134
coordinates: [-10.97, 20.77],
@@ -538,24 +538,24 @@ describe('mongoose-paginate', function () {
538538
var option = {
539539
pagingOptions: {
540540
populate: {
541-
path: 'used',
541+
path: 'user',
542542
},
543543
page: 2,
544544
limit: 3,
545545
},
546546
};
547547

548548
return Book.paginateSubDocs(query, option).then((result) => {
549-
expect(result.used.docs).to.have.length(3);
550-
expect(result.used.totalPages).to.equal(4);
551-
expect(result.used.page).to.equal(2);
552-
expect(result.used.limit).to.equal(3);
553-
expect(result.used.hasPrevPage).to.equal(true);
554-
expect(result.used.hasNextPage).to.equal(true);
555-
expect(result.used.prevPage).to.equal(1);
556-
expect(result.used.nextPage).to.equal(3);
557-
expect(result.used.pagingCounter).to.equal(4);
558-
expect(result.used.docs[0].age).to.equal(3);
549+
expect(result.user.docs).to.have.length(3);
550+
expect(result.user.totalPages).to.equal(4);
551+
expect(result.user.page).to.equal(2);
552+
expect(result.user.limit).to.equal(3);
553+
expect(result.user.hasPrevPage).to.equal(true);
554+
expect(result.user.hasNextPage).to.equal(true);
555+
expect(result.user.prevPage).to.equal(1);
556+
expect(result.user.nextPage).to.equal(3);
557+
expect(result.user.pagingCounter).to.equal(4);
558+
expect(result.user.docs[0].age).to.equal(3);
559559
});
560560
});
561561

0 commit comments

Comments
 (0)
Please sign in to comment.