Skip to content

Commit

Permalink
refactor: introduce Configuration object
Browse files Browse the repository at this point in the history
Instead of using options map directly, it is now wrapped in Configuration
object that also handles its validation and is responsible for extending
defaults.

Also add index files to each module/folder to fix circular dependencies.
  • Loading branch information
Martin Adamek committed Mar 10, 2019
1 parent bf23587 commit d7e7856
Show file tree
Hide file tree
Showing 68 changed files with 538 additions and 393 deletions.
47 changes: 17 additions & 30 deletions lib/EntityManager.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,24 @@
import { EntityRepository } from './entity/EntityRepository';
import { EntityFactory } from './entity/EntityFactory';
import { EntityValidator } from './entity/EntityValidator';
import { EntityAssigner } from './entity/EntityAssigner';
import { EntityLoader } from './entity/EntityLoader';
import { UnitOfWork } from './unit-of-work/UnitOfWork';
import { Utils } from './utils/Utils';
import { MikroORMOptions } from './MikroORM';
import { RequestContext } from './utils/RequestContext';
import { FilterQuery } from './drivers/DatabaseDriver';
import { IDatabaseDriver } from './drivers/IDatabaseDriver';
import { IPrimaryKey } from './decorators/PrimaryKey';
import { QueryBuilder, QueryOrder } from './query/QueryBuilder';
import { EntityClass, EntityData, IEntity, IEntityType } from './decorators/Entity';
import { MetadataStorage } from './metadata/MetadataStorage';
import { Configuration, RequestContext, Utils } from './utils';
import { EntityRepository, EntityAssigner, EntityFactory, EntityLoader, EntityValidator, ReferenceType } from './entity';
import { UnitOfWork } from './unit-of-work';
import { FilterQuery, IDatabaseDriver } from './drivers/IDatabaseDriver';
import { EntityClass, EntityData, IEntity, IEntityType, IPrimaryKey } from './decorators';
import { QueryBuilder } from './query';
import { MetadataStorage } from './metadata';
import { Connection } from './connections/Connection';
import { ReferenceType } from './entity/enums';
import { QueryOrder } from './query';

