diff --git a/package-lock.json b/package-lock.json index 58dfc34..29f3a9b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,22 +13,22 @@ "lodash": "^4.17.21" }, "devDependencies": { - "@prisma/client": "^4.0.0", + "@prisma/client": "^5.0.0", "@types/cls-hooked": "^4.3.3", "@types/jest": "^29.2.6", "@types/lodash": "^4.14.191", "@types/uuid": "^9.0.0", "cls-hooked": "^4.2.2", "jest": "^29.3.1", - "prisma": "^4.9.0", + "prisma": "^5.0.0", "rome": "^11.0.0", "ts-jest": "^29.0.5", "typescript": "^4.9.4", "uuid": "^9.0.0" }, "peerDependencies": { - "@prisma/client": "^4.0.0", - "prisma": "^4.9.0" + "@prisma/client": "^5.0.0", + "prisma": "^5.0.0" } }, "node_modules/@ampproject/remapping": { @@ -978,16 +978,16 @@ } }, "node_modules/@prisma/client": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-4.12.0.tgz", - "integrity": "sha512-j9/ighfWwux97J2dS15nqhl60tYoH8V0IuSsgZDb6bCFcQD3fXbXmxjYC8GHhIgOk3lB7Pq+8CwElz2MiDpsSg==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.1.0.tgz", + "integrity": "sha512-aIxuXlH3p3Vy91buodQhYgAD9m0yuxJzpXMhc1ejQ/WN3pNNWzBA0iDcBObLq8DMdszcXmoLAk3hiRq/ZwBsOw==", "dev": true, "hasInstallScript": true, "dependencies": { - "@prisma/engines-version": "4.12.0-67.659ef412370fa3b41cd7bf6e94587c1dfb7f67e7" + "@prisma/engines-version": "5.1.0-28.a9b7003df90aa623086e4d6f4e43c72468e6339b" }, "engines": { - "node": ">=14.17" + "node": ">=16.13" }, "peerDependencies": { "prisma": "*" @@ -999,16 +999,16 @@ } }, "node_modules/@prisma/engines": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-4.12.0.tgz", - "integrity": "sha512-0alKtnxhNB5hYU+ymESBlGI4b9XrGGSdv7Ud+8TE/fBNOEhIud0XQsAR+TrvUZgS4na5czubiMsODw0TUrgkIA==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.1.0.tgz", + "integrity": "sha512-HqaFsnPmZOdMWkPq6tT2eTVTQyaAXEDdKszcZ4yc7DGMBIYRP6j/zAJTtZUG9SsMV8FaucdL5vRyxY/p5Ni28g==", "dev": true, "hasInstallScript": true }, "node_modules/@prisma/engines-version": { - "version": "4.12.0-67.659ef412370fa3b41cd7bf6e94587c1dfb7f67e7", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-4.12.0-67.659ef412370fa3b41cd7bf6e94587c1dfb7f67e7.tgz", - "integrity": "sha512-JIHNj5jlXb9mcaJwakM0vpgRYJIAurxTUqM0iX0tfEQA5XLZ9ONkIckkhuAKdAzocZ+80GYg7QSsfpjg7OxbOA==", + "version": "5.1.0-28.a9b7003df90aa623086e4d6f4e43c72468e6339b", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.1.0-28.a9b7003df90aa623086e4d6f4e43c72468e6339b.tgz", + "integrity": "sha512-jTwE2oy1yjICmTfnCR0ASIpuMZXZ18sUzQXB7V0RMbrM9OlcmbUwXPuYhnxXuWN8XwRmujeIhsXs/Zeh+fjPOQ==", "dev": true }, "node_modules/@rometools/cli-darwin-arm64": { @@ -1566,9 +1566,9 @@ } }, "node_modules/ci-info": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.7.1.tgz", - "integrity": "sha512-4jYS4MOAaCIStSRwiuxc4B8MYhIe676yO1sYGzARnjXkWpmzZMMYxY6zu8WYWDhSuth5zhrQ1rhNSibyyvv4/w==", + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", + "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", "dev": true, "funding": [ { @@ -3205,20 +3205,19 @@ } }, "node_modules/prisma": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-4.12.0.tgz", - "integrity": "sha512-xqVper4mbwl32BWzLpdznHAYvYDWQQWK2tBfXjdUD397XaveRyAP7SkBZ6kFlIg8kKayF4hvuaVtYwXd9BodAg==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.1.0.tgz", + "integrity": "sha512-wkXvh+6wxk03G8qwpZMOed4Y3j+EQ+bMTlvbDZHeal6k1E8QuGKzRO7DRXlE1NV0WNgOAas8kwZqcLETQ2+BiQ==", "dev": true, "hasInstallScript": true, "dependencies": { - "@prisma/engines": "4.12.0" + "@prisma/engines": "5.1.0" }, "bin": { - "prisma": "build/index.js", - "prisma2": "build/index.js" + "prisma": "build/index.js" }, "engines": { - "node": ">=14.17" + "node": ">=16.13" } }, "node_modules/prompts": { @@ -3266,12 +3265,12 @@ } }, "node_modules/resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "version": "1.22.2", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", + "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", "dev": true, "dependencies": { - "is-core-module": "^2.9.0", + "is-core-module": "^2.11.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -4577,24 +4576,24 @@ } }, "@prisma/client": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-4.12.0.tgz", - "integrity": "sha512-j9/ighfWwux97J2dS15nqhl60tYoH8V0IuSsgZDb6bCFcQD3fXbXmxjYC8GHhIgOk3lB7Pq+8CwElz2MiDpsSg==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.1.0.tgz", + "integrity": "sha512-aIxuXlH3p3Vy91buodQhYgAD9m0yuxJzpXMhc1ejQ/WN3pNNWzBA0iDcBObLq8DMdszcXmoLAk3hiRq/ZwBsOw==", "dev": true, "requires": { - "@prisma/engines-version": "4.12.0-67.659ef412370fa3b41cd7bf6e94587c1dfb7f67e7" + "@prisma/engines-version": "5.1.0-28.a9b7003df90aa623086e4d6f4e43c72468e6339b" } }, "@prisma/engines": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-4.12.0.tgz", - "integrity": "sha512-0alKtnxhNB5hYU+ymESBlGI4b9XrGGSdv7Ud+8TE/fBNOEhIud0XQsAR+TrvUZgS4na5czubiMsODw0TUrgkIA==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.1.0.tgz", + "integrity": "sha512-HqaFsnPmZOdMWkPq6tT2eTVTQyaAXEDdKszcZ4yc7DGMBIYRP6j/zAJTtZUG9SsMV8FaucdL5vRyxY/p5Ni28g==", "dev": true }, "@prisma/engines-version": { - "version": "4.12.0-67.659ef412370fa3b41cd7bf6e94587c1dfb7f67e7", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-4.12.0-67.659ef412370fa3b41cd7bf6e94587c1dfb7f67e7.tgz", - "integrity": "sha512-JIHNj5jlXb9mcaJwakM0vpgRYJIAurxTUqM0iX0tfEQA5XLZ9ONkIckkhuAKdAzocZ+80GYg7QSsfpjg7OxbOA==", + "version": "5.1.0-28.a9b7003df90aa623086e4d6f4e43c72468e6339b", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.1.0-28.a9b7003df90aa623086e4d6f4e43c72468e6339b.tgz", + "integrity": "sha512-jTwE2oy1yjICmTfnCR0ASIpuMZXZ18sUzQXB7V0RMbrM9OlcmbUwXPuYhnxXuWN8XwRmujeIhsXs/Zeh+fjPOQ==", "dev": true }, "@rometools/cli-darwin-arm64": { @@ -5024,9 +5023,9 @@ "dev": true }, "ci-info": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.7.1.tgz", - "integrity": "sha512-4jYS4MOAaCIStSRwiuxc4B8MYhIe676yO1sYGzARnjXkWpmzZMMYxY6zu8WYWDhSuth5zhrQ1rhNSibyyvv4/w==", + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", + "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", "dev": true }, "cjs-module-lexer": { @@ -6260,12 +6259,12 @@ } }, "prisma": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-4.12.0.tgz", - "integrity": "sha512-xqVper4mbwl32BWzLpdznHAYvYDWQQWK2tBfXjdUD397XaveRyAP7SkBZ6kFlIg8kKayF4hvuaVtYwXd9BodAg==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.1.0.tgz", + "integrity": "sha512-wkXvh+6wxk03G8qwpZMOed4Y3j+EQ+bMTlvbDZHeal6k1E8QuGKzRO7DRXlE1NV0WNgOAas8kwZqcLETQ2+BiQ==", "dev": true, "requires": { - "@prisma/engines": "4.12.0" + "@prisma/engines": "5.1.0" } }, "prompts": { @@ -6297,12 +6296,12 @@ "dev": true }, "resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "version": "1.22.2", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", + "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", "dev": true, "requires": { - "is-core-module": "^2.9.0", + "is-core-module": "^2.11.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" } diff --git a/package.json b/package.json index 7cc09b1..ce6a689 100644 --- a/package.json +++ b/package.json @@ -15,14 +15,14 @@ "author": "Cerebrum (https://cerebrum.com)", "license": "MIT", "devDependencies": { - "@prisma/client": "^4.0.0", + "@prisma/client": "^5.0.0", "@types/cls-hooked": "^4.3.3", "@types/jest": "^29.2.6", "@types/lodash": "^4.14.191", "@types/uuid": "^9.0.0", "cls-hooked": "^4.2.2", "jest": "^29.3.1", - "prisma": "^4.9.0", + "prisma": "^5.0.0", "rome": "^11.0.0", "ts-jest": "^29.0.5", "typescript": "^4.9.4", @@ -33,7 +33,7 @@ "lodash": "^4.17.21" }, "peerDependencies": { - "@prisma/client": "^4.0.0", - "prisma": "^4.9.0" + "@prisma/client": "^5.0.0", + "prisma": "^5.0.0" } } \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 5873c91..202a359 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -7,8 +7,7 @@ datasource db { // Note that any user of Yates will also need to use the clientExtensions preview feature generator client { - provider = "prisma-client-js" - previewFeatures = ["clientExtensions"] + provider = "prisma-client-js" } model User { diff --git a/src/expressions.ts b/src/expressions.ts index ba5c7da..a98acb9 100644 --- a/src/expressions.ts +++ b/src/expressions.ts @@ -4,6 +4,7 @@ import random from "lodash/random"; import matches from "lodash/matches"; import { Parser } from "@lucianbuzzo/node-sql-parser"; import { escapeIdentifier, escapeLiteral } from "./escape"; +import { RuntimeDataModel } from "@prisma/client/runtime/library"; const PRISMA_NUMERIC_TYPES = ["Int", "BigInt", "Float", "Decimal"]; @@ -41,12 +42,17 @@ const expressionContext = (context: string) => `___yates_context_${context}`; const getLargeRandomInt = () => random(1000000000, 2147483647); const getDmmfMetaData = (client: PrismaClient, model: string, field: string) => { - const modelData = (client as any)._baseDmmf.datamodel.models.find((m: any) => m.name === model); + const runtimeDataModel = (client as any)._runtimeDataModel as RuntimeDataModel; + const modelData = runtimeDataModel.models[model]; if (!modelData) { throw new Error(`Could not retrieve model data from Prisma Client for model '${model}'`); } const fieldData = modelData.fields.find((f: any) => f.name === field); + if (!fieldData) { + throw new Error(`Could not retrieve field data from Prisma Client for field '${model}.${field}'`); + } + return fieldData; }; @@ -179,6 +185,7 @@ export const expressionToSQL = async (getExpression: Expression, table: string): return getExpression; } + // Create an ephemeral client to capture the SQL query const baseClient = new PrismaClient({ log: [{ level: "query", emit: "event" }], }); @@ -223,7 +230,7 @@ export const expressionToSQL = async (getExpression: Expression, table: string): // as opoosed to a plain SQL expression or "where" object const isSubselect = typeof rawExpression === "object" && typeof rawExpression.then === "function"; - expressionClient.$on("query", (e) => { + baseClient.$on("query", (e: any) => { try { const parser = new Parser(); // Parse the query into an AST diff --git a/src/index.ts b/src/index.ts index 0e2119a..ee8996d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,5 @@ import { Prisma, PrismaClient } from "@prisma/client"; +import { RuntimeDataModel } from "@prisma/client/runtime/library"; import difference from "lodash/difference"; import flatMap from "lodash/flatMap"; import map from "lodash/map"; @@ -90,7 +91,7 @@ export const createClient = (prisma: PrismaClient, getContext: GetContextFn, opt async $allOperations(params) { const { model, args, query, operation } = params; if (!model) { - return query(args); + return (query as any)(args); } const ctx = getContext(); @@ -227,7 +228,12 @@ export const createRoles = async { const abilities: Partial = {}; // See https://github.com/prisma/prisma/discussions/14777 - const models = (prisma as any)._baseDmmf.datamodel.models.map((m: any) => m.name) as Models[]; + // We are reaching into the prisma internals to get the data model. + // This is a bit sketchy, but we can get the internal type definition from the runtime library + // and there is even a test case in prisma that checks that this value is exported + // See https://github.com/prisma/prisma/blob/5.1.0/packages/client/tests/functional/extensions/pdp.ts#L51 + const runtimeDataModel = (prisma as any)._runtimeDataModel as RuntimeDataModel; + const models = Object.keys(runtimeDataModel.models).map((m) => runtimeDataModel.models[m].dbName || m) as Models[]; if (customAbilities) { const diff = difference(Object.keys(customAbilities), models); if (diff.length) { diff --git a/test/integration/expressions.spec.ts b/test/integration/expressions.spec.ts index b2d9e53..b617460 100644 --- a/test/integration/expressions.spec.ts +++ b/test/integration/expressions.spec.ts @@ -368,7 +368,7 @@ describe("expressions", () => { role, }), }), - ).rejects.toThrow("Invalid field name"); + ).rejects.toThrow("Could not retrieve field data"); }); }); diff --git a/test/integration/index.spec.ts b/test/integration/index.spec.ts index e948d99..72d99bd 100644 --- a/test/integration/index.spec.ts +++ b/test/integration/index.spec.ts @@ -247,16 +247,16 @@ describe("setup", () => { }, }); - const post2 = await client.post.update({ - where: { - id: postId2, - }, - data: { - published: true, - }, - }); - - expect(post2.published).toBe(false); + await expect(() => + client.post.update({ + where: { + id: postId2, + }, + data: { + published: true, + }, + }), + ).rejects.toThrow("Record to update not found"); }); it("should be able to allow a role to delete a resource using a custom ability", async () => { diff --git a/test/integration/rbac.spec.ts b/test/integration/rbac.spec.ts index 1aba946..2a41c5f 100644 --- a/test/integration/rbac.spec.ts +++ b/test/integration/rbac.spec.ts @@ -298,17 +298,16 @@ describe("rbac", () => { }, }); - // Because the role can read the post, the update will silently fail and the post will not be updated. - // The Postgres docs indicate that an error should be thrown if the policy "WITH CHECK" expression fails, but this is not the case - // https://www.postgresql.org/docs/11/sql-createpolicy.html#SQL-CREATEPOLICY-UPDATE - await client.post.update({ - where: { id: postId }, - data: { - title: { - set: "lorem ipsum", + await expect(() => + client.post.update({ + where: { id: postId }, + data: { + title: { + set: "lorem ipsum", + }, }, - }, - }); + }), + ).rejects.toThrow("Record to update not found"); const post = await client.post.findUnique({ where: { id: postId },