@mikro-orm/entity-generator 7.0.0-dev.5 → 7.0.0-dev.50

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
+ }
@@ -2,7 +2,9 @@ import { EntityMetadata, ReferenceKind, types, Utils, } from '@mikro-orm/core';
2
2
  import { DatabaseSchema, } from '@mikro-orm/knex';
3
3
  import { dirname, join } from 'node:path';
4
4
  import { writeFile } from 'node:fs/promises';
5
+ import { DefineEntitySourceFile } from './DefineEntitySourceFile.js';
5
6
  import { EntitySchemaSourceFile } from './EntitySchemaSourceFile.js';
7
+ import { NativeEnumSourceFile } from './NativeEnumSourceFile.js';
6
8
  import { SourceFile } from './SourceFile.js';
7
9
  export class EntityGenerator {
8
10
  em;
@@ -32,28 +34,40 @@ export class EntityGenerator {
32
34
  const metadata = await this.getEntityMetadata(schema, options);
33
35
  const defaultPath = `${this.config.get('baseDir')}/generated-entities`;
34
36
  const baseDir = Utils.normalizePath(options.path ?? defaultPath);
37
+ this.sources.length = 0;
38
+ const map = {
39
+ defineEntity: DefineEntitySourceFile,
40
+ entitySchema: EntitySchemaSourceFile,
41
+ decorators: SourceFile,
42
+ };
43
+ if (options.entityDefinition !== 'decorators') {
44
+ options.scalarTypeInDecorator = true;
45
+ }
35
46
  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
- }
47
+ if (meta.pivotTable && !options.outputPurePivotTables && !this.referencedEntities.has(meta)) {
48
+ continue;
43
49
  }
50
+ this.sources.push(new map[options.entityDefinition](meta, this.namingStrategy, this.platform, options));
44
51
  }
52
+ for (const nativeEnum of Object.values(schema.getNativeEnums())) {
53
+ this.sources.push(new NativeEnumSourceFile({}, this.namingStrategy, this.platform, options, nativeEnum));
54
+ }
55
+ const files = this.sources.map(file => [file.getBaseName(), file.generate()]);
45
56
  if (options.save) {
46
57
  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
+ const promises = [];
59
+ for (const [fileName, data] of files) {
60
+ promises.push((async () => {
61
+ const fileDir = dirname(fileName);
62
+ if (fileDir !== '.') {
63
+ Utils.ensureDir(join(baseDir, fileDir));
64
+ }
65
+ await writeFile(join(baseDir, fileName), data, { flush: true });
66
+ })());
67
+ }
68
+ await Promise.all(promises);
55
69
  }
56
- return this.sources.map(file => file.generate());
70
+ return files.map(([, data]) => data);
57
71
  }
