Exercise 3: Mapping the Gaps in Soft Delete — Possible Solution ================================================================= // lib/prisma.ts: also export the unextended client -> export const base = new PrismaClient({ adapter }); // test/softDelete.int.test.ts import { it, expect, beforeEach, afterAll } from "vitest"; import { base, prisma } from "../lib/prisma"; // base = unextended, prisma = with soft delete import { resetDatabase } from "./reset"; beforeEach(resetDatabase); afterAll(() => base.$disconnect()); it("shows which queries hide soft-deleted posts", async () => { const author = await base.user.create({ data: { email: "a@test.local", posts: { create: [{ title: "Keep", slug: "keep" }, { title: "Gone", slug: "gone" }] }, }, }); await base.post.update({ where: { slug: "gone" }, data: { deletedAt: new Date() } }); // Hidden (covered by the extension) expect((await prisma.post.findMany()).map((p) => p.slug)).toEqual(["keep"]); expect(await prisma.post.findFirst({ where: { slug: "gone" } })).toBeNull(); expect(await prisma.post.count()).toBe(1); // Still visible (NOT covered) expect(await prisma.post.findUnique({ where: { slug: "gone" } })).not.toBeNull(); const withPosts = await prisma.user.findUnique({ where: { id: author.id }, include: { posts: true }, }); expect(withPosts!.posts).toHaveLength(2); // nested read const sums = await prisma.post.aggregate({ _count: { _all: true } }); expect(sums._count._all).toBe(2); // aggregate const raw = await prisma.$queryRaw<{ n: number }[]>`SELECT COUNT(*)::int AS n FROM "Post"`; expect(raw[0].n).toBe(2); // raw SQL }); What I'd change before relying on it: 1. Add findUnique (and findUniqueOrThrow) to the extension. Adding deletedAt to a findUnique where clause works on current Prisma versions, because findUnique also accepts non-unique fields alongside the unique one. 2. Add aggregate and groupBy. 3. Accept that nested reads can't be fixed by a query extension. Either always add where: { deletedAt: null } inside include/select, or move the rule into the database: a view of non-deleted posts, or PostgreSQL row-level security. 4. Make sure raw SQL (Chapter 3) adds the condition itself. WHY THIS WORKS AS AN ANSWER ------------------------------ Instead of assuming the extension works, the test documents its exact coverage using real queries against a real database. It uses the unextended base client to set up data (so the setup isn't affected by the extension) and asserts both what is hidden and what leaks. Keeping this test in the suite means a future change to the extension — or to Prisma's behaviour — shows up immediately.