@mikro-orm/entity-generator 7.0.0-dev.8 → 7.0.0-dev.80

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,115 @@
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', 'ref', 'nullable', 'array', 'object', 'mapToPk', 'hidden', 'concurrencyCheck', 'lazy', 'eager',
85
+ 'orphanRemoval', 'version', 'unsigned', 'returning', 'createForeignKeyConstraint', 'fixedOrder', 'owner',
86
+ 'getter', 'setter', 'unique', 'index', 'hydrate', 'persist', 'autoincrement',
87
+ ]);
88
+ const skipOptions = new Set(['entity', 'kind', 'type', 'items']);
89
+ const spreadOptions = new Set([
90
+ 'fieldNames', 'joinColumns', 'inverseJoinColumns', 'referencedColumnNames', 'ownColumns', 'columnTypes',
91
+ 'cascade', 'ignoreSchemaChanges', 'customOrder', 'groups', 'where', 'orderBy',
92
+ ]);
93
+ const rename = {
94
+ fieldName: 'name',
95
+ };
96
+ for (const key of Object.keys(options)) {
97
+ if (typeof options[key] === 'undefined' || skipOptions.has(key)) {
98
+ continue;
99
+ }
100
+ const method = rename[key] ?? key;
101
+ const params = simpleOptions.has(key) && options[key] === true ? '' : options[key];
102
+ builder += `.${method}`;
103
+ if (key === 'enum') {
104
+ builder += `(${options.items})`;
105
+ }
106
+ else if (spreadOptions.has(key) && typeof params === 'string' && params.startsWith('[')) {
107
+ builder += `(${params.slice(1, -1)})`;
108
+ }
109
+ else {
110
+ builder += `(${params})`;
111
+ }
112
+ }
113
+ return builder;
114
+ }
115
+ }
@@ -1,8 +1,11 @@
1
1
  import { EntityMetadata, ReferenceKind, types, Utils, } from '@mikro-orm/core';
2
2
  import { DatabaseSchema, } from '@mikro-orm/knex';
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;
@@ -32,28 +35,40 @@ export class EntityGenerator {
32
35
  const metadata = await this.getEntityMetadata(schema, options);
33
36
  const defaultPath = `${this.config.get('baseDir')}/generated-entities`;
34
37
  const baseDir = Utils.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));
44
52
  }
