Skip to content

Commit

Permalink
feat(core): simplify entity definition and rework typings of FilterQuery
Browse files Browse the repository at this point in the history
Now it is no longer needed to merge entities with IEntity interface, that was polluting entity's interface with internal methods.
New interfaces IdEntity<T>, UuidEntity<T> and MongoEntity<T> are introduced, that should be implemented by entities. They
are not adding any new properties or methods, keeping the entity's interface clean.

This also introduces new strictly typed FilterQuery<T> implementation based on information provided by those new interfaces.

BREAKING CHANGES:
IEntity has been renamed to AnyEntity and it no longer has public methods like toJSON(), toObject() or init(). One can use wrap() method provided by ORM that
will enhance property type when needed with those methods (`await wrap(book.author).init()`). To keep all methods available on the
entity, you can still use interface merging with WrappedEntity that both extends AnyEntity and defines all those methods.
FilterQuery now does not allow using smart query operators. You can either cast your condition as any or use object syntax instead
(instead of `{ 'age:gte': 18 }` use `{ age: { $gte: 18 } }`).

Closes #124, #171
  • Loading branch information
B4nan committed Oct 10, 2019
1 parent 2d37128 commit 1aa706d
Show file tree
Hide file tree
Showing 111 changed files with 1,472 additions and 1,103 deletions.
12 changes: 6 additions & 6 deletions docs/custom-driver.md
Expand Up @@ -98,12 +98,12 @@ export class MyCustomSchemaHelper extends DatabaseDriver {
protected readonly platform = new MyCustomPlatform;

// and implement abstract methods
find<T extends IEntity>(entityName: string, where: FilterQuery<T>, populate?: string[], orderBy?: Record<string, QueryOrder>, limit?: number, offset?: number): Promise<T[]>;
findOne<T extends IEntity>(entityName: string, where: FilterQuery<T> | string, populate: string[]): Promise<T | null>;
nativeInsert<T extends IEntityType<T>>(entityName: string, data: EntityData<T>): Promise<QueryResult>;
nativeUpdate<T extends IEntity>(entityName: string, where: FilterQuery<IEntity> | IPrimaryKey, data: EntityData<T>): Promise<QueryResult>;
nativeDelete<T extends IEntity>(entityName: string, where: FilterQuery<IEntity> | IPrimaryKey): Promise<QueryResult>;
count<T extends IEntity>(entityName: string, where: FilterQuery<T>): Promise<number>;
find<T extends AnyEntity>(entityName: string, where: FilterQuery<T>, populate?: string[], orderBy?: Record<string, QueryOrder>, limit?: number, offset?: number): Promise<T[]>;
findOne<T extends AnyEntity>(entityName: string, where: FilterQuery<T> | string, populate: string[]): Promise<T | null>;
nativeInsert<T extends AnyEntityType<T>>(entityName: string, data: EntityData<T>): Promise<QueryResult>;
nativeUpdate<T extends AnyEntity>(entityName: string, where: FilterQuery<T> | IPrimaryKey, data: EntityData<T>): Promise<QueryResult>;
nativeDelete<T extends AnyEntity>(entityName: string, where: FilterQuery<T> | IPrimaryKey): Promise<QueryResult>;
count<T extends AnyEntity>(entityName: string, where: FilterQuery<T>): Promise<number>;

}
```
8 changes: 4 additions & 4 deletions docs/defining-entities.md
Expand Up @@ -42,13 +42,13 @@ export class Book {

}

export interface Book extends IEntity<string> { }
export interface Book extends AnyEntity<string> { }
```

You will need to extend Book's interface with `IEntity`. The interface represents internal
You will need to extend Book's interface with `AnyEntity`. The interface represents internal
methods added to your entity's prototype via `@Entity` decorator.

> `IEntity` is generic interface, its type parameter depends on data type of normalized primary
> `AnyEntity` is generic interface, its type parameter depends on data type of normalized primary
> key produced by used driver. SQL drivers usually use `number` and Mongo driver uses `string`.
> This type default to union type `number | string`. Keep in mind that you have to worry about
> this only when you define your primary key as `_id` instead of `id`.
Expand Down Expand Up @@ -107,7 +107,7 @@ export class Author {

}

