@lenne.tech/nest-server 11.4.8 → 11.6.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lenne.tech/nest-server",
3
- "version": "11.4.8",
3
+ "version": "11.6.0",
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",
@@ -25,6 +25,14 @@
25
25
  "docs:ci": "ts-node ./scripts/init-server.ts && npm run docs:bootstrap && compodoc -p tsconfig.json",
26
26
  "format": "prettier --write 'src/**/*.ts'",
27
27
  "format:staged": "pretty-quick --staged",
28
+ "jest": "npm run jest:e2e",
29
+ "jest:ci": "NODE_ENV=local jest --config jest-e2e.json --ci --forceExit",
30
+ "jest:cov": "NODE_ENV=local jest --coverage",
31
+ "jest:debug": "NODE_ENV=local node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
32
+ "jest:e2e": "NODE_ENV=local jest --config jest-e2e.json --forceExit",
33
+ "jest:e2e-cov": "NODE_ENV=local jest --config jest-e2e.json --coverage --forceExit",
34
+ "jest:e2e-doh": "NODE_ENV=local jest --config jest-e2e.json --forceExit --detectOpenHandles",
35
+ "jest:watch": "NODE_ENV=local jest --watch",
28
36
  "lint": "eslint '{src,apps,libs,tests}/**/*.{ts,js}' --cache",
29
37
  "lint:fix": "eslint '{src,apps,libs,tests}/**/*.{ts,js}' --fix --cache",
30
38
  "prestart:prod": "npm run build",
@@ -42,17 +50,18 @@
42
50
  "start:dev:swc": "nest start -b swc -w --type-check",
43
51
  "start:local": "NODE_ENV=local nodemon",
44
52
  "start:local:swc": "NODE_ENV=local nest start -b swc -w --type-check",
45
- "test": "npm run test:e2e",
46
- "test:cov": "NODE_ENV=local jest --coverage",
47
- "test:debug": "NODE_ENV=local node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
48
- "test:e2e": "NODE_ENV=local jest --config jest-e2e.json --forceExit",
49
- "test:e2e-cov": "NODE_ENV=local jest --config jest-e2e.json --coverage --forceExit",
50
- "test:e2e-doh": "NODE_ENV=local jest --config jest-e2e.json --forceExit --detectOpenHandles",
51
- "test:ci": "NODE_ENV=local jest --config jest-e2e.json --ci --forceExit",
52
- "test:watch": "NODE_ENV=local jest --watch",
53
+ "test": "npm run vitest",
54
+ "test:ci": "npm run vitest:ci",
55
+ "test:e2e": "npm run vitest",
53
56
  "prepack": "npm run prestart:prod",
54
57
  "prepublishOnly": "npm run lint && npm run test:ci",
55
58
  "preversion": "npm run lint",
59
+ "vitest": "NODE_ENV=local vitest run --config vitest-e2e.config.ts",
60
+ "vitest:ci": "NODE_ENV=ci vitest run --config vitest-e2e.config.ts",
61
+ "vitest:cov": "NODE_ENV=local vitest run --coverage --config vitest-e2e.config.ts",
62
+ "vitest:watch": "NODE_ENV=local vitest --config vitest-e2e.config.ts",
63
+ "vitest:unit": "vitest run --config vitest.config.ts",
64
+ "test:unit:watch": "vitest --config vitest.config.ts",
56
65
  "watch": "npm-watch",
57
66
  "link:eslint": "yalc add @lenne.tech/eslint-config-ts && yalc link @lenne.tech/eslint-config-ts && npm install",
58
67
  "unlink:eslint": "yalc remove @lenne.tech/eslint-config-ts && npm install"
@@ -68,24 +77,22 @@
68
77
  "node": ">= 20"
69
78
  },
