From ebb9559be6c1f6690e1887bafcae24d98675c864 Mon Sep 17 00:00:00 2001
From: ymc9 <104139426+ymc9@users.noreply.github.com>
Date: Tue, 14 Mar 2023 15:55:42 +0800
Subject: [PATCH 1/6] docs: open-api plugin & fastify adapter

---
 docs/reference/error-handling.md              |   2 +-
 docs/reference/plugins/_category_.yml         |   4 +-
 docs/reference/plugins/openapi.md             | 130 ++++++++++++++++++
 docs/reference/server-adapters/_category_.yml |   7 +
 .../server-adapters/_error-handling.md        |  29 ++++
 docs/reference/server-adapters/express.mdx    |   8 ++
 docs/reference/server-adapters/fastify.mdx    | 101 ++++++++++++++
 .../next.md => server-adapters/next.mdx}      |  47 ++-----
 docs/reference/zmodel-language.md             |  16 +++
 9 files changed, 308 insertions(+), 36 deletions(-)
 create mode 100644 docs/reference/plugins/openapi.md
 create mode 100644 docs/reference/server-adapters/_category_.yml
 create mode 100644 docs/reference/server-adapters/_error-handling.md
 create mode 100644 docs/reference/server-adapters/express.mdx
 create mode 100644 docs/reference/server-adapters/fastify.mdx
 rename docs/reference/{plugins/next.md => server-adapters/next.mdx} (55%)

diff --git a/docs/reference/error-handling.md b/docs/reference/error-handling.md
index 8988ac65..2bfc0d5f 100644
--- a/docs/reference/error-handling.md
+++ b/docs/reference/error-handling.md
@@ -1,6 +1,6 @@
 ---
 description: Error handling
-sidebar_position: 5
+sidebar_position: 6
 ---
 
 # Error Handling
diff --git a/docs/reference/plugins/_category_.yml b/docs/reference/plugins/_category_.yml
index cd14fd79..a2d8b377 100644
--- a/docs/reference/plugins/_category_.yml
+++ b/docs/reference/plugins/_category_.yml
@@ -1,7 +1,7 @@
 position: 4
-label: Plugins & Packages
+label: Plugins
 collapsible: true
 collapsed: true
 link:
     type: generated-index
