Skip to content

Commit

Permalink
fix(core): refactor internals to reduce number of cycles
Browse files Browse the repository at this point in the history
Most of the circular dependencies as reported by `madge` are now gone,
both in the TS code as in the compiled JS. This should reduce weird
circular dependency issues in some environments, like with webpack.

Example of issues this should solve:
`TypeError: Cannot read property 'getGlobalStorage' of undefined` due
to `Utils` being undefined from inside `MetadataStorage`.

Some general remarks:
- do not import from barrel files (`index.ts`)
- move types and interfaces to separate files
- when we need some import for a type only, use interface to get around cycles
- sometimes it was needed to fallback to `any` (e.g. with `EntityManager`)

Still some cycles are there, but they should hopefully not matter (hard to
get around them without doing breaking changes).
  • Loading branch information
B4nan committed Sep 10, 2020
1 parent 9a47ad2 commit fd4e5fb
Show file tree
Hide file tree
Showing 83 changed files with 539 additions and 441 deletions.
46 changes: 46 additions & 0 deletions packages/cli/src/CLIConfigurator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import yargs, { Argv } from 'yargs';

import { ConfigurationLoader, Utils } from '@mikro-orm/core';
import { ClearCacheCommand } from './commands/ClearCacheCommand';
import { GenerateEntitiesCommand } from './commands/GenerateEntitiesCommand';
import { SchemaCommandFactory } from './commands/SchemaCommandFactory';
import { MigrationCommandFactory } from './commands/MigrationCommandFactory';
import { DebugCommand } from './commands/DebugCommand';
import { GenerateCacheCommand } from './commands/GenerateCacheCommand';
import { ImportCommand } from './commands/ImportCommand';

export class CLIConfigurator {

static async configure(): Promise<Argv> {
const settings = await ConfigurationLoader.getSettings();

if (settings.useTsNode) {
await ConfigurationLoader.registerTsNode(settings.tsConfigPath);
}

// noinspection HtmlDeprecatedTag
return yargs
.scriptName('mikro-orm')
.version(Utils.getORMVersion())
.usage('Usage: $0 <command> [options]')
.example('$0 schema:update --run', 'Runs schema synchronization')
.alias('v', 'version')
.alias('h', 'help')
.command(new ClearCacheCommand())
.command(new GenerateCacheCommand())
.command(new GenerateEntitiesCommand())
.command(new ImportCommand())
.command(SchemaCommandFactory.create('create'))
.command(SchemaCommandFactory.create('drop'))
.command(SchemaCommandFactory.create('update'))
.command(MigrationCommandFactory.create('create'))
.command(MigrationCommandFactory.create('up'))
.command(MigrationCommandFactory.create('down'))
.command(MigrationCommandFactory.create('list'))
.command(MigrationCommandFactory.create('pending'))
.command(new DebugCommand())
.recommendCommands()
.strict();
}

}
40 changes: 0 additions & 40 deletions packages/cli/src/CLIHelper.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,8 @@
import yargs, { Argv } from 'yargs';
import { pathExists } from 'fs-extra';
import CliTable3, { Table } from 'cli-table3';
import c from 'ansi-colors';

import { Configuration, ConfigurationLoader, IDatabaseDriver, MikroORM, Utils } from '@mikro-orm/core';
import { ClearCacheCommand } from './commands/ClearCacheCommand';
import { GenerateEntitiesCommand } from './commands/GenerateEntitiesCommand';
import { SchemaCommandFactory } from './commands/SchemaCommandFactory';
import { MigrationCommandFactory } from './commands/MigrationCommandFactory';
import { DebugCommand } from './commands/DebugCommand';
import { GenerateCacheCommand } from './commands/GenerateCacheCommand';
import { ImportCommand } from './commands/ImportCommand';

