@lenne.tech/nest-server 10.0.3 → 10.0.4

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.3",
3
+ "version": "10.0.4",
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",
@@ -17,9 +17,9 @@
17
17
  "build": "rimraf dist && nest build",
18
18
  "build:pack": "npm pack && echo 'use file:/ROOT_PATH_TO_TGZ_FILE to integrate the package'",
19
19
  "build:dev": "npm run build && yalc push --private",
20
- "docs": "npm run docs:ci && open ./public/index.html",
20
+ "docs": "npm run docs:ci && open http://127.0.0.1:8080/ && open ./public/index.html && compodoc -p tsconfig.json -s ",
21
21
  "docs:bootstrap": "node extras/update-spectaql-version.mjs && npx -y spectaql ./spectaql.yml",
22
- "docs:ci": "ts-node ./scripts/init-server.ts && npm run docs:bootstrap",
22
+ "docs:ci": "ts-node ./scripts/init-server.ts && npm run docs:bootstrap && compodoc -p tsconfig.json",
23
23
  "format": "prettier --write 'src/**/*.ts'",
24
24
  "format:staged": "pretty-quick --staged",
25
25
  "lint": "eslint \"{src,tests}/**/*.ts\" --fix",
@@ -72,7 +72,7 @@
72
72
  "@nestjs/passport": "10.0.0",
73
73
  "@nestjs/platform-express": "10.1.3",
74
74
  "@nestjs/schedule": "3.0.1",
75
- "@nestjs/terminus": "^10.0.1",
75
+ "@nestjs/terminus": "10.0.1",
76
76
  "apollo-server-core": "3.11.1",
77
77
  "apollo-server-express": "3.11.1",
78
78
  "bcrypt": "5.1.0",
@@ -102,9 +102,12 @@
102
102
  "reflect-metadata": "0.1.13",
103
103
  "rfdc": "1.3.0",
104
104
  "rimraf": "5.0.1",
105
- "rxjs": "7.8.1"
105
+ "rxjs": "7.8.1",
106
+ "yuml-diagram": "1.2.0"
106
107
  },
