@mikro-orm/entity-generator 7.0.0-dev.33 → 7.0.0-dev.330
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/DefineEntitySourceFile.d.ts +5 -0
- package/DefineEntitySourceFile.js +146 -0
- package/EntityGenerator.d.ts +3 -10
- package/EntityGenerator.js +161 -76
- package/EntitySchemaSourceFile.d.ts +3 -2
- package/EntitySchemaSourceFile.js +53 -38
- package/NativeEnumSourceFile.d.ts +12 -0
- package/NativeEnumSourceFile.js +47 -0
- package/README.md +7 -4
- package/SourceFile.d.ts +9 -2
- package/SourceFile.js +307 -112
- package/package.json +32 -32
|
@@ -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
|
+
}
|
package/EntityGenerator.d.ts
CHANGED
|
@@ -1,19 +1,12 @@
|
|
|
1
1
|
import { type GenerateOptions, type MikroORM } from '@mikro-orm/core';
|
|
2
|
-
import { type EntityManager } from '@mikro-orm/
|
|
2
|
+
import { type EntityManager } from '@mikro-orm/sql';
|
|
3
3
|
export declare class EntityGenerator {
|
|
4
|
-
private
|
|
5
|
-
private readonly config;
|
|
6
|
-
private readonly driver;
|
|
7
|
-
private readonly platform;
|
|
8
|
-
private readonly helper;
|
|
9
|
-
private readonly connection;
|
|
10
|
-
private readonly namingStrategy;
|
|
11
|
-
private readonly sources;
|
|
12
|
-
private readonly referencedEntities;
|
|
4
|
+
#private;
|
|
13
5
|
constructor(em: EntityManager);
|
|
14
6
|
static register(orm: MikroORM): void;
|
|
15
7
|
generate(options?: GenerateOptions): Promise<string[]>;
|
|
16
8
|
private getEntityMetadata;
|
|
9
|
+
private cleanUpReferentialIntegrityRules;
|
|
17
10
|
private matchName;
|
|
18
11
|
private detectManyToManyRelations;
|
|
19
12
|
private generateBidirectionalRelations;
|
package/EntityGenerator.js
CHANGED
|
@@ -1,62 +1,78 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { DatabaseSchema, } from '@mikro-orm/
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
#config;
|
|
12
|
+
#driver;
|
|
13
|
+
#platform;
|
|
14
|
+
#helper;
|
|
15
|
+
#connection;
|
|
16
|
+
#namingStrategy;
|
|
17
|
+
#sources = [];
|
|
18
|
+
#referencedEntities = new WeakSet();
|
|
19
|
+
#em;
|
|
17
20
|
constructor(em) {
|
|
18
|
-
this
|
|
19
|
-
this
|
|
20
|
-
this
|
|
21
|
-
this
|
|
22
|
-
this
|
|
23
|
-
this
|
|
24
|
-
this
|
|
21
|
+
this.#em = em;
|
|
22
|
+
this.#config = this.#em.config;
|
|
23
|
+
this.#driver = this.#em.getDriver();
|
|
24
|
+
this.#platform = this.#driver.getPlatform();
|
|
25
|
+
this.#helper = this.#platform.getSchemaHelper();
|
|
26
|
+
this.#connection = this.#driver.getConnection();
|
|
27
|
+
this.#namingStrategy = this.#config.getNamingStrategy();
|
|
25
28
|
}
|
|
26
29
|
static register(orm) {
|
|
27
30
|
orm.config.registerExtension('@mikro-orm/entity-generator', () => new EntityGenerator(orm.em));
|
|
28
31
|
}
|
|
29
32
|
async generate(options = {}) {
|
|
30
|
-
options = Utils.mergeConfig({}, this
|
|
31
|
-
const schema = await DatabaseSchema.create(this
|
|
33
|
+
options = Utils.mergeConfig({}, this.#config.get('entityGenerator'), options);
|
|
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
|
-
const defaultPath = `${this
|
|
34
|
-
const baseDir =
|
|
36
|
+
const defaultPath = `${this.#config.get('baseDir')}/generated-entities`;
|
|
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 (
|
|
37
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
|
71
|
+
return files.map(([, data]) => data);
|
|
57
72
|
}
|
|
58
73
|
async getEntityMetadata(schema, options) {
|
|
59
|
-
const metadata = schema
|
|
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 => {
|
|
@@ -68,16 +84,21 @@ export class EntityGenerator {
|
|
|
68
84
|
}
|
|
69
85
|
}
|
|
70
86
|
}
|
|
71
|
-
return table.getEntityDeclaration(this
|
|
87
|
+
return table.getEntityDeclaration(this.#namingStrategy, this.#helper, options.scalarPropertiesForRelations);
|
|
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 ||
|
|
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
|
-
const mappedTypes = prop.columnTypes.map((t, i) => this
|
|
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 =
|
|
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);
|
|
@@ -88,15 +109,17 @@ export class EntityGenerator {
|
|
|
88
109
|
}
|
|
89
110
|
}
|
|
90
111
|
}
|
|
91
|
-
await options.onInitialMetadata?.(metadata, this
|
|
112
|
+
await options.onInitialMetadata?.(metadata, this.#platform);
|
|
92
113
|
// enforce schema usage in class names only on duplicates
|
|
93
114
|
const duplicates = Utils.findDuplicates(metadata.map(meta => meta.className));
|
|
94
115
|
for (const duplicate of duplicates) {
|
|
95
116
|
for (const meta of metadata.filter(meta => meta.className === duplicate)) {
|
|
96
|
-
meta.className = this
|
|
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 &&
|
|
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
|
}
|
|
@@ -116,9 +140,63 @@ export class EntityGenerator {
|
|
|
116
140
|
if (options.undefinedDefaults) {
|
|
117
141
|
this.castNullDefaultsToUndefined(metadata);
|
|
118
142
|
}
|
|
119
|
-
await options.onProcessedMetadata?.(metadata, this
|
|
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,12 +205,14 @@ 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 &&
|
|
131
|
-
|
|
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
|
-
this
|
|
215
|
+
this.#referencedEntities.add(meta);
|
|
136
216
|
}
|
|
137
217
|
// Entities with non-composite PKs are never pivot tables. Skip.
|
|
138
218
|
if (!meta.compositePK) {
|
|
@@ -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.
|
|
@@ -185,12 +263,23 @@ export class EntityGenerator {
|
|
|
185
263
|
// If this now proven pivot entity has persistent props other than the fixed order column,
|
|
186
264
|
// output it, by considering it as a referenced one.
|
|
187
265
|
if (otherProps.length > 0) {
|
|
188
|
-
this
|
|
266
|
+
this.#referencedEntities.add(meta);
|
|
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
|
|
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,
|
|
@@ -199,8 +288,8 @@ export class EntityGenerator {
|
|
|
199
288
|
joinColumns: meta.relations[0].fieldNames,
|
|
200
289
|
inverseJoinColumns: meta.relations[1].fieldNames,
|
|
201
290
|
};
|
|
202
|
-
if (outputPurePivotTables || this
|
|
203
|
-
ownerProp.pivotEntity = meta.
|
|
291
|
+
if (outputPurePivotTables || this.#referencedEntities.has(meta)) {
|
|
292
|
+
ownerProp.pivotEntity = meta.class;
|
|
204
293
|
}
|
|
205
294
|
if (fixedOrderColumn) {
|
|
206
295
|
ownerProp.fixedOrder = true;
|
|
@@ -215,7 +304,7 @@ export class EntityGenerator {
|
|
|
215
304
|
generateBidirectionalRelations(metadata, includeUnreferencedPurePivotTables) {
|
|
216
305
|
const filteredMetadata = includeUnreferencedPurePivotTables
|
|
217
306
|
? metadata
|
|
218
|
-
: metadata.filter(m => !m.pivotTable || this
|
|
307
|
+
: metadata.filter(m => !m.pivotTable || this.#referencedEntities.has(m));
|
|
219
308
|
for (const meta of filteredMetadata) {
|
|
220
309
|
for (const prop of meta.relations) {
|
|
221
310
|
const targetMeta = metadata.find(m => m.className === prop.type);
|
|
@@ -246,16 +335,16 @@ export class EntityGenerator {
|
|
|
246
335
|
continue;
|
|
247
336
|
}
|
|
248
337
|
let i = 1;
|
|
249
|
-
const name = newProp.name = this
|
|
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 +
|
|
340
|
+
newProp.name = name + i++;
|
|
252
341
|
}
|
|
253
342
|
targetMeta.addProperty(newProp);
|
|
254
343
|
}
|
|
255
344
|
}
|
|
256
345
|
}
|
|
257
346
|
generateIdentifiedReferences(metadata) {
|
|
258
|
-
for (const meta of metadata.filter(m => !m.pivotTable || this
|
|
347
|
+
for (const meta of metadata.filter(m => !m.pivotTable || this.#referencedEntities.has(m))) {
|
|
259
348
|
for (const prop of Object.values(meta.properties)) {
|
|
260
349
|
if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) || prop.lazy) {
|
|
261
350
|
prop.ref = true;
|
|
@@ -264,20 +353,16 @@ export class EntityGenerator {
|
|
|
264
353
|
}
|
|
265
354
|
}
|
|
266
355
|
generateAndAttachCustomBaseEntity(metadata, customBaseEntityName) {
|
|
267
|
-
let
|
|
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
|
|
270
|
-
|
|
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
|
-
|
|
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
|
}
|