From b8560b7988382c8825807b17d84815e82bf45151 Mon Sep 17 00:00:00 2001 From: ruheni Date: Thu, 27 Apr 2023 19:10:24 +0200 Subject: [PATCH 01/11] chore: restart pce on a different branch --- cSpell.json | 3 +- .../053-middleware/200-logging-middleware.mdx | 4 + .../061-custom-validation.mdx | 116 +++++++++++++++++- .../02-prisma-client/062-computed-fields.mdx | 78 +++++++++++- .../02-prisma-client/064-custom-models.mdx | 93 +++++++++++++- .../200-extension-examples.mdx | 37 ++++++ 6 files changed, 324 insertions(+), 7 deletions(-) create mode 100644 content/200-concepts/100-components/02-prisma-client/260-client-extensions/200-extension-examples.mdx diff --git a/cSpell.json b/cSpell.json index 04eb74f768..78e0478fc9 100644 --- a/cSpell.json +++ b/cSpell.json @@ -50,7 +50,8 @@ "Redistributable", "Nuxt", "Sveltekit", - "Pothos" + "Pothos", + "backoff" ], "ignoreWords": [ "Ania", diff --git a/content/200-concepts/100-components/02-prisma-client/053-middleware/200-logging-middleware.mdx b/content/200-concepts/100-components/02-prisma-client/053-middleware/200-logging-middleware.mdx index 059f0d6eab..9902d4e4cd 100644 --- a/content/200-concepts/100-components/02-prisma-client/053-middleware/200-logging-middleware.mdx +++ b/content/200-concepts/100-components/02-prisma-client/053-middleware/200-logging-middleware.mdx @@ -84,3 +84,7 @@ enum Role { ``` + +## Going further + +You can also use [Prisma Client extensions](/concepts/components/prisma-client/client-extensions) to log the time it takes to perform a query. You can also find a functional example in [this GitHub repository](https://github.com/prisma/prisma-client-extensions/tree/main/query-logging). diff --git a/content/200-concepts/100-components/02-prisma-client/061-custom-validation.mdx b/content/200-concepts/100-components/02-prisma-client/061-custom-validation.mdx index 096249f324..fe3fbc1bc7 100644 --- a/content/200-concepts/100-components/02-prisma-client/061-custom-validation.mdx +++ b/content/200-concepts/100-components/02-prisma-client/061-custom-validation.mdx @@ -6,13 +6,120 @@ metaDescription: 'This page explains how to add custom validation to Prisma Clie -Prisma Client has type-safety and run-time type validation but does not include validation for user input. +You can add runtime validation for your user input for Prisma Client queries in one of the following ways: -This means you can use any validation library you'd like. The Node.js ecosystem offers a number of high-quality, easy-to-use validation libraries to choose from including: [joi](https://github.com/sideway/joi), [validator.js](https://github.com/validatorjs/validator.js), [Yup](https://github.com/jquense/yup), [Zod](https://github.com/colinhacks/zod) and [Superstruct](https://github.com/ianstormtaylor/superstruct). +- [Prisma Client extensions](/concepts/components/prisma-client/client-extensions) +- A custom function + +You can use any validation library you'd like. The Node.js ecosystem offers a number of high-quality, easy-to-use validation libraries to choose from including: [joi](https://github.com/sideway/joi), [validator.js](https://github.com/validatorjs/validator.js), [Yup](https://github.com/jquense/yup), [Zod](https://github.com/colinhacks/zod) and [Superstruct](https://github.com/ianstormtaylor/superstruct). -## Custom Signup Validation +## Input validation with Prisma Client extensions + +> Prisma Client extensions are currently in [Preview](/about/prisma/releases#preview). + +This example adds runtime validation when creating and updating values using a Zod schema to check that the data passed to Prisma Client is valid. + + + +Query extensions do not currently work for nested operations. In this example, validations are only run on the top level data object passed to methods such as `prisma.product.create()`. Validations implemented this way do not automatically run for [nested writes](https://www.prisma.io/docs/concepts/components/prisma-client/relation-queries#nested-writes). + + + +,, ]}> + + + +```ts copy +import { PrismaClient } from "@prisma/client"; +import { z } from 'zod' +import { type Prisma } from '@prisma/client' + +const PostCreateInput = z.object({ + title: z.string(), + published: z.boolean().default(false), + content: z.string().nullable(), + authorId: z.number().nullable(), +}) satisfies z.Schema + +const prisma = new PrismaClient().$extends({ + query: { + post: { + create({ args, query }) { + args.data = PostCreateInput.parse(args.data) + return query(args) + }, + update({ args, query }) { + args.data = PostCreateInput.partial().parse(args.data) + return query(args) + }, + updateMany({ args, query }) { + args.data = PostCreateInput.partial().parse(args.data) + return query(args) + }, + upsert({ args, query }) { + args.create = PostCreateInput.parse(args.create) + args.update = PostCreateInput.parse(args.update) + return query(args) + } + } + } +}) +``` + + + + + +```ts copy +// A post with invalid input — missing the title field +const post = await prisma.post.create({ + data: { + content: 'Post content', + }, +}) +``` + + + + + +```prisma copy +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +generator client { + provider = "prisma-client-js" + previewFeatures = ["clientExtensions"] +} + +model User { + id Int @id @default(autoincrement()) + email String @unique + name String? + + posts Post[] +} + +model Post { + id Int @id @default(autoincrement()) + title String + published Boolean @default(true) + content String? + + authorId Int? + author User? @relation(fields: [authorId], references: [id]) +} +``` + + + + + +## Custom signup validation Here's an example using [Superstruct](https://github.com/ianstormtaylor/superstruct) to validate that the data needed to signup a new user is correct: @@ -50,7 +157,8 @@ async function signup(input: Signup): Promise { The example above shows how you can create a custom type-safe `signup` function that ensures the input is valid before creating a user. -## Going Further +## Going further +- Learn how you can use [Prisma Client extensions](/concepts/components/prisma-client/client-extensions) to add input validation for your queries — [example](https://github.com/prisma/prisma-client-extensions/tree/main/input-validation). - Learn how you can organize your code better by moving the `signup` function into [a custom model](/concepts/components/prisma-client/custom-models). - There's an [outstanding feature request](https://github.com/prisma/prisma/issues/3528) to bake user validation into Prisma Client. If you'd like to see that happen, make sure to upvote that issue and share your use case! diff --git a/content/200-concepts/100-components/02-prisma-client/062-computed-fields.mdx b/content/200-concepts/100-components/02-prisma-client/062-computed-fields.mdx index 367f818e78..799a33a960 100644 --- a/content/200-concepts/100-components/02-prisma-client/062-computed-fields.mdx +++ b/content/200-concepts/100-components/02-prisma-client/062-computed-fields.mdx @@ -10,6 +10,81 @@ Computed fields allow you to derive a new field based on existing data. A common +## Computed fields with Prisma Client extensions + +> Prisma Client extensions are currently in [Preview](/about/prisma/releases#preview). + +The following example illustrates how to create a [Prisma Client extension](/concepts/components/prisma-client/client-extensions) that adds a virtual/ computed field to a model in a Prisma schema. The example adds a `fullName` property that is computed at runtime. + +, ,]}> + + + +```tsx +import { PrismaClient } from '@prisma/client' + +const prisma = new PrismaClient().$extends({ + result: { + user: { + fullName: { + needs: { firstName: true, lastName: true }, + compute(user) { + return `${user.firstName} ${user.lastName}` + }, + }, + }, + }, +}) +``` + + + + + +```ts +const user = await prisma.user.findMany({ + take: 5, +}) +``` + + + + + +```prisma file="prisma/schema.prisma" copy +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +generator client { + provider = "prisma-client-js" + previewFeatures = ["clientExtensions"] +} + +model User { + id Int @id @default(autoincrement()) + email String @unique + name String? + + posts Post[] +} + +model Post { + id Int @id @default(autoincrement()) + title String + published Boolean @default(true) + content String? + + authorId Int? + author User? @relation(fields: [authorId], references: [id]) +} +``` + + + + + ## Deriving a Full Name from a First and Last Name Prisma Client does not yet natively support computed fields, but with a bit of TypeScript magic, you can define a function that accepts a generic as an input then extend that generic to ensure it conforms to a specific structure. Finally, you can return that generic with additional computed fields. Let's see how that might look: @@ -74,7 +149,8 @@ A `WithFullName` return type has also been defined, which takes whatever ` With this function, any object that contains `firstName` and `lastName` keys can compute a `fullName`. Pretty neat, right? -## Going Further +## Going further +- Learn how you can use [Prisma Client extensions](/concepts/components/prisma-client/client-extensions) to add a computed field to your schema — [example](https://github.com/prisma/prisma-client-extensions/tree/main/computed-fields). - Learn how you can move the `computeFullName` function into [a custom model](/concepts/components/prisma-client/custom-models). - There's an [outstanding feature request](https://github.com/prisma/prisma/issues/3394) to add native support to Prisma Client. If you'd like to see that happen, make sure to upvote that issue and share your use case! diff --git a/content/200-concepts/100-components/02-prisma-client/064-custom-models.mdx b/content/200-concepts/100-components/02-prisma-client/064-custom-models.mdx index 898729f930..1cc267be51 100644 --- a/content/200-concepts/100-components/02-prisma-client/064-custom-models.mdx +++ b/content/200-concepts/100-components/02-prisma-client/064-custom-models.mdx @@ -6,10 +6,97 @@ metaDescription: 'This page explains how to wrap Prisma Client in custom models' -As your application grows, you may find the need to group related logic together. We suggest either wrapping a model in a class or extending Prisma Client model object. +As your application grows, you may find the need to group related logic together. We suggest either: + +- Creating static methods using a [Prisma Client extension](/concepts/components/prisma-client/client-extensions) +- Wrapping a model in a class +- Extending Prisma Client model object +## Static methods with Prisma Client extensions + +> Prisma Client extensions are currently in [Preview](/about/prisma/releases#preview). + +The following example demonstrates how to create a Prisma Client extension that adds a `signUp` and `findManyByDomain` methods to a User model. + +, ,]}> + + + +```tsx +import bcrypt from 'bcryptjs' +import { PrismaClient } from '@prisma/client' + +const prisma = new PrismaClient().$extends({ + model: { + user: { + async signUp(email: string, password: string) { + const hash = await bcrypt.hash(password, 10) + return prisma.user.create({ + data: { + email, + password: { + create: { + hash, + }, + }, + }, + }) + }, + + async findManyByDomain(domain: string) { + return prisma.user.findMany({ + where: { email: { endsWith: `@${domain}` } }, + }) + }, + }, + }, +}) +``` + + + + + +```ts +await prisma.user.signUp('user2@example2.com', 's3cret') + +await prisma.user.findManyByDomain('example2.com') +``` + + + + + +```prisma file="prisma/schema.prisma" copy +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +generator client { + provider = "prisma-client-js" + previewFeatures = ["clientExtensions"] +} + +model User { + id String @id @default(cuid()) + email String + password Password? +} + +model Password { + hash String + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + userId String @unique +} +``` + + + + + ## Wrap a Prisma Model in a Class In the example below, you'll see how you can wrap the `user` model in the Prisma Client within a `Users` class. @@ -89,3 +176,7 @@ async function main() { ``` Now you can use your custom `signup` method alongside `count`, `updateMany`, `groupBy` and all of the other wonderful methods that Prisma Client provides. Best of all, it's all type-safe! + +## Going further + +We recommend using [Prisma Client extensions](/concepts/components/prisma-client/client-extensions) to extend your models with [custom model methods](https://github.com/prisma/prisma-client-extensions/tree/main/instance-methods). diff --git a/content/200-concepts/100-components/02-prisma-client/260-client-extensions/200-extension-examples.mdx b/content/200-concepts/100-components/02-prisma-client/260-client-extensions/200-extension-examples.mdx new file mode 100644 index 0000000000..c57df5d3c7 --- /dev/null +++ b/content/200-concepts/100-components/02-prisma-client/260-client-extensions/200-extension-examples.mdx @@ -0,0 +1,37 @@ +--- +title: 'Examples' +metaTitle: 'Prisma Client Extension | Examples' +metaDescription: 'Extend the functionality of Prisma Client: Examples' +--- + + + +Prisma Client extensions is currently in [Preview](/about/prisma/releases#preview). The extensions are provided as examples only, and without warranty. They are not +intended to be used in production environments. + + + +## Examples + +| Example | Description | +| :---------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------ | +| [`audit-log-context`](https://github.com/prisma/prisma-client-extensions/tree/main/audit-log-context) | Provides the current user's ID as context to Postgres audit log triggers | +| [`callback-free-itx`](https://github.com/prisma/prisma-client-extensions/tree/main/callback-free-itx) | Adds a method to start interactive transactions without callbacks | +| [`computed-fields`](https://github.com/prisma/prisma-client-extensions/tree/main/computed-fields) | Adds virtual / computed fields to result objects | +| [`input-transformation`](https://github.com/prisma/prisma-client-extensions/tree/main/input-transformation) | Transforms the input arguments passed to Prisma Client queries to filter the result set | +| [`input-validation`](https://github.com/prisma/prisma-client-extensions/tree/main/input-validation) | Runs custom validation logic on input arguments passed to mutation methods | +| [`instance-methods`](https://github.com/prisma/prisma-client-extensions/tree/main/instance-methods) | Adds Active Record-like methods like `save()` and `delete()` to result objects | +| [`json-field-types`](https://github.com/prisma/prisma-client-extensions/tree/main/json-field-types) | Uses strongly-typed runtime parsing for data stored in JSON columns | +| [`model-filters`](https://github.com/prisma/prisma-client-extensions/tree/main/model-filters) | Adds reusable filters that can composed into complex `where` conditions for a model | +| [`obfuscated-fields`](https://github.com/prisma/prisma-client-extensions/tree/main/obfuscated-fields) | Prevents sensitive data (e.g. `password` fields) from being included in results | +| [`query-logging`](https://github.com/prisma/prisma-client-extensions/tree/main/query-logging) | Wraps Prisma Client queries with simple query timing and logging | +| [`readonly-client`](https://github.com/prisma/prisma-client-extensions/tree/main/readonly-client) | Creates a client that only allows read operations | +| [`retry-transactions`](https://github.com/prisma/prisma-client-extensions/tree/main/retry-transactions) | Adds a retry mechanism to transactions with exponential backoff and jitter | +| [`row-level-security`](https://github.com/prisma/prisma-client-extensions/tree/main/row-level-security) | Uses Postgres row-level security policies to isolate data a multi-tenant application | +| [`static-methods`](https://github.com/prisma/prisma-client-extensions/tree/main/static-methods) | Adds custom query methods to Prisma Client models | +| [`transformed-fields`](https://github.com/prisma/prisma-client-extensions/tree/main/transformed-fields) | Demonstrates how to use result extensions to transform query results and add i18n to an app | +| [`exists-method`](https://github.com/prisma/prisma-client-extensions/tree/main/exists-method) | Demonstrates how to add an `exists` method to all your models | + +## Going further + +- Learn more about [Prisma Client extensions](/concepts/components/prisma-client/client-extensions). From 5b0556bcd8964c3e5ae85c1d37ab00654dfb271f Mon Sep 17 00:00:00 2001 From: ruheni Date: Thu, 27 Apr 2023 19:26:07 +0200 Subject: [PATCH 02/11] chore: udpate custom validation example --- .../061-custom-validation.mdx | 31 ++++++++++++------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/content/200-concepts/100-components/02-prisma-client/061-custom-validation.mdx b/content/200-concepts/100-components/02-prisma-client/061-custom-validation.mdx index fe3fbc1bc7..df0ce7a551 100644 --- a/content/200-concepts/100-components/02-prisma-client/061-custom-validation.mdx +++ b/content/200-concepts/100-components/02-prisma-client/061-custom-validation.mdx @@ -27,7 +27,7 @@ Query extensions do not currently work for nested operations. In this example, v -,, ]}> +,, ]}> @@ -43,6 +43,9 @@ const PostCreateInput = z.object({ authorId: z.number().nullable(), }) satisfies z.Schema +/** + * Prisma Client Extension + */ const prisma = new PrismaClient().$extends({ query: { post: { @@ -66,19 +69,25 @@ const prisma = new PrismaClient().$extends({ } } }) -``` - +async function main (){ + // A post with invalid input — missing the title field + const invalidPost = await prisma.post.create({ + data: { + content: 'Prisma Client: extended!', + }, + }) - + // A post with valid input + const validPost = await prisma.post.create({ + data: { + title: 'Prisma Client extensions', + content: 'Prisma Client: extended!', + }, + }) +} -```ts copy -// A post with invalid input — missing the title field -const post = await prisma.post.create({ - data: { - content: 'Post content', - }, -}) +main() ``` From 332dfbf9499946546f4943f7cc85f5efe5628434 Mon Sep 17 00:00:00 2001 From: ruheni Date: Thu, 27 Apr 2023 22:13:37 +0200 Subject: [PATCH 03/11] chore: update computed field example --- .../061-custom-validation.mdx | 6 +++++- .../02-prisma-client/062-computed-fields.mdx | 19 ++++++++++--------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/content/200-concepts/100-components/02-prisma-client/061-custom-validation.mdx b/content/200-concepts/100-components/02-prisma-client/061-custom-validation.mdx index df0ce7a551..50f7f61d14 100644 --- a/content/200-concepts/100-components/02-prisma-client/061-custom-validation.mdx +++ b/content/200-concepts/100-components/02-prisma-client/061-custom-validation.mdx @@ -27,7 +27,7 @@ Query extensions do not currently work for nested operations. In this example, v -,, ]}> +, ]}> @@ -71,6 +71,10 @@ const prisma = new PrismaClient().$extends({ }) async function main (){ + /** + * Example usage + */ + // A post with invalid input — missing the title field const invalidPost = await prisma.post.create({ data: { diff --git a/content/200-concepts/100-components/02-prisma-client/062-computed-fields.mdx b/content/200-concepts/100-components/02-prisma-client/062-computed-fields.mdx index 799a33a960..22a051e4de 100644 --- a/content/200-concepts/100-components/02-prisma-client/062-computed-fields.mdx +++ b/content/200-concepts/100-components/02-prisma-client/062-computed-fields.mdx @@ -16,7 +16,7 @@ Computed fields allow you to derive a new field based on existing data. A common The following example illustrates how to create a [Prisma Client extension](/concepts/components/prisma-client/client-extensions) that adds a virtual/ computed field to a model in a Prisma schema. The example adds a `fullName` property that is computed at runtime. -, ,]}> + ,]}> @@ -35,16 +35,17 @@ const prisma = new PrismaClient().$extends({ }, }, }) -``` - - - +async function main() { + /** + * Example query containing the `fullName` computed field in the response + */ + const user = await prisma.user.findMany({ + take: 5, + }) +} -```ts -const user = await prisma.user.findMany({ - take: 5, -}) +main() ``` From add3ec6f9977c45e628a5691f2f4c96d869e1828 Mon Sep 17 00:00:00 2001 From: ruheni Date: Thu, 27 Apr 2023 22:49:28 +0200 Subject: [PATCH 04/11] chore: clean up headings --- .../061-custom-validation.mdx | 2 +- .../02-prisma-client/062-computed-fields.mdx | 55 ++++++++++++++++++- 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/content/200-concepts/100-components/02-prisma-client/061-custom-validation.mdx b/content/200-concepts/100-components/02-prisma-client/061-custom-validation.mdx index 50f7f61d14..71df29f16e 100644 --- a/content/200-concepts/100-components/02-prisma-client/061-custom-validation.mdx +++ b/content/200-concepts/100-components/02-prisma-client/061-custom-validation.mdx @@ -132,7 +132,7 @@ model Post { -## Custom signup validation +## Input validation with a custom validation function Here's an example using [Superstruct](https://github.com/ianstormtaylor/superstruct) to validate that the data needed to signup a new user is correct: diff --git a/content/200-concepts/100-components/02-prisma-client/062-computed-fields.mdx b/content/200-concepts/100-components/02-prisma-client/062-computed-fields.mdx index 22a051e4de..ec26af9c5d 100644 --- a/content/200-concepts/100-components/02-prisma-client/062-computed-fields.mdx +++ b/content/200-concepts/100-components/02-prisma-client/062-computed-fields.mdx @@ -86,9 +86,60 @@ model Post { -## Deriving a Full Name from a First and Last Name +The query above returns the following result: -Prisma Client does not yet natively support computed fields, but with a bit of TypeScript magic, you can define a function that accepts a generic as an input then extend that generic to ensure it conforms to a specific structure. Finally, you can return that generic with additional computed fields. Let's see how that might look: + + + + + +```js no-copy +;[ + { + id: 'clgzkgy2j00004tc6iwu3gkzu', + firstName: 'Aurelia', + lastName: 'Schneider', + email: 'Jalen_Berge40@hotmail.com', + fullName: 'Aurelia Schneider', + }, + { + id: 'clgzkgy4p00024tc6up79cdmp', + firstName: 'Agustina', + lastName: 'Langworth', + email: 'Manley16@hotmail.com', + fullName: 'Agustina Langworth', + }, + { + id: 'clgzkgy6h00044tc6lt3y4e44', + firstName: 'Nannie', + lastName: 'Dibbert', + email: 'Kole.Morissette95@hotmail.com', + fullName: 'Nannie Dibbert', + }, + { + id: 'clgzkgy8800064tc6c222ujuy', + firstName: 'Zoey', + lastName: 'Lubowitz', + email: 'Laurianne_Pollich72@gmail.com', + fullName: 'Zoey Lubowitz', + }, + { + id: 'clgzkgya400084tc6i3oxvsk2', + firstName: 'Arch', + lastName: 'Ferry', + email: 'Nikki_Lang@yahoo.com', + fullName: 'Arch Ferry', + }, +] +``` + + + + + +## Computed fields with a computation function + +Prisma Client does not yet natively support computed fields, but, you can define a function that accepts a generic type as an input then extend that generic to ensure it conforms to a specific structure. Finally, you can return that generic with additional computed fields. Let's see how that might look: , ]}> From 2f77909213157317a2af703fd09401b34acf0796 Mon Sep 17 00:00:00 2001 From: ruheni Date: Thu, 27 Apr 2023 23:45:34 +0200 Subject: [PATCH 05/11] chore: minor update --- .../100-components/02-prisma-client/062-computed-fields.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/200-concepts/100-components/02-prisma-client/062-computed-fields.mdx b/content/200-concepts/100-components/02-prisma-client/062-computed-fields.mdx index ec26af9c5d..49cb7fa1f5 100644 --- a/content/200-concepts/100-components/02-prisma-client/062-computed-fields.mdx +++ b/content/200-concepts/100-components/02-prisma-client/062-computed-fields.mdx @@ -86,7 +86,7 @@ model Post { -The query above returns the following result: +The query above returns a result with the following structure: From 99dc7409941ebf3148ca6c86a815815c3ca0b9df Mon Sep 17 00:00:00 2001 From: ruheni Date: Tue, 2 May 2023 15:42:51 +0200 Subject: [PATCH 06/11] chore: polish content on computed fields --- .../02-prisma-client/062-computed-fields.mdx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/content/200-concepts/100-components/02-prisma-client/062-computed-fields.mdx b/content/200-concepts/100-components/02-prisma-client/062-computed-fields.mdx index 49cb7fa1f5..f10124f2af 100644 --- a/content/200-concepts/100-components/02-prisma-client/062-computed-fields.mdx +++ b/content/200-concepts/100-components/02-prisma-client/062-computed-fields.mdx @@ -10,11 +10,11 @@ Computed fields allow you to derive a new field based on existing data. A common -## Computed fields with Prisma Client extensions +## Using a Prisma Client extension > Prisma Client extensions are currently in [Preview](/about/prisma/releases#preview). -The following example illustrates how to create a [Prisma Client extension](/concepts/components/prisma-client/client-extensions) that adds a virtual/ computed field to a model in a Prisma schema. The example adds a `fullName` property that is computed at runtime. +The following example illustrates how to create a [Prisma Client extension](/concepts/components/prisma-client/client-extensions) that adds a `fullName` computed field at runtime to the `User` model in a Prisma schema. ,]}> @@ -52,7 +52,7 @@ main() -```prisma file="prisma/schema.prisma" copy +```prisma copy datasource db { provider = "postgresql" url = env("DATABASE_URL") @@ -86,7 +86,7 @@ model Post { -The query above returns a result with the following structure: +The query above would return a result with the following structure: @@ -137,7 +137,9 @@ The query above returns a result with the following structure: -## Computed fields with a computation function +The computed fields are type-safe and can return anything from a concatenated value to complex objects or functions that can act as an instance method for your models. + +## Using a computation function Prisma Client does not yet natively support computed fields, but, you can define a function that accepts a generic type as an input then extend that generic to ensure it conforms to a specific structure. Finally, you can return that generic with additional computed fields. Let's see how that might look: From 455361e4d09072c92bb29051ca28c92b1bf43391 Mon Sep 17 00:00:00 2001 From: ruheni Date: Wed, 3 May 2023 13:41:09 +0200 Subject: [PATCH 07/11] chore: update custom validation example --- .../061-custom-validation.mdx | 116 ++++++++++-------- 1 file changed, 66 insertions(+), 50 deletions(-) diff --git a/content/200-concepts/100-components/02-prisma-client/061-custom-validation.mdx b/content/200-concepts/100-components/02-prisma-client/061-custom-validation.mdx index 71df29f16e..75aedb04ee 100644 --- a/content/200-concepts/100-components/02-prisma-client/061-custom-validation.mdx +++ b/content/200-concepts/100-components/02-prisma-client/061-custom-validation.mdx @@ -21,7 +21,7 @@ You can use any validation library you'd like. The Node.js ecosystem offers a nu This example adds runtime validation when creating and updating values using a Zod schema to check that the data passed to Prisma Client is valid. - + Query extensions do not currently work for nested operations. In this example, validations are only run on the top level data object passed to methods such as `prisma.product.create()`. Validations implemented this way do not automatically run for [nested writes](https://www.prisma.io/docs/concepts/components/prisma-client/relation-queries#nested-writes). @@ -32,63 +32,78 @@ Query extensions do not currently work for nested operations. In this example, v ```ts copy -import { PrismaClient } from "@prisma/client"; +import { PrismaClient, Prisma } from "@prisma/client"; import { z } from 'zod' -import { type Prisma } from '@prisma/client' -const PostCreateInput = z.object({ - title: z.string(), - published: z.boolean().default(false), - content: z.string().nullable(), - authorId: z.number().nullable(), -}) satisfies z.Schema +/** + * Zod schema + */ +export const ProductCreateInput = z.object({ + slug: z + .string() + .max(100) + .regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/), + name: z.string().max(100), + description: z.string().max(1000), + price: z + .instanceof(Prisma.Decimal) + .refine((price) => price.gte("0.01") && price.lt("1000000.00")), +}) satisfies z.Schema; /** * Prisma Client Extension */ const prisma = new PrismaClient().$extends({ query: { - post: { + product: { create({ args, query }) { - args.data = PostCreateInput.parse(args.data) - return query(args) + args.data = ProductCreateInput.parse(args.data); + return query(args); }, update({ args, query }) { - args.data = PostCreateInput.partial().parse(args.data) - return query(args) + args.data = ProductCreateInput.partial().parse(args.data); + return query(args); }, updateMany({ args, query }) { - args.data = PostCreateInput.partial().parse(args.data) - return query(args) + args.data = ProductCreateInput.partial().parse(args.data); + return query(args); }, upsert({ args, query }) { - args.create = PostCreateInput.parse(args.create) - args.update = PostCreateInput.parse(args.update) - return query(args) - } - } - } -}) + args.create = ProductCreateInput.parse(args.create); + args.update = ProductCreateInput.partial().parse(args.update); + return query(args); + }, + }, + }, +}); async function main (){ /** * Example usage */ - - // A post with invalid input — missing the title field - const invalidPost = await prisma.post.create({ + // Valid product + const product = await prisma.product.create({ data: { - content: 'Prisma Client: extended!', + slug: "example-product", + name: "Example Product", + description: "Lorem ipsum dolor sit amet", + price: new Prisma.Decimal("10.95"), }, - }) - - // A post with valid input - const validPost = await prisma.post.create({ - data: { - title: 'Prisma Client extensions', - content: 'Prisma Client: extended!', - }, - }) + }); + + // Invalid product + try { + await prisma.product.create({ + data: { + slug: "invalid-product", + name: "Invalid Product", + description: "Lorem ipsum dolor sit amet", + price: new Prisma.Decimal("-1.00"), + }, + }); + } catch (err: any) { + console.log(err?.cause?.issues); + } } main() @@ -109,22 +124,21 @@ generator client { previewFeatures = ["clientExtensions"] } -model User { - id Int @id @default(autoincrement()) - email String @unique - name String? - - posts Post[] +model Product { + id String @id @default(cuid()) + slug String + name String + description String + price Decimal + reviews Review[] } -model Post { - id Int @id @default(autoincrement()) - title String - published Boolean @default(true) - content String? - - authorId Int? - author User? @relation(fields: [authorId], references: [id]) +model Review { + id String @id @default(cuid()) + body String + stars Int + product Product @relation(fields: [productId], references: [id], onDelete: Cascade) + productId String } ``` @@ -132,6 +146,8 @@ model Post { +The above example uses a Zod schema to validate and parse data provided in a query at runtime before a record is written to the database. + ## Input validation with a custom validation function Here's an example using [Superstruct](https://github.com/ianstormtaylor/superstruct) to validate that the data needed to signup a new user is correct: From 73618b7c0c6adcce7451404c08f71364d82504cc Mon Sep 17 00:00:00 2001 From: ruheni Date: Wed, 10 May 2023 12:10:58 +0200 Subject: [PATCH 08/11] chore: fix casing Headings should use sentence and not title casing #2740 --- .../02-prisma-client/063-excluding-fields.mdx | 2 +- .../02-prisma-client/064-custom-models.mdx | 19 ++++++++----------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/content/200-concepts/100-components/02-prisma-client/063-excluding-fields.mdx b/content/200-concepts/100-components/02-prisma-client/063-excluding-fields.mdx index 705bf6efdb..2bb5efff6b 100644 --- a/content/200-concepts/100-components/02-prisma-client/063-excluding-fields.mdx +++ b/content/200-concepts/100-components/02-prisma-client/063-excluding-fields.mdx @@ -65,7 +65,7 @@ In the TypeScript example, we've provided two generics: `User` and `Key`. The `K These generics flow through the logic, returning a `User` that omits the list of `Key`s provided. -## Going Further +## Going further - Learn how you can move the `exclude` function into [a custom model](/concepts/components/prisma-client/custom-models). - There's an [outstanding feature request](https://github.com/prisma/prisma/issues/5042) to add exclude support natively to the Prisma Client. If you'd like to see that happen, make sure to upvote that issue and share your use case! diff --git a/content/200-concepts/100-components/02-prisma-client/064-custom-models.mdx b/content/200-concepts/100-components/02-prisma-client/064-custom-models.mdx index 1cc267be51..0fd28a3b41 100644 --- a/content/200-concepts/100-components/02-prisma-client/064-custom-models.mdx +++ b/content/200-concepts/100-components/02-prisma-client/064-custom-models.mdx @@ -20,7 +20,7 @@ As your application grows, you may find the need to group related logic together The following example demonstrates how to create a Prisma Client extension that adds a `signUp` and `findManyByDomain` methods to a User model. -, ,]}> +,]}> @@ -53,16 +53,13 @@ const prisma = new PrismaClient().$extends({ }, }, }) -``` - - - - -```ts -await prisma.user.signUp('user2@example2.com', 's3cret') +async function main() { + // Example usage + await prisma.user.signUp('user2@example2.com', 's3cret') -await prisma.user.findManyByDomain('example2.com') + await prisma.user.findManyByDomain('example2.com') +} ``` @@ -97,7 +94,7 @@ model Password { -## Wrap a Prisma Model in a Class +## Wrap a model in a class In the example below, you'll see how you can wrap the `user` model in the Prisma Client within a `Users` class. @@ -137,7 +134,7 @@ Note that in the example above, you're only exposing a `signup` method from Pris This approach works well when you have a large application and you want to intentionally limit what your models can do. -## Extending Prisma Client +## Extending Prisma Client model object But what if you don't want to hide existing functionality but still want to group custom functions together? In this case, you can use `Object.assign` to extend Prisma Client without limiting its functionality: From 1990a4fee7bb131a55a65ef3c965c18345d4ffbc Mon Sep 17 00:00:00 2001 From: Alex Ruheni <33921841+ruheni@users.noreply.github.com> Date: Fri, 12 May 2023 15:54:25 +0100 Subject: [PATCH 09/11] Update content/200-concepts/100-components/02-prisma-client/053-middleware/200-logging-middleware.mdx Co-authored-by: Jon Harrell <4829245+jharrell@users.noreply.github.com> --- .../02-prisma-client/053-middleware/200-logging-middleware.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/200-concepts/100-components/02-prisma-client/053-middleware/200-logging-middleware.mdx b/content/200-concepts/100-components/02-prisma-client/053-middleware/200-logging-middleware.mdx index 9902d4e4cd..145fa01447 100644 --- a/content/200-concepts/100-components/02-prisma-client/053-middleware/200-logging-middleware.mdx +++ b/content/200-concepts/100-components/02-prisma-client/053-middleware/200-logging-middleware.mdx @@ -87,4 +87,4 @@ enum Role { ## Going further -You can also use [Prisma Client extensions](/concepts/components/prisma-client/client-extensions) to log the time it takes to perform a query. You can also find a functional example in [this GitHub repository](https://github.com/prisma/prisma-client-extensions/tree/main/query-logging). +You can also use [Prisma Client extensions](/concepts/components/prisma-client/client-extensions) to log the time it takes to perform a query. A functional example can be found in [this GitHub repository](https://github.com/prisma/prisma-client-extensions/tree/main/query-logging). From cd769f547a1ade82bbe89d1180ca32b510b535ad Mon Sep 17 00:00:00 2001 From: ruheni Date: Mon, 15 May 2023 14:27:03 +0200 Subject: [PATCH 10/11] chore: move around component --- .../02-prisma-client/062-computed-fields.mdx | 78 ++++++------------- 1 file changed, 23 insertions(+), 55 deletions(-) diff --git a/content/200-concepts/100-components/02-prisma-client/062-computed-fields.mdx b/content/200-concepts/100-components/02-prisma-client/062-computed-fields.mdx index f10124f2af..37746bea17 100644 --- a/content/200-concepts/100-components/02-prisma-client/062-computed-fields.mdx +++ b/content/200-concepts/100-components/02-prisma-client/062-computed-fields.mdx @@ -20,7 +20,11 @@ The following example illustrates how to create a [Prisma Client extension](/con -```tsx + + + + +```ts import { PrismaClient } from '@prisma/client' const prisma = new PrismaClient().$extends({ @@ -40,14 +44,29 @@ async function main() { /** * Example query containing the `fullName` computed field in the response */ - const user = await prisma.user.findMany({ - take: 5, - }) + const user = await prisma.user.findFirst() } main() ``` + + + +```js no-copy +{ + id: 'clgzkgy2j00004tc6iwu3gkzu', + firstName: 'Aurelia', + lastName: 'Schneider', + email: 'Jalen_Berge40@hotmail.com', + fullName: 'Aurelia Schneider', +} +``` + + + + + @@ -86,57 +105,6 @@ model Post { -The query above would return a result with the following structure: - - - - - - -```js no-copy -;[ - { - id: 'clgzkgy2j00004tc6iwu3gkzu', - firstName: 'Aurelia', - lastName: 'Schneider', - email: 'Jalen_Berge40@hotmail.com', - fullName: 'Aurelia Schneider', - }, - { - id: 'clgzkgy4p00024tc6up79cdmp', - firstName: 'Agustina', - lastName: 'Langworth', - email: 'Manley16@hotmail.com', - fullName: 'Agustina Langworth', - }, - { - id: 'clgzkgy6h00044tc6lt3y4e44', - firstName: 'Nannie', - lastName: 'Dibbert', - email: 'Kole.Morissette95@hotmail.com', - fullName: 'Nannie Dibbert', - }, - { - id: 'clgzkgy8800064tc6c222ujuy', - firstName: 'Zoey', - lastName: 'Lubowitz', - email: 'Laurianne_Pollich72@gmail.com', - fullName: 'Zoey Lubowitz', - }, - { - id: 'clgzkgya400084tc6i3oxvsk2', - firstName: 'Arch', - lastName: 'Ferry', - email: 'Nikki_Lang@yahoo.com', - fullName: 'Arch Ferry', - }, -] -``` - - - - - The computed fields are type-safe and can return anything from a concatenated value to complex objects or functions that can act as an instance method for your models. ## Using a computation function From 10c8147223255fe7d96886c07596e19d031c04fc Mon Sep 17 00:00:00 2001 From: Alex Ruheni <33921841+ruheni@users.noreply.github.com> Date: Mon, 15 May 2023 14:28:26 +0100 Subject: [PATCH 11/11] Update cSpell.json --- cSpell.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cSpell.json b/cSpell.json index 243085084f..6579087e2f 100644 --- a/cSpell.json +++ b/cSpell.json @@ -51,7 +51,7 @@ "Nuxt", "Sveltekit", "Pothos", - "backoff" + "backoff", "Replibyte", "Snaplet" ],