export class EntityManager {

readonly validator = new EntityValidator(this.options.strict);
readonly validator = new EntityValidator(this.config.get('strict'));

private readonly repositoryMap: Record<string, EntityRepository<IEntity>> = {};
private readonly entityLoader = new EntityLoader(this);
private readonly metadata = MetadataStorage.getMetadata();
private readonly unitOfWork = new UnitOfWork(this);
private readonly entityFactory = new EntityFactory(this.unitOfWork, this.driver, this.options);
private readonly entityFactory = new EntityFactory(this.unitOfWork, this.driver, this.config);

constructor(readonly options: MikroORMOptions,
constructor(readonly config: Configuration,
private readonly driver: IDatabaseDriver<Connection>) { }

getDriver<D extends IDatabaseDriver<Connection> = IDatabaseDriver<Connection>>(): D {
Expand All @@ -42,13 +34,8 @@ export class EntityManager {

if (!this.repositoryMap[entityName]) {
const meta = this.metadata[entityName];

if (meta.customRepository) {
const CustomRepository = meta.customRepository();
this.repositoryMap[entityName] = new CustomRepository(this, entityName);
} else {
this.repositoryMap[entityName] = new this.options.entityRepository(this, entityName);
}
const RepositoryClass = this.config.getRepositoryClass(meta.customRepository);
this.repositoryMap[entityName] = new RepositoryClass(this, entityName);
}

return this.repositoryMap[entityName] as EntityRepository<T>;
Expand Down Expand Up @@ -200,7 +187,7 @@ export class EntityManager {
return this.driver.count(entityName, where);
}

async persist(entity: IEntity | IEntity[], flush = this.options.autoFlush): Promise<void> {
async persist(entity: IEntity | IEntity[], flush = this.config.get('autoFlush')): Promise<void> {
if (flush) {
await this.persistAndFlush(entity);
} else {
Expand All @@ -226,7 +213,7 @@ export class EntityManager {
}
}

async remove<T extends IEntityType<T>>(entityName: string | EntityClass<T>, where: T | any, flush = this.options.autoFlush): Promise<number> {
async remove<T extends IEntityType<T>>(entityName: string | EntityClass<T>, where: T | any, flush = this.config.get('autoFlush')): Promise<number> {
entityName = Utils.className(entityName);

if (Utils.isEntity(where)) {
Expand All @@ -237,7 +224,7 @@ export class EntityManager {
return this.nativeDelete(entityName, where);
}

async removeEntity(entity: IEntity, flush = this.options.autoFlush): Promise<void> {
async removeEntity(entity: IEntity, flush = this.config.get('autoFlush')): Promise<void> {
if (flush) {
await this.removeAndFlush(entity);
} else {
Expand Down Expand Up @@ -286,7 +273,7 @@ export class EntityManager {
}

fork(): EntityManager {
return new EntityManager(this.options, this.driver);
return new EntityManager(this.config, this.driver);
}

getUnitOfWork(): UnitOfWork {
Expand Down
114 changes: 15 additions & 99 deletions lib/MikroORM.ts
Original file line number Diff line number Diff line change
@@ -1,54 +1,22 @@
import { EntityManager } from './EntityManager';
import { IDatabaseDriver } from './drivers/IDatabaseDriver';
import { NamingStrategy } from './naming-strategy/NamingStrategy';
import { FileCacheAdapter } from './cache/FileCacheAdapter';
import { CacheAdapter } from './cache/CacheAdapter';
import { Logger } from './utils/Logger';
import { Utils } from './utils/Utils';
import { TypeScriptMetadataProvider } from './metadata/TypeScriptMetadataProvider';
import { MetadataProvider } from './metadata/MetadataProvider';
import { EntityRepository } from './entity/EntityRepository';
import { EntityClass, IEntity } from './decorators/Entity';
import { NullCacheAdapter } from './cache/NullCacheAdapter';
import { Hydrator } from './hydration/Hydrator';
import { ObjectHydrator } from './hydration/ObjectHydrator';
import { EntityFactory } from './entity/EntityFactory';
import { MetadataDiscovery } from './metadata/MetadataDiscovery';

const defaultOptions = {
entities: [],
entitiesDirs: [],
entitiesDirsTs: [],
tsConfigPath: process.cwd() + '/tsconfig.json',
autoFlush: true,
strict: false,
logger: () => undefined,
baseDir: process.cwd(),
entityRepository: EntityRepository,
hydrator: ObjectHydrator,
debug: false,
cache: {
enabled: true,
adapter: FileCacheAdapter,
options: { cacheDir: process.cwd() + '/temp' },
},
metadataProvider: TypeScriptMetadataProvider,
};
import { MetadataDiscovery } from './metadata';
import { Configuration, Logger, Options } from './utils';

export class MikroORM {

em: EntityManager;
readonly options: MikroORMOptions;
readonly config: Configuration;
private readonly driver: IDatabaseDriver;
private readonly logger: Logger;

static async init(options: Options): Promise<MikroORM> {
const orm = new MikroORM(options);
const driver = await orm.connect();
orm.em = new EntityManager(orm.options, driver);
orm.em = new EntityManager(orm.config, driver);

try {
const storage = new MetadataDiscovery(orm.em, orm.options, orm.logger);
const storage = new MetadataDiscovery(orm.em, orm.config, orm.logger);
await storage.discover();

return orm;
Expand All @@ -58,25 +26,22 @@ export class MikroORM {
}
}

constructor(options: Options) {
this.options = Utils.merge({}, defaultOptions, options);
this.validateOptions();
this.logger = new Logger(this.options);
this.driver = this.initDriver();

if (!this.options.cache.enabled) {
this.options.cache.adapter = NullCacheAdapter;
constructor(options: Options | Configuration) {
if (options instanceof Configuration) {
this.config = options;
} else {
this.config = new Configuration(options);
}

if (!this.options.clientUrl) {
this.options.clientUrl = this.driver.getConnection().getDefaultClientUrl();
}
this.driver = this.config.getDriver();
this.logger = this.config.getLogger();
}

async connect(): Promise<IDatabaseDriver> {
await this.driver.getConnection().connect();
const clientUrl = this.options.clientUrl!.replace(/\/\/([^:]+):(\w+)@/, '//$1:*****@');
this.logger.info(`MikroORM: successfully connected to database ${this.options.dbName}${clientUrl ? ' on ' + clientUrl : ''}`);
const clientUrl = this.config.getClientUrl(true);
const dbName = this.config.get('dbName');
this.logger.info(`MikroORM: successfully connected to database ${dbName}${clientUrl ? ' on ' + clientUrl : ''}`);

return this.driver;
}
Expand All @@ -89,53 +54,4 @@ export class MikroORM {
return this.driver.getConnection().close(force);
}

private validateOptions(): void {
if (!this.options.dbName) {
throw new Error('No database specified, please fill in `dbName` option');
}

if (this.options.entities.length === 0 && this.options.entitiesDirs.length === 0) {
throw new Error('No entities found, please use `entities` or `entitiesDirs` option');
}
}

private initDriver(): IDatabaseDriver {
if (!this.options.driver) {
this.options.driver = require('./drivers/MongoDriver').MongoDriver;
}

return new this.options.driver!(this.options, this.logger);
}

}

export interface MikroORMOptions {
dbName: string;
entities: EntityClass<IEntity>[];
entitiesDirs: string[];
entitiesDirsTs: string[];
tsConfigPath: string;
autoFlush: boolean;
driver?: { new (options: MikroORMOptions, logger: Logger): IDatabaseDriver };
namingStrategy?: { new (): NamingStrategy };
hydrator: { new (factory: EntityFactory, driver: IDatabaseDriver): Hydrator };
entityRepository: { new (em: EntityManager, entityName: string | EntityClass<IEntity>): EntityRepository<IEntity> };
clientUrl?: string;
host?: string;
port?: number;
user?: string;
password?: string;
multipleStatements?: boolean; // for mysql driver
strict: boolean;
logger: (message: string) => void;
debug: boolean;
baseDir: string;
cache: {
enabled: boolean,
adapter: { new (...params: any[]): CacheAdapter },
options: Record<string, any>,
},
metadataProvider: { new (options: MikroORMOptions): MetadataProvider },
}

export type Options = Pick<MikroORMOptions, Exclude<keyof MikroORMOptions, keyof typeof defaultOptions>> | MikroORMOptions;
3 changes: 3 additions & 0 deletions lib/cache/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './CacheAdapter';
export * from './NullCacheAdapter';
export * from './FileCacheAdapter';
8 changes: 4 additions & 4 deletions lib/connections/Connection.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { MikroORMOptions } from '../MikroORM';
import { Logger } from '../utils/Logger';
import { Configuration } from '../utils';

export abstract class Connection {

constructor(protected readonly options: MikroORMOptions,
protected readonly logger: Logger) { }
protected readonly logger = this.config.getLogger();

constructor(protected readonly config: Configuration) { }

/**
* Establishes connection to database
Expand Down
12 changes: 6 additions & 6 deletions lib/connections/MongoConnection.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import {
Collection, Db, DeleteWriteOpResultObject, InsertOneWriteOpResult, MongoClient, ObjectID, UpdateWriteOpResult,
} from 'mongodb';
import { Collection, Db, DeleteWriteOpResultObject, InsertOneWriteOpResult, MongoClient, ObjectID, UpdateWriteOpResult } from 'mongodb';
import { Connection } from './Connection';
import { FilterQuery, QueryOrder, Utils } from '..';
import { Utils } from '../utils';
import { QueryOrder } from '../query';
import { FilterQuery } from '..';

export class MongoConnection extends Connection {

protected client: MongoClient;
protected db: Db;

async connect(): Promise<void> {
this.client = await MongoClient.connect(this.options.clientUrl as string, { useNewUrlParser: true });
this.db = this.client.db(this.options.dbName);
this.client = await MongoClient.connect(this.config.getClientUrl(), { useNewUrlParser: true });
this.db = this.client.db(this.config.get('dbName'));
}

async close(force?: boolean): Promise<void> {
Expand Down
18 changes: 9 additions & 9 deletions lib/connections/MySqlConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,15 @@ export class MySqlConnection extends Connection {

getConnectionOptions(): ConnectionOptions {
const ret = {} as ConnectionOptions;
const url = new URL(this.options.clientUrl!);
ret.host = this.options.host || url.hostname;
ret.port = this.options.port || +url.port;
ret.user = this.options.user || url.username;
ret.password = this.options.password || url.password;
ret.database = this.options.dbName || url.pathname.replace(/^\//, '');

if (this.options.multipleStatements) {
ret.multipleStatements = this.options.multipleStatements;
const url = new URL(this.config.getClientUrl());
ret.host = this.config.get('host', url.hostname);
ret.port = this.config.get('port', +url.port);
ret.user = this.config.get('user', url.username);
ret.password = this.config.get('password', url.password);
ret.database = this.config.get('dbName', url.pathname.replace(/^\//, ''));

if (this.config.get('multipleStatements')) {
ret.multipleStatements = this.config.get('multipleStatements');
}

return ret;
Expand Down
5 changes: 3 additions & 2 deletions lib/connections/SqliteConnection.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import * as sqlite from 'sqlite';
import { Database } from 'sqlite';
import { readFileSync } from 'fs';

import { Connection, QueryResult } from './Connection';
import { EntityData, IEntity } from '../decorators/Entity';
import { EntityData, IEntity } from '../decorators';

export class SqliteConnection extends Connection {

private connection: SqliteDatabase;

async connect(): Promise<void> {
this.connection = await sqlite.open(this.options.dbName) as SqliteDatabase;
this.connection = await sqlite.open(this.config.get('dbName')) as SqliteDatabase;
await this.connection.exec('PRAGMA foreign_keys = ON');
}

Expand Down
8 changes: 4 additions & 4 deletions lib/decorators/Entity.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Collection, EntityRepository, Utils } from '..';
import { MetadataStorage } from '../metadata/MetadataStorage';
import { MetadataStorage } from '../metadata';
import { EntityManager } from '../EntityManager';
import { IPrimaryKey } from './PrimaryKey';
import { Cascade, ReferenceType } from '../entity/enums';
import { Cascade, Collection, EntityRepository, ReferenceType } from '../entity';
import { Utils } from '../utils';

export function Entity(options: EntityOptions = {}): Function {
return function <T extends { new(...args: any[]): IEntity }>(target: T) {
Expand All @@ -19,7 +19,7 @@ export function Entity(options: EntityOptions = {}): Function {

export type EntityOptions = {
collection?: string;
customRepository?: () => { new (em: EntityManager, entityName: string): EntityRepository<IEntity> };
customRepository?: () => { new (em: EntityManager, entityName: string | EntityClass<IEntity>): EntityRepository<IEntity> };
}

export interface IEntity<K = number | string> {
Expand Down
6 changes: 3 additions & 3 deletions lib/decorators/ManyToMany.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { PropertyOptions } from './Property';
import { EntityProperty, IEntity } from './Entity';
import { MetadataStorage } from '../metadata/MetadataStorage';
import { Utils } from '..';
import { Cascade, ReferenceType } from '../entity/enums';
import { MetadataStorage } from '../metadata';
import { Utils } from '../utils';
import { Cascade, ReferenceType } from '../entity';

export function ManyToMany(options: ManyToManyOptions): Function {
return function (target: IEntity, propertyName: string) {
Expand Down
6 changes: 3 additions & 3 deletions lib/decorators/ManyToOne.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { PropertyOptions } from './Property';
import { EntityProperty, IEntity } from './Entity';
import { MetadataStorage } from '../metadata/MetadataStorage';
import { Utils } from '..';
import { Cascade, ReferenceType } from '../entity/enums';
import { MetadataStorage } from '../metadata';
import { Utils } from '../utils';
import { Cascade, ReferenceType } from '../entity';

export function ManyToOne(options: ManyToOneOptions = {}): Function {
return function (target: IEntity, propertyName: string) {
Expand Down
6 changes: 3 additions & 3 deletions lib/decorators/OneToMany.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { PropertyOptions } from './Property';
import { EntityProperty, IEntity } from './Entity';
import { MetadataStorage } from '../metadata/MetadataStorage';
import { Utils } from '..';
import { Cascade, ReferenceType } from '../entity/enums';
import { MetadataStorage } from '../metadata';
import { Utils } from '../utils';
import { Cascade, ReferenceType } from '../entity';

export function OneToMany(options: OneToManyOptions): Function {
return function (target: IEntity, propertyName: string) {
Expand Down

0 comments on commit d7e7856

Please sign in to comment.