diff --git a/README.md b/README.md index 06132bd..a6ee8c9 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 928adaa..ba67093 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -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[] } diff --git a/src/lib/utils/resultFiltering.ts b/src/lib/utils/resultFiltering.ts index 31235ea..5a0cb7e 100644 --- a/src/lib/utils/resultFiltering.ts +++ b/src/lib/utils/resultFiltering.ts @@ -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; } diff --git a/test/e2e/nestedReads.test.ts b/test/e2e/nestedReads.test.ts index f58dab0..15d2046 100644 --- a/test/e2e/nestedReads.test.ts +++ b/test/e2e/nestedReads.test.ts @@ -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(), }, });