@lenne.tech/nest-server 10.0.4 → 10.0.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lenne.tech/nest-server",
3
- "version": "10.0.4",
3
+ "version": "10.0.6",
4
4
  "description": "Modern, fast, powerful Node.js web framework in TypeScript based on Nest with a GraphQL API and a connection to MongoDB (or other databases).",
5
5
  "keywords": [
6
6
  "node",
@@ -71,7 +71,7 @@
71
71
  "@nestjs/mongoose": "10.0.1",
72
72
  "@nestjs/passport": "10.0.0",
73
73
  "@nestjs/platform-express": "10.1.3",
74
- "@nestjs/schedule": "3.0.1",
74
+ "@nestjs/schedule": "3.0.2",
75
75
  "@nestjs/terminus": "10.0.1",
76
76
  "apollo-server-core": "3.11.1",
77
77
  "apollo-server-express": "3.11.1",
@@ -108,30 +108,30 @@
108
108
  "devDependencies": {
109
109
  "@babel/plugin-proposal-private-methods": "7.18.6",
110
110
  "@compodoc/compodoc": "1.1.21",
111
- "@lenne.tech/eslint-config-ts": "0.0.8",
111
+ "@lenne.tech/eslint-config-ts": "0.0.9",
112
112
  "@nestjs/cli": "10.1.11",
113
- "@nestjs/schematics": "10.0.1",
113
+ "@nestjs/schematics": "10.0.2",
114
114
  "@nestjs/testing": "10.1.3",
115
115
  "@swc/cli": "0.1.62",
116
- "@swc/core": "1.3.73",
117
- "@swc/jest": "0.2.27",
116
+ "@swc/core": "1.3.76",
117
+ "@swc/jest": "0.2.28",
118
118
  "@types/compression": "1.7.2",
119
119
  "@types/cookie-parser": "1.4.3",
120
120
  "@types/cron": "2.0.1",
121
121
  "@types/ejs": "3.1.2",
122
122
  "@types/express": "4.17.17",
123
123
  "@types/jest": "29.5.3",
124
- "@types/lodash": "4.14.196",
124
+ "@types/lodash": "4.14.197",
125
125
  "@types/multer": "1.4.7",
126
- "@types/node": "20.4.5",
126
+ "@types/node": "20.4.9",
127
127
  "@types/nodemailer": "6.4.9",
128
128
  "@types/passport": "1.0.12",
129
129
  "@types/supertest": "2.0.12",
130
- "@typescript-eslint/eslint-plugin": "6.2.1",
131
- "@typescript-eslint/parser": "6.2.1",
130
+ "@typescript-eslint/eslint-plugin": "6.3.0",
131
+ "@typescript-eslint/parser": "6.3.0",
132
132
  "coffeescript": "2.7.0",
133
133
  "eslint": "8.46.0",
134
- "eslint-config-prettier": "8.9.0",
134
+ "eslint-config-prettier": "9.0.0",
135
135
  "eslint-plugin-unused-imports": "3.0.0",
136
136
  "find-file-up": "2.0.1",
137
137
  "grunt": "1.6.1",
@@ -143,7 +143,7 @@
143
143
  "jest": "29.6.2",
144
144
  "npm-watch": "0.11.0",
145
145
  "pm2": "5.3.0",
146
- "prettier": "3.0.0",
146
+ "prettier": "3.0.1",
147
147
  "pretty-quick": "3.1.3",
148
148
  "supertest": "6.3.3",
149
149
  "ts-jest": "29.1.1",
@@ -90,8 +90,10 @@ export function findFilter(options?: {
90
90
 
91
91
  // Optimizations
92
92
  if (!filterOptions[config.type].length) {
93
+ // If there are no conditions, return an empty object
93
94
  filterOptions = {};
94
95
  } else if (filterOptions[config.type].length === 1) {
96
+ // if there is only one condition, integrate it directly into the filter options
95
97
  const additionalProperties = filterOptions[config.type][0];
96
98
  delete filterOptions[config.type];
97
99
  assignPlain(filterOptions, additionalProperties);
@@ -150,22 +152,29 @@ export function generateFilterQuery<T = any>(
150
152
  // Process single filter
151
153
  if (filter.singleFilter) {
152
154
  // Init variables
153
- const { not, options, field, convertToObjectId, isReference } = filter.singleFilter;
155
+ const { not, options, convertToObjectId, isReference } = filter.singleFilter;
156
+ let field = filter.singleFilter.field;
154
157
  let value = filter.singleFilter.value;
155
158
 
156
- // Convert value to object ID(s)
159
+ // Convert value to object ID(s), but don't change the name or the filter itself
157
160
  if (convertToObjectId || isReference) {
158
161
  value = getObjectIds(value);
159
162
 
160
163
  // Check if value is a string ID and automatic ObjectID filtering is activated
161
164
  } else if (config.automaticObjectIdFiltering && checkStringIds(value)) {
162
- // Set both the string filter and the ObjectID filtering in an OR construction
163
- const alternativeQuery = clone(filter.singleFilter, { circles: false });
164
- alternativeQuery.value = getObjectIds(value);
165
- const conf = Object.assign({}, config, { automaticObjectIdFiltering: false });
166
- return {
167
- $or: [generateFilterQuery(filter, conf), generateFilterQuery({ singleFilter: alternativeQuery }, conf)],
168
- };
165
+ if (field === 'id') {
166
+ // Replace field name id with _id and convert value to ObjectId
167
+ field = '_id';
168
+ value = getObjectIds(value);
169
+ } else {
170
+ // For every other fields set both the string filter and the ObjectID filtering in an OR construction
171
+ const alternativeQuery = clone(filter.singleFilter, { circles: false });
172
+ alternativeQuery.value = getObjectIds(value);
173
+ const conf = Object.assign({}, config, { automaticObjectIdFiltering: false });
174
+ return {
175
+ $or: [generateFilterQuery(filter, conf), generateFilterQuery({ singleFilter: alternativeQuery }, conf)],
176
+ };
177
+ }
169
178
  }
170
179
 
171
180
  // Convert filter
@@ -1,3 +1,5 @@
1
+ import * as inspector from 'inspector';
2
+ import * as util from 'util';
1
3
  import { BadRequestException, UnauthorizedException } from '@nestjs/common';
2
4
  import { plainToInstance } from 'class-transformer';
3
5
  import { validate } from 'class-validator';
@@ -323,9 +325,8 @@ export function checkAndGetDate(input: any): Date {
323
325
  * Clone object
324
326
  * @param object Any object
325
327
  * @param options Finetuning of rfdc cloning
326
- * @param options.proto Copy prototype properties as well as own properties into the new object.
327
- * It's marginally faster to allow enumerable properties on the prototype to be copied into the
328
- * cloned object (not onto it's prototype, directly onto the object).
328
+ * @param options.checkResult Whether to compare object and cloned object via JSON.stringify and try alternative cloning
329
+ * methods if they are not equal
329
330
  * @param options.circles Keeping track of circular references will slow down performance with an additional 25% overhead.
330
331
  * Even if an object doesn't have any circular references, the tracking overhead is the cost.
331
332
  * By default if an object with a circular reference is passed to rfdc, it will throw
@@ -333,26 +334,48 @@ export function checkAndGetDate(input: any): Date {
333
334
  * circular references in the object. If performance is important, try removing the circular
334
335
  * reference from the object (set to undefined) and then add it back manually after cloning
335
336
  * instead of using this option.
337
+ * @param options.debug Whether to shoe console.debug messages
338
+ * @param options.proto Copy prototype properties as well as own properties into the new object.
339
+ * It's marginally faster to allow enumerable properties on the prototype to be copied into the
340
+ * cloned object (not onto it's prototype, directly onto the object).
336
341
  */
337
- export function clone(object: any, options?: { proto?: boolean; circles?: boolean }) {
342
+ export function clone(object: any, options?: { checkResult?: boolean; circles?: boolean; proto?: boolean }) {
338
343
  const config = {
339
- proto: false,
344
+ checkResult: true,
340
345
  circles: true,
346
+ debug: inspector.url() !== undefined,
347
+ proto: false,
341
348
  ...options,
342
349
  };
350
+
343
351
  try {
344
- return rfdc(config)(object);
352
+ const cloned = rfdc(config)(object);
353
+ if (config.checkResult && !util.isDeepStrictEqual(object, cloned)) {
354
+ throw new Error('Cloned object differs from original object');
355
+ }
356
+ return cloned;
345
357
  } catch (e) {
346
- console.debug(e, config, object, 'automatic try to use rfdc with circles');
347
358
  if (!config.circles) {
359
+ if (config.debug) {
360
+ console.debug(e, config, object, 'automatic try to use rfdc with circles');
361
+ }
348
362
  try {
349
- return rfdc({ ...config, ...{ circles: true } })(object);
363
+ const clonedWithCircles = rfdc({ ...config, ...{ circles: true } })(object);
364
+ if (config.checkResult && !util.isDeepStrictEqual(object, clonedWithCircles)) {
365
+ throw new Error('Cloned object differs from original object');
366
+ }
367
+ return clonedWithCircles;
350
368
  } catch (e) {
351
- console.debug(e, 'rfcd with circles did not work automatic use of _.clone!');
352
- return _.clone(object);
369
+ if (config.debug) {
370
+ console.debug(e, 'rfcd with circles did not work => automatic use of _.clone!');
371
+ }
372
+ return _.cloneDeep(object);
353
373
  }
354
374
  } else {
355
- return _.clone(object);
375
+ if (config.debug) {
376
+ console.debug(e, config, object, 'automatic try to use _.clone instead rfdc');
377
+ }
378
+ return _.cloneDeep(object);
356
379
  }
357
380
  }
358
381
  }
@@ -60,7 +60,9 @@ export interface IJwt {
60
60
  export interface IServerOptions {
61
61
  /**
62
62
  * Automatically detect ObjectIds in string values in FilterQueries
63
- * and expand them as OR query with string and ObjectId
63
+ * and expand them as OR query with string and ObjectId.
64
+ * Fields with the name "id" are renamed to "_id" and the value is converted to ObjectId,
65
+ * without changing the filter into an OR combined filter.
64
66
  * See generateFilterQuery in Filter helper (src/core/common/helpers/filter.helper.ts)
65
67
  */
66
68
  automaticObjectIdFiltering?: boolean;
@@ -319,6 +321,11 @@ export interface IServerOptions {
319
321
  * Configuration for Mongoose
320
322
  */
321
323
  mongoose?: {
324
+
325
+ /**
326
+ * Collation allows users to specify language-specific rules for string comparison,
327
+ * such as rules for letter-case and accent marks.
328
+ */
322
329
  collation?: CollationOptions;
323
330
 
324
331
  /**
@@ -336,6 +343,15 @@ export interface IServerOptions {
336
343
  * Mongoose module options
337
344
  */
338
345
  options?: MongooseModuleOptions;
346
+
347
+ /**
348
+ * Mongoose supports a separate strictQuery option to avoid strict mode for query filters.
349
+ * This is because empty query filters cause Mongoose to return all documents in the model, which can cause issues.
350
+ * See: https://github.com/Automattic/mongoose/issues/10763
351
+ * and: https://mongoosejs.com/docs/guide.html#strictQuery
352
+ * default: false
353
+ */
354
+ strictQuery?: boolean;
339
355
  };
340
356
 
341
357
  /**
@@ -5,6 +5,7 @@ import { GraphQLModule } from '@nestjs/graphql';
5
5
  import { MongooseModule } from '@nestjs/mongoose';
6
6
  import { Context } from 'apollo-server-core';
7
7
  import graphqlUploadExpress = require('graphql-upload/graphqlUploadExpress.js');
8
+ import mongoose from 'mongoose';
8
9
  import { merge } from './core/common/helpers/config.helper';
9
10
  import { IServerOptions } from './core/common/interfaces/server-options.interface';
10
11
  import { MapAndValidatePipe } from './core/common/pipes/map-and-validate.pipe';
@@ -154,6 +155,11 @@ export class CoreModule implements NestModule {
154
155
  providers.push(ModelDocService);
155
156
  }
156
157
 
158
+ // Set strict query to false by default
159
+ // See: https://github.com/Automattic/mongoose/issues/10763
160
+ // and: https://mongoosejs.com/docs/guide.html#strictQuery
161
+ mongoose.set('strictQuery', config.mongoose.strictQuery || false);
162
+
157
163
  const imports: any[] = [
158
164
  MongooseModule.forRoot(config.mongoose.uri, config.mongoose.options),
159
165
  GraphQLModule.forRootAsync<ApolloDriverConfig>(