@lenne.tech/nest-server 10.0.3 → 10.0.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.
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.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",
@@ -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",
@@ -71,8 +71,8 @@
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",
75
- "@nestjs/terminus": "^10.0.1",
74
+ "@nestjs/schedule": "3.0.2",
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,34 +102,37 @@
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": {
108
- "@lenne.tech/eslint-config-ts": "0.0.8",
109
+ "@babel/plugin-proposal-private-methods": "7.18.6",
110
+ "@compodoc/compodoc": "1.1.21",
111
+ "@lenne.tech/eslint-config-ts": "0.0.9",
109
112
  "@nestjs/cli": "10.1.11",
110
- "@nestjs/schematics": "10.0.1",
113
+ "@nestjs/schematics": "10.0.2",
111
114
  "@nestjs/testing": "10.1.3",
112
115
  "@swc/cli": "0.1.62",
113
- "@swc/core": "1.3.73",
114
- "@swc/jest": "0.2.27",
116
+ "@swc/core": "1.3.76",
117
+ "@swc/jest": "0.2.28",
115
118
  "@types/compression": "1.7.2",
116
119
  "@types/cookie-parser": "1.4.3",
117
120
  "@types/cron": "2.0.1",
118
121
  "@types/ejs": "3.1.2",
119
122
  "@types/express": "4.17.17",
120
123
  "@types/jest": "29.5.3",
121
- "@types/lodash": "4.14.196",
124
+ "@types/lodash": "4.14.197",
122
125
  "@types/multer": "1.4.7",
123
- "@types/node": "20.4.5",
126
+ "@types/node": "20.4.9",
124
127
  "@types/nodemailer": "6.4.9",
125
128
  "@types/passport": "1.0.12",
126
129
  "@types/supertest": "2.0.12",
127
- "@typescript-eslint/eslint-plugin": "6.2.1",
128
- "@typescript-eslint/parser": "6.2.1",
130
+ "@typescript-eslint/eslint-plugin": "6.3.0",
131
+ "@typescript-eslint/parser": "6.3.0",
129
132
  "coffeescript": "2.7.0",
130
133
  "eslint": "8.46.0",
131
- "eslint-config-prettier": "8.9.0",
132
- "eslint-plugin-unused-imports": "^3.0.0",
134
+ "eslint-config-prettier": "9.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",
@@ -140,7 +143,7 @@
140
143
  "jest": "29.6.2",
141
144
  "npm-watch": "0.11.0",
142
145
  "pm2": "5.3.0",
143
- "prettier": "3.0.0",
146
+ "prettier": "3.0.1",
144
147
  "pretty-quick": "3.1.3",
145
148
  "supertest": "6.3.3",
146
149
  "ts-jest": "29.1.1",
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,
@@ -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 field 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
@@ -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;
@@ -176,26 +178,98 @@ export interface IServerOptions {
176
178
  * Whether to activate health check endpoints
177
179
  */
178
180
  healthCheck?: {
181
+
182
+ /**
183
+ * Whether health check is enabled
184
+ */
179
185
  enabled?: boolean;
186
+
187
+ /**
188
+ * Configuration of single health checks
189
+ */
180
190
  configs?: {
191
+
192
+ /**
193
+ * Configuration for database health check
194
+ */
181
195
  database?: {
182
- disabled?: boolean;
196
+
197
+ /**
198
+ * Whether to enable the database health check
199
+ */
200
+ enabled?: boolean;
201
+
202
+ /**
203
+ * Key in result JSON
204
+ */
183
205
  key?: string;
206
+
207
+ /**
208
+ * Database health check options
209
+ */
184
210
  options?: MongoosePingCheckSettings;
185
211
  };
212
+
213
+ /**
214
+ * Configuration for memory heap health check
215
+ */
186
216
  memoryHeap?: {
187
- disabled?: boolean;
217
+
218
+ /**
219
+ * Whether to enable the memory heap health check
220
+ */
221
+ enabled?: boolean;
222
+
223
+ /**
224
+ * Key in result JSON
225
+ */
188
226
  key?: string;
227
+
228
+ /**
229
+ * Memory limit in bytes
230
+ */
189
231
  heapUsedThreshold?: number;
190
232
  };
233
+
234
+ /**
235
+ * Configuration for memory resident set size health check
236
+ */
191
237
  memoryRss?: {
192
- disabled?: boolean;
238
+
239
+ /**
240
+ * Whether to enable the memory resident set size health check
241
+ */
242
+ enabled?: boolean;
243
+
244
+ /**
245
+ * Key in result JSON
246
+ */
193
247
  key?: string;
248
+
249
+ /**
250
+ * Memory limit in bytes
251
+ */
194
252
  rssThreshold?: number;
195
253
  };
254
+
255
+ /**
256
+ * Configuration for disk space health check
257
+ */
196
258
  storage?: {
197
- disabled?: boolean;
259
+
260
+ /**
261
+ * Whether to enable the disk space health check
262
+ */
263
+ enabled?: boolean;
264
+
265
+ /**
266
+ * Key in result JSON
267
+ */
198
268
  key?: string;
269
+
270
+ /**
271
+ * Disk health indicator options
272
+ */
199
273
  options?: DiskHealthIndicatorOptions;
200
274
  };
201
275
  };
@@ -247,8 +321,19 @@ export interface IServerOptions {
247
321
  * Configuration for Mongoose
248
322
  */
249
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
+ */
250
329
  collation?: CollationOptions;
251
330
 
331
+ /**
332
+ * Whether to create SVG-Diagrams of mongoose models
333
+ * @beta
334
+ */
335
+ modelDocumentation?: boolean;
336
+
252
337
  /**
253
338
  * Mongoose connection string
254
339
  */
@@ -258,6 +343,15 @@ export interface IServerOptions {
258
343
  * Mongoose module options
259
344
  */
260
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;
261
355
  };
262
356
 
263
357
  /**
@@ -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
  /**
@@ -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';
@@ -14,6 +15,7 @@ import { EmailService } from './core/common/services/email.service';
14
15
  import { MailjetService } from './core/common/services/mailjet.service';
15
16
  import { TemplateService } from './core/common/services/template.service';
16
17
  import { CoreHealthCheckModule } from './core/modules/health-check/core-health-check.module';
18
+ import { ModelDocService } from './core/common/services/model-doc.service';
17
19
 
18
20
  /**
19
21
  * Core module (dynamic)
@@ -127,14 +129,14 @@ export class CoreModule implements NestModule {
127
129
  );
128
130
 
129
131
  // Set providers
130
- const providers = [
132
+ const providers: any[] = [
131
133
  // The ConfigService provides access to the current configuration of the module
132
134
  {
133
135
  provide: ConfigService,
134
136
  useValue: new ConfigService(config),
135
137
  },
136
138
 
137
- // [Global] Map plain objects to metatype and validate
139
+ // [Global] Map plain objects to meta-type and validate
138
140
  {
139
141
  provide: APP_PIPE,
140
142
  useClass: MapAndValidatePipe,
@@ -149,6 +151,15 @@ export class CoreModule implements NestModule {
149
151
  ComplexityPlugin,
150
152
  ];
151
153
 
154
+ if (config.mongoose?.modelDocumentation) {
155
+ providers.push(ModelDocService);
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
+
152
163
  const imports: any[] = [
153
164
  MongooseModule.forRoot(config.mongoose.uri, config.mongoose.options),
154
165
  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';