@yandjin-mikro-orm/entity-generator 6.1.4-rc-sti-changes-1

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,21 @@
1
+ import { type GenerateOptions, type MikroORM } from "@yandjin-mikro-orm/core";
2
+ import { type EntityManager } from "@yandjin-mikro-orm/knex";
3
+ export declare class EntityGenerator {
4
+ private readonly em;
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;
13
+ constructor(em: EntityManager);
14
+ static register(orm: MikroORM): void;
15
+ generate(options?: GenerateOptions): Promise<string[]>;
16
+ private getEntityMetadata;
17
+ private detectManyToManyRelations;
18
+ private generateBidirectionalRelations;
19
+ private generateIdentifiedReferences;
20
+ private generateAndAttachCustomBaseEntity;
21
+ }
@@ -0,0 +1,257 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EntityGenerator = void 0;
4
+ const core_1 = require("@yandjin-mikro-orm/core");
5
+ const knex_1 = require("@yandjin-mikro-orm/knex");
6
+ const fs_extra_1 = require("fs-extra");
7
+ const EntitySchemaSourceFile_1 = require("./EntitySchemaSourceFile");
8
+ const SourceFile_1 = require("./SourceFile");
9
+ class EntityGenerator {
10
+ em;
11
+ config;
12
+ driver;
13
+ platform;
14
+ helper;
15
+ connection;
16
+ namingStrategy;
17
+ sources = [];
18
+ referencedEntities = new WeakSet();
19
+ constructor(em) {
20
+ this.em = em;
21
+ this.config = this.em.config;
22
+ this.driver = this.em.getDriver();
23
+ this.platform = this.driver.getPlatform();
24
+ this.helper = this.platform.getSchemaHelper();
25
+ this.connection = this.driver.getConnection();
26
+ this.namingStrategy = this.config.getNamingStrategy();
27
+ }
28
+ static register(orm) {
29
+ orm.config.registerExtension("@mikro-orm/entity-generator", () => new EntityGenerator(orm.em));
30
+ }
31
+ async generate(options = {}) {
32
+ options = core_1.Utils.mergeConfig({}, this.config.get("entityGenerator"), options);
33
+ const schema = await knex_1.DatabaseSchema.create(this.connection, this.platform, this.config);
34
+ const metadata = await this.getEntityMetadata(schema, options);
35
+ const defaultPath = `${this.config.get("baseDir")}/generated-entities`;
36
+ const baseDir = core_1.Utils.normalizePath(options.path ?? defaultPath);
37
+ for (const meta of metadata) {
38
+ if (!meta.pivotTable || this.referencedEntities.has(meta)) {
39
+ if (options.entitySchema) {
40
+ this.sources.push(new EntitySchemaSourceFile_1.EntitySchemaSourceFile(meta, this.namingStrategy, this.platform, { ...options, scalarTypeInDecorator: true }));
41
+ }
42
+ else {
43
+ this.sources.push(new SourceFile_1.SourceFile(meta, this.namingStrategy, this.platform, options));
44
+ }
45
+ }
46
+ }
47
+ if (options.save) {
48
+ await (0, fs_extra_1.ensureDir)(baseDir);
49
+ await Promise.all(this.sources.map((file) => (0, fs_extra_1.writeFile)(baseDir + "/" + file.getBaseName(), file.generate(), {
50
+ flush: true,
51
+ })));
52
+ }
53
+ return this.sources.map((file) => file.generate());
54
+ }
55
+ async getEntityMetadata(schema, options) {
56
+ let metadata = schema
57
+ .getTables()
58
+ .filter((table) => !options.schema || table.schema === options.schema)
59
+ .sort((a, b) => a.name.localeCompare(b.name))
60
+ .map((table) => {
61
+ const skipColumns = options.skipColumns?.[table.getShortestName()];
62
+ if (skipColumns) {
63
+ table.getColumns().forEach((col) => {
64
+ if (skipColumns.includes(col.name)) {
65
+ table.removeColumn(col.name);
66
+ }
67
+ });
68
+ }
69
+ return table.getEntityDeclaration(this.namingStrategy, this.helper, options.scalarPropertiesForRelations);
70
+ });
71
+ for (const meta of metadata) {
72
+ for (const prop of meta.relations) {
73
+ if (options.skipTables?.includes(prop.referencedTableName)) {
74
+ prop.kind = core_1.ReferenceKind.SCALAR;
75
+ const meta2 = metadata.find((m) => m.className === prop.type);
76
+ prop.type = meta2
77
+ .getPrimaryProps()
78
+ .map((pk) => pk.type)
79
+ .join(" | ");
80
+ }
81
+ }
82
+ }
83
+ metadata = metadata.filter((table) => !options.skipTables || !options.skipTables.includes(table.tableName));
84
+ await options.onInitialMetadata?.(metadata, this.platform);
85
+ this.detectManyToManyRelations(metadata, options.onlyPurePivotTables, options.readOnlyPivotTables);
86
+ if (options.bidirectionalRelations) {
87
+ this.generateBidirectionalRelations(metadata);
88
+ }
89
+ if (options.identifiedReferences) {
90
+ this.generateIdentifiedReferences(metadata);
91
+ }
92
+ if (options.customBaseEntityName) {
93
+ this.generateAndAttachCustomBaseEntity(metadata, options.customBaseEntityName);
94
+ }
95
+ // enforce schema usage in class names only on duplicates
96
+ const duplicates = core_1.Utils.findDuplicates(metadata.map((meta) => meta.className));
97
+ for (const duplicate of duplicates) {
98
+ for (const meta of metadata.filter((meta) => meta.className === duplicate)) {
99
+ meta.className = this.namingStrategy.getEntityName(`${meta.schema}_${meta.className}`);
100
+ metadata.forEach((meta) => meta.relations.forEach((prop) => {
101
+ if (prop.type === duplicate) {
102
+ prop.type = meta.className;
103
+ }
104
+ }));
105
+ }
106
+ }
107
+ await options.onProcessedMetadata?.(metadata, this.platform);
108
+ return metadata;
109
+ }
110
+ detectManyToManyRelations(metadata, onlyPurePivotTables, readOnlyPivotTables) {
111
+ for (const meta of metadata) {
112
+ const isReferenced = metadata.some((m) => {
113
+ return (m.tableName !== meta.tableName &&
114
+ m.relations.some((r) => {
115
+ return (r.referencedTableName === meta.tableName &&
116
+ [core_1.ReferenceKind.MANY_TO_ONE, core_1.ReferenceKind.ONE_TO_ONE].includes(r.kind));
117
+ }));
118
+ });
119
+ if (isReferenced) {
120
+ this.referencedEntities.add(meta);
121
+ }
122
+ // Entities with non-composite PKs are never pivot tables. Skip.
123
+ if (!meta.compositePK) {
124
+ continue;
125
+ }
126
+ // Entities where there are not exactly 2 PK relations that are both ManyToOne are never pivot tables. Skip.
127
+ const pkRelations = meta.relations.filter((rel) => rel.primary);
128
+ if (pkRelations.length !== 2 ||
129
+ pkRelations.some((rel) => rel.kind !== core_1.ReferenceKind.MANY_TO_ONE)) {
130
+ continue;
131
+ }
132
+ const pkRelationFields = new Set(pkRelations.flatMap((rel) => rel.fieldNames));
133
+ const nonPkFields = Array.from(new Set(meta.props.flatMap((prop) => prop.fieldNames))).filter((fieldName) => !pkRelationFields.has(fieldName));
134
+ let fixedOrderColumn;
135
+ let isReadOnly = false;
136
+ // If there are any fields other than the ones in the two PK relations, table may or may not be a pivot one.
137
+ // Check further and skip on disqualification.
138
+ if (nonPkFields.length > 0) {
139
+ // Additional columns have been disabled with the setting.
140
+ // Skip table even it otherwise would have qualified as a pivot table.
141
+ if (onlyPurePivotTables) {
142
+ continue;
143
+ }
144
+ const pkRelationNames = pkRelations.map((rel) => rel.name);
145
+ let otherProps = meta.props.filter((prop) => !pkRelationNames.includes(prop.name) &&
146
+ prop.persist !== false && // Skip checking non-persist props
147
+ prop.fieldNames.some((fieldName) => nonPkFields.includes(fieldName)));
148
+ // Deal with the auto increment column first. That is the column used for fixed ordering, if present.
149
+ const autoIncrementProp = meta.props.find((prop) => prop.autoincrement && prop.fieldNames.length === 1);
150
+ if (autoIncrementProp) {
151
+ otherProps = otherProps.filter((prop) => prop !== autoIncrementProp);
152
+ fixedOrderColumn = autoIncrementProp.fieldNames[0];
153
+ }
154
+ isReadOnly = otherProps.some((prop) => {
155
+ // If the prop is non-nullable and unique, it will trivially end up causing issues.
156
+ // Mark as read only.
157
+ if (!prop.nullable && prop.unique) {
158
+ return true;
159
+ }
160
+ // Any other props need to also be optional.
161
+ // Whether they have a default or are generated,
162
+ // we've already checked that not explicitly setting the property means the default is either NULL,
163
+ // or a non-unique non-null value, making it safe to write to pivot entity.
164
+ return !prop.optional;
165
+ });
166
+ if (isReadOnly && !readOnlyPivotTables) {
167
+ continue;
168
+ }
169
+ // If this now proven pivot entity has persistent props other than the fixed order column,
170
+ // output it, by considering it as a referenced one.
171
+ if (otherProps.length > 0) {
172
+ this.referencedEntities.add(meta);
173
+ }
174
+ }
175
+ meta.pivotTable = true;
176
+ const owner = metadata.find((m) => m.className === meta.relations[0].type);
177
+ const name = this.namingStrategy.columnNameToProperty(meta.tableName.replace(new RegExp("^" + owner.tableName + "_"), ""));
178
+ const ownerProp = {
179
+ name,
180
+ kind: core_1.ReferenceKind.MANY_TO_MANY,
181
+ pivotTable: meta.tableName,
182
+ type: meta.relations[1].type,
183
+ joinColumns: meta.relations[0].fieldNames,
184
+ inverseJoinColumns: meta.relations[1].fieldNames,
185
+ };
186
+ if (this.referencedEntities.has(meta)) {
187
+ ownerProp.pivotEntity = meta.className;
188
+ }
189
+ if (fixedOrderColumn) {
190
+ ownerProp.fixedOrder = true;
191
+ ownerProp.fixedOrderColumn = fixedOrderColumn;
192
+ }
193
+ if (isReadOnly) {
194
+ ownerProp.persist = false;
195
+ }
196
+ owner.addProperty(ownerProp);
197
+ }
198
+ }
199
+ generateBidirectionalRelations(metadata) {
200
+ for (const meta of metadata.filter((m) => !m.pivotTable || this.referencedEntities.has(m))) {
201
+ for (const prop of meta.relations) {
202
+ const targetMeta = metadata.find((m) => m.className === prop.type);
203
+ const newProp = {
204
+ name: prop.name + "Inverse",
205
+ type: meta.className,
206
+ joinColumns: prop.fieldNames,
207
+ referencedTableName: meta.tableName,
208
+ referencedColumnNames: core_1.Utils.flatten(targetMeta.getPrimaryProps().map((pk) => pk.fieldNames)),
209
+ mappedBy: prop.name,
210
+ persist: prop.persist,
211
+ };
212
+ if (prop.kind === core_1.ReferenceKind.MANY_TO_ONE) {
213
+ newProp.kind = core_1.ReferenceKind.ONE_TO_MANY;
214
+ }
215
+ else if (prop.kind === core_1.ReferenceKind.ONE_TO_ONE && !prop.mappedBy) {
216
+ newProp.kind = core_1.ReferenceKind.ONE_TO_ONE;
217
+ newProp.nullable = true;
218
+ }
219
+ else if (prop.kind === core_1.ReferenceKind.MANY_TO_MANY && !prop.mappedBy) {
220
+ newProp.kind = core_1.ReferenceKind.MANY_TO_MANY;
221
+ }
222
+ else {
223
+ continue;
224
+ }
225
+ targetMeta.addProperty(newProp);
226
+ }
227
+ }
228
+ }
229
+ generateIdentifiedReferences(metadata) {
230
+ for (const meta of metadata.filter((m) => !m.pivotTable || this.referencedEntities.has(m))) {
231
+ for (const prop of Object.values(meta.properties)) {
232
+ if ([core_1.ReferenceKind.MANY_TO_ONE, core_1.ReferenceKind.ONE_TO_ONE].includes(prop.kind) ||
233
+ prop.lazy) {
234
+ prop.ref = true;
235
+ }
236
+ }
237
+ }
238
+ }
239
+ generateAndAttachCustomBaseEntity(metadata, customBaseEntityName) {
240
+ let baseClassExists = false;
241
+ for (const meta of metadata) {
242
+ if (meta.className === customBaseEntityName) {
243
+ baseClassExists = true;
244
+ continue;
245
+ }
246
+ meta.extends ??= customBaseEntityName;
247
+ }
248
+ if (!baseClassExists) {
249
+ metadata.push(new core_1.EntityMetadata({
250
+ className: customBaseEntityName,
251
+ abstract: true,
252
+ relations: [],
253
+ }));
254
+ }
255
+ }
256
+ }
257
+ exports.EntityGenerator = EntityGenerator;
@@ -0,0 +1,8 @@
1
+ import { type Dictionary, type EntityProperty } from "@yandjin-mikro-orm/core";
2
+ import { SourceFile } from "./SourceFile";
3
+ export declare class EntitySchemaSourceFile extends SourceFile {
4
+ generate(): string;
5
+ private getPropertyOptions;
6
+ protected getPropertyIndexesOptions(prop: EntityProperty, options: Dictionary): void;
7
+ protected getScalarPropertyDecoratorOptions(options: Dictionary, prop: EntityProperty): void;
8
+ }
@@ -0,0 +1,181 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EntitySchemaSourceFile = void 0;
4
+ const core_1 = require("@yandjin-mikro-orm/core");
5
+ const SourceFile_1 = require("./SourceFile");
6
+ class EntitySchemaSourceFile extends SourceFile_1.SourceFile {
7
+ generate() {
8
+ this.coreImports.add("EntitySchema");
9
+ let classBody = "";
10
+ if (this.meta.className === this.options.customBaseEntityName) {
11
+ this.coreImports.add("Config");
12
+ this.coreImports.add("DefineConfig");
13
+ const defineConfigTypeSettings = {};
14
+ defineConfigTypeSettings.forceObject =
15
+ this.platform.getConfig().get("serialization").forceObject ?? false;
16
+ classBody += `${" ".repeat(2)}[Config]?: DefineConfig<${this.serializeObject(defineConfigTypeSettings)}>;\n`;
17
+ }
18
+ const enumDefinitions = [];
19
+ const eagerProperties = [];
20
+ const primaryProps = [];
21
+ const props = [];
22
+ for (const prop of Object.values(this.meta.properties)) {
23
+ props.push(this.getPropertyDefinition(prop, 2));
24
+ if (prop.enum) {
25
+ const enumClassName = this.namingStrategy.getClassName(this.meta.collection + "_" + prop.fieldNames[0], "_");
26
+ enumDefinitions.push(this.getEnumClassDefinition(enumClassName, prop.items, 2));
27
+ }
28
+ if (prop.eager) {
29
+ eagerProperties.push(prop);
30
+ }
31
+ if (prop.primary &&
32
+ (!["id", "_id", "uuid"].includes(prop.name) || this.meta.compositePK)) {
33
+ primaryProps.push(prop);
34
+ }
35
+ }
36
+ if (primaryProps.length > 0) {
37
+ this.coreImports.add("PrimaryKeyProp");
38
+ const primaryPropNames = primaryProps.map((prop) => `'${prop.name}'`);
39
+ if (primaryProps.length > 1) {
40
+ classBody += `${" ".repeat(2)}[PrimaryKeyProp]?: [${primaryPropNames.join(", ")}];\n`;
41
+ }
42
+ else {
43
+ classBody += `${" ".repeat(2)}[PrimaryKeyProp]?: ${primaryPropNames[0]};\n`;
44
+ }
45
+ }
46
+ if (eagerProperties.length > 0) {
47
+ this.coreImports.add("EagerProps");
48
+ const eagerPropertyNames = eagerProperties
49
+ .map((prop) => `'${prop.name}'`)
50
+ .sort();
51
+ classBody += `${" ".repeat(2)}[EagerProps]?: ${eagerPropertyNames.join(" | ")};\n`;
52
+ }
53
+ classBody += `${props.join("")}`;
54
+ const classDecl = this.getEntityClass(classBody);
55
+ let ret = `${this.generateImports()}\n\n${classDecl}`;
56
+ if (enumDefinitions.length) {
57
+ ret += "\n" + enumDefinitions.join("\n");
58
+ }
59
+ ret += `\n`;
60
+ ret += `export const ${this.meta.className}Schema = new EntitySchema({\n`;
61
+ ret += ` class: ${this.meta.className},\n`;
62
+ if (this.meta.tableName &&
63
+ this.meta.tableName !==
64
+ this.namingStrategy.classToTableName(this.meta.className)) {
65
+ ret += ` tableName: ${this.quote(this.meta.tableName)},\n`;
66
+ }
67
+ /* istanbul ignore next */
68
+ if (this.meta.schema &&
69
+ this.meta.schema !== this.platform.getDefaultSchemaName()) {
70
+ ret += ` schema: ${this.quote(this.meta.schema)},\n`;
71
+ }
72
+ if (this.meta.indexes.length > 0) {
73
+ ret += ` indexes: [\n`;
74
+ this.meta.indexes.forEach((index) => {
75
+ if (index.expression) {
76
+ ret += ` { name: '${index.name}', expression: ${this.quote(index.expression)} },\n`;
77
+ return;
78
+ }
79
+ const properties = core_1.Utils.asArray(index.properties).map((prop) => `'${prop}'`);
80
+ ret += ` { name: '${index.name}', properties: [${properties.join(", ")}] },\n`;
81
+ });
82
+ ret += ` ],\n`;
83
+ }
84
+ if (this.meta.uniques.length > 0) {
85
+ ret += ` uniques: [\n`;
86
+ this.meta.uniques.forEach((index) => {
87
+ if (index.expression) {
88
+ ret += ` { name: '${index.name}', expression: ${this.quote(index.expression)} },\n`;
89
+ return;
90
+ }
91
+ const properties = core_1.Utils.asArray(index.properties).map((prop) => `'${prop}'`);
92
+ ret += ` { name: '${index.name}', properties: [${properties.join(", ")}] },\n`;
93
+ });
94
+ ret += ` ],\n`;
95
+ }
96
+ ret += ` properties: {\n`;
97
+ Object.values(this.meta.properties).forEach((prop) => {
98
+ const options = this.getPropertyOptions(prop);
99
+ let def = this.serializeObject(options);
100
+ if (def.length > 80) {
101
+ def = this.serializeObject(options, 2);
102
+ }
103
+ //
104
+ ret += ` ${prop.name}: ${def},\n`;
105
+ });
106
+ ret += ` },\n`;
107
+ ret += `});\n`;
108
+ return ret;
109
+ }
110
+ getPropertyOptions(prop) {
111
+ const options = {};
112
+ if (prop.primary) {
113
+ options.primary = true;
114
+ }
115
+ if (typeof prop.kind !== "undefined" &&
116
+ prop.kind !== core_1.ReferenceKind.SCALAR) {
117
+ options.kind = this.quote(prop.kind);
118
+ }
119
+ if (prop.kind === core_1.ReferenceKind.MANY_TO_MANY) {
120
+ this.getManyToManyDecoratorOptions(options, prop);
121
+ }
122
+ else if (prop.kind === core_1.ReferenceKind.ONE_TO_MANY) {
123
+ this.getOneToManyDecoratorOptions(options, prop);
124
+ }
125
+ else if (prop.kind === core_1.ReferenceKind.SCALAR ||
126
+ typeof prop.kind === "undefined") {
127
+ this.getScalarPropertyDecoratorOptions(options, prop);
128
+ }
129
+ else if (prop.kind === core_1.ReferenceKind.EMBEDDED) {
130
+ this.getEmbeddedPropertyDeclarationOptions(options, prop);
131
+ }
132
+ else {
133
+ this.getForeignKeyDecoratorOptions(options, prop);
134
+ }
135
+ if (prop.enum) {
136
+ options.enum = true;
137
+ options.items = `() => ${prop.type}`;
138
+ }
139
+ if (prop.formula) {
140
+ options.formula = `${prop.formula}`;
141
+ }
142
+ this.getCommonDecoratorOptions(options, prop);
143
+ this.getPropertyIndexesOptions(prop, options);
144
+ return options;
145
+ }
146
+ getPropertyIndexesOptions(prop, options) {
147
+ if (prop.kind === core_1.ReferenceKind.SCALAR) {
148
+ if (prop.index) {
149
+ options.index = this.quote(prop.index);
150
+ }
151
+ if (prop.unique) {
152
+ options.unique = this.quote(prop.unique);
153
+ }
154
+ return;
155
+ }
156
+ const processIndex = (type) => {
157
+ if (!prop[type]) {
158
+ return;
159
+ }
160
+ const defaultName = this.platform.getIndexName(this.meta.collection, prop.fieldNames, type);
161
+ /* istanbul ignore next */
162
+ options[type] = defaultName === prop[type] ? "true" : `'${prop[type]}'`;
163
+ const expected = {
164
+ index: this.platform.indexForeignKeys(),
165
+ unique: prop.kind === core_1.ReferenceKind.ONE_TO_ONE,
166
+ };
167
+ if (expected[type] && options[type] === "true") {
168
+ delete options[type];
169
+ }
170
+ };
171
+ processIndex("index");
172
+ processIndex("unique");
173
+ }
174
+ getScalarPropertyDecoratorOptions(options, prop) {
175
+ if (prop.kind === core_1.ReferenceKind.SCALAR && !prop.enum) {
176
+ options.type = this.quote(prop.type);
177
+ }
178
+ super.getScalarPropertyDecoratorOptions(options, prop);
179
+ }
180
+ }
181
+ exports.EntitySchemaSourceFile = EntitySchemaSourceFile;
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2018 Martin Adámek
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.