@villedemontreal/mongo 6.7.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 (69) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +226 -0
  3. package/dist/src/config/configs.d.ts +16 -0
  4. package/dist/src/config/configs.js +26 -0
  5. package/dist/src/config/configs.js.map +1 -0
  6. package/dist/src/config/constants.d.ts +85 -0
  7. package/dist/src/config/constants.js +104 -0
  8. package/dist/src/config/constants.js.map +1 -0
  9. package/dist/src/config/init.d.ts +9 -0
  10. package/dist/src/config/init.js +24 -0
  11. package/dist/src/config/init.js.map +1 -0
  12. package/dist/src/config/mongooseConfigs.d.ts +73 -0
  13. package/dist/src/config/mongooseConfigs.js +107 -0
  14. package/dist/src/config/mongooseConfigs.js.map +1 -0
  15. package/dist/src/index.d.ts +6 -0
  16. package/dist/src/index.js +24 -0
  17. package/dist/src/index.js.map +1 -0
  18. package/dist/src/mongoClient.d.ts +19 -0
  19. package/dist/src/mongoClient.js +111 -0
  20. package/dist/src/mongoClient.js.map +1 -0
  21. package/dist/src/mongoUpdater.d.ts +103 -0
  22. package/dist/src/mongoUpdater.js +297 -0
  23. package/dist/src/mongoUpdater.js.map +1 -0
  24. package/dist/src/mongoUpdater.test.d.ts +1 -0
  25. package/dist/src/mongoUpdater.test.js +232 -0
  26. package/dist/src/mongoUpdater.test.js.map +1 -0
  27. package/dist/src/mongoUtils.d.ts +68 -0
  28. package/dist/src/mongoUtils.js +280 -0
  29. package/dist/src/mongoUtils.js.map +1 -0
  30. package/dist/src/mongoUtils.test.d.ts +1 -0
  31. package/dist/src/mongoUtils.test.js +24 -0
  32. package/dist/src/mongoUtils.test.js.map +1 -0
  33. package/dist/src/plugins/pagination/index.d.ts +11 -0
  34. package/dist/src/plugins/pagination/index.js +79 -0
  35. package/dist/src/plugins/pagination/index.js.map +1 -0
  36. package/dist/src/plugins/pagination/index.test.d.ts +1 -0
  37. package/dist/src/plugins/pagination/index.test.js +129 -0
  38. package/dist/src/plugins/pagination/index.test.js.map +1 -0
  39. package/dist/src/plugins/pagination/specs/IPaginateOptions.d.ts +51 -0
  40. package/dist/src/plugins/pagination/specs/IPaginateOptions.js +3 -0
  41. package/dist/src/plugins/pagination/specs/IPaginateOptions.js.map +1 -0
  42. package/dist/src/utils/logger.d.ts +11 -0
  43. package/dist/src/utils/logger.js +54 -0
  44. package/dist/src/utils/logger.js.map +1 -0
  45. package/dist/src/utils/testingConfigurations.d.ts +8 -0
  46. package/dist/src/utils/testingConfigurations.js +17 -0
  47. package/dist/src/utils/testingConfigurations.js.map +1 -0
  48. package/dist/tests/testingMongoUpdates/1.0.0.d.ts +5 -0
  49. package/dist/tests/testingMongoUpdates/1.0.0.js +27 -0
  50. package/dist/tests/testingMongoUpdates/1.0.0.js.map +1 -0
  51. package/dist/tests/testingMongoUpdates/1.0.1.d.ts +5 -0
  52. package/dist/tests/testingMongoUpdates/1.0.1.js +22 -0
  53. package/dist/tests/testingMongoUpdates/1.0.1.js.map +1 -0
  54. package/package.json +63 -0
  55. package/src/config/configs.ts +27 -0
  56. package/src/config/constants.ts +122 -0
  57. package/src/config/init.ts +23 -0
  58. package/src/config/mongooseConfigs.ts +178 -0
  59. package/src/index.ts +13 -0
  60. package/src/mongoClient.ts +122 -0
  61. package/src/mongoUpdater.test.ts +286 -0
  62. package/src/mongoUpdater.ts +423 -0
  63. package/src/mongoUtils.test.ts +23 -0
  64. package/src/mongoUtils.ts +322 -0
  65. package/src/plugins/pagination/index.test.ts +140 -0
  66. package/src/plugins/pagination/index.ts +96 -0
  67. package/src/plugins/pagination/specs/IPaginateOptions.ts +51 -0
  68. package/src/utils/logger.ts +53 -0
  69. package/src/utils/testingConfigurations.ts +13 -0
