Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

read replicas #157

Merged
merged 2 commits into from
Oct 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,57 @@ generator kysely {

Take a look at [the camel case example](examples/camel-case/) to see it in action! Check out the [Kysely documentation](https://kysely.dev/) for more information about plugins.

## Read Replicas

Using read replicas with `prisma-extension-kysely` is a breeze!
Just use the excellent [`@prisma/extension-read-replicas`](https://www.npmjs.com/package/@prisma/extension-read-replicas) extension as normal.
Pay attention to how it's configured, though:

```typescript
// Use a common config for primary and replica clients (or different configs)
const kyselyExtensionArgs: PrismaKyselyExtensionArgs<DB> = {
kysely: (driver) =>
new Kysely<DB>({
dialect: {
createAdapter: () => new SqliteAdapter(),
createDriver: () => driver,
createIntrospector: (db) => new SqliteIntrospector(db),
createQueryCompiler: () => new SqliteQueryCompiler(),
},
}),
};

// Initialize the replica client(s) and add the Kysely extension
const replicaClient = new PrismaClient({
datasourceUrl: "YOUR_REPLICA_URL", // Replace this with your replica's URL!
log: [{ level: "query", emit: "event" }],
}).$extends(kyselyExtension(kyselyExtensionArgs));

// Initialize the primary client and add the Kysely extension and the read replicas extension
const prisma = new PrismaClient()
.$extends(kyselyExtension(kyselyExtensionArgs)) // Apply the Kysely extension before the read replicas extension!
.$extends(
readReplicas({
replicas: [replicaClient],
}),
); // Apply the read replicas extension after the Kysely extension!
```

See how we're setting up the replica client as a fully-fledged Prisma client and extending it separately? That's the secret sauce!
It make sure that the replica client has a separate Kysely instance. If you try to use bare URLs, you'll run into trouble;
it'll share the same Kysely instance as the primary client, and you'll get unpleasant surprises!

```typescript
// Don't do this! It won't work as expected.
readReplicas({
url: "postgresql://user:password@localhost:5432/dbname",
});
```

Also, note that we're applying the Kysely extension before the read replicas extension. This is important! If you apply the read replicas extension first, you won't get `.$kysely` on the primary client.

Check out the [read replicas example](examples/read-replicas/) for a runnable example!

## Examples

Check out the [examples](examples) directory for a sample project!
Expand Down
5 changes: 5 additions & 0 deletions examples/read-replica/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Generated files
prisma/generated

# Prisma local database file
prisma/dev.db
122 changes: 122 additions & 0 deletions examples/read-replica/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { PrismaClient } from "@prisma/client";
import {
Kysely,
SqliteAdapter,
SqliteIntrospector,
SqliteQueryCompiler,
} from "kysely";
import kyselyExtension, {
PrismaKyselyExtensionArgs,
} from "prisma-extension-kysely";
import type { DB } from "./prisma/generated/types.js";
import { readReplicas } from "@prisma/extension-read-replicas";

// Use a common config for primary and replica clients
const kyselyExtensionArgs: PrismaKyselyExtensionArgs<DB> = {
kysely: (driver) =>
new Kysely<DB>({
dialect: {
createAdapter: () => new SqliteAdapter(),
createDriver: () => driver,
createIntrospector: (db) => new SqliteIntrospector(db),
createQueryCompiler: () => new SqliteQueryCompiler(),
},
}),
};

// Initialize the replica client(s) and add the Kysely extension
const replicaClient = new PrismaClient({
// For demonstration purposes, use the same SQLite database
datasourceUrl: "file:./dev.db",
log: [{ level: "query", emit: "event" }],
}).$extends(kyselyExtension(kyselyExtensionArgs));

// Initialize the primary client and add the Kysely extension
const prisma = new PrismaClient()
.$extends(kyselyExtension(kyselyExtensionArgs))
.$extends(
readReplicas({
replicas: [replicaClient],
}),
);

async function main() {
console.log(
"Is prisma.$kysely the same as prisma.$primary().$kysely?",
prisma.$kysely === prisma.$primary().$kysely,
);
console.log(
"Is prisma.$kysely the same as prisma.$replica().$kysely?",
prisma.$kysely === prisma.$replica().$kysely,
);
console.log(
"Is prisma.$primary().$kysely the same as prisma.$replica().$kysely?",
prisma.$primary().$kysely === prisma.$replica().$kysely,
);

// Clear the database before running the example
const deletedPosts = await prisma.$kysely
.deleteFrom("Post")
.executeTakeFirstOrThrow();
console.log("Deleted posts:", Number(deletedPosts.numDeletedRows));
const deletedUsers = await prisma.$kysely
.deleteFrom("User")
.executeTakeFirstOrThrow();
console.log("Deleted users:", Number(deletedUsers.numDeletedRows));

// Create and update a user
const insertedUser = await prisma.$transaction(async (tx) => {
const [insertedUser] = await tx.$kysely
.insertInto("User")
.values({ name: "John", email: "[email protected]" })
.returningAll()
.execute();
const affectedRows = await tx.$kysely
.updateTable("User")
.set({ name: "John Doe" })
.where("id", "=", insertedUser.id)
.executeTakeFirstOrThrow();
console.log("Updated users:", Number(affectedRows.numUpdatedRows));

return insertedUser;
});

// Create a post
await prisma
.$primary()
.$kysely.insertInto("Post")
.values({
title: "Hello prisma!",
content: "This is my first post about prisma",
updatedAt: new Date().toISOString(),
published: 1,
authorId: insertedUser.id,
})
.returningAll()
.execute();

// Select a user and a post
const userQuery = prisma
.$replica()
.$kysely.selectFrom("User")
.selectAll()
.where("id", "=", insertedUser.id);
const user = await userQuery.executeTakeFirstOrThrow();

const postQuery = prisma
.$replica()
.$kysely.selectFrom("Post")
.selectAll()
.where((eb) =>
eb.or([
eb("title", "like", "%prisma%"),
eb("content", "like", "%prisma%"),
]),
)
.where("published", "=", 1);
const post = await postQuery.executeTakeFirstOrThrow();

console.log("Query results:", { user, post });
}

main();
Loading