58
72
  async getEntityMetadata(schema, options) {
59
73
  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
@@ -10,6 +10,7 @@ export declare class SourceFile {
10
10
  protected readonly options: GenerateOptions;
11
11
  protected readonly coreImports: Set<string>;
12
12
  protected readonly entityImports: Set<string>;
13
+ protected readonly enumImports: Map<string, string[]>;
13
14
  constructor(meta: EntityMetadata, namingStrategy: NamingStrategy, platform: Platform, options: GenerateOptions);
14
15
  generate(): string;
15
16
  protected getIndexOptions(index: EntityMetadata['indexes'][number], isAtEntityLevel?: boolean): IndexOptions<Dictionary, string>;
@@ -30,7 +31,7 @@ export declare class SourceFile {
30
31
  protected getCommonDecoratorOptions(options: Dictionary, prop: EntityProperty): void;
31
32
  private propTypeBreakdowns;
32
33
  private breakdownOfIType;
33
- protected getScalarPropertyDecoratorOptions(options: Dictionary, prop: EntityProperty): void;
34
+ protected getScalarPropertyDecoratorOptions(options: Dictionary, prop: EntityProperty, quote?: boolean): void;
34
35
  protected getManyToManyDecoratorOptions(options: Dictionary, prop: EntityProperty): void;
35
36
  protected getOneToManyDecoratorOptions(options: Dictionary, prop: EntityProperty): void;
36
37
  protected getEmbeddedPropertyDeclarationOptions(options: Dictionary, prop: EntityProperty): void;
package/SourceFile.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { Cascade, Config, DecimalType, ReferenceKind, SCALAR_TYPES, UnknownType, Utils, } from '@mikro-orm/core';
2
2
  import { parse, relative } from 'node:path';
3
+ import { inspect } from 'node:util';
3
4
  import { POSSIBLE_TYPE_IMPORTS } from './CoreImportsHelper.js';
4
5
  /**
5
6
  * @see https://github.com/tc39/proposal-regexp-unicode-property-escapes#other-examples
@@ -13,6 +14,7 @@ export class SourceFile {
13
14
  options;
14
15
  coreImports = new Set();
15
16
  entityImports = new Set();
17
+ enumImports = new Map();
16
18
  constructor(meta, namingStrategy, platform, options) {
17
19
  this.meta = meta;
18
20
  this.namingStrategy = namingStrategy;
@@ -64,7 +66,10 @@ export class SourceFile {
64
66
  classBody += definition;
65
67
  classBody += '\n';
66
68
  if (prop.enum) {
67
- enumDefinitions.push(this.getEnumClassDefinition(prop, 2));
69
+ const def = this.getEnumClassDefinition(prop, 2);
70
+ if (def.length) {
71
+ enumDefinitions.push(def);
72
+ }
68
73
  }
69
74
  if (prop.eager) {
70
75
  eagerProperties.push(prop);
@@ -98,9 +103,12 @@ export class SourceFile {
98
103
  if (typeof index.name === 'string') {
99
104
  indexOpt.name = this.quote(index.name);
100
105
  }
101
- if (index.expression) {
106
+ if (typeof index.expression === 'string') {
102
107
  indexOpt.expression = this.quote(index.expression);
103
108
  }
109
+ else if (typeof index.expression === 'function') {
110
+ indexOpt.expression = `${index.expression}`.replace(')=>`', ') => `');
111
+ }
104
112
  if (isAtEntityLevel && index.properties) {
105
113
  indexOpt.properties = Utils.asArray(index.properties).map(prop => this.quote('' + prop));
106
114
  }
@@ -111,9 +119,12 @@ export class SourceFile {
111
119
  if (typeof index.name === 'string') {
112
120
  uniqueOpt.name = this.quote(index.name);
113
121
  }
114
- if (index.expression) {
122
+ if (typeof index.expression === 'string') {
115
123
  uniqueOpt.expression = this.quote(index.expression);
116
124
  }
125
+ else if (typeof index.expression === 'function') {
126
+ uniqueOpt.expression = `${index.expression}`.replace(')=>`', ') => `');
127
+ }
117
128
  if (isAtEntityLevel && index.properties) {
118
129
  uniqueOpt.properties = Utils.asArray(index.properties).map(prop => this.quote('' + prop));
119
130
  }
@@ -139,32 +150,45 @@ export class SourceFile {
139
150
  const basePath = relative(dir, this.options.path ?? '.') || '.';
140
151
  (this.options.extraImports?.(basePath, base) ?? []).forEach(v => this.entityImports.add(v));
141
152
  const entityImports = [...this.entityImports].filter(e => e !== this.meta.className);
142
- entityImports.sort().forEach(entity => {
153
+ const importMap = new Map();
154
+ for (const entity of entityImports) {
143
155
  const file = this.options.onImport?.(entity, basePath, extension, base) ?? {
144
156
  path: `${basePath}/${this.options.fileName(entity)}${extension}`,
145
157
  name: entity,
146
158
  };
147
159
  if (file.path === '') {
148
160
  if (file.name === '') {
149
- return;
161
+ continue;
150
162
  }
151
- imports.add(`import ${this.quote(file.name)};`);
152
- return;
163
+ importMap.set(file.path, `import ${this.quote(file.name)};`);
164
+ continue;
153
165
  }
154
166
  if (file.name === '') {
155
- imports.add(`import * as ${entity} from ${this.quote(file.path)};`);
156
- return;
167
+ importMap.set(file.path, `import * as ${entity} from ${this.quote(file.path)};`);
168
+ continue;
157
169
  }
158
170
  if (file.name === 'default') {
159
- imports.add(`import ${entity} from ${this.quote(file.path)};`);
160
- return;
171
+ importMap.set(file.path, `import ${entity} from ${this.quote(file.path)};`);
172
+ continue;
161
173
  }
162
174
  if (file.name === entity) {
163
- imports.add(`import { ${entity} } from ${this.quote(file.path)};`);
164
- return;
175
+ importMap.set(file.path, `import { ${entity} } from ${this.quote(file.path)};`);
176
+ continue;
165
177
  }
166
- imports.add(`import { ${identifierRegex.test(file.name) ? file.name : this.quote(file.name)} as ${entity} } from ${this.quote(file.path)};`);
167
- });
178
+ importMap.set(file.path, `import { ${identifierRegex.test(file.name) ? file.name : this.quote(file.name)} as ${entity} } from ${this.quote(file.path)};`);
179
+ }
180
+ if (this.enumImports.size) {
181
+ for (const [name, exports] of this.enumImports.entries()) {
182
+ const file = this.options.onImport?.(name, basePath, extension, base) ?? {
183
+ path: `${basePath}/${this.options.fileName(name)}${extension}`,
184
+ name,
185
+ };
186
+ importMap.set(file.path, `import { ${exports.join(', ')} } from ${this.quote(file.path)};`);
187
+ }
188
+ }
189
+ for (const key of [...importMap.keys()].sort()) {
190
+ imports.add(importMap.get(key));
191
+ }
168
192
  return Array.from(imports.values()).join('\n');
169
193
  }
170
194
  getEntityClass(classBody) {
@@ -194,6 +218,7 @@ export class SourceFile {
194
218
  getPropertyDefinition(prop, padLeft) {
195
219
  const padding = ' '.repeat(padLeft);
196
220
  const propName = identifierRegex.test(prop.name) ? prop.name : this.quote(prop.name);
221
+ const enumMode = this.options.enumMode;
197
222
  let hiddenType = '';
198
223
  if (prop.hidden) {
199
224
  hiddenType += ` & ${this.referenceCoreImport('Hidden')}`;
@@ -211,7 +236,11 @@ export class SourceFile {
211
236
  : (() => {
212
237
  if (isScalar) {
213
238
  if (prop.enum) {
214
- return prop.runtimeType;
239
+ const method = enumMode === 'ts-enum' ? 'getEnumClassName' : 'getEnumTypeName';
240
+ if (prop.nativeEnumName) {
241
+ return this.namingStrategy[method](prop.nativeEnumName, undefined, this.meta.schema);
242
+ }
243
+ return this.namingStrategy[method](prop.fieldNames[0], this.meta.collection, this.meta.schema);
215
244
  }
216
245
  breakdownOfIType = this.breakdownOfIType(prop);
217
246
  if (typeof breakdownOfIType !== 'undefined') {
@@ -246,8 +275,14 @@ export class SourceFile {
246
275
  return `${padding}${ret};\n`;
247
276
  }
248
277
  if (prop.enum && typeof prop.default === 'string') {
278
+ if (enumMode === 'union-type') {
279
+ return `${padding}${ret} = ${this.quote(prop.default)};\n`;
280
+ }
281
+ const enumClassName = prop.nativeEnumName
282
+ ? this.namingStrategy.getEnumClassName(prop.nativeEnumName, undefined, this.meta.schema)
283
+ : this.namingStrategy.getEnumClassName(prop.fieldNames[0], this.meta.collection, this.meta.schema);
249
284
  const enumVal = this.namingStrategy.enumValueToEnumProperty(prop.default, prop.fieldNames[0], this.meta.collection, this.meta.schema);
250
- return `${padding}${ret} = ${propType}${identifierRegex.test(enumVal) ? `.${enumVal}` : `[${this.quote(enumVal)}]`};\n`;
285
+ return `${padding}${ret} = ${enumClassName}${identifierRegex.test(enumVal) ? `.${enumVal}` : `[${this.quote(enumVal)}]`};\n`;
251
286
  }
252
287
  if (prop.fieldNames?.length > 1) {
253
288
  // TODO: Composite FKs with default values require additions to default/defaultRaw that are not yet supported.
@@ -263,15 +298,51 @@ export class SourceFile {
263
298
  return `${padding}${ret} = ${prop.ref ? this.referenceCoreImport('ref') : this.referenceCoreImport('rel')}(${propType}, ${defaultVal});\n`;
264
299
  }
265
300
  getEnumClassDefinition(prop, padLeft) {
301
+ const enumMode = this.options.enumMode;
302
+ if (prop.nativeEnumName) {
303
+ const imports = [];
304
+ if (enumMode !== 'union-type') {
305
+ imports.push(prop.runtimeType);
306
+ }
307
+ if (!this.options.inferEntityType && enumMode !== 'ts-enum') {
308
+ const enumTypeName = this.namingStrategy.getEnumTypeName(prop.nativeEnumName, undefined, this.meta.schema);
309
+ imports.push(enumTypeName);
310
+ }
311
+ this.enumImports.set(prop.runtimeType, imports);
312
+ return '';
313
+ }
266
314
  const enumClassName = this.namingStrategy.getEnumClassName(prop.fieldNames[0], this.meta.collection, this.meta.schema);
315
+ const enumTypeName = this.namingStrategy.getEnumTypeName(prop.fieldNames[0], this.meta.collection, this.meta.schema);
267
316
  const padding = ' '.repeat(padLeft);
268
- let ret = `export enum ${enumClassName} {\n`;
269
317
  const enumValues = prop.items;
318
+ if (enumMode === 'union-type') {
319
+ return `export type ${enumTypeName} = ${enumValues.map(item => this.quote(item)).join(' | ')};\n`;
320
+ }
321
+ let ret = '';
322
+ if (enumMode === 'dictionary') {
323
+ ret += `export const ${enumClassName} = {\n`;
324
+ }
325
+ else {
326
+ ret += `export enum ${enumClassName} {\n`;
327
+ }
270
328
  for (const enumValue of enumValues) {
271
329
  const enumName = this.namingStrategy.enumValueToEnumProperty(enumValue, prop.fieldNames[0], this.meta.collection, this.meta.schema);
272
- ret += `${padding}${identifierRegex.test(enumName) ? enumName : this.quote(enumName)} = ${this.quote(enumValue)},\n`;
330
+ if (enumMode === 'dictionary') {
331
+ ret += `${padding}${identifierRegex.test(enumName) ? enumName : this.quote(enumName)}: ${this.quote(enumValue)},\n`;
332
+ }
333
+ else {
334
+ ret += `${padding}${identifierRegex.test(enumName) ? enumName : this.quote(enumName)} = ${this.quote(enumValue)},\n`;
335
+ }
336
+ }
337
+ if (enumMode === 'dictionary') {
338
+ ret += '} as const;\n';
339
+ }
340
+ else {
341
+ ret += '}\n';
342
+ }
343
+ if (enumMode === 'dictionary') {
344
+ ret += `\nexport type ${enumTypeName} = (typeof ${enumClassName})[keyof typeof ${enumClassName}];\n`;
273
345
  }
274
- ret += '}\n';
275
346
  return ret;
276
347
  }
277
348
  serializeObject(options, wordwrap, spaces, level = 0) {
@@ -311,7 +382,7 @@ export class SourceFile {
311
382
  options.expression = this.quote(this.meta.expression);
312
383
  }
313
384
  else if (typeof this.meta.expression === 'function') {
314
- options.expression = `${this.meta.expression}`;
385
+ options.expression = this.meta.expression.toString();
315
386
  }
316
387
  if (this.meta.repositoryClass) {
317
388
  this.entityImports.add(this.meta.repositoryClass);
@@ -372,7 +443,7 @@ export class SourceFile {
372
443
  decorator = [...indexes.sort(), decorator].map(d => padding + d).join('\n');
373
444
  const decoratorArgs = [];
374
445
  if (prop.formula) {
375
- decoratorArgs.push(`${prop.formula}`);
446
+ decoratorArgs.push(prop.formula.toString());
376
447
  }
377
448
  if (Utils.hasObjectKeys(options)) {
378
449
  decoratorArgs.push(`${this.serializeObject(options)}`);
@@ -431,7 +502,7 @@ export class SourceFile {
431
502
  if (prop.primary && (prop.enum || !(typeof prop.kind === 'undefined' || prop.kind === ReferenceKind.SCALAR))) {
432
503
  options.primary = true;
433
504
  }
434
- ['persist', 'hydrate', 'trackChanges']
505
+ ['persist', 'hydrate']
435
506
  .filter(key => prop[key] === false)
436
507
  .forEach(key => options[key] = false);
437
508
  ['onCreate', 'onUpdate', 'serializer']
@@ -530,12 +601,23 @@ export class SourceFile {
530
601
  this.propTypeBreakdowns.set(prop, r);
531
602
  return r;
532
603
  }
533
- getScalarPropertyDecoratorOptions(options, prop) {
604
+ getScalarPropertyDecoratorOptions(options, prop, quote = true) {
534
605
  if (prop.fieldNames[0] !== this.namingStrategy.propertyToColumnName(prop.name)) {
535
606
  options.fieldName = this.quote(prop.fieldNames[0]);
536
607
  }
537
608
  if (prop.enum) {
538
- options.items = `() => ${prop.runtimeType}`;
609
+ if (this.options.enumMode === 'union-type') {
610
+ options.items = `[${prop.items.map(item => this.quote(item)).join(', ')}]`;
611
+ }
612
+ else if (prop.nativeEnumName) {
613
+ const enumClassName = this.namingStrategy.getEnumClassName(prop.nativeEnumName, undefined, this.meta.schema);
614
+ options.items = `() => ${enumClassName}`;
615
+ options.nativeEnumName = this.quote(prop.nativeEnumName);
616
+ }
617
+ else {
618
+ const enumClassName = this.namingStrategy.getEnumClassName(prop.fieldNames[0], this.meta.collection, this.meta.schema);
619
+ options.items = `() => ${enumClassName}`;
620
+ }
539
621
  }
540
622
  // For enum properties, we don't need a column type
541
623
  // or the property length or other information in the decorator.
@@ -564,7 +646,7 @@ export class SourceFile {
564
646
  return ((useDefault && !hasUsableNullDefault) || (prop.optional && !prop.nullable));
565
647
  })() // also when there is the "| Opt" type modifier (because reflect-metadata can't extract the base)
566
648
  ) {
567
- options.type = this.quote(prop.type);
649
+ options.type = quote ? this.quote(prop.type) : prop.type;
568
650
  }
569
651
  }
570
652
  const columnTypeFromMappedRuntimeType = mappedRuntimeType.getColumnType({ ...prop, autoincrement: false }, this.platform);
@@ -618,6 +700,9 @@ export class SourceFile {
618
700
  getManyToManyDecoratorOptions(options, prop) {
619
701
  this.entityImports.add(prop.type);
620
702
  options.entity = `() => ${prop.type}`;
703
+ if (prop.orderBy) {
704
+ options.orderBy = inspect(prop.orderBy);
705
+ }
621
706
  if (prop.mappedBy) {
622
707
  options.mappedBy = this.quote(prop.mappedBy);
623
708
  return;
@@ -652,6 +737,9 @@ export class SourceFile {
652
737
  this.entityImports.add(prop.type);
653
738
  options.entity = `() => ${prop.type}`;
654
739
  options.mappedBy = this.quote(prop.mappedBy);
740
+ if (prop.orderBy) {
741
+ options.orderBy = inspect(prop.orderBy);
742
+ }
655
743
  }
656
744
  getEmbeddedPropertyDeclarationOptions(options, prop) {
657
745
  this.entityImports.add(prop.type);
@@ -659,7 +747,7 @@ export class SourceFile {
659
747
  if (prop.array) {
660
748
  options.array = true;
661
749
  }
662
- if (prop.object) {
750
+ if (prop.object && !prop.array) {
663
751
  options.object = true;
664
752
  }
665
753
  if (prop.prefix === false || typeof prop.prefix === 'string') {
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.5",
4
+ "version": "7.0.0-dev.50",
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",
@@ -50,12 +50,12 @@
50
50
  "access": "public"
51
51
  },
52
52
  "dependencies": {
53
- "@mikro-orm/knex": "7.0.0-dev.5"
53
+ "@mikro-orm/knex": "7.0.0-dev.50"
54
54
  },
55
55
  "devDependencies": {
56
- "@mikro-orm/core": "^6.4.7"
56
+ "@mikro-orm/core": "^6.6.0"
57
57
  },
58
58
  "peerDependencies": {
59
- "@mikro-orm/core": "7.0.0-dev.5"
59
+ "@mikro-orm/core": "7.0.0-dev.50"
60
60
  }
61
61
  }