107
108
  "devDependencies": {
109
+ "@babel/plugin-proposal-private-methods": "7.18.6",
110
+ "@compodoc/compodoc": "1.1.21",
108
111
  "@lenne.tech/eslint-config-ts": "0.0.8",
109
112
  "@nestjs/cli": "10.1.11",
110
113
  "@nestjs/schematics": "10.0.1",
@@ -129,7 +132,7 @@
129
132
  "coffeescript": "2.7.0",
130
133
  "eslint": "8.46.0",
131
134
  "eslint-config-prettier": "8.9.0",
132
- "eslint-plugin-unused-imports": "^3.0.0",
135
+ "eslint-plugin-unused-imports": "3.0.0",
133
136
  "find-file-up": "2.0.1",
134
137
  "grunt": "1.6.1",
135
138
  "grunt-bg-shell": "2.3.3",
package/src/config.env.ts CHANGED
@@ -57,6 +57,11 @@ const config: { [env: string]: IServerOptions } = {
57
57
  },
58
58
  healthCheck: {
59
59
  enabled: true,
60
+ configs: {
61
+ database: {
62
+ enabled: true,
63
+ },
64
+ },
60
65
  },
61
66
  ignoreSelectionsForPopulate: true,
62
67
  jwt: {
@@ -78,6 +83,7 @@ const config: { [env: string]: IServerOptions } = {
78
83
  collation: {
79
84
  locale: 'de',
80
85
  },
86
+ modelDocumentation: true,
81
87
  uri: 'mongodb://127.0.0.1/nest-server-local',
82
88
  },
83
89
  port: 3000,
@@ -133,6 +139,11 @@ const config: { [env: string]: IServerOptions } = {
133
139
  },
134
140
  healthCheck: {
135
141
  enabled: true,
142
+ configs: {
143
+ database: {
144
+ enabled: true,
145
+ },
146
+ },
136
147
  },
137
148
  ignoreSelectionsForPopulate: true,
138
149
  jwt: {
@@ -154,6 +165,7 @@ const config: { [env: string]: IServerOptions } = {
154
165
  collation: {
155
166
  locale: 'de',
156
167
  },
168
+ modelDocumentation: false,
157
169
  uri: 'mongodb://127.0.0.1/nest-server-dev',
158
170
  },
159
171
  port: 3000,
@@ -209,6 +221,11 @@ const config: { [env: string]: IServerOptions } = {
209
221
  },
210
222
  healthCheck: {
211
223
  enabled: true,
224
+ configs: {
225
+ database: {
226
+ enabled: true,
227
+ },
228
+ },
212
229
  },
213
230
  ignoreSelectionsForPopulate: true,
214
231
  jwt: {
@@ -230,6 +247,7 @@ const config: { [env: string]: IServerOptions } = {
230
247
  collation: {
231
248
  locale: 'de',
232
249
  },
250
+ modelDocumentation: false,
233
251
  uri: 'mongodb://127.0.0.1/nest-server-prod',
234
252
  },
235
253
  port: 3000,
@@ -176,26 +176,98 @@ export interface IServerOptions {
176
176
  * Whether to activate health check endpoints
177
177
  */
178
178
  healthCheck?: {
179
+
180
+ /**
181
+ * Whether health check is enabled
182
+ */
179
183
  enabled?: boolean;
184
+
185
+ /**
186
+ * Configuration of single health checks
187
+ */
180
188
  configs?: {
189
+
190
+ /**
191
+ * Configuration for database health check
192
+ */
181
193
  database?: {
182
- disabled?: boolean;
194
+
195
+ /**
196
+ * Whether to enable the database health check
197
+ */
198
+ enabled?: boolean;
199
+
200
+ /**
201
+ * Key in result JSON
202
+ */
183
203
  key?: string;
204
+
205
+ /**
206
+ * Database health check options
207
+ */
184
208
  options?: MongoosePingCheckSettings;
185
209
  };
210
+
211
+ /**
212
+ * Configuration for memory heap health check
213
+ */
186
214
  memoryHeap?: {
187
- disabled?: boolean;
215
+
216
+ /**
217
+ * Whether to enable the memory heap health check
218
+ */
219
+ enabled?: boolean;
220
+
221
+ /**
222
+ * Key in result JSON
223
+ */
188
224
  key?: string;
225
+
226
+ /**
227
+ * Memory limit in bytes
228
+ */
189
229
  heapUsedThreshold?: number;
190
230
  };
231
+
232
+ /**
233
+ * Configuration for memory resident set size health check
234
+ */
191
235
  memoryRss?: {
192
- disabled?: boolean;
236
+
237
+ /**
238
+ * Whether to enable the memory resident set size health check
239
+ */
240
+ enabled?: boolean;
241
+
242
+ /**
243
+ * Key in result JSON
244
+ */
193
245
  key?: string;
246
+
247
+ /**
248
+ * Memory limit in bytes
249
+ */
194
250
  rssThreshold?: number;
195
251
  };
252
+
253
+ /**
254
+ * Configuration for disk space health check
255
+ */
196
256
  storage?: {
197
- disabled?: boolean;
257
+
258
+ /**
259
+ * Whether to enable the disk space health check
260
+ */
261
+ enabled?: boolean;
262
+
263
+ /**
264
+ * Key in result JSON
265
+ */
198
266
  key?: string;
267
+
268
+ /**
269
+ * Disk health indicator options
270
+ */
199
271
  options?: DiskHealthIndicatorOptions;
200
272
  };
201
273
  };
@@ -249,6 +321,12 @@ export interface IServerOptions {
249
321
  mongoose?: {
250
322
  collation?: CollationOptions;
251
323
 
324
+ /**
325
+ * Whether to create SVG-Diagrams of mongoose models
326
+ * @beta
327
+ */
328
+ modelDocumentation?: boolean;
329
+
252
330
  /**
253
331
  * Mongoose connection string
254
332
  */
@@ -0,0 +1,140 @@
1
+ import fs = require('fs');
2
+ import { Injectable, OnApplicationBootstrap } from '@nestjs/common';
3
+ import { InjectConnection } from '@nestjs/mongoose';
4
+ import { Connection } from 'mongoose';
5
+ import YumlDiagram = require('yuml-diagram');
6
+
7
+ /**
8
+ * Schema config for yUml creation
9
+ */
10
+ export interface ModelDocSchemaConfig {
11
+ isArray: boolean;
12
+ name: string;
13
+ ref: string;
14
+ type: string;
15
+ }
16
+
17
+ /**
18
+ * Model documentation as yUML-SVG
19
+ */
20
+ @Injectable()
21
+ export class ModelDocService implements OnApplicationBootstrap {
22
+
23
+ constructor(
24
+ @InjectConnection() private readonly connection: Connection,
25
+ ) {
26
+ }
27
+
28
+ /**
29
+ * Lifecycle hook that is called right after the application has started.
30
+ */
31
+ async onApplicationBootstrap() {
32
+ const schemaJson = this.getSchemaJson();
33
+ const yUml = this.jsonToYuml(schemaJson);
34
+ this.yUmlToSvg(yUml);
35
+ }
36
+
37
+ /**
38
+ * Analyse the mongoose database models and create JSON
39
+ * @protected
40
+ */
41
+ protected getSchemaJson(): Record<string, Record<string, ModelDocSchemaConfig>> {
42
+
43
+ // Prepare results
44
+ const results: Record<string, Record<string, ModelDocSchemaConfig>> = {};
45
+
46
+ // Process models
47
+ const models = this.connection.modelNames();
48
+ for (const modelName of models) {
49
+ results[modelName] = {};
50
+
51
+ // Process schema
52
+ const schema = this.connection.model(modelName).schema;
53
+ Object.keys(schema.paths).forEach((key) => {
54
+ const obj: any = schema.obj[key] || {};
55
+ const path = schema.paths[key];
56
+
57
+ results[modelName][key] = {
58
+ isArray: path.instance === 'Array',
59
+ name: path.path,
60
+ ref: Array.isArray(obj) ? undefined : obj.ref,
61
+ type: path.instance === 'Array'
62
+ ? Array.isArray(obj)
63
+ ? obj[0]() === '' ? 'String' : obj[0]()
64
+ : typeof obj.type === 'function' ? obj.type.name : obj.type
65
+ : path.instance,
66
+ };
67
+ if (results[modelName][key].type === 'Mixed') {
68
+ results[modelName][key].type = 'JSON';
69
+ }
70
+ });
71
+ }
72
+
73
+ // Return results
74
+ return results;
75
+ }
76
+
77
+ /**
78
+ * Convert JSON to yUML
79
+ * @param json
80
+ * @protected
81
+ */
82
+ protected jsonToYuml(json: Record<string, Record<string, ModelDocSchemaConfig>>) {
83
+ // Convert JSON to yUML
84
+ let yumlText = '// {type:class}';
85
+ for (const [modelName, properties] of Object.entries(json)) {
86
+ yumlText += `\n[${modelName} | `;
87
+ const refs = [];
88
+ let subYumlText = '';
89
+ for (const [key, value] of Object.entries(properties)) {
90
+ let type = value.type;
91
+ if (value.isArray) {
92
+ type = `Array<${type}>`;
93
+ }
94
+ if (value.ref) {
95
+ refs.push(`[${modelName}]-${key}>[${value.ref}]`);
96
+ }
97
+ if (key.startsWith('__')) {
98
+ continue;
99
+ }
100
+ if (key === '_id') {
101
+ subYumlText = `id: ObjectId; ${subYumlText}`;
102
+ continue;
103
+ }
104
+ subYumlText += `${key}: ${type}; `;
105
+ }
106
+ yumlText += `${subYumlText}]\n`;
107
+ for (const ref of refs) {
108
+ yumlText += `${ref}\n`;
109
+ }
110
+ }
111
+ return yumlText;
112
+ }
113
+
114
+ /**
115
+ * Convert yUML to SVG
116
+ * @param yUmlText
117
+ * @protected
118
+ */
119
+ protected yUmlToSvg(yUmlText: String) {
120
+
121
+ // Create diagrams
122
+ // see https://github.com/jaime-olivares/yuml-diagram
123
+ // and https://yuml.me/diagram/scruffy/class/samples
124
+ const yuml = new YumlDiagram();
125
+ const svgLightBg = yuml.processYumlDocument(yUmlText, false);
126
+ const svgDarkBg = yuml.processYumlDocument(yUmlText, true);
127
+
128
+ // Save diagrams
129
+ fs.writeFile('model-doc-light.svg', svgLightBg, (err) => {
130
+ if (err) {
131
+ console.error(err);
132
+ }
133
+ });
134
+ fs.writeFile('model-doc-dark.svg', svgDarkBg, (err) => {
135
+ if (err) {
136
+ console.error(err);
137
+ }
138
+ });
139
+ }
140
+ }
@@ -27,26 +27,28 @@ export class CoreHealthCheckService {
27
27
 
28
28
  healthCheck(): Promise<HealthCheckResult> {
29
29
  const healthIndicatorFunctions = [];
30
- if (!this.config.get<boolean>('healthCheck.configs.database.disabled')) {
30
+ if (this.config.get<boolean>('healthCheck.configs.database.enabled')) {
31
31
  healthIndicatorFunctions.push(() =>
32
32
  this.db.pingCheck(
33
33
  this.config.get<string>('healthCheck.configs.database.key') ?? 'database',
34
34
  this.config.get<MongoosePingCheckSettings>('healthCheck.configs.database.options') ?? { timeout: 300 },
35
35
  ));
36
36
  }
37
- if (!this.config.get<boolean>('healthCheck.configs.memoryHeap.disabled')) {
37
+ if (this.config.get<boolean>('healthCheck.configs.memoryHeap.enabled')) {
38
38
  healthIndicatorFunctions.push(() => this.memory.checkHeap(
39
39
  this.config.get<string>('healthCheck.configs.memoryHeap.key') ?? 'memoryHeap',
40
- this.config.get<number>('healthCheck.configs.memoryHeap.heapUsedThreshold') ?? 150 * 1024 * 1024,
40
+ // memory in bytes (4GB default)
41
+ this.config.get<number>('healthCheck.configs.memoryHeap.heapUsedThreshold') ?? 4 * 1024 * 1024 * 1024,
41
42
  ));
42
43
  }
43
- if (!this.config.get<boolean>('healthCheck.configs.memoryRss.disabled')) {
44
+ if (this.config.get<boolean>('healthCheck.configs.memoryRss.enabled')) {
44
45
  healthIndicatorFunctions.push(() => this.memory.checkRSS(
45
46
  this.config.get<string>('healthCheck.configs.memoryRss.key') ?? 'memoryRss',
46
- this.config.get<number>('healthCheck.configs.memoryRss.rssThreshold') ?? 150 * 1024 * 1024,
47
+ // memory in bytes (4GB default)
48
+ this.config.get<number>('healthCheck.configs.memoryRss.rssThreshold') ?? 4 * 1024 * 1024 * 1024,
47
49
  ));
48
50
  }
49
- if (!this.config.get<boolean>('healthCheck.configs.storage.disabled')) {
51
+ if (this.config.get<boolean>('healthCheck.configs.storage.enabled')) {
50
52
  healthIndicatorFunctions.push(() => this.disk.checkStorage(
51
53
  this.config.get<string>('healthCheck.configs.storage.key') ?? 'storage',
52
54
  this.config.get<DiskHealthIndicatorOptions>('healthCheck.configs.storage.options') ?? {
@@ -52,7 +52,7 @@ export abstract class CoreUserModel extends CorePersistenceModel {
52
52
  */
53
53
  @Field(type => [String], { description: 'Roles of the user', nullable: true })
54
54
  @IsOptional()
55
- @Prop()
55
+ @Prop([String])
56
56
  roles: string[] = undefined;
57
57
 
58
58
  /**
@@ -14,6 +14,7 @@ import { EmailService } from './core/common/services/email.service';
14
14
  import { MailjetService } from './core/common/services/mailjet.service';
15
15
  import { TemplateService } from './core/common/services/template.service';
16
16
  import { CoreHealthCheckModule } from './core/modules/health-check/core-health-check.module';
17
+ import { ModelDocService } from './core/common/services/model-doc.service';
17
18
 
18
19
  /**
19
20
  * Core module (dynamic)
@@ -127,14 +128,14 @@ export class CoreModule implements NestModule {
127
128
  );
128
129
 
129
130
  // Set providers
130
- const providers = [
131
+ const providers: any[] = [
131
132
  // The ConfigService provides access to the current configuration of the module
132
133
  {
133
134
  provide: ConfigService,
134
135
  useValue: new ConfigService(config),
135
136
  },
136
137
 
137
- // [Global] Map plain objects to metatype and validate
138
+ // [Global] Map plain objects to meta-type and validate
138
139
  {
139
140
  provide: APP_PIPE,
140
141
  useClass: MapAndValidatePipe,
@@ -149,6 +150,10 @@ export class CoreModule implements NestModule {
149
150
  ComplexityPlugin,
150
151
  ];
151
152
 
153
+ if (config.mongoose?.modelDocumentation) {
154
+ providers.push(ModelDocService);
155
+ }
156
+
152
157
  const imports: any[] = [
153
158
  MongooseModule.forRoot(config.mongoose.uri, config.mongoose.options),
154
159
  GraphQLModule.forRootAsync<ApolloDriverConfig>(
package/src/index.ts CHANGED
@@ -64,6 +64,7 @@ export * from './core/common/services/core-cron-jobs.service';
64
64
  export * from './core/common/services/crud.service';
65
65
  export * from './core/common/services/email.service';
66
66
  export * from './core/common/services/mailjet.service';
67
+ export * from './core/common/services/model-doc.service';
67
68
  export * from './core/common/services/module.service';
68
69
  export * from './core/common/services/template.service';
69
70
  export * from './core/common/types/core-model-constructor.type';