Skip to content

Commit

Permalink
feat: embedded entities with entity schema (#8626)
Browse files Browse the repository at this point in the history
* feat: Embedded entities with entity schema

Added `embeddeds` field into EntitySchemaOptions
Added transformation to MetadataArgsStorage for embedded entities
Updated docs
Created new tests cases for EntitySchema with Embedded Entities
Changed type for field: `target` in EmbeddedMetadataArgs

CLOSES: #3632

* fixed docs

Co-authored-by: Umed Khudoiberdiev <pleerock.me@gmail.com>
  • Loading branch information
pnkp and pleerock committed Feb 17, 2022
1 parent 60a5b84 commit 7dbe956
Show file tree
Hide file tree
Showing 19 changed files with 622 additions and 197 deletions.
60 changes: 53 additions & 7 deletions docs/separating-entity-definition.md
Expand Up @@ -6,7 +6,7 @@

## Defining Schemas

You can define an entity and its columns right in the model, using decorators.
You can define an entity and its columns right in the model, using decorators.
But some people prefer to define an entity and its columns inside separate files
which are called "entity schemas" in TypeORM.

Expand Down Expand Up @@ -139,14 +139,14 @@ export const CategoryEntity = new EntitySchema<Category>({

## Extending Schemas

When using the `Decorator` approach it is easy to `extend` basic columns to an abstract class and simply extend this.
For example, your `id`, `createdAt` and `updatedAt` columns may be defined in such a `BaseEntity`. For more details, see
When using the `Decorator` approach it is easy to `extend` basic columns to an abstract class and simply extend this.
For example, your `id`, `createdAt` and `updatedAt` columns may be defined in such a `BaseEntity`. For more details, see
the documentation on [concrete table inheritance](entity-inheritance.md#concrete-table-inheritance).

When using the `EntitySchema` approach, this is not possible. However, you can use the `Spread Operator` (`...`) to your
When using the `EntitySchema` approach, this is not possible. However, you can use the `Spread Operator` (`...`) to your
advantage.

Reconsider the `Category` example from above. You may want to `extract` basic column descriptions and reuse it across
Reconsider the `Category` example from above. You may want to `extract` basic column descriptions and reuse it across
your other schemas. This may be done in the following way:

```ts
Expand Down Expand Up @@ -177,7 +177,7 @@ Now you can use the `BaseColumnSchemaPart` in your other schema models, like thi
export const CategoryEntity = new EntitySchema<Category>({
name: "category",
columns: {
...BaseColumnSchemaPart,
...BaseColumnSchemaPart,
// the CategoryEntity now has the defined id, createdAt, updatedAt columns!
// in addition, the following NEW fields are defined
name: {
Expand All @@ -187,12 +187,58 @@ export const CategoryEntity = new EntitySchema<Category>({
});
```

You can use embedded entities in schema models, like this:
```ts
export interface Name {
first: string;
last: string;
}

export const NameEntitySchema = new EntitySchema<Name>({
name: "name",
columns: {
first: {
type: "varchar"
},
last: {
type: "varchar"
}
},
});

export interface User {
id: string;
name: Name;
isActive: boolean;
}

export const UserEntitySchema = new EntitySchema<User>({
name: "user",
columns: {
id: {
primary: true,
generated: "uuid",
type: "uuid"
},
isActive: {
type: "boolean"
}
},
embeddeds: {
name: {
schema: NameEntitySchema,
prefix: "name_",
},
}
});
```

Be sure to add the `extended` columns also to the `Category` interface (e.g., via `export interface Category extend BaseEntity`).

## Using Schemas to Query / Insert Data

Of course, you can use the defined schemas in your repositories or entity manager as you would use the decorators.
Consider the previously defined `Category` example (with its `Interface` and `CategoryEntity` schema in order to get
Consider the previously defined `Category` example (with its `Interface` and `CategoryEntity` schema in order to get
some data or manipulate the database.

```ts
Expand Down
1 change: 0 additions & 1 deletion src/entity-schema/EntitySchemaColumnOptions.ts
Expand Up @@ -199,5 +199,4 @@ export interface EntitySchemaColumnOptions extends SpatialColumnOptions {
* this column when reading or writing to the database.
*/
transformer?: ValueTransformer|ValueTransformer[];

}
21 changes: 21 additions & 0 deletions src/entity-schema/EntitySchemaEmbeddedColumnOptions.ts
@@ -0,0 +1,21 @@
import { EntitySchema } from "./EntitySchema";

export class EntitySchemaEmbeddedColumnOptions {
/**
* Schema of embedded entity
*/
schema: EntitySchema;

/**
* Embedded column prefix.
* If set to empty string or false, then prefix is not set at all.
*/
prefix?: string | boolean;

/**
* Indicates if this embedded is in array mode.
*
* This option works only in mongodb.
*/
array?: boolean;
}
15 changes: 15 additions & 0 deletions src/entity-schema/EntitySchemaEmbeddedError.ts
@@ -0,0 +1,15 @@
import { TypeORMError } from "../error";

export class EntitySchemaEmbeddedError extends TypeORMError {
static createEntitySchemaIsRequiredException(field: string): EntitySchemaEmbeddedError {
return new EntitySchemaEmbeddedError(`EntitySchema is required for ${field} embedded field`);
}

static createTargetIsRequired(field: string): EntitySchemaEmbeddedError {
return new EntitySchemaEmbeddedError(`Target field is required for ${field} embedded EntitySchema`);
}

constructor(message: string) {
super(message);
}
}
13 changes: 10 additions & 3 deletions src/entity-schema/EntitySchemaOptions.ts
@@ -1,4 +1,4 @@
import {Connection, SelectQueryBuilder} from "..";
import {Connection, EntitySchemaEmbeddedColumnOptions, SelectQueryBuilder} from "..";
import {EntitySchemaIndexOptions} from "./EntitySchemaIndexOptions";
import {EntitySchemaColumnOptions} from "./EntitySchemaColumnOptions";
import {EntitySchemaRelationOptions} from "./EntitySchemaRelationOptions";
Expand Down Expand Up @@ -87,6 +87,13 @@ export class EntitySchemaOptions<T> {
*/
exclusions?: EntitySchemaExclusionOptions[];

/**
* Embedded Entities options
*/
embeddeds?: {
[P in keyof Partial<T>]: EntitySchemaEmbeddedColumnOptions;
};

/**
* Indicates if schema synchronization is enabled or disabled for this entity.
* If it will be set to false then schema sync will and migrations ignore this entity.
Expand All @@ -96,8 +103,8 @@ export class EntitySchemaOptions<T> {

/**
* If set to 'true' this option disables Sqlite's default behaviour of secretly creating
* an integer primary key column named 'rowid' on table creation.
* @see https://www.sqlite.org/withoutrowid.html.
* an integer primary key column named 'rowid' on table creation.
* @see https://www.sqlite.org/withoutrowid.html.
*/
withoutRowid?: boolean;

Expand Down

0 comments on commit 7dbe956

Please sign in to comment.