70
79
  "dependencies": {
71
- "@apollo/gateway": "2.12.0",
72
80
  "@getbrevo/brevo": "3.0.1",
73
81
  "@nestjs/apollo": "13.1.0",
74
- "@nestjs/common": "11.1.8",
75
- "@nestjs/core": "11.1.8",
76
- "@nestjs/graphql": "13.1.0",
77
- "@nestjs/jwt": "11.0.1",
78
- "@nestjs/mongoose": "11.0.3",
82
+ "@nestjs/common": "11.1.9",
83
+ "@nestjs/core": "11.1.9",
84
+ "@nestjs/graphql": "13.2.0",
85
+ "@nestjs/jwt": "11.0.2",
86
+ "@nestjs/mongoose": "11.0.4",
79
87
  "@nestjs/passport": "11.0.5",
80
- "@nestjs/platform-express": "11.1.8",
81
- "@nestjs/schedule": "6.0.1",
82
- "@nestjs/swagger": "11.2.1",
88
+ "@nestjs/platform-express": "11.1.9",
89
+ "@nestjs/schedule": "6.1.0",
90
+ "@nestjs/swagger": "11.2.3",
83
91
  "@nestjs/terminus": "11.0.0",
84
92
  "apollo-server-core": "3.13.0",
85
- "apollo-server-express": "3.13.0",
86
93
  "bcrypt": "6.0.0",
87
94
  "class-transformer": "0.5.1",
88
- "class-validator": "0.14.2",
95
+ "class-validator": "0.14.3",
89
96
  "compression": "1.8.1",
90
97
  "cookie-parser": "1.4.7",
91
98
  "dotenv": "17.2.3",
@@ -101,38 +108,38 @@
101
108
  "mongoose": "8.19.3",
102
109
  "multer": "2.0.2",
103
110
  "node-mailjet": "6.0.11",
104
- "nodemailer": "7.0.10",
111
+ "nodemailer": "7.0.11",
105
112
  "passport": "0.7.0",
106
113
  "passport-jwt": "4.0.1",
107
114
  "reflect-metadata": "0.2.2",
108
115
  "rfdc": "1.4.1",
109
116
  "rxjs": "7.8.2",
117
+ "ts-jest": "29.4.6",
110
118
  "yuml-diagram": "1.2.0"
111
119
  },
112
120
  "devDependencies": {
113
- "@babel/plugin-proposal-private-methods": "7.18.6",
114
121
  "@compodoc/compodoc": "1.1.32",
115
122
  "@lenne.tech/eslint-config-ts": "2.1.4",
116
- "@nestjs/cli": "11.0.10",
123
+ "@nestjs/cli": "11.0.14",
117
124
  "@nestjs/schematics": "11.0.9",
118
- "@nestjs/testing": "11.1.8",
125
+ "@nestjs/testing": "11.1.9",
119
126
  "@swc/cli": "0.7.9",
120
- "@swc/core": "1.15.0",
121
- "@swc/jest": "0.2.39",
127
+ "@swc/core": "1.15.3",
122
128
  "@types/compression": "1.8.1",
123
129
  "@types/cookie-parser": "1.4.10",
124
130
  "@types/ejs": "3.1.5",
125
131
  "@types/express": "4.17.21",
126
- "@types/jest": "30.0.0",
127
- "@types/lodash": "4.17.20",
132
+ "@types/lodash": "4.17.21",
128
133
  "@types/multer": "2.0.0",
129
- "@types/node": "24.10.0",
130
- "@types/nodemailer": "7.0.3",
134
+ "@types/node": "25.0.1",
135
+ "@types/nodemailer": "7.0.4",
131
136
  "@types/passport": "1.0.17",
132
137
  "@types/supertest": "6.0.3",
133
- "@typescript-eslint/eslint-plugin": "8.46.3",
134
- "@typescript-eslint/parser": "8.46.3",
135
- "coffeescript": "2.7.0",
138
+ "@typescript-eslint/eslint-plugin": "8.49.0",
139
+ "@typescript-eslint/parser": "8.49.0",
140
+ "@vitest/coverage-v8": "4.0.15",
141
+ "@vitest/ui": "4.0.15",
142
+ "ansi-colors": "4.1.3",
136
143
  "eslint": "9.39.1",
137
144
  "eslint-config-prettier": "10.1.8",
138
145
  "eslint-plugin-unused-imports": "4.3.0",
@@ -143,20 +150,23 @@
143
150
  "grunt-contrib-watch": "1.1.0",
144
151
  "grunt-sync": "0.8.2",
145
152
  "husky": "9.1.7",
146
- "jest": "30.2.0",
147
- "nodemon": "3.1.10",
153
+ "nodemon": "3.1.11",
148
154
  "npm-watch": "0.13.0",
149
- "pm2": "6.0.13",
150
- "prettier": "3.6.2",
155
+ "pm2": "6.0.14",
156
+ "prettier": "3.7.4",
151
157
  "pretty-quick": "4.2.2",
152
- "rimraf": "6.1.0",
158
+ "rimraf": "6.1.2",
153
159
  "supertest": "7.1.4",
154
- "ts-jest": "29.4.5",
155
160
  "ts-loader": "9.5.4",
156
161
  "ts-morph": "27.0.2",
157
162
  "ts-node": "10.9.2",
158
163
  "tsconfig-paths": "4.2.0",
159
164
  "typescript": "5.9.3",
165
+ "unplugin-swc": "1.5.9",
166
+ "vite": "7.2.7",
167
+ "vite-plugin-node": "7.0.0",
168
+ "vite-tsconfig-paths": "5.1.4",
169
+ "vitest": "4.0.15",
160
170
  "yalc": "1.0.0-pre.53"
161
171
  },
