From 1fab8e2f6547f3e93bcccc57cc9f054e52628c8c Mon Sep 17 00:00:00 2001 From: Mathieu Post Date: Thu, 5 Feb 2026 12:59:14 +0100 Subject: [PATCH] feat: add support for Prisma TypedSQL ($queryRawTyped) Adds `$queryRawTyped` method to the generated PrismaService for type-safe raw SQL queries using Prisma's TypedSQL feature. Changes: - Add `$queryRawTyped` wrapper that accepts TypedSql queries - Enable `typedSql` preview feature in test schema - Add integration tests for TypedSQL usage and transactions - Update test script to use `prisma generate --sql` Co-Authored-By: Claude Opus 4.5 --- scripts/runTests.ts | 4 +-- src/index.ts | 9 ++++++ tests/integration.test.ts | 50 +++++++++++++++++++++++++++++ tests/prisma/schema.prisma | 5 +-- tests/prisma/sql/getUsersByName.sql | 2 ++ 5 files changed, 66 insertions(+), 4 deletions(-) create mode 100644 tests/prisma/sql/getUsersByName.sql diff --git a/scripts/runTests.ts b/scripts/runTests.ts index 4d6a9b2..ced3df9 100644 --- a/scripts/runTests.ts +++ b/scripts/runTests.ts @@ -32,11 +32,11 @@ const program = Effect.gen(function* () { if (clean) { yield* run(".", "tsc", "--noEmit"); } - yield* run("./tests", "prisma", "generate"); - const dbExists = yield* fs.exists("prisma/dev.db"); + const dbExists = yield* fs.exists("tests/prisma/dev.db"); if (clean || !dbExists) { yield* run("./tests", "prisma", "db", "push"); } + yield* run("./tests", "prisma", "generate", "--sql"); yield* run("./tests", "tsc", "--noEmit"); yield* run("./tests", "vitest", "run"); }).pipe( diff --git a/src/index.ts b/src/index.ts index 136dbb5..2d1f021 100644 --- a/src/index.ts +++ b/src/index.ts @@ -76,6 +76,14 @@ function generateRawSqlOperations() { try: () => client.$queryRawUnsafe(query, ...values), catch: (error) => mapError(error, "$queryRawUnsafe", "Prisma") }), + ), + + $queryRawTyped: (typedQuery: runtime.TypedSql) => + Effect.flatMap(clientOrTx(client), client => + Effect.tryPromise({ + try: () => client.$queryRawTyped(typedQuery), + catch: (error) => mapError(error, "$queryRawTyped", "Prisma") + }), ),`; } @@ -310,6 +318,7 @@ async function generateUnifiedService( import { Cause, Context, Data, Effect, Exit, Option, Runtime } from "effect" import { Service } from "effect/Effect" import { Prisma, PrismaClient } from "${clientImportPath}" +import * as runtime from "@prisma/client/runtime/client" export class PrismaClientService extends Context.Tag("PrismaClientService")< PrismaClientService, diff --git a/tests/integration.test.ts b/tests/integration.test.ts index a5a4c4d..5bd7ba7 100644 --- a/tests/integration.test.ts +++ b/tests/integration.test.ts @@ -8,6 +8,7 @@ import { PrismaTransactionClientService, PrismaUniqueConstraintError, } from "./prisma/generated/effect"; +import { getUsersByName } from "./prisma/generated/sql"; describe("Prisma Effect Generator", () => { const url = "file:prisma/dev.db"; @@ -240,4 +241,53 @@ describe("Prisma Effect Generator", () => { } }).pipe(Effect.provide(MainLayer)), ); + + it.effect("should support $queryRawTyped with TypedSQL", () => + Effect.gen(function* () { + const prisma = yield* PrismaService; + const email = `typed-sql-test-${Date.now()}@example.com`; + + // Create test user + const user = yield* prisma.user.create({ + data: { email, name: "TypedSQL Test User" }, + }); + + // Use TypedSQL query + const users = yield* prisma.$queryRawTyped( + getUsersByName("%TypedSQL%"), + ); + + // Verify result + expect(users.length).toBeGreaterThan(0); + expect(users[0].email).toBeDefined(); + expect(users[0].name).toContain("TypedSQL"); + + // Cleanup + yield* prisma.user.delete({ where: { id: user.id } }); + }).pipe(Effect.provide(MainLayer)), + ); + + it.effect("should support $queryRawTyped within transactions", () => + Effect.gen(function* () { + const prisma = yield* PrismaService; + const email = `tx-typed-sql-${Date.now()}@example.com`; + + yield* prisma.$transaction( + Effect.gen(function* () { + yield* prisma.user.create({ + data: { email, name: "Transaction TypedSQL User" }, + }); + + const users = yield* prisma.$queryRawTyped( + getUsersByName("%Transaction TypedSQL%"), + ); + expect(users.length).toBe(1); + expect(users[0].name).toBe("Transaction TypedSQL User"); + }), + ); + + // Cleanup + yield* prisma.user.delete({ where: { email } }); + }).pipe(Effect.provide(MainLayer)), + ); }); diff --git a/tests/prisma/schema.prisma b/tests/prisma/schema.prisma index 20ba283..4e71b66 100644 --- a/tests/prisma/schema.prisma +++ b/tests/prisma/schema.prisma @@ -3,8 +3,9 @@ datasource db { } generator client { - provider = "prisma-client" - output = "./generated" + provider = "prisma-client" + output = "./generated" + previewFeatures = ["typedSql"] } generator effect { diff --git a/tests/prisma/sql/getUsersByName.sql b/tests/prisma/sql/getUsersByName.sql new file mode 100644 index 0000000..b43a1b7 --- /dev/null +++ b/tests/prisma/sql/getUsersByName.sql @@ -0,0 +1,2 @@ +-- @param {String} $1:namePattern - Pattern to match user names +SELECT id, email, name FROM "User" WHERE name LIKE ?