@mikro-orm/entity-generator 7.0.0-dev.32 → 7.0.0-dev.320

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.
@@ -0,0 +1,5 @@
1
+ import { EntitySchemaSourceFile } from './EntitySchemaSourceFile.js';
2
+ export declare class DefineEntitySourceFile extends EntitySchemaSourceFile {
3
+ generate(): string;
4
+ private getPropertyBuilder;
5
+ }
@@ -0,0 +1,146 @@
1
+ import { Config, ReferenceKind, types, } from '@mikro-orm/core';
2
+ import { EntitySchemaSourceFile } from './EntitySchemaSourceFile.js';
3
+ export class DefineEntitySourceFile extends EntitySchemaSourceFile {
4
+ generate() {
5
+ const enumDefinitions = [];
6
+ for (const prop of Object.values(this.meta.properties)) {
7
+ if (prop.enum && (typeof prop.kind === 'undefined' || prop.kind === ReferenceKind.SCALAR)) {
8
+ const def = this.getEnumClassDefinition(prop, 2);
9
+ if (def.length) {
10
+ enumDefinitions.push(def);
11
+ }
12
+ }
13
+ }
14
+ let ret = '';
15
+ if (!this.options.inferEntityType) {
16
+ ret += this.generateClassDefinition() + '\n';
17
+ }
18
+ if (enumDefinitions.length) {
19
+ ret += enumDefinitions.join('\n') + '\n';
20
+ }
21
+ const entitySchemaOptions = {};
22
+ if (this.options.inferEntityType) {
23
+ entitySchemaOptions.name = this.quote(this.meta.className);
24
+ if (this.meta.compositePK) {
25
+ entitySchemaOptions.primaryKeys = this.meta.getPrimaryProps().map(p => this.quote(p.name));
26
+ }
27
+ }
28
+ else {
29
+ entitySchemaOptions.class = this.meta.className;
30
+ }
31
+ Object.assign(entitySchemaOptions, this.meta.embeddable ? this.getEmbeddableDeclOptions() : this.meta.collection ? this.getEntityDeclOptions() : {});
32
+ const nameSuffix = this.options.inferEntityType ? '' : 'Schema';
33
+ const declLine = `export const ${this.meta.className + nameSuffix} = ${this.referenceCoreImport('defineEntity')}(`;
34
+ ret += declLine;
35
+ if (this.meta.indexes.length > 0) {
36
+ entitySchemaOptions.indexes = this.meta.indexes.map(index => this.getIndexOptions(index));
37
+ }
38
+ if (this.meta.uniques.length > 0) {
39
+ entitySchemaOptions.uniques = this.meta.uniques.map(index => this.getUniqueOptions(index));
40
+ }
41
+ entitySchemaOptions.properties = Object.fromEntries(Object.entries(this.meta.properties).map(([name, prop]) => [name, this.getPropertyBuilder(prop)]));
42
+ // Force top level and properties to be indented, regardless of line length
43
+ entitySchemaOptions[Config] = true;
44
+ entitySchemaOptions.properties[Config] = true;
45
+ ret += this.serializeObject(entitySchemaOptions, declLine.length > 80 ? undefined : 80 - declLine.length, 0);
46
+ ret += ');\n';
47
+ if (this.options.inferEntityType) {
48
+ ret += `\nexport interface I${this.meta.className} extends ${this.referenceCoreImport('InferEntity')}<typeof ${this.meta.className}> {}\n`;
49
+ }
50
+ ret = `${this.generateImports()}\n\n${ret}`;
51
+ return ret;
52
+ }
53
+ getPropertyBuilder(prop) {
54
+ const options = this.getPropertyOptions(prop, false);
55
+ const p = this.referenceCoreImport('p');
56
+ let builder = '';
57
+ switch (prop.kind) {
58
+ case ReferenceKind.ONE_TO_ONE:
59
+ builder += `() => ${p}.oneToOne(${prop.type})`;
60
+ break;
61
+ case ReferenceKind.ONE_TO_MANY:
62
+ builder += `() => ${p}.oneToMany(${prop.type})`;
63
+ break;
64
+ case ReferenceKind.MANY_TO_ONE:
65
+ builder += `() => ${p}.manyToOne(${prop.type})`;
66
+ break;
67
+ case ReferenceKind.MANY_TO_MANY:
68
+ builder += `() => ${p}.manyToMany(${prop.type})`;
69
+ break;
70
+ case ReferenceKind.EMBEDDED:
71
+ builder += `() => ${p}.embedded(${prop.type})`;
72
+ break;
73
+ case ReferenceKind.SCALAR:
74
+ default: {
75
+ if (options.type && !(options.type in types)) {
76
+ builder += `${p}.type(${options.type})`;
77
+ }
78
+ else {
79
+ builder += options.type ? `${p}.${options.type}()` : p;
80
+ }
81
+ }
82
+ }
83
+ const simpleOptions = new Set([
84
+ 'primary',
85
+ 'ref',
86
+ 'nullable',
87
+ 'array',
88
+ 'object',
89
+ 'mapToPk',
90
+ 'hidden',
91
+ 'concurrencyCheck',
92
+ 'lazy',
93
+ 'eager',
94
+ 'orphanRemoval',
95
+ 'version',
96
+ 'unsigned',
97
+ 'returning',
98
+ 'createForeignKeyConstraint',
99
+ 'fixedOrder',
100
+ 'owner',
101
+ 'getter',
102
+ 'setter',
103
+ 'unique',
104
+ 'index',
105
+ 'hydrate',
106
+ 'persist',
107
+ 'autoincrement',
108
+ ]);
109
+ const skipOptions = new Set(['entity', 'kind', 'type', 'items']);
110
+ const spreadOptions = new Set([
111
+ 'fieldNames',
112
+ 'joinColumns',
113
+ 'inverseJoinColumns',
114
+ 'referencedColumnNames',
115
+ 'ownColumns',
116
+ 'columnTypes',
117
+ 'cascade',
118
+ 'ignoreSchemaChanges',
119
+ 'customOrder',
120
+ 'groups',
121
+ 'where',
122
+ 'orderBy',
123
+ ]);
124
+ const rename = {
125
+ fieldName: 'name',
126
+ };
127
+ for (const key of Object.keys(options)) {
128
+ if (typeof options[key] === 'undefined' || skipOptions.has(key)) {
129
+ continue;
130
+ }
131
+ const method = rename[key] ?? key;
132
+ const params = simpleOptions.has(key) && options[key] === true ? '' : options[key];
133
+ builder += `.${method}`;
134
+ if (key === 'enum') {
135
+ builder += `(${options.items})`;
136
+ }
137
+ else if (spreadOptions.has(key) && typeof params === 'string' && params.startsWith('[')) {
138
+ builder += `(${params.slice(1, -1)})`;
139
+ }
140
+ else {
141
+ builder += `(${params})`;
142
+ }
143
+ }
144
+ return builder;
145
+ }
146
+ }
@@ -1,5 +1,5 @@
1
1
  import { type GenerateOptions, type MikroORM } from '@mikro-orm/core';
