@lenne.tech/nest-server 11.14.0 → 11.15.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.14.0",
3
+ "version": "11.15.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",
@@ -53,6 +53,7 @@
53
53
  "vitest:unit": "vitest run --config vitest.config.ts",
54
54
  "test:unit:watch": "vitest --config vitest.config.ts",
55
55
  "test:types": "tsc --noEmit --skipLibCheck -p tests/types/tsconfig.json",
56
+ "test:cleanup": "find tests -type f \\( -name '*.txt' -o -name '*.bin' \\) -not -name '.gitkeep' -delete && echo 'Test artifacts cleaned up'",
56
57
  "watch": "npm-watch"
57
58
  },
58
59
  "repository": {
@@ -1099,28 +1099,32 @@ export interface IServerOptions {
1099
1099
  * Configuration of the GraphQL module
1100
1100
  * see https://docs.nestjs.com/graphql/quick-start
1101
1101
  * and https://www.apollographql.com/docs/apollo-server/api/apollo-server/
1102
+ *
1103
+ * Set to `false` to completely disable GraphQL (no GraphQLModule, no /graphql endpoint).
1102
1104
  */
1103
- graphQl?: {
1104
- /**
1105
- * Driver configuration for Apollo
1106
- */
1107
- driver?: ApolloDriverConfig;
1105
+ graphQl?:
1106
+ | false
1107
+ | {
1108
+ /**
1109
+ * Driver configuration for Apollo
1110
+ */
1111
+ driver?: ApolloDriverConfig;
1108
1112
 
1109
- /**
1110
- * Subscription authentication
1111
- */
1112
- enableSubscriptionAuth?: boolean;
1113
+ /**
1114
+ * Subscription authentication
1115
+ */
1116
+ enableSubscriptionAuth?: boolean;
1113
1117
 
1114
- /**
1115
- * Maximum complexity of GraphQL requests
1116
- */
1117
- maxComplexity?: number;
1118
+ /**
1119
+ * Maximum complexity of GraphQL requests
1120
+ */
1121
+ maxComplexity?: number;
1118
1122
 
1119
- /**
1120
- * Module options (forRootAsync)
1121
- */
1122
- options?: GqlModuleAsyncOptions;
1123
- };
1123
+ /**
1124
+ * Module options (forRootAsync)
1125
+ */
1126
+ options?: GqlModuleAsyncOptions;
1127
+ };
1124
1128
 
1125
1129
  /**
1126
1130
  * Whether to activate health check endpoints
@@ -168,9 +168,13 @@ export class CoreBetterAuthEmailVerificationService {
168
168
  url = this.buildFrontendVerificationUrl(token);
169
169
  }
170
170
 
171
- // Always log verification URL for development/testing (useful for capturing in tests)
171
+ // Log verification URL in non-production environments for debugging and test capture
172
172
  // Uses console.log directly to ensure reliable capture in test environments (Vitest)
173
173
  // NestJS Logger may buffer output which makes interception unreliable in tests
174
+ if (process.env.NODE_ENV !== 'production') {
175
+ // oxlint-disable-next-line no-console
176
+ console.log(`[EMAIL VERIFICATION] User: ${user.email}, URL: ${url}`);
177
+ }
174
178
 
175
179
  // Brevo template path: send via Brevo transactional API if configured
176
180
  if (this.config.brevoTemplateId && this.brevoService) {
@@ -42,11 +42,15 @@ import { CoreSystemSetupModule } from './core/modules/system-setup/core-system-s
42
42
  @Global()
43
43
  @Module({})
44
44
  export class CoreModule implements NestModule {
45
+ private static graphQlEnabled = true;
46
+
45
47
  /**
46
48
  * Integrate middleware, e.g. GraphQL upload handing for express
47
49
  */
48
50
  configure(consumer: MiddlewareConsumer) {
49
- consumer.apply(graphqlUploadExpress()).forRoutes('graphql');
51
+ if (CoreModule.graphQlEnabled) {
52
+ consumer.apply(graphqlUploadExpress()).forRoutes('graphql');
53
+ }
50
54
  }
51
55
 
52
56
  /**
@@ -138,24 +142,35 @@ export class CoreModule implements NestModule {
138
142
  };
139
143
  }
140
144
 
145
+ // Determine if GraphQL is enabled (false means explicitly disabled)
146
+ const isGraphQlEnabled = options.graphQl !== false;
147
+ CoreModule.graphQlEnabled = isGraphQlEnabled;
148
+
141
149
  // Check if autoRegister: false for IAM-only mode (project imports its own BetterAuth module)
142
150
  const rawBetterAuth = options?.betterAuth;
143
151
  const isAutoRegisterDisabledEarly = typeof rawBetterAuth === 'object' && rawBetterAuth?.autoRegister === false;
144
152
 
145
- // Build GraphQL driver configuration based on auth mode
146
- const graphQlDriverConfig = isIamOnlyMode
147
- ? isAutoRegisterDisabledEarly
148
- ? this.buildLazyIamGraphQlDriver(cors, options)
149
- : this.buildIamOnlyGraphQlDriver(cors, options)
150
- : this.buildLegacyGraphQlDriver(AuthService, AuthModule, cors, options);
153
+ // Build GraphQL driver configuration based on auth mode (only if GraphQL is enabled)
154
+ let graphQlDriverConfig = {};
155
+ if (isGraphQlEnabled) {
156
+ graphQlDriverConfig = isIamOnlyMode
157
+ ? isAutoRegisterDisabledEarly
158
+ ? this.buildLazyIamGraphQlDriver(cors, options)
159
+ : this.buildIamOnlyGraphQlDriver(cors, options)
160
+ : this.buildLegacyGraphQlDriver(AuthService, AuthModule, cors, options);
161
+ }
151
162
 
152
163
  const config: IServerOptions = merge(
153
164
  {
154
165
  env: 'develop',
155
- graphQl: {
156
- driver: graphQlDriverConfig,
157
- enableSubscriptionAuth: true,
158
- },
166
+ ...(isGraphQlEnabled
167
+ ? {
168
+ graphQl: {
169
+ driver: graphQlDriverConfig,
170
+ enableSubscriptionAuth: true,
171
+ },
172
+ }
173
+ : {}),
159
174
  mongoose: {
160
175
  options: {
161
176
  connectionFactory: (connection) => {
@@ -190,8 +205,8 @@ export class CoreModule implements NestModule {
190
205
  MailjetService,
191
206
  ];
192
207
 
193
- // Add ComplexityPlugin only if not in Vitest (Vitest has dual GraphQL loading issue)
194
- if (!process.env.VITEST) {
208
+ // Add ComplexityPlugin only if not in Vitest (Vitest has dual GraphQL loading issue) and GraphQL is enabled
209
+ if (!process.env.VITEST && isGraphQlEnabled) {
195
210
  providers.push(ComplexityPlugin);
196
211
  }
197
212
 
@@ -228,12 +243,15 @@ export class CoreModule implements NestModule {
228
243
  // and: https://mongoosejs.com/docs/guide.html#strictQuery
229
244
  mongoose.set('strictQuery', config.mongoose.strictQuery || false);
230
245
 
231
- const imports: any[] = [
232
- MongooseModule.forRoot(config.mongoose.uri, config.mongoose.options),
233
- GraphQLModule.forRootAsync<ApolloDriverConfig>(
234
- Object.assign({ driver: ApolloDriver }, config.graphQl.driver, config.graphQl.options),
235
- ),
236
- ];
246
+ const imports: any[] = [MongooseModule.forRoot(config.mongoose.uri, config.mongoose.options)];
247
+
248
+ if (isGraphQlEnabled && config.graphQl) {
249
+ imports.push(
250
+ GraphQLModule.forRootAsync<ApolloDriverConfig>(
251
+ Object.assign({ driver: ApolloDriver }, config.graphQl.driver, config.graphQl.options),
252
+ ),
253
+ );
254
+ }
237
255
 
238
256
  // Add ErrorCodeModule based on configuration
239
257
  // autoRegister defaults to true (backward compatible)
@@ -311,7 +329,7 @@ export class CoreModule implements NestModule {
311
329
 
312
330
  // Set exports
313
331
  const exports: any[] = [ConfigService, EmailService, TemplateService, MailjetService];
314
- if (!process.env.VITEST) {
332
+ if (!process.env.VITEST && isGraphQlEnabled) {
315
333
  exports.push(ComplexityPlugin);
316
334
  }
317
335
 
@@ -335,6 +353,8 @@ export class CoreModule implements NestModule {
335
353
  * This is the recommended mode for new projects.
336
354
  */
337
355
  private static buildIamOnlyGraphQlDriver(cors: object, options: Partial<IServerOptions>) {
356
+ // This method is only called when graphQl !== false, extract config with type narrowing
357
+ const graphQlOpts = typeof options?.graphQl === 'object' ? options.graphQl : undefined;
338
358
  return {
339
359
  imports: [CoreBetterAuthModule],
340
360
  inject: [CoreBetterAuthService, CoreBetterAuthUserMapper],
@@ -350,7 +370,7 @@ export class CoreModule implements NestModule {
350
370
  context: ({ extra }) => extra,
351
371
  onConnect: async (context: Context<any>) => {
352
372
  const { connectionParams, extra } = context;
353
- const enableAuth = options?.graphQl?.enableSubscriptionAuth ?? true;
373
+ const enableAuth = graphQlOpts?.enableSubscriptionAuth ?? true;
354
374
 
355
375
  if (enableAuth) {
356
376
  // Get headers from raw headers or connection params
@@ -385,7 +405,7 @@ export class CoreModule implements NestModule {
385
405
  },
386
406
  'subscriptions-transport-ws': {
387
407
  onConnect: async (connectionParams) => {
388
- const enableAuth = options?.graphQl?.enableSubscriptionAuth ?? true;
408
+ const enableAuth = graphQlOpts?.enableSubscriptionAuth ?? true;
389
409
 
390
410
  if (enableAuth) {
391
411
  const authToken: string = connectionParams?.Authorization?.split(' ')[1];
@@ -415,7 +435,7 @@ export class CoreModule implements NestModule {
415
435
  },
416
436
  },
417
437
  },
418
- options?.graphQl?.driver,
438
+ graphQlOpts?.driver,
419
439
  ),
420
440
  };
421
441
  }
@@ -430,6 +450,8 @@ export class CoreModule implements NestModule {
430
450
  * which happens after all modules are initialized.
431
451
  */
432
452
  private static buildLazyIamGraphQlDriver(cors: object, options: Partial<IServerOptions>) {
453
+ // This method is only called when graphQl !== false, extract config with type narrowing
454
+ const graphQlOpts = typeof options?.graphQl === 'object' ? options.graphQl : undefined;
433
455
  return {
434
456
  useFactory: async () =>
435
457
  Object.assign(
@@ -443,7 +465,7 @@ export class CoreModule implements NestModule {
443
465
  context: ({ extra }) => extra,
444
466
  onConnect: async (context: Context<any>) => {
445
467
  const { connectionParams, extra } = context;
446
- const enableAuth = options?.graphQl?.enableSubscriptionAuth ?? true;
468
+ const enableAuth = graphQlOpts?.enableSubscriptionAuth ?? true;
447
469
 
448
470
  if (enableAuth) {
449
471
  const betterAuthService = CoreBetterAuthModule.getServiceInstance();
@@ -482,7 +504,7 @@ export class CoreModule implements NestModule {
482
504
  },
483
505
  'subscriptions-transport-ws': {
484
506
  onConnect: async (connectionParams) => {
485
- const enableAuth = options?.graphQl?.enableSubscriptionAuth ?? true;
507
+ const enableAuth = graphQlOpts?.enableSubscriptionAuth ?? true;
486
508
 
487
509
  if (enableAuth) {
488
510
  const betterAuthService = CoreBetterAuthModule.getServiceInstance();
@@ -517,7 +539,7 @@ export class CoreModule implements NestModule {
517
539
  },
518
540
  },
519
541
  },
520
- options?.graphQl?.driver,
542
+ graphQlOpts?.driver,
521
543
  ),
522
544
  };
523
545
  }
@@ -536,8 +558,9 @@ export class CoreModule implements NestModule {
536
558
  cors: object,
537
559
  options: Partial<IServerOptions>,
538
560
  ) {
539
- // Store config reference for use in callbacks
540
- const enableSubscriptionAuth = options?.graphQl?.enableSubscriptionAuth ?? true;
561
+ // This method is only called when graphQl !== false, extract config with type narrowing
562
+ const graphQlOpts = typeof options?.graphQl === 'object' ? options.graphQl : undefined;
563
+ const enableSubscriptionAuth = graphQlOpts?.enableSubscriptionAuth ?? true;
541
564
 
542
565
  return {
543
566
  imports: [AuthModule],
@@ -599,7 +622,7 @@ export class CoreModule implements NestModule {
599
622
  },
600
623
  },
601
624
  },
602
- options?.graphQl?.driver,
625
+ graphQlOpts?.driver,
603
626
  ),
604
627
  };
605
628
  }