Skip to content

Add support for custom-defined createValue function #16

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

Merged
merged 4 commits into from
Jan 13, 2024
Merged
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
21 changes: 18 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -122,8 +122,13 @@ client.$use(
```

The `field` property is the name of the field to use for soft delete, and the `createValue` property is a function that
takes a deleted argument and returns the value for whether the record is soft deleted or not. The `createValue` method
must return a falsy value if the record is not deleted and a truthy value if it is deleted.
takes a deleted argument and returns the value for whether the record is soft deleted or not.

The `createValue` method must return a deterministic value if the record is not deleted. This is because the middleware
uses the value returned by `createValue` to modify the `where` object in the query, or to manually filter the results
when including or selecting a toOne relationship. If the value for the field can have multiple values when the record is
not deleted then this filtering will fail. Examples for good values to use when the `deleted` arg in `createValue` is
false are `false`, `null`, `0` or `Date(0)`.

It is possible to setup soft delete for multiple models at once by passing a config for each model in the `models`
object:
@@ -226,7 +231,6 @@ client.$use(
For more information for why updating through toOne relationship is disabled by default see the
[Excluding Soft Deleted Records in a `findUnique` Operation](#excluding-soft-deleted-records-in-a-findunique-operation) section.


To allow to one updates or compound unique index fields globally you can use the `defaultConfig` to do so:

```typescript
@@ -271,6 +275,17 @@ model Comment {
}
```

If the Comment model was configured to use a `deletedAt` field where the value is `Date(0)` by default and a `Date()`
when the record is deleted you would need to add the following to your Prisma schema:

```prisma
model Comment {
deletedAt DateTime @default(dbgenerated("to_timestamp(0)"))
// Note that the example above is for PostgreSQL, you will need to use the appropriate default function for your db.
[other fields]
}
```

Models configured to use soft delete that are related to other models through a toOne relationship must have this
relationship defined as optional. This is because the middleware will exclude soft deleted records when the relationship
is included or selected. If the relationship is not optional the types for the relation will be incorrect and you may
9 changes: 5 additions & 4 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
@@ -52,8 +52,9 @@ model Comment {
}

model Profile {
id Int @id @default(autoincrement())
bio String?
deleted Boolean @default(false)
users User[]
id Int @id @default(autoincrement())
bio String?
deleted Boolean @default(false)
deletedAt DateTime @default(dbgenerated("to_timestamp(0)"))
users User[]
}
9 changes: 6 additions & 3 deletions src/lib/utils/resultFiltering.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { NestedParams } from "prisma-nested-middleware";
import isEqual from "lodash/isEqual";

import { ModelConfig } from "../types";

@@ -10,18 +11,20 @@ export function shouldFilterDeletedFromReadResult(
!params.scope?.relations.to.isList &&
(!params.args.where ||
typeof params.args.where[config.field] === "undefined" ||
params.args.where[config.field] === config.createValue(false))
isEqual(params.args.where[config.field], config.createValue(false)))
);
}

export function filterSoftDeletedResults(result: any, config: ModelConfig) {
// filter out deleted records from array results
if (result && Array.isArray(result)) {
return result.filter((item) => !item[config.field]);
return result.filter((item) =>
isEqual(item[config.field], config.createValue(false))
);
}

// if the result is deleted return null
if (result && result[config.field]) {
if (result && !isEqual(result[config.field], config.createValue(false))) {
return null;
}

19 changes: 14 additions & 5 deletions test/e2e/nestedReads.test.ts
Original file line number Diff line number Diff line change
@@ -11,7 +11,16 @@ describe("nested reads", () => {
beforeAll(async () => {
testClient = new PrismaClient();
testClient.$use(
createSoftDeleteMiddleware({ models: { Comment: true, Profile: true } })
createSoftDeleteMiddleware({
models: {
Comment: true,
// use truthy value for non-deleted to test that they are still filtered
Profile: {
field: "deletedAt",
createValue: (deleted) => (deleted ? new Date() : new Date(0)),
},
},
})
);

user = await client.user.create({
@@ -66,7 +75,7 @@ describe("nested reads", () => {
// restore soft deleted profile
await client.profile.updateMany({
where: {},
data: { deleted: false },
data: { deletedAt: new Date(0) },
});
});
afterAll(async () => {
@@ -107,7 +116,7 @@ describe("nested reads", () => {
await client.profile.updateMany({
where: {},
data: {
deleted: true,
deletedAt: new Date(),
},
});

@@ -214,7 +223,7 @@ describe("nested reads", () => {
await client.profile.updateMany({
where: {},
data: {
deleted: true,
deletedAt: new Date(),
},
});

@@ -402,7 +411,7 @@ describe("nested reads", () => {
await client.profile.update({
where: { id: profile!.id },
data: {
deleted: true,
deletedAt: new Date(),
},
});