export class CLIHelper {

Expand All @@ -34,38 +26,6 @@ export class CLIHelper {
return MikroORM.init(options);
}

static async configure(): Promise<Argv> {
const settings = await ConfigurationLoader.getSettings();

if (settings.useTsNode) {
await ConfigurationLoader.registerTsNode(settings.tsConfigPath);
}

// noinspection HtmlDeprecatedTag
return yargs
.scriptName('mikro-orm')
.version(Utils.getORMVersion())
.usage('Usage: $0 <command> [options]')
.example('$0 schema:update --run', 'Runs schema synchronization')
.alias('v', 'version')
.alias('h', 'help')
.command(new ClearCacheCommand())
.command(new GenerateCacheCommand())
.command(new GenerateEntitiesCommand())
.command(new ImportCommand())
.command(SchemaCommandFactory.create('create'))
.command(SchemaCommandFactory.create('drop'))
.command(SchemaCommandFactory.create('update'))
.command(MigrationCommandFactory.create('create'))
.command(MigrationCommandFactory.create('up'))
.command(MigrationCommandFactory.create('down'))
.command(MigrationCommandFactory.create('list'))
.command(MigrationCommandFactory.create('pending'))
.command(new DebugCommand())
.recommendCommands()
.strict();
}

static getNodeVersion(): string {
return process.versions.node;
}
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ require('yargonaut')
.errorsStyle('red');

import yargs from 'yargs';
import { CLIHelper } from './CLIHelper';
import { CLIConfigurator } from './CLIConfigurator';

(async () => {
const args = (await CLIHelper.configure()).parse(process.argv.slice(2)) as { _: string[] };
const args = (await CLIConfigurator.configure()).parse(process.argv.slice(2)) as { _: string[] };

if (args._.length === 0) {
yargs.showHelp();
Expand Down
1 change: 1 addition & 0 deletions packages/cli/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './CLIHelper';
export * from './CLIConfigurator';
9 changes: 5 additions & 4 deletions packages/core/src/EntityManager.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { v4 as uuid } from 'uuid';
import { inspect } from 'util';

import { Configuration, OptimisticLockError, QueryHelper, RequestContext, Utils, ValidationError } from './utils';
import { AssignOptions, EntityAssigner, EntityFactory, EntityLoader, EntityRepository, EntityValidator, IdentifiedReference, LoadStrategy, Reference, ReferenceType, SCALAR_TYPES } from './entity';
import { LockMode, UnitOfWork } from './unit-of-work';
import { Configuration, QueryHelper, RequestContext, Utils } from './utils';
import { AssignOptions, EntityAssigner, EntityFactory, EntityLoader, EntityRepository, EntityValidator, IdentifiedReference, Reference } from './entity';
import { UnitOfWork } from './unit-of-work';
import { CountOptions, DeleteOptions, EntityManagerType, FindOneOptions, FindOneOrFailOptions, FindOptions, IDatabaseDriver, UpdateOptions } from './drivers';
import { AnyEntity, Dictionary, EntityData, EntityMetadata, EntityName, FilterDef, FilterQuery, Loaded, Primary, Populate, PopulateMap, PopulateOptions, New, GetRepository } from './typings';
import { QueryOrderMap } from './enums';
import { LoadStrategy, LockMode, QueryOrderMap, ReferenceType, SCALAR_TYPES } from './enums';
import { MetadataStorage } from './metadata';
import { Transaction } from './connections';
import { EventManager } from './events';
import { OptimisticLockError, ValidationError } from './errors';

/**
* The EntityManager is the central access point to ORM functionality. It is a facade to all different ORM subsystems
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/cache/FileCacheAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import globby from 'globby';
import { ensureDir, pathExists, readFile, readJSON, unlink, writeJSON } from 'fs-extra';

import { CacheAdapter } from './CacheAdapter';
import { Utils } from '../utils';
import { Utils } from '../utils/Utils';

export class FileCacheAdapter implements CacheAdapter {

Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/decorators/Embedded.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { AnyEntity, EntityProperty } from '../typings';
import { MetadataStorage, MetadataValidator } from '../metadata';
import { ReferenceType } from '../entity/enums';
import { Utils } from '../utils';
import { ReferenceType } from '../enums';

export function Embedded(options: EmbeddedOptions | (() => AnyEntity) = {}) {
return function (target: AnyEntity, propertyName: string) {
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/decorators/Enum.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { MetadataStorage } from '../metadata';
import { ReferenceType } from '../entity';
import { PropertyOptions } from '.';
import { ReferenceType } from '../enums';
import { PropertyOptions } from './Property';
import { EntityProperty, AnyEntity, Dictionary } from '../typings';

export function Enum(options: EnumOptions<AnyEntity> | (() => Dictionary) = {}) {
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/decorators/Formula.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { MetadataStorage } from '../metadata';
import { ReferenceType } from '../entity';
import { ReferenceType } from '../enums';
import { EntityProperty, AnyEntity } from '../typings';

export function Formula(formula: string | ((alias: string) => string)) {
Expand Down
3 changes: 1 addition & 2 deletions packages/core/src/decorators/ManyToMany.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { ReferenceOptions } from './Property';
import { MetadataStorage, MetadataValidator } from '../metadata';
import { Utils } from '../utils';
import { ReferenceType } from '../entity';
import { EntityName, EntityProperty, AnyEntity } from '../typings';
import { QueryOrder } from '../enums';
import { ReferenceType, QueryOrder } from '../enums';

export function ManyToMany<T, O>(
entity?: ManyToManyOptions<T, O> | string | (() => EntityName<T>),
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/decorators/ManyToOne.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ReferenceOptions } from './Property';
import { MetadataStorage, MetadataValidator } from '../metadata';
import { Utils } from '../utils';
import { ReferenceType } from '../entity';
import { ReferenceType } from '../enums';
import { AnyEntity, EntityName, EntityProperty } from '../typings';

export function ManyToOne<T, O>(
Expand Down
3 changes: 1 addition & 2 deletions packages/core/src/decorators/OneToMany.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { ReferenceOptions } from './Property';
import { MetadataStorage, MetadataValidator } from '../metadata';
import { Utils } from '../utils';
import { ReferenceType } from '../entity';
import { QueryOrder } from '../enums';
import { ReferenceType, QueryOrder } from '../enums';
import { EntityName, EntityProperty, AnyEntity } from '../typings';

export function createOneToDecorator<T, O>(
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/decorators/OneToOne.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ReferenceType } from '../entity';
import { ReferenceType } from '../enums';
import { createOneToDecorator, OneToManyOptions } from './OneToMany';
import { EntityName } from '../typings';

Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/decorators/PrimaryKey.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { MetadataStorage, MetadataValidator } from '../metadata';
import { ReferenceType } from '../entity';
import { PropertyOptions } from '.';
import { ReferenceType } from '../enums';
import { PropertyOptions } from './Property';
import { AnyEntity, EntityProperty } from '../typings';

function createDecorator<T>(options: PrimaryKeyOptions<T> | SerializedPrimaryKeyOptions<T>, serialized: boolean) {
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/decorators/Property.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { MetadataStorage, MetadataValidator } from '../metadata';
import { Utils } from '../utils';
import { Cascade, ReferenceType, LoadStrategy } from '../entity';
import { Cascade, ReferenceType, LoadStrategy } from '../enums';
import { EntityName, EntityProperty, AnyEntity, Constructor } from '../typings';
import { Type } from '../types';

Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/decorators/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { MetadataStorage } from '../metadata';
import { EventType } from '../events';
import { EventType } from '../enums';

function hook(type: EventType) {
return function (target: any, method: string) {
Expand Down
10 changes: 6 additions & 4 deletions packages/core/src/drivers/DatabaseDriver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ import { EntityManagerType, FindOneOptions, FindOptions, IDatabaseDriver } from
import { EntityData, EntityMetadata, EntityProperty, FilterQuery, AnyEntity, Dictionary, Primary, PopulateOptions } from '../typings';
import { MetadataStorage } from '../metadata';
import { Connection, QueryResult, Transaction } from '../connections';
import { Configuration, ConnectionOptions, Utils, ValidationError } from '../utils';
import { QueryOrder, QueryOrderMap } from '../enums';
import { Configuration, ConnectionOptions, Utils } from '../utils';
import { LockMode, QueryOrder, QueryOrderMap, ReferenceType } from '../enums';
import { Platform } from '../platforms';
import { Collection, ReferenceType } from '../entity';
import { DriverException, EntityManager, LockMode } from '../index';
import { Collection } from '../entity';
import { EntityManager } from '../EntityManager';
import { ValidationError } from '../errors';
import { DriverException } from '../exceptions';

export abstract class DatabaseDriver<C extends Connection> implements IDatabaseDriver<C> {

Expand Down
7 changes: 3 additions & 4 deletions packages/core/src/drivers/IDatabaseDriver.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { EntityData, EntityMetadata, EntityProperty, AnyEntity, FilterQuery, Primary, Dictionary, QBFilterQuery, IPrimaryKey, Populate, PopulateOptions } from '../typings';
import { Connection, QueryResult, Transaction } from '../connections';
import { QueryOrderMap, QueryFlag } from '../enums';
import { LockMode, QueryOrderMap, QueryFlag, LoadStrategy } from '../enums';
import { Platform } from '../platforms';
import { MetadataStorage } from '../metadata';
import { LockMode } from '../unit-of-work';
import { Collection, LoadStrategy } from '../entity';
import { EntityManager } from '../index';
import { Collection } from '../entity';
import { EntityManager } from '../EntityManager';
import { DriverException } from '../exceptions';

export const EntityManagerType = Symbol('EntityManagerType');
Expand Down
19 changes: 14 additions & 5 deletions packages/core/src/entity/ArrayCollection.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { AnyEntity, Dictionary, EntityProperty, IPrimaryKey, Primary } from '../typings';
import { ReferenceType } from './enums';
import { Collection } from './Collection';
import { Reference } from './Reference';
import { wrap } from './wrap';
import { ReferenceType } from '../enums';

export class ArrayCollection<T extends AnyEntity<T>, O extends AnyEntity<O>> {

[k: number]: T;

protected readonly items: T[] = [];
protected initialized = true;
private _property?: EntityProperty;

constructor(readonly owner: O, items?: T[]) {
Expand All @@ -20,6 +20,7 @@ export class ArrayCollection<T extends AnyEntity<T>, O extends AnyEntity<O>> {
Object.defineProperty(this, 'items', { enumerable: false });
Object.defineProperty(this, 'owner', { enumerable: false, writable: true });
Object.defineProperty(this, '_property', { enumerable: false, writable: true });
Object.defineProperty(this, '__collection', { value: true });
}

getItems(): T[] {
Expand Down Expand Up @@ -111,6 +112,14 @@ export class ArrayCollection<T extends AnyEntity<T>, O extends AnyEntity<O>> {
return this.items.length;
}

isInitialized(fully = false): boolean {
if (fully) {
return this.initialized && this.items.every(item => item.__helper!.isInitialized());
}

return this.initialized;
}

get length(): number {
return this.count();
}
Expand Down Expand Up @@ -143,15 +152,15 @@ export class ArrayCollection<T extends AnyEntity<T>, O extends AnyEntity<O>> {
}

protected propagateToInverseSide(item: T, method: 'add' | 'remove'): void {
const collection = item[this.property.inversedBy as keyof T] as unknown as Collection<O, T>;
const collection = item[this.property.inversedBy as keyof T] as unknown as ArrayCollection<O, T>;

if (this.shouldPropagateToCollection(collection, method)) {
collection[method](this.owner);
}
}

protected propagateToOwningSide(item: T, method: 'add' | 'remove'): void {
const collection = item[this.property.mappedBy as keyof T] as unknown as Collection<O, T>;
const collection = item[this.property.mappedBy as keyof T] as unknown as ArrayCollection<O, T>;

if (this.property.reference === ReferenceType.MANY_TO_MANY && this.shouldPropagateToCollection(collection, method)) {
collection[method](this.owner);
Expand All @@ -160,7 +169,7 @@ export class ArrayCollection<T extends AnyEntity<T>, O extends AnyEntity<O>> {
}
}

protected shouldPropagateToCollection(collection: Collection<O, T>, method: 'add' | 'remove'): boolean {
protected shouldPropagateToCollection(collection: ArrayCollection<O, T>, method: 'add' | 'remove'): boolean {
if (!collection || !collection.isInitialized()) {
return false;
}
Expand Down
4 changes: 4 additions & 0 deletions packages/core/src/entity/BaseEntity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import { AssignOptions, EntityAssigner } from './EntityAssigner';

export abstract class BaseEntity<T extends AnyEntity<T>, PK extends keyof T> implements IWrappedEntity<T, PK> {

constructor() {
Object.defineProperty(this, '__baseEntity', { value: true });
}

isInitialized(): boolean {
return (this as unknown as T).__helper!.isInitialized();
}
Expand Down
23 changes: 7 additions & 16 deletions packages/core/src/entity/Collection.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { AnyEntity, Dictionary, EntityData, FilterQuery, Populate, Primary } from '../typings';
import { ArrayCollection } from './index';
import { ReferenceType } from './enums';
import { Utils, ValidationError } from '../utils';
import { QueryOrder, QueryOrderMap } from '../enums';
import { ArrayCollection } from './ArrayCollection';
import { Utils } from '../utils/Utils';
import { ValidationError } from '../errors';
import { QueryOrder, QueryOrderMap, ReferenceType } from '../enums';
import { Reference } from './Reference';

export class Collection<T extends AnyEntity<T>, O extends AnyEntity<O> = AnyEntity> extends ArrayCollection<T, O> {

private snapshot: T[] | undefined = []; // used to create a diff of the collection at commit time, undefined marks overridden values so we need to wipe when flushing
private initialized = false;
private dirty = false;
private _populated = false;
private _lazyInitialized = false;
Expand Down Expand Up @@ -129,14 +128,6 @@ export class Collection<T extends AnyEntity<T>, O extends AnyEntity<O> = AnyEnti
return super.count();
}

isInitialized(fully = false): boolean {
if (fully) {
return this.initialized && this.items.every(item => item.__helper!.isInitialized());
}

return this.initialized;
}

shouldPopulate(): boolean {
return this._populated && !this._lazyInitialized;
}
Expand Down Expand Up @@ -165,8 +156,8 @@ export class Collection<T extends AnyEntity<T>, O extends AnyEntity<O> = AnyEnti
}

if (!this.initialized && this.property.reference === ReferenceType.MANY_TO_MANY && em.getDriver().getPlatform().usesPivotTable()) {
const map = await em.getDriver().loadFromPivotTable<T, O>(this.property, [this.owner.__helper!.__primaryKeys], options.where, options.orderBy);
this.hydrate(map[this.owner.__helper!.__serializedPrimaryKey].map(item => em.merge<T>(this.property.type, item, false, true)));
const map = await em.getDriver().loadFromPivotTable(this.property, [this.owner.__helper!.__primaryKeys], options.where, options.orderBy);
this.hydrate(map[this.owner.__helper!.__serializedPrimaryKey].map((item: EntityData<T>) => em.merge(this.property.type, item, false, true)));
this._lazyInitialized = true;

return this;
Expand All @@ -185,7 +176,7 @@ export class Collection<T extends AnyEntity<T>, O extends AnyEntity<O> = AnyEnti
const order = [...this.items]; // copy order of references
const customOrder = !!options.orderBy;
orderBy = this.createOrderBy(options.orderBy);
const items = await em.find<T>(this.property.type, where, options.populate, orderBy);
const items = await em.find(this.property.type, where, options.populate, orderBy);

if (!customOrder) {
this.reorderItems(items, order);
Expand Down
Loading

0 comments on commit fd4e5fb

Please sign in to comment.