@lenne.tech/nest-server 8.6.2 → 8.6.5

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 (39) hide show
  1. package/dist/config.env.js +4 -2
  2. package/dist/config.env.js.map +1 -1
  3. package/dist/core/common/inputs/filter.input.js.map +1 -1
  4. package/dist/core/common/interfaces/server-options.interface.d.ts +16 -13
  5. package/dist/core.module.d.ts +3 -2
  6. package/dist/core.module.js +26 -4
  7. package/dist/core.module.js.map +1 -1
  8. package/dist/main.js +13 -0
  9. package/dist/main.js.map +1 -1
  10. package/dist/server/modules/auth/auth.module.js +1 -2
  11. package/dist/server/modules/auth/auth.module.js.map +1 -1
  12. package/dist/server/modules/file/file.controller.js +1 -1
  13. package/dist/server/modules/file/file.controller.js.map +1 -1
  14. package/dist/server/modules/file/file.resolver.d.ts +5 -0
  15. package/dist/server/modules/file/file.resolver.js +51 -0
  16. package/dist/server/modules/file/file.resolver.js.map +1 -0
  17. package/dist/server/modules/user/user.resolver.js +1 -1
  18. package/dist/server/modules/user/user.resolver.js.map +1 -1
  19. package/dist/server/modules/user/user.service.js +1 -1
  20. package/dist/server/modules/user/user.service.js.map +1 -1
  21. package/dist/server/server.module.js +3 -1
  22. package/dist/server/server.module.js.map +1 -1
  23. package/dist/test/test.helper.d.ts +4 -1
  24. package/dist/test/test.helper.js +42 -2
  25. package/dist/test/test.helper.js.map +1 -1
  26. package/dist/tsconfig.build.tsbuildinfo +1 -1
  27. package/package.json +24 -19
  28. package/src/config.env.ts +4 -2
  29. package/src/core/common/inputs/filter.input.ts +0 -1
  30. package/src/core/common/interfaces/server-options.interface.ts +68 -52
  31. package/src/core.module.ts +38 -6
  32. package/src/main.ts +16 -0
  33. package/src/server/modules/auth/auth.module.ts +1 -2
  34. package/src/server/modules/file/file.controller.ts +1 -1
  35. package/src/server/modules/file/file.resolver.ts +55 -0
  36. package/src/server/modules/user/user.resolver.ts +1 -1
  37. package/src/server/modules/user/user.service.ts +1 -1
  38. package/src/server/server.module.ts +5 -1
  39. package/src/test/test.helper.ts +67 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lenne.tech/nest-server",