53
+ for (const nativeEnum of Object.values(schema.getNativeEnums())) {
54
+ this.sources.push(new NativeEnumSourceFile({}, this.namingStrategy, this.platform, options, nativeEnum));
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
74
  const metadata = schema.getTables()
@@ -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,56 @@ 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 ? this.getEmbeddableDeclOptions() : (this.meta.collection ? this.getEntityDeclOptions() : {})),
23
+ };
24
+ const declLine = `export const ${this.meta.className}Schema = new ${this.referenceCoreImport('EntitySchema')}(`;
25
+ ret += declLine;
26
+ if (this.meta.indexes.length > 0) {
27
+ entitySchemaOptions.indexes = this.meta.indexes.map(index => this.getIndexOptions(index));
28
+ }
29
+ if (this.meta.uniques.length > 0) {
30
+ entitySchemaOptions.uniques = this.meta.uniques.map(index => this.getUniqueOptions(index));
31
+ }
32
+ entitySchemaOptions.properties = Object.fromEntries(Object.entries(this.meta.properties).map(([name, prop]) => [name, this.getPropertyOptions(prop)]));
33
+ // Force top level and properties to be indented, regardless of line length
34
+ entitySchemaOptions[Config] = true;
35
+ entitySchemaOptions.properties[Config] = true;
36
+ ret += this.serializeObject(entitySchemaOptions, declLine.length > 80 ? undefined : 80 - declLine.length, 0);
37
+ ret += ');\n';
38
+ ret = `${this.generateImports()}\n\n${ret}`;
39
+ return ret;
40
+ }
41
+ generateClassDefinition() {
5
42
  let classBody = '';
6
- if (this.meta.className === this.options.customBaseEntityName) {
43
+ if (!this.options.customBaseEntityName || this.meta.className === this.options.customBaseEntityName) {
7
44
  const defineConfigTypeSettings = {};
8
45
  defineConfigTypeSettings.forceObject = this.platform.getConfig().get('serialization').forceObject ?? false;
9
- classBody += `${' '.repeat(2)}[${this.referenceCoreImport('Config')}]?: ${this.referenceCoreImport('DefineConfig')}<${this.serializeObject(defineConfigTypeSettings)}>;\n`;
46
+ if (defineConfigTypeSettings.forceObject) {
47
+ classBody += `${' '.repeat(2)}[${this.referenceCoreImport('Config')}]?: ${this.referenceCoreImport('DefineConfig')}<${this.serializeObject(defineConfigTypeSettings)}>;\n`;
48
+ }
10
49
  }
11
- const enumDefinitions = [];
12
50
  const eagerProperties = [];
13
51
  const primaryProps = [];
14
52
  const props = [];
15
53
  for (const prop of Object.values(this.meta.properties)) {
16
54
  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
55
  if (prop.eager) {
21
56
  eagerProperties.push(prop);
22
57
  }
@@ -38,33 +73,9 @@ export class EntitySchemaSourceFile extends SourceFile {
38
73
  classBody += `${' '.repeat(2)}[${this.referenceCoreImport('EagerProps')}]?: ${eagerPropertyNames.join(' | ')};\n`;
39
74
  }
40
75
  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;
76
+ return this.getEntityClass(classBody);
66
77
  }
67
- getPropertyOptions(prop) {
78
+ getPropertyOptions(prop, quote = true) {
68
79
  const options = {};
69
80
  if (prop.primary) {
70
81
  options.primary = true;
@@ -79,7 +90,7 @@ export class EntitySchemaSourceFile extends SourceFile {
79
90
  this.getOneToManyDecoratorOptions(options, prop);
80
91
  }
81
92
  else if (prop.kind === ReferenceKind.SCALAR || typeof prop.kind === 'undefined') {
82
- this.getScalarPropertyDecoratorOptions(options, prop);
93
+ this.getScalarPropertyDecoratorOptions(options, prop, quote);
83
94
  }
84
95
  else if (prop.kind === ReferenceKind.EMBEDDED) {
85
96
  this.getEmbeddedPropertyDeclarationOptions(options, prop);
@@ -123,14 +134,14 @@ export class EntitySchemaSourceFile extends SourceFile {
123
134
  processIndex('index');
124
135
  processIndex('unique');
125
136
  }
126
- getScalarPropertyDecoratorOptions(options, prop) {
137
+ getScalarPropertyDecoratorOptions(options, prop, quote = true) {
127
138
  if (prop.enum) {
128
139
  options.enum = true;
129
140
  options.items = `() => ${prop.runtimeType}`;
130
141
  }
131
142
  else {
132
- options.type = this.quote(prop.type);
143
+ options.type = quote ? this.quote(prop.type) : prop.type;
133
144
  }
134
- super.getScalarPropertyDecoratorOptions(options, prop);
145
+ super.getScalarPropertyDecoratorOptions(options, prop, quote);
135
146
  }
136
147
  }
@@ -0,0 +1,16 @@
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
+ protected readonly nativeEnum: {
5
+ name: string;
6
+ schema?: string;
7
+ items: string[];
8
+ };
9
+ constructor(meta: EntityMetadata, namingStrategy: NamingStrategy, platform: Platform, options: GenerateOptions, nativeEnum: {
10
+ name: string;
11
+ schema?: string;
12
+ items: string[];
13
+ });
14
+ generate(): string;
15
+ getBaseName(extension?: string): string;
16
+ }
@@ -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
+ }
package/README.md CHANGED
@@ -11,7 +11,6 @@ TypeScript ORM for Node.js based on Data Mapper, [Unit of Work](https://mikro-or
11
11
  [![Chat on discord](https://img.shields.io/discord/1214904142443839538?label=discord&color=blue)](https://discord.gg/w8bjxFHS7X)
12
12
  [![Downloads](https://img.shields.io/npm/dm/@mikro-orm/core.svg)](https://www.npmjs.com/package/@mikro-orm/core)
13
13
  [![Coverage Status](https://img.shields.io/coveralls/mikro-orm/mikro-orm.svg)](https://coveralls.io/r/mikro-orm/mikro-orm?branch=master)
14
- [![Maintainability](https://api.codeclimate.com/v1/badges/27999651d3adc47cfa40/maintainability)](https://codeclimate.com/github/mikro-orm/mikro-orm/maintainability)
15
14
  [![Build Status](https://github.com/mikro-orm/mikro-orm/workflows/tests/badge.svg?branch=master)](https://github.com/mikro-orm/mikro-orm/actions?workflow=tests)
16
15
 
17
16
  ## 🤔 Unit of What?
@@ -141,7 +140,7 @@ There is also auto-generated [CHANGELOG.md](CHANGELOG.md) file based on commit m
141
140
  - [Composite and Foreign Keys as Primary Key](https://mikro-orm.io/docs/composite-keys)
142
141
  - [Filters](https://mikro-orm.io/docs/filters)
143
142
  - [Using `QueryBuilder`](https://mikro-orm.io/docs/query-builder)
144
- - [Preloading Deeply Nested Structures via populate](https://mikro-orm.io/docs/nested-populate)
143
+ - [Populating relations](https://mikro-orm.io/docs/populating-relations)
145
144
  - [Property Validation](https://mikro-orm.io/docs/property-validation)
146
145
  - [Lifecycle Hooks](https://mikro-orm.io/docs/events#hooks)
147
146
  - [Vanilla JS Support](https://mikro-orm.io/docs/usage-with-js)
@@ -382,6 +381,8 @@ See also the list of contributors who [participated](https://github.com/mikro-or
382
381
 
383
382
  Please ⭐️ this repository if this project helped you!
384
383
 
384
+ > If you'd like to support my open-source work, consider sponsoring me directly at [github.com/sponsors/b4nan](https://github.com/sponsors/b4nan).
385
+
385
386
  ## 📝 License
386
387
 
387
388
  Copyright © 2018 [Martin Adámek](https://github.com/b4nan).
package/SourceFile.d.ts CHANGED
@@ -9,7 +9,9 @@ export declare class SourceFile {
9
9
  protected readonly platform: Platform;
10
10
  protected readonly options: GenerateOptions;
11
11
  protected readonly coreImports: Set<string>;
12
+ protected readonly decoratorImports: Set<string>;
12
13
  protected readonly entityImports: Set<string>;
14
+ protected readonly enumImports: Map<string, string[]>;
13
15
  constructor(meta: EntityMetadata, namingStrategy: NamingStrategy, platform: Platform, options: GenerateOptions);
14
16
  generate(): string;
15
17
  protected getIndexOptions(index: EntityMetadata['indexes'][number], isAtEntityLevel?: boolean): IndexOptions<Dictionary, string>;
@@ -30,11 +32,12 @@ export declare class SourceFile {
30
32
  protected getCommonDecoratorOptions(options: Dictionary, prop: EntityProperty): void;
31
33
  private propTypeBreakdowns;
32
34
  private breakdownOfIType;
33
- protected getScalarPropertyDecoratorOptions(options: Dictionary, prop: EntityProperty): void;
35
+ protected getScalarPropertyDecoratorOptions(options: Dictionary, prop: EntityProperty, quote?: boolean): void;
34
36
  protected getManyToManyDecoratorOptions(options: Dictionary, prop: EntityProperty): void;
35
37
  protected getOneToManyDecoratorOptions(options: Dictionary, prop: EntityProperty): void;
36
38
  protected getEmbeddedPropertyDeclarationOptions(options: Dictionary, prop: EntityProperty): void;
37
39
  protected getForeignKeyDecoratorOptions(options: OneToOneOptions<any, any>, prop: EntityProperty): void;
38
40
  protected getDecoratorType(prop: EntityProperty): string;
39
41
  protected referenceCoreImport(identifier: string): string;
42
+ protected referenceDecoratorImport(identifier: string): string;
40
43
  }
package/SourceFile.js CHANGED
@@ -6,14 +6,16 @@ import { POSSIBLE_TYPE_IMPORTS } from './CoreImportsHelper.js';
6
6
  * @see https://github.com/tc39/proposal-regexp-unicode-property-escapes#other-examples
7
7
  */
8
8
  export const identifierRegex = /^(?:[$_\p{ID_Start}])(?:[$\u200C\u200D\p{ID_Continue}])*$/u;
9
- const primitivesAndLibs = [...SCALAR_TYPES, 'bigint', 'Uint8Array', 'unknown', 'object', 'any'];
9
+ const primitivesAndLibs = [...SCALAR_TYPES, 'unknown', 'object', 'any'];
10
10
  export class SourceFile {
11
11
  meta;
12
12
  namingStrategy;
13
13
  platform;
14
14
  options;
15
15
  coreImports = new Set();
16
+ decoratorImports = new Set();
16
17
  entityImports = new Set();
18
+ enumImports = new Map();
17
19
  constructor(meta, namingStrategy, platform, options) {
18
20
  this.meta = meta;
19
21
  this.namingStrategy = namingStrategy;
@@ -25,24 +27,24 @@ export class SourceFile {
25
27
  if (this.meta.embeddable || this.meta.collection) {
26
28
  if (this.meta.embeddable) {
27
29
  const options = this.getEmbeddableDeclOptions();
28
- ret += `@${this.referenceCoreImport('Embeddable')}(${Utils.hasObjectKeys(options) ? this.serializeObject(options) : ''})\n`;
30
+ ret += `@${this.referenceDecoratorImport('Embeddable')}(${Utils.hasObjectKeys(options) ? this.serializeObject(options) : ''})\n`;
29
31
  }
30
32
  else {
31
33
  const options = this.getEntityDeclOptions();
32
- ret += `@${this.referenceCoreImport('Entity')}(${Utils.hasObjectKeys(options) ? this.serializeObject(options) : ''})\n`;
34
+ ret += `@${this.referenceDecoratorImport('Entity')}(${Utils.hasObjectKeys(options) ? this.serializeObject(options) : ''})\n`;
33
35
  }
34
36
  }
35
37
  for (const index of this.meta.indexes) {
36
38
  if (index.properties?.length === 1 && typeof this.meta.properties[index.properties[0]] !== 'undefined') {
37
39
  continue;
38
40
  }
39
- ret += `@${this.referenceCoreImport('Index')}(${this.serializeObject(this.getIndexOptions(index))})\n`;
41
+ ret += `@${this.referenceDecoratorImport('Index')}(${this.serializeObject(this.getIndexOptions(index))})\n`;
40
42
  }
41
43
  for (const index of this.meta.uniques) {
42
44
  if (index.properties?.length === 1 && typeof this.meta.properties[index.properties[0]] !== 'undefined') {
43
45
  continue;
44
46
  }
45
- ret += `@${this.referenceCoreImport('Unique')}(${this.serializeObject(this.getUniqueOptions(index))})\n`;
47
+ ret += `@${this.referenceDecoratorImport('Unique')}(${this.serializeObject(this.getUniqueOptions(index))})\n`;
46
48
  }
47
49
  let classHead = '';
48
50
  if (this.meta.className === this.options.customBaseEntityName) {
@@ -65,7 +67,10 @@ export class SourceFile {
65
67
  classBody += definition;
66
68
  classBody += '\n';
67
69
  if (prop.enum) {
68
- enumDefinitions.push(this.getEnumClassDefinition(prop, 2));
70
+ const def = this.getEnumClassDefinition(prop, 2);
71
+ if (def.length) {
72
+ enumDefinitions.push(def);
73
+ }
69
74
  }
70
75
  if (prop.eager) {
71
76
  eagerProperties.push(prop);
@@ -99,9 +104,12 @@ export class SourceFile {
99
104
  if (typeof index.name === 'string') {
100
105
  indexOpt.name = this.quote(index.name);
101
106
  }
102
- if (index.expression) {
107
+ if (typeof index.expression === 'string') {
103
108
  indexOpt.expression = this.quote(index.expression);
104
109
  }
110
+ else if (typeof index.expression === 'function') {
111
+ indexOpt.expression = `${index.expression}`.replace(')=>`', ') => `');
112
+ }
105
113
  if (isAtEntityLevel && index.properties) {
106
114
  indexOpt.properties = Utils.asArray(index.properties).map(prop => this.quote('' + prop));
107
115
  }
@@ -112,9 +120,12 @@ export class SourceFile {
112
120
  if (typeof index.name === 'string') {
113
121
  uniqueOpt.name = this.quote(index.name);
114
122
  }
115
- if (index.expression) {
123
+ if (typeof index.expression === 'string') {
116
124
  uniqueOpt.expression = this.quote(index.expression);
117
125
  }
126
+ else if (typeof index.expression === 'function') {
127
+ uniqueOpt.expression = `${index.expression}`.replace(')=>`', ') => `');
128
+ }
118
129
  if (isAtEntityLevel && index.properties) {
119
130
  uniqueOpt.properties = Utils.asArray(index.properties).map(prop => this.quote('' + prop));
120
131
  }
@@ -135,37 +146,61 @@ export class SourceFile {
135
146
  return ret;
136
147
  }).join(', '))} } from '@mikro-orm/core';`);
137
148
  }
149
+ if (this.decoratorImports.size > 0) {
150
+ const type = this.options.decorators;
151
+ imports.add(`import { ${([...this.decoratorImports].sort().map(t => {
152
+ let ret = t;
153
+ if (this.options.coreImportsPrefix) {
154
+ const resolvedIdentifier = `${this.options.coreImportsPrefix}${t}`;
155
+ ret += ` as ${resolvedIdentifier}`;
156
+ }
157
+ return ret;
158
+ }).join(', '))} } from '@mikro-orm/decorators/${type}';`);
159
+ }
138
160
  const extension = this.options.esmImport ? '.js' : '';
139
161
  const { dir, base } = parse(`${this.options.path ?? '.'}/${this.getBaseName()}`);
140
162
  const basePath = relative(dir, this.options.path ?? '.') || '.';
141
163
  (this.options.extraImports?.(basePath, base) ?? []).forEach(v => this.entityImports.add(v));
142
164
  const entityImports = [...this.entityImports].filter(e => e !== this.meta.className);
143
- entityImports.sort().forEach(entity => {
165
+ const importMap = new Map();
166
+ for (const entity of entityImports) {
144
167
  const file = this.options.onImport?.(entity, basePath, extension, base) ?? {
145
168
  path: `${basePath}/${this.options.fileName(entity)}${extension}`,
146
169
  name: entity,
147
170
  };
148
171
  if (file.path === '') {
149
172
  if (file.name === '') {
150
- return;
173
+ continue;
151
174
  }
152
- imports.add(`import ${this.quote(file.name)};`);
153
- return;
175
+ importMap.set(file.path, `import ${this.quote(file.name)};`);
176
+ continue;
154
177
  }
155
178
  if (file.name === '') {
156
- imports.add(`import * as ${entity} from ${this.quote(file.path)};`);
157
- return;
179
+ importMap.set(file.path, `import * as ${entity} from ${this.quote(file.path)};`);
180
+ continue;
158
181
  }
159
182
  if (file.name === 'default') {
160
- imports.add(`import ${entity} from ${this.quote(file.path)};`);
161
- return;
183
+ importMap.set(file.path, `import ${entity} from ${this.quote(file.path)};`);
184
+ continue;
162
185
  }
163
186
  if (file.name === entity) {
164
- imports.add(`import { ${entity} } from ${this.quote(file.path)};`);
165
- return;
187
+ importMap.set(file.path, `import { ${entity} } from ${this.quote(file.path)};`);
188
+ continue;
166
189
  }
167
- imports.add(`import { ${identifierRegex.test(file.name) ? file.name : this.quote(file.name)} as ${entity} } from ${this.quote(file.path)};`);
168
- });
190
+ importMap.set(file.path, `import { ${identifierRegex.test(file.name) ? file.name : this.quote(file.name)} as ${entity} } from ${this.quote(file.path)};`);
191
+ }
192
+ if (this.enumImports.size) {
193
+ for (const [name, exports] of this.enumImports.entries()) {
194
+ const file = this.options.onImport?.(name, basePath, extension, base) ?? {
195
+ path: `${basePath}/${this.options.fileName(name)}${extension}`,
196
+ name,
197
+ };
198
+ importMap.set(file.path, `import { ${exports.join(', ')} } from ${this.quote(file.path)};`);
199
+ }
200
+ }
201
+ for (const key of [...importMap.keys()].sort()) {
202
+ imports.add(importMap.get(key));
203
+ }
169
204
  return Array.from(imports.values()).join('\n');
170
205
  }
171
206
  getEntityClass(classBody) {
@@ -195,6 +230,7 @@ export class SourceFile {
195
230
  getPropertyDefinition(prop, padLeft) {
196
231
  const padding = ' '.repeat(padLeft);
197
232
  const propName = identifierRegex.test(prop.name) ? prop.name : this.quote(prop.name);
233
+ const enumMode = this.options.enumMode;
198
234
  let hiddenType = '';
199
235
  if (prop.hidden) {
200
236
  hiddenType += ` & ${this.referenceCoreImport('Hidden')}`;
@@ -212,7 +248,11 @@ export class SourceFile {
212
248
  : (() => {
213
249
  if (isScalar) {
214
250
  if (prop.enum) {
215
- return prop.runtimeType;
251
+ const method = enumMode === 'ts-enum' ? 'getEnumClassName' : 'getEnumTypeName';
252
+ if (prop.nativeEnumName) {
253
+ return this.namingStrategy[method](prop.nativeEnumName, undefined, this.meta.schema);
254
+ }
255
+ return this.namingStrategy[method](prop.fieldNames[0], this.meta.collection, this.meta.schema);
216
256
  }
217
257
  breakdownOfIType = this.breakdownOfIType(prop);
218
258
  if (typeof breakdownOfIType !== 'undefined') {
@@ -247,8 +287,14 @@ export class SourceFile {
247
287
  return `${padding}${ret};\n`;
248
288
  }
249
289
  if (prop.enum && typeof prop.default === 'string') {
290
+ if (enumMode === 'union-type') {
291
+ return `${padding}${ret} = ${this.quote(prop.default)};\n`;
292
+ }
293
+ const enumClassName = prop.nativeEnumName
294
+ ? this.namingStrategy.getEnumClassName(prop.nativeEnumName, undefined, this.meta.schema)
295
+ : this.namingStrategy.getEnumClassName(prop.fieldNames[0], this.meta.collection, this.meta.schema);
250
296
  const enumVal = this.namingStrategy.enumValueToEnumProperty(prop.default, prop.fieldNames[0], this.meta.collection, this.meta.schema);
251
- return `${padding}${ret} = ${propType}${identifierRegex.test(enumVal) ? `.${enumVal}` : `[${this.quote(enumVal)}]`};\n`;
297
+ return `${padding}${ret} = ${enumClassName}${identifierRegex.test(enumVal) ? `.${enumVal}` : `[${this.quote(enumVal)}]`};\n`;
252
298
  }
253
299
  if (prop.fieldNames?.length > 1) {
254
300
  // TODO: Composite FKs with default values require additions to default/defaultRaw that are not yet supported.
@@ -264,15 +310,51 @@ export class SourceFile {
264
310
  return `${padding}${ret} = ${prop.ref ? this.referenceCoreImport('ref') : this.referenceCoreImport('rel')}(${propType}, ${defaultVal});\n`;
265
311
  }
266
312
  getEnumClassDefinition(prop, padLeft) {
313
+ const enumMode = this.options.enumMode;
314
+ if (prop.nativeEnumName) {
315
+ const imports = [];
316
+ if (enumMode !== 'union-type') {
317
+ imports.push(prop.runtimeType);
318
+ }
319
+ if (!this.options.inferEntityType && enumMode !== 'ts-enum') {
320
+ const enumTypeName = this.namingStrategy.getEnumTypeName(prop.nativeEnumName, undefined, this.meta.schema);
321
+ imports.push(enumTypeName);
322
+ }
323
+ this.enumImports.set(prop.runtimeType, imports);
324
+ return '';
325
+ }
267
326
  const enumClassName = this.namingStrategy.getEnumClassName(prop.fieldNames[0], this.meta.collection, this.meta.schema);
327
+ const enumTypeName = this.namingStrategy.getEnumTypeName(prop.fieldNames[0], this.meta.collection, this.meta.schema);
268
328
  const padding = ' '.repeat(padLeft);
269
- let ret = `export enum ${enumClassName} {\n`;
270
329
  const enumValues = prop.items;
330
+ if (enumMode === 'union-type') {
331
+ return `export type ${enumTypeName} = ${enumValues.map(item => this.quote(item)).join(' | ')};\n`;
332
+ }
333
+ let ret = '';
334
+ if (enumMode === 'dictionary') {
335
+ ret += `export const ${enumClassName} = {\n`;
336
+ }
337
+ else {
338
+ ret += `export enum ${enumClassName} {\n`;
339
+ }
271
340
  for (const enumValue of enumValues) {
272
341
  const enumName = this.namingStrategy.enumValueToEnumProperty(enumValue, prop.fieldNames[0], this.meta.collection, this.meta.schema);
273
- ret += `${padding}${identifierRegex.test(enumName) ? enumName : this.quote(enumName)} = ${this.quote(enumValue)},\n`;
342
+ if (enumMode === 'dictionary') {
343
+ ret += `${padding}${identifierRegex.test(enumName) ? enumName : this.quote(enumName)}: ${this.quote(enumValue)},\n`;
344
+ }
345
+ else {
346
+ ret += `${padding}${identifierRegex.test(enumName) ? enumName : this.quote(enumName)} = ${this.quote(enumValue)},\n`;
347
+ }
348
+ }
349
+ if (enumMode === 'dictionary') {
350
+ ret += '} as const;\n';
351
+ }
352
+ else {
353
+ ret += '}\n';
354
+ }
355
+ if (enumMode === 'dictionary') {
356
+ ret += `\nexport type ${enumTypeName} = (typeof ${enumClassName})[keyof typeof ${enumClassName}];\n`;
274
357
  }
275
- ret += '}\n';
276
358
  return ret;
277
359
  }
278
360
  serializeObject(options, wordwrap, spaces, level = 0) {
@@ -312,7 +394,7 @@ export class SourceFile {
312
394
  options.expression = this.quote(this.meta.expression);
313
395
  }
314
396
  else if (typeof this.meta.expression === 'function') {
315
- options.expression = `${this.meta.expression}`;
397
+ options.expression = this.meta.expression.toString();
316
398
  }
317
399
  if (this.meta.repositoryClass) {
318
400
  this.entityImports.add(this.meta.repositoryClass);
@@ -352,7 +434,7 @@ export class SourceFile {
352
434
  getPropertyDecorator(prop, padLeft) {
353
435
  const padding = ' '.repeat(padLeft);
354
436
  const options = {};
355
- let decorator = `@${this.referenceCoreImport(this.getDecoratorType(prop))}`;
437
+ let decorator = `@${this.referenceDecoratorImport(this.getDecoratorType(prop))}`;
356
438
  if (prop.kind === ReferenceKind.MANY_TO_MANY) {
357
439
  this.getManyToManyDecoratorOptions(options, prop);
358
440
  }
@@ -373,7 +455,7 @@ export class SourceFile {
373
455
  decorator = [...indexes.sort(), decorator].map(d => padding + d).join('\n');
374
456
  const decoratorArgs = [];
375
457
  if (prop.formula) {
376
- decoratorArgs.push(`${prop.formula}`);
458
+ decoratorArgs.push(prop.formula.toString());
377
459
  }
378
460
  if (Utils.hasObjectKeys(options)) {
379
461
  decoratorArgs.push(`${this.serializeObject(options)}`);
@@ -402,26 +484,26 @@ export class SourceFile {
402
484
  let propIndexIsNonTrivialIndex = false;
403
485
  const nonTrivialIndexes = this.meta.indexes.filter(i => i.properties?.length === 1 && i.properties[0] === prop.name);
404
486
  for (const i of nonTrivialIndexes) {
405
- ret.push(`@${this.referenceCoreImport('Index')}(${this.serializeObject(this.getIndexOptions(i, false))})`);
487
+ ret.push(`@${this.referenceDecoratorImport('Index')}(${this.serializeObject(this.getIndexOptions(i, false))})`);
406
488
  if (prop.index === i.name) {
407
489
  propIndexIsNonTrivialIndex = true;
408
490
  delete options.index;
409
491
  }
410
492
  }
411
493
  if (prop.index && !options.index && !propIndexIsNonTrivialIndex) {
412
- ret.push(`@${this.referenceCoreImport('Index')}(${typeof prop.index === 'string' ? `{ name: ${this.quote(prop.index)} }` : ''})`);
494
+ ret.push(`@${this.referenceDecoratorImport('Index')}(${typeof prop.index === 'string' ? `{ name: ${this.quote(prop.index)} }` : ''})`);
413
495
  }
414
496
  let propIndexIsNonTrivialUnique = false;
415
497
  const nonTrivialUnique = this.meta.uniques.filter(i => i.properties?.length === 1 && i.properties[0] === prop.name);
416
498
  for (const i of nonTrivialUnique) {
417
- ret.push(`@${this.referenceCoreImport('Unique')}(${this.serializeObject(this.getUniqueOptions(i, false))})`);
499
+ ret.push(`@${this.referenceDecoratorImport('Unique')}(${this.serializeObject(this.getUniqueOptions(i, false))})`);
418
500
  if (prop.unique === i.name) {
419
501
  propIndexIsNonTrivialUnique = true;
420
502
  delete options.unique;
421
503
  }
422
504
  }
423
505
  if (prop.unique && !options.unique && !propIndexIsNonTrivialUnique) {
424
- ret.push(`@${this.referenceCoreImport('Unique')}(${typeof prop.unique === 'string' ? `{ name: ${this.quote(prop.unique)} }` : ''})`);
506
+ ret.push(`@${this.referenceDecoratorImport('Unique')}(${typeof prop.unique === 'string' ? `{ name: ${this.quote(prop.unique)} }` : ''})`);
425
507
  }
426
508
  return ret;
427
509
  }
@@ -432,7 +514,7 @@ export class SourceFile {
432
514
  if (prop.primary && (prop.enum || !(typeof prop.kind === 'undefined' || prop.kind === ReferenceKind.SCALAR))) {
433
515
  options.primary = true;
434
516
  }
435
- ['persist', 'hydrate', 'trackChanges']
517
+ ['persist', 'hydrate']
436
518
  .filter(key => prop[key] === false)
437
519
  .forEach(key => options[key] = false);
438
520
  ['onCreate', 'onUpdate', 'serializer']
@@ -531,12 +613,23 @@ export class SourceFile {
531
613
  this.propTypeBreakdowns.set(prop, r);
532
614
  return r;
533
615
  }
534
- getScalarPropertyDecoratorOptions(options, prop) {
616
+ getScalarPropertyDecoratorOptions(options, prop, quote = true) {
535
617
  if (prop.fieldNames[0] !== this.namingStrategy.propertyToColumnName(prop.name)) {
536
618
  options.fieldName = this.quote(prop.fieldNames[0]);
537
619
  }
538
620
  if (prop.enum) {
539
- options.items = `() => ${prop.runtimeType}`;
621
+ if (this.options.enumMode === 'union-type') {
622
+ options.items = `[${prop.items.map(item => this.quote(item)).join(', ')}]`;
623
+ }
624
+ else if (prop.nativeEnumName) {
625
+ const enumClassName = this.namingStrategy.getEnumClassName(prop.nativeEnumName, undefined, this.meta.schema);
626
+ options.items = `() => ${enumClassName}`;
627
+ options.nativeEnumName = this.quote(prop.nativeEnumName);
628
+ }
629
+ else {
630
+ const enumClassName = this.namingStrategy.getEnumClassName(prop.fieldNames[0], this.meta.collection, this.meta.schema);
631
+ options.items = `() => ${enumClassName}`;
632
+ }
540
633
  }
541
634
  // For enum properties, we don't need a column type
542
635
  // or the property length or other information in the decorator.
@@ -565,7 +658,7 @@ export class SourceFile {
565
658
  return ((useDefault && !hasUsableNullDefault) || (prop.optional && !prop.nullable));
566
659
  })() // also when there is the "| Opt" type modifier (because reflect-metadata can't extract the base)
567
660
  ) {
568
- options.type = this.quote(prop.type);
661
+ options.type = quote ? this.quote(prop.type) : prop.type;
569
662
  }
570
663
  }
571
664
  const columnTypeFromMappedRuntimeType = mappedRuntimeType.getColumnType({ ...prop, autoincrement: false }, this.platform);
@@ -592,7 +685,7 @@ export class SourceFile {
592
685
  assign('length');
593
686
  }
594
687
  // those are already included in the `columnType` in most cases, and when that option is present, they would be ignored anyway
595
- /* v8 ignore next 4 */
688
+ /* v8 ignore next */
596
689
  if (mappedColumnType instanceof DecimalType && !options.columnType) {
597
690
  assign('precision');
598
691
  assign('scale');
@@ -666,7 +759,7 @@ export class SourceFile {
666
759
  if (prop.array) {
667
760
  options.array = true;
668
761
  }
669
- if (prop.object) {
762
+ if (prop.object && !prop.array) {
670
763
  options.object = true;
671
764
  }
672
765
  if (prop.prefix === false || typeof prop.prefix === 'string') {
@@ -749,4 +842,10 @@ export class SourceFile {
749
842
  ? `${this.options.coreImportsPrefix}${identifier}`
750
843
  : identifier;
751
844
  }
845
+ referenceDecoratorImport(identifier) {
846
+ this.decoratorImports.add(identifier);
847
+ return this.options.coreImportsPrefix
848
+ ? `${this.options.coreImportsPrefix}${identifier}`
849
+ : identifier;
850
+ }
752
851
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mikro-orm/entity-generator",
3
3
  "type": "module",
4
- "version": "7.0.0-dev.8",
4
+ "version": "7.0.0-dev.80",
5
5
  "description": "TypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, PostgreSQL and SQLite databases as well as usage with vanilla JavaScript.",
6
6
  "exports": {
7
7
  "./package.json": "./package.json",
@@ -38,7 +38,7 @@
38
38
  },
39
39
  "homepage": "https://mikro-orm.io",
40
40
  "engines": {
41
- "node": ">= 22.11.0"
41
+ "node": ">= 22.17.0"
42
42
  },
43
43
  "scripts": {
44
44
  "build": "yarn clean && yarn compile && yarn copy",
@@ -50,12 +50,12 @@
50
50
  "access": "public"
51
51
  },
52
52
  "dependencies": {
53
- "@mikro-orm/knex": "7.0.0-dev.8"
53
+ "@mikro-orm/knex": "7.0.0-dev.80"
54
54
  },
55
55
  "devDependencies": {
56
- "@mikro-orm/core": "^6.4.9"
56
+ "@mikro-orm/core": "^6.6.1"
57
57
  },
58
58
  "peerDependencies": {
59
- "@mikro-orm/core": "7.0.0-dev.8"
59
+ "@mikro-orm/core": "7.0.0-dev.80"
60
60
  }
61
61
  }