@mikro-orm/entity-generator 7.0.0-dev.9 → 7.0.0-dev.91

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
@@ -1,19 +1,20 @@
1
- import { Cascade, Config, DecimalType, ReferenceKind, SCALAR_TYPES, UnknownType, Utils, } from '@mikro-orm/core';
1
+ import { Cascade, Config, DecimalType, ReferenceKind, SCALAR_TYPES, UnknownType, Utils, inspect, } from '@mikro-orm/core';
2
2
  import { parse, relative } from 'node:path';
3
- import { inspect } from 'node:util';
4
3
  import { POSSIBLE_TYPE_IMPORTS } from './CoreImportsHelper.js';
5
4
  /**
6
5
  * @see https://github.com/tc39/proposal-regexp-unicode-property-escapes#other-examples
7
6
  */
8
7
  export const identifierRegex = /^(?:[$_\p{ID_Start}])(?:[$\u200C\u200D\p{ID_Continue}])*$/u;
9
- const primitivesAndLibs = [...SCALAR_TYPES, 'bigint', 'Uint8Array', 'unknown', 'object', 'any'];
8
+ const primitivesAndLibs = [...SCALAR_TYPES, 'unknown', 'object', 'any'];
10
9
  export class SourceFile {
11
10
  meta;
12
11
  namingStrategy;
13
12
  platform;
14
13
  options;
15
14
  coreImports = new Set();
15
+ decoratorImports = new Set();
16
16
  entityImports = new Set();
17
+ enumImports = new Map();
17
18
  constructor(meta, namingStrategy, platform, options) {
18
19
  this.meta = meta;
19
20
  this.namingStrategy = namingStrategy;
@@ -25,24 +26,24 @@ export class SourceFile {
25
26
  if (this.meta.embeddable || this.meta.collection) {
26
27
  if (this.meta.embeddable) {
27
28
  const options = this.getEmbeddableDeclOptions();
28
- ret += `@${this.referenceCoreImport('Embeddable')}(${Utils.hasObjectKeys(options) ? this.serializeObject(options) : ''})\n`;
29
+ ret += `@${this.referenceDecoratorImport('Embeddable')}(${Utils.hasObjectKeys(options) ? this.serializeObject(options) : ''})\n`;
29
30
  }
30
31
  else {
31
32
  const options = this.getEntityDeclOptions();
32
- ret += `@${this.referenceCoreImport('Entity')}(${Utils.hasObjectKeys(options) ? this.serializeObject(options) : ''})\n`;
33
+ ret += `@${this.referenceDecoratorImport('Entity')}(${Utils.hasObjectKeys(options) ? this.serializeObject(options) : ''})\n`;
33
34
  }
34
35
  }
35
36
  for (const index of this.meta.indexes) {
36
37
  if (index.properties?.length === 1 && typeof this.meta.properties[index.properties[0]] !== 'undefined') {
37
38
  continue;
38
39
  }
39
- ret += `@${this.referenceCoreImport('Index')}(${this.serializeObject(this.getIndexOptions(index))})\n`;
40
+ ret += `@${this.referenceDecoratorImport('Index')}(${this.serializeObject(this.getIndexOptions(index))})\n`;
40
41
  }
41
42
  for (const index of this.meta.uniques) {
42
43
  if (index.properties?.length === 1 && typeof this.meta.properties[index.properties[0]] !== 'undefined') {
43
44
  continue;
44
45
  }
45
- ret += `@${this.referenceCoreImport('Unique')}(${this.serializeObject(this.getUniqueOptions(index))})\n`;
46
+ ret += `@${this.referenceDecoratorImport('Unique')}(${this.serializeObject(this.getUniqueOptions(index))})\n`;
46
47
  }
47
48
  let classHead = '';
48
49
  if (this.meta.className === this.options.customBaseEntityName) {
@@ -65,7 +66,10 @@ export class SourceFile {
65
66
  classBody += definition;
66
67
  classBody += '\n';
67
68
  if (prop.enum) {
68
- enumDefinitions.push(this.getEnumClassDefinition(prop, 2));
69
+ const def = this.getEnumClassDefinition(prop, 2);
70
+ if (def.length) {
71
+ enumDefinitions.push(def);
72
+ }
69
73
  }
70
74
  if (prop.eager) {
71
75
  eagerProperties.push(prop);
@@ -99,9 +103,12 @@ export class SourceFile {
99
103
  if (typeof index.name === 'string') {
100
104
  indexOpt.name = this.quote(index.name);
101
105
  }
102
- if (index.expression) {
106
+ if (typeof index.expression === 'string') {
103
107
  indexOpt.expression = this.quote(index.expression);
104
108
  }
109
+ else if (typeof index.expression === 'function') {
110
+ indexOpt.expression = `${index.expression}`.replace(')=>`', ') => `');
111
+ }
105
112
  if (isAtEntityLevel && index.properties) {
106
113
  indexOpt.properties = Utils.asArray(index.properties).map(prop => this.quote('' + prop));
107
114
  }
@@ -112,9 +119,12 @@ export class SourceFile {
112
119
  if (typeof index.name === 'string') {
113
120
  uniqueOpt.name = this.quote(index.name);
114
121
  }
115
- if (index.expression) {
122
+ if (typeof index.expression === 'string') {
116
123
  uniqueOpt.expression = this.quote(index.expression);
117
124
  }
125
+ else if (typeof index.expression === 'function') {
126
+ uniqueOpt.expression = `${index.expression}`.replace(')=>`', ') => `');
127
+ }
118
128
  if (isAtEntityLevel && index.properties) {
119
129
  uniqueOpt.properties = Utils.asArray(index.properties).map(prop => this.quote('' + prop));
120
130
  }
@@ -135,37 +145,61 @@ export class SourceFile {
135
145
  return ret;
136
146
  }).join(', '))} } from '@mikro-orm/core';`);
137
147
  }
148
+ if (this.decoratorImports.size > 0) {
149
+ const type = this.options.decorators;
150
+ imports.add(`import { ${([...this.decoratorImports].sort().map(t => {
151
+ let ret = t;
152
+ if (this.options.coreImportsPrefix) {
153
+ const resolvedIdentifier = `${this.options.coreImportsPrefix}${t}`;
154
+ ret += ` as ${resolvedIdentifier}`;
155
+ }
156
+ return ret;
157
+ }).join(', '))} } from '@mikro-orm/decorators/${type}';`);
158
+ }
138
159
  const extension = this.options.esmImport ? '.js' : '';
139
160
  const { dir, base } = parse(`${this.options.path ?? '.'}/${this.getBaseName()}`);
140
161
  const basePath = relative(dir, this.options.path ?? '.') || '.';
141
162
  (this.options.extraImports?.(basePath, base) ?? []).forEach(v => this.entityImports.add(v));
142
163
  const entityImports = [...this.entityImports].filter(e => e !== this.meta.className);
143
- entityImports.sort().forEach(entity => {
164
+ const importMap = new Map();
165
+ for (const entity of entityImports) {
144
166
  const file = this.options.onImport?.(entity, basePath, extension, base) ?? {
145
167
  path: `${basePath}/${this.options.fileName(entity)}${extension}`,
146
168
  name: entity,
147
169
  };
148
170
  if (file.path === '') {
149
171
  if (file.name === '') {
150
- return;
172
+ continue;
151
173
  }
152
- imports.add(`import ${this.quote(file.name)};`);
153
- return;
174
+ importMap.set(file.path, `import ${this.quote(file.name)};`);
175
+ continue;
154
176
  }
155
177
  if (file.name === '') {
156
- imports.add(`import * as ${entity} from ${this.quote(file.path)};`);
157
- return;
178
+ importMap.set(file.path, `import * as ${entity} from ${this.quote(file.path)};`);
179
+ continue;
158
180
  }
159
181
  if (file.name === 'default') {
160
- imports.add(`import ${entity} from ${this.quote(file.path)};`);
161
- return;
182
+ importMap.set(file.path, `import ${entity} from ${this.quote(file.path)};`);
183
+ continue;
162
184
  }
163
185
  if (file.name === entity) {
164
- imports.add(`import { ${entity} } from ${this.quote(file.path)};`);
165
- return;
186
+ importMap.set(file.path, `import { ${entity} } from ${this.quote(file.path)};`);
187
+ continue;
166
188
  }
167
- imports.add(`import { ${identifierRegex.test(file.name) ? file.name : this.quote(file.name)} as ${entity} } from ${this.quote(file.path)};`);
168
- });
189
+ importMap.set(file.path, `import { ${identifierRegex.test(file.name) ? file.name : this.quote(file.name)} as ${entity} } from ${this.quote(file.path)};`);
190
+ }
191
+ if (this.enumImports.size) {
192
+ for (const [name, exports] of this.enumImports.entries()) {
193
+ const file = this.options.onImport?.(name, basePath, extension, base) ?? {
194
+ path: `${basePath}/${this.options.fileName(name)}${extension}`,
195
+ name,
196
+ };
197
+ importMap.set(file.path, `import { ${exports.join(', ')} } from ${this.quote(file.path)};`);
198
+ }
199
+ }
200
+ for (const key of [...importMap.keys()].sort()) {
201
+ imports.add(importMap.get(key));
202
+ }
169
203
  return Array.from(imports.values()).join('\n');
170
204
  }
171
205
  getEntityClass(classBody) {
@@ -195,6 +229,7 @@ export class SourceFile {
195
229
  getPropertyDefinition(prop, padLeft) {
196
230
  const padding = ' '.repeat(padLeft);
197
231
  const propName = identifierRegex.test(prop.name) ? prop.name : this.quote(prop.name);
232
+ const enumMode = this.options.enumMode;
198
233
  let hiddenType = '';
199
234
  if (prop.hidden) {
200
235
  hiddenType += ` & ${this.referenceCoreImport('Hidden')}`;
@@ -212,7 +247,11 @@ export class SourceFile {
212
247
  : (() => {
213
248
  if (isScalar) {
214
249
  if (prop.enum) {
215
- return prop.runtimeType;
250
+ const method = enumMode === 'ts-enum' ? 'getEnumClassName' : 'getEnumTypeName';
251
+ if (prop.nativeEnumName) {
252
+ return this.namingStrategy[method](prop.nativeEnumName, undefined, this.meta.schema);
253
+ }
254
+ return this.namingStrategy[method](prop.fieldNames[0], this.meta.collection, this.meta.schema);
216
255
  }
217
256
  breakdownOfIType = this.breakdownOfIType(prop);
218
257
  if (typeof breakdownOfIType !== 'undefined') {
@@ -247,8 +286,14 @@ export class SourceFile {
247
286
  return `${padding}${ret};\n`;
248
287
  }
249
288
  if (prop.enum && typeof prop.default === 'string') {
289
+ if (enumMode === 'union-type') {
290
+ return `${padding}${ret} = ${this.quote(prop.default)};\n`;
291
+ }
292
+ const enumClassName = prop.nativeEnumName
293
+ ? this.namingStrategy.getEnumClassName(prop.nativeEnumName, undefined, this.meta.schema)
294
+ : this.namingStrategy.getEnumClassName(prop.fieldNames[0], this.meta.collection, this.meta.schema);
250
295
  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`;
296
+ return `${padding}${ret} = ${enumClassName}${identifierRegex.test(enumVal) ? `.${enumVal}` : `[${this.quote(enumVal)}]`};\n`;
252
297
  }
253
298
  if (prop.fieldNames?.length > 1) {
254
299
  // TODO: Composite FKs with default values require additions to default/defaultRaw that are not yet supported.
@@ -264,15 +309,51 @@ export class SourceFile {
264
309
  return `${padding}${ret} = ${prop.ref ? this.referenceCoreImport('ref') : this.referenceCoreImport('rel')}(${propType}, ${defaultVal});\n`;
265
310
  }
266
311
  getEnumClassDefinition(prop, padLeft) {
312
+ const enumMode = this.options.enumMode;
313
+ if (prop.nativeEnumName) {
314
+ const imports = [];
315
+ if (enumMode !== 'union-type') {
316
+ imports.push(prop.runtimeType);
317
+ }
318
+ if (!this.options.inferEntityType && enumMode !== 'ts-enum') {
319
+ const enumTypeName = this.namingStrategy.getEnumTypeName(prop.nativeEnumName, undefined, this.meta.schema);
320
+ imports.push(enumTypeName);
321
+ }
322
+ this.enumImports.set(prop.runtimeType, imports);
323
+ return '';
324
+ }
267
325
  const enumClassName = this.namingStrategy.getEnumClassName(prop.fieldNames[0], this.meta.collection, this.meta.schema);
326
+ const enumTypeName = this.namingStrategy.getEnumTypeName(prop.fieldNames[0], this.meta.collection, this.meta.schema);
268
327
  const padding = ' '.repeat(padLeft);
269
- let ret = `export enum ${enumClassName} {\n`;
270
328
  const enumValues = prop.items;
329
+ if (enumMode === 'union-type') {
330
+ return `export type ${enumTypeName} = ${enumValues.map(item => this.quote(item)).join(' | ')};\n`;
331
+ }
332
+ let ret = '';
333
+ if (enumMode === 'dictionary') {
334
+ ret += `export const ${enumClassName} = {\n`;
335
+ }
336
+ else {
337
+ ret += `export enum ${enumClassName} {\n`;
338
+ }
271
339
  for (const enumValue of enumValues) {
272
340
  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`;
341
+ if (enumMode === 'dictionary') {
342
+ ret += `${padding}${identifierRegex.test(enumName) ? enumName : this.quote(enumName)}: ${this.quote(enumValue)},\n`;
343
+ }
344
+ else {
345
+ ret += `${padding}${identifierRegex.test(enumName) ? enumName : this.quote(enumName)} = ${this.quote(enumValue)},\n`;
346
+ }
347
+ }
348
+ if (enumMode === 'dictionary') {
349
+ ret += '} as const;\n';
350
+ }
351
+ else {
352
+ ret += '}\n';
353
+ }
354
+ if (enumMode === 'dictionary') {
355
+ ret += `\nexport type ${enumTypeName} = (typeof ${enumClassName})[keyof typeof ${enumClassName}];\n`;
274
356
  }
275
- ret += '}\n';
276
357
  return ret;
277
358
  }
278
359
  serializeObject(options, wordwrap, spaces, level = 0) {
@@ -312,7 +393,7 @@ export class SourceFile {
312
393
  options.expression = this.quote(this.meta.expression);
313
394
  }
314
395
  else if (typeof this.meta.expression === 'function') {
315
- options.expression = `${this.meta.expression}`;
396
+ options.expression = this.meta.expression.toString();
316
397
  }
317
398
  if (this.meta.repositoryClass) {
318
399
  this.entityImports.add(this.meta.repositoryClass);
@@ -352,7 +433,7 @@ export class SourceFile {
352
433
  getPropertyDecorator(prop, padLeft) {
353
434
  const padding = ' '.repeat(padLeft);
354
435
  const options = {};
355
- let decorator = `@${this.referenceCoreImport(this.getDecoratorType(prop))}`;
436
+ let decorator = `@${this.referenceDecoratorImport(this.getDecoratorType(prop))}`;
356
437
  if (prop.kind === ReferenceKind.MANY_TO_MANY) {
357
438
  this.getManyToManyDecoratorOptions(options, prop);
358
439
  }
@@ -373,7 +454,7 @@ export class SourceFile {
373
454
  decorator = [...indexes.sort(), decorator].map(d => padding + d).join('\n');
374
455
  const decoratorArgs = [];
375
456
  if (prop.formula) {
376
- decoratorArgs.push(`${prop.formula}`);
457
+ decoratorArgs.push(prop.formula.toString());
377
458
  }
378
459
  if (Utils.hasObjectKeys(options)) {
379
460
  decoratorArgs.push(`${this.serializeObject(options)}`);
@@ -402,26 +483,26 @@ export class SourceFile {
402
483
  let propIndexIsNonTrivialIndex = false;
403
484
  const nonTrivialIndexes = this.meta.indexes.filter(i => i.properties?.length === 1 && i.properties[0] === prop.name);
404
485
  for (const i of nonTrivialIndexes) {
405
- ret.push(`@${this.referenceCoreImport('Index')}(${this.serializeObject(this.getIndexOptions(i, false))})`);
486
+ ret.push(`@${this.referenceDecoratorImport('Index')}(${this.serializeObject(this.getIndexOptions(i, false))})`);
406
487
  if (prop.index === i.name) {
407
488
  propIndexIsNonTrivialIndex = true;
408
489
  delete options.index;
409
490
  }
410
491
  }
411
492
  if (prop.index && !options.index && !propIndexIsNonTrivialIndex) {
412
- ret.push(`@${this.referenceCoreImport('Index')}(${typeof prop.index === 'string' ? `{ name: ${this.quote(prop.index)} }` : ''})`);
493
+ ret.push(`@${this.referenceDecoratorImport('Index')}(${typeof prop.index === 'string' ? `{ name: ${this.quote(prop.index)} }` : ''})`);
413
494
  }
414
495
  let propIndexIsNonTrivialUnique = false;
415
496
  const nonTrivialUnique = this.meta.uniques.filter(i => i.properties?.length === 1 && i.properties[0] === prop.name);
416
497
  for (const i of nonTrivialUnique) {
417
- ret.push(`@${this.referenceCoreImport('Unique')}(${this.serializeObject(this.getUniqueOptions(i, false))})`);
498
+ ret.push(`@${this.referenceDecoratorImport('Unique')}(${this.serializeObject(this.getUniqueOptions(i, false))})`);
418
499
  if (prop.unique === i.name) {
419
500
  propIndexIsNonTrivialUnique = true;
420
501
  delete options.unique;
421
502
  }
422
503
  }
423
504
  if (prop.unique && !options.unique && !propIndexIsNonTrivialUnique) {
424
- ret.push(`@${this.referenceCoreImport('Unique')}(${typeof prop.unique === 'string' ? `{ name: ${this.quote(prop.unique)} }` : ''})`);
505
+ ret.push(`@${this.referenceDecoratorImport('Unique')}(${typeof prop.unique === 'string' ? `{ name: ${this.quote(prop.unique)} }` : ''})`);
425
506
  }
426
507
  return ret;
427
508
  }
@@ -432,7 +513,7 @@ export class SourceFile {
432
513
  if (prop.primary && (prop.enum || !(typeof prop.kind === 'undefined' || prop.kind === ReferenceKind.SCALAR))) {
433
514
  options.primary = true;
434
515
  }
435
- ['persist', 'hydrate', 'trackChanges']
516
+ ['persist', 'hydrate']
436
517
  .filter(key => prop[key] === false)
437
518
  .forEach(key => options[key] = false);
438
519
  ['onCreate', 'onUpdate', 'serializer']
@@ -531,12 +612,23 @@ export class SourceFile {
531
612
  this.propTypeBreakdowns.set(prop, r);
532
613
  return r;
533
614
  }
534
- getScalarPropertyDecoratorOptions(options, prop) {
615
+ getScalarPropertyDecoratorOptions(options, prop, quote = true) {
535
616
  if (prop.fieldNames[0] !== this.namingStrategy.propertyToColumnName(prop.name)) {
536
617
  options.fieldName = this.quote(prop.fieldNames[0]);
537
618
  }
538
619
  if (prop.enum) {
539
- options.items = `() => ${prop.runtimeType}`;
620
+ if (this.options.enumMode === 'union-type') {
621
+ options.items = `[${prop.items.map(item => this.quote(item)).join(', ')}]`;
622
+ }
623
+ else if (prop.nativeEnumName) {
624
+ const enumClassName = this.namingStrategy.getEnumClassName(prop.nativeEnumName, undefined, this.meta.schema);
625
+ options.items = `() => ${enumClassName}`;
626
+ options.nativeEnumName = this.quote(prop.nativeEnumName);
627
+ }
628
+ else {
629
+ const enumClassName = this.namingStrategy.getEnumClassName(prop.fieldNames[0], this.meta.collection, this.meta.schema);
630
+ options.items = `() => ${enumClassName}`;
631
+ }
540
632
  }
541
633
  // For enum properties, we don't need a column type
542
634
  // or the property length or other information in the decorator.
@@ -565,7 +657,7 @@ export class SourceFile {
565
657
  return ((useDefault && !hasUsableNullDefault) || (prop.optional && !prop.nullable));
566
658
  })() // also when there is the "| Opt" type modifier (because reflect-metadata can't extract the base)
567
659
  ) {
568
- options.type = this.quote(prop.type);
660
+ options.type = quote ? this.quote(prop.type) : prop.type;
569
661
  }
570
662
  }
571
663
  const columnTypeFromMappedRuntimeType = mappedRuntimeType.getColumnType({ ...prop, autoincrement: false }, this.platform);
@@ -592,7 +684,7 @@ export class SourceFile {
592
684
  assign('length');
593
685
  }
594
686
  // 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 */
687
+ /* v8 ignore next */
596
688
  if (mappedColumnType instanceof DecimalType && !options.columnType) {
597
689
  assign('precision');
598
690
  assign('scale');
@@ -666,7 +758,7 @@ export class SourceFile {
666
758
  if (prop.array) {
667
759
  options.array = true;
668
760
  }
669
- if (prop.object) {
761
+ if (prop.object && !prop.array) {
670
762
  options.object = true;
671
763
  }
672
764
  if (prop.prefix === false || typeof prop.prefix === 'string') {
@@ -749,4 +841,10 @@ export class SourceFile {
749
841
  ? `${this.options.coreImportsPrefix}${identifier}`
750
842
  : identifier;
751
843
  }
844
+ referenceDecoratorImport(identifier) {
845
+ this.decoratorImports.add(identifier);
846
+ return this.options.coreImportsPrefix
847
+ ? `${this.options.coreImportsPrefix}${identifier}`
848
+ : identifier;
849
+ }
752
850
  }
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.9",
4
+ "version": "7.0.0-dev.91",
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.9"
53
+ "@mikro-orm/knex": "7.0.0-dev.91"
54
54
  },
55
55
  "devDependencies": {
56
- "@mikro-orm/core": "^6.4.13"
56
+ "@mikro-orm/core": "^6.6.2"
57
57
  },
58
58
  "peerDependencies": {
59
- "@mikro-orm/core": "7.0.0-dev.9"
59
+ "@mikro-orm/core": "7.0.0-dev.91"
60
60
  }
61
61
  }