diff --git a/apps/site/public/illustrations/client/client_0.svg b/apps/site/public/illustrations/client/client_0.svg
new file mode 100644
index 0000000000..1f56717e05
--- /dev/null
+++ b/apps/site/public/illustrations/client/client_0.svg
@@ -0,0 +1,101 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/site/public/illustrations/client/client_0_light.svg b/apps/site/public/illustrations/client/client_0_light.svg
new file mode 100644
index 0000000000..51444c8f8a
--- /dev/null
+++ b/apps/site/public/illustrations/client/client_0_light.svg
@@ -0,0 +1,101 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/site/public/illustrations/client/client_1.svg b/apps/site/public/illustrations/client/client_1.svg
new file mode 100644
index 0000000000..998fb14935
--- /dev/null
+++ b/apps/site/public/illustrations/client/client_1.svg
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/site/public/illustrations/client/client_1_light.svg b/apps/site/public/illustrations/client/client_1_light.svg
new file mode 100644
index 0000000000..b48691cb92
--- /dev/null
+++ b/apps/site/public/illustrations/client/client_1_light.svg
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/site/src/app/client/page.tsx b/apps/site/src/app/client/page.tsx
new file mode 100644
index 0000000000..48210db38e
--- /dev/null
+++ b/apps/site/src/app/client/page.tsx
@@ -0,0 +1,328 @@
+import type { Metadata } from "next";
+import {
+ SITE_HOME_DESCRIPTION,
+ SITE_HOME_TITLE,
+} from "../../lib/blog-metadata";
+import { Action, Button, Card } from "@prisma/eclipse";
+import API from "@/components/client/api";
+import { CardSection } from "@/components/homepage/card-section/card-section";
+import { cn } from "@/lib/cn";
+import { Technology } from "@/components/client/technology";
+
+export const metadata: Metadata = {
+ title: SITE_HOME_TITLE,
+ description: SITE_HOME_DESCRIPTION,
+};
+
+const databases = {
+ title: "Supported Databases",
+ list: [
+ {
+ name: "PostgreSQL",
+ icon: "/icons/companies/postgres.svg",
+ url: "/",
+ },
+ {
+ name: "MySQL",
+ icon: "/icons/technologies/mysqlsimple.svg",
+ url: "/",
+ },
+ {
+ name: "MariaDB",
+ icon: "/icons/technologies/mariadb.svg",
+ url: "/",
+ },
+ {
+ name: "SQLite",
+ icon: "/icons/companies/sqlite.svg",
+ url: "/",
+ },
+ {
+ name: "SQL Server",
+ icon: "/icons/companies/sqlserver.svg",
+ url: "/",
+ },
+ {
+ name: "CockroachDB",
+ icon: "/icons/companies/cockroachdb.svg",
+ url: "/",
+ },
+ {
+ name: "PlanetScale",
+ icon: "/icons/companies/planetscale.svg",
+ url: "/",
+ },
+ {
+ name: "MongoDB",
+ icon: "/icons/technologies/mongodbsimple.svg",
+ url: "/",
+ },
+ ],
+};
+const frameworks = {
+ title: "Selected Frameworks",
+ description:
+ "Easy to integrate into your framework of choice, Prisma simplifies database access, saves repetitive CRUD boilerplate and increases type safety.",
+ list: [
+ {
+ name: "React",
+ icon: "/icons/technologies/react.svg",
+ url: "/react",
+ },
+ {
+ name: "Next.js",
+ icon: "/icons/technologies/nextjs.svg",
+ url: "/nextjs",
+ },
+ {
+ name: "NestJS",
+ icon: "/icons/technologies/nestjs.svg",
+ url: "/nestjs",
+ },
+ {
+ name: "Apollo",
+ icon: "/icons/technologies/apollo.svg",
+ url: "/apollo",
+ },
+ {
+ name: "Hapi",
+ icon: "/icons/technologies/hapi.svg",
+ url: "/hapi",
+ },
+ {
+ name: "GraphQL",
+ icon: "/icons/technologies/graphql.svg",
+ url: "/graphql",
+ },
+ {
+ name: "ExpressJS",
+ icon: "/icons/technologies/express.svg",
+ url: "/express",
+ },
+ {
+ name: "Redwood",
+ icon: "/icons/technologies/redwoodjs.svg",
+ url: "/redwood",
+ },
+ ],
+};
+const twoCol = [
+ {
+ content: (
+
+
+
+ Editor Integration
+
+
+ Autocomplete your way to success
+
+
+
+ The best code is the code that writes itself. Prisma Client gives you
+ a fantastic autocomplete experience so you can move quickly and be
+ sure you don't write an invalid query. Our obsession with type safety
+ means you can rest assured that your code works as expected, every
+ time.
+
+
+ Get started in 5 minutes
+
+
+ ),
+ imageUrl: "/illustrations/client/client_0",
+ imageAlt: "Autocomplete your way to success",
+ mobileImageUrl: null,
+ mobileImageAlt: null,
+ logos: null,
+ useDefaultLogos: false,
+ visualPosition: "left" as const,
+ visualType: "image" as const,
+ },
+ {
+ content: (
+
+
+
+ TypedSQL
+
+
+ Fully type-safe raw SQL
+
+
+
+ Execute SQL queries directly against your database without losing the
+ benefits of Prisma’s type-checking and auto-completion. TypedSQL
+ leverages the capabilities of Prisma Client to write raw SQL queries
+ that are type-checked at compile time.
+
+
+ Learn more about TypedSQL
+
+
+ ),
+ imageUrl: "/illustrations/client/client_1",
+ imageAlt: "Fully type-safe raw SQL",
+ mobileImageUrl: null,
+ mobileImageAlt: null,
+ logos: null,
+ useDefaultLogos: false,
+ noShadow: true,
+ visualPosition: "right" as const,
+ visualType: "image" as const,
+ },
+];
+
+export default function Client() {
+ return (
+
+
+
+
+
+ Prisma Client
+
+
+ Intuitive database client for TypeScript and Node.js
+
+ Database Migrations
+
+
+
+ The Prisma Client works seamlessly across languages and databases.
+ Ship faster by writing less SQL. Avoid mistakes with a fully type-safe
+ API tailored specifically for your app.
+
+
+
+
+
+
+
+
+ Works with your favourite
+
+ databases and framework
+
+
+
+ {databases.title}
+
+
+ {databases.list.map((db) => (
+
+
+
+
+
+ ))}
+
+
+
+
+
+ {frameworks.title}
+
+
+ {frameworks.description}
+
+
+
+ {frameworks.list.map((fw) => (
+
+
+
+
+
+ ))}
+
+
+
+
+ Browse examples on GitHub
+
+
+
+ Prisma in your stack
+
+
+
+
+
+
+
+ Prisma Studio
+
+
+ Visual database browser
+
+
+ Prisma Studio is the easiest way to explore and manipulate data
+ in your Prisma projects. Understand your data by browsing across
+ tables, filter, paginate, traverse relations and edit your data
+ with safety.
+
+
+ Learn more about Prisma Studio
+
+
+
+
+
+ Prisma Migrate
+
+
+ Hassle-free migrations
+
+
+ Prisma Migrate auto-generates SQL migrations from your Prisma
+ schema. These migration files are fully customizable, giving you
+ full control and ultimate flexibility — from local development
+ to production environments.
+
+
+ Learn more about Prisma Migrate
+
+
+
+
+
+
+
+ );
+}
diff --git a/apps/site/src/app/pricing/pricing-hero-plans.tsx b/apps/site/src/app/pricing/pricing-hero-plans.tsx
index a882dfb451..363bafbf8a 100644
--- a/apps/site/src/app/pricing/pricing-hero-plans.tsx
+++ b/apps/site/src/app/pricing/pricing-hero-plans.tsx
@@ -70,7 +70,7 @@ export function PricingHeroPlans({
-
+
{Object.entries(symbols).map(([code, symbol]) => (
{code} {symbol}
@@ -88,7 +88,9 @@ export function PricingHeroPlans({
{highlighted && (
@@ -129,7 +131,8 @@ export function PricingHeroPlans({
{plan.price[currency]}
- { ' ' } / month
+ {" "}
+ / month
- *An operation is each time you interact with your database, no matter
- the compute time.
+ *An operation is each time you interact with your database, no
+ matter the compute time.
We count the Prisma ORM queries you make, not the SQL statements you
run.{" "}
@@ -181,7 +184,8 @@ export function PricingHeroPlans({
about our pricing model.
- All quotas and limits are shared across all databases in your account.
+ All quotas and limits are shared across all databases in your
+ account.
diff --git a/apps/site/src/components/client/api-data.ts b/apps/site/src/components/client/api-data.ts
new file mode 100644
index 0000000000..867181d6ac
--- /dev/null
+++ b/apps/site/src/components/client/api-data.ts
@@ -0,0 +1,194 @@
+// DO NOT EDIT BY HAND
+// Generated by `scripts/generate-explore.js` at 2022-11-07T16:31:03.482Z.
+// You can adjust the examples in ui/explore, then run `node scripts/generate-explore.js`.
+
+export const apiItems = [
+ {
+ label: "Reading Data",
+ value: "1-reading-data",
+ functions: [
+ {
+ name: "Find Records",
+ jsCodeBlocks: [
+ "// Find all posts\nconst allPosts = await prisma.post.findMany()",
+ "// Find a user by ID\nconst userById = await prisma.user.findUnique({\n where: {\n id: 2\n }\n})",
+ "// Find a user by email\nconst userByEmail = await prisma.user.findUnique({\n where: {\n email: 'ada@prisma.io'\n },\n})",
+ "// Find the first user that starts with Ada\nconst userByName = await prisma.user.findFirst({\n where: {\n name: {\n startsWith: 'Ada'\n }\n },\n})",
+ "// Select specific fields\nconst userName = await prisma.user.findUnique({\n where: {\n email: 'ada@prisma.io',\n },\n select: {\n name: true,\n email: true,\n },\n})",
+ ],
+ tsCodeBlocks: [
+ "// Find all posts\nconst allPosts: Post[] = await prisma.post.findMany()",
+ "// Find a user by ID\nconst userById: User | null = await prisma.user.findUnique({\n where: {\n id: 2,\n },\n})",
+ "// Find a user by email\nconst userByEmail = await prisma.user.findUnique({\n where: {\n email: 'ada@prisma.io',\n },\n})",
+ "// Find the first user that contains Ada\nconst userByName = await prisma.user.findFirst({\n where: {\n name: {\n contains: 'Ada',\n },\n },\n})",
+ "// Select specific fields\nconst userName = await prisma.user.findUnique({\n where: {\n email: 'ada@prisma.io',\n },\n select: {\n name: true,\n email: true,\n },\n})",
+ ],
+ },
+ {
+ name: "Traverse Relations",
+ jsCodeBlocks: [
+ "// Retrieve the posts of a user\nconst postsByUser = await prisma.user\n .findUnique({ where: { email: 'ada@prisma.io' } })\n .posts()",
+ "// Retrieve the categories of a post\nconst categoriesOfPost = await prisma.post\n .findUnique({ where: { id: 1 } })\n .categories()",
+ "// Retrieve the profile of a user via a specific post\nconst authorProfile = await prisma.post\n .findUnique({ where: { id: 1 } })\n .author()\n .profile()",
+ "// Return all users and include their posts and profile\nconst users = await prisma.user.findMany({\n include: {\n posts: true,\n profile: true,\n },\n})",
+ "// Select all users and all their post titles\nconst userPosts = await prisma.user.findMany({\n select: {\n name: true,\n posts: {\n select: {\n title: true,\n },\n },\n },\n})",
+ ],
+ tsCodeBlocks: [
+ "// Retrieve the posts of a user\nconst postsByUser: Post[] = await prisma.user\n .findUnique({ where: { email: 'ada@prisma.io' } })\n .posts()",
+ "// Retrieve the categories of a post\nconst categoriesOfPost: Category[] = await prisma.post\n .findUnique({ where: { id: 1 } })\n .categories()",
+ "// Retrieve the profile of a user via a specific post\nconst authorProfile: Profile | null = await prisma.post\n .findUnique({ where: { id: 1 } })\n .author()\n .profile()",
+ "// Return all users and include their posts and profile\nconst users: User[] = await prisma.user.findMany({\n include: {\n posts: true,\n profile: true,\n },\n})",
+ "// Select all users and all their post titles\nconst userPosts = await prisma.user.findMany({\n select: {\n name: true,\n posts: {\n select: {\n title: true,\n },\n },\n },\n})",
+ ],
+ },
+ {
+ name: "Order By, Limits & Cursors",
+ jsCodeBlocks: [
+ "// Sort posts alphabetically\nconst alphabeticPosts = await prisma.post.findMany({\n orderBy: { title: 'asc' },\n})",
+ "// Order by most prolific authors\nconst prolificAuthors = await prisma.user.findMany({\n orderBy: {\n posts: {\n _count: 'asc',\n },\n },\n})",
+ "// Find the second page of posts\nconst secondPagePosts = await prisma.post.findMany({\n take: 5,\n skip: 5,\n})",
+ "// Find the last 5 posts\nconst lastPosts = await prisma.post.findMany({\n take: -5,\n})",
+ "// Find the next 5 posts after post id 2\nconst paginatedPosts3 = await prisma.post.findMany({\n take: 5,\n cursor: {\n id: 2,\n },\n})",
+ "// Find the last 5 posts before post id 2\nconst paginatedPosts4 = await prisma.post.findMany({\n take: -5,\n cursor: {\n id: 11,\n },\n})",
+ ],
+ tsCodeBlocks: [
+ "// Sort posts alphabetically\nconst alphabeticPosts: Post[] = await prisma.post.findMany({\n orderBy: { title: 'asc' },\n})",
+ "// Order by most prolific authors\nconst prolificAuthors: Category[] = await prisma.user.findMany({\n orderBy: {\n posts: {\n _count: 'asc',\n },\n },\n})",
+ "// Find the second page of posts\nconst secondPagePosts: Post[] = await prisma.post.findMany({\n take: 5,\n skip: 5,\n})",
+ "// Find the last 5 posts\nconst lastPosts: Post[] = await prisma.post.findMany({\n take: -5,\n})",
+ "// Find the next 5 posts after post id 2\nconst paginatedPosts3: Post[] = await prisma.post.findMany({\n take: 5,\n cursor: {\n id: 2,\n },\n})",
+ "// Find the last 5 posts before post id 2\nconst paginatedPosts4: Post[] = await prisma.post.findMany({\n take: -5,\n cursor: {\n id: 11,\n },\n})",
+ ],
+ },
+ {
+ name: "Aggregates & Group By",
+ jsCodeBlocks: [
+ "// Count the number of users\nconst count = await prisma.user.count()",
+ "// Average age of our users\nconst averageAge = await prisma.user.aggregate({\n _avg: {\n age: true,\n },\n})",
+ "// Group users by country\nconst groupUsers = await prisma.user.groupBy({\n by: ['country'],\n _count: {\n id: true,\n },\n})",
+ "// Group users by country with more than 3 users.\nconst usersByCountry = await prisma.user.groupBy({\n by: ['country'],\n _count: {\n country: true,\n },\n having: {\n country: {\n _count: {\n gt: 3,\n },\n },\n },\n})",
+ ],
+ tsCodeBlocks: [
+ "// Count the number of users\nconst count = await prisma.user.count()",
+ "// Average age of our users\nconst averageAge = await prisma.user.aggregate({\n _avg: {\n age: true,\n },\n})",
+ "// Group users by country\nconst groupUsers = await prisma.user.groupBy({\n by: ['country'],\n _count: {\n id: true,\n },\n})",
+ "// Group users by country with more than 3 users.\nconst usersByCountry = await prisma.user.groupBy({\n by: ['country'],\n _count: {\n country: true,\n },\n having: {\n country: {\n _count: {\n gt: 3,\n },\n },\n },\n})",
+ ],
+ },
+ ],
+ },
+ {
+ label: "Writing Data",
+ value: "2-writing-data",
+ functions: [
+ {
+ name: "Create Records",
+ jsCodeBlocks: [
+ "// Create a user\nconst user = await prisma.user.create({\n data: {\n email: 'elsa@prisma.io',\n name: 'Elsa Prisma',\n country: 'Italy',\n age: 30,\n },\n})",
+ "// Create a user and two posts\nconst userWithPosts = await prisma.user.create({\n data: {\n email: 'elsa@prisma.io',\n name: 'Elsa Prisma',\n country: 'Italy',\n age: 30,\n posts: {\n create: [\n {\n title: 'Include this post!',\n },\n {\n title: 'Include this post!',\n },\n ],\n },\n },\n})",
+ "// Create a customer and connect to a user\nconst profile = await prisma.profile.create({\n data: {\n bio: 'Flying Trapeze Artist. Prisma developer.',\n user: {\n connect: {\n id: 10,\n },\n },\n },\n})",
+ "// Create many users at once\nconst users = await prisma.user.createMany({\n data: [\n {\n name: 'Bob',\n email: 'bob@prisma.io',\n country: 'USA',\n age: 24,\n },\n {\n name: 'Yewande',\n email: 'yewande@prisma.io',\n country: 'Zimbabwe',\n age: 19,\n },\n {\n name: 'Angelique',\n email: 'angelique@prisma.io',\n country: 'Greece',\n age: 43,\n },\n ],\n})",
+ "// Create many users at once while skipping duplicates\nconst moreUsers = await prisma.user.createMany({\n data: [\n {\n name: 'Bob',\n email: 'bob@prisma.io',\n country: 'USA',\n age: 24,\n },\n {\n name: 'Bobby',\n email: 'bob@prisma.io',\n country: 'United States of America',\n age: 24,\n }, // Duplicate unique key is ignored!\n {\n name: 'Yewande',\n email: 'yewande@prisma.io',\n country: 'Zimbabwe',\n age: 19,\n },\n {\n name: 'Angelique',\n email: 'angelique@prisma.io',\n country: 'Greece',\n age: 43,\n },\n ],\n skipDuplicates: true, // Skip 'Bobby'\n})",
+ ],
+ tsCodeBlocks: [
+ "// Create a user\nconst user: User = await prisma.user.create({\n data: {\n email: 'elsa@prisma.io',\n name: 'Elsa Prisma',\n country: 'Italy',\n age: 30,\n },\n})",
+ "// Create a user and two posts\nconst userWithPosts: User = await prisma.user.create({\n data: {\n email: 'elsa@prisma.io',\n name: 'Elsa Prisma',\n country: 'Italy',\n age: 30,\n posts: {\n create: [\n {\n title: 'Include this post!',\n },\n {\n title: 'Include this post!',\n },\n ],\n },\n },\n})",
+ "// Create a customer and connect to a user\nconst profile: Profile = await prisma.profile.create({\n data: {\n bio: 'Flying Trapeze Artist. Prisma developer.',\n user: {\n connect: {\n id: 10,\n },\n },\n },\n})",
+ "// Create many users at once\nconst users: Prisma.BatchPayload = await prisma.user.createMany({\n data: [\n {\n name: 'Bob',\n email: 'bob@prisma.io',\n country: 'USA',\n age: 24,\n },\n {\n name: 'Yewande',\n email: 'yewande@prisma.io',\n country: 'Zimbabwe',\n age: 19,\n },\n {\n name: 'Angelique',\n email: 'angelique@prisma.io',\n country: 'Greece',\n age: 43,\n },\n ],\n})",
+ "// Create many users at once while skipping duplicates\nconst moreUsers: Prisma.BatchPayload = await prisma.user.createMany({\n data: [\n {\n name: 'Bob',\n email: 'bob@prisma.io',\n country: 'USA',\n age: 24,\n },\n {\n name: 'Bobby',\n email: 'bob@prisma.io',\n country: 'United States of America',\n age: 24,\n }, // Duplicate unique key is ignored!\n {\n name: 'Yewande',\n email: 'yewande@prisma.io',\n country: 'Zimbabwe',\n age: 19,\n },\n {\n name: 'Angelique',\n email: 'angelique@prisma.io',\n country: 'Greece',\n age: 43,\n },\n ],\n skipDuplicates: true, // Skip 'Bobby'\n})",
+ ],
+ },
+ {
+ name: "Update Records",
+ jsCodeBlocks: [
+ "// Update an existing user\nconst alice = await prisma.user.update({\n data: {\n role: 'ADMIN',\n },\n where: {\n email: 'alice@prisma.io',\n },\n})",
+ "// Change the author of a post in a single transaction\nconst updatedPost = await prisma.post.update({\n where: {\n id: 2,\n },\n data: {\n author: {\n connect: {\n email: 'alice@prisma.io',\n },\n },\n },\n})",
+ "// Connect a post to a user, creating the post if it isn't found\nconst updatedUser = await prisma.user.update({\n where: {\n id: 1,\n },\n data: {\n posts: {\n connectOrCreate: {\n create: {\n title: 'The Gray Gatsby',\n },\n where: {\n id: 10,\n },\n },\n },\n },\n})",
+ "// Update all users with the country Deutschland\nconst updatedUsers = await prisma.user.updateMany({\n data: {\n country: 'Germany',\n },\n where: {\n country: 'Deutschland',\n },\n})",
+ ],
+ tsCodeBlocks: [
+ "// Update an existing user\nconst alice: User = await prisma.user.update({\n data: {\n role: 'ADMIN',\n },\n where: {\n email: 'alice@prisma.io',\n },\n})",
+ "// Change the author of a post in a single transaction\nconst updatedPost: Post = await prisma.post.update({\n where: {\n id: 2,\n },\n data: {\n author: {\n connect: {\n email: 'alice@prisma.io',\n },\n },\n },\n})",
+ "// Connect a post to a user, creating the post if it isn't found\nconst updatedUser: User = await prisma.user.update({\n where: {\n id: 1,\n },\n data: {\n posts: {\n connectOrCreate: {\n create: {\n title: 'The Gray Gatsby',\n },\n where: {\n id: 10,\n },\n },\n },\n },\n})",
+ "// Update all users with the country Deutschland\nconst updatedUsers: Prisma.BatchPayload = await prisma.user.updateMany({\n data: {\n country: 'Germany',\n },\n where: {\n country: 'Deutschland',\n },\n})",
+ ],
+ },
+ {
+ name: "Delete Records",
+ jsCodeBlocks: [
+ "// Delete an existing user\nconst deletedUser = await prisma.user.delete({\n where: {\n email: 'alice@prisma.io',\n },\n})",
+ "// Delete all the admins at once\nconst deletedAdmins = await prisma.user.deleteMany({\n where: {\n role: 'ADMIN',\n },\n})",
+ ],
+ tsCodeBlocks: [
+ "// Delete an existing user\nconst deletedUser: User = await prisma.user.delete({\n where: {\n email: 'alice@prisma.io',\n },\n})",
+ "// Delete all the admins at once\nconst deletedAdmins: Prisma.BatchPayload = await prisma.user.deleteMany({\n where: {\n role: 'ADMIN',\n },\n})",
+ ],
+ },
+ {
+ name: "Upsert Records",
+ jsCodeBlocks: [
+ "// Create Alice or update her role to admin.\nconst alice = await prisma.user.upsert({\n update: {\n role: 'ADMIN',\n },\n where: {\n email: 'alice@prisma.io',\n },\n create: {\n name: 'Alice',\n email: 'alice@prisma.io',\n country: 'England',\n role: 'ADMIN',\n age: 43,\n },\n})",
+ ],
+ tsCodeBlocks: [
+ "// Create Alice or update her role to admin.\nconst alice: User = await prisma.user.upsert({\n update: {\n role: 'ADMIN',\n },\n where: {\n email: 'alice@prisma.io',\n },\n create: {\n name: 'Alice',\n email: 'alice@prisma.io',\n country: 'England',\n role: 'ADMIN',\n age: 43,\n },\n})",
+ ],
+ },
+ ],
+ },
+ {
+ label: "Advanced Patterns",
+ value: "3-advanced-patterns",
+ functions: [
+ {
+ name: "Transactions",
+ jsCodeBlocks: [
+ '// Create Bob and update Carol as a batch within a transaction\nawait prisma.$transaction([\n prisma.user.create({\n data: {\n name: "Bob",\n email: "bob@prisma.io",\n age: 49,\n country: "USA",\n },\n }),\n prisma.user.update({\n where: {\n email: "carol@prisma.io"\n },\n data: {\n country: "Germany",\n },\n }),\n])',
+ " // Example function turning an email into a country\n const locate = async (email) => ({ country: 'Germany' })\n const geo = await locate(bob.email)\n return await tx.user.update({\n where: {\n id: bob.id,\n },\n data: {\n country: geo.country,\n },\n })\n})",
+ ],
+ tsCodeBlocks: [
+ "// Create Bob and update Carol as a batch within a transaction\nawait prisma.$transaction([\n prisma.user.create({\n data: {\n name: 'Bob',\n email: 'bob@prisma.io',\n age: 49,\n country: 'USA',\n },\n }),\n prisma.user.update({\n where: {\n email: 'carol@prisma.io',\n },\n data: {\n country: 'Germany',\n },\n }),\n])",
+ " // Example function turning an email into a country\n const locate = async (email: string) => ({ country: 'Germany' })\n const geo = await locate(bob.email)\n return await tx.user.update({\n where: {\n id: bob.id,\n },\n data: {\n country: geo.country,\n },\n })\n})",
+ ],
+ },
+ {
+ name: "Middleware",
+ jsCodeBlocks: [
+ "// Timing middleware\nprisma.$use(async (params, next) => {\n const before = Date.now()\n const result = await next(params)\n const after = Date.now()\n console.log(\n `Query ${params.model}.${params.action} took ${after - before}ms`,\n )\n return result\n})",
+ "// Hash a User password automatically\nprisma.$use(async (params, next) => {\n if (params.model === 'User') {\n if (params.action === 'create' || params.action === 'update') {\n if (params.args.data.password) {\n const hashPass = hash(params.args.data.password)\n params.args.data.password = hashPass\n }\n }\n }\n return next(params)\n})",
+ ],
+ tsCodeBlocks: [
+ "// Timing middleware\nprisma.$use(async (params, next) => {\n const before = Date.now()\n const result = await next(params)\n const after = Date.now()\n console.log(\n `Query ${params.model}.${params.action} took ${after - before}ms`,\n )\n return result\n})",
+ "// Hash a User password automatically\nprisma.$use(async (params, next) => {\n if (params.model === 'User') {\n if (params.action === 'create' || params.action === 'update') {\n if (params.args.data.password) {\n const hashPass = hash(params.args.data.password)\n params.args.data.password = hashPass\n }\n }\n }\n return next(params)\n})",
+ ],
+ },
+ {
+ name: "Raw Queries with TypedSQL",
+ tsCodeBlocks: [
+ '-- in selectUserByEmail.sql\nSELECT * FROM "User" WHERE email = ${email};\n\n // in app.ts\nconst users: User[] = await prisma.$queryRawTyped(selectUserByEmail(email))',
+ '-- in select5PostsRandomly.sql\nSELECT * FROM "Post" order by random() limit 5\n\n // in app.ts\nconst posts: Post[] = await prisma.$queryRawTyped(select5PostsRandomly())',
+ '-- in clearUsersTable.sql\nDELETE from "User"\n\n // in app.ts\nconst clearUsersTable = await prisma.$executeRawTyped(clearUsersTable())',
+ '-- in dropUsersTable.sql\nTRUNCATE TABLE "User" CASCADE\n\n // in app.ts\nconst dropUsersTable = await prisma.$executeRawTyped(dropUsersTable())',
+ ],
+ },
+ {
+ name: "Logging",
+ tsCodeBlocks: [
+ "// Enable query logging\nconst prisma = new PrismaClient({\n log: [\n {\n emit: 'event',\n level: 'query',\n },\n ],\n})\nprisma.$on('query', (e) => {\n console.log('Query: ' + e.query)\n console.log('Params: ' + e.params)\n console.log('Duration: ' + e.duration + 'ms')\n})",
+ ],
+ },
+ ],
+ },
+ {
+ label: "Prisma Schema",
+ value: "4-prisma-schema",
+ functions: [
+ {
+ name: "Data Model",
+ prismaCodeBlock:
+ '// This is your Prisma schema file. Learn more in the\n// documentation: https://pris.ly/d/prisma-schema\n\ndatasource db {\n provider = "postgres"\n url = env("DATABASE_URL")\n}\n\ngenerator client {\n provider = "prisma-client-js"\n previewFeatures = ["interactiveTransactions"]\n}\n\nmodel User {\n id Int @id @default(autoincrement())\n createdAt DateTime @default(now())\n email String @unique\n name String\n age Int\n role Role @default(USER)\n country String\n posts Post[]\n profile Profile?\n}\n\nmodel Profile {\n id Int @id @default(autoincrement())\n bio String\n user User @relation(fields: [userId], references: [id])\n userId Int @unique\n}\n\nmodel Post {\n id Int @id @default(autoincrement())\n createdAt DateTime @default(now())\n title String\n published Boolean @default(false)\n categories Category[] @relation(references: [id])\n author User @relation(fields: [authorId], references: [id])\n authorId Int\n}\n\nmodel Category {\n id Int @id @default(autoincrement())\n name String\n posts Post[] @relation(references: [id])\n}\n\nenum Role {\n USER\n ADMIN\n}\n',
+ jsCodeBlocks: [""],
+ tsCodeBlocks: [""],
+ },
+ ],
+ },
+];
diff --git a/apps/site/src/components/client/api.tsx b/apps/site/src/components/client/api.tsx
new file mode 100644
index 0000000000..0f5334f1c2
--- /dev/null
+++ b/apps/site/src/components/client/api.tsx
@@ -0,0 +1,262 @@
+"use client";
+import React, { useEffect, useState } from "react";
+import { cn } from "@/lib/cn";
+
+import { apiItems } from "./api-data";
+import {
+ CodeBlock,
+ Button,
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@prisma/eclipse";
+import { prisma_highlighter as getHighlighter } from "@/lib/shiki_prisma";
+
+const langOptions = [
+ {
+ value: "ts",
+ label: "TypeScript",
+ },
+ {
+ value: "js",
+ label: "JavaScript",
+ },
+];
+
+const CurvedArrow = (props: any) => (
+
+
+
+);
+
+const icons: any = {
+ js: ,
+ ts: ,
+};
+
+const renderItem = (item: any) => {
+ return (
+
+ {icons[item.value]}
+ {item.label}
+
+ );
+};
+
+const API = () => {
+ const [funcSelected, setFuncSelected]: any = useState(
+ apiItems[0].functions[0],
+ );
+
+ const [selectedAPIItem, setSelectedAPIItem] = useState(apiItems[0]);
+
+ const [selectedLang, setSelectedLang] = useState(langOptions[0]);
+ const [highlightedCode, setHighlightedCode] = useState([]);
+ const [isLoading, setIsLoading] = useState(true);
+
+ const handleChange = (item: any) => {
+ setSelectedLang(item);
+ };
+
+ const blockType =
+ selectedLang.value === "js" ? "jsCodeBlocks" : "tsCodeBlocks";
+
+ const codeBlockSelected = funcSelected[blockType];
+
+ // Load highlighted code when funcSelected or selectedLang changes
+ useEffect(() => {
+ let isMounted = true;
+
+ const loadHighlightedCode = async () => {
+ setIsLoading(true);
+ try {
+ const highlighter = await getHighlighter();
+ const codeBlocks: string[] = [];
+
+ if ("prismaCodeBlock" in funcSelected) {
+ const html = await highlighter.codeToHtml(
+ funcSelected["prismaCodeBlock"],
+ {
+ lang: "prisma",
+ theme: "prisma-dark",
+ },
+ );
+ codeBlocks.push(html);
+ }
+
+ if (codeBlockSelected && !("prismaCodeBlock" in funcSelected)) {
+ for (let index = 0; index < codeBlockSelected.length; index++) {
+ const html = await highlighter.codeToHtml(
+ codeBlockSelected[index],
+ {
+ lang: "typescript",
+ theme: "prisma-dark",
+ },
+ );
+ codeBlocks.push(html);
+ }
+ }
+
+ if (isMounted) {
+ setHighlightedCode(codeBlocks);
+ setIsLoading(false);
+ }
+ } catch (error) {
+ console.error("Failed to highlight code:", error);
+ if (isMounted) {
+ setIsLoading(false);
+ }
+ }
+ };
+
+ loadHighlightedCode();
+
+ return () => {
+ isMounted = false;
+ };
+ }, [funcSelected, selectedLang, blockType, codeBlockSelected]);
+
+ const contentBlocks = highlightedCode.map((html, index) => (
+ .absolute]:text-foreground-neutral-reverse-weak [&>.absolute]:hover:text-foreground-neutral px-4",
+ )}
+ >
+ {isLoading ? (
+ Loading...
+ ) : (
+
+ )}
+
+ ));
+
+ const CodeUIItems = ({ item, blockType }: any) => {
+ const labelToDisplay = item.functions.filter(
+ (i: any) => i[blockType] && i[blockType].length > 0,
+ );
+ return (
+
+ );
+ };
+
+ const handleCodeBlockChange = (item: any) => {
+ setSelectedAPIItem(item);
+ setFuncSelected(item.functions[0]);
+ };
+
+ return (
+
+
+
+
+
+
+ Explore the Prisma Client API
+
+
+ From simple reads to complex nested writes, the Prisma Client supports
+ a wide range of operations to help you make the most of your data.
+
+
+
+ {
+ if (value) {
+ handleChange(
+ langOptions.find((lang: any) => lang.value === value),
+ );
+ }
+ }}
+ >
+
+
+ {selectedLang.label && renderItem(selectedLang)}
+
+
+
+ {langOptions.map((lang: any) => (
+
+ {renderItem(lang)}
+
+ ))}
+
+
+
+
+ Get Started
+
+
+
+ {
+ if (value) {
+ handleCodeBlockChange(
+ apiItems.find((item: any) => item.value === value),
+ );
+ }
+ }}
+ >
+
+
+ {selectedAPIItem.label && {selectedAPIItem.label} }
+
+
+
+ {apiItems.map((item: any) => (
+
+ {item.label}
+
+ ))}
+
+
+
+ {
}
+
+
+ {contentBlocks}
+
+
+ );
+};
+
+export default API;
diff --git a/apps/site/src/components/client/technology.tsx b/apps/site/src/components/client/technology.tsx
new file mode 100644
index 0000000000..ab24464528
--- /dev/null
+++ b/apps/site/src/components/client/technology.tsx
@@ -0,0 +1,36 @@
+"use client";
+import {
+ Button,
+ Tooltip,
+ TooltipContent,
+ TooltipProvider,
+ TooltipTrigger,
+} from "@prisma/eclipse";
+import { useState } from "react";
+
+export const Technology = ({
+ children,
+ text,
+ url,
+}: {
+ children: React.ReactNode;
+ text: string;
+ url?: string;
+}) => {
+ return (
+
+
+
+
+ {children}
+
+
+ {text}
+
+
+ );
+};
diff --git a/apps/site/src/components/navigation-wrapper.tsx b/apps/site/src/components/navigation-wrapper.tsx
index 2f62a01b78..b53fe12338 100644
--- a/apps/site/src/components/navigation-wrapper.tsx
+++ b/apps/site/src/components/navigation-wrapper.tsx
@@ -35,6 +35,7 @@ const orm = [
"/newsletter",
"/typedsql",
"/partners",
+ "/client",
];
type ColorType = "orm" | "ppg" | undefined;
diff --git a/packages/ui/src/components/footer.tsx b/packages/ui/src/components/footer.tsx
index 465f008a64..d293cea09e 100644
--- a/packages/ui/src/components/footer.tsx
+++ b/packages/ui/src/components/footer.tsx
@@ -22,9 +22,16 @@ type LinkProps = AnchorHTMLAttributes & {
};
const Link = ({ external, color, children, href, ...rest }: LinkProps) => {
+ const hoverClass =
+ color === "orm"
+ ? "hover:bg-background-orm-strong"
+ : color === "ppg"
+ ? "hover:bg-background-ppg-strong"
+ : "";
+
const className = cn(
"text-foreground-neutral-weak text-md font-semibold leading-md flex items-center cursor-pointer font-medium box-border no-underline px-2.5 -ml-2.5 py-1.5 transition-colors rounded-square transition-all",
- color && `hover:bg-background-${color}-strong`,
+ hoverClass,
);
if (external || !href || href.startsWith("http") || href.startsWith("#")) {
@@ -85,25 +92,34 @@ const Footer = ({
- {footerData.socialIcons.map((socialLink: any, idx: number) => (
-
div]:bg-background-${color}-strong`,
- )}
- >
-
-
-
-
- ))}
+ {footerData.socialIcons.map((socialLink: any, idx: number) => {
+ const socialHoverClass =
+ color === "orm"
+ ? "hover:[&>div]:bg-background-orm-strong"
+ : color === "ppg"
+ ? "hover:[&>div]:bg-background-ppg-strong"
+ : "";
+
+ return (
+
+
+
+
+
+ );
+ })}
{/* Main Grid Row */}
@@ -133,7 +149,11 @@ const Footer = ({
@@ -143,20 +163,27 @@ const Footer = ({
{link.links.map(
- (dropLink: { title: string; url: string }) => (
-
- {
+ const dropdownHoverClass =
+ color === "orm"
+ ? "hover:bg-background-orm-strong!"
+ : "hover:bg-background-ppg-strong!";
+
+ return (
+
- {dropLink.title}
-
-
- ),
+
+ {dropLink.title}
+
+
+ );
+ },
)}
diff --git a/packages/ui/src/components/navigation-menu.tsx b/packages/ui/src/components/navigation-menu.tsx
index 8d2794a5c4..81d1dcbad4 100644
--- a/packages/ui/src/components/navigation-menu.tsx
+++ b/packages/ui/src/components/navigation-menu.tsx
@@ -109,18 +109,36 @@ function NavigationMenuItem({
}
const navigationMenuTriggerStyle = cva(
- "bg-transparent hover:bg-background-ppg-strong data-open:hover:bg-background-ppg-strong data-open:focus:bg-background-ppg-strong data-open:bg-background-ppg-strong focus-visible:ring-ring/50 data-popup-open:bg-background-ppg-strong data-popup-open:hover:bg-background-ppg-strong rounded-none! px-2.5 py-1.5 text-base font-semibold transition-all focus-visible:ring-1 focus-visible:outline-1 disabled:opacity-50 group/navigation-menu-trigger inline-flex h-9 w-max items-center justify-center disabled:pointer-events-none outline-none md:rounded-square! md:overflow-hidden cursor-pointer",
+ "bg-transparent rounded-none! px-2.5 py-1.5 text-base font-semibold transition-all focus-visible:ring-1 focus-visible:outline-1 disabled:opacity-50 group/navigation-menu-trigger inline-flex h-9 w-max items-center justify-center disabled:pointer-events-none outline-none md:rounded-square! md:overflow-hidden cursor-pointer focus-visible:ring-ring/50",
+ {
+ variants: {
+ variant: {
+ ppg: "hover:bg-background-ppg-strong data-open:hover:bg-background-ppg-strong data-open:focus:bg-background-ppg-strong data-open:bg-background-ppg-strong data-popup-open:bg-background-ppg-strong data-popup-open:hover:bg-background-ppg-strong",
+ orm: "hover:bg-background-orm-strong data-open:hover:bg-background-orm-strong data-open:focus:bg-background-orm-strong data-open:bg-background-orm-strong data-popup-open:bg-background-orm-strong data-popup-open:hover:bg-background-orm-strong",
+ },
+ },
+ defaultVariants: {
+ variant: "ppg",
+ },
+ },
);
function NavigationMenuTrigger({
className,
children,
+ variant = "ppg",
...props
-}: NavigationMenuPrimitive.Trigger.Props) {
+}: NavigationMenuPrimitive.Trigger.Props & {
+ variant?: "ppg" | "orm";
+}) {
return (
{children}{" "}
@@ -179,13 +197,21 @@ function NavigationMenuPositioner({
function NavigationMenuLink({
className,
+ variant = "ppg",
...props
-}: NavigationMenuPrimitive.Link.Props) {
+}: React.ComponentProps & {
+ variant?: "ppg" | "orm";
+}) {
+ const variantClasses =
+ variant === "orm"
+ ? "data-active:focus:bg-background-orm-strong data-active:hover:bg-background-orm-strong data-active:bg-background-orm-strong hover:bg-background-orm-strong"
+ : "data-active:focus:bg-background-ppg-strong data-active:hover:bg-background-ppg-strong data-active:bg-background-ppg-strong hover:bg-background-ppg-strong";
+
return (
[number];
+ variant?: "ppg" | "orm";
}) {
+ const hoverClass =
+ variant === "orm"
+ ? "hover:bg-background-orm-strong"
+ : "hover:bg-background-ppg-strong";
+ const iconColor =
+ variant === "orm"
+ ? "text-background-orm-reverse"
+ : "text-background-ppg-reverse";
+
return (
{link.icon ? (
-
-
+
+
) : null}
@@ -326,15 +367,32 @@ function MenuNavigationItem({
}
// Add this new component before NavigationMobileMenu
-function MobileMenuItemWithSubmenu({ link }: { link: WebNavigationLink }) {
+function MobileMenuItemWithSubmenu({
+ link,
+ variant = "ppg",
+}: {
+ link: WebNavigationLink;
+ variant?: "ppg" | "orm";
+}) {
const [isOpen, setOpen] = useState(false);
+ const hoverClass =
+ variant === "orm"
+ ? "hover:bg-background-orm-strong! data-open:hover:bg-background-orm-strong! data-open:bg-background-orm-strong! data-popup-open:bg-background-orm-strong! data-popup-open:hover:bg-background-orm-strong!"
+ : "hover:bg-background-ppg-strong! data-open:hover:bg-background-ppg-strong! data-open:bg-background-ppg-strong! data-popup-open:bg-background-ppg-strong! data-popup-open:hover:bg-background-ppg-strong!";
+ const openClass =
+ variant === "orm"
+ ? "bg-background-orm-strong!"
+ : "bg-background-ppg-strong!";
+
return (
setOpen(!isOpen)}
>
@@ -343,7 +401,11 @@ function MobileMenuItemWithSubmenu({ link }: { link: WebNavigationLink }) {
{isOpen && link.sub && (
{link.sub.map((sublink) => (
-
+
))}
)}
@@ -376,7 +438,11 @@ function NavigationMobileMenu({
) : link.sub?.length ? (
-
+
) : null,
)}
diff --git a/packages/ui/src/components/web-navigation.tsx b/packages/ui/src/components/web-navigation.tsx
index d746665916..7b5dbe14de 100644
--- a/packages/ui/src/components/web-navigation.tsx
+++ b/packages/ui/src/components/web-navigation.tsx
@@ -81,13 +81,15 @@ export function WebNavigation({
{links.map((link) =>
link.url ? (
-
+
{link.text}
) : link?.sub?.length ? (
- {link.text}
+
+ {link.text}
+
))}