162
172
  "overrides": {
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Mongoose plugin to add a string 'id' field to documents based on the '_id' ObjectId.
3
+ * @param schema - The Mongoose schema to which the plugin will be applied.
4
+ */
5
+ export function mongooseIdPlugin(schema) {
6
+ schema.post(/.*/, (docs) => {
7
+ const docsArray = Array.isArray(docs) ? docs : [docs];
8
+ for (const doc of docsArray) {
9
+ if (doc?._id && typeof doc._id.toHexString === 'function') {
10
+ doc.id = doc._id.toHexString();
11
+ }
12
+ }
13
+ });
14
+ }
@@ -15,6 +15,18 @@ import { ICoreAuthUser } from '../interfaces/core-auth-user.interface';
15
15
  import { JwtPayload } from '../interfaces/jwt-payload.interface';
16
16
  import { CoreAuthUserService } from './core-auth-user.service';
17
17
 
18
+ /**
19
+ * Options for getResult method
20
+ */
21
+ export interface GetResultOptions {
22
+ /** Current refresh token (for renewal) */
23
+ currentRefreshToken?: string;
24
+ /** Additional data (deviceId, deviceDescription, etc.) */
25
+ data?: { [key: string]: any; deviceId?: string };
26
+ /** Service options including currentUser for securityCheck */
27
+ serviceOptions?: ServiceOptions;
28
+ }
29
+
18
30
  /**
19
31
  * CoreAuthService to handle user authentication
20
32
  */
@@ -71,16 +83,17 @@ export class CoreAuthService {
71
83
  /**
72
84
  * Refresh tokens
73
85
  */
74
- async refreshTokens(user: ICoreAuthUser, currentRefreshToken: string) {
86
+ async refreshTokens(user: ICoreAuthUser, currentRefreshToken: string, serviceOptions?: ServiceOptions) {
75
87
  // Create new tokens
76
88
  const { deviceDescription, deviceId } = this.decodeJwt(currentRefreshToken);
77
89
  const tokens = await this.createTokens(user.id, { deviceDescription, deviceId });
78
90
  tokens.refreshToken = await this.updateRefreshToken(user, currentRefreshToken, tokens.refreshToken);
79
91
 
80
- // Return
81
- return CoreAuthModel.map({
82
- ...tokens,
83
- user: await this.userService.prepareOutput(user),
92
+ // Return with currentUser set so securityCheck knows user is requesting own data
93
+ return this.getResult(user, {
94
+ currentRefreshToken,
95
+ data: { deviceDescription, deviceId },
96
+ serviceOptions: { ...serviceOptions, currentUser: user },
84
97
  });
85
98
  }
86
99
 
@@ -114,8 +127,11 @@ export class CoreAuthService {
114
127
  throw new UnauthorizedException('Wrong password');
115
128
  }
116
129
 
117
- // Return tokens and user
118
- return this.getResult(user, { deviceDescription, deviceId });
130
+ // Return tokens and user with currentUser set so securityCheck knows user is requesting own data
131
+ return this.getResult(user, {
132
+ data: { deviceDescription, deviceId },
133
+ serviceOptions: { ...serviceOptions, currentUser: user },
134
+ });
119
135
  }
120
136
 
121
137
  /**
@@ -138,8 +154,11 @@ export class CoreAuthService {
138
154
  // Set device ID
139
155
  const { deviceDescription, deviceId } = input;
140
156
 
141
- // Return tokens and user
142
- return this.getResult(user, { deviceDescription, deviceId });
157
+ // Return tokens and user with currentUser set so securityCheck knows user is requesting own data
158
+ return this.getResult(user, {
159
+ data: { deviceDescription, deviceId },
160
+ serviceOptions: { ...serviceOptions, currentUser: user },
161
+ });
143
162
  } catch (err) {
144
163
  if (err?.message === 'Unprocessable Entity') {
145
164
  throw new BadRequestException('Email address already in use');
@@ -171,12 +190,16 @@ export class CoreAuthService {
171
190
 
172
191
  /**
173
192
  * Rest result with user and tokens
193
+ *
194
+ * @param user - The authenticated user
195
+ * @param options - Optional configuration for result generation
196
+ * @param options.data - Additional data (deviceId, deviceDescription, etc.)
197
+ * @param options.currentRefreshToken - Current refresh token (for renewal)
198
+ * @param options.serviceOptions - Service options including currentUser for securityCheck
174
199
  */
175
- protected async getResult(
176
- user: ICoreAuthUser,
177
- data?: { [key: string]: any; deviceId?: string },
178
- currentRefreshToken?: string,
179
- ) {
200
+ protected async getResult(user: ICoreAuthUser, options?: GetResultOptions) {
201
+ const { currentRefreshToken, data, serviceOptions } = options || {};
202
+
180
203
  // Create new tokens
181
204
  const tokens = await this.createTokens(user.id, data);
182
205
 
@@ -184,9 +207,10 @@ export class CoreAuthService {
184
207
  tokens.refreshToken = await this.updateRefreshToken(user, currentRefreshToken, tokens.refreshToken, data);
185
208
 
186
209
  // Return tokens and user
210
+ // Pass serviceOptions to prepareOutput so currentUser is available for securityCheck
187
211
  return CoreAuthModel.map({
188
212
  ...tokens,
189
- user: await this.userService.prepareOutput(user),
213
+ user: await this.userService.prepareOutput(user, serviceOptions),
190
214
  });
191
215
  }
192
216
 
@@ -199,10 +223,10 @@ export class CoreAuthService {
199
223
  path += '.refresh';
200
224
  }
201
225
  return (
202
- this.configService.getFastButReadOnly(`${path}.signInOptions.secret`)
203
- || this.configService.getFastButReadOnly(`${path}.signInOptions.secretOrPrivateKey`)
204
- || this.configService.getFastButReadOnly(`${path}.secret`)
205
- || this.configService.getFastButReadOnly(`${path}.secretOrPrivateKey`)
226
+ this.configService.getFastButReadOnly(`${path}.signInOptions.secret`) ||
227
+ this.configService.getFastButReadOnly(`${path}.signInOptions.secretOrPrivateKey`) ||
228
+ this.configService.getFastButReadOnly(`${path}.secret`) ||
229
+ this.configService.getFastButReadOnly(`${path}.secretOrPrivateKey`)
206
230
  );
207
231
  }
208
232
 
@@ -13,6 +13,7 @@ import { CheckSecurityInterceptor } from './core/common/interceptors/check-secur
13
13
  import { IServerOptions } from './core/common/interfaces/server-options.interface';
14
14
  import { MapAndValidatePipe } from './core/common/pipes/map-and-validate.pipe';
15
15
  import { ComplexityPlugin } from './core/common/plugins/complexity.plugin';
16
+ import { mongooseIdPlugin } from './core/common/plugins/mongoose-id.plugin';
16
17
  import { ConfigService } from './core/common/services/config.service';
17
18
  import { EmailService } from './core/common/services/email.service';
18
19
  import { MailjetService } from './core/common/services/mailjet.service';
@@ -98,8 +99,8 @@ export class CoreModule implements NestModule {
98
99
  if (config.graphQl.enableSubscriptionAuth) {
99
100
  // get authToken from authorization header
100
101
  const headers = this.getHeaderFromArray(extra.request?.rawHeaders);
101
- const authToken: string
102
- = connectionParams?.Authorization?.split(' ')[1] ?? headers.Authorization?.split(' ')[1];
102
+ const authToken: string =
103
+ connectionParams?.Authorization?.split(' ')[1] ?? headers.Authorization?.split(' ')[1];
103
104
  if (authToken) {
104
105
  // verify authToken/getJwtPayLoad
105
106
  const payload = authService.decodeJwt(authToken);
@@ -148,7 +149,7 @@ export class CoreModule implements NestModule {
148
149
  mongoose: {
149
150
  options: {
150
151
  connectionFactory: (connection) => {
151
- connection.plugin(require('./core/common/plugins/mongoose-id.plugin'));
152
+ connection.plugin(mongooseIdPlugin);
152
153
  return connection;
153
154
  },
154
155
  },
@@ -177,11 +178,13 @@ export class CoreModule implements NestModule {
177
178
  EmailService,
178
179
  TemplateService,
179
180
  MailjetService,
180
-
181
- // Plugins
182
- ComplexityPlugin,
183
181
  ];
184
182
 
183
+ // Add ComplexityPlugin only if not in Vitest (Vitest has dual GraphQL loading issue)
184
+ if (!process.env.VITEST) {
185
+ providers.push(ComplexityPlugin);
186
+ }
187
+
185
188
  if (config.security?.checkResponseInterceptor ?? true) {
186
189
  // Check restrictions for output (models and output objects)
187
190
  providers.push({
@@ -225,9 +228,15 @@ export class CoreModule implements NestModule {
225
228
  imports.push(CoreHealthCheckModule);
226
229
  }
227
230
 
231
+ // Set exports
232
+ const exports: any[] = [ConfigService, EmailService, TemplateService, MailjetService];
233
+ if (!process.env.VITEST) {
234
+ exports.push(ComplexityPlugin);
235
+ }
236
+
228
237
  // Return dynamic module
229
238
  return {
230
- exports: [ConfigService, EmailService, TemplateService, MailjetService, ComplexityPlugin],
239
+ exports,
231
240
  imports,
232
241
  module: CoreModule,
233
242
  providers,
@@ -1,15 +0,0 @@
1
- export function mongooseIdPlugin(schema) {
2
- schema.post(['find', 'findOne', 'save', 'deleteOne'], (docs) => {
3
- if (!Array.isArray(docs)) {
4
- docs = [docs];
5
- }
6
-
7
- for (const doc of docs) {
8
- if (doc !== null && doc._id) {
9
- doc.id = doc._id.toHexString();
10
- }
11
- }
12
- });
13
- }
14
-
15
- module.exports = mongooseIdPlugin;