@mikro-orm/entity-generator 7.0.4 → 7.0.5-dev.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/SourceFile.js CHANGED
@@ -1,13 +1,4 @@
1
- import {
2
- Cascade,
3
- Config,
4
- DecimalType,
5
- ReferenceKind,
6
- SCALAR_TYPES,
7
- UnknownType,
8
- Utils,
9
- inspect,
10
- } from '@mikro-orm/core';
1
+ import { Cascade, Config, DecimalType, ReferenceKind, SCALAR_TYPES, UnknownType, Utils, inspect, } from '@mikro-orm/core';
11
2
  import { parse, relative } from 'node:path';
12
3
  import { POSSIBLE_TYPE_IMPORTS } from './CoreImportsHelper.js';
13
4
  /**
@@ -16,1003 +7,944 @@ import { POSSIBLE_TYPE_IMPORTS } from './CoreImportsHelper.js';
16
7
  export const identifierRegex = /^(?:[$_\p{ID_Start}])(?:[$\u200C\u200D\p{ID_Continue}])*$/u;
17
8
  const primitivesAndLibs = [...SCALAR_TYPES, 'unknown', 'object', 'any'];
18
9
  export class SourceFile {
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
- }
43
- }
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`;
49
- }
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`;
55
- }
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`;
61
- }
62
- if (this.meta.repositoryClass) {
63
- this.entityImports.add(this.meta.repositoryClass);
64
- classHead += `\n${' '.repeat(2)}[${this.referenceCoreImport('EntityRepositoryType')}]?: ${this.meta.repositoryClass};\n`;
65
- }
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
- }
96
- }
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`;
100
- }
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,
272
- };
273
- importMap.set(file.path, `import { ${exports.join(', ')} } from ${this.quote(file.path)};`);
274
- }
275
- }
276
- for (const key of [...importMap.keys()].sort()) {
277
- imports.add(importMap.get(key));
278
- }
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) {
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';
326
68
  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));
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
+ });
641
121
  }
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(', ')}]`;
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;
647
161
  }
648
- if (typeof prop.comment === 'string') {
649
- options.comment = this.quote(prop.comment);
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');
650
269
  }
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
- }
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;
669
429
  }
670
- if (typeof prop.extra === 'string') {
671
- options.extra = this.quote(prop.extra);
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;
672
456
  }
673
- if (prop.deferMode) {
674
- options.deferMode = `${this.referenceCoreImport('DeferMode')}.INITIALLY_${prop.deferMode.toUpperCase()}`;
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;
675
494
  }
676
- if (typeof prop.ignoreSchemaChanges !== 'undefined') {
677
- options.ignoreSchemaChanges ??= [];
678
- options.ignoreSchemaChanges.push(...prop.ignoreSchemaChanges.map(v => this.quote(v)));
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`;
679
546
  }
680
- }
681
- #propTypeBreakdowns = new WeakMap();
682
- breakdownOfIType(prop) {
683
- if (this.#propTypeBreakdowns.has(prop)) {
684
- return this.#propTypeBreakdowns.get(prop);
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
+ }
562
+ };
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;
685
591
  }
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
- }
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
+ }
701
644
  }
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
- }
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;
725
700
  }
701
+ const r = undefined;
726
702
  this.#propTypeBreakdowns.set(prop, r);
727
703
  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;
739
- }
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]);
747
- }
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
- }
763
- }
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;
768
- }
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
- }
799
- }
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]);
832
- }
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
704
  }
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';
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
+ }
986
812
  }
987
- if (prop.kind === ReferenceKind.MANY_TO_ONE) {
988
- return 'ManyToOne';
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
+ }
989
849
  }
990
- if (prop.kind === ReferenceKind.ONE_TO_MANY) {
991
- return 'OneToMany';
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
+ }
992
857
  }
993
- if (prop.kind === ReferenceKind.MANY_TO_MANY) {
994
- return 'ManyToMany';
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
+ }
995
870
  }
996
- if (prop.kind === ReferenceKind.EMBEDDED) {
997
- return 'Embedded';
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
+ }
998
914
  }
999
- if (prop.enum) {
1000
- return 'Enum';
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';
1001
941
  }
1002
- if (prop.primary) {
1003
- return 'PrimaryKey';
942
+ referenceCoreImport(identifier) {
943
+ this.coreImports.add(identifier);
944
+ return this.options.coreImportsPrefix ? `${this.options.coreImportsPrefix}${identifier}` : identifier;
1004
945
  }
1005
- if (prop.formula) {
1006
- return 'Formula';
946
+ referenceDecoratorImport(identifier) {
947
+ this.decoratorImports.add(identifier);
948
+ return this.options.coreImportsPrefix ? `${this.options.coreImportsPrefix}${identifier}` : identifier;
1007
949
  }
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
- }
1018
950
  }