@mikro-orm/entity-generator 7.0.3-dev.9 → 7.0.3

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/SourceFile.js CHANGED
@@ -1,4 +1,13 @@
1
- import { Cascade, Config, DecimalType, ReferenceKind, SCALAR_TYPES, UnknownType, Utils, inspect, } from '@mikro-orm/core';
1
+ import {
2
+ Cascade,
3
+ Config,
4
+ DecimalType,
5
+ ReferenceKind,
6
+ SCALAR_TYPES,
7
+ UnknownType,
8
+ Utils,
9
+ inspect,
10
+ } from '@mikro-orm/core';
2
11
  import { parse, relative } from 'node:path';
3
12
  import { POSSIBLE_TYPE_IMPORTS } from './CoreImportsHelper.js';
4
13
  /**
@@ -7,944 +16,1003 @@ import { POSSIBLE_TYPE_IMPORTS } from './CoreImportsHelper.js';
7
16
  export const identifierRegex = /^(?:[$_\p{ID_Start}])(?:[$\u200C\u200D\p{ID_Continue}])*$/u;
8
17
  const primitivesAndLibs = [...SCALAR_TYPES, 'unknown', 'object', 'any'];
9
18
  export class SourceFile {
10
- meta;
11
- namingStrategy;
12
- platform;
13
- options;
14
- coreImports = new Set();
15
- decoratorImports = new Set();
16
- entityImports = new Set();
17
- enumImports = new Map();
18
- constructor(meta, namingStrategy, platform, options) {
19
- this.meta = meta;
20
- this.namingStrategy = namingStrategy;
21
- this.platform = platform;
22
- this.options = options;
23
- }
24
- generate() {
25
- let ret = '';
26
- if (this.meta.embeddable || this.meta.collection) {
27
- if (this.meta.embeddable) {
28
- const options = this.getEmbeddableDeclOptions();
29
- ret += `@${this.referenceDecoratorImport('Embeddable')}(${Utils.hasObjectKeys(options) ? this.serializeObject(options) : ''})\n`;
30
- }
31
- else {
32
- const options = this.getEntityDeclOptions();
33
- ret += `@${this.referenceDecoratorImport('Entity')}(${Utils.hasObjectKeys(options) ? this.serializeObject(options) : ''})\n`;
34
- }
35
- }
36
- for (const index of this.meta.indexes) {
37
- if (index.properties?.length === 1 && typeof this.meta.properties[index.properties[0]] !== 'undefined') {
38
- continue;
39
- }
40
- ret += `@${this.referenceDecoratorImport('Index')}(${this.serializeObject(this.getIndexOptions(index))})\n`;
41
- }
42
- for (const index of this.meta.uniques) {
43
- if (index.properties?.length === 1 && typeof this.meta.properties[index.properties[0]] !== 'undefined') {
44
- continue;
45
- }
46
- ret += `@${this.referenceDecoratorImport('Unique')}(${this.serializeObject(this.getUniqueOptions(index))})\n`;
47
- }
48
- let classHead = '';
49
- if (this.meta.className === this.options.customBaseEntityName) {
50
- const defineConfigTypeSettings = {};
51
- defineConfigTypeSettings.forceObject = this.platform.getConfig().get('serialization').forceObject ?? false;
52
- classHead += `\n${' '.repeat(2)}[${this.referenceCoreImport('Config')}]?: ${this.referenceCoreImport('DefineConfig')}<${this.serializeObject(defineConfigTypeSettings)}>;\n\n`;
53
- }
54
- if (this.meta.repositoryClass) {
55
- this.entityImports.add(this.meta.repositoryClass);
56
- classHead += `\n${' '.repeat(2)}[${this.referenceCoreImport('EntityRepositoryType')}]?: ${this.meta.repositoryClass};\n`;
57
- }
58
- const enumDefinitions = [];
59
- const eagerProperties = [];
60
- const primaryProps = [];
61
- let classBody = '';
62
- Object.values(this.meta.properties).forEach(prop => {
63
- const decorator = this.getPropertyDecorator(prop, 2);
64
- const definition = this.getPropertyDefinition(prop, 2);
65
- classBody += decorator;
66
- classBody += definition;
67
- classBody += '\n';
68
- if (prop.enum) {
69
- const def = this.getEnumClassDefinition(prop, 2);
70
- if (def.length) {
71
- enumDefinitions.push(def);
72
- }
73
- }
74
- if (prop.eager) {
75
- eagerProperties.push(prop);
76
- }
77
- if (prop.primary && (!['id', '_id', 'uuid'].includes(prop.name) || this.meta.compositePK)) {
78
- primaryProps.push(prop);
79
- }
80
- });
81
- if (primaryProps.length > 0) {
82
- const primaryPropNames = primaryProps.map(prop => `'${prop.name}'`);
83
- if (primaryProps.length > 1) {
84
- classHead += `\n${' '.repeat(2)}[${this.referenceCoreImport('PrimaryKeyProp')}]?: [${primaryPropNames.join(', ')}];\n`;
85
- }
86
- else {
87
- classHead += `\n${' '.repeat(2)}[${this.referenceCoreImport('PrimaryKeyProp')}]?: ${primaryPropNames[0]};\n`;
88
- }
89
- }
90
- if (eagerProperties.length > 0) {
91
- const eagerPropertyNames = eagerProperties.map(prop => `'${prop.name}'`).sort();
92
- classHead += `\n${' '.repeat(2)}[${this.referenceCoreImport('EagerProps')}]?: ${eagerPropertyNames.join(' | ')};\n`;
93
- }
94
- ret += this.getEntityClass(classBody ? `${classHead}\n${classBody}` : classHead);
95
- ret = `${this.generateImports()}\n\n${enumDefinitions.length ? enumDefinitions.join('\n') + '\n' : ''}${ret}`;
96
- return ret;
97
- }
98
- /**
99
- * Convert index column options to quoted output format.
100
- */
101
- getColumnOptions(columns) {
102
- if (!columns?.length) {
103
- return undefined;
104
- }
105
- return columns.map(col => {
106
- const colOpt = { name: this.quote(col.name) };
107
- if (col.sort) {
108
- colOpt.sort = this.quote(col.sort.toUpperCase());
109
- }
110
- if (col.nulls) {
111
- colOpt.nulls = this.quote(col.nulls.toUpperCase());
112
- }
113
- if (col.length != null) {
114
- colOpt.length = col.length;
115
- }
116
- if (col.collation) {
117
- colOpt.collation = this.quote(col.collation);
118
- }
119
- return colOpt;
120
- });
19
+ meta;
20
+ namingStrategy;
21
+ platform;
22
+ options;
23
+ coreImports = new Set();
24
+ decoratorImports = new Set();
25
+ entityImports = new Set();
26
+ enumImports = new Map();
27
+ constructor(meta, namingStrategy, platform, options) {
28
+ this.meta = meta;
29
+ this.namingStrategy = namingStrategy;
30
+ this.platform = platform;
31
+ this.options = options;
32
+ }
33
+ generate() {
34
+ let ret = '';
35
+ if (this.meta.embeddable || this.meta.collection) {
36
+ if (this.meta.embeddable) {
37
+ const options = this.getEmbeddableDeclOptions();
38
+ ret += `@${this.referenceDecoratorImport('Embeddable')}(${Utils.hasObjectKeys(options) ? this.serializeObject(options) : ''})\n`;
39
+ } else {
40
+ const options = this.getEntityDeclOptions();
41
+ ret += `@${this.referenceDecoratorImport('Entity')}(${Utils.hasObjectKeys(options) ? this.serializeObject(options) : ''})\n`;
42
+ }
121
43
  }
122
- getIndexOptions(index, isAtEntityLevel = true) {
123
- const indexOpt = {};
124
- if (typeof index.name === 'string') {
125
- indexOpt.name = this.quote(index.name);
126
- }
127
- if (typeof index.expression === 'string') {
128
- indexOpt.expression = this.quote(index.expression);
129
- }
130
- else if (typeof index.expression === 'function') {
131
- indexOpt.expression = `${index.expression}`.replace(')=>`', ') => `');
132
- }
133
- if (isAtEntityLevel && index.properties) {
134
- indexOpt.properties = Utils.asArray(index.properties).map(prop => this.quote('' + prop));
135
- }
136
- // Index type (e.g., 'fulltext', 'spatial', 'btree', 'hash')
137
- if (index.type) {
138
- indexOpt.type = this.quote(index.type);
139
- }
140
- // Advanced index options
141
- const columns = this.getColumnOptions(index.columns);
142
- if (columns) {
143
- indexOpt.columns = columns;
144
- }
145
- if (index.include) {
146
- indexOpt.include = Utils.asArray(index.include).map(prop => this.quote('' + prop));
147
- }
148
- if (index.fillFactor != null) {
149
- indexOpt.fillFactor = index.fillFactor;
150
- }
151
- if (index.invisible) {
152
- indexOpt.invisible = true;
153
- }
154
- if (index.disabled) {
155
- indexOpt.disabled = true;
156
- }
157
- if (index.clustered) {
158
- indexOpt.clustered = true;
159
- }
160
- return indexOpt;
44
+ for (const index of this.meta.indexes) {
45
+ if (index.properties?.length === 1 && typeof this.meta.properties[index.properties[0]] !== 'undefined') {
46
+ continue;
47
+ }
48
+ ret += `@${this.referenceDecoratorImport('Index')}(${this.serializeObject(this.getIndexOptions(index))})\n`;
161
49
  }
162
- getUniqueOptions(index, isAtEntityLevel = true) {
163
- const uniqueOpt = {};
164
- if (typeof index.name === 'string') {
165
- uniqueOpt.name = this.quote(index.name);
166
- }
167
- if (typeof index.expression === 'string') {
168
- uniqueOpt.expression = this.quote(index.expression);
169
- }
170
- else if (typeof index.expression === 'function') {
171
- uniqueOpt.expression = `${index.expression}`.replace(')=>`', ') => `');
172
- }
173
- if (isAtEntityLevel && index.properties) {
174
- uniqueOpt.properties = Utils.asArray(index.properties).map(prop => this.quote('' + prop));
175
- }
176
- if (index.deferMode) {
177
- uniqueOpt.deferMode =
178
- `${this.referenceCoreImport('DeferMode')}.INITIALLY_${index.deferMode.toUpperCase()}`;
179
- }
180
- const columns = this.getColumnOptions(index.columns);
181
- if (columns) {
182
- uniqueOpt.columns = columns;
183
- }
184
- if (index.include) {
185
- uniqueOpt.include = Utils.asArray(index.include).map(prop => this.quote('' + prop));
186
- }
187
- if (index.fillFactor != null) {
188
- uniqueOpt.fillFactor = index.fillFactor;
189
- }
190
- if (index.disabled) {
191
- uniqueOpt.disabled = true;
192
- }
193
- return uniqueOpt;
194
- }
195
- generateImports() {
196
- const imports = new Set();
197
- if (this.coreImports.size > 0) {
198
- imports.add(`import { ${[...this.coreImports]
199
- .sort()
200
- .map(t => {
201
- let ret = POSSIBLE_TYPE_IMPORTS.includes(t) ? `type ${t}` : t;
202
- if (this.options.coreImportsPrefix) {
203
- const resolvedIdentifier = `${this.options.coreImportsPrefix}${t}`;
204
- ret += ` as ${resolvedIdentifier}`;
205
- }
206
- return ret;
207
- })
208
- .join(', ')} } from '@mikro-orm/core';`);
209
- }
210
- if (this.decoratorImports.size > 0) {
211
- const type = this.options.decorators;
212
- imports.add(`import { ${[...this.decoratorImports]
213
- .sort()
214
- .map(t => {
215
- let ret = t;
216
- if (this.options.coreImportsPrefix) {
217
- const resolvedIdentifier = `${this.options.coreImportsPrefix}${t}`;
218
- ret += ` as ${resolvedIdentifier}`;
219
- }
220
- return ret;
221
- })
222
- .join(', ')} } from '@mikro-orm/decorators/${type}';`);
223
- }
224
- const extension = this.options.esmImport ? '.js' : '';
225
- const { dir, base } = parse(`${this.options.path ?? '.'}/${this.getBaseName()}`);
226
- const basePath = relative(dir, this.options.path ?? '.') || '.';
227
- (this.options.extraImports?.(basePath, base) ?? []).forEach(v => this.entityImports.add(v));
228
- const entityImports = [...this.entityImports].filter(e => e !== this.meta.className);
229
- const importMap = new Map();
230
- for (const entity of entityImports) {
231
- const file = this.options.onImport?.(entity, basePath, extension, base) ?? {
232
- path: `${basePath}/${this.options.fileName(entity)}${extension}`,
233
- name: entity,
234
- };
235
- if (file.path === '') {
236
- if (file.name === '') {
237
- continue;
238
- }
239
- importMap.set(file.path, `import ${this.quote(file.name)};`);
240
- continue;
241
- }
242
- if (file.name === '') {
243
- importMap.set(file.path, `import * as ${entity} from ${this.quote(file.path)};`);
244
- continue;
245
- }
246
- if (file.name === 'default') {
247
- importMap.set(file.path, `import ${entity} from ${this.quote(file.path)};`);
248
- continue;
249
- }
250
- if (file.name === entity) {
251
- importMap.set(file.path, `import { ${entity} } from ${this.quote(file.path)};`);
252
- continue;
253
- }
254
- importMap.set(file.path, `import { ${identifierRegex.test(file.name) ? file.name : this.quote(file.name)} as ${entity} } from ${this.quote(file.path)};`);
255
- }
256
- if (this.enumImports.size) {
257
- for (const [name, exports] of this.enumImports.entries()) {
258
- const file = this.options.onImport?.(name, basePath, extension, base) ?? {
259
- path: `${basePath}/${this.options.fileName(name)}${extension}`,
260
- name,
261
- };
262
- importMap.set(file.path, `import { ${exports.join(', ')} } from ${this.quote(file.path)};`);
263
- }
264
- }
265
- for (const key of [...importMap.keys()].sort()) {
266
- imports.add(importMap.get(key));
267
- }
268
- return Array.from(imports.values()).join('\n');
50
+ for (const index of this.meta.uniques) {
51
+ if (index.properties?.length === 1 && typeof this.meta.properties[index.properties[0]] !== 'undefined') {
52
+ continue;
53
+ }
54
+ ret += `@${this.referenceDecoratorImport('Unique')}(${this.serializeObject(this.getUniqueOptions(index))})\n`;
269
55
  }
270
- getEntityClass(classBody) {
271
- let ret = `export `;
272
- if (this.meta.abstract) {
273
- ret += `abstract `;
274
- }
275
- ret += `class ${this.meta.className}`;
276
- if (this.meta.extends) {
277
- this.entityImports.add(Utils.className(this.meta.extends));
278
- ret += ` extends ${Utils.className(this.meta.extends)}`;
279
- }
280
- else if (this.options.useCoreBaseEntity) {
281
- ret += ` extends ${this.referenceCoreImport('BaseEntity')}`;
282
- }
283
- ret += ` {\n${classBody}}\n`;
284
- return ret;
285
- }
286
- getBaseName(extension = '.ts') {
287
- return `${this.options.fileName(this.meta.className)}${extension}`;
288
- }
289
- quote(val) {
290
- const backtick = val.startsWith(`'`) || val.includes('\n');
291
- /* v8 ignore next */
292
- return backtick ? `\`${val.replaceAll('`', '\\``')}\`` : `'${val.replaceAll(`'`, `\\'`)}'`;
293
- }
294
- getPropertyDefinition(prop, padLeft) {
295
- const padding = ' '.repeat(padLeft);
296
- const propName = identifierRegex.test(prop.name) ? prop.name : this.quote(prop.name);
297
- const enumMode = this.options.enumMode;
298
- let hiddenType = '';
299
- if (prop.hidden) {
300
- hiddenType += ` & ${this.referenceCoreImport('Hidden')}`;
301
- }
302
- if ([ReferenceKind.ONE_TO_MANY, ReferenceKind.MANY_TO_MANY].includes(prop.kind)) {
303
- return `${padding}${propName}${hiddenType ? `: ${this.referenceCoreImport('Collection')}<${prop.type}>${hiddenType}` : ''} = new ${this.referenceCoreImport('Collection')}<${prop.type}>(this);\n`;
304
- }
305
- const isScalar = typeof prop.kind === 'undefined' || prop.kind === ReferenceKind.SCALAR;
306
- let breakdownOfIType;
307
- const propType = prop.mapToPk
308
- ? (() => {
309
- const runtimeTypes = prop.columnTypes.map((t, i) => (prop.customTypes?.[i] ?? this.platform.getMappedType(t)).runtimeType);
310
- return runtimeTypes.length === 1 ? runtimeTypes[0] : this.serializeObject(runtimeTypes);
311
- })()
312
- : (() => {
313
- if (isScalar) {
314
- if (prop.enum) {
315
- const method = enumMode === 'ts-enum' ? 'getEnumClassName' : 'getEnumTypeName';
316
- if (prop.nativeEnumName) {
317
- return this.namingStrategy[method](prop.nativeEnumName, undefined, this.meta.schema);
318
- }
319
- return this.namingStrategy[method](prop.fieldNames[0], this.meta.collection, this.meta.schema);
320
- }
321
- breakdownOfIType = this.breakdownOfIType(prop);
322
- if (typeof breakdownOfIType !== 'undefined') {
323
- if (breakdownOfIType.length >= 3) {
324
- hiddenType = '';
325
- }
326
- return `${this.referenceCoreImport('IType')}<${breakdownOfIType.join(', ')}>`;
327
- }
328
- return prop.runtimeType;
329
- }
330
- return prop.type;
331
- })();
332
- const hasUsableNullDefault = prop.nullable && !this.options.forceUndefined && prop.default === null;
333
- const useDefault = hasUsableNullDefault ||
334
- (!(typeof prop.default === 'undefined' || prop.default === null) &&
335
- propType !== 'unknown' &&
336
- typeof breakdownOfIType === 'undefined');
337
- const optional = prop.nullable && (this.options.forceUndefined || prop.optional) ? '?' : useDefault ? '' : '!';
338
- let ret = `${propName}${optional}: `;
339
- const isArray = prop.array && (prop.kind === ReferenceKind.EMBEDDED || prop.enum);
340
- const complexType = isArray ? `${propType}[]` : propType;
341
- let wrappedType = prop.ref
342
- ? `${this.referenceCoreImport('Ref')}<${complexType}${isScalar && prop.nullable && !this.options.forceUndefined ? ' | null' : ''}>`
343
- : this.options.esmImport && !isScalar
344
- ? `${this.referenceCoreImport('Rel')}<${complexType}>`
345
- : complexType;
346
- if (prop.nullable &&
347
- !this.options.forceUndefined &&
348
- (!isScalar || (!prop.ref && !wrappedType.includes(' | null')))) {
349
- wrappedType += ' | null';
350
- }
351
- const optionalType = optional !== '?' && prop.optional ? ` & ${this.referenceCoreImport('Opt')}` : '';
352
- ret +=
353
- !this.options.forceUndefined && prop.nullable && (hiddenType || optionalType) ? `(${wrappedType})` : wrappedType;
354
- ret += hiddenType;
355
- ret += optionalType;
356
- if (!useDefault) {
357
- return `${padding}${ret};\n`;
358
- }
359
- if (prop.enum && typeof prop.default === 'string') {
360
- if (enumMode === 'union-type') {
361
- return `${padding}${ret} = ${this.quote(prop.default)};\n`;
362
- }
363
- const enumClassName = prop.nativeEnumName
364
- ? this.namingStrategy.getEnumClassName(prop.nativeEnumName, undefined, this.meta.schema)
365
- : this.namingStrategy.getEnumClassName(prop.fieldNames[0], this.meta.collection, this.meta.schema);
366
- const enumVal = this.namingStrategy.enumValueToEnumProperty(prop.default, prop.fieldNames[0], this.meta.collection, this.meta.schema);
367
- return `${padding}${ret} = ${enumClassName}${identifierRegex.test(enumVal) ? `.${enumVal}` : `[${this.quote(enumVal)}]`};\n`;
368
- }
369
- if (prop.fieldNames?.length > 1) {
370
- // TODO: Composite FKs with default values require additions to default/defaultRaw that are not yet supported.
371
- return `${padding}${ret};\n`;
372
- }
373
- const defaultVal = typeof prop.default === 'string' ? this.quote(prop.default) : prop.default;
374
- if (isScalar) {
375
- return `${padding}${ret} = ${prop.ref ? `${this.referenceCoreImport('ref')}(${defaultVal})` : defaultVal};\n`;
376
- }
377
- if (hasUsableNullDefault) {
378
- return `${padding}${ret} = null;\n`;
379
- }
380
- return `${padding}${ret} = ${prop.ref ? this.referenceCoreImport('ref') : this.referenceCoreImport('rel')}(${propType}, ${defaultVal});\n`;
381
- }
382
- getEnumClassDefinition(prop, padLeft) {
383
- const enumMode = this.options.enumMode;
384
- if (prop.nativeEnumName) {
385
- const imports = [];
386
- if (enumMode !== 'union-type') {
387
- imports.push(prop.runtimeType);
388
- }
389
- if (!this.options.inferEntityType && enumMode !== 'ts-enum') {
390
- const enumTypeName = this.namingStrategy.getEnumTypeName(prop.nativeEnumName, undefined, this.meta.schema);
391
- imports.push(enumTypeName);
392
- }
393
- this.enumImports.set(prop.runtimeType, imports);
394
- return '';
395
- }
396
- const enumClassName = this.namingStrategy.getEnumClassName(prop.fieldNames[0], this.meta.collection, this.meta.schema);
397
- const enumTypeName = this.namingStrategy.getEnumTypeName(prop.fieldNames[0], this.meta.collection, this.meta.schema);
398
- const padding = ' '.repeat(padLeft);
399
- const enumValues = prop.items;
400
- if (enumMode === 'union-type') {
401
- return `export type ${enumTypeName} = ${enumValues.map(item => this.quote(item)).join(' | ')};\n`;
402
- }
403
- let ret = '';
404
- if (enumMode === 'dictionary') {
405
- ret += `export const ${enumClassName} = {\n`;
406
- }
407
- else {
408
- ret += `export enum ${enumClassName} {\n`;
409
- }
410
- for (const enumValue of enumValues) {
411
- const enumName = this.namingStrategy.enumValueToEnumProperty(enumValue, prop.fieldNames[0], this.meta.collection, this.meta.schema);
412
- if (enumMode === 'dictionary') {
413
- ret += `${padding}${identifierRegex.test(enumName) ? enumName : this.quote(enumName)}: ${this.quote(enumValue)},\n`;
414
- }
415
- else {
416
- ret += `${padding}${identifierRegex.test(enumName) ? enumName : this.quote(enumName)} = ${this.quote(enumValue)},\n`;
417
- }
418
- }
419
- if (enumMode === 'dictionary') {
420
- ret += '} as const;\n';
421
- }
422
- else {
423
- ret += '}\n';
424
- }
425
- if (enumMode === 'dictionary') {
426
- ret += `\nexport type ${enumTypeName} = (typeof ${enumClassName})[keyof typeof ${enumClassName}];\n`;
427
- }
428
- return ret;
56
+ let classHead = '';
57
+ if (this.meta.className === this.options.customBaseEntityName) {
58
+ const defineConfigTypeSettings = {};
59
+ defineConfigTypeSettings.forceObject = this.platform.getConfig().get('serialization').forceObject ?? false;
60
+ classHead += `\n${' '.repeat(2)}[${this.referenceCoreImport('Config')}]?: ${this.referenceCoreImport('DefineConfig')}<${this.serializeObject(defineConfigTypeSettings)}>;\n\n`;
429
61
  }
430
- serializeObject(options, wordwrap, spaces, level = 0) {
431
- if (typeof wordwrap === 'number' && !Object.hasOwn(options, Config)) {
432
- const res = this.serializeObject(options, undefined, undefined, level);
433
- if (res.length <= wordwrap) {
434
- return res;
435
- }
436
- }
437
- const nextWordwrap = typeof wordwrap === 'number' ? 80 - (spaces ?? 0) - level * 2 : undefined;
438
- const sep = typeof spaces === 'undefined' ? ', ' : `,\n${' '.repeat(spaces)}`;
439
- const doIndent = typeof spaces !== 'undefined';
440
- if (Array.isArray(options)) {
441
- return `[${doIndent ? `\n${' '.repeat(spaces)}` : ''}${options.map(val => `${doIndent ? ' '.repeat(level * 2 + (spaces + 2)) : ''}${this.serializeValue(val, typeof nextWordwrap === 'number' ? nextWordwrap : undefined, doIndent ? spaces : undefined, level + 1)}`).join(sep)}${doIndent ? `${options.length > 0 ? ',\n' : ''}${' '.repeat(spaces + level * 2)}` : ''}]`;
442
- }
443
- const entries = Object.entries(options);
444
- return `{${doIndent ? `\n${' '.repeat(spaces)}` : ' '}${entries
445
- .map(([opt, val]) => {
446
- const key = identifierRegex.test(opt) ? opt : this.quote(opt);
447
- return `${doIndent ? ' '.repeat(level * 2 + (spaces + 2)) : ''}${key}: ${this.serializeValue(val, typeof nextWordwrap === 'number' ? nextWordwrap - key.length - 2 /* ': '.length*/ : undefined, doIndent ? spaces : undefined, level + 1)}`;
448
- })
449
- .join(sep)}${doIndent ? `${entries.length > 0 ? ',\n' : ''}${' '.repeat(spaces + level * 2)}` : ' '}}`;
450
- }
451
- serializeValue(val, wordwrap, spaces, level = 1) {
452
- if (typeof val === 'object' && val !== null) {
453
- return this.serializeObject(val, wordwrap, spaces, level);
454
- }
455
- return val;
62
+ if (this.meta.repositoryClass) {
63
+ this.entityImports.add(this.meta.repositoryClass);
64
+ classHead += `\n${' '.repeat(2)}[${this.referenceCoreImport('EntityRepositoryType')}]?: ${this.meta.repositoryClass};\n`;
456
65
  }
457
- getEntityDeclOptions() {
458
- const options = {};
459
- if (this.meta.tableName !== this.namingStrategy.classToTableName(this.meta.className)) {
460
- options.tableName = this.quote(this.meta.tableName);
461
- }
462
- if (this.meta.schema && this.meta.schema !== this.platform.getDefaultSchemaName()) {
463
- options.schema = this.quote(this.meta.schema);
464
- }
465
- if (typeof this.meta.expression === 'string') {
466
- options.expression = this.quote(this.meta.expression);
467
- }
468
- else if (typeof this.meta.expression === 'function') {
469
- options.expression = this.meta.expression.toString();
470
- }
471
- if (this.meta.repositoryClass) {
472
- this.entityImports.add(this.meta.repositoryClass);
473
- options.repository = `() => ${this.meta.repositoryClass}`;
474
- }
475
- if (this.meta.comment) {
476
- options.comment = this.quote(this.meta.comment);
477
- }
478
- if (this.meta.readonly && !this.meta.virtual) {
479
- options.readonly = this.meta.readonly;
480
- }
481
- if (this.meta.virtual) {
482
- options.virtual = this.meta.virtual;
483
- }
484
- return this.getCollectionDecl(options);
485
- }
486
- getEmbeddableDeclOptions() {
487
- const options = {};
488
- const result = this.getCollectionDecl(options);
489
- if (result.discriminatorColumn) {
490
- result.discriminator = result.discriminatorColumn;
491
- delete result.discriminatorColumn;
492
- }
493
- return result;
66
+ const enumDefinitions = [];
67
+ const eagerProperties = [];
68
+ const primaryProps = [];
69
+ let classBody = '';
70
+ Object.values(this.meta.properties).forEach(prop => {
71
+ const decorator = this.getPropertyDecorator(prop, 2);
72
+ const definition = this.getPropertyDefinition(prop, 2);
73
+ classBody += decorator;
74
+ classBody += definition;
75
+ classBody += '\n';
76
+ if (prop.enum) {
77
+ const def = this.getEnumClassDefinition(prop, 2);
78
+ if (def.length) {
79
+ enumDefinitions.push(def);
80
+ }
81
+ }
82
+ if (prop.eager) {
83
+ eagerProperties.push(prop);
84
+ }
85
+ if (prop.primary && (!['id', '_id', 'uuid'].includes(prop.name) || this.meta.compositePK)) {
86
+ primaryProps.push(prop);
87
+ }
88
+ });
89
+ if (primaryProps.length > 0) {
90
+ const primaryPropNames = primaryProps.map(prop => `'${prop.name}'`);
91
+ if (primaryProps.length > 1) {
92
+ classHead += `\n${' '.repeat(2)}[${this.referenceCoreImport('PrimaryKeyProp')}]?: [${primaryPropNames.join(', ')}];\n`;
93
+ } else {
94
+ classHead += `\n${' '.repeat(2)}[${this.referenceCoreImport('PrimaryKeyProp')}]?: ${primaryPropNames[0]};\n`;
95
+ }
494
96
  }
495
- getCollectionDecl(options) {
496
- if (this.meta.abstract) {
497
- options.abstract = true;
498
- }
499
- if (this.meta.discriminatorValue) {
500
- options.discriminatorValue =
501
- typeof this.meta.discriminatorValue === 'string'
502
- ? this.quote(this.meta.discriminatorValue)
503
- : this.meta.discriminatorValue;
504
- }
505
- if (this.meta.discriminatorColumn) {
506
- options.discriminatorColumn = this.quote(this.meta.discriminatorColumn);
507
- }
508
- if (this.meta.discriminatorMap) {
509
- options.discriminatorMap = Object.fromEntries(Object.entries(this.meta.discriminatorMap).map(([discriminatorValue, cls]) => [
510
- discriminatorValue,
511
- this.quote(Utils.className(cls)),
512
- ]));
513
- }
514
- return options;
515
- }
516
- getPropertyDecorator(prop, padLeft) {
517
- const padding = ' '.repeat(padLeft);
518
- const options = {};
519
- let decorator = `@${this.referenceDecoratorImport(this.getDecoratorType(prop))}`;
520
- if (prop.kind === ReferenceKind.MANY_TO_MANY) {
521
- this.getManyToManyDecoratorOptions(options, prop);
522
- }
523
- else if (prop.kind === ReferenceKind.ONE_TO_MANY) {
524
- this.getOneToManyDecoratorOptions(options, prop);
525
- }
526
- else if (prop.kind === ReferenceKind.SCALAR || typeof prop.kind === 'undefined') {
527
- this.getScalarPropertyDecoratorOptions(options, prop);
528
- }
529
- else if (prop.kind === ReferenceKind.EMBEDDED) {
530
- this.getEmbeddedPropertyDeclarationOptions(options, prop);
531
- }
532
- else {
533
- this.getForeignKeyDecoratorOptions(options, prop);
534
- }
535
- this.getCommonDecoratorOptions(options, prop);
536
- const indexes = this.getPropertyIndexes(prop, options);
537
- decorator = [...indexes.sort(), decorator].map(d => padding + d).join('\n');
538
- const decoratorArgs = [];
539
- if (prop.formula) {
540
- decoratorArgs.push(prop.formula.toString());
541
- }
542
- if (Utils.hasObjectKeys(options)) {
543
- decoratorArgs.push(this.serializeObject(options));
544
- }
545
- return `${decorator}(${decoratorArgs.join(', ')})\n`;
97
+ if (eagerProperties.length > 0) {
98
+ const eagerPropertyNames = eagerProperties.map(prop => `'${prop.name}'`).sort();
99
+ classHead += `\n${' '.repeat(2)}[${this.referenceCoreImport('EagerProps')}]?: ${eagerPropertyNames.join(' | ')};\n`;
546
100
  }
547
- getPropertyIndexes(prop, options) {
548
- const processIndex = (type) => {
549
- const propType = prop[type];
550
- if (!propType) {
551
- return;
552
- }
553
- const defaultName = this.platform.getIndexName(this.meta.collection, prop.fieldNames, type);
554
- options[type] = propType === true || defaultName === propType ? 'true' : this.quote(propType);
555
- const expected = {
556
- index: this.platform.indexForeignKeys(),
557
- unique: prop.kind === ReferenceKind.ONE_TO_ONE,
558
- };
559
- if (expected[type] && options[type] === 'true') {
560
- delete options[type];
561
- }
101
+ ret += this.getEntityClass(classBody ? `${classHead}\n${classBody}` : classHead);
102
+ ret = `${this.generateImports()}\n\n${enumDefinitions.length ? enumDefinitions.join('\n') + '\n' : ''}${ret}`;
103
+ return ret;
104
+ }
105
+ /**
106
+ * Convert index column options to quoted output format.
107
+ */
108
+ getColumnOptions(columns) {
109
+ if (!columns?.length) {
110
+ return undefined;
111
+ }
112
+ return columns.map(col => {
113
+ const colOpt = { name: this.quote(col.name) };
114
+ if (col.sort) {
115
+ colOpt.sort = this.quote(col.sort.toUpperCase());
116
+ }
117
+ if (col.nulls) {
118
+ colOpt.nulls = this.quote(col.nulls.toUpperCase());
119
+ }
120
+ if (col.length != null) {
121
+ colOpt.length = col.length;
122
+ }
123
+ if (col.collation) {
124
+ colOpt.collation = this.quote(col.collation);
125
+ }
126
+ return colOpt;
127
+ });
128
+ }
129
+ getIndexOptions(index, isAtEntityLevel = true) {
130
+ const indexOpt = {};
131
+ if (typeof index.name === 'string') {
132
+ indexOpt.name = this.quote(index.name);
133
+ }
134
+ if (typeof index.expression === 'string') {
135
+ indexOpt.expression = this.quote(index.expression);
136
+ } else if (typeof index.expression === 'function') {
137
+ indexOpt.expression = `${index.expression}`.replace(')=>`', ') => `');
138
+ }
139
+ if (isAtEntityLevel && index.properties) {
140
+ indexOpt.properties = Utils.asArray(index.properties).map(prop => this.quote('' + prop));
141
+ }
142
+ // Index type (e.g., 'fulltext', 'spatial', 'btree', 'hash')
143
+ if (index.type) {
144
+ indexOpt.type = this.quote(index.type);
145
+ }
146
+ // Advanced index options
147
+ const columns = this.getColumnOptions(index.columns);
148
+ if (columns) {
149
+ indexOpt.columns = columns;
150
+ }
151
+ if (index.include) {
152
+ indexOpt.include = Utils.asArray(index.include).map(prop => this.quote('' + prop));
153
+ }
154
+ if (index.fillFactor != null) {
155
+ indexOpt.fillFactor = index.fillFactor;
156
+ }
157
+ if (index.invisible) {
158
+ indexOpt.invisible = true;
159
+ }
160
+ if (index.disabled) {
161
+ indexOpt.disabled = true;
162
+ }
163
+ if (index.clustered) {
164
+ indexOpt.clustered = true;
165
+ }
166
+ return indexOpt;
167
+ }
168
+ getUniqueOptions(index, isAtEntityLevel = true) {
169
+ const uniqueOpt = {};
170
+ if (typeof index.name === 'string') {
171
+ uniqueOpt.name = this.quote(index.name);
172
+ }
173
+ if (typeof index.expression === 'string') {
174
+ uniqueOpt.expression = this.quote(index.expression);
175
+ } else if (typeof index.expression === 'function') {
176
+ uniqueOpt.expression = `${index.expression}`.replace(')=>`', ') => `');
177
+ }
178
+ if (isAtEntityLevel && index.properties) {
179
+ uniqueOpt.properties = Utils.asArray(index.properties).map(prop => this.quote('' + prop));
180
+ }
181
+ if (index.deferMode) {
182
+ uniqueOpt.deferMode = `${this.referenceCoreImport('DeferMode')}.INITIALLY_${index.deferMode.toUpperCase()}`;
183
+ }
184
+ const columns = this.getColumnOptions(index.columns);
185
+ if (columns) {
186
+ uniqueOpt.columns = columns;
187
+ }
188
+ if (index.include) {
189
+ uniqueOpt.include = Utils.asArray(index.include).map(prop => this.quote('' + prop));
190
+ }
191
+ if (index.fillFactor != null) {
192
+ uniqueOpt.fillFactor = index.fillFactor;
193
+ }
194
+ if (index.disabled) {
195
+ uniqueOpt.disabled = true;
196
+ }
197
+ return uniqueOpt;
198
+ }
199
+ generateImports() {
200
+ const imports = new Set();
201
+ if (this.coreImports.size > 0) {
202
+ imports.add(
203
+ `import { ${[...this.coreImports]
204
+ .sort()
205
+ .map(t => {
206
+ let ret = POSSIBLE_TYPE_IMPORTS.includes(t) ? `type ${t}` : t;
207
+ if (this.options.coreImportsPrefix) {
208
+ const resolvedIdentifier = `${this.options.coreImportsPrefix}${t}`;
209
+ ret += ` as ${resolvedIdentifier}`;
210
+ }
211
+ return ret;
212
+ })
213
+ .join(', ')} } from '@mikro-orm/core';`,
214
+ );
215
+ }
216
+ if (this.decoratorImports.size > 0) {
217
+ const type = this.options.decorators;
218
+ imports.add(
219
+ `import { ${[...this.decoratorImports]
220
+ .sort()
221
+ .map(t => {
222
+ let ret = t;
223
+ if (this.options.coreImportsPrefix) {
224
+ const resolvedIdentifier = `${this.options.coreImportsPrefix}${t}`;
225
+ ret += ` as ${resolvedIdentifier}`;
226
+ }
227
+ return ret;
228
+ })
229
+ .join(', ')} } from '@mikro-orm/decorators/${type}';`,
230
+ );
231
+ }
232
+ const extension = this.options.esmImport ? '.js' : '';
233
+ const { dir, base } = parse(`${this.options.path ?? '.'}/${this.getBaseName()}`);
234
+ const basePath = relative(dir, this.options.path ?? '.') || '.';
235
+ (this.options.extraImports?.(basePath, base) ?? []).forEach(v => this.entityImports.add(v));
236
+ const entityImports = [...this.entityImports].filter(e => e !== this.meta.className);
237
+ const importMap = new Map();
238
+ for (const entity of entityImports) {
239
+ const file = this.options.onImport?.(entity, basePath, extension, base) ?? {
240
+ path: `${basePath}/${this.options.fileName(entity)}${extension}`,
241
+ name: entity,
242
+ };
243
+ if (file.path === '') {
244
+ if (file.name === '') {
245
+ continue;
246
+ }
247
+ importMap.set(file.path, `import ${this.quote(file.name)};`);
248
+ continue;
249
+ }
250
+ if (file.name === '') {
251
+ importMap.set(file.path, `import * as ${entity} from ${this.quote(file.path)};`);
252
+ continue;
253
+ }
254
+ if (file.name === 'default') {
255
+ importMap.set(file.path, `import ${entity} from ${this.quote(file.path)};`);
256
+ continue;
257
+ }
258
+ if (file.name === entity) {
259
+ importMap.set(file.path, `import { ${entity} } from ${this.quote(file.path)};`);
260
+ continue;
261
+ }
262
+ importMap.set(
263
+ file.path,
264
+ `import { ${identifierRegex.test(file.name) ? file.name : this.quote(file.name)} as ${entity} } from ${this.quote(file.path)};`,
265
+ );
266
+ }
267
+ if (this.enumImports.size) {
268
+ for (const [name, exports] of this.enumImports.entries()) {
269
+ const file = this.options.onImport?.(name, basePath, extension, base) ?? {
270
+ path: `${basePath}/${this.options.fileName(name)}${extension}`,
271
+ name,
562
272
  };
563
- processIndex('index');
564
- processIndex('unique');
565
- const ret = [];
566
- let propIndexIsNonTrivialIndex = false;
567
- const nonTrivialIndexes = this.meta.indexes.filter(i => i.properties?.length === 1 && i.properties[0] === prop.name);
568
- for (const i of nonTrivialIndexes) {
569
- ret.push(`@${this.referenceDecoratorImport('Index')}(${this.serializeObject(this.getIndexOptions(i, false))})`);
570
- if (prop.index === i.name) {
571
- propIndexIsNonTrivialIndex = true;
572
- delete options.index;
573
- }
574
- }
575
- if (prop.index && !options.index && !propIndexIsNonTrivialIndex) {
576
- ret.push(`@${this.referenceDecoratorImport('Index')}(${typeof prop.index === 'string' ? `{ name: ${this.quote(prop.index)} }` : ''})`);
577
- }
578
- let propIndexIsNonTrivialUnique = false;
579
- const nonTrivialUnique = this.meta.uniques.filter(i => i.properties?.length === 1 && i.properties[0] === prop.name);
580
- for (const i of nonTrivialUnique) {
581
- ret.push(`@${this.referenceDecoratorImport('Unique')}(${this.serializeObject(this.getUniqueOptions(i, false))})`);
582
- if (prop.unique === i.name) {
583
- propIndexIsNonTrivialUnique = true;
584
- delete options.unique;
585
- }
586
- }
587
- if (prop.unique && !options.unique && !propIndexIsNonTrivialUnique) {
588
- ret.push(`@${this.referenceDecoratorImport('Unique')}(${typeof prop.unique === 'string' ? `{ name: ${this.quote(prop.unique)} }` : ''})`);
589
- }
590
- return ret;
273
+ importMap.set(file.path, `import { ${exports.join(', ')} } from ${this.quote(file.path)};`);
274
+ }
591
275
  }
592
- getCommonDecoratorOptions(options, prop) {
593
- if (!prop.mappedBy && (prop.nullable || prop.customTypes?.[0]?.prop?.nullable)) {
594
- options.nullable = true;
595
- }
596
- if (prop.primary && (prop.enum || !(typeof prop.kind === 'undefined' || prop.kind === ReferenceKind.SCALAR))) {
597
- options.primary = true;
598
- }
599
- ['persist', 'hydrate'].filter(key => prop[key] === false).forEach(key => (options[key] = false));
600
- ['onCreate', 'onUpdate', 'serializer']
601
- .filter(key => typeof prop[key] === 'function')
602
- .forEach(key => (options[key] = `${prop[key]}`));
603
- if (typeof prop.serializedName === 'string') {
604
- options.serializedName = this.quote(prop.serializedName);
605
- }
606
- if (Array.isArray(prop.groups)) {
607
- options.groups = prop.groups.map(group => this.quote(group));
608
- }
609
- ['hidden', 'version', 'concurrencyCheck', 'eager', 'lazy', 'orphanRemoval']
610
- .filter(key => prop[key])
611
- .forEach(key => (options[key] = true));
612
- if (prop.cascade && (prop.cascade.length !== 1 || prop.cascade[0] !== Cascade.PERSIST)) {
613
- options.cascade = `[${prop.cascade.map(value => `${this.referenceCoreImport('Cascade')}.${value.toUpperCase()}`).join(', ')}]`;
614
- }
615
- if (typeof prop.comment === 'string') {
616
- options.comment = this.quote(prop.comment);
617
- }
618
- // TODO: Composite FKs with default values require additions to default/defaultRaw that are not yet supported.
619
- if (prop.fieldNames?.length <= 1) {
620
- if (typeof prop.defaultRaw !== 'undefined' &&
621
- prop.defaultRaw !== 'null' &&
622
- prop.defaultRaw !== '' &&
623
- prop.defaultRaw !== (typeof prop.default === 'string' ? this.quote(prop.default) : `${prop.default}`)) {
624
- options.defaultRaw = `\`${prop.defaultRaw}\``;
625
- }
626
- else if (!(typeof prop.default === 'undefined' || prop.default === null) &&
627
- (prop.ref ||
628
- (!prop.enum &&
629
- (typeof prop.kind === 'undefined' || prop.kind === ReferenceKind.SCALAR) &&
630
- (prop.type === 'unknown' || typeof this.breakdownOfIType(prop) !== 'undefined')))) {
631
- options.default = typeof prop.default === 'string' ? this.quote(prop.default) : prop.default;
632
- }
633
- }
634
- if (typeof prop.extra === 'string') {
635
- options.extra = this.quote(prop.extra);
636
- }
637
- if (prop.deferMode) {
638
- options.deferMode = `${this.referenceCoreImport('DeferMode')}.INITIALLY_${prop.deferMode.toUpperCase()}`;
639
- }
640
- if (typeof prop.ignoreSchemaChanges !== 'undefined') {
641
- options.ignoreSchemaChanges ??= [];
642
- options.ignoreSchemaChanges.push(...prop.ignoreSchemaChanges.map(v => this.quote(v)));
643
- }
276
+ for (const key of [...importMap.keys()].sort()) {
277
+ imports.add(importMap.get(key));
644
278
  }
645
- #propTypeBreakdowns = new WeakMap();
646
- breakdownOfIType(prop) {
647
- if (this.#propTypeBreakdowns.has(prop)) {
648
- return this.#propTypeBreakdowns.get(prop);
649
- }
650
- const mappedDeclaredType = this.platform.getMappedType(prop.type);
651
- const mappedRawType = prop.customTypes?.[0] ??
652
- (prop.type !== 'unknown' && mappedDeclaredType instanceof UnknownType
653
- ? this.platform.getMappedType(prop.columnTypes[0])
654
- : mappedDeclaredType);
655
- const rawType = mappedRawType.runtimeType;
656
- const mappedSerializedType = prop.customType ?? mappedRawType;
657
- const serializedType = mappedSerializedType.runtimeType;
658
- // Add non-lib imports where needed.
659
- for (const typeSpec of [prop.runtimeType, rawType, serializedType]) {
660
- const simplePropType = typeSpec.replace(/\[]+$/, '');
661
- if (!primitivesAndLibs.includes(simplePropType)) {
662
- this.entityImports.add(simplePropType);
663
- }
664
- }
665
- const nullables = [
666
- prop.nullable ?? false,
667
- mappedRawType.prop?.nullable ?? prop.nullable ?? false,
668
- mappedSerializedType.prop?.nullable ?? prop.nullable ?? false,
669
- ];
670
- const hasMixedNullability = new Set(nullables).size > 1;
671
- if (prop.runtimeType !== rawType || rawType !== serializedType || hasMixedNullability) {
672
- const nullType = this.options.forceUndefined ? ' | undefined' : ' | null';
673
- if (rawType !== serializedType ||
674
- nullables[1] !== nullables[2] ||
675
- (prop.hidden && nullables[0] !== nullables[1])) {
676
- const r = [prop.runtimeType, rawType, serializedType];
677
- if (hasMixedNullability || prop.hidden) {
678
- for (let i = r.length - 1; i >= 0; --i) {
679
- if (nullables[i]) {
680
- r[i] += nullType;
681
- }
682
- }
683
- if (prop.hidden) {
684
- r[2] = `(${r[2]}) & ${this.referenceCoreImport('Hidden')}`;
685
- }
686
- }
687
- this.#propTypeBreakdowns.set(prop, r);
688
- return r;
689
- }
690
- const r = [prop.runtimeType, rawType];
691
- if (hasMixedNullability) {
692
- for (let i = r.length - 1; i >= 0; --i) {
693
- if (nullables[i]) {
694
- r[i] += nullType;
695
- }
696
- }
697
- }
698
- this.#propTypeBreakdowns.set(prop, r);
699
- return r;
279
+ return Array.from(imports.values()).join('\n');
280
+ }
281
+ getEntityClass(classBody) {
282
+ let ret = `export `;
283
+ if (this.meta.abstract) {
284
+ ret += `abstract `;
285
+ }
286
+ ret += `class ${this.meta.className}`;
287
+ if (this.meta.extends) {
288
+ this.entityImports.add(Utils.className(this.meta.extends));
289
+ ret += ` extends ${Utils.className(this.meta.extends)}`;
290
+ } else if (this.options.useCoreBaseEntity) {
291
+ ret += ` extends ${this.referenceCoreImport('BaseEntity')}`;
292
+ }
293
+ ret += ` {\n${classBody}}\n`;
294
+ return ret;
295
+ }
296
+ getBaseName(extension = '.ts') {
297
+ return `${this.options.fileName(this.meta.className)}${extension}`;
298
+ }
299
+ quote(val) {
300
+ const backtick = val.startsWith(`'`) || val.includes('\n');
301
+ /* v8 ignore next */
302
+ return backtick ? `\`${val.replaceAll('`', '\\``')}\`` : `'${val.replaceAll(`'`, `\\'`)}'`;
303
+ }
304
+ getPropertyDefinition(prop, padLeft) {
305
+ const padding = ' '.repeat(padLeft);
306
+ const propName = identifierRegex.test(prop.name) ? prop.name : this.quote(prop.name);
307
+ const enumMode = this.options.enumMode;
308
+ let hiddenType = '';
309
+ if (prop.hidden) {
310
+ hiddenType += ` & ${this.referenceCoreImport('Hidden')}`;
311
+ }
312
+ if ([ReferenceKind.ONE_TO_MANY, ReferenceKind.MANY_TO_MANY].includes(prop.kind)) {
313
+ return `${padding}${propName}${hiddenType ? `: ${this.referenceCoreImport('Collection')}<${prop.type}>${hiddenType}` : ''} = new ${this.referenceCoreImport('Collection')}<${prop.type}>(this);\n`;
314
+ }
315
+ const isScalar = typeof prop.kind === 'undefined' || prop.kind === ReferenceKind.SCALAR;
316
+ let breakdownOfIType;
317
+ const propType = prop.mapToPk
318
+ ? (() => {
319
+ const runtimeTypes = prop.columnTypes.map(
320
+ (t, i) => (prop.customTypes?.[i] ?? this.platform.getMappedType(t)).runtimeType,
321
+ );
322
+ return runtimeTypes.length === 1 ? runtimeTypes[0] : this.serializeObject(runtimeTypes);
323
+ })()
324
+ : (() => {
325
+ if (isScalar) {
326
+ if (prop.enum) {
327
+ const method = enumMode === 'ts-enum' ? 'getEnumClassName' : 'getEnumTypeName';
328
+ if (prop.nativeEnumName) {
329
+ return this.namingStrategy[method](prop.nativeEnumName, undefined, this.meta.schema);
330
+ }
331
+ return this.namingStrategy[method](prop.fieldNames[0], this.meta.collection, this.meta.schema);
332
+ }
333
+ breakdownOfIType = this.breakdownOfIType(prop);
334
+ if (typeof breakdownOfIType !== 'undefined') {
335
+ if (breakdownOfIType.length >= 3) {
336
+ hiddenType = '';
337
+ }
338
+ return `${this.referenceCoreImport('IType')}<${breakdownOfIType.join(', ')}>`;
339
+ }
340
+ return prop.runtimeType;
341
+ }
342
+ return prop.type;
343
+ })();
344
+ const hasUsableNullDefault = prop.nullable && !this.options.forceUndefined && prop.default === null;
345
+ const useDefault =
346
+ hasUsableNullDefault ||
347
+ (!(typeof prop.default === 'undefined' || prop.default === null) &&
348
+ propType !== 'unknown' &&
349
+ typeof breakdownOfIType === 'undefined');
350
+ const optional = prop.nullable && (this.options.forceUndefined || prop.optional) ? '?' : useDefault ? '' : '!';
351
+ let ret = `${propName}${optional}: `;
352
+ const isArray = prop.array && (prop.kind === ReferenceKind.EMBEDDED || prop.enum);
353
+ const complexType = isArray ? `${propType}[]` : propType;
354
+ let wrappedType = prop.ref
355
+ ? `${this.referenceCoreImport('Ref')}<${complexType}${isScalar && prop.nullable && !this.options.forceUndefined ? ' | null' : ''}>`
356
+ : this.options.esmImport && !isScalar
357
+ ? `${this.referenceCoreImport('Rel')}<${complexType}>`
358
+ : complexType;
359
+ if (
360
+ prop.nullable &&
361
+ !this.options.forceUndefined &&
362
+ (!isScalar || (!prop.ref && !wrappedType.includes(' | null')))
363
+ ) {
364
+ wrappedType += ' | null';
365
+ }
366
+ const optionalType = optional !== '?' && prop.optional ? ` & ${this.referenceCoreImport('Opt')}` : '';
367
+ ret +=
368
+ !this.options.forceUndefined && prop.nullable && (hiddenType || optionalType) ? `(${wrappedType})` : wrappedType;
369
+ ret += hiddenType;
370
+ ret += optionalType;
371
+ if (!useDefault) {
372
+ return `${padding}${ret};\n`;
373
+ }
374
+ if (prop.enum && typeof prop.default === 'string') {
375
+ if (enumMode === 'union-type') {
376
+ return `${padding}${ret} = ${this.quote(prop.default)};\n`;
377
+ }
378
+ const enumClassName = prop.nativeEnumName
379
+ ? this.namingStrategy.getEnumClassName(prop.nativeEnumName, undefined, this.meta.schema)
380
+ : this.namingStrategy.getEnumClassName(prop.fieldNames[0], this.meta.collection, this.meta.schema);
381
+ const enumVal = this.namingStrategy.enumValueToEnumProperty(
382
+ prop.default,
383
+ prop.fieldNames[0],
384
+ this.meta.collection,
385
+ this.meta.schema,
386
+ );
387
+ return `${padding}${ret} = ${enumClassName}${identifierRegex.test(enumVal) ? `.${enumVal}` : `[${this.quote(enumVal)}]`};\n`;
388
+ }
389
+ if (prop.fieldNames?.length > 1) {
390
+ // TODO: Composite FKs with default values require additions to default/defaultRaw that are not yet supported.
391
+ return `${padding}${ret};\n`;
392
+ }
393
+ const defaultVal = typeof prop.default === 'string' ? this.quote(prop.default) : prop.default;
394
+ if (isScalar) {
395
+ return `${padding}${ret} = ${prop.ref ? `${this.referenceCoreImport('ref')}(${defaultVal})` : defaultVal};\n`;
396
+ }
397
+ if (hasUsableNullDefault) {
398
+ return `${padding}${ret} = null;\n`;
399
+ }
400
+ return `${padding}${ret} = ${prop.ref ? this.referenceCoreImport('ref') : this.referenceCoreImport('rel')}(${propType}, ${defaultVal});\n`;
401
+ }
402
+ getEnumClassDefinition(prop, padLeft) {
403
+ const enumMode = this.options.enumMode;
404
+ if (prop.nativeEnumName) {
405
+ const imports = [];
406
+ if (enumMode !== 'union-type') {
407
+ imports.push(prop.runtimeType);
408
+ }
409
+ if (!this.options.inferEntityType && enumMode !== 'ts-enum') {
410
+ const enumTypeName = this.namingStrategy.getEnumTypeName(prop.nativeEnumName, undefined, this.meta.schema);
411
+ imports.push(enumTypeName);
412
+ }
413
+ this.enumImports.set(prop.runtimeType, imports);
414
+ return '';
415
+ }
416
+ const enumClassName = this.namingStrategy.getEnumClassName(
417
+ prop.fieldNames[0],
418
+ this.meta.collection,
419
+ this.meta.schema,
420
+ );
421
+ const enumTypeName = this.namingStrategy.getEnumTypeName(
422
+ prop.fieldNames[0],
423
+ this.meta.collection,
424
+ this.meta.schema,
425
+ );
426
+ const padding = ' '.repeat(padLeft);
427
+ const enumValues = prop.items;
428
+ if (enumMode === 'union-type') {
429
+ return `export type ${enumTypeName} = ${enumValues.map(item => this.quote(item)).join(' | ')};\n`;
430
+ }
431
+ let ret = '';
432
+ if (enumMode === 'dictionary') {
433
+ ret += `export const ${enumClassName} = {\n`;
434
+ } else {
435
+ ret += `export enum ${enumClassName} {\n`;
436
+ }
437
+ for (const enumValue of enumValues) {
438
+ const enumName = this.namingStrategy.enumValueToEnumProperty(
439
+ enumValue,
440
+ prop.fieldNames[0],
441
+ this.meta.collection,
442
+ this.meta.schema,
443
+ );
444
+ if (enumMode === 'dictionary') {
445
+ ret += `${padding}${identifierRegex.test(enumName) ? enumName : this.quote(enumName)}: ${this.quote(enumValue)},\n`;
446
+ } else {
447
+ ret += `${padding}${identifierRegex.test(enumName) ? enumName : this.quote(enumName)} = ${this.quote(enumValue)},\n`;
448
+ }
449
+ }
450
+ if (enumMode === 'dictionary') {
451
+ ret += '} as const;\n';
452
+ } else {
453
+ ret += '}\n';
454
+ }
455
+ if (enumMode === 'dictionary') {
456
+ ret += `\nexport type ${enumTypeName} = (typeof ${enumClassName})[keyof typeof ${enumClassName}];\n`;
457
+ }
458
+ return ret;
459
+ }
460
+ serializeObject(options, wordwrap, spaces, level = 0) {
461
+ if (typeof wordwrap === 'number' && !Object.hasOwn(options, Config)) {
462
+ const res = this.serializeObject(options, undefined, undefined, level);
463
+ if (res.length <= wordwrap) {
464
+ return res;
465
+ }
466
+ }
467
+ const nextWordwrap = typeof wordwrap === 'number' ? 80 - (spaces ?? 0) - level * 2 : undefined;
468
+ const sep = typeof spaces === 'undefined' ? ', ' : `,\n${' '.repeat(spaces)}`;
469
+ const doIndent = typeof spaces !== 'undefined';
470
+ if (Array.isArray(options)) {
471
+ return `[${doIndent ? `\n${' '.repeat(spaces)}` : ''}${options.map(val => `${doIndent ? ' '.repeat(level * 2 + (spaces + 2)) : ''}${this.serializeValue(val, typeof nextWordwrap === 'number' ? nextWordwrap : undefined, doIndent ? spaces : undefined, level + 1)}`).join(sep)}${doIndent ? `${options.length > 0 ? ',\n' : ''}${' '.repeat(spaces + level * 2)}` : ''}]`;
472
+ }
473
+ const entries = Object.entries(options);
474
+ return `{${doIndent ? `\n${' '.repeat(spaces)}` : ' '}${entries
475
+ .map(([opt, val]) => {
476
+ const key = identifierRegex.test(opt) ? opt : this.quote(opt);
477
+ return `${doIndent ? ' '.repeat(level * 2 + (spaces + 2)) : ''}${key}: ${this.serializeValue(val, typeof nextWordwrap === 'number' ? nextWordwrap - key.length - 2 /* ': '.length*/ : undefined, doIndent ? spaces : undefined, level + 1)}`;
478
+ })
479
+ .join(sep)}${doIndent ? `${entries.length > 0 ? ',\n' : ''}${' '.repeat(spaces + level * 2)}` : ' '}}`;
480
+ }
481
+ serializeValue(val, wordwrap, spaces, level = 1) {
482
+ if (typeof val === 'object' && val !== null) {
483
+ return this.serializeObject(val, wordwrap, spaces, level);
484
+ }
485
+ return val;
486
+ }
487
+ getEntityDeclOptions() {
488
+ const options = {};
489
+ if (this.meta.tableName !== this.namingStrategy.classToTableName(this.meta.className)) {
490
+ options.tableName = this.quote(this.meta.tableName);
491
+ }
492
+ if (this.meta.schema && this.meta.schema !== this.platform.getDefaultSchemaName()) {
493
+ options.schema = this.quote(this.meta.schema);
494
+ }
495
+ if (typeof this.meta.expression === 'string') {
496
+ options.expression = this.quote(this.meta.expression);
497
+ } else if (typeof this.meta.expression === 'function') {
498
+ options.expression = this.meta.expression.toString();
499
+ }
500
+ if (this.meta.repositoryClass) {
501
+ this.entityImports.add(this.meta.repositoryClass);
502
+ options.repository = `() => ${this.meta.repositoryClass}`;
503
+ }
504
+ if (this.meta.comment) {
505
+ options.comment = this.quote(this.meta.comment);
506
+ }
507
+ if (this.meta.readonly && !this.meta.virtual) {
508
+ options.readonly = this.meta.readonly;
509
+ }
510
+ if (this.meta.virtual) {
511
+ options.virtual = this.meta.virtual;
512
+ }
513
+ return this.getCollectionDecl(options);
514
+ }
515
+ getEmbeddableDeclOptions() {
516
+ const options = {};
517
+ const result = this.getCollectionDecl(options);
518
+ if (result.discriminatorColumn) {
519
+ result.discriminator = result.discriminatorColumn;
520
+ delete result.discriminatorColumn;
521
+ }
522
+ return result;
523
+ }
524
+ getCollectionDecl(options) {
525
+ if (this.meta.abstract) {
526
+ options.abstract = true;
527
+ }
528
+ if (this.meta.discriminatorValue) {
529
+ options.discriminatorValue =
530
+ typeof this.meta.discriminatorValue === 'string'
531
+ ? this.quote(this.meta.discriminatorValue)
532
+ : this.meta.discriminatorValue;
533
+ }
534
+ if (this.meta.discriminatorColumn) {
535
+ options.discriminatorColumn = this.quote(this.meta.discriminatorColumn);
536
+ }
537
+ if (this.meta.discriminatorMap) {
538
+ options.discriminatorMap = Object.fromEntries(
539
+ Object.entries(this.meta.discriminatorMap).map(([discriminatorValue, cls]) => [
540
+ discriminatorValue,
541
+ this.quote(Utils.className(cls)),
542
+ ]),
543
+ );
544
+ }
545
+ return options;
546
+ }
547
+ getPropertyDecorator(prop, padLeft) {
548
+ const padding = ' '.repeat(padLeft);
549
+ const options = {};
550
+ let decorator = `@${this.referenceDecoratorImport(this.getDecoratorType(prop))}`;
551
+ if (prop.kind === ReferenceKind.MANY_TO_MANY) {
552
+ this.getManyToManyDecoratorOptions(options, prop);
553
+ } else if (prop.kind === ReferenceKind.ONE_TO_MANY) {
554
+ this.getOneToManyDecoratorOptions(options, prop);
555
+ } else if (prop.kind === ReferenceKind.SCALAR || typeof prop.kind === 'undefined') {
556
+ this.getScalarPropertyDecoratorOptions(options, prop);
557
+ } else if (prop.kind === ReferenceKind.EMBEDDED) {
558
+ this.getEmbeddedPropertyDeclarationOptions(options, prop);
559
+ } else {
560
+ this.getForeignKeyDecoratorOptions(options, prop);
561
+ }
562
+ this.getCommonDecoratorOptions(options, prop);
563
+ const indexes = this.getPropertyIndexes(prop, options);
564
+ decorator = [...indexes.sort(), decorator].map(d => padding + d).join('\n');
565
+ const decoratorArgs = [];
566
+ if (prop.formula) {
567
+ decoratorArgs.push(prop.formula.toString());
568
+ }
569
+ if (Utils.hasObjectKeys(options)) {
570
+ decoratorArgs.push(this.serializeObject(options));
571
+ }
572
+ return `${decorator}(${decoratorArgs.join(', ')})\n`;
573
+ }
574
+ getPropertyIndexes(prop, options) {
575
+ const processIndex = type => {
576
+ const propType = prop[type];
577
+ if (!propType) {
578
+ return;
579
+ }
580
+ const defaultName = this.platform.getIndexName(this.meta.collection, prop.fieldNames, type);
581
+ options[type] = propType === true || defaultName === propType ? 'true' : this.quote(propType);
582
+ const expected = {
583
+ index: this.platform.indexForeignKeys(),
584
+ unique: prop.kind === ReferenceKind.ONE_TO_ONE,
585
+ };
586
+ if (expected[type] && options[type] === 'true') {
587
+ delete options[type];
588
+ }
589
+ };
590
+ processIndex('index');
591
+ processIndex('unique');
592
+ const ret = [];
593
+ let propIndexIsNonTrivialIndex = false;
594
+ const nonTrivialIndexes = this.meta.indexes.filter(
595
+ i => i.properties?.length === 1 && i.properties[0] === prop.name,
596
+ );
597
+ for (const i of nonTrivialIndexes) {
598
+ ret.push(`@${this.referenceDecoratorImport('Index')}(${this.serializeObject(this.getIndexOptions(i, false))})`);
599
+ if (prop.index === i.name) {
600
+ propIndexIsNonTrivialIndex = true;
601
+ delete options.index;
602
+ }
603
+ }
604
+ if (prop.index && !options.index && !propIndexIsNonTrivialIndex) {
605
+ ret.push(
606
+ `@${this.referenceDecoratorImport('Index')}(${typeof prop.index === 'string' ? `{ name: ${this.quote(prop.index)} }` : ''})`,
607
+ );
608
+ }
609
+ let propIndexIsNonTrivialUnique = false;
610
+ const nonTrivialUnique = this.meta.uniques.filter(i => i.properties?.length === 1 && i.properties[0] === prop.name);
611
+ for (const i of nonTrivialUnique) {
612
+ ret.push(`@${this.referenceDecoratorImport('Unique')}(${this.serializeObject(this.getUniqueOptions(i, false))})`);
613
+ if (prop.unique === i.name) {
614
+ propIndexIsNonTrivialUnique = true;
615
+ delete options.unique;
616
+ }
617
+ }
618
+ if (prop.unique && !options.unique && !propIndexIsNonTrivialUnique) {
619
+ ret.push(
620
+ `@${this.referenceDecoratorImport('Unique')}(${typeof prop.unique === 'string' ? `{ name: ${this.quote(prop.unique)} }` : ''})`,
621
+ );
622
+ }
623
+ return ret;
624
+ }
625
+ getCommonDecoratorOptions(options, prop) {
626
+ if (!prop.mappedBy && (prop.nullable || prop.customTypes?.[0]?.prop?.nullable)) {
627
+ options.nullable = true;
628
+ }
629
+ if (prop.primary && (prop.enum || !(typeof prop.kind === 'undefined' || prop.kind === ReferenceKind.SCALAR))) {
630
+ options.primary = true;
631
+ }
632
+ ['persist', 'hydrate'].filter(key => prop[key] === false).forEach(key => (options[key] = false));
633
+ ['onCreate', 'onUpdate', 'serializer']
634
+ .filter(key => typeof prop[key] === 'function')
635
+ .forEach(key => (options[key] = `${prop[key]}`));
636
+ if (typeof prop.serializedName === 'string') {
637
+ options.serializedName = this.quote(prop.serializedName);
638
+ }
639
+ if (Array.isArray(prop.groups)) {
640
+ options.groups = prop.groups.map(group => this.quote(group));
641
+ }
642
+ ['hidden', 'version', 'concurrencyCheck', 'eager', 'lazy', 'orphanRemoval']
643
+ .filter(key => prop[key])
644
+ .forEach(key => (options[key] = true));
645
+ if (prop.cascade && (prop.cascade.length !== 1 || prop.cascade[0] !== Cascade.PERSIST)) {
646
+ options.cascade = `[${prop.cascade.map(value => `${this.referenceCoreImport('Cascade')}.${value.toUpperCase()}`).join(', ')}]`;
647
+ }
648
+ if (typeof prop.comment === 'string') {
649
+ options.comment = this.quote(prop.comment);
650
+ }
651
+ // TODO: Composite FKs with default values require additions to default/defaultRaw that are not yet supported.
652
+ if (prop.fieldNames?.length <= 1) {
653
+ if (
654
+ typeof prop.defaultRaw !== 'undefined' &&
655
+ prop.defaultRaw !== 'null' &&
656
+ prop.defaultRaw !== '' &&
657
+ prop.defaultRaw !== (typeof prop.default === 'string' ? this.quote(prop.default) : `${prop.default}`)
658
+ ) {
659
+ options.defaultRaw = `\`${prop.defaultRaw}\``;
660
+ } else if (
661
+ !(typeof prop.default === 'undefined' || prop.default === null) &&
662
+ (prop.ref ||
663
+ (!prop.enum &&
664
+ (typeof prop.kind === 'undefined' || prop.kind === ReferenceKind.SCALAR) &&
665
+ (prop.type === 'unknown' || typeof this.breakdownOfIType(prop) !== 'undefined')))
666
+ ) {
667
+ options.default = typeof prop.default === 'string' ? this.quote(prop.default) : prop.default;
668
+ }
669
+ }
670
+ if (typeof prop.extra === 'string') {
671
+ options.extra = this.quote(prop.extra);
672
+ }
673
+ if (prop.deferMode) {
674
+ options.deferMode = `${this.referenceCoreImport('DeferMode')}.INITIALLY_${prop.deferMode.toUpperCase()}`;
675
+ }
676
+ if (typeof prop.ignoreSchemaChanges !== 'undefined') {
677
+ options.ignoreSchemaChanges ??= [];
678
+ options.ignoreSchemaChanges.push(...prop.ignoreSchemaChanges.map(v => this.quote(v)));
679
+ }
680
+ }
681
+ #propTypeBreakdowns = new WeakMap();
682
+ breakdownOfIType(prop) {
683
+ if (this.#propTypeBreakdowns.has(prop)) {
684
+ return this.#propTypeBreakdowns.get(prop);
685
+ }
686
+ const mappedDeclaredType = this.platform.getMappedType(prop.type);
687
+ const mappedRawType =
688
+ prop.customTypes?.[0] ??
689
+ (prop.type !== 'unknown' && mappedDeclaredType instanceof UnknownType
690
+ ? this.platform.getMappedType(prop.columnTypes[0])
691
+ : mappedDeclaredType);
692
+ const rawType = mappedRawType.runtimeType;
693
+ const mappedSerializedType = prop.customType ?? mappedRawType;
694
+ const serializedType = mappedSerializedType.runtimeType;
695
+ // Add non-lib imports where needed.
696
+ for (const typeSpec of [prop.runtimeType, rawType, serializedType]) {
697
+ const simplePropType = typeSpec.replace(/\[]+$/, '');
698
+ if (!primitivesAndLibs.includes(simplePropType)) {
699
+ this.entityImports.add(simplePropType);
700
+ }
701
+ }
702
+ const nullables = [
703
+ prop.nullable ?? false,
704
+ mappedRawType.prop?.nullable ?? prop.nullable ?? false,
705
+ mappedSerializedType.prop?.nullable ?? prop.nullable ?? false,
706
+ ];
707
+ const hasMixedNullability = new Set(nullables).size > 1;
708
+ if (prop.runtimeType !== rawType || rawType !== serializedType || hasMixedNullability) {
709
+ const nullType = this.options.forceUndefined ? ' | undefined' : ' | null';
710
+ if (
711
+ rawType !== serializedType ||
712
+ nullables[1] !== nullables[2] ||
713
+ (prop.hidden && nullables[0] !== nullables[1])
714
+ ) {
715
+ const r = [prop.runtimeType, rawType, serializedType];
716
+ if (hasMixedNullability || prop.hidden) {
717
+ for (let i = r.length - 1; i >= 0; --i) {
718
+ if (nullables[i]) {
719
+ r[i] += nullType;
720
+ }
721
+ }
722
+ if (prop.hidden) {
723
+ r[2] = `(${r[2]}) & ${this.referenceCoreImport('Hidden')}`;
724
+ }
700
725
  }
701
- const r = undefined;
702
726
  this.#propTypeBreakdowns.set(prop, r);
703
727
  return r;
728
+ }
729
+ const r = [prop.runtimeType, rawType];
730
+ if (hasMixedNullability) {
731
+ for (let i = r.length - 1; i >= 0; --i) {
732
+ if (nullables[i]) {
733
+ r[i] += nullType;
734
+ }
735
+ }
736
+ }
737
+ this.#propTypeBreakdowns.set(prop, r);
738
+ return r;
704
739
  }
705
- getScalarPropertyDecoratorOptions(options, prop, quote = true) {
706
- if (prop.fieldNames[0] !== this.namingStrategy.propertyToColumnName(prop.name)) {
707
- options.fieldName = this.quote(prop.fieldNames[0]);
708
- }
709
- if (prop.enum) {
710
- if (this.options.enumMode === 'union-type') {
711
- options.items = `[${prop.items.map(item => this.quote(item)).join(', ')}]`;
712
- }
713
- else if (prop.nativeEnumName) {
714
- const enumClassName = this.namingStrategy.getEnumClassName(prop.nativeEnumName, undefined, this.meta.schema);
715
- options.items = `() => ${enumClassName}`;
716
- options.nativeEnumName = this.quote(prop.nativeEnumName);
717
- }
718
- else {
719
- const enumClassName = this.namingStrategy.getEnumClassName(prop.fieldNames[0], this.meta.collection, this.meta.schema);
720
- options.items = `() => ${enumClassName}`;
721
- }
722
- }
723
- // For enum properties, we don't need a column type
724
- // or the property length or other information in the decorator.
725
- if (prop.enum) {
726
- return;
727
- }
728
- const mappedColumnType = this.platform.getMappedType(prop.columnTypes[0]);
729
- // If the column's runtimeType matches the declared runtimeType, assume it's the same underlying type.
730
- const mappedRuntimeType = mappedColumnType.runtimeType === prop.runtimeType
731
- ? mappedColumnType
732
- : this.platform.getMappedType(prop.runtimeType);
733
- const mappedDeclaredType = this.platform.getMappedType(prop.type);
734
- const isTypeStringMissingFromMap = prop.type !== 'unknown' && mappedDeclaredType instanceof UnknownType;
735
- if (isTypeStringMissingFromMap) {
736
- this.entityImports.add(prop.type);
737
- options.type = prop.type;
738
- }
739
- else {
740
- if (this.options.scalarTypeInDecorator || // always output type if forced by the generator options
741
- (prop.nullable && !this.options.forceUndefined) || // also when there is the "| null" type modifier (because reflect-metadata can't extract the base)
742
- prop.hidden || // also when there is the "& Hidden" type modifier (because reflect-metadata can't extract the base)
743
- new Set([
744
- mappedRuntimeType.name,
745
- mappedColumnType.name,
746
- mappedDeclaredType.name,
747
- this.platform.getMappedType(prop.runtimeType === 'Date' ? 'datetime' : prop.runtimeType).name,
748
- ]).size > 1 || // also, if there's any ambiguity in the type
749
- (() => {
750
- const hasUsableNullDefault = prop.nullable && !this.options.forceUndefined && prop.default === null;
751
- const useDefault = hasUsableNullDefault || !(typeof prop.default === 'undefined' || prop.default === null);
752
- return (useDefault && !hasUsableNullDefault) || (prop.optional && !prop.nullable);
753
- })() // also when there is the "| Opt" type modifier (because reflect-metadata can't extract the base)
754
- ) {
755
- options.type = quote ? this.quote(prop.type) : prop.type;
756
- }
757
- }
758
- const columnTypeFromMappedRuntimeType = mappedRuntimeType.getColumnType({ ...prop, autoincrement: false }, this.platform);
759
- const columnTypeFromMappedColumnType = mappedColumnType.getColumnType({ ...prop, autoincrement: false }, this.platform);
760
- const columnTypeFromMappedDeclaredType = mappedDeclaredType.getColumnType({ ...prop, autoincrement: false }, this.platform);
761
- const needsExplicitColumnType = () => {
762
- if (isTypeStringMissingFromMap ||
763
- [mappedRuntimeType, mappedColumnType, columnTypeFromMappedDeclaredType].some(t => t instanceof UnknownType)) {
764
- return true;
765
- }
766
- if (this.platform.normalizeColumnType(prop.columnTypes[0], prop) !==
767
- this.platform.normalizeColumnType(columnTypeFromMappedColumnType, prop)) {
768
- return prop.columnTypes[0] !== columnTypeFromMappedColumnType;
769
- }
770
- return (columnTypeFromMappedRuntimeType !== columnTypeFromMappedColumnType ||
771
- columnTypeFromMappedDeclaredType !== columnTypeFromMappedColumnType);
772
- };
773
- if (needsExplicitColumnType()) {
774
- options.columnType = this.quote(prop.columnTypes[0]);
775
- }
776
- const assign = (key) => {
777
- if (prop[key] != null) {
778
- options[key] = prop[key];
779
- }
780
- };
781
- if (!options.columnType &&
782
- (typeof mappedColumnType.getDefaultLength === 'undefined' ||
783
- mappedColumnType.getDefaultLength(this.platform) !== prop.length)) {
784
- assign('length');
785
- }
786
- // those are already included in the `columnType` in most cases, and when that option is present, they would be ignored anyway
787
- /* v8 ignore next */
788
- if (mappedColumnType instanceof DecimalType && !options.columnType) {
789
- assign('precision');
790
- assign('scale');
791
- }
792
- if (this.platform.supportsUnsigned() &&
793
- ((!prop.primary && prop.unsigned) ||
794
- (prop.primary && !prop.unsigned && this.platform.isNumericColumn(mappedColumnType)))) {
795
- assign('unsigned');
796
- }
797
- if (prop.autoincrement) {
798
- if (!prop.primary ||
799
- !this.platform.isNumericColumn(mappedColumnType) ||
800
- this.meta.getPrimaryProps().length !== 1) {
801
- options.autoincrement = true;
802
- }
803
- }
804
- else if (prop.primary &&
805
- this.platform.isNumericColumn(mappedColumnType) &&
806
- this.meta.getPrimaryProps().length === 1) {
807
- options.autoincrement = false;
808
- }
809
- if (prop.generated) {
810
- options.generated = typeof prop.generated === 'string' ? this.quote(prop.generated) : `${prop.generated}`;
811
- }
740
+ const r = undefined;
741
+ this.#propTypeBreakdowns.set(prop, r);
742
+ return r;
743
+ }
744
+ getScalarPropertyDecoratorOptions(options, prop, quote = true) {
745
+ if (prop.fieldNames[0] !== this.namingStrategy.propertyToColumnName(prop.name)) {
746
+ options.fieldName = this.quote(prop.fieldNames[0]);
812
747
  }
813
- getManyToManyDecoratorOptions(options, prop) {
814
- this.entityImports.add(prop.type);
815
- options.entity = `() => ${prop.type}`;
816
- if (prop.orderBy) {
817
- options.orderBy = inspect(prop.orderBy);
818
- }
819
- if (prop.mappedBy) {
820
- options.mappedBy = this.quote(prop.mappedBy);
821
- return;
822
- }
823
- if (prop.pivotTable !==
824
- this.namingStrategy.joinTableName(this.meta.collection, prop.type, prop.name, this.meta.tableName)) {
825
- options.pivotTable = this.quote(prop.pivotTable);
826
- }
827
- if (prop.pivotEntity && Utils.className(prop.pivotEntity) !== prop.pivotTable) {
828
- this.entityImports.add(Utils.className(prop.pivotEntity));
829
- options.pivotEntity = `() => ${Utils.className(prop.pivotEntity)}`;
830
- }
831
- if (prop.joinColumns.length === 1) {
832
- options.joinColumn = this.quote(prop.joinColumns[0]);
833
- }
834
- else {
835
- options.joinColumns = `[${prop.joinColumns.map(c => this.quote(c)).join(', ')}]`;
836
- }
837
- if (prop.inverseJoinColumns.length === 1) {
838
- options.inverseJoinColumn = this.quote(prop.inverseJoinColumns[0]);
839
- }
840
- else {
841
- options.inverseJoinColumns = `[${prop.inverseJoinColumns.map(c => this.quote(c)).join(', ')}]`;
842
- }
843
- if (prop.fixedOrder) {
844
- options.fixedOrder = true;
845
- if (prop.fixedOrderColumn && prop.fixedOrderColumn !== this.namingStrategy.referenceColumnName()) {
846
- options.fixedOrderColumn = this.quote(prop.fixedOrderColumn);
847
- }
848
- }
748
+ if (prop.enum) {
749
+ if (this.options.enumMode === 'union-type') {
750
+ options.items = `[${prop.items.map(item => this.quote(item)).join(', ')}]`;
751
+ } else if (prop.nativeEnumName) {
752
+ const enumClassName = this.namingStrategy.getEnumClassName(prop.nativeEnumName, undefined, this.meta.schema);
753
+ options.items = `() => ${enumClassName}`;
754
+ options.nativeEnumName = this.quote(prop.nativeEnumName);
755
+ } else {
756
+ const enumClassName = this.namingStrategy.getEnumClassName(
757
+ prop.fieldNames[0],
758
+ this.meta.collection,
759
+ this.meta.schema,
760
+ );
761
+ options.items = `() => ${enumClassName}`;
762
+ }
849
763
  }
850
- getOneToManyDecoratorOptions(options, prop) {
851
- this.entityImports.add(prop.type);
852
- options.entity = `() => ${prop.type}`;
853
- options.mappedBy = this.quote(prop.mappedBy);
854
- if (prop.orderBy) {
855
- options.orderBy = inspect(prop.orderBy);
856
- }
764
+ // For enum properties, we don't need a column type
765
+ // or the property length or other information in the decorator.
766
+ if (prop.enum) {
767
+ return;
857
768
  }
858
- getEmbeddedPropertyDeclarationOptions(options, prop) {
859
- this.entityImports.add(prop.type);
860
- options.entity = `() => ${prop.type}`;
861
- if (prop.array) {
862
- options.array = true;
863
- }
864
- if (prop.object && !prop.array) {
865
- options.object = true;
866
- }
867
- if (prop.prefix === false || typeof prop.prefix === 'string') {
868
- options.prefix = prop.prefix === false ? false : this.quote(prop.prefix);
869
- }
769
+ const mappedColumnType = this.platform.getMappedType(prop.columnTypes[0]);
770
+ // If the column's runtimeType matches the declared runtimeType, assume it's the same underlying type.
771
+ const mappedRuntimeType =
772
+ mappedColumnType.runtimeType === prop.runtimeType
773
+ ? mappedColumnType
774
+ : this.platform.getMappedType(prop.runtimeType);
775
+ const mappedDeclaredType = this.platform.getMappedType(prop.type);
776
+ const isTypeStringMissingFromMap = prop.type !== 'unknown' && mappedDeclaredType instanceof UnknownType;
777
+ if (isTypeStringMissingFromMap) {
778
+ this.entityImports.add(prop.type);
779
+ options.type = prop.type;
780
+ } else {
781
+ if (
782
+ this.options.scalarTypeInDecorator || // always output type if forced by the generator options
783
+ (prop.nullable && !this.options.forceUndefined) || // also when there is the "| null" type modifier (because reflect-metadata can't extract the base)
784
+ prop.hidden || // also when there is the "& Hidden" type modifier (because reflect-metadata can't extract the base)
785
+ new Set([
786
+ mappedRuntimeType.name,
787
+ mappedColumnType.name,
788
+ mappedDeclaredType.name,
789
+ this.platform.getMappedType(prop.runtimeType === 'Date' ? 'datetime' : prop.runtimeType).name,
790
+ ]).size > 1 || // also, if there's any ambiguity in the type
791
+ (() => {
792
+ const hasUsableNullDefault = prop.nullable && !this.options.forceUndefined && prop.default === null;
793
+ const useDefault = hasUsableNullDefault || !(typeof prop.default === 'undefined' || prop.default === null);
794
+ return (useDefault && !hasUsableNullDefault) || (prop.optional && !prop.nullable);
795
+ })() // also when there is the "| Opt" type modifier (because reflect-metadata can't extract the base)
796
+ ) {
797
+ options.type = quote ? this.quote(prop.type) : prop.type;
798
+ }
870
799
  }
871
- getForeignKeyDecoratorOptions(options, prop) {
872
- this.entityImports.add(prop.type);
873
- options.entity = `() => ${prop.type}`;
874
- if (prop.ref) {
875
- options.ref = true;
876
- }
877
- if (prop.mapToPk) {
878
- options.mapToPk = true;
879
- }
880
- if (prop.mappedBy) {
881
- options.mappedBy = this.quote(prop.mappedBy);
882
- return;
883
- }
884
- if (prop.fieldNames.length === 1) {
885
- if (prop.fieldNames[0] !== this.namingStrategy.joinKeyColumnName(prop.name, prop.referencedColumnNames[0])) {
886
- options.fieldName = this.quote(prop.fieldNames[0]);
887
- }
888
- }
889
- else {
890
- if (prop.fieldNames.length > 1 &&
891
- prop.fieldNames.some((fieldName, i) => fieldName !== this.namingStrategy.joinKeyColumnName(prop.name, prop.referencedColumnNames[i]))) {
892
- options.fieldNames = prop.fieldNames.map(fieldName => this.quote(fieldName));
893
- }
894
- }
895
- if (prop.ownColumns && prop.ownColumns.length !== prop.fieldNames.length) {
896
- options.referencedColumnNames = prop.referencedColumnNames.map(fieldName => this.quote(fieldName));
897
- }
898
- if (prop.updateRule) {
899
- options.updateRule = this.quote(prop.updateRule);
900
- }
901
- if (prop.deleteRule) {
902
- options.deleteRule = this.quote(prop.deleteRule);
903
- }
904
- if (prop.primary) {
905
- options.primary = true;
906
- }
907
- if (prop.generated) {
908
- options.generated = typeof prop.generated === 'string' ? this.quote(prop.generated) : `${prop.generated}`;
909
- }
910
- if (prop.fieldNames.length > 1 && !(typeof prop.default === 'undefined' || prop.default === null)) {
911
- // TODO: Composite FKs with default values require additions to default/defaultRaw that are not yet supported.
912
- options.ignoreSchemaChanges = [this.quote('default')];
913
- }
800
+ const columnTypeFromMappedRuntimeType = mappedRuntimeType.getColumnType(
801
+ { ...prop, autoincrement: false },
802
+ this.platform,
803
+ );
804
+ const columnTypeFromMappedColumnType = mappedColumnType.getColumnType(
805
+ { ...prop, autoincrement: false },
806
+ this.platform,
807
+ );
808
+ const columnTypeFromMappedDeclaredType = mappedDeclaredType.getColumnType(
809
+ { ...prop, autoincrement: false },
810
+ this.platform,
811
+ );
812
+ const needsExplicitColumnType = () => {
813
+ if (
814
+ isTypeStringMissingFromMap ||
815
+ [mappedRuntimeType, mappedColumnType, columnTypeFromMappedDeclaredType].some(t => t instanceof UnknownType)
816
+ ) {
817
+ return true;
818
+ }
819
+ if (
820
+ this.platform.normalizeColumnType(prop.columnTypes[0], prop) !==
821
+ this.platform.normalizeColumnType(columnTypeFromMappedColumnType, prop)
822
+ ) {
823
+ return prop.columnTypes[0] !== columnTypeFromMappedColumnType;
824
+ }
825
+ return (
826
+ columnTypeFromMappedRuntimeType !== columnTypeFromMappedColumnType ||
827
+ columnTypeFromMappedDeclaredType !== columnTypeFromMappedColumnType
828
+ );
829
+ };
830
+ if (needsExplicitColumnType()) {
831
+ options.columnType = this.quote(prop.columnTypes[0]);
914
832
  }
915
- getDecoratorType(prop) {
916
- if (prop.kind === ReferenceKind.ONE_TO_ONE) {
917
- return 'OneToOne';
918
- }
919
- if (prop.kind === ReferenceKind.MANY_TO_ONE) {
920
- return 'ManyToOne';
921
- }
922
- if (prop.kind === ReferenceKind.ONE_TO_MANY) {
923
- return 'OneToMany';
924
- }
925
- if (prop.kind === ReferenceKind.MANY_TO_MANY) {
926
- return 'ManyToMany';
927
- }
928
- if (prop.kind === ReferenceKind.EMBEDDED) {
929
- return 'Embedded';
930
- }
931
- if (prop.enum) {
932
- return 'Enum';
933
- }
934
- if (prop.primary) {
935
- return 'PrimaryKey';
936
- }
937
- if (prop.formula) {
938
- return 'Formula';
939
- }
940
- return 'Property';
833
+ const assign = key => {
834
+ if (prop[key] != null) {
835
+ options[key] = prop[key];
836
+ }
837
+ };
838
+ if (
839
+ !options.columnType &&
840
+ (typeof mappedColumnType.getDefaultLength === 'undefined' ||
841
+ mappedColumnType.getDefaultLength(this.platform) !== prop.length)
842
+ ) {
843
+ assign('length');
844
+ }
845
+ // those are already included in the `columnType` in most cases, and when that option is present, they would be ignored anyway
846
+ /* v8 ignore next */
847
+ if (mappedColumnType instanceof DecimalType && !options.columnType) {
848
+ assign('precision');
849
+ assign('scale');
850
+ }
851
+ if (
852
+ this.platform.supportsUnsigned() &&
853
+ ((!prop.primary && prop.unsigned) ||
854
+ (prop.primary && !prop.unsigned && this.platform.isNumericColumn(mappedColumnType)))
855
+ ) {
856
+ assign('unsigned');
857
+ }
858
+ if (prop.autoincrement) {
859
+ if (
860
+ !prop.primary ||
861
+ !this.platform.isNumericColumn(mappedColumnType) ||
862
+ this.meta.getPrimaryProps().length !== 1
863
+ ) {
864
+ options.autoincrement = true;
865
+ }
866
+ } else if (
867
+ prop.primary &&
868
+ this.platform.isNumericColumn(mappedColumnType) &&
869
+ this.meta.getPrimaryProps().length === 1
870
+ ) {
871
+ options.autoincrement = false;
872
+ }
873
+ if (prop.generated) {
874
+ options.generated = typeof prop.generated === 'string' ? this.quote(prop.generated) : `${prop.generated}`;
875
+ }
876
+ }
877
+ getManyToManyDecoratorOptions(options, prop) {
878
+ this.entityImports.add(prop.type);
879
+ options.entity = `() => ${prop.type}`;
880
+ if (prop.orderBy) {
881
+ options.orderBy = inspect(prop.orderBy);
882
+ }
883
+ if (prop.mappedBy) {
884
+ options.mappedBy = this.quote(prop.mappedBy);
885
+ return;
886
+ }
887
+ if (
888
+ prop.pivotTable !==
889
+ this.namingStrategy.joinTableName(this.meta.collection, prop.type, prop.name, this.meta.tableName)
890
+ ) {
891
+ options.pivotTable = this.quote(prop.pivotTable);
892
+ }
893
+ if (prop.pivotEntity && Utils.className(prop.pivotEntity) !== prop.pivotTable) {
894
+ this.entityImports.add(Utils.className(prop.pivotEntity));
895
+ options.pivotEntity = `() => ${Utils.className(prop.pivotEntity)}`;
896
+ }
897
+ if (prop.joinColumns.length === 1) {
898
+ options.joinColumn = this.quote(prop.joinColumns[0]);
899
+ } else {
900
+ options.joinColumns = `[${prop.joinColumns.map(c => this.quote(c)).join(', ')}]`;
901
+ }
902
+ if (prop.inverseJoinColumns.length === 1) {
903
+ options.inverseJoinColumn = this.quote(prop.inverseJoinColumns[0]);
904
+ } else {
905
+ options.inverseJoinColumns = `[${prop.inverseJoinColumns.map(c => this.quote(c)).join(', ')}]`;
906
+ }
907
+ if (prop.fixedOrder) {
908
+ options.fixedOrder = true;
909
+ if (prop.fixedOrderColumn && prop.fixedOrderColumn !== this.namingStrategy.referenceColumnName()) {
910
+ options.fixedOrderColumn = this.quote(prop.fixedOrderColumn);
911
+ }
912
+ }
913
+ }
914
+ getOneToManyDecoratorOptions(options, prop) {
915
+ this.entityImports.add(prop.type);
916
+ options.entity = `() => ${prop.type}`;
917
+ options.mappedBy = this.quote(prop.mappedBy);
918
+ if (prop.orderBy) {
919
+ options.orderBy = inspect(prop.orderBy);
920
+ }
921
+ }
922
+ getEmbeddedPropertyDeclarationOptions(options, prop) {
923
+ this.entityImports.add(prop.type);
924
+ options.entity = `() => ${prop.type}`;
925
+ if (prop.array) {
926
+ options.array = true;
927
+ }
928
+ if (prop.object && !prop.array) {
929
+ options.object = true;
930
+ }
931
+ if (prop.prefix === false || typeof prop.prefix === 'string') {
932
+ options.prefix = prop.prefix === false ? false : this.quote(prop.prefix);
933
+ }
934
+ }
935
+ getForeignKeyDecoratorOptions(options, prop) {
936
+ this.entityImports.add(prop.type);
937
+ options.entity = `() => ${prop.type}`;
938
+ if (prop.ref) {
939
+ options.ref = true;
940
+ }
941
+ if (prop.mapToPk) {
942
+ options.mapToPk = true;
943
+ }
944
+ if (prop.mappedBy) {
945
+ options.mappedBy = this.quote(prop.mappedBy);
946
+ return;
947
+ }
948
+ if (prop.fieldNames.length === 1) {
949
+ if (prop.fieldNames[0] !== this.namingStrategy.joinKeyColumnName(prop.name, prop.referencedColumnNames[0])) {
950
+ options.fieldName = this.quote(prop.fieldNames[0]);
951
+ }
952
+ } else {
953
+ if (
954
+ prop.fieldNames.length > 1 &&
955
+ prop.fieldNames.some(
956
+ (fieldName, i) =>
957
+ fieldName !== this.namingStrategy.joinKeyColumnName(prop.name, prop.referencedColumnNames[i]),
958
+ )
959
+ ) {
960
+ options.fieldNames = prop.fieldNames.map(fieldName => this.quote(fieldName));
961
+ }
962
+ }
963
+ if (prop.ownColumns && prop.ownColumns.length !== prop.fieldNames.length) {
964
+ options.referencedColumnNames = prop.referencedColumnNames.map(fieldName => this.quote(fieldName));
965
+ }
966
+ if (prop.updateRule) {
967
+ options.updateRule = this.quote(prop.updateRule);
968
+ }
969
+ if (prop.deleteRule) {
970
+ options.deleteRule = this.quote(prop.deleteRule);
971
+ }
972
+ if (prop.primary) {
973
+ options.primary = true;
974
+ }
975
+ if (prop.generated) {
976
+ options.generated = typeof prop.generated === 'string' ? this.quote(prop.generated) : `${prop.generated}`;
977
+ }
978
+ if (prop.fieldNames.length > 1 && !(typeof prop.default === 'undefined' || prop.default === null)) {
979
+ // TODO: Composite FKs with default values require additions to default/defaultRaw that are not yet supported.
980
+ options.ignoreSchemaChanges = [this.quote('default')];
981
+ }
982
+ }
983
+ getDecoratorType(prop) {
984
+ if (prop.kind === ReferenceKind.ONE_TO_ONE) {
985
+ return 'OneToOne';
986
+ }
987
+ if (prop.kind === ReferenceKind.MANY_TO_ONE) {
988
+ return 'ManyToOne';
989
+ }
990
+ if (prop.kind === ReferenceKind.ONE_TO_MANY) {
991
+ return 'OneToMany';
992
+ }
993
+ if (prop.kind === ReferenceKind.MANY_TO_MANY) {
994
+ return 'ManyToMany';
995
+ }
996
+ if (prop.kind === ReferenceKind.EMBEDDED) {
997
+ return 'Embedded';
998
+ }
999
+ if (prop.enum) {
1000
+ return 'Enum';
941
1001
  }
942
- referenceCoreImport(identifier) {
943
- this.coreImports.add(identifier);
944
- return this.options.coreImportsPrefix ? `${this.options.coreImportsPrefix}${identifier}` : identifier;
1002
+ if (prop.primary) {
1003
+ return 'PrimaryKey';
945
1004
  }
946
- referenceDecoratorImport(identifier) {
947
- this.decoratorImports.add(identifier);
948
- return this.options.coreImportsPrefix ? `${this.options.coreImportsPrefix}${identifier}` : identifier;
1005
+ if (prop.formula) {
1006
+ return 'Formula';
949
1007
  }
1008
+ return 'Property';
1009
+ }
1010
+ referenceCoreImport(identifier) {
1011
+ this.coreImports.add(identifier);
1012
+ return this.options.coreImportsPrefix ? `${this.options.coreImportsPrefix}${identifier}` : identifier;
1013
+ }
1014
+ referenceDecoratorImport(identifier) {
1015
+ this.decoratorImports.add(identifier);
1016
+ return this.options.coreImportsPrefix ? `${this.options.coreImportsPrefix}${identifier}` : identifier;
1017
+ }
950
1018
  }