2
- import { type EntityManager } from '@mikro-orm/knex';
2
+ import { type EntityManager } from '@mikro-orm/sql';
3
3
  export declare class EntityGenerator {
4
4
  private readonly em;
5
5
  private readonly config;
@@ -14,6 +14,7 @@ export declare class EntityGenerator {
14
14
  static register(orm: MikroORM): void;
15
15
  generate(options?: GenerateOptions): Promise<string[]>;
16
16
  private getEntityMetadata;
17
+ private cleanUpReferentialIntegrityRules;
17
18
  private matchName;
18
19
  private detectManyToManyRelations;
19
20
  private generateBidirectionalRelations;
@@ -1,8 +1,11 @@
1
- import { EntityMetadata, ReferenceKind, types, Utils, } from '@mikro-orm/core';
2
- import { DatabaseSchema, } from '@mikro-orm/knex';
1
+ import { EntitySchema, ReferenceKind, types, Utils, } from '@mikro-orm/core';
2
+ import { DatabaseSchema, } from '@mikro-orm/sql';
3
+ import { fs } from '@mikro-orm/core/fs-utils';
3
4
  import { dirname, join } from 'node:path';
4
5
  import { writeFile } from 'node:fs/promises';
6
+ import { DefineEntitySourceFile } from './DefineEntitySourceFile.js';
5
7
  import { EntitySchemaSourceFile } from './EntitySchemaSourceFile.js';
8
+ import { NativeEnumSourceFile } from './NativeEnumSourceFile.js';
6
9
  import { SourceFile } from './SourceFile.js';
7
10
  export class EntityGenerator {
8
11
  em;
@@ -31,32 +34,45 @@ export class EntityGenerator {
31
34
  const schema = await DatabaseSchema.create(this.connection, this.platform, this.config, undefined, undefined, options.takeTables, options.skipTables);
32
35
  const metadata = await this.getEntityMetadata(schema, options);
33
36
  const defaultPath = `${this.config.get('baseDir')}/generated-entities`;
34
- const baseDir = Utils.normalizePath(options.path ?? defaultPath);
37
+ const baseDir = fs.normalizePath(options.path ?? defaultPath);
38
+ this.sources.length = 0;
39
+ const map = {
40
+ defineEntity: DefineEntitySourceFile,
41
+ entitySchema: EntitySchemaSourceFile,
42
+ decorators: SourceFile,
43
+ };
44
+ if (options.entityDefinition !== 'decorators') {
45
+ options.scalarTypeInDecorator = true;
46
+ }
35
47
  for (const meta of metadata) {
36
- if (!meta.pivotTable || options.outputPurePivotTables || this.referencedEntities.has(meta)) {
37
- if (options.entitySchema) {
38
- this.sources.push(new EntitySchemaSourceFile(meta, this.namingStrategy, this.platform, { ...options, scalarTypeInDecorator: true }));
39
- }
40
- else {
41
- this.sources.push(new SourceFile(meta, this.namingStrategy, this.platform, options));
42
- }
48
+ if (meta.pivotTable && !options.outputPurePivotTables && !this.referencedEntities.has(meta)) {
49
+ continue;
43
50
  }
51
+ this.sources.push(new map[options.entityDefinition](meta, this.namingStrategy, this.platform, options));
52
+ }
53
+ for (const nativeEnum of Object.values(schema.getNativeEnums())) {
54
+ this.sources.push(new NativeEnumSourceFile({}, this.namingStrategy, this.platform, options, nativeEnum));
44
55
  }
56
+ const files = this.sources.map(file => [file.getBaseName(), file.generate()]);
45
57
  if (options.save) {
46
- Utils.ensureDir(baseDir);
47
- await Promise.all(this.sources.map(async (file) => {
48
- const fileName = file.getBaseName();
49
- const fileDir = dirname(fileName);
50
- if (fileDir !== '.') {
51
- Utils.ensureDir(join(baseDir, fileDir));
52
- }
53
- return writeFile(join(baseDir, fileName), file.generate(), { flush: true });
54
- }));
58
+ fs.ensureDir(baseDir);
59
+ const promises = [];
60
+ for (const [fileName, data] of files) {
61
+ promises.push((async () => {
62
+ const fileDir = dirname(fileName);
63
+ if (fileDir !== '.') {
64
+ fs.ensureDir(join(baseDir, fileDir));
65
+ }
66
+ await writeFile(join(baseDir, fileName), data, { flush: true });
67
+ })());
68
+ }
69
+ await Promise.all(promises);
55
70
  }
56
- return this.sources.map(file => file.generate());
71
+ return files.map(([, data]) => data);
57
72
  }
58
73
  async getEntityMetadata(schema, options) {
59
- const metadata = schema.getTables()
74
+ const metadata = schema
75
+ .getTables()
60
76
  .filter(table => !options.schema || table.schema === options.schema)
61
77
  .sort((a, b) => `${a.schema}.${a.name}`.localeCompare(`${b.schema}.${b.name}`))
62
78
  .map(table => {
@@ -72,12 +88,17 @@ export class EntityGenerator {
72
88
  });
73
89
  for (const meta of metadata) {
74
90
  for (const prop of meta.relations) {
75
- if (!metadata.some(otherMeta => prop.referencedTableName === otherMeta.collection || prop.referencedTableName === `${otherMeta.schema ?? schema.name}.${otherMeta.collection}`)) {
91
+ if (!metadata.some(otherMeta => prop.referencedTableName === otherMeta.collection ||
92
+ prop.referencedTableName === `${otherMeta.schema ?? schema.name}.${otherMeta.collection}`)) {
76
93
  prop.kind = ReferenceKind.SCALAR;
77
94
  const mappedTypes = prop.columnTypes.map((t, i) => this.platform.getMappedType(t));
78
95
  const runtimeTypes = mappedTypes.map(t => t.runtimeType);
79
96
  prop.runtimeType = (runtimeTypes.length === 1 ? runtimeTypes[0] : `[${runtimeTypes.join(', ')}]`);
80
- prop.type = mappedTypes.length === 1 ? (Utils.entries(types).find(([k, v]) => Object.getPrototypeOf(mappedTypes[0]) === v.prototype)?.[0] ?? mappedTypes[0].name) : 'unknown';
97
+ prop.type =
98
+ mappedTypes.length === 1
99
+ ? (Utils.entries(types).find(([k, v]) => Object.getPrototypeOf(mappedTypes[0]) === v.prototype)?.[0] ??
100
+ mappedTypes[0].name)
101
+ : 'unknown';
81
102
  }
82
103
  const meta2 = metadata.find(meta2 => meta2.className === prop.type);
83
104
  const targetPrimaryColumns = meta2?.getPrimaryProps().flatMap(p => p.fieldNames);
@@ -96,7 +117,9 @@ export class EntityGenerator {
96
117
  meta.className = this.namingStrategy.getEntityName(`${meta.schema ?? schema.name}_${meta.className}`);
97
118
  for (const relMeta of metadata) {
98
119
  for (const prop of relMeta.relations) {
99
- if (prop.type === duplicate && (prop.referencedTableName === meta.collection || prop.referencedTableName === `${meta.schema ?? schema.name}.${meta.collection}`)) {
120
+ if (prop.type === duplicate &&
121
+ (prop.referencedTableName === meta.collection ||
122
+ prop.referencedTableName === `${meta.schema ?? schema.name}.${meta.collection}`)) {
100
123
  prop.type = meta.className;
101
124
  }
102
125
  }
@@ -104,6 +127,7 @@ export class EntityGenerator {
104
127
  }
105
128
  }
106
129
  this.detectManyToManyRelations(metadata, options.onlyPurePivotTables, options.readOnlyPivotTables, options.outputPurePivotTables);
130
+ this.cleanUpReferentialIntegrityRules(metadata);
107
131
  if (options.bidirectionalRelations) {
108
132
  this.generateBidirectionalRelations(metadata, options.outputPurePivotTables);
109
133
  }
@@ -119,6 +143,60 @@ export class EntityGenerator {
119
143
  await options.onProcessedMetadata?.(metadata, this.platform);
120
144
  return metadata;
121
145
  }
146
+ cleanUpReferentialIntegrityRules(metadata) {
147
+ // Clear FK rules that match defaults for:
148
+ // 1. FK-as-PK entities (all PKs are FKs) - cascade for both update and delete
149
+ // 2. Fixed-order pivot tables (autoincrement id + 2 FK relations only) - cascade for both
150
+ // 3. Relations to composite PK targets - cascade for update
151
+ for (const meta of metadata) {
152
+ const pks = meta.getPrimaryProps();
153
+ const fkPks = pks.filter(pk => pk.kind !== ReferenceKind.SCALAR);
154
+ // Case 1: All PKs are FKs - default is cascade for both update and delete
155
+ if (fkPks.length > 0 && fkPks.length === pks.length) {
156
+ for (const pk of fkPks) {
157
+ if (pk.deleteRule === 'cascade') {
158
+ delete pk.deleteRule;
159
+ }
160
+ if (pk.updateRule === 'cascade') {
161
+ delete pk.updateRule;
162
+ }
163
+ }
164
+ }
165
+ // Case 2: Fixed-order pivot table (single autoincrement id PK + exactly 2 M:1 relations)
166
+ const hasAutoIncrementPk = pks.length === 1 && pks[0].autoincrement;
167
+ const m2oRelations = meta.relations.filter(r => r.kind === ReferenceKind.MANY_TO_ONE);
168
+ if (hasAutoIncrementPk && m2oRelations.length === 2) {
169
+ // Check if all columns are either the PK or FK columns
170
+ const fkColumns = new Set(m2oRelations.flatMap(r => r.fieldNames));
171
+ const pkColumns = new Set(pks.flatMap(p => p.fieldNames));
172
+ const allColumns = new Set(meta.props.filter(p => p.persist !== false).flatMap(p => p.fieldNames));
173
+ const isPivotLike = [...allColumns].every(col => fkColumns.has(col) || pkColumns.has(col));
174
+ if (isPivotLike) {
175
+ for (const rel of m2oRelations) {
176
+ if (rel.updateRule === 'cascade') {
177
+ delete rel.updateRule;
178
+ }
179
+ if (rel.deleteRule === 'cascade') {
180
+ delete rel.deleteRule;
181
+ }
182
+ }
183
+ }
184
+ }
185
+ // Case 3: Relations to composite PK targets - default is cascade for update
186
+ // Case 4: Nullable relations - default is set null for delete
187
+ for (const rel of meta.relations) {
188
+ if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(rel.kind)) {
189
+ const targetMeta = metadata.find(m => m.className === rel.type);
190
+ if (targetMeta?.compositePK && rel.updateRule === 'cascade') {
191
+ delete rel.updateRule;
192
+ }
193
+ if (rel.nullable && rel.deleteRule === 'set null') {
194
+ delete rel.deleteRule;
195
+ }
196
+ }
197
+ }
198
+ }
199
+ }
122
200
  matchName(name, nameToMatch) {
123
201
  return typeof nameToMatch === 'string'
124
202
  ? name.toLocaleLowerCase() === nameToMatch.toLocaleLowerCase()
@@ -127,9 +205,11 @@ export class EntityGenerator {
127
205
  detectManyToManyRelations(metadata, onlyPurePivotTables, readOnlyPivotTables, outputPurePivotTables) {
128
206
  for (const meta of metadata) {
129
207
  const isReferenced = metadata.some(m => {
130
- return m.tableName !== meta.tableName && m.relations.some(r => {
131
- return r.referencedTableName === meta.tableName && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(r.kind);
132
- });
208
+ return (m.tableName !== meta.tableName &&
209
+ m.relations.some(r => {
210
+ return (r.referencedTableName === meta.tableName &&
211
+ [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(r.kind));
212
+ }));
133
213
  });
134
214
  if (isReferenced) {
135
215
  this.referencedEntities.add(meta);
@@ -140,8 +220,7 @@ export class EntityGenerator {
140
220
  }
141
221
  // Entities where there are not exactly 2 PK relations that are both ManyToOne are never pivot tables. Skip.
142
222
  const pkRelations = meta.relations.filter(rel => rel.primary);
143
- if (pkRelations.length !== 2 ||
144
- pkRelations.some(rel => rel.kind !== ReferenceKind.MANY_TO_ONE)) {
223
+ if (pkRelations.length !== 2 || pkRelations.some(rel => rel.kind !== ReferenceKind.MANY_TO_ONE)) {
145
224
  continue;
146
225
  }
147
226
  const pkRelationFields = new Set(pkRelations.flatMap(rel => rel.fieldNames));
@@ -157,8 +236,7 @@ export class EntityGenerator {
157
236
  continue;
158
237
  }
159
238
  const pkRelationNames = pkRelations.map(rel => rel.name);
160
- let otherProps = meta.props
161
- .filter(prop => !pkRelationNames.includes(prop.name) &&
239
+ let otherProps = meta.props.filter(prop => !pkRelationNames.includes(prop.name) &&
162
240
  prop.persist !== false && // Skip checking non-persist props
163
241
  prop.fieldNames.some(fieldName => nonPkFields.includes(fieldName)));
164
242
  // Deal with the auto increment column first. That is the column used for fixed ordering, if present.
@@ -189,8 +267,19 @@ export class EntityGenerator {
189
267
  }
190
268
  }
191
269
  meta.pivotTable = true;
270
+ // Clear FK rules that match the default for pivot tables (cascade)
271
+ // so they don't get output explicitly in generated code
272
+ for (const rel of meta.relations) {
273
+ if (rel.updateRule === 'cascade') {
274
+ delete rel.updateRule;
275
+ }
276
+ if (rel.deleteRule === 'cascade') {
277
+ delete rel.deleteRule;
278
+ }
279
+ }
192
280
  const owner = metadata.find(m => m.className === meta.relations[0].type);
193
- const name = this.namingStrategy.columnNameToProperty(meta.tableName.replace(new RegExp('^' + owner.tableName + '_'), ''));
281
+ const target = metadata.find(m => m.className === meta.relations[1].type);
282
+ const name = this.namingStrategy.manyToManyPropertyName(owner.className, target.className, meta.tableName, owner.tableName, meta.schema);
194
283
  const ownerProp = {
195
284
  name,
196
285
  kind: ReferenceKind.MANY_TO_MANY,
@@ -200,7 +289,7 @@ export class EntityGenerator {
200
289
  inverseJoinColumns: meta.relations[1].fieldNames,
201
290
  };
202
291
  if (outputPurePivotTables || this.referencedEntities.has(meta)) {
203
- ownerProp.pivotEntity = meta.className;
292
+ ownerProp.pivotEntity = meta.class;
204
293
  }
205
294
  if (fixedOrderColumn) {
206
295
  ownerProp.fixedOrder = true;
@@ -246,9 +335,9 @@ export class EntityGenerator {
246
335
  continue;
247
336
  }
248
337
  let i = 1;
249
- const name = newProp.name = this.namingStrategy.inverseSideName(meta.className, prop.name, newProp.kind);
338
+ const name = (newProp.name = this.namingStrategy.inverseSideName(meta.className, prop.name, newProp.kind));
250
339
  while (targetMeta.properties[newProp.name]) {
251
- newProp.name = name + (i++);
340
+ newProp.name = name + i++;
252
341
  }
253
342
  targetMeta.addProperty(newProp);
254
343
  }
@@ -264,20 +353,16 @@ export class EntityGenerator {
264
353
  }
265
354
  }
266
355
  generateAndAttachCustomBaseEntity(metadata, customBaseEntityName) {
267
- let baseClassExists = false;
356
+ let base = metadata.find(meta => meta.className === customBaseEntityName);
357
+ if (!base) {
358
+ const schema = new EntitySchema({ className: customBaseEntityName, abstract: true });
359
+ base = schema.init().meta;
360
+ metadata.push(base);
361
+ }
268
362
  for (const meta of metadata) {
269
- if (meta.className === customBaseEntityName) {
270
- baseClassExists = true;
271
- continue;
363
+ if (meta.className !== customBaseEntityName) {
364
+ meta.extends ??= base.class;
272
365
  }
273
- meta.extends ??= customBaseEntityName;
274
- }
275
- if (!baseClassExists) {
276
- metadata.push(new EntityMetadata({
277
- className: customBaseEntityName,
278
- abstract: true,
279
- relations: [],
280
- }));
281
366
  }
282
367
  }
283
368
  castNullDefaultsToUndefined(metadata) {
@@ -2,7 +2,8 @@ import { type Dictionary, type EntityProperty } from '@mikro-orm/core';
2
2
  import { SourceFile } from './SourceFile.js';
3
3
  export declare class EntitySchemaSourceFile extends SourceFile {
4
4
  generate(): string;
5
- private getPropertyOptions;
5
+ protected generateClassDefinition(): string;
6
+ protected getPropertyOptions(prop: EntityProperty, quote?: boolean): Dictionary;
6
7
  protected getPropertyIndexesOptions(prop: EntityProperty, options: Dictionary): void;
7
- protected getScalarPropertyDecoratorOptions(options: Dictionary, prop: EntityProperty): void;
8
+ protected getScalarPropertyDecoratorOptions(options: Dictionary, prop: EntityProperty, quote?: boolean): void;
8
9
  }
@@ -2,21 +2,60 @@ import { Config, ReferenceKind, } from '@mikro-orm/core';
2
2
  import { SourceFile } from './SourceFile.js';
3
3
  export class EntitySchemaSourceFile extends SourceFile {
4
4
  generate() {
5
+ const classDefinition = this.generateClassDefinition();
6
+ const enumDefinitions = [];
7
+ for (const prop of Object.values(this.meta.properties)) {
8
+ if (prop.enum && (typeof prop.kind === 'undefined' || prop.kind === ReferenceKind.SCALAR)) {
9
+ const def = this.getEnumClassDefinition(prop, 2);
10
+ if (def.length) {
11
+ enumDefinitions.push(def);
12
+ }
13
+ }
14
+ }
15
+ let ret = classDefinition;
16
+ if (enumDefinitions.length) {
17
+ ret += '\n' + enumDefinitions.join('\n');
18
+ }
19
+ ret += `\n`;
20
+ const entitySchemaOptions = {
21
+ class: this.meta.className,
22
+ ...(this.meta.embeddable
23
+ ? this.getEmbeddableDeclOptions()
24
+ : this.meta.collection
25
+ ? this.getEntityDeclOptions()
26
+ : {}),
27
+ };
28
+ const declLine = `export const ${this.meta.className}Schema = new ${this.referenceCoreImport('EntitySchema')}(`;
29
+ ret += declLine;
30
+ if (this.meta.indexes.length > 0) {
31
+ entitySchemaOptions.indexes = this.meta.indexes.map(index => this.getIndexOptions(index));
32
+ }
33
+ if (this.meta.uniques.length > 0) {
34
+ entitySchemaOptions.uniques = this.meta.uniques.map(index => this.getUniqueOptions(index));
35
+ }
36
+ entitySchemaOptions.properties = Object.fromEntries(Object.entries(this.meta.properties).map(([name, prop]) => [name, this.getPropertyOptions(prop)]));
37
+ // Force top level and properties to be indented, regardless of line length
38
+ entitySchemaOptions[Config] = true;
39
+ entitySchemaOptions.properties[Config] = true;
40
+ ret += this.serializeObject(entitySchemaOptions, declLine.length > 80 ? undefined : 80 - declLine.length, 0);
41
+ ret += ');\n';
42
+ ret = `${this.generateImports()}\n\n${ret}`;
43
+ return ret;
44
+ }
45
+ generateClassDefinition() {
5
46
  let classBody = '';
6
- if (this.meta.className === this.options.customBaseEntityName) {
47
+ if (!this.options.customBaseEntityName || this.meta.className === this.options.customBaseEntityName) {
7
48
  const defineConfigTypeSettings = {};
8
49
  defineConfigTypeSettings.forceObject = this.platform.getConfig().get('serialization').forceObject ?? false;
9
- classBody += `${' '.repeat(2)}[${this.referenceCoreImport('Config')}]?: ${this.referenceCoreImport('DefineConfig')}<${this.serializeObject(defineConfigTypeSettings)}>;\n`;
50
+ if (defineConfigTypeSettings.forceObject) {
51
+ classBody += `${' '.repeat(2)}[${this.referenceCoreImport('Config')}]?: ${this.referenceCoreImport('DefineConfig')}<${this.serializeObject(defineConfigTypeSettings)}>;\n`;
52
+ }
10
53
  }
11
- const enumDefinitions = [];
12
54
  const eagerProperties = [];
13
55
  const primaryProps = [];
14
56
  const props = [];
15
57
  for (const prop of Object.values(this.meta.properties)) {
16
58
  props.push(this.getPropertyDefinition(prop, 2));
17
- if (prop.enum && (typeof prop.kind === 'undefined' || prop.kind === ReferenceKind.SCALAR)) {
18
- enumDefinitions.push(this.getEnumClassDefinition(prop, 2));
19
- }
20
59
  if (prop.eager) {
21
60
  eagerProperties.push(prop);
22
61
  }
@@ -37,34 +76,10 @@ export class EntitySchemaSourceFile extends SourceFile {
37
76
  const eagerPropertyNames = eagerProperties.map(prop => `'${prop.name}'`).sort();
38
77
  classBody += `${' '.repeat(2)}[${this.referenceCoreImport('EagerProps')}]?: ${eagerPropertyNames.join(' | ')};\n`;
39
78
  }
40
- classBody += `${props.join('')}`;
41
- let ret = this.getEntityClass(classBody);
42
- if (enumDefinitions.length) {
43
- ret += '\n' + enumDefinitions.join('\n');
44
- }
45
- ret += `\n`;
46
- const entitySchemaOptions = {
47
- class: this.meta.className,
48
- ...(this.meta.embeddable ? this.getEmbeddableDeclOptions() : (this.meta.collection ? this.getEntityDeclOptions() : {})),
49
- };
50
- const declLine = `export const ${this.meta.className}Schema = new ${this.referenceCoreImport('EntitySchema')}(`;
51
- ret += declLine;
52
- if (this.meta.indexes.length > 0) {
53
- entitySchemaOptions.indexes = this.meta.indexes.map(index => this.getIndexOptions(index));
54
- }
55
- if (this.meta.uniques.length > 0) {
56
- entitySchemaOptions.uniques = this.meta.uniques.map(index => this.getUniqueOptions(index));
57
- }
58
- entitySchemaOptions.properties = Object.fromEntries(Object.entries(this.meta.properties).map(([name, prop]) => [name, this.getPropertyOptions(prop)]));
59
- // Force top level and properties to be indented, regardless of line length
60
- entitySchemaOptions[Config] = true;
61
- entitySchemaOptions.properties[Config] = true;
62
- ret += this.serializeObject(entitySchemaOptions, declLine.length > 80 ? undefined : 80 - declLine.length, 0);
63
- ret += ');\n';
64
- ret = `${this.generateImports()}\n\n${ret}`;
65
- return ret;
79
+ classBody += props.join('');
80
+ return this.getEntityClass(classBody);
66
81
  }
67
- getPropertyOptions(prop) {
82
+ getPropertyOptions(prop, quote = true) {
68
83
  const options = {};
69
84
  if (prop.primary) {
70
85
  options.primary = true;
@@ -79,7 +94,7 @@ export class EntitySchemaSourceFile extends SourceFile {
79
94
  this.getOneToManyDecoratorOptions(options, prop);
80
95
  }
81
96
  else if (prop.kind === ReferenceKind.SCALAR || typeof prop.kind === 'undefined') {
82
- this.getScalarPropertyDecoratorOptions(options, prop);
97
+ this.getScalarPropertyDecoratorOptions(options, prop, quote);
83
98
  }
84
99
  else if (prop.kind === ReferenceKind.EMBEDDED) {
85
100
  this.getEmbeddedPropertyDeclarationOptions(options, prop);
@@ -111,7 +126,7 @@ export class EntitySchemaSourceFile extends SourceFile {
111
126
  }
112
127
  const defaultName = this.platform.getIndexName(this.meta.collection, prop.fieldNames, type);
113
128
  /* v8 ignore next */
114
- options[type] = (propType === true || defaultName === propType) ? 'true' : this.quote(propType);
129
+ options[type] = propType === true || defaultName === propType ? 'true' : this.quote(propType);
115
130
  const expected = {
116
131
  index: this.platform.indexForeignKeys(),
117
132
  unique: prop.kind === ReferenceKind.ONE_TO_ONE,
@@ -123,14 +138,14 @@ export class EntitySchemaSourceFile extends SourceFile {
123
138
  processIndex('index');
124
139
  processIndex('unique');
125
140
  }
126
- getScalarPropertyDecoratorOptions(options, prop) {
141
+ getScalarPropertyDecoratorOptions(options, prop, quote = true) {
127
142
  if (prop.enum) {
128
143
  options.enum = true;
129
144
  options.items = `() => ${prop.runtimeType}`;
130
145
  }
131
146
  else {
132
- options.type = this.quote(prop.type);
147
+ options.type = quote ? this.quote(prop.type) : prop.type;
133
148
  }
134
- super.getScalarPropertyDecoratorOptions(options, prop);
149
+ super.getScalarPropertyDecoratorOptions(options, prop, quote);
135
150
  }
136
151
  }
@@ -0,0 +1,12 @@
1
+ import type { EntityMetadata, GenerateOptions, NamingStrategy, Platform } from '@mikro-orm/core';
2
+ import { SourceFile } from './SourceFile.js';
3
+ export declare class NativeEnumSourceFile extends SourceFile {
4
+ private readonly nativeEnum;
5
+ constructor(meta: EntityMetadata, namingStrategy: NamingStrategy, platform: Platform, options: GenerateOptions, nativeEnum: {
6
+ name: string;
7
+ schema?: string;
8
+ items: string[];
9
+ });
10
+ generate(): string;
11
+ getBaseName(extension?: string): string;
12
+ }
@@ -0,0 +1,47 @@
1
+ import { identifierRegex, SourceFile } from './SourceFile.js';
2
+ export class NativeEnumSourceFile extends SourceFile {
3
+ nativeEnum;
4
+ constructor(meta, namingStrategy, platform, options, nativeEnum) {
5
+ super(meta, namingStrategy, platform, options);
6
+ this.nativeEnum = nativeEnum;
7
+ }
8
+ generate() {
9
+ const enumClassName = this.namingStrategy.getEnumClassName(this.nativeEnum.name, undefined, this.nativeEnum.schema);
10
+ const enumTypeName = this.namingStrategy.getEnumTypeName(this.nativeEnum.name, undefined, this.nativeEnum.schema);
11
+ const padding = ' ';
12
+ const enumMode = this.options.enumMode;
13
+ const enumValues = this.nativeEnum.items;
14
+ if (enumMode === 'union-type') {
15
+ return `export type ${enumTypeName} = ${enumValues.map(item => this.quote(item)).join(' | ')};\n`;
16
+ }
17
+ let ret = '';
18
+ if (enumMode === 'dictionary') {
19
+ ret += `export const ${enumClassName} = {\n`;
20
+ }
21
+ else {
22
+ ret += `export enum ${enumClassName} {\n`;
23
+ }
24
+ for (const enumValue of enumValues) {
25
+ const enumName = this.namingStrategy.enumValueToEnumProperty(enumValue, this.nativeEnum.name, '', this.nativeEnum.schema);
26
+ if (enumMode === 'dictionary') {
27
+ ret += `${padding}${identifierRegex.test(enumName) ? enumName : this.quote(enumName)}: ${this.quote(enumValue)},\n`;
28
+ }
29
+ else {
30
+ ret += `${padding}${identifierRegex.test(enumName) ? enumName : this.quote(enumName)} = ${this.quote(enumValue)},\n`;
31
+ }
32
+ }
33
+ if (enumMode === 'dictionary') {
34
+ ret += '} as const;\n';
35
+ }
36
+ else {
37
+ ret += '}\n';
38
+ }
39
+ if (enumMode === 'dictionary') {
40
+ ret += `\nexport type ${enumTypeName} = (typeof ${enumClassName})[keyof typeof ${enumClassName}];\n`;
41
+ }
42
+ return ret;
43
+ }
44
+ getBaseName(extension = '.ts') {
45
+ return `${this.options.fileName(this.nativeEnum.name)}${extension}`;
46
+ }
47
+ }