@@ -0,0 +1,322 @@
1
+ // ==========================================
2
+ // Mongo utilities
3
+ // ==========================================
4
+ import {
5
+ ApiErrorAndInfo,
6
+ createError,
7
+ createInvalidParameterError,
8
+ createNotFoundError,
9
+ createServerError,
10
+ globalConstants,
11
+ IApiError,
12
+ utils
13
+ } from '@villedemontreal/general-utils';
14
+ import { LogLevel } from '@villedemontreal/logger';
15
+ import * as fs from 'fs-extra';
16
+ import * as HttpStatusCodes from 'http-status-codes';
17
+ import * as _ from 'lodash';
18
+ import * as mocha from 'mocha';
19
+ import { MongoMemoryReplSet, MongoMemoryServer } from 'mongodb-memory-server-core';
20
+ import * as mongoose from 'mongoose';
21
+ import { constants } from './config/constants';
22
+
23
+ /**
24
+ * Mongo utilities
25
+ */
26
+ export class MongoUtils {
27
+ private mongoMemServer: MongoMemoryServer | MongoMemoryReplSet;
28
+ private useReplSet: boolean = false;
29
+
30
+ private mockgosseMockedFlag = 'mocked';
31
+
32
+ /**
33
+ * Mocks the Mongo databases created through Mongoose.
34
+ *
35
+ * IMPORTANT!!
36
+ * For the "mochaInstance" parameter to be the proper
37
+ * one, this function must be called from a *regular function* in a
38
+ * test file, not from an *arrow function*! For more informations,
39
+ * see : https://github.com/mochajs/mocha/issues/2018
40
+ *
41
+ * Note that, currently, once this mocking is in place,
42
+ * it can't be removed. You should only call this function
43
+ * during testing!
44
+ *
45
+ * If Mongoose is already mocked, the function does nothing.
46
+ *
47
+ * @param mocha from a Mocha test file, pass "this" as the value
48
+ * for this parameter or "null" from elsewhere.
49
+ *
50
+ * @param mongoServerVersion the Mongo version to use.
51
+ * Pass null (or undefined) to use the default version
52
+ * downloaded by mongodb-memory-server.
53
+ */
54
+ public async mockMongoose(
55
+ mochaInstance: mocha.Context,
56
+ mongoServerVersion: string,
57
+ useReplSet: boolean = false
58
+ ): Promise<MongoMemoryServer | MongoMemoryReplSet> {
59
+ // ==========================================
60
+ // We only mock the database if it's
61
+ // not already so.
62
+ // ==========================================
63
+ if (!this.mongoMemServer) {
64
+ // this.useReplSet = useReplSet;
65
+ // ==========================================
66
+ // Path to download the mocked Mongo server to.
67
+ // We make sure this folder is wihtin the application
68
+ // so when the application is mounted in a Docker
69
+ // container, the server will not be downloaded
70
+ // over and over.
71
+ // ==========================================
72
+ const downloadDirPath = constants.appRoot + '/temp/mockServer';
73
+ if (!fs.existsSync(downloadDirPath)) {
74
+ fs.mkdirsSync(downloadDirPath);
75
+ }
76
+
77
+ // ==========================================
78
+ // Data directory
79
+ // ==========================================
80
+ const dataRootPath = downloadDirPath + '/data';
81
+ if (!fs.existsSync(dataRootPath)) {
82
+ fs.mkdirsSync(dataRootPath);
83
+ } else {
84
+ await utils.clearDir(dataRootPath);
85
+ }
86
+ const dataPath = `${dataRootPath}/${Date.now()}`;
87
+ fs.mkdirsSync(dataPath);
88
+
89
+ // ==========================================
90
+ // Increases the Mocha timeout to allow more time to
91
+ // download the test server, if required.
92
+ // ==========================================
93
+ if (mochaInstance) {
94
+ if (!mochaInstance.timeout || !_.isFunction(mochaInstance.timeout)) {
95
+ throw new Error(
96
+ `The "mocha" parameter passed to the "mockMongoose(...)" method ` +
97
+ `doesn't seems to be of the correct type. Make sure the function in which you ` +
98
+ `call "mockMongoose(...)" is itself a *regular* function, not an *arrow* function. ` +
99
+ `Have a look at https://github.com/mochajs/mocha/issues/2018 for more infos.`
100
+ );
101
+ }
102
+
103
+ mochaInstance.timeout(120000);
104
+ }
105
+
106
+ // Voir https://www.npmjs.com/package/mongodb-memory-server pour les options
107
+ const memoryServerOption: any = {
108
+ instance: {
109
+ dbPath: dataPath // by default create in temp directory,
110
+ },
111
+
112
+ binary: {
113
+ version: mongoServerVersion ? mongoServerVersion : 'latest', // by default 'latest'
114
+ downloadDir: downloadDirPath // by default node_modules/.cache/mongodb-memory-server/mongodb-binaries
115
+ },
116
+ debug: false
117
+ };
118
+
119
+ if (useReplSet) {
120
+ const replSetOptions: any = {
121
+ ...memoryServerOption,
122
+ replSet: { count: 3 }
123
+ };
124
+ this.mongoMemServer = await MongoMemoryReplSet.create(replSetOptions);
125
+ } else {
126
+ // Create mock
127
+ this.mongoMemServer = await MongoMemoryServer.create(memoryServerOption);
128
+ }
129
+ }
130
+ return this.mongoMemServer;
131
+ }
132
+
133
+ /**
134
+ * Drop all mocked Mongo databases.
135
+ *
136
+ */
137
+ public async dropMockedDatabases(): Promise<void> {
138
+ if (this.mongoMemServer) {
139
+ await mongoose.disconnect();
140
+ await this.mongoMemServer.stop();
141
+ this.mongoMemServer = null;
142
+ }
143
+ }
144
+
145
+ public async getMockedServerPort(): Promise<number> {
146
+ const mongooseConnection = mongoose[this.mockgosseMockedFlag];
147
+ if (mongooseConnection && this.mongoMemServer && !this.useReplSet) {
148
+ return (this.mongoMemServer as MongoMemoryServer).instanceInfo.port;
149
+ }
150
+ if (mongooseConnection && this.mongoMemServer && this.useReplSet) {
151
+ const servers = (this.mongoMemServer as MongoMemoryReplSet).servers;
152
+ let port = null;
153
+ for (const serv of servers) {
154
+ if (serv.instanceInfo.instance.isInstancePrimary) {
155
+ port = serv.instanceInfo.port;
156
+ break;
157
+ }
158
+ }
159
+ return port;
160
+ }
161
+
162
+ return null;
163
+ }
164
+
165
+ /**
166
+ * Validates if an object is a Mongoose
167
+ * error.
168
+ */
169
+ public isMongooseError(obj: any): boolean {
170
+ if (_.isEmpty(obj)) {
171
+ return false;
172
+ }
173
+
174
+ if (_.isEmpty(obj.errors) && 'kind' in obj && 'path' in obj && 'message' in obj) {
175
+ return true;
176
+ }
177
+
178
+ Object.keys(obj.errors).forEach(errorKey => {
179
+ const errorObject = obj.errors[errorKey];
180
+
181
+ if (!('kind' in errorObject) || !('path' in errorObject) || !('message' in errorObject)) {
182
+ return false;
183
+ }
184
+ return true;
185
+ });
186
+ return true;
187
+ }
188
+
189
+ /**
190
+ * Validates if an object is a plain
191
+ * Mongo error.
192
+ */
193
+ public isPlainMongoError(errorObject: any): boolean {
194
+ if (_.isEmpty(errorObject)) {
195
+ return false;
196
+ }
197
+
198
+ if (
199
+ !('code' in errorObject) ||
200
+ !('name' in errorObject) ||
201
+ !('errmsg' in errorObject) ||
202
+ !('message' in errorObject)
203
+ ) {
204
+ return false;
205
+ }
206
+ return true;
207
+ }
208
+
209
+ /**
210
+ * Creates an Api eror from an error thrown
211
+ * by Mongoose.
212
+ *
213
+ * If the specified error is not a Mongo/Mongoose error, it
214
+ * will be returned as is.
215
+ *
216
+ * @param error the Mongo/Mongoose error object
217
+ * @param publicMessage a public message to be used in the
218
+ * generated error. Fopr example : "The user is invalid".
219
+ */
220
+ public convertMongoOrMongooseErrorToApiError(err: any, publicMessage: string): ApiErrorAndInfo | any {
221
+ if (!err) {
222
+ return createServerError('Empty error object');
223
+ }
224
+
225
+ let errClean = err;
226
+
227
+ // ==========================================
228
+ // Plain Mongo error
229
+ // ==========================================
230
+ if (this.isPlainMongoError(errClean)) {
231
+ // ==========================================
232
+ // Duplicate key error?
233
+ // We create an error with a "Conflit" http status
234
+ // code.
235
+ // ==========================================
236
+ if (errClean.code === constants.mongo.mongoErrorCodes.DUPLICATE_KEY) {
237
+ return createError(globalConstants.errors.apiGeneralErrors.codes.DUPLICATE_KEY, publicMessage)
238
+ .httpStatus(HttpStatusCodes.CONFLICT)
239
+ .publicMessage(publicMessage)
240
+ .logLevel(LogLevel.INFO)
241
+ .logStackTrace(false)
242
+ .build();
243
+ }
244
+
245
+ // ==========================================
246
+ // Unmanaged Mongo error, we return it as is.
247
+ // ==========================================
248
+ return errClean;
249
+ }
250
+ if (this.isMongooseError(errClean)) {
251
+ // ==========================================
252
+ // Mongoose error
253
+ // ==========================================
254
+ if (_.isEmpty(errClean.errors)) {
255
+ // ==========================================
256
+ // Invalid id
257
+ // ==========================================
258
+ if (
259
+ errClean.kind === constants.mongo.mongoose.errorKinds.OBJECT_ID &&
260
+ errClean.name === constants.mongo.mongoose.errorNames.CAST_ERROR
261
+ ) {
262
+ throw createNotFoundError('Invalid ObjectId id', publicMessage);
263
+ } else {
264
+ const error = errClean;
265
+ errClean = {
266
+ errors: [error]
267
+ };
268
+ }
269
+ }
270
+
271
+ let errorDetails: IApiError[];
272
+ if (errClean.errors && !_.isEmpty(errClean.errors)) {
273
+ errorDetails = [];
274
+ Object.keys(errClean.errors).forEach(errorKey => {
275
+ const errorMessage = errClean.errors[errorKey];
276
+
277
+ const errorDetail: IApiError = {
278
+ code: errorMessage.kind,
279
+ target: errorMessage.path,
280
+ message: errorMessage.message
281
+ };
282
+ errorDetails.push(errorDetail);
283
+ });
284
+ }
285
+ throw createInvalidParameterError(publicMessage, errorDetails);
286
+ } else {
287
+ // ==========================================
288
+ // Not a Mongo or Mongoose error, we return it
289
+ // as is.
290
+ // ==========================================
291
+ return errClean;
292
+ }
293
+ }
294
+
295
+ /**
296
+ * Converts a Mongoose Document to a plain Pojo.
297
+ */
298
+ public convertMongooseDocumentToPlainObject<T>(document: T & mongoose.Document): T {
299
+ if (!document) {
300
+ return document;
301
+ }
302
+
303
+ const pojo: T = document.toObject() as T;
304
+
305
+ // ==========================================
306
+ // Converts the "_id" property to "id"
307
+ // ==========================================
308
+ // tslint:disable-next-line:no-string-literal
309
+ pojo['id'] = pojo['_id'].toString();
310
+ // tslint:disable-next-line:no-string-literal
311
+ delete pojo['_id'];
312
+
313
+ // ==========================================
314
+ // Removes the "__v"
315
+ // ==========================================
316
+ // tslint:disable-next-line:no-string-literal
317
+ delete pojo['__v'];
318
+
319
+ return pojo;
320
+ }
321
+ }
322
+ export let mongoUtils: MongoUtils = new MongoUtils();
@@ -0,0 +1,140 @@
1
+ // Some way of using chai requires disabling this rule:
2
+ // tslint:disable:no-unused-expression
3
+
4
+ import { IPaginatedResult } from '@villedemontreal/general-utils';
5
+ import * as chai from 'chai';
6
+ import * as mongoose from 'mongoose';
7
+ import { constants } from '../../config/constants';
8
+ import { mongoUtils } from '../../mongoUtils';
9
+ import { mongoosePaginate } from './index';
10
+
11
+ const expect = chai.expect;
12
+
13
+ const authorSchema = new mongoose.Schema({ name: String });
14
+ const authorModel = mongoose.model('Author', authorSchema);
15
+
16
+ const bookSchema = new mongoose.Schema({
17
+ title: String,
18
+ date: Date,
19
+ author: {
20
+ type: String,
21
+ ref: 'Author'
22
+ }
23
+ });
24
+
25
+ bookSchema.plugin(mongoosePaginate);
26
+ const bookModel: any = mongoose.model('Book', bookSchema);
27
+
28
+ describe('plugin pagination', () => {
29
+ before(async function() {
30
+ this.timeout(120000);
31
+
32
+ // Makes sure Mongoose is mocked, but not in Jenkins as we will start a dedicated mongodb container.
33
+ const mockedDb = await mongoUtils.mockMongoose(this, constants.testsConfig.mockServer.serverVersion);
34
+ const connString = mockedDb.getUri();
35
+ await mongoose.connect(connString, { useNewUrlParser: true });
36
+ });
37
+
38
+ before(async function() {
39
+ this.timeout(10000);
40
+ let book;
41
+ const books: any = [];
42
+ const date = new Date();
43
+ return authorModel.create({ name: 'Arthur Conan Doyle' }).then(author => {
44
+ for (let i = 1; i <= 100; i++) {
45
+ book = new bookModel({
46
+ title: 'Book #' + i,
47
+ date: new Date(date.getTime() + i),
48
+ author: author._id
49
+ });
50
+ books.push(book);
51
+ }
52
+ return bookModel.create(books);
53
+ });
54
+ });
55
+
56
+ it('returns promise', () => {
57
+ const promise = bookModel.paginate();
58
+ expect(promise.then).to.be.an.instanceof(Function);
59
+ });
60
+
61
+ it('calls callback', done => {
62
+ bookModel.paginate({}, {}, (err: Error, result: IPaginatedResult<any>) => {
63
+ expect(err).to.be.null;
64
+ expect(result).to.be.an.instanceOf(Object);
65
+ done();
66
+ });
67
+ });
68
+
69
+ describe('paginates', () => {
70
+ it('with criteria', () => {
71
+ return bookModel.paginate({ title: 'Book #10' }).then((result: IPaginatedResult<any>) => {
72
+ expect(result.items).to.have.length(1);
73
+ expect(result.items[0].title).to.equal('Book #10');
74
+ });
75
+ });
76
+ it('with default options (limit=10, lean=false)', () => {
77
+ return bookModel.paginate().then((result: IPaginatedResult<any>) => {
78
+ expect(result.items).to.have.length(10);
79
+ expect(result.items[0]).to.be.an.instanceof((mongoose as any).Document);
80
+ expect(result.paging.totalCount).to.equal(100);
81
+ expect(result.paging.limit).to.equal(10);
82
+ expect(result.paging.offset).to.equal(0);
83
+ });
84
+ });
85
+ it('with offset and limit', () => {
86
+ return bookModel.paginate({}, { offset: 30, limit: 20 }).then((result: IPaginatedResult<any>) => {
87
+ expect(result.items).to.have.length(20);
88
+ expect(result.paging.totalCount).to.equal(100);
89
+ expect(result.paging.limit).to.equal(20);
90
+ expect(result.paging.offset).to.equal(30);
91
+ });
92
+ });
93
+ it('with zero limit', () => {
94
+ return bookModel.paginate({}, { page: 1, limit: 0 }).then((result: IPaginatedResult<any>) => {
95
+ expect(result.items).to.have.length(0);
96
+ expect(result.paging.totalCount).to.equal(100);
97
+ expect(result.paging.limit).to.equal(0);
98
+ });
99
+ });
100
+ it('with select', () => {
101
+ return bookModel.paginate({}, { select: 'title' }).then((result: IPaginatedResult<any>) => {
102
+ expect(result.items[0].title).to.exist;
103
+ expect(result.items[0].date).to.not.exist;
104
+ });
105
+ });
106
+ it('with sort', () => {
107
+ return bookModel.paginate({}, { sort: { date: -1 } }).then((result: IPaginatedResult<any>) => {
108
+ expect(result.items[0].title).to.equal('Book #100');
109
+ });
110
+ });
111
+ it('with populate', () => {
112
+ return bookModel.paginate({}, { populate: 'author' }).then((result: IPaginatedResult<any>) => {
113
+ expect(result.items[0].author.name).to.equal('Arthur Conan Doyle');
114
+ });
115
+ });
116
+ describe('with lean', () => {
117
+ it('with default leanWithId=true', () => {
118
+ return bookModel.paginate({}, { lean: true }).then((result: IPaginatedResult<any>) => {
119
+ expect(result.items[0]).to.not.be.an.instanceof((mongoose as any).Document);
120
+ expect(result.items[0].id).to.exist;
121
+ expect(result.items[0]).to.not.have.property('_id');
122
+ });
123
+ });
124
+ it('with leanWithId=false', () => {
125
+ return bookModel.paginate({}, { lean: true, leanWithId: false }).then((result: IPaginatedResult<any>) => {
126
+ expect(result.items[0]).to.not.be.an.instanceof((mongoose as any).Document);
127
+ expect(result.items[0]).to.not.have.property('id');
128
+ });
129
+ });
130
+ });
131
+ });
132
+
133
+ after(done => {
134
+ mongoose.connection.db.dropDatabase(done);
135
+ });
136
+
137
+ after(done => {
138
+ mongoose.disconnect(done);
139
+ });
140
+ });
@@ -0,0 +1,96 @@
1
+ import { IPaginateOptions } from './specs/IPaginateOptions';
2
+
3
+ function paginate(q: any, options: IPaginateOptions, callback: (error: Error, result: any) => void) {
4
+ const optionsClean = PaginateBuilder.getOptions((paginate as any).options, options);
5
+
6
+ const query = q || {};
7
+ const promises = PaginateBuilder.executeQueries(this, query, optionsClean);
8
+
9
+ return PaginateBuilder.processResult(promises, optionsClean, callback);
10
+ }
11
+
12
+ /**
13
+ * @param {Schema} schema
14
+ */
15
+
16
+ export function mongoosePaginate(schema: any) {
17
+ schema.statics.paginate = paginate;
18
+ }
19
+
20
+ export class PaginateBuilder {
21
+ private static readonly defaultOptions = Object.freeze({
22
+ lean: false,
23
+ leanWithId: true,
24
+ limit: 10,
25
+ offset: 0
26
+ });
27
+
28
+ public static getOptions(...options: IPaginateOptions[]): IPaginateOptions {
29
+ return Object.assign({}, PaginateBuilder.defaultOptions, ...options);
30
+ }
31
+
32
+ public static executeQueries(model: any, query: any, options: IPaginateOptions): [Promise<any[]>, Promise<number>] {
33
+ const { select, sort, populate, lean, leanWithId, limit, offset } = options;
34
+ let itemsQuery: any;
35
+
36
+ if (leanWithId) {
37
+ // only to prevent "'leanWithId' is declared but its value is never read"
38
+ }
39
+
40
+ if (limit > 0) {
41
+ itemsQuery = model
42
+ .find(query)
43
+ .select(select)
44
+ .sort(sort)
45
+ .skip(offset)
46
+ .limit(limit)
47
+ .lean(lean);
48
+
49
+ if (populate) {
50
+ [].concat(populate).forEach(item => itemsQuery.populate(item));
51
+ }
52
+ }
53
+
54
+ return [itemsQuery && limit > 0 ? itemsQuery.exec() : Promise.resolve([]), model.countDocuments(query).exec()];
55
+ }
56
+
57
+ public static processResult(
58
+ promises: any[],
59
+ options: IPaginateOptions,
60
+ callback: (error: Error, result: any) => void
61
+ ): Promise<any> {
62
+ const { lean, leanWithId, limit, offset } = options;
63
+ return new Promise((resolve, reject) => {
64
+ Promise.all(promises).then(
65
+ data => {
66
+ const items = data[0] as any[];
67
+ const count = data[1] as number;
68
+ const result: any = { paging: { limit, offset, totalCount: count } };
69
+
70
+ if (lean && leanWithId) {
71
+ result.items = items.map((doc: any) => {
72
+ doc.id = String(doc._id);
73
+ delete doc._id;
74
+ delete doc.__v;
75
+ return doc;
76
+ });
77
+ } else {
78
+ result.items = items;
79
+ }
80
+
81
+ if (typeof callback === 'function') {
82
+ return callback(null, result);
83
+ }
84
+
85
+ resolve(result);
86
+ },
87
+ error => {
88
+ if (typeof callback === 'function') {
89
+ return callback(error, null);
90
+ }
91
+ reject(error);
92
+ }
93
+ );
94
+ });
95
+ }
96
+ }
@@ -0,0 +1,51 @@
1
+ export interface IPaginateOptions {
2
+ /**
3
+ * Fields to return (by default returns all fields)
4
+ * http://mongoosejs.com/docs/api.html#query_Query-select
5
+ * @type {string}
6
+ * @memberof IPaginateOptions
7
+ */
8
+ select?: string;
9
+ /**
10
+ * Sort order
11
+ * http://mongoosejs.com/docs/api.html#query_Query-sort
12
+ * @type {*}
13
+ * @memberof IPaginateOptions
14
+ */
15
+ sort?: any;
16
+ /**
17
+ * Paths which should be populated with other documents.
18
+ * http://mongoosejs.com/docs/api.html#query_Query-populate
19
+ * @type {string}
20
+ * @memberof IPaginateOptions
21
+ */
22
+ populate?: string;
23
+ /**
24
+ * Should return plain javascript objects instead of Mongoose documents?
25
+ * default false
26
+ * @type {boolean}
27
+ * @memberof IPaginateOptions
28
+ */
29
+ lean?: boolean;
30
+ /**
31
+ * If lean and leanWithId are true, adds id field with string representation of _id to every document
32
+ * default true
33
+ * @type {boolean}
34
+ * @memberof IPaginateOptions
35
+ */
36
+ leanWithId?: boolean;
37
+ /**
38
+ * Use offset to set skip position
39
+ * default 0
40
+ * @type {number}
41
+ * @memberof IPaginateOptions
42
+ */
43
+ offset?: number;
44
+ /**
45
+ * limit the items returned
46
+ * default 10
47
+ * @type {number}
48
+ * @memberof IPaginateOptions
49
+ */
50
+ limit?: number;
51
+ }
@@ -0,0 +1,53 @@
1
+ import { ILogger, initLogger, LazyLogger, Logger, LoggerConfigs, LogLevel } from '@villedemontreal/logger';
2
+ import { configs } from '../config/configs';
3
+
4
+ let testingLoggerLibInitialised = false;
5
+
6
+ /**
7
+ * Creates a Logger.
8
+ */
9
+ export function createLogger(name: string): ILogger {
10
+ // ==========================================
11
+ // We use a LazyLogger so the real Logger
12
+ // is only created when the first
13
+ // log is actually performed... At that point,
14
+ // our "configs.loggerCreator" configuration
15
+ // must have been set by the code using our library!
16
+ //
17
+ // This pattern allows calling code to import
18
+ // modules from us in which a logger is
19
+ // created in the global scope :
20
+ //
21
+ // let logger = createLogger('someName');
22
+ //
23
+ // Without a Lazy Logger, the library configurations
24
+ // would at that moment *not* have been set yet
25
+ // (by the calling code) and an Error would be thrown
26
+ // because the "configs.loggerCreator" is required.
27
+ // ==========================================
28
+ return new LazyLogger(name, (nameArg: string) => {
29
+ return configs.loggerCreator(nameArg);
30
+ });
31
+ }
32
+
33
+ function initTestingLoggerConfigs() {
34
+ const loggerConfig: LoggerConfigs = new LoggerConfigs(() => 'test-cid');
35
+ loggerConfig.setLogLevel(LogLevel.DEBUG);
36
+ initLogger(loggerConfig);
37
+ }
38
+
39
+ /**
40
+ * A Logger that uses a dummy cid provider.
41
+ *
42
+ * Only use this when running the tests!
43
+ */
44
+ export function getTestingLoggerCreator(): (name: string) => ILogger {
45
+ return (name: string): ILogger => {
46
+ if (!testingLoggerLibInitialised) {
47
+ initTestingLoggerConfigs();
48
+ testingLoggerLibInitialised = true;
49
+ }
50
+
51
+ return new Logger(name);
52
+ };
53
+ }
@@ -0,0 +1,13 @@
1
+ import { configs } from '../config/configs';
2
+ import { getTestingLoggerCreator } from '../utils/logger';
3
+
4
+ /**
5
+ * Call this when your need to set
6
+ * *Testing* configurations to the current
7
+ * library, without the need for a calling code
8
+ * to do so.
9
+ *
10
+ */
11
+ export function setTestingConfigurations(): void {
12
+ configs.setLoggerCreator(getTestingLoggerCreator());
13
+ }