Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MikroORM v6 compatibility #6

Merged
merged 2 commits into from
Jan 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 37 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Mikro ORM Soft Delete

Generic soft delete solution for MikroORM.
The declarative soft-delete solution for MikroORM.

```
npm i mikro-orm-soft-delete
Expand All @@ -10,14 +10,30 @@ npm i mikro-orm-soft-delete

## Tutorial

### Basic
### Initialization

It is so simple to enable soft delete with this package that you only need one example to understand what's going on:
To enable soft-delete for your `MikroORM` instance, register `SoftDeleteHandler` as an [extension](https://mikro-orm.io/docs/configuration#extensions) in the initialization config:

```ts
import { SoftDeleteHandler } from "mikro-orm-soft-delete";

await MikroORM.init({
// ...
extensions: [SoftDeleteHandler],
// ...
});
```

### Basics

Put a `SoftDeletable` decorator on your entity definition to make it soft-deletable:

```ts
import { SoftDeletable } from "mikro-orm-soft-delete";

@SoftDeletable(() => User, "deletedAt", () => new Date())
@Entity()
export class User extends BaseEntity<User, "id"> {
export class User extends BaseEntity {
@PrimaryKey()
id: number;

Expand All @@ -26,25 +42,26 @@ export class User extends BaseEntity<User, "id"> {
}
```

This means that:
The above code snippet means that:

- A filter with conditions `{ deletedAt: null }` has been defined on `User` and enabled by default, so that those deleted entities will be filtered out by default. The filter can be disabled by:
- A filter with conditions `{ deletedAt: null }` is applied to `User` and enabled by default, so that those soft-deleted entities will be excluded from your queries. This filter could be disabled by:
```ts
import { SOFT_DELETABLE_FILTER } from "mikro-orm-soft-delete";
repo.find({ ... }, { filters: { [SOFT_DELETABLE_FILTER]: false } });
repo.find({ ... }, { filters: false }); // if you are sure that there are no other filters enabled
```
- When you try to delete a `User` entity, it will not be actually deleted from the database, and its `deletedAt` property will be set to a newly instantiated `Date`. You can find that `delete` statements are replaced with `update` ones with MikroORM's debug mode on.
- When an deletion command is executed on a `User` entity, its `deletedAt` field will be set to a newly instantiated `Date`. You could find all `delete` statements replaced with `update` ones under MikroORM's debugging mode:
```ts
repo.remove(user);
await repo.flush();
user.id !== undefined; // true
user.deletedAt === true; // true
user.deletedAt instanceof Date; // true
```
- `cascade: [Cascade.Remove]` and `orphanRemoval: true` still work fine with `repo.remove()`. But you must avoid removing items from collections when using `orphanRemoval` because we cannot catch the deletions caused by it.
- `cascade: [Cascade.Remove]` and `orphanRemoval: true` still work with `repo.remove()`. But you would have to avoid removing items from collections when using `orphanRemoval` as it's currently not possible to intercept deletions caused by these operations.

### Object-based API
### Config API

Aside from passing the parameters by position, there is also an object-based API that allows you to pass in an config object:
Aside from passing the parameters by positions, there is also an object-based API that accepts a config object instead:

```ts
@SoftDeletable({
Expand All @@ -54,49 +71,46 @@ Aside from passing the parameters by position, there is also an object-based API
})
```

### `valueInitial` Option
### Default Field Value

By default, a `null` value is used in the query to exclude deleted objects: `{ deletedAt: null }`. However, if the default value of the field is not `null`, the query would not work as we expected.
By default, a `null` value is used in the filter to exclude soft-deleted entities: `{ deletedAt: null }`. However, if the default value of the field is not `null`, the query would not work as we expected.

For example, when the field is `isDeleted` and the default value is `false`, the query `{ isDeleted: null }` would not match any entities.

In this case, an additional option `valueInitial` can be provided:
In this case, an additional option `valueInitial` needs to be specified:

```ts
@SoftDeletable({
type: () => User,
field: 'isDeleted',
value: () => true,
valueInitial: false,
valueInitial: false, // indicating that the default value of `isDeleted` is `false`.
})
```

...which would make the query look like `{ isDeleted: false }` to find all the entities that is not soft-deleted.

When not using the object-based API, this option can be also provided as the 4th argument:
This option could also be specified through the 4th argument:

```ts
@SoftDeletable(() => User, 'isDeleted', () => true, false)
```

### Inheritance

If you want all your entities to be soft deletable, you can create a `SoftDeletableBaseEntity` and make all your other entity classes extend it:
Inheritance is supported for the `SoftDeletable` decorator, thus it is possible to create a `SoftDeletableBaseEntity` to make all the sub entity classes soft-deletable:

```ts
@SoftDeletable(() => SoftDeletableBaseEntity, "deletedAt", () => new Date())
export abstract class SoftDeletableBaseEntity<
T,
PK extends keyof T,
> extends BaseEntity<T, PK> {
export abstract class SoftDeletableBaseEntity extends BaseEntity {
@Property({ nullable: true })
deletedAt?: Date;
}
```

### Hard Deleting
### Hard Deletions

Currently it's impossible to perform perfect hard deletes. As a workaround, we can hard delete entities using the native API:
Currently it's impossible to hard-delete an entity marked as soft-deletable. As a workaround, the native API could be used for hard-deletions:

```ts
em.nativeDelete(...);
Expand Down
12 changes: 6 additions & 6 deletions __tests__/decorator.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ import {
} from "@mikro-orm/core";
import { MikroORM } from "@mikro-orm/sqlite";

import { SoftDeletable } from "../src";
import { SoftDeletable, SoftDeleteHandler } from "../src";

@SoftDeletable(() => UserDeletedAt, "deletedAt", () => new Date())
@Entity()
class UserDeletedAt extends BaseEntity<UserDeletedAt, "id"> {
class UserDeletedAt extends BaseEntity {
@PrimaryKey()
id!: number;

Expand All @@ -22,7 +22,7 @@ class UserDeletedAt extends BaseEntity<UserDeletedAt, "id"> {

@SoftDeletable(() => UserIsDeleted, "isDeleted", () => true, false)
@Entity()
class UserIsDeleted extends BaseEntity<UserDeletedAt, "id"> {
class UserIsDeleted extends BaseEntity {
@PrimaryKey()
id!: number;

Expand All @@ -37,7 +37,7 @@ class UserIsDeleted extends BaseEntity<UserDeletedAt, "id"> {
valueInitial: false,
})
@Entity()
class UserIsDeletedAlt extends BaseEntity<UserDeletedAt, "id"> {
class UserIsDeletedAlt extends BaseEntity {
@PrimaryKey()
id!: number;

Expand All @@ -50,9 +50,9 @@ describe("decorator", () => {

async function init(entities: EntityClass<any>[]) {
orm = await MikroORM.init({
type: "sqlite",
dbName: ":memory:",
entities: entities,
entities,
extensions: [SoftDeleteHandler],
loggerFactory: (options) => new SimpleLogger(options),
// debug: true,
allowGlobalContext: true,
Expand Down
Loading