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