Skip to content

Commit

Permalink
Merge pull request #40 from eoin-obrien/kysely-transactions
Browse files Browse the repository at this point in the history
Add transaction support
  • Loading branch information
eoin-obrien authored Jan 20, 2024
2 parents 7831c13 + d04ebb9 commit e848e0a
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 4 deletions.
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,31 @@ const result = await query.execute();
await prisma.$kysely.deleteFrom("User").where("id", "=", id).execute();
```

## Transactions

Prisma's interactive transactions are fully supported by `prisma-extension-kysely`! Just remeber to use `tx.$kysely` instead of `prisma.$kysely`, and you're good to go:

```typescript
await prisma.$transaction(async (tx) => {
await tx.$kysely
.insertInto("User")
.values({ id: 1, name: "John Doe" })
.execute();

await tx.$kysely
.insertInto("User")
.values({ id: 2, name: "Jane Doe" })
.execute();
});
```

Don't try to use Kysely's `transaction` method directly, though. It's not supported by `prisma-extension-kysely`, and it will throw an error if you try to use it.

```typescript
// Don't do this! Prefer prisma.$transaction instead.
await prisma.$kysely.transaction().execute(async (trx) => {});
```

## Plugins

Do you love Kysely's plugins? So do we! You can use them with `prisma-extension-kysely` as well:
Expand Down
38 changes: 35 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Prisma } from "@prisma/client/extension";
import { Prisma } from "@prisma/client";
import { Kysely } from "kysely";
import { PrismaDriver } from "./driver";

Expand All @@ -21,10 +21,9 @@ export type PrismaKyselyExtensionArgs<Database> = {
export default <Database>(extensionArgs: PrismaKyselyExtensionArgs<Database>) =>
Prisma.defineExtension((client) => {
const driver = new PrismaDriver(client);

const kysely = extensionArgs.kysely(driver);

return client.$extends({
const extendedClient = client.$extends({
name: "prisma-extension-kysely",
client: {
/**
Expand All @@ -33,4 +32,37 @@ export default <Database>(extensionArgs: PrismaKyselyExtensionArgs<Database>) =>
$kysely: kysely,
},
});

// Wrap the $transaction method to attach a fresh Kysely instance to the transaction client
const kyselyTransaction =
(target: typeof extendedClient) =>
(...args: Parameters<typeof target.$transaction>) => {
if (typeof args[0] === "function") {
// If the first argument is a function, add a fresh Kysely instance to the transaction client
const [fn, options] = args;
return target.$transaction(async (tx) => {
// The Kysely instance should call the transaction client, not the original client
const driver = new PrismaDriver(tx);
const kysely = extensionArgs.kysely(driver);
tx.$kysely = kysely;
return fn(tx);
}, options);
} else {
// Otherwise, just call the original $transaction method
return target.$transaction(...args);
}
};

// Attach the wrapped $transaction method to the extended client using a proxy
const extendedClientProxy = new Proxy(extendedClient, {
get: (target, prop, receiver) => {
if (prop === "$transaction") {
return kyselyTransaction(target);
}

return Reflect.get(target, prop, receiver);
},
});

return extendedClientProxy;
});
37 changes: 36 additions & 1 deletion test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,42 @@ describe("prisma-extension-kysely", () => {
);
});

it("should throw an error if a transaction is started", async () => {
it("should expose kysely inside a Prisma transaction", async () => {
await xprisma.$transaction(async (tx) => {
expect(tx.$kysely).toBeInstanceOf(Kysely);
expect(tx.$kysely).not.toBe(xprisma.$kysely);
});
});

it("should support prisma transactions", async () => {
await xprisma.$transaction(async (tx) => {
await tx.$kysely.deleteFrom("Model").execute();
});
await expect(
xprisma.$kysely.selectFrom("Model").selectAll().execute(),
).resolves.toHaveLength(0);
});

it("should rollback prisma transactions", async () => {
await xprisma
.$transaction(async (tx) => {
await tx.$kysely.deleteFrom("Model").execute();
throw new Error("rollback");
})
.catch(() => {});
await expect(
xprisma.$kysely.selectFrom("Model").selectAll().execute(),
).resolves.toHaveLength(1);
});

it("should not interfere with non-interactive transactions", async () => {
await xprisma.$transaction([xprisma.model.deleteMany()]);
await expect(
xprisma.$kysely.selectFrom("Model").selectAll().execute(),
).resolves.toHaveLength(0);
});

it("should forbid the use of kysely's built-in transactions", async () => {
await expect(
xprisma.$kysely.transaction().execute(async () => {}),
).rejects.toThrow("prisma-extension-kysely does not support transactions");
Expand Down

0 comments on commit e848e0a

Please sign in to comment.