diff --git a/docker-compose.yml b/docker-compose.yml index 000266d..8f2a4d1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,7 +3,6 @@ version: '3.7' services: postgres: image: "postgres:latest" - container_name: further-postgres hostname: postgres user: postgres restart: always diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 75439f2..5bb8bd6 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -49,4 +49,5 @@ model Profile { age Int? user User @relation(fields: [userId], references: [id]) userId Int @unique + meta Json? } diff --git a/src/lib/utils/cloneParams.ts b/src/lib/utils/cloneParams.ts new file mode 100644 index 0000000..d738253 --- /dev/null +++ b/src/lib/utils/cloneParams.ts @@ -0,0 +1,26 @@ +import { Prisma } from "@prisma/client"; +import { cloneDeep, cloneDeepWith } from "lodash"; + +import { NestedParams } from "../types"; + +// Prisma v4 requires that instances of Prisma.NullTypes are not cloned, +// otherwise it will parse them as 'undefined' and the operation will fail. +function passThroughNullTypes(value: any) { + if ( + value instanceof Prisma.NullTypes.DbNull || + value instanceof Prisma.NullTypes.JsonNull || + value instanceof Prisma.NullTypes.AnyNull + ) { + return value; + } +} + +export function cloneParams(params: NestedParams) { + // only handle null types if they are present, Prisma versions lower than v4 + // do not have them and we can clone the string values as usual + if (Prisma.NullTypes) { + return cloneDeepWith(params, passThroughNullTypes); + } + + return cloneDeep(params); +} diff --git a/src/lib/utils/execution.ts b/src/lib/utils/execution.ts index ef87111..2603199 100644 --- a/src/lib/utils/execution.ts +++ b/src/lib/utils/execution.ts @@ -1,6 +1,5 @@ import { DeferredPromise } from "@open-draft/deferred-promise"; import { omit } from "lodash"; -import cloneDeep from "lodash/cloneDeep"; import { MiddlewareCall, @@ -8,6 +7,7 @@ import { NestedParams, Target, } from "../types"; +import { cloneParams } from "./cloneParams"; export async function executeMiddleware( middleware: NestedMiddleware, @@ -19,7 +19,7 @@ export async function executeMiddleware( >(); const nextPromise = new DeferredPromise(); - const result = middleware(cloneDeep(params), (updatedParams) => { + const result = middleware(cloneParams(params), (updatedParams) => { paramsUpdatedPromise.resolve(updatedParams); return nextPromise; }).catch((e) => { diff --git a/src/lib/utils/params.ts b/src/lib/utils/params.ts index 2c37563..9516ec3 100644 --- a/src/lib/utils/params.ts +++ b/src/lib/utils/params.ts @@ -2,7 +2,6 @@ import set from "lodash/set"; import get from "lodash/get"; import unset from "lodash/unset"; import merge from "lodash/merge"; -import cloneDeep from "lodash/cloneDeep"; import { omit } from "lodash"; import { @@ -32,6 +31,7 @@ import { toOneRelationNonListActions, } from "./actions"; import { fieldsByWriteAction } from "./extractNestedActions"; +import { cloneParams } from "./cloneParams"; function addWriteArgsToParams( params: NestedParams, @@ -159,7 +159,7 @@ export function buildParamsFromCalls( calls: MiddlewareCall[], parentParams: NestedParams ) { - const finalParams = cloneDeep(parentParams); + const finalParams = cloneParams(parentParams); // calls should update the parent calls updated params diff --git a/test/e2e/smoke.test.ts b/test/e2e/smoke.test.ts index d2c97f3..68a623c 100644 --- a/test/e2e/smoke.test.ts +++ b/test/e2e/smoke.test.ts @@ -1,4 +1,4 @@ -import { Post, PrismaClient, User } from "@prisma/client"; +import { Post, Prisma, PrismaClient, User } from "@prisma/client"; import faker from "faker"; import { createNestedMiddleware } from "../../src"; @@ -131,6 +131,26 @@ describe("smoke", () => { }, }); expect(updatedComment).not.toBeNull(); + + // supports Json null values + await testClient.profile.create({ + data: { + userId: secondUser.id, + meta: Prisma.DbNull, + }, + }); + + const profile = await testClient.profile.findFirst({ + where: { + OR: [ + { meta: { equals: Prisma.AnyNull } }, + { meta: { equals: Prisma.DbNull } }, + { meta: { equals: Prisma.JsonNull } }, + ], + }, + }); + expect(profile).not.toBeNull(); + expect(profile!.meta).toBe(null); }); }); }); diff --git a/test/unit/args.test.ts b/test/unit/args.test.ts index 55c5c9f..8e241c2 100644 --- a/test/unit/args.test.ts +++ b/test/unit/args.test.ts @@ -1,3 +1,4 @@ +import { Prisma } from "@prisma/client"; import faker from "faker"; import { set } from "lodash"; @@ -27,6 +28,32 @@ describe("params", () => { expect(params.args.data.posts.create).not.toHaveProperty("test"); }); + it("passes through instances of Prisma.NullTypes to next", async () => { + const nestedMiddleware = createNestedMiddleware((params, next) => { + return next(params); + }); + + const next = jest.fn((_: any) => Promise.resolve(null)); + const params = createParams("Profile", "findFirst", { + where: { + OR: [ + { meta: { equals: Prisma.JsonNull } }, + { meta: { equals: Prisma.DbNull } }, + { meta: { equals: Prisma.AnyNull } }, + ], + }, + }); + await nestedMiddleware(params, next); + + expect(next).toHaveBeenCalledWith(params); + expect(next.mock.calls[0][0].args.where.OR).toHaveLength(3); + next.mock.calls[0][0].args.where.OR.forEach( + ({ meta }: any, index: number) => { + expect(meta.equals).toBe(params.args.where.OR[index].meta.equals); + } + ); + }); + it("allows middleware to modify root args", async () => { const nestedMiddleware = createNestedMiddleware((params, next) => { return next({