export interface Author extends IEntity { }
export interface Author extends AnyEntity { }
```

More information about modelling relationships can be found on [modelling relationships page](relationships.md).
Expand Down
2 changes: 1 addition & 1 deletion docs/entity-constructors.md
Expand Up @@ -36,7 +36,7 @@ export class Book {

}

export interface Book extends IEntity { }
export interface Book extends AnyEntity { }
```

[&larr; Back to table of contents](index.md#table-of-contents)
8 changes: 4 additions & 4 deletions docs/entity-helper.md
Expand Up @@ -3,11 +3,11 @@

# EntityHelper and Decorated Entities

## Updating Entity Values with IEntity.assign()
## Updating Entity Values with Entity.assign()

When you want to update entity based on user input, you will usually have just plain
string ids of entity relations as user input. Normally you would need to use
`EntityManager.getReference()` to create references from each id first, and then
`em.getReference()` to create references from each id first, and then
use those references to update entity relations:

```typescript
Expand All @@ -16,7 +16,7 @@ const book = new Book('Book', jon);
book.author = orm.em.getReference<Author>(Author, '...id...');
```

Same result can be easily achieved with `IEntity.assign()`:
Same result can be easily achieved with `Entity.assign()`:

```typescript
book.assign({
Expand All @@ -28,7 +28,7 @@ console.log(book.author); // instance of Author with id: '...id...'
console.log(book.author.id); // '...id...'
```

By default, `IEntity.assign(data)` behaves same way as `Object.assign(entity, data)`,
By default, `Entity.assign(data)` behaves same way as `Object.assign(entity, data)`,
e.g. it does not merge things recursively. To enable deep merging of object properties,
use second parameter to enable `mergeObjects` flag:

Expand Down
36 changes: 18 additions & 18 deletions docs/entity-manager.md
Expand Up @@ -205,7 +205,7 @@ try {

## Type of Fetched Entities

Both `EntityManager.find` and `EntityManager.findOne()` methods have generic return types.
Both `em.find` and `em.findOne()` methods have generic return types.
All of following examples are equal and will let typescript correctly infer the entity type:

```typescript
Expand All @@ -229,12 +229,12 @@ or [`tests/EntityManager.mysql.test.ts`](https://github.com/mikro-orm/mikro-orm/

## EntityManager API

#### `getRepository<T extends IEntity>(entityName: string | EntityClass<T>): EntityRepository<T>`
#### `getRepository<T extends AnyEntity>(entityName: string | EntityClass<T>): EntityRepository<T>`

Returns `EntityRepository` for given entity, respects `customRepository` option of `@Entity`
and `entityRepository` option of `MikroORM.init()`.

#### `find<T extends IEntity>(entityName: string | EntityClass<T>, where: FilterQuery<T>, options?: FindOptions): Promise<T[]>`
#### `find<T extends AnyEntity>(entityName: string | EntityClass<T>, where: FilterQuery<T>, options?: FindOptions): Promise<T[]>`

Returns array of entities found for given condition. You can specify `FindOptions` to request
population of referenced entities or control the pagination:
Expand All @@ -250,49 +250,49 @@ export interface FindOptions {

---

#### `find<T extends IEntity>(entityName: string | EntityClass<T>, where: FilterQuery<T>, populate?: string[], orderBy?: { [k: string]: QueryOrder }, limit?: number, offset?: number): Promise<T[]>`
#### `find<T extends AnyEntity>(entityName: string | EntityClass<T>, where: FilterQuery<T>, populate?: string[], orderBy?: { [k: string]: QueryOrder }, limit?: number, offset?: number): Promise<T[]>`

Same as previous `find` method, just with dedicated parameters for `populate`, `orderBy`, `limit`
and `offset`.

---

#### `findAndCount<T extends IEntity>(entityName: string | EntityClass<T>, where: FilterQuery<T>, populate?: string[], orderBy?: { [k: string]: QueryOrder }, limit?: number, offset?: number): Promise<[T[], number]>`
#### `findAndCount<T extends AnyEntity>(entityName: string | EntityClass<T>, where: FilterQuery<T>, populate?: string[], orderBy?: { [k: string]: QueryOrder }, limit?: number, offset?: number): Promise<[T[], number]>`

Combination of `find` and `count` methods.

---

#### `findOne<T extends IEntity>(entityName: string | EntityClass<T>, where: FilterQuery<T> | IPrimaryKey, populate?: string[]): Promise<T | null>`
#### `findOne<T extends AnyEntity>(entityName: string | EntityClass<T>, where: FilterQuery<T> | IPrimaryKey, populate?: string[]): Promise<T | null>`

Finds an entity by given `where` condition. You can use primary key as `where` value, then
if the entity is already managed, no database call will be made.

---

#### `findOneOrFail<T extends IEntity>(entityName: string | EntityClass<T>, where: FilterQuery<T> | IPrimaryKey, populate?: string[]): Promise<T>`
#### `findOneOrFail<T extends AnyEntity>(entityName: string | EntityClass<T>, where: FilterQuery<T> | IPrimaryKey, populate?: string[]): Promise<T>`

Just like `findOne`, but throws when entity not found, so it always resolves to given entity.
You can customize the error either globally via `findOneOrFailHandler` option, or locally via
`failHandler` option in `findOneOrFail` call.

---

#### `merge<T extends IEntity>(entityName: string | EntityClass<T>, data: EntityData<T>): T`
#### `merge<T extends AnyEntity>(entityName: string | EntityClass<T>, data: EntityData<T>): T`

Adds given entity to current Identity Map. After merging, entity becomes managed.
This is useful when you want to work with cached entities.

---

#### `map<T extends IEntity>(entityName: string | EntityClass<T>, data: EntityData<T>): T`
#### `map<T extends AnyEntity>(entityName: string | EntityClass<T>, data: EntityData<T>): T`

Maps raw DB result to entity, adding it to current Identity Map. Equivalent to
`IDatabaseDriver.mapResult()` followed by `EntityManager.merge()`.
`IDatabaseDriver.mapResult()` followed by `em.merge()`.

---

#### `getReference<T extends IEntity>(entityName: string | EntityClass<T>, id: string): T`
#### `getReference<T extends AnyEntity>(entityName: string | EntityClass<T>, id: string): T`

Gets a reference to the entity identified by the given type and identifier without actually
loading it, if the entity is not yet loaded.
Expand All @@ -305,7 +305,7 @@ Gets count of entities matching the `where` condition.

---

#### `persist(entity: IEntity | IEntity[], flush?: boolean): void | Promise<void>`
#### `persist(entity: AnyEntity | AnyEntity[], flush?: boolean): void | Promise<void>`

Tells the EntityManager to make an instance managed and persistent. The entity will be
entered into the database at or before transaction commit or as a result of the flush
Expand All @@ -314,13 +314,13 @@ configuration option.

---

#### `persistAndFlush(entity: IEntity | IEntity[]): Promise<void>`
#### `persistAndFlush(entity: AnyEntity | AnyEntity[]): Promise<void>`

Shortcut for `persist` & `flush`.

---

#### `persistLater(entity: IEntity | IEntity[]): void`
#### `persistLater(entity: AnyEntity | AnyEntity[]): void`

Shortcut for just `persist`, without flushing.

Expand All @@ -332,7 +332,7 @@ Flushes all changes to objects that have been queued up to now to the database.

---

#### `remove(entityName: string | EntityClass<T>, where: IEntity | FilterQuery<T> | IPrimaryKey, flush?: boolean): Promise<number>`
#### `remove(entityName: string | EntityClass<T>, where: AnyEntity | FilterQuery<T> | IPrimaryKey, flush?: boolean): Promise<number>`

When provided entity instance as `where` value, then it calls `removeEntity(entity, flush)`,
otherwise it fires delete query with given `where` condition.
Expand All @@ -341,7 +341,7 @@ This method fires `beforeDelete` and `afterDelete` hooks only if you provide ent

---

#### `removeEntity(entity: IEntity, flush?: boolean): Promise<number>`
#### `removeEntity(entity: AnyEntity, flush?: boolean): Promise<number>`

Removes an entity instance. A removed entity will be removed from the database at or before
transaction commit or as a result of the flush operation. You can control immediate flushing
Expand All @@ -351,13 +351,13 @@ This method fires `beforeDelete` and `afterDelete` hooks.

---

#### `removeAndFlush(entity: IEntity): Promise<void>`
#### `removeAndFlush(entity: AnyEntity): Promise<void>`

Shortcut for `removeEntity` & `flush`.

---

#### `removeLater(entity: IEntity): void`
#### `removeLater(entity: AnyEntity): void`

Shortcut for `removeEntity` without flushing.

Expand Down
2 changes: 1 addition & 1 deletion docs/entity-references.md
Expand Up @@ -147,7 +147,7 @@ console.log(book.author.id); // ok, returns string PK
console.log(book.author._id); // ok, returns ObjectId PK
```

> As opposed to `IEntity.init()` which always refreshes the entity, `Reference.load()`
> As opposed to `Entity.init()` which always refreshes the entity, `Reference.load()`
> method will query the database only if the entity is not already loaded in Identity Map.
[&larr; Back to table of contents](index.md#table-of-contents)
2 changes: 1 addition & 1 deletion docs/identity-map.md
Expand Up @@ -15,7 +15,7 @@ const authors = await authorRepository.findAll(['books']);
console.log(jon === authors[0]); // true
```

If you want to clear this identity map cache, you can do so via `EntityManager.clear()` method:
If you want to clear this identity map cache, you can do so via `em.clear()` method:

```typescript
orm.em.clear();
Expand Down
2 changes: 1 addition & 1 deletion docs/index.md
Expand Up @@ -39,7 +39,7 @@ compatible with Vanilla JavaScript.
- [Smart Query Conditions](query-conditions.md)
- [Using `QueryBuilder`](query-builder.md)
- [Serializing](serializing.md)
- [Updating Entity Values with `IEntity.assign()`](entity-helper.md)
- [Updating Entity Values with `Entity.assign()`](entity-helper.md)
- [Property Validation](property-validation.md)
- [Lifecycle Hooks](lifecycle-hooks.md)
- [Naming Strategy](naming-strategy.md)
Expand Down
2 changes: 1 addition & 1 deletion docs/query-conditions.md
Expand Up @@ -67,7 +67,7 @@ will be converted automatically:
const res = await orm.em.find(Author, { favouriteBook: [1, 2, 7] });
```

For primary key lookup, you can provide the array directly to `EntityManager.find()`:
For primary key lookup, you can provide the array directly to `em.find()`:

```typescript
const res = await orm.em.find(Author, [1, 2, 7]);
Expand Down
14 changes: 7 additions & 7 deletions docs/repositories.md
Expand Up @@ -54,7 +54,7 @@ export class Author {
Note that we need to pass that repository reference inside a callback so we will not run
into circular dependency issues when using entity references inside that repository.

Now you can access your custom repository via `EntityManager.getRepository()` method.
Now you can access your custom repository via `em.getRepository()` method.

> You can also register custom base repository (for all entities where you do not specify
`customRepository`) globally, via `MikroORM.init({ entityRepository: CustomBaseRepository })`
Expand Down Expand Up @@ -142,7 +142,7 @@ Gets count of entities matching the `where` condition.

---

#### `persist(entity: IEntity | IEntity[], flush?: boolean): Promise<void>`
#### `persist(entity: AnyEntity | AnyEntity[], flush?: boolean): Promise<void>`

Tells the EntityManager to make an instance managed and persistent. The entity will be
entered into the database at or before transaction commit or as a result of the flush
Expand All @@ -151,13 +151,13 @@ configuration option.

---

#### `persistAndFlush(entity: IEntity | IEntity[]): Promise<void>`
#### `persistAndFlush(entity: AnyEntity | AnyEntity[]): Promise<void>`

Shortcut for `persist` & `flush`.

---

#### `persistLater(entity: IEntity | IEntity[]): void`
#### `persistLater(entity: AnyEntity | AnyEntity[]): void`

Shortcut for just `persist`, without flushing.

Expand All @@ -169,7 +169,7 @@ Flushes all changes to objects that have been queued up to now to the database.

---

#### `remove(where: IEntity | FilterQuery<T>, flush?: boolean): Promise<number>`
#### `remove(where: AnyEntity | FilterQuery<T>, flush?: boolean): Promise<number>`

When provided entity instance as `where` value, then it calls `removeEntity(entity, flush)`,
otherwise it fires delete query with given `where` condition.
Expand All @@ -178,15 +178,15 @@ This method fires `beforeDelete` and `afterDelete` hooks only if you provide ent

---

#### `removeAndFlush(entity: IEntity): Promise<void>`
#### `removeAndFlush(entity: AnyEntity): Promise<void>`

Shortcut for `removeEntity` & `flush`.

This method fires `beforeDelete` and `afterDelete` hooks.

---

#### `removeLater(entity: IEntity): void`
#### `removeLater(entity: AnyEntity): void`

Shortcut for `removeEntity` without flushing.

Expand Down
8 changes: 4 additions & 4 deletions docs/serializing.md
Expand Up @@ -6,8 +6,8 @@
By default, all entities are monkey patched with `toObject()` and `toJSON` methods:

```typescript
export interface IEntity<K = number | string> {
toObject(parent?: IEntity, isCollection?: boolean): Record<string, any>;
export interface AnyEntity<K = number | string> {
toObject(parent?: AnyEntity, isCollection?: boolean): Record<string, any>;
toJSON(...args: any[]): Record<string, any>;
// ...
}
Expand Down Expand Up @@ -62,8 +62,8 @@ console.log(book.toJSON().hiddenField); // undefined

The opposite situation where you want to define a property that lives only in memory (is
not persisted into database) can be solved by defining your property as `persist: false`.
Such property can be assigned via one of `IEntity.assign()`, `EntityManager.create()` and
`EntityManager.merge()`. It will be also part of serialized result.
Such property can be assigned via one of `Entity.assign()`, `em.create()` and
`em.merge()`. It will be also part of serialized result.

This can be handle when dealing with additional values selected via `QueryBuilder` or
MongoDB's aggregations.
Expand Down
8 changes: 4 additions & 4 deletions docs/usage-with-mongo.md
Expand Up @@ -62,9 +62,9 @@ boilerplate code. In this case, you can use one of `nativeInsert/nativeUpdate/na
methods:

```typescript
EntityManager.nativeInsert<T extends IEntity>(entityName: string, data: any): Promise<IPrimaryKey>;
EntityManager.nativeUpdate<T extends IEntity>(entityName: string, where: FilterQuery<T>, data: any): Promise<number>;
EntityManager.nativeDelete<T extends IEntity>(entityName: string, where: FilterQuery<T> | any): Promise<number>;
em.nativeInsert<T extends AnyEntity>(entityName: string, data: any): Promise<IPrimaryKey>;
em.nativeUpdate<T extends AnyEntity>(entityName: string, where: FilterQuery<T>, data: any): Promise<number>;
em.nativeDelete<T extends AnyEntity>(entityName: string, where: FilterQuery<T> | any): Promise<number>;
```
Those methods execute native driver methods like Mongo's `insertOne/updateMany/deleteMany` collection methods respectively.
Expand All @@ -82,7 +82,7 @@ EntityRepository.nativeDelete(where: FilterQuery<T> | any): Promise<number>;
There is also shortcut for calling `aggregate` method:
```typescript
EntityManager.aggregate(entityName: string, pipeline: any[]): Promise<any[]>;
em.aggregate(entityName: string, pipeline: any[]): Promise<any[]>;
EntityRepository.aggregate(pipeline: any[]): Promise<any[]>;
```
Expand Down

0 comments on commit 1aa706d

Please sign in to comment.