-    title: Plugins & Packages
+    title: Plugins
diff --git a/docs/reference/plugins/openapi.md b/docs/reference/plugins/openapi.md
new file mode 100644
index 00000000..480900b8
--- /dev/null
+++ b/docs/reference/plugins/openapi.md
@@ -0,0 +1,130 @@
+---
+description: Plugin for generating OpenAPI 3.0 specifications
+sidebar_position: 4
+---
+
+# @zenstackhq/openapi
+
+The `@zenstackhq/openapi` generates an [OpenAPI 3.0](https://spec.openapis.org/oas/v3.0.3) specification based on your ZModel schema. The output is a plain YAML or JSON file that can be used with tools like [Swagger UI](https://swagger.io/tools/swagger-ui/). You can also merge it with other OpenAPI specifications before serving it to your users.
+
+## Options
+
+| Name    | Type   | Description                                   | Required | Default                |
+| ------- | ------ | --------------------------------------------- | -------- | ---------------------- |
+| output  | String | Output file path (with suffix .yaml or .json) | Yes      |                        |
+| title   | String | API title                                     | No       | ZenStack Generated API |
+| version | String | API version                                   | No       | 1.0.0                  |
+
+## Attributes
+
+-   `@@openapi.meta`
+
+    ```prisma
+        attribute @@openapi.meta(_ meta: Object)
+    ```
+
+    Provide metadata for a data model for generating OpenAPI specification. The input is an object containing customized configuration for each model and each CRUD operation. You can use to override per-operation-level Http method, API endpoint path, description, summary and tags. Currently there's no type checking for the structure, but we'll add that in the near future.
+
+    For example:
+
+    ```prisma
+        model User {
+            id String @id
+            email String @unique
+
+            @@openapi.meta({
+                findMany: {
+                    description: 'Find users matching the given conditions'
+                },
+                delete: {
+                    method: 'put',
+                    path: 'dodelete',
+                    description: 'Delete a unique user',
+                    summary: 'Delete a user yeah yeah',
+                    tags: ['delete', 'user']
+                },
+            })
+        }
+    ```
+
+-   `@@openapi.ignore`
+
+    ```prisma
+    attribute @@openapi.ignore()
+    ```
+
+    Mark a data model to be ignored when generating OpenAPI specification.
+
+## Example
+
+```prisma title='/schema.zmodel'
+datasource db {
+    provider = 'sqlite'
+    url = 'file:./dev.db'
+}
+
+plugin openapi {
+    provider = '@zenstackhq/openapi'
+    output = './openapi.yaml'
+    title = 'My awesome API'
+    version = '0.5.0'
+}
+```
+
+## Generated API paths
+
+The generated API paths have 1:1 mapping with PrismaClient's CRUD methods. For `GET`/`DELETE` operations, the query args is passed as JSON-stringified text in the query parameter `q`. For `POST`/`PATCH`/`PUT` operations, the query args is passed as `application/json` in the request body.
+
+Check [here](https://editor.swagger.io/?url=https://gist.githubusercontent.com/ymc9/9c48bfbc9a0853ceb43497c3228c342b/raw/e264e4b04cb2d7c98ef02b64a84531c77a196d7a/blogging.openapi.yaml) for the generated specification for a blogging app.
+
+-   **[model]/findMany**
+
+    _Default Http method:_ `GET`
+
+-   **[model]/findUnique**
+
+    _Default Http method:_ `GET`
+
+-   **[model]/findFirst**
+
+    _Default Http method:_ `GET`
+
+-   **[model]/count**
+
+    _Default Http method:_ `GET`
+
+-   **[model]/aggregate**
+
+    _Default Http method:_ `GET`
+
+-   **[model]/groupBy**
+
+    _Default Http method:_ `GET`
+
+-   **[model]/create**
+
+    _Default Http method:_ `POST`
+
+-   **[model]/createMany**
+
+    _Default Http method:_ `POST`
+
+-   **[model]/update**
+
+    _Default Http method:_ `PATCH` or `PUT`
+
+-   **[model]/updateMany**
+
+    _Default Http method:_ `PATCH` or `PUT`
+
+-   **[model]/upsert**
+
+    _Default Http method:_ `POST`
+
+-   **[model]/delete**
+
+    _Default Http method:_ `DELETE`
+
+-   **[model]/deleteMany**
+
+    _Default Http method:_ `DELETE`
diff --git a/docs/reference/server-adapters/_category_.yml b/docs/reference/server-adapters/_category_.yml
new file mode 100644
index 00000000..21ccbbc0
--- /dev/null
+++ b/docs/reference/server-adapters/_category_.yml
@@ -0,0 +1,7 @@
+position: 5
+label: Server Adapters
+collapsible: true
+collapsed: true
+link:
+    type: generated-index
+    title: Server Adapters
diff --git a/docs/reference/server-adapters/_error-handling.md b/docs/reference/server-adapters/_error-handling.md
new file mode 100644
index 00000000..12e41953
--- /dev/null
+++ b/docs/reference/server-adapters/_error-handling.md
@@ -0,0 +1,29 @@
+### Error Handling
+
+The request handler catches [Prisma errors](https://www.prisma.io/docs/reference/api-reference/error-reference) and turns them into responses with proper HTTP status and body.
+
+-   `PrismaClientKnownRequestError` with code `P2004` is mapped to HTTP 403 to indicate the request is denied due to lack of permission.
+-   Other `PrismaClientKnownRequestError`, `PrismaClientUnknownRequestError` and `PrismaClientValidationError` are mapped to HTTP 400.
+-   None-prisma errors are mapped to HTTP 500.
+
+Error body:
+
+```ts
+{
+    // true for Prisma errors
+    prisma?: boolean;
+
+    // true for PrismaClientKnownRequestError with code 'P2004'
+    rejectedByPolicy?: boolean;
+
+    // original Prisma error code, available for PrismaClientKnownRequestError
+    code?: string;
+
+    // error message
+    message: string;
+
+    // extra reason about why a failure happened (e.g., 'RESULT_NOT_READABLE' indicates
+    // a mutation succeeded but the result cannot be read back due to access policy)
+    reason?: string;
+}
+```
diff --git a/docs/reference/server-adapters/express.mdx b/docs/reference/server-adapters/express.mdx
new file mode 100644
index 00000000..14e21a83
--- /dev/null
+++ b/docs/reference/server-adapters/express.mdx
@@ -0,0 +1,8 @@
+---
+description: Adapter for integrating with Express.js
+sidebar_position: 3
+---
+
+# 🚧 Express.js
+
+Coming soon.
diff --git a/docs/reference/server-adapters/fastify.mdx b/docs/reference/server-adapters/fastify.mdx
new file mode 100644
index 00000000..11015b58
--- /dev/null
+++ b/docs/reference/server-adapters/fastify.mdx
@@ -0,0 +1,101 @@
+---
+description: Adapter for integrating with Fastify
+sidebar_position: 2
+---
+
+import ErrorHandling from './_error-handling.md';
+
+# Fastify
+
+The `@zenstackhq/server/fastify` module provides a quick way to install OpenAPI endpoints onto a [Fastify](https://www.fastify.io/) project for exposing database CRUD. Combined with ZenStack's power of enhancing Prisma with access policies, it's surprisingly simple to achieve a secure data backend without manually coding it.
+
+### Mounting the APIs
+
+You can integrate ZenStack into your project with the `ZenStackFastifyPlugin` [fastify plugin](https://www.fastify.io/docs/latest/Reference/Plugins/):
+
+```ts
+import { withPreset } from '@zenstackhq/runtime';
+import { ZenStackFastifyPlugin } from '@zenstackhq/server/fastify';
+import { prisma } from './db.ts';
+import { getSessionUser } from './auth.ts';
+
+const server = fastify();
+
+// serve OpenAPI at /api/openapi
+server.register(ZenStackFastifyPlugin, {
+    prefix: '/api/openapi',
+    // getSessionUser extracts the current session user from the request, its
+    // implementation depends on your auth solution
+    getPrisma: (request) => withPresets(prisma, { user: getSessionUser(request) }),
+});
+```
+
+The fastify plugin takes two options to initialize:
+
+-   prefix
+
+    The API path to install the OpenAPI CRUD endpoints.
+
+-   getPrisma
+
+    ```ts
+    (request: FastifyRequest, reply: FastifyReply) => unknown | Promise<unknown>;
+    ```
+
+    A callback for getting a PrismaClient instance for talking to the database. Usually you'll use an enhanced instance created with ZenStack's [`withPresets`](/docs/reference/runtime-api#withpresets) or [`withPolicy`](/docs/reference/runtime-api#withpolicy) APIs to ensure access policies are enforced.
+
+### Using the APIs
+
+The APIs mounted by `ZenStackFastifyPlugin` conforms to the OpenAPI V3 spec, and provides a path for each model and database operation combination, as listed [here](/docs/reference/plugins/openapi#generated-api-paths).
+
+You can use any HTTP client to consume the APIs. Here's some samples using [node-fetch](https://github.com/node-fetch/node-fetch):
+
+```ts
+const base = 'http://localhost:8080/api/openapi';
+
+// create a user with two posts
+const r = await fetch(`${base}/user/create`, {
+    method: 'POST',
+    headers: { 'Content-Type': 'application/json' },
+    body: JSON.stringify({
+        include: { posts: true },
+        data: {
+            email: 'user1@abc.com',
+            posts: {
+                create: [{ title: 'Post 1' }, { title: 'Post 2' }],
+            },
+        },
+    }),
+});
+
+console.log(await r.json());
+```
+
+Output:
+
+```json
+{
+    "id": "clf7y7k840000vhs3iej7skve",
+    "email": "user1@abc.com",
+    "zenstack_guard": true,
+    "zenstack_transaction": null,
+    "posts": [
+        {
+            "id": "clf7y7k840001vhs377njs07l",
+            "createdAt": "2023-03-14T07:45:04.036Z",
+            "updatedAt": "2023-03-14T07:45:04.036Z",
+            "title": "Post 1",
+            "authorId": "clf7y7k840000vhs3iej7skve"
+        },
+        {
+            "id": "clf7y7k840002vhs3w11iitmi",
+            "createdAt": "2023-03-14T07:45:04.036Z",
+            "updatedAt": "2023-03-14T07:45:04.036Z",
+            "title": "Post 2",
+            "authorId": "clf7y7k840000vhs3iej7skve"
+        }
+    ]
+}
+```
+
+<ErrorHandling />
diff --git a/docs/reference/plugins/next.md b/docs/reference/server-adapters/next.mdx
similarity index 55%
rename from docs/reference/plugins/next.md
rename to docs/reference/server-adapters/next.mdx
index d11fd865..30e74f0c 100644
--- a/docs/reference/plugins/next.md
+++ b/docs/reference/server-adapters/next.mdx
@@ -1,11 +1,19 @@
 ---
-description: Package for integrating with Next.js
-sidebar_position: 3
+description: Adapter for integrating with Next.js
+sidebar_position: 1
 ---
 
-# @zenstackhq/next
+import ErrorHandling from './_error-handling.md';
 
-The `@zenstackhq/next` provides a quick way to install API endpoints onto a Next.js project for exposing database CRUD. Combined with ZenStack's power of enhancing Prisma with access policies, it's surprisingly simple to achieve a secure data backend without manually coding it.
+# Next.js
+
+The `@zenstackhq/next` package provides a quick way to install API endpoints onto a [Next.js](https://nextjs.org/) project for exposing database CRUD. Combined with ZenStack's power of enhancing Prisma with access policies, it's surprisingly simple to achieve a secure data backend without manually coding it.
+
+:::caution
+
+Moving forward, server adapter implementations will be consolidated to `@zenstackhq/server` package. This package (`@zenstackhq/next`) will be deprecated in the future, but will continue being maintained for a few releases.
+
+:::
 
 ### Exports
 
@@ -20,10 +28,7 @@ export type RequestHandlerOptions = {
     /**
      * Callback method for getting a Prisma instance for the given request/response pair.
      */
-    getPrisma: (
-        req: NextApiRequest,
-        res: NextApiResponse
-    ) => Promise<unknown> | unknown;
+    getPrisma: (req: NextApiRequest, res: NextApiResponse) => Promise<unknown> | unknown;
 
     /**
      * Whether to use superjson for serialization/deserialization. Defaults to true.
@@ -63,28 +68,4 @@ export default requestHandler({ getPrisma });
 
 The APIs can be easily consumed using the [`@zenstackhq/react`](./react) plugin.
 
-### Error Handling
-
-The request handler catches [Prisma errors](https://www.prisma.io/docs/reference/api-reference/error-reference) and turns them into responses with proper HTTP status and body.
-
--   `PrismaClientKnownRequestError` with code `P2004` is mapped to HTTP 403 to indicate the request is denied due to lack of permission.
--   Other `PrismaClientKnownRequestError`, `PrismaClientUnknownRequestError` and `PrismaClientValidationError` are mapped to HTTP 400.
--   None-prisma errors are mapped to HTTP 500.
-
-Error body:
-
-```ts
-{
-    // true for Prisma errors
-    prisma?: boolean;
-
-    // true for PrismaClientKnownRequestError with code 'P2004'
-    rejectedByPolicy?: boolean;
-
-    // original Prisma error code, available for PrismaClientKnownRequestError
-    code?: string;
-
-    // error message
-    message: string;
-}
-```
+<ErrorHandling />
diff --git a/docs/reference/zmodel-language.md b/docs/reference/zmodel-language.md
index 7d80f1d6..fbc3ff43 100644
--- a/docs/reference/zmodel-language.md
+++ b/docs/reference/zmodel-language.md
@@ -533,6 +533,14 @@ You can find a list of predefined attribute functions [here](#predefined-attribu
 
     Automatically stores the time when a record was last updated.
 
+-   `@ignore`
+
+    ```prisma
+        attribute @ignore()
+    ```
+
+    Exclude a field from the Prisma Client (for example, a field that you do not want Prisma users to update).
+
 -   `@password`
 
     ```prisma
@@ -667,6 +675,14 @@ You can find a list of predefined attribute functions [here](#predefined-attribu
     | ---- | -------------------------------------------------------- |
     | name | The name of the underlying table or enum in the database |
 
+-   `@@ignore`
+
+    ```prisma
+        attribute @@ignore()
+    ```
+
+    Exclude a model from the Prisma Client (for example, a model that you do not want Prisma users to update).
+
 -   `@@allow`
 
     ```prisma

From d2a693c6db2915eabcfdc99ae473d1791b6d26cf Mon Sep 17 00:00:00 2001
From: ymc9 <104139426+ymc9@users.noreply.github.com>
Date: Tue, 14 Mar 2023 16:00:47 +0800
Subject: [PATCH 2/6] fix links

---
 docs/reference/plugins/react.md         | 2 +-
 docs/reference/server-adapters/next.mdx | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/docs/reference/plugins/react.md b/docs/reference/plugins/react.md
index 3cff8c69..ab0db72c 100644
--- a/docs/reference/plugins/react.md
+++ b/docs/reference/plugins/react.md
@@ -5,7 +5,7 @@ sidebar_position: 1
 
 # @zenstackhq/react
 
-The `@zenstack/react` plugin generates React hooks that call into the CRUD services provided by packages like [`@zenstack/next`](./next).
+The `@zenstack/react` plugin generates React hooks that call into the CRUD services provided by packages like [`@zenstack/next`](/docs/reference/server-adapters/next).
 
 The hooks syntactically mirror the APIs of a standard Prisma client, including the function names and shapes of parameters (hooks directly use types generated by Prisma). At runtime, the hooks use [SWR](https://swr.vercel.app/) to query and mutate so that you can enjoy its features like caching, automatic revalidation, etc.
 
diff --git a/docs/reference/server-adapters/next.mdx b/docs/reference/server-adapters/next.mdx
index 30e74f0c..a48cb58e 100644
--- a/docs/reference/server-adapters/next.mdx
+++ b/docs/reference/server-adapters/next.mdx
@@ -66,6 +66,6 @@ function getPrisma(req: NextApiRequest, res: NextApiResponse) {
 export default requestHandler({ getPrisma });
 ```
 
-The APIs can be easily consumed using the [`@zenstackhq/react`](./react) plugin.
+The APIs can be easily consumed using the [`@zenstackhq/react`](/docs/reference/plugins/react) plugin.
 
 <ErrorHandling />

From 460b2a9c86245dde0cc1de0f9f77f9be2726ab8e Mon Sep 17 00:00:00 2001
From: ymc9 <104139426+ymc9@users.noreply.github.com>
Date: Tue, 14 Mar 2023 16:04:06 +0800
Subject: [PATCH 3/6] fix formatting

---
 docs/reference/plugins/openapi.md | 34 +++++++++++++++----------------
 1 file changed, 17 insertions(+), 17 deletions(-)

diff --git a/docs/reference/plugins/openapi.md b/docs/reference/plugins/openapi.md
index 480900b8..86bbfbf8 100644
--- a/docs/reference/plugins/openapi.md
+++ b/docs/reference/plugins/openapi.md
@@ -28,23 +28,23 @@ The `@zenstackhq/openapi` generates an [OpenAPI 3.0](https://spec.openapis.org/o
     For example:
 
     ```prisma
-        model User {
-            id String @id
-            email String @unique
-
-            @@openapi.meta({
-                findMany: {
-                    description: 'Find users matching the given conditions'
-                },
-                delete: {
-                    method: 'put',
-                    path: 'dodelete',
-                    description: 'Delete a unique user',
-                    summary: 'Delete a user yeah yeah',
-                    tags: ['delete', 'user']
-                },
-            })
-        }
+    model User {
+        id String @id
+        email String @unique
+
+        @@openapi.meta({
+            findMany: {
+                description: 'Find users matching the given conditions'
+            },
+            delete: {
+                method: 'put',
+                path: 'dodelete',
+                description: 'Delete a unique user',
+                summary: 'Delete a user yeah yeah',
+                tags: ['delete', 'user']
+            },
+        })
+    }
     ```
 
 -   `@@openapi.ignore`

From 0d6a9ab591ecf38b9a526b6cd9d7c0c827532d7a Mon Sep 17 00:00:00 2001
From: ymc9 <104139426+ymc9@users.noreply.github.com>
Date: Tue, 14 Mar 2023 18:16:37 +0800
Subject: [PATCH 4/6] add faq

---
 docs/advanced/_category_.yml |  2 +-
 docs/faq.md                  | 39 ++++++++++++++++++++++++++++++++++++
 2 files changed, 40 insertions(+), 1 deletion(-)
 create mode 100644 docs/faq.md

diff --git a/docs/advanced/_category_.yml b/docs/advanced/_category_.yml
index 8803a241..4203d81b 100644
--- a/docs/advanced/_category_.yml
+++ b/docs/advanced/_category_.yml
@@ -1,4 +1,4 @@
-position: 5
+position: 100
 label: 'Advanced'
 collapsible: true
 collapsed: true
diff --git a/docs/faq.md b/docs/faq.md
new file mode 100644
index 00000000..ba18c03b
--- /dev/null
+++ b/docs/faq.md
@@ -0,0 +1,39 @@
+---
+description: ZenStack FAQ.
+
+slug: /faq
+sidebar_label: FAQ
+sidebar_position: 5
+---
+
+# 🙋🏻 FAQ
+
+### Is ZenStack implemented with Postgres RLS (row-level-security)?
+
+No, it is not implemented with Postgres RLS. Instead, ZenStack applies access control rules by injecting into Prisma's query arguments and (in certain scenarios) conducting post-query filtering. The choice brings several benefits:
+
+1. Database agnostic: ZenStack works with any database that Prisma supports.
+1. No need to create migrations when changing access control rules.
+1. A significantly simpler syntax for access control rules compared to SQL RLS.
+1. Colocating access control rules with the data model.
+
+### Is ZenStack framework-agnostic?
+
+Yes, ZenStack is framework-agnostic. The core of ZenStack's runtime is a transparent proxy wrapping a PrismaClient, so it can be used in any framework that can run Prisma. ZenStack already provides adapters for popular frameworks like [Next.js](/docs/reference/server-adapters/next) and [Fastify](/docs/reference/server-adapters/fastify), and more are being made. It's also easy to write your own adapters.
+
+### How to use ZenStack in a monorepo setup?
+
+To use ZenStack in a monorepo setup (e.g., pnpm workspace), run `zenstack init` in the package where `schema.prisma` resides (usually also where the global `prisma` instance is exported). There's no need to install ZenStack-related packages at the workspace level.
+
+### Is ZModel fully compatible with Prisma Schema?
+
+The ZModel language is designed to be a superset of Prisma Schema. The major extension that ZModel made to Prisma schema today are:
+
+-   [Custom attributes](/docs/reference/zmodel-language#custom-attributes-and-functions)
+-   [Plugins](/docs/category/plugins)
+
+Using a separate DSL gives us the flexibility to add more extensions in the future; however, the goal is to maintain the "superset" positioning, i.e., every valid `schema.prisma` is a valid `schema.zmodel`.
+
+In practice, you may run into problems that the `schema.prisma` generated by ZenStack triggers validation errors in `prisma` CLI. This is because Prisma CLI has many validation rules, some quite subtle. We try to replicate those rules in `zenstack` CLI, but it's more of a best-effort approach. Fortunately, the errors reported by `prisma` CLI usually give pretty good hints on what to change in `schema.zmodel`.
+
+We will continue improving the parity between ZModel and Prisma Schema regarding validation rules.

From 2f552de61b1174e6568a7ffbf310c34b51feeda0 Mon Sep 17 00:00:00 2001
From: ymc9 <104139426+ymc9@users.noreply.github.com>
Date: Tue, 14 Mar 2023 18:19:20 +0800
Subject: [PATCH 5/6] update limitations

---
 docs/reference/limitations.md | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/docs/reference/limitations.md b/docs/reference/limitations.md
index 0b85be87..88c695b7 100644
--- a/docs/reference/limitations.md
+++ b/docs/reference/limitations.md
@@ -12,3 +12,7 @@ This section lists the current limitations of ZenStack.
 [Sequential operations transaction](https://www.prisma.io/docs/concepts/components/prisma-client/transactions#sequential-prisma-client-operations) is not supported by enhanced Prisma clients yet.
 
 As a workaround, use [interactive transactions](https://www.prisma.io/docs/concepts/components/prisma-client/transactions#interactive-transactions) instead.
+
+### MongoDB is not supported
+
+Right now, the focus of this project is SQL databases, and there's no plan to support MongoDB in the near future.

From e4dd42eb401bb947fb2120fa4553ede6a5331277 Mon Sep 17 00:00:00 2001
From: ymc9 <104139426+ymc9@users.noreply.github.com>
Date: Thu, 16 Mar 2023 10:36:16 +0800
Subject: [PATCH 6/6] docs: express.js documentation

---
 docs/reference/plugins/openapi.md             |  2 +-
 docs/reference/plugins/prisma.md              | 25 ++++++
 docs/reference/plugins/react.md               |  2 +-
 docs/reference/plugins/trpc.md                |  2 +-
 docs/reference/plugins/zod.md                 | 89 +++++++++++++++++++
 .../reference/server-adapters/_api-request.md | 47 ++++++++++
 .../server-adapters/_common-options.md        |  9 ++
 docs/reference/server-adapters/express.mdx    | 45 +++++++++-
 docs/reference/server-adapters/fastify.mdx    | 66 ++------------
 9 files changed, 223 insertions(+), 64 deletions(-)
 create mode 100644 docs/reference/plugins/prisma.md
 create mode 100644 docs/reference/plugins/zod.md
 create mode 100644 docs/reference/server-adapters/_api-request.md
 create mode 100644 docs/reference/server-adapters/_common-options.md

diff --git a/docs/reference/plugins/openapi.md b/docs/reference/plugins/openapi.md
index 86bbfbf8..8a2f59ec 100644
--- a/docs/reference/plugins/openapi.md
+++ b/docs/reference/plugins/openapi.md
@@ -1,6 +1,6 @@
 ---
 description: Plugin for generating OpenAPI 3.0 specifications
-sidebar_position: 4
+sidebar_position: 5
 ---
 
 # @zenstackhq/openapi
diff --git a/docs/reference/plugins/prisma.md b/docs/reference/plugins/prisma.md
new file mode 100644
index 00000000..36b8ba9d
--- /dev/null
+++ b/docs/reference/plugins/prisma.md
@@ -0,0 +1,25 @@
+---
+description: Built-in plugin for generating Prisma schema
+sidebar_position: 1
+---
+
+# @core/prisma
+
+The `@core/prisma` plugin generates a Prisma schema from ZModel. This plugin is always automatically included when `zenstack generate` is run. You only need to add it to your ZModel if you want to customize its options.
+
+## Options
+
+| Name           | Type    | Description                                       | Required | Default                |
+| -------------- | ------- | ------------------------------------------------- | -------- | ---------------------- |
+| output         | String  | Output file path                                  | No       | ./prisma/schema.prisma |
+| generateClient | Boolean | Whether to run `prisma generate` after generation | No       | true                   |
+
+## Example
+
+```prisma title='/schema.zmodel'
+plugin prisma {
+    provider = '@core/prisma'
+    output = './src/prisma/schema.prisma'
+    generateClient = false
+}
+```
diff --git a/docs/reference/plugins/react.md b/docs/reference/plugins/react.md
index ab0db72c..6ac27a83 100644
--- a/docs/reference/plugins/react.md
+++ b/docs/reference/plugins/react.md
@@ -1,6 +1,6 @@
 ---
 description: Plugin for generating React data query hooks
-sidebar_position: 1
+sidebar_position: 3
 ---
 
 # @zenstackhq/react
diff --git a/docs/reference/plugins/trpc.md b/docs/reference/plugins/trpc.md
index 2516fecb..bdb900ac 100644
--- a/docs/reference/plugins/trpc.md
+++ b/docs/reference/plugins/trpc.md
@@ -1,6 +1,6 @@
 ---
 description: Plugin for generating tRPC data query routes
-sidebar_position: 2
+sidebar_position: 4
 ---
 
 # @zenstackhq/trpc
diff --git a/docs/reference/plugins/zod.md b/docs/reference/plugins/zod.md
new file mode 100644
index 00000000..4435c961
--- /dev/null
+++ b/docs/reference/plugins/zod.md
@@ -0,0 +1,89 @@
+---
+description: Built-in plugin for generating Zod schemas for validating CRUD input arguments
+sidebar_position: 2
+---
+
+# @core/zod
+
+The `@core/zod` plugin generates [Zod](https://github.com/colinhacks/zod) schemas for input arguments of Prisma CRUD operations.
+
+The hooks syntactically mirror the APIs of a standard Prisma client, including the function names and shapes of parameters (hooks directly use types generated by Prisma). At runtime, the hooks use [SWR](https://swr.vercel.app/) to query and mutate so that you can enjoy its features like caching, automatic revalidation, etc.
+
+## Options
+
+| Name   | Type   | Description      | Required |
+| ------ | ------ | ---------------- | -------- |
+| output | String | Output directory | Yes      |
+
+## Example
+
+Here's an example with a blogging app:
+
+```prisma title='/schema.zmodel'
+plugin reactHooks {
+  provider = '@zenstackhq/react'
+  output = "./src/lib/hooks"
+}
+
+model User {
+  id            String    @id @default(cuid())
+  email         String
+  posts         Post[]
+
+  // everyone can signup, and user profile is also publicly readable
+  @@allow('create,read', true)
+}
+
+model Post {
+  id        String @id @default(cuid())
+  title     String
+  published Boolean @default(false)
+  author    User @relation(fields: [authorId], references: [id])
+  authorId  String
+
+  // author has full access
+  @@allow('all', auth() == author)
+
+  // logged-in users can view published posts
+  @@allow('read', auth() != null && published)
+}
+```
+
+```tsx title='/src/components/posts.tsx'
+import type { Post } from '@prisma/client';
+import { usePost } from '../lib/hooks';
+
+// post list component
+const Posts = ({ userId }: { userId: string }) => {
+    // Post crud hooks
+    const { findMany, create } = usePost();
+
+    // list all posts that're visible to the current user, together with their authors
+    const { data: posts } = findMany({
+        include: { author: true },
+        orderBy: { createdAt: 'desc' },
+    });
+
+    async function onCreatePost() {
+        await create({
+            data: {
+                title: 'My awesome post',
+                authorId: userId,
+            },
+        });
+    }
+
+    return (
+        <>
+            <button onClick={onCreatePost}>Create</button>
+            <ul>
+                {posts?.map((post) => (
+                    <li key={post.id}>
+                        {post.title} by {post.author.email}
+                    </li>
+                ))}
+            </ul>
+        </>
+    );
+};
+```
diff --git a/docs/reference/server-adapters/_api-request.md b/docs/reference/server-adapters/_api-request.md
new file mode 100644
index 00000000..096d35a5
--- /dev/null
+++ b/docs/reference/server-adapters/_api-request.md
@@ -0,0 +1,47 @@
+You can use any HTTP client to consume the APIs. Here's some samples using [node-fetch](https://github.com/node-fetch/node-fetch):
+
+```ts
+const base = 'http://localhost:8080/api/openapi';
+
+// create a user with two posts
+const r = await fetch(`${base}/user/create`, {
+    method: 'POST',
+    headers: { 'Content-Type': 'application/json' },
+    body: JSON.stringify({
+        include: { posts: true },
+        data: {
+            email: 'user1@abc.com',
+            posts: {
+                create: [{ title: 'Post 1' }, { title: 'Post 2' }],
+            },
+        },
+    }),
+});
+
+console.log(await r.json());
+```
+
+Output:
+
+```json
+{
+    "id": "clf7y7k840000vhs3iej7skve",
+    "email": "user1@abc.com",
+    "posts": [
+        {
+            "id": "clf7y7k840001vhs377njs07l",
+            "createdAt": "2023-03-14T07:45:04.036Z",
+            "updatedAt": "2023-03-14T07:45:04.036Z",
+            "title": "Post 1",
+            "authorId": "clf7y7k840000vhs3iej7skve"
+        },
+        {
+            "id": "clf7y7k840002vhs3w11iitmi",
+            "createdAt": "2023-03-14T07:45:04.036Z",
+            "updatedAt": "2023-03-14T07:45:04.036Z",
+            "title": "Post 2",
+            "authorId": "clf7y7k840000vhs3iej7skve"
+        }
+    ]
+}
+```
diff --git a/docs/reference/server-adapters/_common-options.md b/docs/reference/server-adapters/_common-options.md
new file mode 100644
index 00000000..2b5cbb38
--- /dev/null
+++ b/docs/reference/server-adapters/_common-options.md
@@ -0,0 +1,9 @@
+-   getPrisma: `(request: FastifyRequest, reply: FastifyReply) => unknown | Promise<unknown>`
+
+    A callback for getting a PrismaClient instance for talking to the database. Usually you'll use an enhanced instance created with ZenStack's [`withPresets`](/docs/reference/runtime-api#withpresets) or [`withPolicy`](/docs/reference/runtime-api#withpolicy) APIs to ensure access policies are enforced.
+
+-   zodSchemas: `ModelZodSchema | boolean | undefined`
+
+    Provides the Zod schemas used for validating CRUD request input. The Zod schemas can be generated with the `@core/zod` plugin. Pass `true` for this option to load the schemas from the default location. If you configured `@core/zod` plugin to output to a custom location, you can load the schemas explicitly and pass the loaded module to this option.
+
+    Not passing this option or passing in `undefined` disables input validation.
diff --git a/docs/reference/server-adapters/express.mdx b/docs/reference/server-adapters/express.mdx
index 14e21a83..833b02a7 100644
--- a/docs/reference/server-adapters/express.mdx
+++ b/docs/reference/server-adapters/express.mdx
@@ -3,6 +3,47 @@ description: Adapter for integrating with Express.js
 sidebar_position: 3
 ---
 
-# 🚧 Express.js
+import ErrorHandling from './_error-handling.md';
+import ApiRequest from './_api-request.md';
+import CommonOptions from './_common-options.md';
 
-Coming soon.
+# Express.js
+
+The `@zenstackhq/server/express` module provides a quick way to install OpenAPI endpoints onto a [Express.js](https://expressjs.com/) project for exposing database CRUD. Combined with ZenStack's power of enhancing Prisma with access policies, it's surprisingly simple to achieve a secure data backend without manually coding it.
+
+### Mounting the APIs
+
+You can integrate ZenStack into your project with the `ZenStackFastifyPlugin` [fastify plugin](https://www.fastify.io/docs/latest/Reference/Plugins/):
+
+```ts
+import { PrismaClient } from '@prisma/client';
+import { withPresets } from '@zenstackhq/runtime';
+import { ZenStackMiddleware } from '@zenstackhq/server/express';
+import express from 'express';
+
+const prisma = new PrismaClient();
+const app = express();
+
+app.use(express.json());
+
+app.use(
+    '/api/openapi',
+    ZenStackMiddleware({
+        // getSessionUser extracts the current session user from the request, its
+        // implementation depends on your auth solution
+        getPrisma: (request) => withPresets(prisma, { user: getSessionUser(request) }),
+    })
+);
+```
+
+The Express.js adapter takes the following options to initialize:
+
+<CommonOptions />
+
+### Using the APIs
+
+The APIs mounted by `ZenStackFastifyPlugin` conforms to the OpenAPI V3 spec, and provides a path for each model and database operation combination, as listed [here](/docs/reference/plugins/openapi#generated-api-paths).
+
+<ApiRequest />
+
+<ErrorHandling />
diff --git a/docs/reference/server-adapters/fastify.mdx b/docs/reference/server-adapters/fastify.mdx
index 11015b58..053fa320 100644
--- a/docs/reference/server-adapters/fastify.mdx
+++ b/docs/reference/server-adapters/fastify.mdx
@@ -4,6 +4,8 @@ sidebar_position: 2
 ---
 
 import ErrorHandling from './_error-handling.md';
+import ApiRequest from './_api-request.md';
+import CommonOptions from './_common-options.md';
 
 # Fastify
 
@@ -14,7 +16,7 @@ The `@zenstackhq/server/fastify` module provides a quick way to install OpenAPI
 You can integrate ZenStack into your project with the `ZenStackFastifyPlugin` [fastify plugin](https://www.fastify.io/docs/latest/Reference/Plugins/):
 
 ```ts
-import { withPreset } from '@zenstackhq/runtime';
+import { withPresets } from '@zenstackhq/runtime';
 import { ZenStackFastifyPlugin } from '@zenstackhq/server/fastify';
 import { prisma } from './db.ts';
 import { getSessionUser } from './auth.ts';
@@ -30,72 +32,18 @@ server.register(ZenStackFastifyPlugin, {
 });
 ```
 
-The fastify plugin takes two options to initialize:
+The Fastify adapter takes the following options to initialize:
 
--   prefix
+-   prefix: `string`
 
     The API path to install the OpenAPI CRUD endpoints.
 
--   getPrisma
-
-    ```ts
-    (request: FastifyRequest, reply: FastifyReply) => unknown | Promise<unknown>;
-    ```
-
-    A callback for getting a PrismaClient instance for talking to the database. Usually you'll use an enhanced instance created with ZenStack's [`withPresets`](/docs/reference/runtime-api#withpresets) or [`withPolicy`](/docs/reference/runtime-api#withpolicy) APIs to ensure access policies are enforced.
+<CommonOptions />
 
 ### Using the APIs
 
 The APIs mounted by `ZenStackFastifyPlugin` conforms to the OpenAPI V3 spec, and provides a path for each model and database operation combination, as listed [here](/docs/reference/plugins/openapi#generated-api-paths).
 
-You can use any HTTP client to consume the APIs. Here's some samples using [node-fetch](https://github.com/node-fetch/node-fetch):
-
-```ts
-const base = 'http://localhost:8080/api/openapi';
-
-// create a user with two posts
-const r = await fetch(`${base}/user/create`, {
-    method: 'POST',
-    headers: { 'Content-Type': 'application/json' },
-    body: JSON.stringify({
-        include: { posts: true },
-        data: {
-            email: 'user1@abc.com',
-            posts: {
-                create: [{ title: 'Post 1' }, { title: 'Post 2' }],
-            },
-        },
-    }),
-});
-
-console.log(await r.json());
-```
-
-Output:
-
-```json
-{
-    "id": "clf7y7k840000vhs3iej7skve",
-    "email": "user1@abc.com",
-    "zenstack_guard": true,
-    "zenstack_transaction": null,
-    "posts": [
-        {
-            "id": "clf7y7k840001vhs377njs07l",
-            "createdAt": "2023-03-14T07:45:04.036Z",
-            "updatedAt": "2023-03-14T07:45:04.036Z",
-            "title": "Post 1",
-            "authorId": "clf7y7k840000vhs3iej7skve"
-        },
-        {
-            "id": "clf7y7k840002vhs3w11iitmi",
-            "createdAt": "2023-03-14T07:45:04.036Z",
-            "updatedAt": "2023-03-14T07:45:04.036Z",
-            "title": "Post 2",
-            "authorId": "clf7y7k840000vhs3iej7skve"
-        }
-    ]
-}
-```
+<ApiRequest />
 
 <ErrorHandling />