3
- "version": "8.6.2",
3
+ "version": "8.6.5",
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",
@@ -16,6 +16,9 @@
16
16
  "scripts": {
17
17
  "build": "rimraf dist && tsc -p tsconfig.build.json",
18
18
  "build:pack": "npm pack && echo 'use file:/ROOT_PATH_TO_TGZ_FILE to integrate the package'",
19
+ "docs": "npm run docs:ci && open ./public/index.html",
20
+ "docs:bootstrap": "node extras/update-spectaql-version.mjs && npx -y spectaql ./spectaql.yml",
21
+ "docs:ci": "ts-node ./scripts/init-server.ts && npm run docs:boostrap",
19
22
  "format": "prettier --write 'src/**/*.ts'",
20
23
  "format:staged": "pretty-quick --staged",
21
24
  "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
@@ -32,9 +35,10 @@
32
35
  "test": "NODE_ENV=local jest",
33
36
  "test:cov": "NODE_ENV=local jest --coverage",
34
37
  "test:debug": "NODE_ENV=local node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
35
- "test:e2e": "NODE_ENV=local jest --config jest-e2e.json --forceExit --detectOpenHandles",
36
- "test:e2e-cov": "NODE_ENV=local jest --config jest-e2e.json --coverage --forceExit --detectOpenHandles",
37
- "test:ci": "NODE_ENV=local jest --config jest-e2e.json --ci --forceExit --detectOpenHandles",
38
+ "test:e2e": "NODE_ENV=local jest --config jest-e2e.json --forceExit",
39
+ "test:e2e-cov": "NODE_ENV=local jest --config jest-e2e.json --coverage --forceExit",
40
+ "test:e2e-doh": "NODE_ENV=local jest --config jest-e2e.json --forceExit --detectOpenHandles",
41
+ "test:ci": "NODE_ENV=local jest --config jest-e2e.json --ci --forceExit",
38
42
  "test:watch": "NODE_ENV=local jest --watch",
39
43
  "prepack": "npm run prestart:prod",
40
44
  "prepare": "husky install",
@@ -54,26 +58,27 @@
54
58
  "dependencies": {
55
59
  "@apollo/gateway": "0.50.2",
56
60
  "@nestjs/apollo": "10.0.11",
57
- "@nestjs/common": "8.4.4",
58
- "@nestjs/core": "8.4.4",
61
+ "@nestjs/common": "8.4.5",
62
+ "@nestjs/core": "8.4.5",
59
63
  "@nestjs/graphql": "10.0.11",
60
64
  "@nestjs/jwt": "8.0.0",
61
65
  "@nestjs/mongoose": "9.0.3",
62
66
  "@nestjs/passport": "8.2.1",
63
- "@nestjs/platform-express": "8.4.4",
67
+ "@nestjs/platform-express": "8.4.5",
64
68
  "apollo-server-core": "3.7.0",
65
69
  "apollo-server-express": "3.7.0",
66
70
  "bcrypt": "5.0.1",
67
71
  "class-transformer": "0.5.1",
68
72
  "class-validator": "0.13.2",
69
- "ejs": "3.1.7",
70
- "graphql": "16.4.0",
73
+ "ejs": "3.1.8",
74
+ "graphql": "16.5.0",
71
75
  "graphql-subscriptions": "2.0.0",
76
+ "graphql-upload": "13.0.0",
72
77
  "json-to-graphql-query": "2.2.4",
73
78
  "light-my-request": "4.10.1",
74
79
  "lodash": "4.17.21",
75
- "mongodb": "4.5.0",
76
- "mongoose": "6.3.2",
80
+ "mongodb": "4.6.0",
81
+ "mongoose": "6.3.3",
77
82
  "multer": "1.4.4",
78
83
  "node-mailjet": "3.4.1",
79
84
  "nodemailer": "6.7.5",
@@ -85,28 +90,28 @@
85
90
  "rxjs": "7.5.5"
86
91
  },
87
92
  "devDependencies": {
88
- "@nestjs/testing": "8.4.4",
89
- "@types/ejs": "3.1.0",
90
- "@types/jest": "27.5.0",
93
+ "@nestjs/testing": "8.4.5",
94
+ "@types/ejs": "3.1.1",
95
+ "@types/jest": "27.5.1",
91
96
  "@types/lodash": "4.14.182",
92
97
  "@types/multer": "1.4.7",
93
- "@types/node": "16.11.33",
98
+ "@types/node": "16.11.35",
94
99
  "@types/node-mailjet": "3.3.8",
95
100
  "@types/nodemailer": "6.4.4",
96
101
  "@types/passport": "1.0.7",
97
102
  "@types/supertest": "2.0.12",
98
- "@typescript-eslint/eslint-plugin": "5.22.0",
99
- "@typescript-eslint/parser": "5.22.0",
103
+ "@typescript-eslint/eslint-plugin": "5.23.0",
104
+ "@typescript-eslint/parser": "5.23.0",
100
105
  "coffeescript": "2.7.0",
101
106
  "eslint": "8.15.0",
102
107
  "eslint-config-prettier": "8.5.0",
103
108
  "find-file-up": "2.0.1",
104
- "grunt": "1.5.2",
109
+ "grunt": "1.5.3",
105
110
  "grunt-bg-shell": "2.3.3",
106
111
  "grunt-contrib-clean": "2.0.1",
107
112
  "grunt-contrib-watch": "1.1.0",
108
113
  "grunt-sync": "0.8.2",
109
- "husky": "7.0.4",
114
+ "husky": "8.0.1",
110
115
  "jest": "28.1.0",
111
116
  "pm2": "5.2.0",
112
117
  "prettier": "2.6.2",
package/src/config.env.ts CHANGED
@@ -31,6 +31,7 @@ const config: { [env: string]: IServerOptions } = {
31
31
  passwordResetLink: 'http://localhost:4200/user/password-reset',
32
32
  },
33
33
  env: 'development',
34
+ execAfterInit: 'npm run docs:bootstrap',
34
35
  graphQl: {
35
36
  driver: {
36
37
  debug: true,
@@ -46,7 +47,7 @@ const config: { [env: string]: IServerOptions } = {
46
47
  port: 3000,
47
48
  staticAssets: {
48
49
  path: join(__dirname, '..', 'public'),
49
- options: { prefix: '/public/' },
50
+ options: { prefix: '' },
50
51
  },
51
52
  templates: {
52
53
  path: join(__dirname, 'templates'),
@@ -80,6 +81,7 @@ const config: { [env: string]: IServerOptions } = {
80
81
  passwordResetLink: 'http://localhost:4200/user/password-reset',
81
82
  },
82
83
  env: 'productive',
84
+ execAfterInit: 'npm run docs:bootstrap',
83
85
  graphQl: {
84
86
  driver: {
85
87
  debug: false,
@@ -95,7 +97,7 @@ const config: { [env: string]: IServerOptions } = {
95
97
  port: 3000,
96
98
  staticAssets: {
97
99
  path: join(__dirname, '..', 'public'),
98
- options: { prefix: '/public/' },
100
+ options: { prefix: '' },
99
101
  },
100
102
  templates: {
101
103
  path: join(__dirname, 'templates'),
@@ -1,5 +1,4 @@
1
1
  import { Field, InputType } from '@nestjs/graphql';
2
- import { ModelHelper } from '../helpers/model.helper';
3
2
  import { CombinedFilterInput } from './combined-filter.input';
4
3
  import { CoreInput } from './core-input.input';
5
4
  import { SingleFilterInput } from './single-filter.input';
@@ -1,4 +1,5 @@
1
1
  import { ApolloDriverConfig } from '@nestjs/apollo';
2
+ import { GqlModuleAsyncOptions } from '@nestjs/graphql';
2
3
  import { JwtModuleOptions } from '@nestjs/jwt';
3
4
  import { MongooseModuleOptions } from '@nestjs/mongoose/dist/interfaces/mongoose-options.interface';
4
5
  import { ServeStaticOptions } from '@nestjs/platform-express/interfaces/serve-static-options.interface';
@@ -9,12 +10,58 @@ import { MailjetOptions } from './mailjet-options.interface';
9
10
  * Options for the server
10
11
  */
11
12
  export interface IServerOptions {
13
+ /**
14
+ * SMTP and template configuration for sending emails
15
+ */
16
+ email?: {
17
+ /**
18
+ * Data for default sender
19
+ */
20
+ defaultSender?: {
21
+ /**
22
+ * Default email for sending emails
23
+ */
24
+ email?: string;
25
+
26
+ /**
27
+ * Default name for sending emails
28
+ */
29
+ name?: string;
30
+ };
31
+
32
+ /**
33
+ * Options for Mailjet
34
+ */
35
+ mailjet?: MailjetOptions;
36
+
37
+ /**
38
+ * Password reset link for email
39
+ */
40
+ passwordResetLink?: string;
41
+
42
+ /**
43
+ * SMTP configuration for nodemailer
44
+ */
45
+ smtp?: SMTPTransport | SMTPTransport.Options | string;
46
+
47
+ /**
48
+ * Verification link for email
49
+ */
50
+ verificationLink?: string;
51
+ };
52
+
12
53
  /**
13
54
  * Environment
14
55
  * e.g. 'development'
15
56
  */
16
57
  env?: string;
17
58
 
59
+ /**
60
+ * Exec a command after server is initialized
61
+ * e.g. 'npm run docs:bootstrap'
62
+ */
63
+ execAfterInit?: string;
64
+
18
65
  /**
19
66
  * Configuration of the GraphQL module
20
67
  * see https://docs.nestjs.com/graphql/quick-start
@@ -30,6 +77,11 @@ export interface IServerOptions {
30
77
  * Subscription authentication
31
78
  */
32
79
  enableSubscriptionAuth?: boolean;
80
+
81
+ /**
82
+ * Module options (forRootAsync)
83
+ */
84
+ options?: GqlModuleAsyncOptions;
33
85
  };
34
86
 
35
87
  /**
@@ -67,6 +119,11 @@ export interface IServerOptions {
67
119
  secretOrPrivateKey?: string;
68
120
  } & JwtModuleOptions;
69
121
 
122
+ /**
123
+ * Configuration for Mongoose
124
+ */
125
+ mongoose?: { uri: string; options?: MongooseModuleOptions };
126
+
70
127
  /**
71
128
  * Port number of the server
72
129
  * e.g. 8080
@@ -74,77 +131,36 @@ export interface IServerOptions {
74
131
  port?: number;
75
132
 
76
133
  /**
77
- * SMTP and template configuration for sending emails
134
+ * Configuration for useStaticAssets
78
135
  */
79
- email?: {
80
- /**
81
- * SMTP configuration for nodemailer
82
- */
83
- smtp?: SMTPTransport | SMTPTransport.Options | string;
84
-
85
- mailjet?: MailjetOptions;
86
- /**
87
- * Verification link for email
88
- */
89
- verificationLink?: string;
90
-
91
- /**
92
- * Password reset link for email
93
- */
94
- passwordResetLink?: string;
95
-
136
+ staticAssets?: {
96
137
  /**
97
- * Data for default sender
138
+ * Additional options for useStaticAssets
139
+ * e.g. {prefix: '/public/'}
98
140
  */
99
- defaultSender?: {
100
- /**
101
- * Default email for sending emails
102
- */
103
- email?: string;
104
-
105
- /**
106
- * Default name for sending emails
107
- */
108
- name?: string;
109
- };
110
- };
111
-
112
- /**
113
- * Configuration for Mongoose
114
- */
115
- mongoose?: { uri: string; options?: MongooseModuleOptions };
141
+ options?: ServeStaticOptions;
116
142
 
117
- /**
118
- * Configuration for useStaticAssets
119
- */
120
- staticAssets?: {
121
143
  /**
122
144
  * Root directory for static assets
123
145
  * e.g. join(__dirname, '..', 'public')
124
146
  */
125
147
  path?: string;
126
-
127
- /**
128
- * Additional options for useStaticAssets
129
- * e.g. {prefix: '/public/'}
130
- */
131
- options?: ServeStaticOptions;
132
148
  };
133
149
 
134
150
  /**
135
151
  * Templates
136
152
  */
137
153
  templates?: {
138
- /**
139
- * Directory for templates
140
- * e.g. join(__dirname, '..', 'templates')
141
- */
142
- path?: string;
143
-
144
154
  /**
145
155
  * View engine
146
156
  * e.g. 'ejs'
147
157
  */
148
158
  engine?: string;
159
+
160
+ /**
161
+ * Directory for templates
162
+ * e.g. join(__dirname, '..', 'templates')
163
+ */
164
+ path?: string;
149
165
  };
150
166
  }
@@ -1,15 +1,17 @@
1
- import { DynamicModule, Global, Module, UnauthorizedException } from '@nestjs/common';
1
+ import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
2
+ import { DynamicModule, Global, MiddlewareConsumer, Module, NestModule, UnauthorizedException } from '@nestjs/common';
2
3
  import { APP_PIPE } from '@nestjs/core';
3
4
  import { GraphQLModule } from '@nestjs/graphql';
5
+ import { MongooseModule } from '@nestjs/mongoose';
6
+ import { Context } from 'apollo-server-core';
7
+ import { graphqlUploadExpress } from 'graphql-upload';
4
8
  import { merge } from './core/common/helpers/config.helper';
5
9
  import { IServerOptions } from './core/common/interfaces/server-options.interface';
6
10
  import { MapAndValidatePipe } from './core/common/pipes/map-and-validate.pipe';
7
11
  import { ConfigService } from './core/common/services/config.service';
8
12
  import { EmailService } from './core/common/services/email.service';
9
- import { TemplateService } from './core/common/services/template.service';
10
- import { MongooseModule } from '@nestjs/mongoose';
11
13
  import { MailjetService } from './core/common/services/mailjet.service';
12
- import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
14
+ import { TemplateService } from './core/common/services/template.service';
13
15
 
14
16
  /**
15
17
  * Core module (dynamic)
@@ -26,7 +28,13 @@ import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
26
28
  */
27
29
  @Global()
28
30
  @Module({})
29
- export class CoreModule {
31
+ export class CoreModule implements NestModule {
32
+ /**
33
+ * Integrate middleware, e.g. GraphQL upload handing for express
34
+ */
35
+ configure(consumer: MiddlewareConsumer) {
36
+ consumer.apply(graphqlUploadExpress()).forRoutes('graphql');
37
+ }
30
38
  /**
31
39
  * Dynamic module
32
40
  * see https://docs.nestjs.com/modules#dynamic-modules
@@ -66,6 +74,28 @@ export class CoreModule {
66
74
  }
67
75
  },
68
76
  },
77
+ 'graphql-ws': {
78
+ onConnect: async (context: Context<any>) => {
79
+ const { connectionParams, extra } = context;
80
+ if (config.graphQl.enableSubscriptionAuth) {
81
+ // get authToken from authorization header
82
+ const authToken: string =
83
+ 'Authorization' in connectionParams && connectionParams?.Authorization?.split(' ')[1];
84
+ if (authToken) {
85
+ // verify authToken/getJwtPayLoad
86
+ const payload = authService.decodeJwt(authToken);
87
+ const user = await authService.validateUser(payload);
88
+ // the user/jwtPayload object found will be available as context.currentUser/jwtPayload in your GraphQL resolvers
89
+ extra.user = user;
90
+ extra.header = connectionParams;
91
+ return extra;
92
+ }
93
+
94
+ throw new UnauthorizedException();
95
+ }
96
+ },
97
+ context: ({ extra }) => extra,
98
+ },
69
99
  },
70
100
  },
71
101
  options?.graphQl?.driver
@@ -113,7 +143,9 @@ export class CoreModule {
113
143
  module: CoreModule,
114
144
  imports: [
115
145
  MongooseModule.forRoot(config.mongoose.uri, config.mongoose.options),
116
- GraphQLModule.forRootAsync<ApolloDriverConfig>(Object.assign({ driver: ApolloDriver }, config.graphQl.driver)),
146
+ GraphQLModule.forRootAsync<ApolloDriverConfig>(
147
+ Object.assign({ driver: ApolloDriver }, config.graphQl.driver, config.graphQl.options)
148
+ ),
117
149
  ],
118
150
  providers,
119
151
  exports: [ConfigService, EmailService, TemplateService, MailjetService],
package/src/main.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { NestFactory } from '@nestjs/core';
2
2
  import { NestExpressApplication } from '@nestjs/platform-express';
3
+ import { exec } from 'child_process';
3
4
  import envConfig from './config.env';
4
5
  import { ServerModule } from './server/server.module';
5
6
 
@@ -25,6 +26,21 @@ async function bootstrap() {
25
26
 
26
27
  // Start server on configured port
27
28
  await server.listen(envConfig.port);
29
+
30
+ // Run command after server init
31
+ if (envConfig.execAfterInit) {
32
+ exec(envConfig.execAfterInit, (error, stdout, stderr) => {
33
+ if (error) {
34
+ console.error(`error: ${error.message}`);
35
+ return;
36
+ }
37
+
38
+ if (stderr) {
39
+ console.error(`stderr: ${stderr}`);
40
+ return;
41
+ }
42
+ });
43
+ }
28
44
  }
29
45
 
30
46
  // Start server
@@ -27,9 +27,8 @@ export class AuthModule {
27
27
  // providers: [] // Integrate additional Providers here to resolve dependencies
28
28
  },
29
29
  }),
30
- EmailService,
31
30
  ],
32
- providers: [AuthResolver, AuthService],
31
+ providers: [AuthResolver, AuthService, EmailService],
33
32
  exports: [AuthResolver, CoreAuthModule],
34
33
  };
35
34
  }
@@ -32,6 +32,6 @@ export class FileController {
32
32
  })
33
33
  )
34
34
  uploadFile(@UploadedFiles() files, @Body() fields: any) {
35
- console.log(files, fields);
35
+ console.log(JSON.stringify({ files, fields }, null, 2));
36
36
  }
37
37
  }
@@ -0,0 +1,55 @@
1
+ import { Args, Mutation, Resolver } from '@nestjs/graphql';
2
+ import { createWriteStream } from 'fs';
3
+ import { FileUpload, GraphQLUpload } from 'graphql-upload';
4
+
5
+ /**
6
+ * File resolver
7
+ */
8
+ @Resolver()
9
+ export class FileResolver {
10
+ /**
11
+ * Upload file
12
+ */
13
+ @Mutation(() => Boolean)
14
+ async uploadFile(
15
+ @Args({ name: 'file', type: () => GraphQLUpload })
16
+ file: FileUpload
17
+ ) {
18
+ console.log(JSON.stringify(file, null, 2));
19
+ /*
20
+ const {filename, mimetype, encoding, createReadStream} = file;
21
+ await new Promise((resolve, reject) =>
22
+ createReadStream()
23
+ .pipe(createWriteStream(`./uploads/${filename}`))
24
+ .on('finish', () => resolve(true))
25
+ .on('error', (error) => reject(error))
26
+ );
27
+ */
28
+ return true;
29
+ }
30
+
31
+ /**
32
+ * Upload files
33
+ */
34
+ @Mutation(() => Boolean)
35
+ async uploadFiles(
36
+ @Args({ name: 'files', type: () => [GraphQLUpload] })
37
+ files: FileUpload[]
38
+ ) {
39
+ const promises: Promise<any>[] = [];
40
+ for (const file of files) {
41
+ console.log(JSON.stringify(await file, null, 2));
42
+ /*
43
+ const {filename, mimetype, encoding, createReadStream} = await file
44
+ promises.push(new Promise((resolve, reject) =>
45
+ createReadStream()
46
+ .pipe(createWriteStream(`./uploads/${filename}`))
47
+ .on('finish', () => resolve(true))
48
+ .on('error', (error) => reject(error))
49
+ ));
50
+ */
51
+ }
52
+ await Promise.all(promises);
53
+ return true;
54
+ }
55
+ }
@@ -145,7 +145,7 @@ export class UserResolver {
145
145
  */
146
146
  @Subscription(() => User, {
147
147
  filter(this: UserResolver, payload, variables, context) {
148
- return context.user.roles.include(RoleEnum.ADMIN);
148
+ return context.user.hasRole(RoleEnum.ADMIN);
149
149
  },
150
150
  resolve: (user) => user,
151
151
  })
@@ -50,7 +50,7 @@ export class UserService extends CoreUserService<User, UserInput, UserCreateInpu
50
50
  // and could not exist as currentUser before
51
51
  if (!user.createdBy) {
52
52
  await this.mainDbModel.findByIdAndUpdate(user.id, { createdBy: user.id });
53
- user = await this.get(user.id, { ...serviceOptions, currentUser: serviceOptions.currentUser || user });
53
+ user = await this.get(user.id, { ...serviceOptions, currentUser: serviceOptions?.currentUser || user });
54
54
  }
55
55
 
56
56
  // Publish action
@@ -1,10 +1,11 @@
1
1
  import { Module } from '@nestjs/common';
2
2
  import envConfig from '../config.env';
3
3
  import { CoreModule } from '../core.module';
4
+ import { CoreAuthService } from '../core/modules/auth/services/core-auth.service';
4
5
  import { AuthModule } from './modules/auth/auth.module';
5
6
  import { FileController } from './modules/file/file.controller';
7
+ import { FileResolver } from './modules/file/file.resolver';
6
8
  import { ServerController } from './server.controller';
7
- import { CoreAuthService } from '../core/modules/auth/services/core-auth.service';
8
9
 
9
10
  /**
10
11
  * Server module (dynamic)
@@ -26,6 +27,9 @@ import { CoreAuthService } from '../core/modules/auth/services/core-auth.service
26
27
  // Include REST controllers
27
28
  controllers: [FileController, ServerController],
28
29
 
30
+ // Include resolvers, services and other providers
31
+ providers: [FileResolver],
32
+
29
33
  // Export modules for reuse in other modules
30
34
  exports: [CoreModule, AuthModule],
31
35
  })
@@ -1,9 +1,11 @@
1
1
  import { INestApplication } from '@nestjs/common';
2
+ import { createClient } from 'graphql-ws';
2
3
  // import { FastifyInstance } from 'fastify';
3
4
  import { jsonToGraphQLQuery } from 'json-to-graphql-query';
4
5
  import * as LightMyRequest from 'light-my-request';
5
6
  import * as supertest from 'supertest';
6
7
  import * as util from 'util';
8
+ import * as ws from 'ws';
7
9
 
8
10
  /**
9
11
  * GraphQL request type
@@ -64,6 +66,11 @@ export interface TestGraphQLOptions {
64
66
  */
65
67
  convertEnums?: boolean | string[];
66
68
 
69
+ /**
70
+ * Count of subscription messages, specifies how many messages are to be received on subscription
71
+ */
72
+ countOfSubscriptionMessages?: number;
73
+
67
74
  /**
68
75
  * Print console logs
69
76
  */
@@ -104,11 +111,16 @@ export class TestHelper {
104
111
  // app: FastifyInstance | INestApplication;
105
112
  app: INestApplication;
106
113
 
114
+ // URL with port and directory to subscription endpoint
115
+ // e.g.: ws://localhost:3030/graphql
116
+ subscriptionUrl: string;
117
+
107
118
  /**
108
119
  * Constructor
109
120
  */
110
- constructor(app: any) {
121
+ constructor(app: any, subscriptionUrl?: string) {
111
122
  this.app = app;
123
+ this.subscriptionUrl = subscriptionUrl;
112
124
  }
113
125
 
114
126
  /**
@@ -121,6 +133,7 @@ export class TestHelper {
121
133
  options = Object.assign(
122
134
  {
123
135
  convertEnums: true,
136
+ countOfSubscriptionMessages: 1,
124
137
  token: null,
125
138
  statusCode: 200,
126
139
  log: false,
@@ -174,6 +187,10 @@ export class TestHelper {
174
187
  query = jsonToGraphQLQuery(queryObj, { pretty: true });
175
188
  }
176
189
 
190
+ if ((graphql as TestGraphQLConfig).type === TestGraphQLType.SUBSCRIPTION) {
191
+ return this.getSubscription(graphql as TestGraphQLConfig, query, options);
192
+ }
193
+
177
194
  // Convert uppercase strings in arguments of query to enums
178
195
  if (options.convertEnums) {
179
196
  if (Array.isArray(options.convertEnums)) {
@@ -354,7 +371,7 @@ export class TestHelper {
354
371
 
355
372
  // Log response
356
373
  if (log) {
357
- console.log(response);
374
+ console.log(JSON.stringify(response, null, 2));
358
375
  }
359
376
 
360
377
  // Log error
@@ -373,4 +390,52 @@ export class TestHelper {
373
390
  // Return response
374
391
  return response;
375
392
  }
393
+
394
+ /**
395
+ * Get subscription
396
+ */
397
+ async getSubscription(graphql: TestGraphQLConfig, query: string, options?: TestGraphQLOptions) {
398
+ // Check url
399
+ if (!this.subscriptionUrl) {
400
+ throw new Error("Missing subscriptionUrl in TestHelper: new TestHelper(app, 'ws://localhost:3030/graphql')");
401
+ }
402
+
403
+ // Prepare subscription
404
+ let connectionParams;
405
+ if (options?.token) {
406
+ connectionParams = { Authorization: `Bearer ${options?.token}` };
407
+ }
408
+
409
+ // Init client
410
+ if (options.log) {
411
+ console.log('Subscription query', JSON.stringify(query, null, 2));
412
+ }
413
+ const client = createClient({ url: this.subscriptionUrl, connectionParams, webSocketImpl: ws });
414
+ const messages: any[] = [];
415
+ let unsubscribe: () => void;
416
+ const onNext = (message) => {
417
+ if (options.log) {
418
+ console.log('Subscription message', JSON.stringify(message, null, 2));
419
+ }
420
+ messages.push(message?.data?.[graphql.name]);
421
+ if (messages.length <= options.countOfSubscriptionMessages) {
422
+ unsubscribe();
423
+ }
424
+ };
425
+
426
+ // Subscribe
427
+ await new Promise((resolve, reject) => {
428
+ unsubscribe = client.subscribe(
429
+ { query },
430
+ {
431
+ next: onNext,
432
+ error: reject,
433
+ complete: resolve as any,
434
+ }
435
+ );
436
+ });
437
+
438
+ // Return subscribed messages
439
+ return messages;
440
+ }
376
441
  }