diff --git a/.gitignore b/.gitignore
index b2d6de30..3651560a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,3 +18,5 @@
 npm-debug.log*
 yarn-debug.log*
 yarn-error.log*
+
+.idea
\ No newline at end of file
diff --git a/blog/adapt-to-edge/cover.png b/blog/adapt-to-edge/cover.png
new file mode 100644
index 00000000..68a3c804
Binary files /dev/null and b/blog/adapt-to-edge/cover.png differ
diff --git a/blog/adapt-to-edge/index.md b/blog/adapt-to-edge/index.md
new file mode 100644
index 00000000..e38bf0ff
--- /dev/null
+++ b/blog/adapt-to-edge/index.md
@@ -0,0 +1,104 @@
+---
+title: "Adapting ZenStack to the Edge: Our Struggles and Learnings"
+description: This post introduces the nature of edge runtime, and the common problems that you'll face when adapting a project to run on it.
+tags: [edge,vercel,cloudflare]
+authors: yiming
+date: 2024-04-19
+image: ./cover.png
+---
+
+# Adapting ZenStack to the Edge: Our Struggles and Learnings
+
+![Cover Image](cover.png)
+
+Edge has been a buzzword for web development for a while, but the reality is the ecosystem support is still lagging, many tools and libraries are not ready for it yet. We've recently been working on adapting [ZenStack](https://zenstack.dev) to the edge runtime. As we're wrapping up the work, it's time to share our struggles and learnings with you.
+
+<!--truncate-->
+
+## What is Edge Runtime?
+
+In the context of JavaScript development, edge runtime is a kind of runtime environment that allows code to run closer to the user's location, typically at the "edge" of the network. It can potentially improve the performance of web applications by reducing latency and increasing the speed of content delivery.
+
+For developers, the main mind-warping aspect of edge runtime is that it's a server-side environment, but it offers browser-like APIs. It means you can't enjoy the full power of Node.js or use arbitrary npm packages there. Most hurdles in adapting a project to edge runtime are about going around these limitations. You can get a feeling of what APIs are supported by skimming through [this specification from WinterCG](https://common-min-api.proposal.wintercg.org/). 
+
+## The Hurdles and Solutions
+
+### CommonJS Support
+
+My initial worry was that to run in an edge runtime, your code has to be compiled into ESM. To my surprise, that's not the case. Although the edge runtime itself is not Node.js compatible and doesn't support the `require` API, apparently, the tooling of the hosting environments (like Vercel and Cloudflare) can handle CommonJS modules just fine. This saved us from the immediate trouble of converting our codebase to ESM, although we should do it anyway to keep up with the future trend.
+
+### Loading Modules Dynamically
+
+Being able to use CommonJS doesn't mean there are no restrictions. One of the biggest hurdles we met was that `require` a module from a dynamically calculated path is not supported. We do that in ZenStack for several reasons. For example, to deal with differences between multiple versions of a peer dependency (Prisma ORM specifically), we detect its version and adjust the module load path accordingly. It'll result in errors like:
+
+```plaintext
+Error: Dynamic require of "[Module Name]" is not supported.
+```
+
+Fortunately, ZenStack has a compiler that generates JavaScript modules (from a DSL) loaded at runtime. We could turn the runtime detection into compile-time and generate the correct module import code for the specific project setup (Prisma version). That way, the module load path is static at runtime and works well with edge runtime.
+
+### Node APIs
+
+Edge runtime is not Node.js compatible, and this is an intentional trade-off made for a lighter footprint and better security. You can avoid using Node-specific APIs in your code, but it can be a harder problem to solve if some of your essential dependencies are using them.
+
+For example, ZenStack relies on [bcryptjs](https://github.com/dcodeIO/bcrypt.js) to hash passwords, which uses Node's `crypto` module. Ironically, `crypto` API is available in the edge runtime, but it's provided as a built-in, meaning you can't explicitly `require` it. To work around the problem, we have to let our users add a "browser" entry into "package.json" to tell Vercel's bundler to ignore the package during resolution:
+
+```json
+{
+  ...
+  "browser": {
+    "crypto": false
+  }
+}
+```
+
+It's ugly, but it solves the problem.
+
+Another problem with `bcryptjs` is its async `hash` function uses the `process.nextTick` Node API to yield control to the event loop for better performance. Cloudflare Worker's runtime [provides a shim](https://developers.cloudflare.com/workers/runtime-apis/nodejs/process/) for the API, but unfortunately, Vercel doesn't have it. We have to [detect if we're running on Vercel](https://edge-runtime.vercel.app/features/available-apis#addressing-the-runtime) and fall back to the synchronous `hashSync` function.
+
+```ts
+const hashFunc = typeof EdgeRuntime === 'string' 
+    ? require('bcryptjs').hashSync 
+    : require('bcryptjs').hash;
+```
+
+### Vendor Maturity and Consistency
+
+If you've read to this point, you've probably already noticed that different edge environments behave differently. Yes, there is a specification about the common APIs to support, but many details remain unspecified, leaving vendors to make their own decisions. We've only tested Vercel and Cloudflare so far and have no idea how other vendors like Netlify Edge or AWS Lambda@Edge may break in other ways 🤔.
+
+So far, the experience working with Cloudflare has been smoother than Vercel (or, more accurately, Next.js?). We've run into numerous issues with Vercel without an actionable error message. For example, the reason for the following is still unknown:
+
+```plaintext
+Compiler edge-server unexpectedly exited with code: null and signal: SIGTERM
+```
+
+### A Bonus Trap
+
+After getting everything running locally with a Next.js project targeting edge runtime, we excitedly deployed it to Vercel. And it worked, well, sometimes ... and other times the APIs on edge randomly return HTTP 405, 500, and 504 with incomprehensible errors like: 
+
+```plaintext
+[GET ] /api/... reason=INTERNAL_EDGE_FUNCTION_INVOCATION_FAILED, status=500, upstream_status=500, user_error=false
+[POST] /api/... reason=EDGE_FUNCTION_INVOCATION_TIMEOUT, status=504, user_error=true
+```
+
+We created a Vercel ticket, communicated back and forth with their support for a few days, and couldn't identify the cause. Until I accidentally found [this piece of comment](https://github.com/prisma/prisma/issues/20566#issuecomment-2021594203) from Prisma's dev lead [@janpio](https://github.com/janpio):
+
+```
+> I also want to know if this way of instantiating a Prisma Client on each function call
+> is the best approach, as in other app components I'm using a global prisma client as 
+> from the docs
+
+Yes, this is unfortunately a limitation of the edge runtimes. Connections can only be used
+in the request where they were created, if you try to reuse them this might initially even
+work - but as soon as one worker or environment is called to handle multiple requests at
+the same time (which can happen in these environments) your worker will crash with an
+exception.
+```
+
+Right. As how you usually do in a Node.js application, I had a global `PrismaClient` singleton. After changing to create a new instance for each request, the random errors are gone, and everything works perfectly. 
+
+I still don't know why using a global instance causes such bizarre errors. Please leave a comment if you do, and I'll really appreciate it. The world is amazing - if you try hard enough, you can find a solution to pretty much any problem by some odd coincidence.
+
+## Now That You Can. Should You?
+
+Many excellent posts already analyze whether you should run your API on the edge, and I don't need to repeat them. Just be sure to do the research and make first-hand performance measurements before making a final decision to have your API talk to a database from the edge. [This reflection](https://twitter.com/leeerob/status/1780705942734331983) from [@leeerob](https://twitter.com/leeerob) is a very interesting one to read.
diff --git a/blog/ai-agent/cover.png b/blog/ai-agent/cover.png
new file mode 100644
index 00000000..bbb1028c
Binary files /dev/null and b/blog/ai-agent/cover.png differ
diff --git a/blog/ai-agent/index.md b/blog/ai-agent/index.md
new file mode 100644
index 00000000..a328284d
--- /dev/null
+++ b/blog/ai-agent/index.md
@@ -0,0 +1,320 @@
+---
+title: How to Build AI Agents to Enhance  SaaS With Minimal Code and Effort
+description: A thought experiment on the roles AI can play in software development.
+tags: [ai, saas, agent, authorization]
+authors: jiasheng
+date: 2025-05-19
+image: ./cover.png
+---
+
+# How to Build AI Agents to Enhance SaaS With Minimal Code and Effort
+
+![Cover Image](cover.png)
+
+## Is SaaS Really Dead?
+
+Several months ago,  the internet was abuzz with the Microsoft CEO Satya Nadella saying, “SaaS is dead.”   
+
+The conversation started with a question from Bill Gurley about whether Satya was worried that newer startups are building applications with an AI-first approach, which could obfuscate traditional infrastructure like Excel or CRM.  Here is Satya’s response:
+
+<!--truncate-->
+
+>  I think, the notion that business applications exist, that's probably where they'll all collapse, right in the agent era because if you think about it, right, they are essentially **CRUD databases with a bunch of business logic. The business logic is all going to these agents**, and these agents are going to be multi repo CRUD
+> 
+
+Some people might assume he suggested that AI agents could or would replace SaaS. However, if you watch the entire podcast, you'll understand that he's actually discussing how AI agents will transform SaaS rather than replace it: 
+
+<iframe width="560" height="315" src="https://www.youtube.com/embed/9NtsnzRFJ_o?si=8jAuPYCPYzg1j7-4&amp;start=2770" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
+
+
+How? Let me share an example from my experience. At my previous company, we used Trello for project management. I was impressed by its clean and efficient UX design. 
+
+![trello](https://github.com/user-attachments/assets/72af641a-25a5-4554-a07f-a6fe8bced243)
+
+However, one drawback that consistently bothered me was the lack of flexibility in performing queries.  For instance:
+
+- How many cards were done last week?
+- Who has the most incomplete cards?
+- Which list has the most incomplete cards?
+
+Trello doesn't provide a direct way to show the answer; you'll need to do the math manually.  I understand that offering such flexibility from a UI design perspective is challenging,  yet each encounter often brings a sigh of frustration. 😮‍💨
+
+Balancing flexibility and simplicity is a core challenge for SaaS. This is where an AI agent can play a role. The most straightforward solution is to introduce a chatbot that can easily provide answers to all the questions mentioned, without altering any existing features.
+
+## Challenge of Building an AI Chatbot
+
+We are all aware of the hallucinations that AI causes.  But there is another level of hallucination: 
+
+AI makes many things seem easily accomplished on the surface, but you will see the challenge under the iceberg when it comes to production. 
+
+My previous company tried to integrate a chat agent into their SaaS product, and was struggling with those challenges:
+
+1. **LLM Failed to Generate the Correct API Call**
+    
+    The initial plan was to have the LLM generate an API call directly based on user input. However, probably because the existing API is not well designed,  LLM often struggles to interpret and generate the correct parameters. Sometimes, it even calls the wrong API.
+    
+2. **The Complexity of Transforming the LLM Result**
+    
+    Since the initial plan fails, they ask LLM to create the intermediate structured query object, then use code to convert this object into an actual database query.  The good thing is that it is the code that makes the final call; the bad thing is that the code can become highly complex as it needs to account for all possible cases the LLM might produce.
+    
+    One critical issue that needs to be addressed is authorization. You have to scrutinize and ensure the generated query doesn’t break the authorization rule of the current system, which can sometimes be quite complex. Any oversight could lead to significant security breaches, potentially disastrous for a B2B SaaS.
+    
+
+The fundamental issue seems to be that the current infrastructure is not friendly enough for AI agents, as Bill questioned Satya. So, what type of infrastructure would be suitable for AI?
+
+## Schema-First Fit AI-First
+
+Among all the discussions regarding Satya’s statement:
+
+> **They are essentially CRUD databases with a bunch of business logic**.The business logic is all going to these agents.
+>
+
+Many people focus solely on the business logic that will be handled by the agent, often neglecting the essential prerequisite: **the CRUD database**. In other words, AI must accurately and precisely translate the business logic to the CRUD operation to ensure success. This is where the challenges arise, as illustrated in the previous example.  It seems there is a missing layer that focuses on 'what' rather than 'how' to make AI better work: **a schema**.  AI excels at declarative schemas over imperative code.   Therefore, if we could make the CRUD database a well-designed schema for AI to manipulate,  that could provide a robust foundation for the mission to be done. 
+
+The ZenStack schema-first toolkit is well-suited for the task. The core part is a DSL that unifies data modeling and access control, two essential parts of CRUD databases.  Here is an example of what a simple blog post looks like:
+
+```jsx
+enum Role {
+    USER
+    ADMIN
+}
+
+model Post {
+    id        String  @id @default(cuid())
+    title     String
+    published Boolean @default(false)
+    author    User    @relation(fields: [authorId], references: [id])
+    authorId  String  @default(auth().id)
+
+    @@allow('all', auth() == author)
+    @@allow('read', auth() != null && published )
+    @@allow('read', auth().role == 'ADMIN')
+}
+
+model User {
+    id       String  @id @default(cuid())
+    email    String? @unique
+    password String  @password @omit
+    role     Role    @default(USER)
+    posts    Post[]
+
+    @@allow('create,read', true)
+    @@allow('update,delete', auth() == this)
+}
+```
+
+Based on the schema, ZenStack automatically generates well-structured, type-safe, and authorized CRUD APIs to the database.  Not only can AI understand and manipulate it better with less chance of hallucination, but also developers will be able to build the AI agent on top of it easily.   
+
+I know talk is cheap, so let me show you the code!
+
+## Building an AI Chatbot from Scratch
+
+Let’s build an AI chatbot for a Todo app to address the pain points of Trello earlier. To keep this concise, I'll focus on the key steps. You can find the link to the completed project on GitHub at the end of the post.  Here is the final outcome:
+
+![zenstack-ai-chatbot](https://github.com/user-attachments/assets/abd5750c-ebc4-4fd3-85f1-286c4e2659e7)
+
+Here is the tech stack we are using:
+- [Next.js](https://nextjs.org/) - React framework
+- [ZenStack](https://zenstack.dev/) - Full-stack toolkit with access control
+- [NextAuth](https://next-auth.js.org/) - Authentication for Next.js
+- [AI SDK](https://sdk.vercel.ai/) - AI integration for chat features
+
+### 1. Creating the project
+
+Create a Next.js project with `create-t3-app` with Prisma, NextAuth, and TailwindCSS:
+
+```jsx
+npx create-t3-app@latest --prisma --nextAuth --tailwind --appRouter --CI todo-ai
+```
+
+### 2. Initialize the project for ZenStack
+
+Run the `zenstack` CLI to prepare your project for using ZenStack.
+
+```jsx
+npx zenstack@latest init
+```
+
+Replace the `schema.zmodel` with the below content:
+
+```jsx
+generator client {
+    provider = "prisma-client-js"
+}
+
+datasource db {
+    provider = "sqlite"
+    url      = env("DATABASE_URL")
+}
+
+plugin zod {
+    provider = '@core/zod'
+}
+
+/*
+ * Model for a Todo list
+ */
+model List {
+    id        String   @id @default(uuid())
+    createdAt DateTime @default(now())
+    updatedAt DateTime @updatedAt
+    owner     User     @relation(fields: [ownerId], references: [id], onDelete: Cascade)
+    ownerId   String   @default(auth().id)
+    title     String   @length(1, 100)
+    private   Boolean  @default(false)
+    todos     Todo[]
+    // can be read by owner or space members (only if not private) 
+    @@allow('read', !private)
+
+    // owner can do anything
+    @@allow('all', owner == auth())
+}
+
+/*
+ * Model for a single Todo
+ */
+model Todo {
+    id          String    @id @default(uuid())
+    createdAt   DateTime  @default(now())
+    updatedAt   DateTime  @updatedAt
+    owner       User      @relation(fields: [ownerId], references: [id], onDelete: Cascade)
+    ownerId     String    @default(auth().id)
+    list        List      @relation(fields: [listId], references: [id], onDelete: Cascade)
+    listId      String
+    title       String    @length(1, 100)
+    completedAt DateTime?
+
+    // full access if the parent list is readable
+    @@allow('all', check(list, 'read'))
+}
+
+model User {
+    id       String  @id @default(cuid())
+    name     String?
+    email    String? @unique
+    password String  @password @omit
+    todo     Todo[]
+    list     List[]
+
+    // everyone can signup, and user profile is also publicly readable
+    @@allow('create,read', true)
+    // only the user can update or delete their own profile
+    @@allow('update,delete', auth() == this)
+}
+```
+
+This represents a multi-user Todo app.  Todo list can be private or public; list owners have full control over their list.  If a user can see the list, they can manipulate all the todos under that list.  
+
+### 3.  Implement Signup/Signin
+
+The tasks here are configuring `NextAuth` to use credential-based auth and creating the signup/signin form.  Check out the code for details.
+
+### 4. Implement the chatbot logic with Vercel AI SDK and ZenStack
+
+We will primarily utilize the AI SDK to develop the chatbot. It not only standardizes integration across various LLM providers but also offers a range of hooks for creating chat and generative user interfaces.   With that, building a chatbot with real-time message streaming requires just a few lines of code.  Here is the [official doc](https://ai-sdk.dev/docs/ai-sdk-ui/chatbot).
+
+Simply put, it provides a `userChat` hook for the client and a corresponding server endpoint in `src/app/chat/route.ts`.  I will skip the UI part and focus on implementing the endpoint, which is essentially the complete functionality of this bot.
+
+Every AI agent typically consists of two components: **Tools and Prompts**. As previously discussed, **Tools** in this context refer to the CRUD APIs of the database. So let’s see how it is getting implemented. 
+
+The AI SDK allows Zod Schema to be used to define the parameters of the Tool.  So, have you noticed a Zod plugin defined in the `schema.zmodel` ?
+
+```tsx
+plugin zod {
+    provider = '@core/zod'
+}
+```
+
+This is a ZenStack native plugin that generates Zod schemas for models and input arguments of Prisma CRUD operations.   For instance, it will generate the CRUD schema for every model as below:
+
+```tsx
+declare type TodoInputSchemaType = {
+    findUnique: z.ZodType<Prisma.TodoFindUniqueArgs>;
+    findFirst: z.ZodType<Prisma.TodoFindFirstArgs>;
+    findMany: z.ZodType<Prisma.TodoFindManyArgs>;
+    create: z.ZodType<Prisma.TodoCreateArgs>;
+    createMany: z.ZodType<Prisma.TodoCreateManyArgs>;
+    delete: z.ZodType<Prisma.TodoDeleteArgs>;
+    deleteMany: z.ZodType<Prisma.TodoDeleteManyArgs>;
+    update: z.ZodType<Prisma.TodoUpdateArgs>;
+    updateMany: z.ZodType<Prisma.TodoUpdateManyArgs>;
+    upsert: z.ZodType<Prisma.TodoUpsertArgs>;
+    aggregate: z.ZodType<Prisma.TodoAggregateArgs>;
+    groupBy: z.ZodType<Prisma.TodoGroupByArgs>;
+    count: z.ZodType<Prisma.TodoCountArgs>;
+};
+```
+
+Therefore, all we need to do is convert each CRUD operation to an AI SDK `tool`.  Let’s create a `createToolsFromZodSchema` function iterates all the models:
+
+```tsx
+import prismaInputSchema from "@zenstackhq/runtime/zod/input";
+import { type Tool, tool, zodSchema } from "ai";
+
+async function createToolsFromZodSchema(prisma: PrismaClient) {
+  const tools: Record<string, Tool> = {};
+  const functionNames = ["findMany", "createMany", "deleteMany", "updateMany"];
+  for (const [inputTypeName, functions] of Object.entries(prismaInputSchema)) {
+    // remove the postfix InputSchema from the model name
+    const modelName = inputTypeName.replace("InputSchema", "");
+    for (const [functionName, functionSchema] of Object.entries(
+      functions,
+    ).filter((x) => functionNames.includes(x[0]))) {
+      const functionParameterType = zodSchema(
+        functionSchema as z.ZodObject<z.ZodRawShape>,
+        {
+          useReferences: true,
+        },
+      );
+      tools[`${modelName}_${functionName}`] = tool({
+        description: `Prisma client API '${functionName}' function input argument for model '${modelName}'`,
+        parameters: functionParameterType,
+        execute: async (input: unknown) => {
+          console.log(
+            `Executing ${modelName}.${functionName} with input:`,
+            JSON.stringify(input),
+          );
+          // eslint-disable-next-line
+          return (prisma as any)[modelName][functionName](input);
+        },
+      });
+    }
+  }
+```
+:::info
+To minimize the tool count and alleviate the AI's workload, we offer only four fundamental operations: "findMany," "createMany," "deleteMany," and "updateMany.”
+:::
+
+The `execute` function inside is quite simple, just invoke the corresponding prisma client API function using the passed in `PrismaClient` object.  Here comes the most crucial part,  which is also the most exciting part of ZenStack:
+
+The `PriamClient` object should be the `enhanced` Prisma client that contains the current user identity instead of the regular Prisma client:
+
+```tsx
+export async function POST(req: Request) {
+  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
+  const { messages }: { messages: CoreMessage[] } = await req.json();
+
+  const authObj = await auth();
+  const enhancedPrisma = enhance(db, { user: authObj?.user });
+  const tools = await createToolsFromZodSchema(enhancedPrisma);
+  ...
+}
+```
+
+**The benefit is that no matter what parameters  AI generates for a function,  you never have to worry about unauthorized access.  This is because the access policy defined in the schema will always be injected under the hood before reaching the database.**   
+
+The system prompt is straightforward and general; you can review the code and tailor it to suit your specific requirements.
+
+Thanks to the AI SDK and ZenStack, the entire server implementation of this chatbot was completed in less than 100 lines of code!   Even more impressive, this implementation is completely app-agnostic. In other words, regardless of your app (zmodel), it works seamlessly.  
+
+So if you're a ZenStack user, you now know the best practice for implementing an AI Chatbot. 😉
+
+## Try It for Your Own Application
+
+Here is the final project that you can run directly:
+
+[https://github.com/jiashengguo/zenstack-ai-chatbot](https://github.com/jiashengguo/zenstack-ai-chatbot)
+
+The benefit is that you can easily test this AI chat agent for your own application — all you need to do is customize `schema.zmodel` to fit your application.  
+
+I would love to hear your feedback on it!
diff --git a/blog/ai-programmer/cover.png b/blog/ai-programmer/cover.png
new file mode 100644
index 00000000..33d8a563
Binary files /dev/null and b/blog/ai-programmer/cover.png differ
diff --git a/blog/ai-programmer/index.md b/blog/ai-programmer/index.md
new file mode 100644
index 00000000..883b0a38
--- /dev/null
+++ b/blog/ai-programmer/index.md
@@ -0,0 +1,90 @@
+---
+title: Programmers, Will AI Work For You, With You, or Without You?
+description: A thought experiment on the roles AI can play in software development.
+tags: [ai, programming]
+authors: yiming
+date: 2024-10-14
+image: ./cover.png
+---
+
+# Programmers, Will AI Work For You, With You, or Without You?
+
+![Cover Image](cover.png)
+> **"Programming is dead."**
+
+A friend said so when he heard I was working on dev tools. It was right after LLM’s coding abilities shocked the world. He felt it pointless to continue building tools for human programmers anymore when AI is taking over this profession. You'll never be wrong by predicting without attaching a time frame. Dead when? At least for the time being, the emergence of generative AI has introduced more work for programmers, some more difficult than before.
+
+Whether AI will replace human developers is too big a topic to tackle. However, we can keep an open mind and explore different roles that AI can play in software development. Let's conduct thought experiments to imagine what each means to us.
+<!--truncate-->
+
+## Coding Copilot
+
+We all love GitHub Copilot. There’s absolutely zero learning curve. Your IDE suddenly gets 10x smarter and generates code snippet completions as you type. It’s fast. It’s context-aware. It keeps you in the flow by letting you stay inside the IDE. 
+
+The quality is pretty good. Far from perfect, but the tolerance in this scenario is also pretty high. Who doesn’t enjoy some free code written for? Even if it’s not 100% correct, it can still be a good starting point. Otherwise, you’ll go to StackOverflow and grab something there anyway, won’t you?
+
+A Copilot works **FOR** developers. It’s a tool, but a very smart one. It’s fully at your disposal, and its entire goal is to make you more productive. You, the human programmer, are the pilot. This brings several implications:
+
+1. All prior wisdom generated by humans for humans continues to be valid and important — writing readable code, using design patterns to combat complexity, planning for the future, etc. Eventually, they are used to overcome humans' limited capacity for comprehension, memory, and consistency.
+   
+2. Languages, frameworks, and libraries will continue to be created and evolve as they are today. Made by humans for humans, these tools are the actualization of wisdom mentioned in #1. Contrary to what laymen may expect, the top priority of these tools is not generating powerful and performant applications but lowering programmers' mental load. DX will continue to be one of the most essential topics in the industry.
+   
+3. Following #1 and #2, such AI Copilot must adapt well to whatever tools the human programmer has chosen to use. It must serve him by creating code that not only works but is also "good" by human standards. Preferably, the code should have a consistent style as those manually written.
+
+Copilots like GitHub Copilot, Codeium, and Cursor have proven very useful. However, they are certainly not the kind of disruptive innovation the crowd anticipates.
+
+What's next?
+
+## Buddy Developer
+
+Although routines are the primary unit developers organize and work on their code, they usually think at a higher granularity — feature. Features have business meanings, making them a good fit for planning, communication, and delivery. They’re also the ideal unit for breaking work down and distributing it among team members.
+
+Naturally, AI's more ambitious goal is to tackle feature development directly.
+
+You play a product manager or team leader role in such a setup. The AI works **WITH** you. It takes requirements from you, employs its own thoughts to generate an implementation strategy, and eventually comes up with code that contains the feature and integrates seamlessly into the project's code base. Instead of prompting with code or comments, as when you use Copilot, you prompt your AI buddy with requirements in natural language, optionally complemented by other high-level artifacts like a flow chart.
+
+This poses some very tough challenges. One obvious problem is that humans are terribly incapable of describing things precisely.
+
+> Wife: buy a watermelon on your way home. If you see tomatoes, buy two.
+>
+> Q: how many watermelons should the husband buy if he sees tomatoes?
+> 
+
+Humans are even worse at precisely understanding other people’s thoughts. So when we work with our AI buddy, we can have these desperate struggles:
+
+- Have I explained enough of my needs to the AI?
+- Does the execution plan generated by the AI (most AIs take a "plan-then-write" approach) show that it’ll actually cover everything I need?
+
+Of course, the ultimate guarantee is to review every line of code the AI writes thoroughly, but our expectations should be higher than that. When humans see ambiguity, we resort to common sense. If a pair of communicators share a "common" common sense, they’re likely to be on the same page. That's why a team gets more efficient after working together for an extended period of time. LLMs are already amazingly good at capturing common sense (albeit with a brute-force approach). As they get even better in the future, we can probably have enough confidence that we’re actually talking to a reasonable person.
+
+Another bigger question is whether LLMs are powerful enough to implement features. Today’s LLMs have extremely tight context size limits. Unlike Copilots, which work at the code snippet level, feature-building AIs need a ton of context, often a big portion of the entire code base, and maybe access the code change history to understand why something currently works the way it does. Also, generating large chunks of code is computation-intensive, which can be rather slow and prohibitively expensive.
+
+Of course, we can wait for LLMs to get much stronger or employ techniques like CoT to mitigate the problem, but another parallel solution is probably to adapt our project setup to help AIs do a better job. Use more high-level programming languages (including DSLs), do more declarative coding, make APIs more succinct, and employ more pre-built components so that AIs don’t need to generate from scratch. Basically, just try programming at a higher abstraction level and write less code. Just think about it: how much difference will it make if AI codes against a well-designed ORM API vs. generating SQL directly to access the database? A higher level of abstraction reduces AI’s cognition and generation load, and thus less chance of hallucination. 
+
+The benefits are also mutual. If AI generates higher-level code, you’ll also have a much easier time comprehending it, validating it, and making changes to it as necessary.
+
+Feature-building AI is still in its very early stages. Products like [Cursor Composer](https://youtu.be/V9_RzjqCXP8), [Marblism](https://www.marblism.com/), and [Replit Agent](https://docs.replit.com/replitai/agent) are having a good head start. For such products to succeed, a solid long-term partnership needs to form between humans and AI. It requires finding the right compromise (languages, frameworks, libraries, etc.) that allows both humans and AI to work effectively.
+
+## Fully Autonomous Software Engineer
+
+> We even tried giving Devin real jobs on Upwork and it could do those too!
+— [Introducing Devin, Cognition AI](https://www.cognition.ai/blog/introducing-devin#:~:text=We%20even%20tried%20giving%20Devin%20real%20jobs%20on%20Upwork%20and%20it%20could%20do%20those%20too!)
+> 
+
+I’m not sure how true the story is, as it mentioned no details about what kind of jobs were done, how many times they were done, the success rate, how satisfied the customers were, etc.
+
+The ultimate form of AI-assisted programming will be fully autonomous programming agents. It requires no supervision from professional software engineers, takes requirements directly from non-technical stakeholders, and delivers systems end to end. From a developer’s perspective, the AI works **WITHOUT** us! Or, in more brutal words, it replaces us. Instead of hiring an IT team, a non-tech-savvy company may purchase AI computation power to build and host the business applications they need. 
+
+It can have some very interesting implications:
+
+1. How the application is built can be a complete black box. This is what happens inside an organization when the IT department delivers an app to a business team. The demanding party doesn’t care and can’t understand how it works internally.
+   
+2. Given #1, AI can implement the system as straightforwardly as it sees fit. There’s no more restraint on writing code that humans can understand and change. There’s no need to follow best practices designed to mitigate human weakness. Duplicated code? Not a problem as long as it makes sure when a change is needed, all duplicates are consistently updated. SOLID principles? No more relevant. What humans see as shitty code can be perfectly fine for AI.
+
+If we pursue this direction, it'll make sense to design languages and building blocks that maximize LLM's performance. AI programming and human programming would become two distinct worlds. Is it utopia or dystopia? I’m not sure. Fortunately, after a two-year cooling down, we know today’s AI is still extremely limited. A full-scale replacement of human developers is not coming any time soon.
+
+## Final Thoughts
+
+The future is very uncertain. But what’s certain is that software developers are among the first cohort to benefit from AI. We will also be the first group of people who struggle with it. Anyone who seriously integrated LLM into a product knows how frustrating it is to program against it — it’s restricted, unstable, slow, and hallucinating all the time. It's an API that requires us to think very differently. If old-days APIs are like Newtonian physics, LLMs are quantum mechanics — no more determinism, everything is probabilistic.
+
+It’s too early to worry about being replaced by AI programmers. What’s imminent is to learn how to combat its weakness. Regardless of its immaturity, Generative AI is undoubtedly the most promising key to unlocking an exciting future.
diff --git a/blog/better-auth/cover.png b/blog/better-auth/cover.png
new file mode 100644
index 00000000..f11f8714
Binary files /dev/null and b/blog/better-auth/cover.png differ
diff --git a/blog/better-auth/index.mdx b/blog/better-auth/index.mdx
new file mode 100644
index 00000000..84d5261d
--- /dev/null
+++ b/blog/better-auth/index.mdx
@@ -0,0 +1,571 @@
+---
+title: "When Embedded AuthN Meets Embedded AuthZ - Building Multi-Tenant Apps With Better-Auth and ZenStack"
+description: Better-auth is an emerging open-source TypeScript authentication framework that offers a comprehensive set of features and great extensibility. In this post, we'll explore how to integrate it with ZenStack to build a multi-tenant application with fine-grained access control.
+tags: [auth, better-auth, multi-tenancy]
+authors: yiming
+date: 2024-12-14
+image: ./cover.png
+---
+
+# When Embedded AuthN Meets Embedded AuthZ - Building Multi-Tenant Apps With Better-Auth and ZenStack
+
+![Cover Image](cover.png)
+
+Building a full-fledged multi-tenant application can be very challenging. Besides having a flexible sign-up and sign-in system, you also need to implement several other essential pieces:
+
+- Creating and managing tenants
+- User invitation flow
+- Managing roles and permissions
+- Enforcing data segregation and access control throughout the entire application
+
+It sounds like lots of work, and it indeed is. You may have done this multiple times if you're a veteran SaaS developer.
+
+<!--truncate-->
+
+[Better-auth](https://better-auth.com) is an emerging open-source TypeScript authentication framework that offers a comprehensive set of features and great extensibility. Besides supporting a wide range of identity providers, its powerful plugin system allows you to add new features that contribute extensions across the entire stack - data model, backend API, and frontend hooks. A good example is the [Organization plugin](https://www.better-auth.com/docs/plugins/organization), which sets the foundation for implementing multi-tenant apps with access control.
+
+While better-auth solves the problem of determining a user's identity and roles, [ZenStack](https://zenstack.dev) continues from there and uses such information to control what actions the user can perform on a piece of data. ZenStack is built above [Prisma ORM](https://prisma.io) and extends Prisma's power with flexible access control and automatic CRUD API. Since better-auth has built-in integration with Prisma, the two can make a perfect combination for building secure multi-tenant applications. This post will walk you through the steps of creating one.
+
+## The goal and the stack
+
+The target application we'll build is a Todo List. Its core functionalities are simple: creating lists and managing todos within them. However, the focus will be on the multi-tenancy and access control aspects:
+
+- **Organization management**
+  
+  Users can create organizations and invite others to join. They can manage members and set their roles.
+
+- **Current context**
+  
+  Users can choose an organization to be the active one.
+
+- **Data segregation**
+  
+  Only data within the active organization can be accessed.
+
+- **Role-based access control**
+  
+  - Admin members have full access to all data within their organization.
+  - Regular members have full access to the todo lists they own.
+  - Regular members can view the other members' todo lists and manage their content.
+
+The essential weapons we'll use to build the app are:
+
+- [Next.js](https://nextjs.org/): the full-stack framework
+- [Better-Auth](https://better-auth.com/): user authentication and organization management
+- [Prisma](https://prisma.io): the ORM that we use to talk to the database
+- [ZenStack](https://zenstack.dev): enhancing Prisma with access control and automatic CRUD API
+- [TanStack Query](https://tanstack.com/query/latest): the data fetching/caching library
+
+The benefit of this stack is that everything runs embedded inside Next.js. There are no third-party cloud services or self-hosted ones. The only thing you need is a Next.js hoster and a database provider.
+
+You can find the link to the completed project at the end of the post.
+
+## Base setup
+
+Better-auth's [Next.js Demo](https://github.com/better-auth/better-auth/tree/main/demo/nextjs) provides a great starting point for us, which already includes:
+
+- Authentication configuration
+- User sign-up and sign-in flow
+- Organization plugin for organization management and member invitation flow
+- Dashboard for self-serviced user management and organization management
+- Admin UI for managing users
+
+One major change that we'll make to the demo is switching from Kysely to Prisma as the database client.
+
+```ts title='/lib/auth.ts'
+import { prismaAdapter } from 'better-auth/adapters/prisma';
+
+export const auth = betterAuth({
+  appName: 'Better Auth Demo',
+  database: prismaAdapter(prisma, {
+      provider: 'sqlite',
+  }),
+  ...
+});  
+```
+
+Then, install Prisma packages and generate a schema with the better-auth CLI:
+
+```bash
+npm install -D prisma
+npm install @prisma/client
+npx @better-auth/cli generate
+```
+
+The generated schema file should contain the following models:
+
+- **User**: a registered user
+- **Session**: a user session
+- **Account**: OAuth account (not used)
+- **Verification**: sign-up verification record
+- **Organization**: an organization
+- **Member**: a member of an organization (join table between `User` and `Organization`)
+- **Invitation**: an invitation to join an organization
+
+```mermaid
+erDiagram
+
+  "User" {
+    String id "🗝️"
+    String name 
+    String email 
+    Boolean emailVerified 
+    String image "❓"
+    DateTime createdAt 
+    DateTime updatedAt 
+    String role "❓"
+    Boolean banned "❓"
+    String banReason "❓"
+    DateTime banExpires "❓"
+    }
+  
+
+  "Session" {
+    String id "🗝️"
+    DateTime expiresAt 
+    String token 
+    DateTime createdAt 
+    DateTime updatedAt 
+    String ipAddress "❓"
+    String userAgent "❓"
+    String activeOrganizationId "❓"
+    String impersonatedBy "❓"
+    }
+  
+
+  "Account" {
+    String id "🗝️"
+    String accountId 
+    String providerId 
+    String accessToken "❓"
+    String refreshToken "❓"
+    String idToken "❓"
+    DateTime accessTokenExpiresAt "❓"
+    DateTime refreshTokenExpiresAt "❓"
+    String scope "❓"
+    String password "❓"
+    DateTime createdAt 
+    DateTime updatedAt 
+    }
+  
+
+  "Verification" {
+    String id "🗝️"
+    String identifier 
+    String value 
+    DateTime expiresAt 
+    DateTime createdAt "❓"
+    DateTime updatedAt "❓"
+    }
+  
+
+  "Organization" {
+    String id "🗝️"
+    String name 
+    String slug "❓"
+    String logo "❓"
+    DateTime createdAt 
+    String metadata "❓"
+    }
+  
+
+  "Member" {
+    String id "🗝️"
+    String role 
+    DateTime createdAt 
+    }
+  
+
+  "Invitation" {
+    String id "🗝️"
+    String email 
+    String role "❓"
+    String status 
+    DateTime expiresAt 
+    }
+  
+    "User" o{--}o "Session" : "sessions"
+    "User" o{--}o "Account" : "accounts"
+    "User" o{--}o "Member" : "members"
+    "User" o{--}o "Invitation" : "invitations"
+    "Session" o|--|| "User" : "user"
+    "Account" o|--|| "User" : "user"
+    "Organization" o{--}o "Member" : "members"
+    "Organization" o{--}o "Invitation" : "invitations"
+    "Member" o|--|| "Organization" : "organization"
+    "Member" o|--|| "User" : "user"
+    "Invitation" o|--|| "Organization" : "organization"
+    "Invitation" o|--|| "User" : "user"
+```
+
+The initial dashboard UI looks like:
+
+![Initial UI](./initial-ui.png)
+
+## Setting up ZenStack
+
+In the following sections, we'll use ZenStack to implement the access control requirements. ZenStack uses its own DSL called [ZModel](/docs/the-complete-guide/part1/zmodel) to define data models and access policy rules. ZModel is a superset of the Prisma schema language. The ZenStack CLI can generate a Prisma schema from a ZModel file so that downstream Prisma consumers (like better-auth) will continue to work seamlessly.
+
+Let's initialize the project with ZenStack:
+
+```bash
+npx zenstack@latest init
+```
+
+The command will install the necessary dependencies, and copies the "prisma/schema.prisma" file to "/schema.zmodel". Moving forward, we'll modify "schema.zmodel" and use the ZenStack CLI to regenerate the Prisma schema.
+
+```bash
+npx zenstack generate
+```
+
+## Preparing data models
+
+Better-auth helped us generate the authentication-related data models, leaving us to work on the application-specific ones: Todo List and Todo. As mentioned previously, we should update "schema.zmodel" to define them:
+
+```zmodel title="/schema.zmodel"
+model TodoList {
+  id             String        @id @default(cuid())
+  createdAt      DateTime      @default(now())
+  updatedAt      DateTime      @updatedAt
+  name           String
+  owner          User          @relation(fields: [ownerId], references: [id])
+  ownerId        String
+  organization   Organization? @relation(fields: [organizationId], references: [id])
+  organizationId String?
+  todos          Todo[]
+}
+
+model Todo {
+  id        String   @id @default(cuid())
+  createdAt DateTime @default(now())
+  updatedAt DateTime @updatedAt
+  title     String
+  done      Boolean  @default(false)
+  listId    String
+  list      TodoList @relation(fields: [listId], references: [id])
+}
+```
+
+```mermaid
+erDiagram
+
+  "TodoList" {
+    String id "🗝️"
+    DateTime createdAt 
+    DateTime updatedAt 
+    String name 
+    }
+  
+
+  "Todo" {
+    String id "🗝️"
+    DateTime createdAt 
+    DateTime updatedAt 
+    String title 
+    Boolean done 
+    }
+  
+
+  "User" {
+    String id "🗝️"
+    String name 
+    String email 
+    Boolean emailVerified 
+    String image "❓"
+    DateTime createdAt 
+    DateTime updatedAt 
+    String role "❓"
+    Boolean banned "❓"
+    String banReason "❓"
+    DateTime banExpires "❓"
+    }
+
+  "Organization" {
+    String id "🗝️"
+    String name 
+    String slug "❓"
+    String logo "❓"
+    DateTime createdAt 
+    String metadata "❓"
+    }
+  
+    "TodoList" o|--|| "User" : "owner"
+    "TodoList" o|--|o "Organization" : "organization"
+    "TodoList" o{--}o "Todo" : "todos"
+    "Todo" o|--|| "TodoList" : "list"
+    "User" o{--}o "TodoList" : "todoLists"
+    "Organization" o{--}o "TodoList" : "todoLists"
+```
+
+:::info Why is the "organization" relation optional?
+
+Even with the "Organization" plugin enabled, our app still allows users to work without an active organization (personal mode). A `TodoList` created in personal mode won't have an `organization` associated with it.
+
+:::
+
+Then regenerate Prisma schema and push changes to the database:
+
+```base
+npx zenstack generate
+npx prisma db push
+```
+
+Finally, create a "/lib/db.ts" file to export the Prisma client:
+
+```ts title="/lib/db.ts"
+import { PrismaClient } from "@prisma/client";
+export const prisma = new PrismaClient();
+```
+
+## Mounting automatic CRUD API
+
+ZenStack provides a Next.js server adapter that automatically exposes Prisma-style CRUD APIs. To mount it, install the server adapter package:
+
+```bash
+npm install @zenstackhq/server
+```
+
+, and then create a "/app/api/[...path]/router.ts" file:
+
+```ts title="/app/api/[...path]/router.ts"
+import { prisma } from '@/lib/db';
+import { NextRequestHandler } from '@zenstackhq/server/next';
+
+async function getPrisma() {
+  return prisma;
+}
+
+const handler = NextRequestHandler({ getPrisma, useAppDir: true });
+
+export {
+  handler as DELETE,
+  handler as GET,
+  handler as PATCH,
+  handler as POST,
+  handler as PUT,
+};
+```
+
+This will expose a set of CRUD endpoints like `/api/model/TodoList/findMany`, `/api/model/TodoList/create`, etc. You can find more details [here](https://zenstack.dev/docs/reference/server-adapters/api-handlers/rpc).
+
+Although we can call these APIs with `fetch` directly, a much easier way is to leverage ZenStack's [TanStack Query plugin](https://zenstack.dev/docs/reference/plugins/tanstack-query) to generate client-side hooks.
+
+```bash
+npm install @zenstackhq/tanstack-query
+```
+
+```zmodel title="/schema.zmodel"
+plugin hooks {
+  provider = "@zenstackhq/tanstack-query"
+  target = "react"
+  output = "./hooks/model"
+}
+```
+
+```bash
+npx zenstack generate
+```
+
+You can then enjoy type-safe Prisma-style hooks in the frontend code.
+
+```ts
+import { useFindManyTodoList }  from '@/hooks/model';
+
+export function MyComponent() {
+  const { data: lists, isLoading } = useFindManyTodoList({ include: { owner: true }});
+  ...
+}
+```
+
+## Implementing access control
+
+Now, we can manipulate the database from the frontend through the generated hooks and automatic API. However, the APIs are open to all without any protection, which is obviously not what we want.
+
+The biggest value ZenStack adds above Prisma is access control, which can be implemented directly inside the schema using the `@@allow` and `@@deny` attributes. At runtime, ZenStack lets you create a wrapper around `PrismaClient` (called ***enhanced*** PrismaClient) that automatically enforces these policy rules. Access is rejected by default unless explicitly granted with an `@@allow` rule and not rejected by any `@@deny` rule. When using an enhanced client to access the database, inaccessible records are filtered out during read, and mutations with insufficient permissions are rejected.
+
+In real-world applications, authorization is always connected to authentication: you'll determine a user's access based on his identity and other information (like organization membership, roles, etc.). In our context, we'll use better-auth to retrieve the current user's identity, active organization, and role in the organization and use this information as the "user context" when creating the enhanced `PrismaClient`. Since the auto APIs use the enhanced client, they are also secured.
+
+```ts title="/app/api/model/[...path]/route.ts"
+async function getPrisma() {
+  const reqHeaders = await headers();
+  const sessionResult = await auth.api.getSession({
+    headers: reqHeaders,
+  });
+
+  if (!sessionResult) {
+    // anonymous user, create enhanced client without user context
+    return enhance(prisma);
+  }
+
+  let organizationId: string | undefined = undefined;
+  let organizationRole: string | undefined = undefined;
+  const { session } = sessionResult;
+
+  if (session.activeOrganizationId) {
+    // if there's an active orgId, get the role of the user in the org
+    organizationId = session.activeOrganizationId;
+    const org = await auth.api.getFullOrganization({ headers: reqHeaders });
+    if (org?.members) {
+      const myMember = org.members.find(
+          (m) => m.userId === session.userId
+      );
+      organizationRole = myMember?.role;
+    }
+  }
+
+  // create enhanced client with user context
+  const userContext = {
+    userId: session.userId,
+    organizationId,
+    organizationRole,
+  };
+  return enhance(prisma, { user: userContext });
+}
+```
+
+The user context will be accessible in ZModel policy rules via the special `auth()` function. To get it to work, we'll use a type to define the shape of `auth()`:
+
+```zmodel title="/schema.zmodel"
+type Auth {
+  userId           String  @id
+  organizationId   String?
+  organizationRole String?
+  @@auth
+}
+```
+
+Now, we're ready to write the policy rules. You can find more information about access polices [here](https://zenstack.dev/docs/the-complete-guide/part1/access-policy/).
+
+#### 1. Tenant segregation
+
+```zmodel title="/schema.zmodel"
+model TodoList {
+  ...
+
+  // deny anonymous users
+  @@deny('all', auth() == null)
+
+  // deny access to lists that don't belong to the user's active organization
+  @@deny('all', auth().organizationId != organizationId)
+}
+```
+
+#### 2. Users can only create lists for themselves
+
+```zmodel title="/schema.zmodel"
+model TodoList {
+  ...
+
+  // users can create lists for themselves
+  @@allow('create', auth().userId == ownerId)
+}
+```
+
+#### 3. Owner and admins have full access
+
+By default, better-auth's organization members can have "owner", "admin", or "member" role.
+
+```zmodel title="/schema.zmodel"
+model TodoList {
+  ...
+
+  // full access to: list owner, org owner, and org admins
+  @@allow('all', 
+    auth().userId == ownerId ||
+    auth().organizationRole == 'owner' ||
+    auth().organizationRole == 'admin')
+}
+```
+
+#### 4. Readable to organization members
+
+```zmodel title="/schema.zmodel"
+model TodoList {
+  ...
+
+  // if the list belongs to an org, it's readable to all members
+  @@allow('read', organizationId != null)
+}
+```
+
+#### 5. Owner and organization cannot be changed
+
+You can use `@allow` and `@deny` attributes (note the single `@` sign) to define field-level rules.
+
+```zmodel title="/schema.zmodel"
+model TodoList {
+  ...
+  ownerId        String  @allow('update', false)
+  organizationId String? @allow('update', false)
+}
+```
+
+#### 6. A user as full access to `Todo` if he can read its parent `TodoList`
+
+We've managed to protect the `TodoList` model, and rules for the `Todo` model are yet to be defined. Fortunately, ZenStack allows you to reference relations in policy rules. The `check()` helper allows you to directly delegate permission check to a relation (here `Todo` -> `TodoList`).
+
+```zmodel title="/schema.zmodel"
+model Todo {
+  ...
+
+  // `check()` delegates permission check to a relation
+  @@allow('all', check(list, 'read'))
+}
+```
+
+:::info
+Make sure to rerun `npx zenstack generate` after changing ZModel.
+:::
+
+## Finally, the Todo list UI
+
+With the CRUD APIs secured and frontend hooks generated, implementing the UI for managing `TodoList`s becomes very straightforward. I'm only showing part of the implementation here.
+
+```tsx title="/app/dashboard/todo-lists-card.tsx"
+export default function TodoListsCard() {
+  // Note that you don't need to filter for the current user and the active organization
+  // because the ZModel rules have taken care of it
+  const { data: todoLists } = useFindManyTodoList({
+    orderBy: { createdAt: 'desc' },
+  });
+
+  const { mutateAsync: del, isPending: isDeleting } = useDeleteTodoList();
+
+  async function onDelete(id: string) {
+    await del({ where: { id } });
+  }
+
+  return (
+    <Card>
+      <CardHeader>
+        <CardTitle>Todo List</CardTitle>
+      </CardHeader>
+      <CardContent>
+        <div>
+          {todoLists?.map((list) => (
+            <div key={list.id}>
+              <p>{list.name}</p>
+              <p>{list.createdAt.toLocaleString()}</p>
+              <Button disabled={isDeleting} onClick={() => onDelete(list.id)}>
+                Delete
+              </Button>
+            </div>
+          ))}
+        </div>
+      </CardContent>
+    </Card>
+  );
+}
+```
+
+<div align="center" style={{width: '100%'}}>
+  <iframe width="100%" height="600" src="https://www.youtube.com/embed/atfFZkypRN0?si=lbqKj4zlkx5quhVn" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
+</div>
+
+You can find the fully completed code [here](https://github.com/ymc9/better-auth-zenstack-multitenancy).
+
+## Conclusion
+
+Authentication and authorization are two cornerstones of most applications. They can be especially challenging to build for multi-tenant ones. This post demonstrated how the work can be significantly simplified and streamlined by combining better-auth and ZenStack. The end result is a secure application with great flexibility and little boilerplate code.
+
+Better-auth also supports defining [custom permissions](https://www.better-auth.com/docs/plugins/organization#custom-permissions) for organizations. Although not covered in this post, with some tweaking, you should be able to leverage it to define access policies. That way, you can manage permissions with better-auth's API and have ZenStack enforce them at runtime.
diff --git a/blog/better-auth/initial-ui.png b/blog/better-auth/initial-ui.png
new file mode 100644
index 00000000..61df1370
Binary files /dev/null and b/blog/better-auth/initial-ui.png differ
diff --git a/blog/better-auth/list-ui.gif b/blog/better-auth/list-ui.gif
new file mode 100644
index 00000000..c1620889
Binary files /dev/null and b/blog/better-auth/list-ui.gif differ
diff --git a/blog/build-language/cover.png b/blog/build-language/cover.png
new file mode 100644
index 00000000..8c455eb9
Binary files /dev/null and b/blog/build-language/cover.png differ
diff --git a/blog/build-language/dragon-book.png b/blog/build-language/dragon-book.png
new file mode 100644
index 00000000..affde45f
Binary files /dev/null and b/blog/build-language/dragon-book.png differ
diff --git a/blog/build-language/ide.png b/blog/build-language/ide.png
new file mode 100644
index 00000000..8e6f396a
Binary files /dev/null and b/blog/build-language/ide.png differ
diff --git a/blog/build-language/index.md b/blog/build-language/index.md
new file mode 100644
index 00000000..95c2a0ed
--- /dev/null
+++ b/blog/build-language/index.md
@@ -0,0 +1,266 @@
+---
+title: How Much Work Does It Take to Build a Programming Language?
+description: This post introduces steps to building a new programming language using an example and shows how much modern language engineering tools simplify the process.
+tags: [compiler, language]
+authors: yiming
+date: 2024-01-22
+image: ./cover.png
+---
+
+# How Much Work Does It Take to Build a Programming Language?
+
+![Cover Image](cover.png)
+
+"This book is a classic. Treat it with respect".
+
+The architect of my team said it when handing me [*The Dragon Book*](https://en.wikipedia.org/wiki/Compilers:_Principles,_Techniques,_and_Tools). It was around 15 years ago when I accidentally got into building language compilers in the early stage of my career. Unfortunately, I fell asleep while reading it at night and dropped it badly on the floor. I hope he didn't notice the small dent on the cover when I returned it.
+
+<!--truncate-->
+![The Dragon Book](dragon-book.png)
+
+The book was written in 1986. Building a compiler was extremely challenging back then, and it condensed many arts and techniques in computer science and programming. Almost four decades later, I'm up to the job again. How hard is it now? Let's explore what's involved in creating a language and how much modern tools have simplified it.
+
+## The Target Language
+
+We need to have some concrete language to build to help make sense of things. I always felt real-world examples are much more effective than toys, so I'll use the ZModel language that we're building at [ZenStack](https://zenstack.dev) as an example. It's a DSL used to model database tables and access control rules. To keep the post short, I'm going only to use a small set of features to demonstrate. Our goal will be to compile the following code snippet:
+
+```zmodel
+model User {
+  id Int
+  name String
+  posts Post[]
+}
+
+model Post {
+  id Int
+  title String
+  author User
+  published Boolean
+
+  @@allow('read', published == true)
+}
+```
+
+A few quick notes:
+
+- The model syntax denotes a database table; its fields are mapped to table columns.
+- Models can reference each other to form relations. The `User` and `Post` models form a one-to-many relation in the sample above.
+- The `@@allow` syntax represents access control rules. It takes two arguments: one for access type ("create", "read", "update", "delete", or "all"), and the other a boolean expression indicating if the access should be allowed.
+
+That's it. Let's roll up our sleeves and compile this thing!
+
+>ZModel is a superset of the [Prisma Schema Language](https://www.prisma.io/docs/orm/prisma-schema).
+
+## Creating a Language in Six Steps
+
+### Step 1: From Text to Syntax Tree
+
+The general steps of constructing a compiler haven't changed much over the years. You'll first need a lexer to break the text down to "tokens" and then a parser to construct a stream of tokens into a "parse tree". High-level language construction tools tend to combine these two steps and allow you to go from text to tree in one step.
+
+We used the [Langium](https://langium.org/) OSS toolkit to help us build the language. It's an excellent TypeScript-based language engineering tool that streamlines the entire process of language construction. Langium provides an intuitive DSL for you to define lexing and parsing rules.
+
+> The Langium DSL itself is built with Langium. This kind of recursion is called *bootstrapping* in compiler jargon. The initial version of the compiler has to be written with another language/tool.
+
+Our ZModel language syntax can be formalized as the following:
+
+```ts
+grammar ZModel
+
+entry Schema:
+    (models+=Model)*;
+
+Model:
+    'model' name=ID '{'
+        (fields+=Field)+
+        (rules+=Rule)*
+    '}';
+
+Field:
+    name=ID type=(Type | ModelReference) (isArray?='[' ']')?;
+
+ModelReference:
+    target=[Model];
+
+Type returns string:
+    'Int' | 'String' | 'Boolean';
+
+Rule:
+    '@@allow' '('
+        accessType=STRING ',' condition=Condition
+    ')';
+
+Condition:
+    field=SimpleExpression '==' value=SimpleExpression;
+
+SimpleExpression:
+    FieldReference | Boolean;
+
+FieldReference:
+    target=[Field];
+
+Boolean returns boolean:
+    'true' | 'false';
+
+hidden terminal WS: /\s+/;
+terminal ID: /[_a-zA-Z][\w_]*/;
+terminal STRING: /"(\\.|[^"\\])*"|'(\\.|[^'\\])*'/;
+```
+
+I hope the syntax is intuitive enough to understand. It consists of two parts:
+
+- Lexing rules
+
+    The terminal rules at the bottom are lexing rules, determining how source text should be broken down into tokens. Our simple language only has identifiers (ID) and string (STRING) tokens. White spaces are ignored.
+
+- Parsing rules
+
+    The rest of the rules are parsing rules. They determine how a stream of tokens should be organized into a tree. Parser rules can also contain keywords (e.g., `Int`, `@@allow`) that also participate in the lexing process. In a complex language, you'll likely have recursive parsing rules (e.g., nested expressions) that need special attention to design, but our simple sample doesn't involve that.
+
+With the language rules ready, we can use Langium's API to convert our sample code snippet into the following parse tree:
+
+```mermaid
+graph LR
+  Schema --> user[model: name=User]
+	Schema --> post[model: name=Post]
+	user --> userId[field: name=id, type=Int]
+	user --> userName[field: name=name, type=String]
+	user --> posts[field: name=posts isArray=true] --type--> postRef[modelRef: target=Post]
+	post --> postId[field: name=id, type=Int]
+	post --> postTitle[field: name=title, type=String]
+	post --> published[field: name=published, type=String]
+	post --> author[field: name=author] --type--> userRef[modelRef: target=User]
+	Schema --> rule[rule: accessType='read'] --condition--> condition
+  condition --left--> fieldRef[fieldRef: target=published]
+  condition --right--> boolean[boolean: true]
+```
+
+### Step 2: From Syntax Tree to Linked Tree
+
+The parse tree is tremendously helpful for us to understand the semantics of the source file. However, we often need to go one more step to make it complete. Our ZModel language allows so-called "cross-references." For example, the `User` model's `posts` field references the `Post` model. And the `Post` model references back with its author field. When we traverse the parse tree, if we reach a `ModelReference` node, we'll see it references a name "Post" but can't directly know what it means. You can do an ad-hoc lookup to find the model with a matching name, but a more systematic approach is to do a "linking" pass to resolve all such references and link them to the target nodes. After such linking is done, our parse tree looks like the following (only part of the tree is shown for brevity):
+
+```mermaid
+graph LR
+	user --> userId[field: name=id, type=Int]
+	user --> userName[field: name=name, type=String]
+	user --> posts[field: name=posts isArray=true] --type--> postRef[modelRef: target=Post]
+	post --> postId[field: name=id, type=Int]
+	post --> postTitle[field: name=title, type=String]
+	post --> published[field: name=published, type=String]
+	post --> author[field: name=author] --type--> userRef[modelRef: target=User]
+  postRef -."resolved to".-> post
+  userRef -."resolved to".-> user
+```
+
+Technically speaking, it's a graph rather than a tree now, but by convention, we'll continue calling it a parse tree.
+
+What's nice about Langium is that, in most cases, the tool helps do the linking pass automatically. It follows the nesting hierarchy of parsed nodes and uses it to construct "scopes" to resolve a name when encountered and link it to the appropriate target node. For a complex language, there will be cases where you need to have special resolving behaviors. Langium makes it easy by allowing you to hook into the linking process by custom-implementing several services.
+
+### Step 3. From Linked Tree to Semantic Correctness
+
+The compiler will report and halt if an input source file contains lexer or parser errors.
+
+```ts
+model {
+  id
+  title String
+}
+```
+
+```
+Expecting token of type 'ID' but found `{`. [Ln1, Col7]
+```
+
+However, being free of such errors doesn't mean the code is semantically correct. For example, the following is syntactically valid but semantically wrong. It doesn't make sense to compare `title` with `true`.
+
+```zmodel
+model Post {
+  id Int
+  title String
+  author User
+  published Boolean
+
+  @@allow('read', title == true) // <- the comparison should be invalid
+}
+```
+
+The semantic rules are usually language-specific, and the tools can barely do anything automatically. The way how Langium lets you do it is by providing hooks for validating different node types.
+
+```ts
+export function registerValidationChecks(services: ZModelServices) {
+    const registry = services.validation.ValidationRegistry;
+    const validator = services.validation.ZModelValidator;
+    const checks: ValidationChecks<ZModelAstType> = {
+        SimpleExpression: validator.checkExpression,
+    };
+    registry.register(checks, validator);
+}
+
+export class ZModelValidator {
+    checkExpression(expr: SimpleExpression, accept: ValidationAcceptor): void {
+        if (isFieldReference(expr) && expr.target.ref?.type !== 'Boolean') {
+            accept('error', 'Only boolean fields are allowed in conditions', {
+                node: expr,
+            });
+        }
+    }
+}
+```
+
+We can now get a nice error for the semantic issue:
+
+```
+Only boolean fields are allowed in conditions [Ln 7, Col 19]
+```
+
+Unlike lexing, parsing, and linking, the semantic checking process is not very declarative or systematic. For complex languages, you'll write many rules with imperative code.
+
+![Parsing vs Validation](parsing-vs-validation.png)
+> Image courtesy of [How and Why of Feature Engineering](https://www.slideshare.net/AliceZheng3/the-how-and-why-of-feature-engineering)
+
+### Step 4. Polishing Developer Experience
+
+The bar for creating dev tools is high today. An innovation needs to work great and also feel great to thrive. In terms of language and compiler, DX is mainly about three things:
+
+- IDE support
+
+    Great IDE support - syntax highlighting, formatting, auto-completion, etc. - dramatically lowers the learning curve and improves the developer's quality of life. One thing I love about Langium is its built-in support for [Language Server Protocol](https://en.wikipedia.org/wiki/Language_Server_Protocol). Your parsing rules and validation checks automatically become a decent default LSP implementation, directly working with VSCode and the latest [JetBrains IDEs](https://blog.jetbrains.com/platform/2023/07/lsp-for-plugin-developers/) (with limitations). However, to deliver great in-IDE experiences, you'll still need to do lots of polishing by overriding the default implementation of LSP-related services by Langium.
+
+    ![IDE Experience](ide.png)
+
+- Error reporting
+
+    Your validation logic will generate error messages in many cases, and the precision and helpfulness of the messages will highly determine how fast a developer can comprehend and take action to fix them.
+
+- Debugging
+
+    If your language "runs" (see next section for more), a debugging experience is a must-have. What debugging means depends on the nature of the language. If it's an imperative language involving statements and control flow, it'll require stepping and state inspection. Or, if it's a declarative one, debugging probably means visualizations that help untangle complexities (rules, expressions, etc.).
+
+### Step 5. Making It Useful
+
+Deriving a resolved and error-free parse tree is pretty cool, but it's not very useful by itself. You have a few options to continue from there and generate actual values out of it:
+
+1. Just stop here
+
+    You can stop here, declare the parse tree is the outcome, and let your users decide what to do with it.
+
+2. Transform it into other languages.
+
+    Often, a language will have a "backend" to transform the parse tree to a lower-level language. For example, Java compiler's backend generates JVM byte code. TypeScript's backend generates Javascript code. At ZenStack, we transform ZModel to Prisma Schema Language. Then, the tooling/runtime of the target language can take it as input.
+
+3. Implement a pluggable transformation mechanism.
+
+    You can also implement a plug-in mechanism to let your language users provide their backend transformation. It's a more structural way of doing #1.
+
+4. Build a runtime to execute the parse tree.
+
+    This is the most "complete" route for building a language. You can implement an interpreter to "run" the parsed code. Whatever "run" means is entirely up to you. At ZenStack, besides transforming ZModel to Prisma Schema Language, we also have a runtime that interprets the access control rules to enforce them during data access.
+
+### Step 6. Making People Use It
+
+Congratulations! You can pat yourself on the back now, as you have completed 20% of the work of creating a new language. As with almost all innovation, the most challenging part is always selling it to people - even when it's free. You can call it a day if the language is to be used just by yourself or internally in your team. However, market it hard if it's meant for the public audience. That constitutes the remaining 80% of the work 😄.
+
+## Final Thoughts
+
+Compiler construction feels like an ancient art, given how fast software engineering has evolved in the past decades. However, I still think it's something that a serious developer should try out to gain unique experiences. It very nicely reflects the dualism of programming - aesthetics and pragmatism. An excellent software system usually has an elegant conceptual model, but you'll find many improvisations that don't look too pretty under the surface.
+
+You should build a language because why not?
diff --git a/blog/build-language/parsing-vs-validation.png b/blog/build-language/parsing-vs-validation.png
new file mode 100644
index 00000000..d296f0f9
Binary files /dev/null and b/blog/build-language/parsing-vs-validation.png differ
diff --git a/blog/check-function/cover.jpg b/blog/check-function/cover.jpg
new file mode 100644
index 00000000..108daf5e
Binary files /dev/null and b/blog/check-function/cover.jpg differ
diff --git a/blog/check-function/index.md b/blog/check-function/index.md
new file mode 100644
index 00000000..e7011520
--- /dev/null
+++ b/blog/check-function/index.md
@@ -0,0 +1,224 @@
+---
+title: How the "check" Function Helps Keep Your Policies DRY
+description: This post introduces a typical pattern of access policy duplication in ZenStack schemas, and explains how the new `check` attribute function can help you keep your policies DRY.
+tags: [zenstack]
+authors: yiming
+date: 2024-07-13
+image: ./cover.jpg
+---
+
+import RecursionIssue from './recursion-issue.png';
+
+# How the "check" Function Helps Keep Your Policies DRY
+
+![Cover Image](cover.jpg)
+
+Among ZenStack's features, the most beloved one is the ability to define access control policies inside the data schema. This ensures that your rules are colocated with the source code, always in sync with the data model, and easy to understand. It arguably provides a superior DX to other solutions like hand-coded authorization logic, or Postgres row-level security.
+
+However, as your application grows more complex, you may find yourself repeating the same policy patterns across multiple models. This post explores one typical pattern of such duplication and demonstrates how the new `check()` attribute function can help you keep your policies DRY.
+
+<!--truncate-->
+
+## The parent-child duplication pattern
+
+Consider a simple Todo application with two models: `List` and `Todo`. Each list can have multiple todos. The author of a list has full access to it. A list can also be set as public so anyone can read it. A todo's access control is determined by its containing list: one has the same permissions to a todo as to its parent.
+
+Here's how a ZModel schema for the application might look:
+
+```zmodel
+model List {
+  id       Int @id
+  name     String
+  public   Boolean
+  author   User @relation(fields: [authorId], references: [id])
+  authorId Int
+  todos    Todo[]
+
+  // highlight-start
+  @@allow('all', auth() == author)
+  @@allow('read', public)
+  // highlight-end
+}
+
+model Todo {
+  id     Int @id
+  name   String
+  list   List @relation(fields: [listId], references: [id])
+  listId Int
+
+  // highlight-start
+  @@allow('all', auth() == list.author)
+  @@allow('read', list.public)
+  // highlight-end
+}
+```
+
+As you can easily spot, the access policies for `Todo` are almost identical to those for `List`. The duplication is not only tedious to write but also error-prone to maintain. If you ever need to change the policy, you have to remember to update it in two places. In a real application, the rules will be more complex, and as a result, the duplication will be more severe and can sometimes get several level deep.
+
+The key to the problem is that from the access control point of view, the child model `Todo` simply "follows" the parent model `List`, which is a typical pattern in many applications. We can avoid duplication if we had a way to "delegate" the check of the child model to its parent.
+
+## The `check()` function
+
+The new `check()` attribute function introduced in ZenStack [v2.3](https://github.com/zenstackhq/zenstack/releases/tag/v2.3.0) is designed exactly for such "delegation". The function has the following signature:
+
+```ts
+function check(field: FieldReference, operation String?): Boolean
+```
+
+- `field`
+
+  A relation field to check access for. Must be a non-array field.
+
+- `operation`
+
+  An optional argument indicating the CRUD permission kind to check. If the operation is not provided, it defaults to the operation of the policy rule containing it.
+
+You can use the function in a few different forms:
+
+1. You can explicitly specify the kind of CRUD operation to delegate to:
+
+   ```zmodel
+   model Child {
+     parent Parent
+     @@allow('read', check(parent, 'update'))
+   }
+   ```
+
+2. Or you can omit the operation so it defaults to the current context:
+
+   ```zmodel
+   model Child {
+     ...
+     parent Parent
+
+     @@allow('read', check(parent)) // here the operation is implicitly 'read'
+   }
+   ```
+
+3. You can also delegate "all" operations:
+
+   ```zmodel
+   model Child {
+     ...
+     parent Parent
+
+     @@allow('all', check(parent))
+   }
+   ```
+
+   The above is equivalent to:
+
+   ```zmodel
+   model Child {
+     ...
+     parent Parent
+
+     @@allow('read', check(parent))
+     @@allow('create', check(parent))
+     @@allow('update', check(parent))
+     @@allow('delete', check(parent))
+   }
+   ```
+
+4. Since `check` is just a boolean function, you can freely combine it with other conditions:
+
+   ```zmodel
+   model Child {
+     ...
+     parent Parent
+
+     @@allow('read', check(parent) || auth().status == 'PAID')
+   }
+   ```
+
+## Before and after
+
+With this new weapon in hand, we can easily refactor our Todo schema to eliminate the duplication:
+
+```zmodel
+model List {
+  id       Int @id
+  name     String
+  public   Boolean
+  author   User @relation(fields: [authorId], references: [id])
+  authorId Int
+  todos    Todo[]
+
+  @@allow('all', auth() == author)
+  @@allow('read', public)
+}
+
+model Todo {
+  id     Int @id
+  name   String
+  list   List @relation(fields: [listId], references: [id])
+  listId Int
+
+  // highlight-next-line
+  @@allow('all', check(list))
+}
+```
+
+Pretty neat, isn't it?
+
+## What's next?
+
+Having the `check` function is great for resolving the parent-child duplication pattern. However, there are more related features that can be explored in future versions of ZenStack. Here are some ideas:
+
+### 1. `*-to-many` relations
+
+You've probably already noticed the limitation: the `check` function only works for the `*-to-one` side of a relation. Although I feel it should cover most of the use cases, there might be cases where you want to deal with the "*-to-many" side.
+
+There are two possible ways to add such support:
+
+- A: a new set of check functions: `checkSome`, `checkAll`, `checkNone`, etc.
+
+  ```zmodel
+  model Parent {
+    children Child[]
+
+    @@allow('read', checkSome(children, 'read'))
+  }
+  ```
+
+- B: make it work with [collection predicate expressions](../docs/reference/zmodel-language#collection-predicate-expressions)
+
+  ```zmodel
+
+  model Parent {
+    children Child[]
+
+    // check if at least one child is readable
+    @@allow('read', children?[child, check(child, 'read')])
+  }
+
+  ```
+
+What's your preference? Leave a comment below!
+
+### 2. Recursive relations
+
+The `zenstack` CLI checks for cycles in the `check` call graph and reports an error if it finds one. This is needed to prevent infinite loops during evaluation. However, there are cases where cyclic delegation is intentionally needed, especially in the case of recursion. For example, if you want to model a Google Drive-like system, you may end up with a recursion like this:
+
+```zmodel
+model Folder {
+  parent Folder?
+  children Folder[]
+  permissions Permission[]
+
+  // a folder is readable if it's configured with a permission or if its parent is readable
+  @@allow('read', check(parent) || permissions?[type == 'read' && user == auth()])
+}
+```
+
+This will be a hard problem to solve since Prisma inherently doesn't support recursive queries. You can check the following issue for more details:
+
+<div align="center">
+  <a href="https://github.com/prisma/prisma/issues/3725"><img src={RecursionIssue} alt="Prisma recursion issue" width="600px" style={{'border-radius':'0.5rem'}} /></a>
+</div>
+
+A possible solution is to expand the recursion with a (configurable) finite levels of depth. Is this something your app needs?
+
+### 3. Other forms of duplication?
+
+Parent-child is just one of the patterns of duplication out there. Are there other patterns hurting you? Leave a comment or join our [discord server](https://discord.gg/Ykhr738dUe) to discuss! We love real-world problems and our innovation never stops.
diff --git a/blog/check-function/recursion-issue.png b/blog/check-function/recursion-issue.png
new file mode 100644
index 00000000..b160f514
Binary files /dev/null and b/blog/check-function/recursion-issue.png differ
diff --git a/blog/clerk-multitenancy/cover.png b/blog/clerk-multitenancy/cover.png
new file mode 100644
index 00000000..6ab453cb
Binary files /dev/null and b/blog/clerk-multitenancy/cover.png differ
diff --git a/blog/clerk-multitenancy/index.mdx b/blog/clerk-multitenancy/index.mdx
new file mode 100644
index 00000000..6569f678
--- /dev/null
+++ b/blog/clerk-multitenancy/index.mdx
@@ -0,0 +1,312 @@
+---
+title: "Building Multi-Tenant Apps Using Clerk's \"Organization\" and Next.js"
+description: Clerk's "organization" feature provides a powerful pre-built tenant management experience. Let's see how we can easily create a full-fledged multi-tenant application with it.
+tags: [auth, clerk, multi-tenancy]
+authors: yiming
+date: 2024-11-24
+image: ./cover.png
+---
+
+# Building Multi-Tenant Apps Using Clerk's \"Organization\" and Next.js
+
+![Cover Image](cover.png)
+
+Building a full-fledged multi-tenant application can be very challenging. Besides having a flexible sign-up and sign-in system, you also need to implement several other essential pieces:
+
+- Creating and managing tenants
+- User invitation flow
+- Managing roles and permissions
+- Enforcing data segregation and access control throughout the entire application
+
+It sounds like lots of work, and it indeed is. You may have done this multiple times if you're a veteran SaaS developer.
+
+<!--truncate-->
+
+[Clerk](https://clerk.com) is one of the most popular authentication and user management cloud services. Its combination of APIs and pre-built UI components dramatically simplifies the integration of such capabilities into your application. Similarly, its newer "Organization" feature provides an excellent starting point for creating multi-tenant applications. In this post, we'll explore leveraging it to build a non-trivial one while trying to keep our code simple and clean.
+
+## The goal and the stack
+
+The target application we'll build is a Todo List. Its core functionalities are simple: creating lists and managing todos within them. However, the focus will be on the multi-tenancy and access control aspects:
+
+- **Organization management**
+  
+  Users can create organizations and invite others to join. They can manage members and set their roles.
+
+- **Current context**
+  
+  Users can choose an organization to be the current context.
+
+- **Data segregation**
+  
+  Only data within the current organization can be accessed.
+
+- **Role-based access control**
+  
+  - Admin members have full access to all data within their organization.
+  - Regular members have full access to the todo lists they own.
+  - Regular members can view the other members' todo lists and manage their content, as long as the list is not private.
+
+Clerk can be used with any JavaScript framework, but its support for Next.js seems to be the best. So we'll use Next.js as our full-stack framework, along with two other essential pieces of weapon:
+
+- [Prisma](https://prisma.io): the ORM
+- [ZenStack](https://zenstack.dev): the access control layer on top of Prisma
+
+You can find the link of the completed project at the end of the post.
+
+## Adding organization management
+
+I assume you've created a Next.js project and set up the basic Clerk sign-up/sign-in flow following [the guide](https://clerk.com/docs/quickstarts/nextjs). Also, make sure you've[ enabled the "Organization" feature](https://clerk.com/docs/organizations/overview) in Clerk's dashboard.
+
+Now, we can add the "OrganizationSwitcher" component into the layout.
+
+```tsx title="src/app/layout.tsx"
+// highlight-next-line
+import { OrganizationSwitcher } from "@clerk/nextjs";
+...
+
+export default function RootLayout({ children }: { children: React.ReactNode }) {
+  return (
+    <ClerkProvider>
+      <html lang="en">
+        <body>
+          <header>
+            <SignedOut>
+              <SignInButton />
+            </SignedOut>
+            <SignedIn>
+              <div>
+                // highlight-next-line
+                <OrganizationSwitcher />
+                <UserButton />
+              </div>
+            </SignedIn>
+          </header>
+        </body>
+      </html>
+    </ClerkProvider>
+  );
+}
+```
+
+With this one-liner, you'll have a set of fully working UI components for managing organizations and choosing an active one!
+
+<div align="center">
+    <img src={require('./org-switcher.png').default} style={{borderRadius: '15px'}} width="480" />
+</div>
+
+## Setting up the database
+
+Our user and organization data are stored on Clerk's side. We need to store the todo lists and items in our own database. In this section, we'll set up Prisma and ZenStack and create the database schema.
+
+Let's start with installing the necessary packages:
+
+```bash
+npm install --save-dev prisma zenstack
+npm install @prisma/client @zenstackhq/runtime
+```
+
+Then we can create the database schema. Please note that we're creating a **schema.zmodel** file (as a replacement of "schema.prisma"). The [ZModel language](/docs/the-complete-guide/part1/zmodel) is a superset of Prisma schema language, allowing you to model both the data schema and access control policies. In this section, we'll only focus on the data modeling part.
+
+```zmodel title="/schema.zmodel"
+datasource db {
+  provider = "postgresql"
+  url      = env("DATABASE_URL")
+}
+
+generator js {
+  provider = "prisma-client-js"
+}
+
+// Todo list
+model List {
+  id        String        @id @default(cuid())
+  createdAt DateTime      @default(now())
+  title     String
+  private   Boolean       @default(false)
+  orgId     String?
+  ownerId   String
+  todos     Todo[]
+}
+
+// Todo item
+model Todo {
+  id          String    @id @default(cuid())
+  title       String
+  completedAt DateTime?
+  list        List      @relation(fields: [listId], references: [id], onDelete: Cascade)
+  listId      String
+}
+```
+
+You can then generate a regular Prisma schema file and push the schema to the database:
+
+```bash
+# The `zenstack generate` command generates the "prisma/schema.prisma" file and runs "prisma generate"
+npx zenstack generate
+npx prisma db push
+```
+
+Finally, create a "src/server/db.ts" file to export the Prisma client:
+
+```ts title="src/server/db.ts"
+import { PrismaClient } from "@prisma/client";
+export const prisma = new PrismaClient();
+```
+
+## Implementing access control
+
+As mentioned, ZenStack allows you to model both data and access control in a single schema. Let's see how we can entirely implement our authorization requirements with it. The rules are defined with the `@@allow` and `@@deny` attributes. Access is rejected by default unless explicitly granted with an `@@allow` rule.
+
+Although authorization is a distinct concept from authentication, it usually depends on authentication to work. For example, to determine if the current user has access to a list, a verdict must be made based on the user's id, current organization, and role in the organization. To access such information, let's first declare a type to express it:
+
+```zmodel title="/schema.zmodel"
+// The shape of `auth()`
+type Auth {
+  // Current user's ID
+  userId         String  @id
+
+  // User's current organization ID
+  currentOrgId   String?
+
+  // User's role in the current organization
+  currentOrgRole Role?
+
+  @@auth
+}
+```
+
+Then you can use the special `auth()` function in access policy rules to access the current user's information. Let's use the `List` model as an example to demonstrate how the rules are defined.
+
+```zmodel title="/schema.zmodel"
+model List {
+  ...
+
+  // deny anonymous access
+  @@deny('all', auth() == null)
+
+  // tenant segregation: deny access if the user's current org doesn't match
+  @@deny('all', auth().currentOrgId != orgId)
+
+  // owner/admin has full access
+  @@allow('all', auth().userId == ownerId || auth().currentOrgRole == 'org:admin')
+
+  // can be read by org members if not private
+  @@allow('read', !private)
+
+  // when create, owner must be set to current user
+  @@allow('create', ownerId == auth().userId)
+}
+```
+
+The last piece of the puzzle is, as you may already be wondering, where the value of `auth()` comes from? At runtime, ZenStack offers an `enhance()` API to create an enhanced `PrismaClient` (a lightweighted wrapper) that automatically enforces the access policies. You pass in a user context (usually fetched from the authentication provider) when calling `enhance()`, and that context provides the value for `auth()`.
+
+We'll see how it works in detail in the next section.
+
+## Finally, the UI
+
+Before diving into creating the UI, let's first make a helper to get an enhanced `PrismaClient` for the current user.
+  
+```ts title="src/server/db.ts"
+import { auth } from "@clerk/nextjs/server";
+import { Role } from "@prisma/client";
+import { enhance } from "@zenstackhq/runtime";
+
+export async function getUserDb() {
+  // get the current user's information from Clerk
+  const { userId, orgId, orgRole } = await auth();
+
+  // create an enhanced Prisma Client with proper user context
+  const user = userId
+    ? {
+        userId,
+        currentOrgId: orgId,
+        currentOrgRole: orgRole
+      }
+    : undefined; // anonymous
+  return enhance(prisma, { user });
+}
+```
+
+Let's build the UI using [React Server Components](https://nextjs.org/docs/app/building-your-application/rendering/server-components) (RSC) and [Server Actions](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations). We'll also consistently use the `getUserDb()` helper to access the database with access control enforcement.
+
+Here's the RSC that renders the todo lists for the current user (with styling omitted):
+
+```tsx title="src/components/TodoList.tsx"
+// Component showing Todo list for the current user
+
+export default async function TodoLists() {
+  const db = await getUserDb();
+
+  // enhanced PrismaClient automatically filters out
+  // the lists that the user doesn't have access to
+  const lists = await db.list.findMany({
+    orderBy: { updatedAt: "desc" },
+  });
+
+  return (
+    <div>
+      <div>
+        {/* client component for creating a new List */}
+        <CreateList />
+
+        <ul>
+          {lists?.map((list) => (
+            <Link href={`/lists/${list.id}`} key={list.id}>
+              <li>{list.title}</li>
+            </Link>
+          ))}
+        </ul>
+      </div>
+    </div>
+  );
+}
+```
+
+A client component that creates a new list by calling into a server action:
+
+```tsx title="src/components/CreateList.tsx"
+"use client";
+
+import { createList } from "~/app/actions";
+
+export default function CreateList() {
+  function onCreate() {
+    const title = prompt("Enter a title for your list");
+    if (title) {
+      createList(title);
+    }
+  }
+
+  return (
+    <button onClick={onCreate}>
+      Create a list
+    </button>
+  );
+}
+```
+
+```ts title="src/app/actions.ts"
+'use server';
+
+import { revalidatePath } from "next/cache";
+import { getUserDb } from "~/server/db";
+
+export async function createList(title: string) {
+  const db = await getUserDb();
+  await db.list.create({ data: { title } });
+  revalidatePath("/");
+}
+```
+
+<div align="center">
+    <img src={require('./list-ui.gif').default} style={{borderRadius: '15px'}} width="640" />
+</div>
+
+The components that manage Todo items are not shown for brevity, but the ideas are similar. You can find the fully completed code [here](https://github.com/ymc9/clerk-zenstack-multitenancy).
+
+## Conclusion
+
+Authentication and authorization are two cornerstones of most applications. They can be especially challenging to build for multi-tenant ones. This post demonstrated how the work can be significantly simplified and streamlined by combining Clerk's "Organization" feature and ZenStack's access control capabilities. The end result is a secure application with great flexibility and little boilerplate code.
+
+Clerk also supports defining [custom roles and permissions](https://clerk.com/docs/organizations/roles-permissions) (still Beta) for organizations. Although not covered in this post, with some tweaking, you should be able to leverage it to define access policies. That way, you can manage permissions with Clerk's dashboard and have ZenStack enforce them at runtime.
+
diff --git a/blog/clerk-multitenancy/list-ui.gif b/blog/clerk-multitenancy/list-ui.gif
new file mode 100644
index 00000000..18e1bc3b
Binary files /dev/null and b/blog/clerk-multitenancy/list-ui.gif differ
diff --git a/blog/clerk-multitenancy/org-switcher.png b/blog/clerk-multitenancy/org-switcher.png
new file mode 100644
index 00000000..88041964
Binary files /dev/null and b/blog/clerk-multitenancy/org-switcher.png differ
diff --git a/blog/code-as-doc/cover.png b/blog/code-as-doc/cover.png
new file mode 100644
index 00000000..18385635
Binary files /dev/null and b/blog/code-as-doc/cover.png differ
diff --git a/blog/code-as-doc/index.md b/blog/code-as-doc/index.md
new file mode 100644
index 00000000..eb9afab2
--- /dev/null
+++ b/blog/code-as-doc/index.md
@@ -0,0 +1,341 @@
+---
+title: 'Code as Doc: Automate by Vercel AI SDK and ZenStack for Free'
+description: Introduce a ZenStack markdown plugin that generates documentation from the ZModel schema using Vercel AI SDK. 
+tags: [ai, vercel, documentation]
+authors: jiasheng
+date: 2024-12-23
+image: ./cover.png
+---
+
+# Code as Doc: Automate by Vercel AI SDK and ZenStack for Free
+
+![Cover Image](cover.png)
+
+## Few developers like writing document
+
+If you have ever worked as a developer in a large company, you know that coding is just one small part of the daily responsibilities. One of Google's full-stack software engineers, Ray Farias, once estimated that developers write about [100-150 lines of code per day at Google](https://www.quora.com/How-many-lines-of-code-get-written-at-Google-each-day). While this estimate may vary across different teams, the order of magnitude matches my observations as a developer at Microsoft.  
+<!--truncate-->
+
+So where does the time go? A significant portion goes to activities like meetings, code reviews, planning sessions, and documentation tasks. Among all these tasks, documentation is my least favorite—and I suspect many other teammates feel the same way.  
+
+The main reason is that we didn't see much value in it. We were required to write design documents at the start of each sprint before coding, and after reviewing them with each other, most would remain unchanged forever. I can't count how many times I found something strange in a document only to have its author tell me it was outdated. 😂  Why don't we update the docs? Because our boss considers it less important than fixing bugs or adding new features.
+
+Documentation should serve as a high-level abstraction of code to aid understanding. When documentation falls out of sync with the code, it loses its purpose. However, keeping doc synchronized with code requires effort—something few people actually enjoy.   
+
+Uncle Bob(Robert C. Martin)  has a famous quote about clean code: 
+
+> Good code serves as its own comments
+> 
+
+I think it would be great if this principle could be extended to documentation as well:
+
+**Good code is its own best documentation**
+
+## Generate document using AI
+
+There is a simple rule for the current trend in AI adoption: if humans don't enjoy doing something, let AI handle it. Documentation seems to fit perfectly into this category, especially as more and more code has already been generated by AI nowadays. 
+
+The timing couldn't be better, as GitHub has just announced that [Copilot features are free](https://code.visualstudio.com/blogs/2024/12/18/free-github-copilot). You could simply try to let it generate the documentation for your project for free.  However, the result might not be as good as you expected. Is it because your prompt isn't good enough? Maybe, but there's a more essential reason behind this: 
+
+LLMs don't handle imperative code as well as they handle declarative text.  
+
+Imperative code often involves complex control flow, state management, and intricate dependencies. This procedural nature requires a deeper understanding of the intent behind the code, which can be difficult for LLMs to infer accurately. Moreover, the larger the code volume, the more likely the result will be inaccurate and less informative. 
+
+What's the first thing you want to see in a web application's documentation? Most likely, it's the data models that serve as the foundation of the entire application. Can data models be defined declaratively? Absolutely! [Prisma ORM](https://www.prisma.io/) has already done a great job by allowing developers to define their application models **in an intuitive data modeling language.  
+
+The ZenStack toolkit, built on top of Prisma, enhances the schema with additional capabilities. By defining access policies and validation rules directly within the data model, it becomes the single source of truth for the backend of your application.   
+
+When I say "single source of truth," it contains not only all the information for the backend—it actually is your entire backend.  ZenStack automatically generates APIs and corresponding frontend hooks for you. With access policies defined, these can be safely called directly from the frontend without needing to enable row-level security (RLS) in the database layer.  Or, in another way,  you’ll hardly need to write any code for your backend. 
+
+Here is an extremely simplified example of a blog post app:
+
+```tsx
+datasource db {
+    provider = 'postgresql'
+    url = env('DATABASE_URL')
+}
+
+generator js {
+    provider = 'prisma-client-js'
+}
+
+plugin hooks {
+    provider = '@zenstackhq/tanstack-query'
+    output = 'lib/hooks'
+    target = 'react'
+}
+
+enum Role {
+    USER
+    ADMIN
+}
+
+model Post {
+    id        String  @id @default(cuid())
+    title     String
+    published Boolean @default(false)
+    author    User    @relation(fields: [authorId], references: [id])
+    authorId  String  @default(auth().id)
+
+    @@allow('all', auth() == author)
+    @@allow('read', auth() != null && published )
+    @@allow('read', auth().role == 'ADMIN')
+}
+
+model User {
+    id       String  @id @default(cuid())
+    name     String?
+    email    String? @unique
+    password String  @password @omit
+    role     Role    @default(USER)
+    posts    Post[]
+    
+    @@allow('create,read', true)
+    @@allow('update,delete', auth() == this)
+}
+```
+
+We can easily create a tool that generates documentation from this schema using AI. You don't have to manually write and maintain docs anymore—simply integrate the generation process into the CI/CD pipeline, and there's no out-of-sync problem anymore. Here's an example of documentation generated from the schema:
+
+![zenstack-doc](https://github.com/user-attachments/assets/85218aa2-f7ff-4a43-90a3-afc35ee5ab4a)
+
+I will walk you through the steps of how to create this tool. 
+
+## ZenStack plugin system
+
+Like many wonderful tools in the web development world, ZenStack adopts a plugin-based architecture. At the core of the system is the ZModel schema, around which features are implemented as plugins.   Let's create a plugin to generate a markdown for a ZModel so it can be easily adopted by others. 
+
+> For brevity, we'll focus on core parts. See the [ZenStack documentation](https://zenstack.dev/docs/the-complete-guide/part2/writing-plugins) for complete plugin development details.
+> 
+
+A plugin is simply a Node.js module that has the two parts:
+
+1. A named export `name` that specifies the name of the plugin used for logging and error reporting.
+2. A default function export containing the plugin logic.
+
+Here's what it looks like:
+
+```tsx
+import type { PluginOptions } from '@zenstackhq/sdk';
+import type { DMMF } from '@zenstackhq/sdk/prisma';
+import type { Model } from '@zenstackhq/sdk/ast';
+
+export const name = 'ZenStack MarkDown';
+
+export default async function run(model: Model, options: PluginOptions, dmmf: DMMF.Document) {
+    ...
+}
+```
+
+`model` is the ZModel AST. It is the result object model of parsing and linking the ZModel schema, which is a tree structure containing all the information in the schema.
+
+We can use `ZModelCodeGenerator` provided by ZenStack sdk to get the ZModel content from the AST.
+
+```tsx
+import { ZModelCodeGenerator } from '@zenstackhq/sdk';
+const zModelGenerator = new ZModelCodeGenerator();
+const zmodel = zModelGenerator.generate(model);
+```
+
+Now that we have the ingredients let's have AI do the cooking. 
+
+## Use Vercel AI SDK to generate document
+
+Initially, I planned to use OpenAI to do the job. But soon, I realized this would exclude developers who don't have access to paid OpenAI services. Thanks to Elon Musk, you can get free API keys from Grok ([https://x.ai/](https://x.ai/)).  
+
+However, I had to write separate code for each model provider. This is where the Vercel AI SDK shines. It provides a standardized interface for interacting with various LLM providers, allowing us to write code that works with multiple AI models. Whether you're using OpenAI, Anthropic's Claude, or other providers, the implementation remains consistent.
+
+It provides a unified LanguageModel type, allowing you to specify any LLM model you wish to use. Simply check the environment to determine which model is available.
+
+```tsx
+    let model: LanguageModel;
+
+    if (process.env.OPENAI_API_KEY) {
+        model = openai('gpt-4-turbo');
+    } else if (process.env.XAI_API_KEY) {
+        model = xai('grok-beta');
+    }
+    ...
+```
+
+The rest of the implementation uses the same unified API, regardless of which provider you choose.
+
+![vercel-ai-sdk](https://github.com/user-attachments/assets/fab6fd46-0639-46b5-87cf-66964898b060)
+
+Here is the prompt we use to let AI generate the content of the doc: 
+
+```tsx
+  const prompt = `
+    You are the expert of ZenStack open-source toolkit. 
+    You will generate a technical design document from a provided ZModel schema file that help developer understand the structure and behavior of the application. 
+    The document should include the following sections:
+    1. Overview 
+        a. A short paragraph for the high-level description of this app
+        b. Functionality
+    2. an array of model. Each model has below two information:
+        a. model name
+        b. array of access policies explained by plain text
+    here is the ZModel schema file:
+    \`\`\`zmodel
+    ${zmodel}
+    \`\`\`
+    `;
+```
+
+## Generating structured data
+
+When dealing with APIs, we prefer to use JSON data instead of plain text. Although many LLMs are capable of generating JSON, each has its own approach. For example, OpenAI provides a JSON mode, while Claude requires JSON formatting to be specified in the prompt. The good news is that Vercel SDK also unifies this ability across model providers using Zod schema. 
+
+For the prompt above, here is the corresponding response data structure we expect to receive. 
+
+```tsx
+    const schema = z.object({
+        overview: z.object({
+            description: z.string(),
+            functionality: z.string(),
+        }),
+        models: z.array(
+            z.object({
+                name: z.string(),
+                access_control_policies: z.array(z.string()),
+            })
+        ),
+    });
+```
+
+Then call the `generateObject` API to let AI do his job: 
+
+```tsx
+const { object } = await generateObject({
+        model
+        schema
+        prompt
+    });
+```
+
+Here is the returned type that allows you to work within a type-safe manner:
+
+```tsx
+const object: {
+    overview: {
+        description: string;
+        functionality: string;
+    };
+    models: {
+        name: string;
+        access_control_policies: string[];
+    }[];
+}
+```
+
+## Generate Mermaid  ERD diagram
+
+Let's also generate the ERD diagram for each model. This part is quite straightforward and easy to implement, so I think writing code is more reliable and efficient here. Of course, you could still employ AI as a copilot here. 😄
+
+```tsx
+export default class MermaidGenerator {
+    generate(dataModel: DataModel) {
+        const fields = dataModel.fields
+            .filter((x) => !isRelationshipField(x))
+            .map((x) => {
+                return [
+                    x.type.type || x.type.reference?.ref?.name,
+                    x.name,
+                    isIdField(x) ? 'PK' : isForeignKeyField(x) ? 'FK' : '',
+                    x.type.optional ? '"?"' : '',
+                ].join(' ');
+            })
+            .map((x) => `  ${x}`)
+            .join('\n');
+
+        const relations = dataModel.fields
+            .filter((x) => isRelationshipField(x))
+            .map((x) => {
+                // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+                const oppositeModel = x.type.reference!.ref as DataModel;
+
+                const oppositeField = oppositeModel.fields.find(
+                    (x) => x.type.reference?.ref == dataModel
+                ) as DataModelField;
+
+                const currentType = x.type;
+                const oppositeType = oppositeField.type;
+
+                let relation = '';
+
+                if (currentType.array && oppositeType.array) {
+                    //many to many
+                    relation = '}o--o{';
+                } else if (currentType.array && !oppositeType.array) {
+                    //one to many
+                    relation = '||--o{';
+                } else if (!currentType.array && oppositeType.array) {
+                    //many to one
+                    relation = '}o--||';
+                } else {
+                    //one to one
+                    relation = currentType.optional ? '||--o|' : '|o--||';
+                }
+
+                return [`"${dataModel.name}"`, relation, `"${oppositeField.$container.name}": ${x.name}`].join(' ');
+            })
+            .join('\n');
+
+        return ['```mermaid', 'erDiagram', `"${dataModel.name}" {\n${fields}\n}`, relations, '```'].join('\n');
+    }
+}
+```
+
+## Stitch everything up
+
+Finally, we'll combine all the generated components together to get our final doc:
+
+```tsx
+ const modelChapter = dataModels
+        .map((x) => {
+            return [
+                `### ${x.name}`,
+                mermaidGenerator.generate(x),
+                object.models
+                    .find((model) => model.name === x.name)
+                    ?.access_control_policies.map((x) => `- ${x}`)
+                    .join('\n'),
+            ].join('\n');
+        })
+        .join('\n');
+
+ const content = [
+        `# Technical Design Document`,
+        '> Generated by [`ZenStack-markdown`](https://github.com/jiashengguo/zenstack-markdown)',
+        `${object.overview.description}`,
+        `## Functionality`,
+        `${object.overview.functionality}`,
+        '## Models:',
+        dataModels.map((x) => `- [${x.name}](#${x.name})`).join('\n'),
+        modelChapter,
+    ].join('\n\n');
+```
+
+## Off the shelf
+
+Of course, you don't have to implement this yourself. It's already published as an NPM package for you to install:
+
+```bash
+npm i -D zenstack-markdown
+```
+
+Add the plugin to your ZModel schema file
+
+```bash
+plugin zenstackmd {
+    provider = 'zenstack-markdown'
+}
+```
+
+Just don’t forget to put whatever AI API keys are available for you in your .env.  Otherwise, you might get some surprising results. 😉
+
+```bash
+OPENAI_API_KEY=xxxx
+XAI_API_KEY=xxxxx
+ANTHROPIC_API_KEY=xxxx
+```
\ No newline at end of file
diff --git a/blog/connect-dots/cover.png b/blog/connect-dots/cover.png
new file mode 100644
index 00000000..62151161
Binary files /dev/null and b/blog/connect-dots/cover.png differ
diff --git a/blog/connect-dots/index.md b/blog/connect-dots/index.md
new file mode 100644
index 00000000..1c840548
--- /dev/null
+++ b/blog/connect-dots/index.md
@@ -0,0 +1,84 @@
+---
+title: What Are the Chances of You Creating a Programming Language?
+description: Enjoy whatever you are doing and wait for the dots to connect
+tags: [compiler, language, DSL]
+authors: jiasheng
+date: 2024-02-07
+image: ./cover.png
+---
+
+## What Are the Chances of You Creating a Programming Language?
+![Cover Image](cover.png)
+
+If I asked what’s your favorite  words of Steve Jobs, I believe most people would say:
+
+> Stay Hungry. Stay Foolish
+
+It’s coming from the 3rd story of the speech he gave at Stanford.  But my favorite one is coming from the first story:
+
+> Connecting the Dots
+
+<!--truncate-->
+## Would You Ever Create a New Language?
+
+Does the below content look familiar to you?
+
+![compiler-content](https://github.com/zenstackhq/zenstack/assets/16688722/7881350c-d579-4c2a-b7fa-d2768a4ca15a)
+
+If you have studied computer science before, it should be. This is the content from the classic [Dragon Book](https://en.wikipedia.org/wiki/Compilers:_Principles,_Techniques,_and_Tools): 
+
+![compiler-book-cover](https://github.com/zenstackhq/zenstack/assets/16688722/c0f57cd9-b0ad-4388-a7c3-8f7a94ac688f)
+
+Even if your school adopted a different textbook for the Compiler Principles course, the concept of DFA is a cornerstone that you can't forget. Or does it? 😄
+
+If this course were not compulsory for our major, I doubt how many students would choose to take it. Not only is it challenging to learn, but also from a pragmatic perspective:
+
+> What are the chances of us creating a new programming language in the workplace?
+
+Therefore, the motivation for many students is just to pass the exam and to get the credit. Some of them ended up getting a good score on the final exam due to the excessive practice of manual deduction of the regular expression, NFA, DFA, BNF, etc.  However, few of them are acutally interested in how a programming language is constructed from the ground.  
+
+I also had that doubt too. But for me, it was the first time I ever felt the **science** part of my major.  To be honest, I never expected to have the opportunity to create a language either. However, somehow it did attract me to learn it rather than pass it. Perhaps it's because I feel relieved to understand the principles behind the tools I'll likely use throughout my career.
+
+With hindsight, now I would say:
+
+## You Never Know What Would Happen
+
+During my last year of graduate school, I was lucky to get an internship at Microsoft.  The even more lucky part is that the team I joined was building a new DSL(Domain Specific Language) for protocol.  
+
+Initially, I worked on an output adapter that generates documents from the parsed Abstract Syntax Tree (AST).  After a while, my leader assigned me a new task to investigate the possibility of rewriting the current LR parser with a new Recursive-Descent parser. I guess that due to my previous devotion to Compiler Principles, it didn't take as much time as they had expected for me to obtain the result.  
+
+I still remember the day when the architect of my team reviewed my prototype with me one-on-one. In general, he affirmed my approach and provided some guidance and suggestions.  Although it's just him and me because all the other team members are going off-site, it was by far the best moment in my internship. 
+
+I thought the job was done for me, the next step would be assigning a full-time employee to write the production code.  To my surprise,  I’m the one who put it into the production.  I assume that’s the main reason why I finally got the offer to join Microsoft. 
+
+## I Would Never Write Front-End Code
+
+As a backend developer with over 10 years of experience, I never imagined that I would write any front-end code. This is not only because front-end and back-end development require different skill sets but also because I believe that having a good sense of visual aesthetics is a crucial quality for front-end developers, which I lack.   However, there is a significant shortage of developer resources for the new SaaS product we were building in my previous company. As a result, I had to write front-end code. 
+
+Although the view still stands that I would never be a good front-end developer, it is that experience that opened the door to full-stack development for me. I could see the knowledge gap between front-end and back-end developers, which makes it inefficient to deliver features end-to-end for complex web apps. 
+
+## The Dots Are Connected
+
+If you investigate some successful software, you will notice that many of them are created due to the pain point of the creator himself. The underlying logic this is easy to understand: 
+
+**you need to be the customer of your own product to make it right**
+
+The reason why we created ZenStack is the same because of the experience above.  We have felt many pain points that significantly slowed down product delivery from end-to-end during that time.  Therefore, it gave us a strong motivation to explore tooling & infrastructure space and find ways to improve efficiency. 
+
+The DSL experience comes naturally to us as we have witnessed how well-designed language can greatly simplify programming tasks.  We know it is challenging but not as difficult as many may feel. Therefore, we chose the schema-first design to create [ZenStack](https://zenstack.dev/) toolkit on top of [Prisma ORM](https://www.prisma.io/) that leverages code generation to significantly reduce the code developers need to write and make them move a lot faster.
+
+Check out the details of how we build it:
+
+[How Much Work Does It Take to Build a Programming Language? | ZenStack](https://zenstack.dev/blog/build-language)
+
+## Allow It and Make the Magic Happen
+
+Despite ZenStack being relatively young, it has already integrated with various types of stacks:
+
+![ZenStack-cover](https://github.com/zenstackhq/zenstack/assets/16688722/b2df2e5e-cdb1-4753-b144-04c3b85dfcb4)
+
+Sometimes we were questioned if we were a little unfocused.  The short answer to that is that we haven’t figured out it clearly, so we have to give it a try.   
+
+Here, I would like to use Steve Jobs’s words to answer it:
+
+> Again, you can’t connect the dots looking forward; you can only connect them looking backward. So you have to trust that the dots will somehow connect in your future. You have to trust in something — your gut, destiny, life, karma, whatever. This approach has never let me down, and it has made all the difference in my life.
\ No newline at end of file
diff --git a/blog/contribute-oss/cover.png b/blog/contribute-oss/cover.png
new file mode 100644
index 00000000..5dd4d0ae
Binary files /dev/null and b/blog/contribute-oss/cover.png differ
diff --git a/blog/contribute-oss/index.md b/blog/contribute-oss/index.md
new file mode 100644
index 00000000..34f058c8
--- /dev/null
+++ b/blog/contribute-oss/index.md
@@ -0,0 +1,117 @@
+---
+title: Contributing To Open Source Projects Might Be Easier Than You Think
+description: Illustrate with a real case how open-source contributions go beyond code from maintainers perspective
+tags: [open-source, oss, community, zenstack]
+authors: jiasheng
+date: 2023-12-05
+image: ./cover.png
+---
+
+# Contributing To Open Source Projects Might Be Easier Than You Think
+
+![Cover Image](cover.png)
+
+As a software developer, you may not have directly participated in any open-source project, but you have likely benefited from the open-source world unless you don't use Git, GitHub, or Linux. 😄 In return, many of us would like to contribute and be a part of this new wave of collaborative and transparent development.
+<!--truncate-->
+However, many people have not taken their first step because the process of contributing can be intimidating.
+
+- How to find a suitable issue to contribute?
+- How to confirm with the maintainer that this is actually what they want?
+- How to make sure my solution is the right way to go?
+- Do I need to add some test cases or documentation?
+- What if something goes wrong?
+
+First of all, due to the nature of human beings, we tend to exaggerate difficulties before attempting a new task.  Even leave it alone,  here lies a common misconception about contributing to open-source projects: 
+
+**You need to contribute code** 
+
+While code contributions are undoubtedly valuable, they represent just one facet of the broader landscape of open-source collaboration.  There are other ways you might make an even greater contribution than code. 
+
+## What It Means To Contribute
+
+I wrote a post about implementing a high-concurrency ticket booking system leveraging on Optimistic Concurrency Control (OCC):
+
+[How To Build a High-Concurrency Ticket Booking System With Prisma](https://dev.to/zenstack/how-to-build-a-high-concurrency-ticket-booking-system-with-prisma-184n)
+
+Many people are impressed by the concise code by Prisma or ZenStack to achieve that:
+
+```tsx
+    await client.seat.update({
+        data: {
+            userId: userId,
+            version: {
+                increment: 1,
+            },
+        },
+        where: {
+            id: availableSeat.id,
+        },
+    });
+```
+
+What they might not know is that it’s not possible before Prisma 4.5.0 because of the below issue:
+
+[Be able to update or retrieve a single record including non-unique fields in the "where" conditions](https://github.com/prisma/prisma/issues/7290)
+
+Before that you have to use `updateMany` followed by the count check as below:
+
+```tsx
+const seats = await client.seat.updateMany({
+  data: {
+    claimedBy: userEmail,
+    version: {
+      increment: 1,
+    },
+  },
+  where: {
+    id: availableSeat.id,
+    version: availableSeat.version, 
+})
+
+if (seats.count === 0) {
+  throw new Error(`That seat is already booked! Please try again.`)
+}
+```
+
+Although a little verbose, it works. However, before Prisma 4.4.0, it was unable to function due to the issue described below:
+
+[updateMany() causes lost-updates](https://github.com/prisma/prisma/issues/8612)
+
+The issue was filed when it was already listed as an example of Optimistic Concurrency Control (OCC) in the [official documentation of Prisma](https://www.prisma.io/docs/guides/performance-and-optimization/prisma-client-transactions-guide#optimistic-concurrency-control) (which is still present today). Therefore, before it was fixed, the Prisma team had to temporarily add a warning to the documentation at that time.
+
+![prisma-doc-fix](https://github.com/jiashengguo/Test/assets/16688722/19c0faef-5b4c-4517-86cc-1ec306167bf5)
+
+After closely examining the threads of the two mentioned issues, I believe you would agree that Prisma team implemented the fix as a result of conversations and discussions among actively involved community members. If you are the maintainer of Prisma, would you consider these people as contributors? Regardless of your opinion, GitHub will do:
+
+>[Issues, pull requests and discussions](https://docs.github.com/en/account-and-profile/setting-up-and-managing-your-github-profile/managing-contribution-settings-on-your-profile/why-are-my-contributions-not-showing-up-on-my-profile#issues-pull-requests-and-discussions)
+>Issues, pull requests, and discussions will appear on your contribution graph if they were opened in a standalone repository, not a fork.
+
+## Feedback Might Be More Valuable Than PR
+
+I recently listened to a talk by Tanner Linsley, the creator of [TanStack](https://tanstack.com/) (React Query), about his personal experience in the open-source community. I highly recommend listening to it:
+
+[Open Source Hour with Tanner Linsley](https://twitter.com/i/spaces/1yoJMwdVWpoKQ)
+
+During the interview, the hoster asked him a question:
+
+> What types of contributions do you find most valuable for your projects
+
+Here is the simplified version of his answer:
+
+> The most valuable contributions are at kind of the leaf node level. It’s the gentle feedback of 
+- I tried following the documnetaion or I tired doing this and it didn’t work
+- I got confused or I’m stuck
+It may not be feedback that makes it all the way out of the library. 
+
+> And once in a while, you will get people who go above and beyond and propose something, either a documentation change itself or maybe an API change. I feel like it's an exponential falloff. There are so many people who are willing to offer their two cents on, it didn't work. Most people beyond that aren't going to help you out; they are just going to kind of dine and dash. 
+
+> Only one percent of those people are going to open a PR. Those are great, but usually, they require guidance from the core maintainers to say: "We would love to do that, but we can't do it that way because of XYZ." And I feel like less than 10 percent of those people will stick to the PR. Those are the people you need to nurture and pay attention to.
+
+> **contributions come in many forms.** 
+
+
+## Open Source Contributions Go Beyond Code
+
+Open source projects are more than just code. They provide a collaborative space where people can report problems, ask questions, and suggest fixes or improvements, among other things. So, if you notice anything wrong or feel that something could be better or different in the project you already use or want to use, don't hesitate to let the community hear your voice.
+
+[ZenStack](https://github.com/zenstackhq/zenstack) is an open-source toolkit that we are developing on top of Prisma ORM. It provides an innovative schema-first approach for building web applications. If you feel that it could be helpful for you, simply trying it out and letting us know your thoughts would be a great contribution to us!
\ No newline at end of file
diff --git a/blog/database-to-mcp/cover.png b/blog/database-to-mcp/cover.png
new file mode 100644
index 00000000..6b2fc8c8
Binary files /dev/null and b/blog/database-to-mcp/cover.png differ
diff --git a/blog/database-to-mcp/index.md b/blog/database-to-mcp/index.md
new file mode 100644
index 00000000..dc793fd2
--- /dev/null
+++ b/blog/database-to-mcp/index.md
@@ -0,0 +1,503 @@
+---
+title: Turning Your Database Into an MCP Server With Auth
+description: Benefits of using the Model Context Protocol (MCP) with OAuth and how to implement it using ZenStack and MCP SDK.
+tags: [ai, mcp, database, authorization]
+authors: jiasheng
+date: 2025-06-10
+image: ./cover.png
+---
+
+# Turning Your Database Into an MCP Server With Auth
+
+![Cover Image](cover.png)
+
+## MCP is trending in AI,  But…
+
+MCP(Model Context Protocol) is undoubtedly one of the most exciting trends in AI now. Not only is everyone talking about it, but everyone is also rushing to develop their version to secure a seat at the table of the AI era. Don't feel so?  Guess how many MCP servers are listed in [MCP Server Directory](https://www.pulsemcp.com/servers) ?
+<!--truncate-->
+
+The number is 4,684 now, and remember, MCP is just a six-month-old baby.   However, have you noticed the one and only filter on the left:
+
+![mcp-filter](https://github.com/user-attachments/assets/2dd0a708-16fb-48af-bca0-1089abd2e63b)
+
+Guess how many remain after applying this filter? Only 59—approximately 1% of the total. For those unfamiliar with this filter, I'll provide a brief explanation that you can skip if you already understand it. 
+
+MCP was initially conceived primarily as a protocol for local execution, where AI models could interact with local tools and data sources running on the same machine, like the example MCP FileSystem and Fetch listed in the official doc. It means you must install and run the MCP server on your local machine, regardless of whether the underlying transport is stdio or HTTP SSE. This design choice made sense in the early days, as it simplified security concerns and reduced latency. The protocol's simplicity made it perfect for developers to experiment with AI tool integration on their personal machines. However, as MCP gained traction and the AI ecosystem evolved, the need for secure remote execution became increasingly apparent:
+
+<div align='center'>
+[![Kent-Tweet](https://github.com/user-attachments/assets/56c26bb2-bf7f-423e-9a2a-7416f09298a7)](https://x.com/kentcdodds/status/1907218594624372868)
+</div>
+
+With a remote MCP server, organizations expose their data and services to AI models over the internet,  so the most important thing is Auth (authentication and authorization). Initially, this was addressed by asking users to generate an API key within the product and then configure it in the MCP settings. Here is an example from the Neon MCP server:
+
+```json
+{
+  "mcpServers": {
+    "neon": {
+      "command": "npx",
+      "args": [
+        "-y",
+        "@neondatabase/mcp-server-neon",
+        "start",
+        "<YOUR_NEON_API_KEY>"
+      ]
+    }
+  }
+}
+```
+
+Developers are all familiar with this process. But as more and more SaaS products are providing the MCP server due to fear of death(search ‘SaaS is dead’ if you don't get it 😁), that's definitely not a good user experience for the non-developers. Moreover, users need to manually update the NPM package from time to time to stay current.
+
+Kent raises the same concern in his tweet:
+
+> All the examples I see so far have the user generate a token and then put that token in the MCP configuration. Is that the best we have right now? I was hoping to find an example of an MCP client that supported an OAuth flow with a hosted MCP server.
+> 
+
+## The Advent of Authorization for MCP
+
+The MCP is young but is growing very fast. The OAuth support was introduced in March 2025, around the time he was 3 months old. Here are the Authorization flow steps in the [official specification](https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization):
+
+![MCP-Auth-Flow](https://github.com/user-attachments/assets/6c8857ab-4436-4fc9-b264-10409e61a2ab)
+
+The specification comes with SDK support for both client and server, enabling more MCP servers to adopt and leverage it.   Even some existing servers that rely on API keys are beginning to embrace the new modern solution. For example, the Neon MCP server mentioned above adds support for it, too.   
+
+```json
+{
+  "mcpServers": {
+    "Neon": {
+      "command": "npx",
+      "args": ["-y", "mcp-remote", "https://mcp.neon.tech/sse"]
+    }
+  }
+}
+```
+
+<aside>
+💡The `mcp-remote` is for clients that don’t support OAuth. It's unnecessary for clients like Cursor that have supported it.
+</aside>
+
+After adding the above MCP configuration, the MCP client will automatically open the browser to go through the standard OAuth process without needing the API key anymore:
+
+![Neon-OAuth](https://github.com/user-attachments/assets/c3eb58f9-db43-4376-bf80-af5a342dfc3d)
+
+With this modern MCP server, you can stop worrying about the PM or CEO showing up at your desk asking for help connecting to the MCP server of some SaaS product your company uses. Trust me, it happens. 😂
+
+## MCP Is a Good Enhancement for SaaS
+
+With the seamless experience of OAuth support, many companies are eager to provide an MCP server to enhance their existing product or service, especially within the SaaS landscape. Balancing flexibility and simplicity is always a core challenge for SaaS  - too many buttons overwhelm users, too few constrain power users.   For instance, I always love the clean and efficient UX/UI design of Trello:
+
+![Trello](https://github.com/user-attachments/assets/d9ec4451-6c76-484c-858f-8f8a02e5ddbf)
+
+However, it pricks me every time for certain routine jobs.  
+
+- How many cards were done last week?
+- Who has the most incomplete cards?
+- Which list has the most incomplete cards?
+
+Trello doesn’t provide a direct way to show the answer; you have to do the math manually.   This is where the MCP-LLM duo could provide a great solution to let user use natural language to get their job done.    I think that’s the actual point that Microsoft CEO Satya Nadella was trying to convey in his presumed “SaaS is dead” interview.
+
+## Two Approaches to Building an MCP Server
+
+### 1. Relying on the Existing API
+
+This seems like the obvious choice. However,  since those APIs were most likely designed a long time before the LLM was born, they might not be suitable for the LLM to consume.  One thing is that the semantics of the parameters and return data might not be self-explanatory enough for the LLM to understand; The other thing is that the API might not be flexible enough to provide the functionality to support what the user wants to achieve. 
+
+### 2. Start from Scratch
+
+For those considering this approach, the biggest concern is probably time.   Don’t worry,  we never really build from scratch, do we?  The MCP is evolving fast, and so is the whole ecosystem. Let me show you the modern stack that could dramatically reduce the code you need to write to speed it up.
+
+- [MCP TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk)
+    
+    It fully implements the MCP specification, including Authorization. It provides a strong abstraction, freeing you from wrestling with low-level protocol implementation so you can focus on the business logic. 
+    
+- [ZenStack](https://zenstack.dev/)
+    
+    ZenStack is a schema-first TypeScript toolkit on top of Prisma ORM.  The core part is a ZModel DSL that unifies data modeling and access control.  Here is an example of what a simple blog post looks like:
+    
+    ```tsx
+    model User {
+      id            Int                 @id @default(autoincrement())
+      email         String              @unique
+      name          String?
+      password      String              @password @omit
+      posts         Post[]
+    
+      @@allow('read', true)
+    }
+    
+    model Post {
+      id        Int      @id @default(autoincrement())
+      createdAt DateTime @default(now())
+      updatedAt DateTime @updatedAt
+      title     String
+      content   String?
+      published Boolean  @default(false)
+      viewCount Int      @default(0)
+      author    User?    @relation(fields: [authorId], references: [id])
+      authorId  Int?     @default(auth().id)
+    
+      @@allow('all', auth() == author)
+      // logged-in users can view published posts
+      @@allow('read', auth() != null && published)
+    }
+    ```
+    
+    Based on the ZModel schema, ZenStack automatically creates well-structured, type-safe, and authorized CRUD APIs for your database. MCP's core value lies in its tools, which fundamentally consist of schema definitions and executable operations—specifically, the CRUD operations on your database for most SaaS applications. ZenStack can generate both directly from its schema, which can be safely called by LLM thanks to the access control policy. Thus, your primary task is to define the schema, and ZenStack manages everything else.
+    
+
+## Building an MCP Server from Scratch
+
+So let me walk you through the process of turning your database into an MCP server with OAuth-based authentication and authorization from scratch. I will use the blog post mentioned above as a sample; you can adapt it for your app, and all you need to do is change the ZModel schema accordingly.  
+
+You can find the final running project below:
+
+[https://github.com/jiashengguo/zenstack-mcp-auth](https://github.com/jiashengguo/zenstack-mcp-auth)
+
+### Initialize the Project
+
+Use the below script to initialize a project with Prisma and Express:
+
+```bash
+npx try-prisma@latest -t orm/express -n blog-app-mcp
+```
+
+Install the MCP SDK,  bcrypt(used to hash password):
+
+```bash
+npm install @modelcontextprotocol/sdk, bcrypt, @types/bcrypt
+```
+
+Initialize ZenStack:
+
+```bash
+npx zenstack@latest init
+```
+
+### Auth
+
+To support the OAuth, the server needs to store the following information:
+
+- OAuth Client
+- Authorization Code
+- Access Token
+- Refresh Token
+
+So let’s add them in the ZModel schema file so that we can use the generated type-safe API to operate on them:
+
+```tsx
+model OAuthClient {
+  id                       Int      @id @default(autoincrement())
+  client_id                String   @unique
+  client_secret            String?
+  ...
+}
+
+model AuthorizationCode {
+  id            Int      @id @default(autoincrement())
+  code          String   @unique
+  ...
+}
+
+model AccessToken {
+  id        Int      @id @default(autoincrement())
+  token     String   @unique
+  ...
+}
+
+model RefreshToken {
+  id        Int      @id @default(autoincrement())
+  token     String   @unique
+  ...
+}
+```
+
+According to the MCP specification,  here are the three endpoints that the server must implement:
+
+| Endpoint | Url | Usage |
+| --- | --- | --- |
+| Authorization | /authorize | Used for authorization requests |
+| Token | /token | Used for token exchange & refresh |
+| Registration | /register | Used for dynamic client registration |
+
+MCP SDK provides a utility function, `mcpAuthRouter`, to install these standard authorization server endpoints.   What we need to do is implement an `OAuthServerProvider` to pass it to the `mcpAuthRouter`.  
+
+Let’s create an `AuthMiddleware` class to handle all the Auth logic, and a `PasswordAuthProvider` implementing the `OAuthServerProvider` to do the actual logic. 
+
+```tsx
+export class AuthMiddleware {
+   private authProvider: PasswordAuthProvider;
+   private authRouter: express.Router = express.Router();
+   ...
+    private setupRouter() {
+        // Add OAuth router using the mcpAuthRouter function
+        this.authRouter.use(
+            '/',
+            mcpAuthRouter({
+                provider: this.authProvider,
+                issuerUrl: new URL(config.baseUrl),
+                baseUrl: new URL(config.baseUrl),
+                scopesSupported: ['read', 'write'],
+            })
+        );
+        ...
+    }
+}
+```
+
+#### PasswordAuthProvider
+
+Here are the three functions that will actually be called, corresponding to the three necessary endpoints  mentioned above:
+
+```tsx
+export interface OAuthServerProvider {
+		// /register
+    get clientsStore(): OAuthRegisteredClientsStore;
+    
+    // /authorize
+    authorize(client: OAuthClientInformationFull, params: AuthorizationParams, res: Response): Promise<void>; 
+
+    // /token
+    exchangeAuthorizationCode(client: OAuthClientInformationFull, authorizationCode: string, codeVerifier?: string, redirectUri?: string): Promise<OAuthTokens>;
+}
+```
+
+The `clientStore` deals with register and read through `registerClient` and `getClient` functions, it simply writes and reads the `OAuthClient` model from the database. 
+
+The actual authorization flow begins with `authorize`.  Its job is to redirect to the actual authorization server with all the necessary parameters.   Since in our case, the authorization server is itself,  let’s redirect to the`/auth/login` path:
+
+```tsx
+      const loginUrl = new URL('/auth/login', config.baseUrl);
+      ...
+      res.redirect(loginUrl.toString());
+```
+
+Therefore, we need to create a `login.html`  to serve it.     It’s a typical login form like below: 
+
+![Login-Page](https://github.com/user-attachments/assets/ca2ffe72-82c2-4a3b-a65f-c8919c06e86f)
+
+After clicking Login, it combines all the URL parameters passed from the authorization flow along with the email and passwords to post to the server endpoint `/auth/login`.
+
+The server endpoint `/auth/login` would eventually call into the function `handlelogin` of  `PasswordAuthProvider`.   It verifies the user identity. If it is correct, then create an auth code, return to the client, and store it in an `AuthorizationCode` instance in the database
+
+```tsx
+  // Generate authorization code
+  const authCode = crypto.randomBytes(32).toString('hex');
+
+  // Store authorization code in database
+  await this.prisma.authorizationCode.create({
+      data: {
+          code: authCode,
+          clientId,
+          userId: user.id,
+          codeChallenge,
+          redirectUri,
+          expiresAt: new Date(Date.now() + 600000), // 10 minutes
+          scopes,
+      },
+  });
+```
+
+After getting the auth code returned from the server, `login.html` will redirect to the MCP client with the auth code.    Finally, the MCP client will use the auth code to call the `/token` endpoint to exchange for the access token, which will call into the `exchangeAuthorizationCode` function.      
+
+In `exchangeAuthorizationCode`, the server will first verify that the auth code it just granted is valid, and the client’s identification, such as PKCE.   If everything is correct, it will generate both access token and refresh token,  store them in the database, and return to the MCP client. 
+
+```tsx
+// Store tokens in database
+await this.prisma.accessToken.create({
+    data: {
+        token: accessToken,
+        clientId: client.client_id,
+        userId: authData.userId,
+        scopes: authData.scopes as any,
+        expiresAt,
+    },
+});
+
+await this.prisma.refreshToken.create({
+    data: {
+        token: refreshToken,
+        clientId: client.client_id,
+        userId: authData.userId,
+        scopes: authData.scopes as any,
+        expiresAt: new Date(Date.now() + 86400 * 30 * 1000), // 30 days
+    },
+});
+
+// Clean up authorization code
+await this.prisma.authorizationCode.delete({
+    where: { code: authorizationCode },
+});
+
+return {
+    access_token: accessToken,
+    token_type: 'Bearer',
+    expires_in: expiresIn,
+    refresh_token: refreshToken,
+    scope: (authData.scopes as string[]).join(' '),
+};
+```
+
+### Stateful Multi-connections Streamable HTTP Server
+
+After the MCP client gets the access token,  each time it communicates with the server through the main endpoint, it will put the access token in the Authorization header.  The server should use it to verify the client and get the client’s identity.    Let’s choose the conventional `/mcp` endpoint as the main endpoint and `getFlexibleAuthMiddleware` to do so. 
+
+The first request sent by the MCP client to the server is an **`initialize`** request. The server should respond with server information and a generated session ID, which the client would always include to maintain the session.  As a production-ready MCP server, it definitely should support multiple simultaneous connections.   Therefore, we use a map to store each client by its session ID. 
+
+```tsx
+const transports: { [sessionId: string]: StreamableHTTPServerTransport } = {};
+```
+
+To manage the session for each user, we will create a dedicated `MCPServer` for it.  Since it’s per user per MCP server, we could store the `userId` in it, which will be used for Authorization when executing tools. 
+
+```tsx
+if (sessionId && transports[sessionId]) {
+    transport = transports[sessionId];
+  } 
+else if (!sessionId && isInitializeRequest(req.body)) {
+    // Handle new MCP connection initialization
+    transport = new StreamableHTTPServerTransport({
+        sessionIdGenerator: () => crypto.randomUUID(),
+        onsessioninitialized: (sessionId: string) => {
+            console.log(`New MCP session initialized: ${sessionId}, User ID: ${userId}`);
+            transports[sessionId] = transport!;
+        },
+    });
+
+    const mcpServer = createMCPServer(userId);
+    await mcpServer.connect(transport);
+    console.log(`MCP server connected for User ID: ${userId}`);
+    // Handle connection close
+    transport.onclose = () => {
+        if (transport?.sessionId) {
+            console.log(`MCP session closed: ${transport.sessionId}`);
+            delete transports[transport.sessionId];
+        }
+    };
+}
+else {
+  // invalid request
+  ...
+}
+// Handle the request
+await transport.handleRequest(req, res, req.body);
+```
+
+### Tool
+
+We will fully rely on the ZenStack to do the job.   Remember the two parts of the Tool: schema and execution. 
+
+#### Schema
+
+ZenStack has a native Zod plugin to generate the Zod schema for its CRUD API.  We can enable it by adding the one below to the `schema.zmodel` :
+
+```tsx
+plugin zod {
+    provider = '@core/zod'
+}
+```
+
+Then, after running `zenstack generate`,  it generates the Zod schema for all the operations it supports for every model in `@zenstackhq/runtime/zod/input`.  For example, this is the one for `Post` :
+
+```tsx
+import { z } from 'zod';
+import type { Prisma } from '.zenstack/models';
+declare type PostInputSchemaType = {
+    findUnique: z.ZodType<Prisma.PostFindUniqueArgs>;
+    findFirst: z.ZodType<Prisma.PostFindFirstArgs>;
+    findMany: z.ZodType<Prisma.PostFindManyArgs>;
+    create: z.ZodType<Prisma.PostCreateArgs>;
+    createMany: z.ZodType<Prisma.PostCreateManyArgs>;
+    delete: z.ZodType<Prisma.PostDeleteArgs>;
+    deleteMany: z.ZodType<Prisma.PostDeleteManyArgs>;
+    update: z.ZodType<Prisma.PostUpdateArgs>;
+    updateMany: z.ZodType<Prisma.PostUpdateManyArgs>;
+    upsert: z.ZodType<Prisma.PostUpsertArgs>;
+    aggregate: z.ZodType<Prisma.PostAggregateArgs>;
+    groupBy: z.ZodType<Prisma.PostGroupByArgs>;
+    count: z.ZodType<Prisma.PostCountArgs>;
+};
+```
+
+So each operation for each model will become a Tool of our MCP server. 
+
+#### Execution
+
+The execution of each function is very straightforward; just dynamically invoke the function with the argument generated by the LLM.   Therefore, the whole Tool creating logic is simply one lambda expression in less than 30 lines of code:
+
+```tsx
+import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
+import crudInputSchema from '@zenstackhq/runtime/zod/input';
+...
+export function createMCPServer(userId: number) {
+    const server = new McpServer(
+        ...
+    )
+    Object.entries(crudInputSchema)
+        .filter(([name]) => modelNames.includes(getModelName(name)))
+        .forEach(([name, functions]) => {
+            const modelName = getModelName(name);
+
+            Object.entries(functions as Record<string, any>)
+                .filter(([functionName]) => functionNames.includes(functionName))
+                .forEach(([functionName, schema]) => {
+                    const toolName = `${modelName}_${functionName}`;
+                    server.tool(
+                        toolName,
+                        `Prisma client API '${functionName}' function input argument for model '${modelName}'. ${currentUserPrompt}`,
+                        {
+                            args: schema,
+                        },
+                        async ({ args }) => {
+                            console.log(`Calling tool: ${toolName} with args:`, JSON.stringify(args, null, 2));
+                            const prisma = getPrisma(userId);
+                            const data = await (prisma as any)[modelName][functionName](args);
+                            console.log(`Tool ${toolName} returned:`, JSON.stringify(data, null, 2));
+                            return {
+                                content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
+                            };
+                        }
+                    );
+                });
+        });
+
+    return server;
+}
+```
+
+One thing that needs to be mentioned is that the Prisma client that is used to call that operation is the ZenStack-`enhanced` one that contains the current user identity, which is the `userId` stored in the `MCPServer` during initialization.  This will completely eliminate unauthorized data access, no matter what parameters LLM generates for the function, even if hallucination happens:
+
+```tsx
+import { enhance } from '@zenstackhq/runtime';
+// Gets a Prisma client bound to the current user identity
+export function getPrisma(userId: number | null) {
+    const user = userId ? { id: userId } : undefined;
+    return enhance(prisma, { user });
+}
+```
+
+There is another benefit that since these Tools are actually the standard Prisma Client API, it works well for the LLM:
+
+- First, its declarative and well-structured nature makes it easy to understand and reason about, allowing LLMs to generate accurate and predictable queries with minimal ambiguity
+- Second, the API's flexibility allows one single call to access multiple models (database tables), enabling LLMs to efficiently navigate complex data relationships and perform sophisticated queries across different entities without requiring separate API calls or complex orchestration logic.
+- Third, Prisma has been widely adopted for years, providing a rich ecosystem of documentation, examples, and community usage that LLMs can learn from—greatly increasing the chance of generating correct and context-aware code.
+
+## Test
+
+The sample project includes seed data for you to play around with. Simply run the below command to make it ready:
+
+```bash
+npx prisma db push
+npx prisma db seed
+```
+
+It creates three users with posts. The passwords for all users are `password123`.
+
+- [alex@zenstack.dev](mailto:alex@zenstack.dev)
+- [sarah@stripe.com](mailto:sarah@stripe.com)
+- [jordan@vercel.com](mailto:jordan@vercel.com)
+
+Enjoy playing it with whatever MCP client of your choice: 
+
+![VSCode-Copilot](https://github.com/user-attachments/assets/3e59af8c-e7cf-4ed8-a953-7085e811faa2)
+
+I’m really eager to hear about the results you achieved with your application! If you’re looking for any tips on writing the ZModel schema, please feel free to reach out to [me](https://x.com/jiashenggo) or hop onto [our Discord](https://discord.gg/Ykhr738dUe). I’m always willing to help!
diff --git a/blog/database-to-the-public/index.md b/blog/database-to-the-public/index.md
index 9f4a9d62..981526f8 100644
--- a/blog/database-to-the-public/index.md
+++ b/blog/database-to-the-public/index.md
@@ -69,7 +69,7 @@ As running web workload becomes the prevailing use case of databases, some new-g
 
 -   [Supabase](/blog/supabase)
 -   [PostgREST](/blog/postgrest)
--   [ZenStack](blog/prisma-zenstack)
+-   [ZenStack](/blog/prisma-zenstack)
 -   [Hasura](https://hasura.io)
 -   [WunderGraph](https://wundergraph.com/)
 
diff --git a/blog/find-user/index.md b/blog/find-user/index.md
index 4fcb03ae..12b02799 100644
--- a/blog/find-user/index.md
+++ b/blog/find-user/index.md
@@ -124,7 +124,7 @@ Once again, a peek time and is still going up. 😄
 
 ZenStack could also generate the tRPC route, which is based on the awesome work of [Omar Dulaimi](https://github.com/omar-dulaimi/prisma-trpc-generator). We showed our gratitude by buying some coffee for him. he gave us a star later. One of his followers noticed it and decided to check out ZenStack. The first thing he said in our Discord was:
 
-> Zenstack seems like a dream come true
+> ZenStack seems like a dream come true
 
 After talking with him, we found out that he is the perfect match for our ideal user portrait. And he reported 3 bugs we might never be able to find by ourselves just on the first day. Isn't it incredible how a small action can create such a positive impact, much like the butterfly effect?😄
 
diff --git a/blog/fullstack-zenstack/index.mdx b/blog/fullstack-zenstack/index.mdx
index 62272db9..14bb9702 100644
--- a/blog/fullstack-zenstack/index.mdx
+++ b/blog/fullstack-zenstack/index.mdx
@@ -182,4 +182,4 @@ model Order{
 }
 ```
 
-If this is what you need or you have any other pain point hope could be solved, welcome to join our [discord](https://go.zenstack.dev/chat) or discuss it in [GitHub](https://github.com/zenstackhq/zenstack). Let’s make a better [ZenStack](https://zenstack.dev) together.
+If this is what you need or you have any other pain point hope could be solved, welcome to join our [discord](https://discord.gg/Ykhr738dUe) or discuss it in [GitHub](https://github.com/zenstackhq/zenstack). Let’s make a better [ZenStack](https://zenstack.dev) together.
diff --git a/blog/furture-fullstack/cover.jpeg b/blog/furture-fullstack/cover.jpeg
new file mode 100644
index 00000000..0a6756b4
Binary files /dev/null and b/blog/furture-fullstack/cover.jpeg differ
diff --git a/blog/furture-fullstack/index.md b/blog/furture-fullstack/index.md
new file mode 100644
index 00000000..d846c863
--- /dev/null
+++ b/blog/furture-fullstack/index.md
@@ -0,0 +1,90 @@
+---
+title: What will happen to the full-stack framework in the future?
+description: Illustrate some features of the full-stack framework that would evolve in the future
+tags: [webdev, zenstack, fullstack, AI]
+authors: jiasheng
+date: 2023-12-21
+image: ./cover.jpeg
+---
+
+# What will happen to the full-stack framework in the future?
+
+![Cover Image](cover.jpeg)
+
+## ~~Don't~~ Reinvent the Wheel
+
+As software developers, we are all familiar with the phrase "Don't reinvent the wheel". However, I have heard many complaints that the Javascript world seems to do the exact opposite. 😂
+<!--truncate-->
+From the positive side of the coin, there are always abundant choices at almost every level from the bottom up: 
+
+- Runtime: Node.js, Deno, Bun
+- Package Manager: Npm, Pnpm, Yarn
+- Bundler: Webpack, Rollup, Parcel, Vite, Turbopack
+- UI Framework: React, Vue, Svelte, Angular
+- FullStack framework: Next.js, Remix, Nuxt, Sveltekit
+
+![JavaScript-ecosystem](https://github.com/zenstackhq/zenstack/assets/16688722/90258209-5e61-47c7-8dc8-1e5b86e5e935)
+
+The negative side is that developers, especially those with less experience, may feel overwhelmed and caught in the crossfire of choices.  The recent debate between Remix and Next.js camps could well illustrate that: 
+
+[Why I Won't Use Next.js](https://www.epicweb.dev/why-i-wont-use-nextjs)
+
+[Why I'm Using Next.js](https://leerob.io/blog/using-nextjs)
+
+Although there are quite a few **opinionated** battery-included frameworks that have picked up everything for you like [RedwoodJS](https://redwoodjs.com/), [Blitz](https://blitzjs.com/), and [Create-T3-App](https://create.t3.gg/),  you still need to choose between them and hope that they will remain mainstream and well-maintained in the future.  So how should we choose?
+
+In "The 7 Habits of Highly Effective People," the habit that resonates most with me is the second one: 
+
+**Begin With the End in Mind**
+
+When we find ourselves lacking a clear vision in the present, one effective strategy is to envision what the framework could potentially evolve into in the future.
+
+## Features of the Future Framework
+
+### Abstraction of the Data Layer
+
+Bezos has a famous quote about the success of Amazon: 
+
+> So people today want fast shipping. And they’re going to want faster and faster shipping in 10 years… so that’s going to matter today and tomorrow.
+
+It means focusing on things that don’t change.  While technologies and methodologies continuously shift and evolve, the fundamental need to store, manage, and retrieve data consistently persists.  So you still need to have at least a database to achieve so.  
+
+Even now, fewer full-stack developers are willing to directly talk with databases through the complexities of SQL queries and database schema management, let alone the ones that come from the front-end world.  Therefore ORM has already been the standard kit for the existing framework.  For instance, all three frameworks mentioned above have adopted [Prisma ORM](https://www.prisma.io/).
+
+Furthermore, when it comes to scaling an application, it's common to think about the complexity of the code. However, in reality, the underlying data layer is often the root cause of scaling issues. If the data layer is tightly coupled with the application code, and as the application grows, the relationships between data entities become increasingly complex and difficult to manage. This challenge is even more pronounced in a full-stack environment where the frontend and backend are tightly coupled. Therefore, the abstraction layer that ORM provides ensures consistency across different layers of the application and facilitates efficient communication between the frontend and backend.
+
+### Write Less Code and Utilize Code Generation
+
+Writing less code not only saves time and energy from developers but also makes the application better to scale and maintain:
+
+> a hundred lines of code is easier to scale than a thousand lines of code
+
+That's exactly the job of the framework to handle more things that used to be implemented by ourselves like Routing, Data Fetching, Rendering Error Handling, etc. 
+
+"**Writing less boilerplate code**" has become the slogan for almost every framework.  They usually provide the scaffold CLI to generate all kinds of stuff for you, including those that were previously considered essential like API. 
+
+Since the ORM has defined the data models, which include the data types, relationships, and constraints,  it could automatically generate meaningful APIs from it.  It allows developers to gain more bandwidth to concentrate on the business logic instead of dedicating manual API implementation.  
+
+For example, RedwoodJS generates the GraphQL API using the types generated from Prisma. If you are using [ZenStack](https://zenstack.dev/?utm_campaign=devto&utm_medium=organic&utm_content=future-fullstack), which is built on top of Prisma, it can generate both RESTful-style and RPC-style APIs with Swagger documentation or tRPC routers.
+
+### More Declarative for AI
+
+Austrian economist [Joseph Schumpeter](https://www.investopedia.com/terms/j/joseph-schumpeter.asp) is most known for coining the phrase "**creative destruction**" which describes the process that sees new innovations replacing existing ones that are rendered obsolete over time. The analogy could be: 
+
+> no matter how much improvement is made to a wagon, it can never become a car.
+
+Since we are discussing the next generation of the framework, we would like to see some innovations that can significantly improve productivity, similar to the leap from a wagon to a car.
+
+What is the strong force that could make this happen? Take a look at the list of the new YC batch W24, and you will find out 😄
+
+[The YC Startup Directory | Y Combinator](https://www.ycombinator.com/companies?batch=W24)
+
+Yes, definitely AI. I think the more declarative the framework could be, the better AI could understand the application and help to manipulate it as much as possible.   The reason is that the declarative approach is a paradigm where you write code by describing **WHAT** you want to do rather than **HOW**.  That’s exactly what we are talking to ChatGPT every day.   
+
+You can find the POC from the declarative framework [Wasp](https://wasp-lang.dev/):
+
+[MAGE GPT Web App Generator ✨ MageGPT](https://usemage.ai/)
+
+---
+
+Of course, these are just some aspects I think are important. I would love to hear your thoughts or any opposing viewpoints. Please feel free to leave comments or reach out to me on Twitter!
\ No newline at end of file
diff --git a/blog/good-dx/index.md b/blog/good-dx/index.md
index 72b1f475..a9578d21 100644
--- a/blog/good-dx/index.md
+++ b/blog/good-dx/index.md
@@ -1,7 +1,7 @@
 ---
 title: 'How to make good DX(Developer Experience): Empathize'
 description: Why DX matters and how to create great DX toolkit for developers.
-tags: [DX, zensatck]
+tags: [DX, zenstack, programming]
 authors: jiasheng
 date: 2023-03-24
 image: ./cover.png
@@ -138,4 +138,4 @@ Therefore, people could “get it” easily. Simultaneously, we need to ensure t
 
 ## Last Takeaway
 
-To truly understand and empathize with developers, the most efficient approach is to communicate with them directly. Therefore, if you want to assist [ZenStack](https://zenstack.dev) in creating the most optimal DX for developing web applications, why not engage with us in [GitHub](https://github.com/zenstackhq/zenstack) or [Discord](https://go.zenstack.dev/chat)?
+To truly understand and empathize with developers, the most efficient approach is to communicate with them directly. Therefore, if you want to assist [ZenStack](https://zenstack.dev) in creating the most optimal DX for developing web applications, why not engage with us in [GitHub](https://github.com/zenstackhq/zenstack) or [Discord](https://discord.gg/Ykhr738dUe)?
diff --git a/blog/how-to-authz-2/cover.jpg b/blog/how-to-authz-2/cover.jpg
new file mode 100644
index 00000000..a2801f29
Binary files /dev/null and b/blog/how-to-authz-2/cover.jpg differ
diff --git a/blog/how-to-authz-2/final-quadrant.png b/blog/how-to-authz-2/final-quadrant.png
new file mode 100644
index 00000000..b52715db
Binary files /dev/null and b/blog/how-to-authz-2/final-quadrant.png differ
diff --git a/blog/how-to-authz-2/index.md b/blog/how-to-authz-2/index.md
new file mode 100644
index 00000000..e8d2332a
--- /dev/null
+++ b/blog/how-to-authz-2/index.md
@@ -0,0 +1,112 @@
+---
+title: 'How to Do Authorization - A Decision Framework: Part 2'
+description: A comparison of different approaches to implement authorization - those run inside your application and those as standalone services.
+tags: [authorization, webdev]
+authors: yiming
+date: 2024-02-05
+image: ./cover.jpg
+---
+
+# How to Do Authorization - A Decision Framework: Part 2
+
+![Cover Image](cover.jpg)
+
+In the [previous post](/blog/how-to-authz), we discussed two different approaches to implementing authorization: coupled with the database and decoupled from it. In this post, we'll focus on the other axis of the quadrant: authorization embedded inside the application vs. as a separate service. Let's complete the second half of the quadrant.
+
+<!-- truncate -->
+
+![Starting quadrant](starting-quadrant.png)
+
+## Embedded vs. As-a-Service
+
+If you implement AuthZ yourself, you'll likely just write them as part of your service code. So, the authorization runs as an embedded module of your application. Even if you choose to use a third-party solution, the default route seems to be finding a suitable library, installing it, and calling its APIs (functions) from your application code.
+
+However, with the cloud computing boom, everything can be offered as a cloud service - data storage, authentication, content management, payment, etc. Authorization is no exception. There are now many providers that provide authorization as standalone services.
+
+### Embedded
+
+An embedded AuthZ solution resides inside an existing service. The service is usually your application's backend, but it can also be something else (like a database).
+
+The most straightforward form of embedded AuthZ is libraries that help you model access control. In the previous part, we've seen how you can use [CASL](https://casl.js.org/) to define access rules and then check for permissions. [ZenStack](https://zenstack.dev) is also an embedded AuthZ, with a unique approach to wrap around Prisma to turn the ORM into a secure data access layer. You can find more solutions in library forms like [accesscontrol](https://github.com/onury/accesscontrol), [casbin](https://casbin.org/), etc.
+
+Another form of embedded AuthZ is those that are built-in features of databases. In the previous post, we've seen how Postgres's RLS feature allows you to define complex access control rules inside the database directly. [Firebase](https://firebase.google.com/) and [EdgeDB](https://www.edgedb.com/) are also excellent examples of this category.
+
+#### Pros & Cons
+
+Having AuthZ as part of an existing service has some clear benefits:
+
+- **Ease of operation**: it doesn't increase the number of services you need to manage. You don't need to worry about one more deployment environment.
+- **Debugging**: if AuthZ is embedded in your backend service, debugging is easy since you can see a combined stack trace and easily single-step deep into its logic as needed.
+- **Performance**: access control checks don't involve extra calls across the network. If the AuthZ is coupled with the database, it usually has optimal performance in controlling CRUD access to the database entities.
+
+Some drawbacks are:
+
+- **Hard to share**: AuthZ as libraries typically maintain their own state inside the hosting service. This makes it difficult to share a set of common access control policies across multiple services and keep them always in sync.
+- **Hard to scale**: if your application grows into hyper-scale, you'll likely find the AuthZ part to have its own sophisticated persistence, caching needs and its own performance characteristics. It makes more sense to spin it out as a scalable service.
+- **Language lock-in**: if you choose a library, you're usually locked into the language it supports.
+
+### As-a-Service
+
+AuthZ-as-a-Service is the kind of cloud service that's either dedicated to solving authorization problems or at least has AuthZ as a central part of it.
+
+AuthZ-as-a-Service became popular with the publication of the [Zanzibar paper](https://research.google/pubs/zanzibar-googles-consistent-global-authorization-system/) by Google. Zanzibar described how Google successfully built a global, consistent, and scalable authorization system that's used by many of its products, serving billions of users. Since then, many OSS and commercial products have implemented Zanzibar-like systems, like [Authzed](https://authzed.com/), [Ory Keto](https://www.ory.sh/keto/), [Warrant](https://warrant.dev/), etc., to name a few.
+
+It should be noted that AuthZ-as-a-Service is not just about Zanzibar. Products like [Hasura](https://hasura.io/) have started to provide AuthZ capabilities long before that.
+
+#### Pros & Cons
+
+Separating AuthZ as a standalone service brings us several benefits:
+
+- **Centralized access control**: if you have multiple applications with a shared authorization model, having a standalone AuthZ service is the best way to manage them in a centralized way.
+- **Language agnostic**: since it's a service (most likely RESTful or GraphQL), it's usually language agnostic.
+- **Scalability**: it's easier to scale a standalone service. Commercial services usually have built-in elasticity.
+
+But it also has its drawbacks:
+
+- **Synchronization complexity**: you need to make sure everything related to access control is synchronized to the service: a new user, a new resource, a new relationship, a new piece of rule, etc.
+- **Performance**: synchronization and permission queries must go across the network and are much slower than in-process access.
+- **Operational costs**: self-hosting a new service is often not trivial and involves constant care. Commercial services move the burden away from you at the cost of giving up control.
+
+## Decision Factors
+
+Running AuthZ as an in-process module and as a service has vast differences. I'll list a few factors that you can use to decide which way to go.
+
+**Signs for choosing embedded**:
+
+- It should be the default choice for small to medium-sized applications.
+- Your AuthZ requirements are deeply coupled with the database.
+
+**Signs for choosing as-a-service**:
+
+- You have multiple applications that need to share the same authorization scheme.
+- Your backend services are implemented in multiple languages.
+- You want to avoid building a backend completely and will leverage a BaaS (Backend-as-a-Service). AuthZ is usually a central pillar of a BaaS system.
+
+## The Final Quadrant
+
+We've now completed our dichotomy of AuthZ solutions along the second axis - embedded vs. as-a-service. Here's the final quadrant with products and projects in their respective categories.
+
+![Final quadrant](final-quadrant.png)
+
+You've probably noticed the bottom right corner is quite empty. One of the most significant benefits of AuthZ-as-a-Service is managing access control across multiple services or applications. Individual service or application may have their private database, so it's not practical to make the AuthZ directly couple with any of them.
+
+---
+
+### Solution Catalog
+
+- [accesscontrol](https://github.com/onury/accesscontrol)
+- [authzed](https://authzed.com/)
+- [casbin](https://casbin.org/)
+- [CASL](https://casl.js.org/)
+- [cerbos](https://cerbos.dev/)
+- [edgedb](https://www.edgedb.com/)
+- [firebase](https://firebase.google.com/)
+- [hasura](https://hasura.io/)
+- [ory/keto](https://www.ory.sh/keto/)
+- [Permit.io](https://www.permit.io/)
+- [Permify](https://www.permify.co/)
+- [PostGraphile](https://www.graphile.org/postgraphile)
+- [PostgREST](https://postgrest.org/)
+- [Supabase](https://supabase.com/)
+- [Warrant](https://warrant.dev/)
+- [ZenStack](https://zenstack.dev/)
diff --git a/blog/how-to-authz-2/starting-quadrant.png b/blog/how-to-authz-2/starting-quadrant.png
new file mode 100644
index 00000000..9dd0e64d
Binary files /dev/null and b/blog/how-to-authz-2/starting-quadrant.png differ
diff --git a/blog/how-to-authz/cover.png b/blog/how-to-authz/cover.png
new file mode 100644
index 00000000..0a907ba3
Binary files /dev/null and b/blog/how-to-authz/cover.png differ
diff --git a/blog/how-to-authz/empty-quadrant.png b/blog/how-to-authz/empty-quadrant.png
new file mode 100644
index 00000000..f763ac18
Binary files /dev/null and b/blog/how-to-authz/empty-quadrant.png differ
diff --git a/blog/how-to-authz/index.md b/blog/how-to-authz/index.md
new file mode 100644
index 00000000..d986dd4c
--- /dev/null
+++ b/blog/how-to-authz/index.md
@@ -0,0 +1,288 @@
+---
+title: 'How to Do Authorization - A Decision Framework: Part 1'
+description: A comparison of different approaches to implement authorization - those coupled with the database and those decoupled.
+tags: [authorization, webdev]
+authors: yiming
+date: 2023-12-14
+image: ./cover.png
+---
+
+# How to Do Authorization - A Decision Framework: Part 1
+
+![Cover Image](cover.png)
+
+The security of most applications depends on two pillars: authentication (AuthN) and authorization (AuthZ). The former checks if the user is who one claims to be, and the latter governs what one can do in the system.
+
+Authentication is a well-understood problem. After many years of evolution, the industry has summarized clear patterns you can adopt and best practices to follow. You'll find mature libraries and services available for all major programming languages, whether with credentials, magic links, OTP, MFA, or OAuth. Once you settle on a solution, it tends to be stable and rarely needs much change over time.
+
+Authorization, on the contrary, is a whole different story. Concepts like RBAC and ABAC are well-known and widely accepted. However, how to implement them is vaguely defined, leaving developers a lot of room for creativity and mistakes. Authorization is a hard problem because it's often deeply coupled with the unique characteristics of your application, making it hard to have a one-size-fits-all solution.
+
+<!-- truncate -->
+
+This post aims to set up a decision framework for how to implement authorization in an application by using two dichotomy axes:
+
+- Should it be coupled with the database or not?
+- Should it be embedded in the application or as a separate service?
+
+I’ll explain the pros and cons of each side of the axis and explore existing solutions that fit into the category. You can then try placing your needs into the quadrant and see which is the most sensible choice to make.
+
+![Empty quadrant](empty-quadrant.png)
+
+## Data Model vs. Authorization Model
+
+One of the main reasons why authorization is complex is because it's often tangled with your business models. For example, in a CMS system, you may have `User`, `Group`, `Image`, `Video`, `Article`, `Page`, etc. models, and authorization likely needs to touch all of them. We can divide solutions into two categories by inspecting the relationship between authorization and data storage.
+
+### Coupled With Database
+
+So, an intuitive solution is just to overlay authorization with the data models. For our CMS example, if we were to implement a simple ACL-based authz, we can introduce an `AccessControlList` model, let it have relations to other models, and store authorization information as part of our application data. We can then ask permission questions like “give me the list of `Article` readable to the current user” with plain SQL queries (assuming using a SQL database), like:
+
+```sql
+SELECT DISTINCT Article.*
+FROM Article
+JOIN AccessControlList ON AccessControlList.resource_id = Article.id
+LEFT JOIN USER ON AccessControlList.user_id = User.id
+LEFT JOIN GROUP ON AccessControlList.group_id = Group.id
+WHERE (User.id = CURRENT_USER_ID
+       OR Group.id IN
+         (SELECT group_id
+          FROM UserGroup
+          WHERE user_id = CURRENT_USER_ID))
+  AND AccessControlList.permission = 'read';
+```
+
+Before getting to know any fancy authorization solutions, we all started solving authorization like the above: constructing proper queries with imperative code. Although conceptually simple, it's error-prone and hard to evolve for even slightly complicated applications. Fortunately, some great solutions exist to help you model authorization at the database level in a more scalable way.
+
+#### 1. Google Firebase
+
+[Google Firebase](https://firebase.google.com) is one of the pioneers to integrate access policies into the database. It's a NoSQL data store that holds documents in collections and allows you to define security rules for object access. When making queries and mutations, Firebase checks if the request operates over objects the user can access and rejects it if it does not.
+
+```sql
+service cloud.firestore {
+  match /databases/{database}/documents {
+    
+    // Matches any document in the 'articles' collection
+    match /articles/{articleId} {
+      // Allows read access if an ACL entry exists that grants read access to this user or a group they are part of
+      allow read: if isUserAuthorizedToRead(articleId);
+    }
+
+    // Helper function to check if a user is authorized to read an article
+    function isUserAuthorizedToRead(articleId) {
+      // Check if there's an ACL entry for this user and article with read access
+      let userAcl = exists(/databases/$(database)/documents/accessControlList/$(request.auth.uid) + '_' + articleId);
+      if (userAcl) return true;
+
+      // If not, check each group the user is part of to see if any have read access
+      let groups = get(/databases/$(database)/documents/users/$(request.auth.uid)).data.groups;
+      for (let group in groups) {
+        let groupAcl = exists(/databases/$(database)/documents/accessControlList/$(group) + '_' + articleId);
+        if (groupAcl) return true;
+      }
+      return false;
+    }
+  }
+}
+```
+
+#### 2. PostgreSQL row-level-security
+
+PostgreSQL offers a feature called “[row-level-security](https://www.postgresql.org/docs/current/ddl-rowsecurity.html)”, allowing you to define fine-grained access policies with SQL. During CRUD, the database engine automatically enforces the policies in the context of the current user.
+
+```sql
+CREATE POLICY user_access_policy ON Article
+FOR SELECT
+USING (
+  EXISTS (
+    SELECT 1 FROM AccessControlList
+    WHERE 
+      AccessControlList.resource_id = Article.id AND
+      AccessControlList.user_id = current_user AND
+      AccessControlList.permission_type = 'read'
+  )
+);
+
+CREATE POLICY group_access_policy ON Article
+FOR SELECT
+USING (
+  EXISTS (
+    SELECT 1 FROM AccessControlList
+    JOIN user_groups ON AccessControlList.group_id = user_groups.group_id
+    WHERE 
+      AccessControlList.resource_id = Article.id AND
+      user_groups.user_id = current_user AND
+      AccessControlList.permission_type = 'read'
+  )
+);
+```
+
+A family of products leverages PostgreSQL's row-level-security capabilities to provide fully authorized data access, like [Supabase](https://supabase.com), [PostgREST](https://postgrest.org/), and [PostGraphile](https://www.graphile.org/postgraphile/).
+
+#### 3. ZenStack
+
+[ZenStack](https://github.com/zenstackhq/zenstack) takes a unique approach and solves the problem at a slightly higher level: the ORM. It is implemented above [Prisma ORM](https://www.prisma.io) and supports a wide variety of databases. It extends Prisma to allow modeling access policies inside the data schema and enforces them at runtime by injecting into Prisma queries.
+
+```zmodel
+model User {
+  ...
+  groups Group[]
+}
+
+model Group {
+  ...
+  members User[]
+}
+
+model AccessControlList {
+  user User?
+  userId Int?
+  group Group?
+  groupId Int?
+  article Article?
+  articleId Int?
+}
+
+model Article {
+  accessList AccessControlList[]
+
+  @@allow('read', 
+    accessList?[
+      // user access
+      user == auth() 
+      // group access
+      || accessList?[user.groups?[members?[id == auth().id]]]]
+  )
+}
+```
+
+#### Pros & Cons
+
+Coupling authorization with the database has several clear benefits:
+
+- **Single source of truth**: it avoids duplicating the data model on the authorization side. The database is the single source of truth for data and its access rules.
+    
+- **Performance**: performance is good because permission check and data fetching are combined. There’s no penalty caused by read-then-discard.
+    
+
+The drawbacks are obvious, too:
+
+- **Database lock-in**: Firebase and PostgreSQL’s features are bound to the specific data store types. ZenStack is more flexible with database choices, but still you’re limited to those supported by Prisma.
+    
+- **Deployment**: both Firebase and Postgres RLS require you to “deploy” policies to the data store - an extra burden for CI/CD.
+
+### Decoupled From Database
+
+More choices are on this side of the axis, including simple libraries and standalone services. These solutions provide APIs for you to model authorization policies separately from your data model. They usually define modeling primitives involving `Subject`, `Action`, `Condition`, etc. After constructing policies using these primitives, you can ask whether a user request should be allowed.
+
+Let’s explore a few examples falling into this category.
+
+#### 1. Libraries
+
+You can find numerous libraries dedicated to authorization, depending on the language you use. For example, [CASL](https://casl.js.org/) is a Javascript library that helps you model flexible authorization schemes utilizing a set of declarative APIs. The “give me the list of `Article` readable to the current user” problem can be modeled and queried like the following:
+
+```tsx
+function isReadable(acl, user) {
+  return acl.some(permission => {
+    return (
+      (permission.userId === user.id || user.groups.includes(permission.groupId)) 
+			&& permission.action === 'read'
+    );
+  });
+}
+
+const articleAbility = (user, article) => defineAbility((can) => {
+  if (isReadable(article.acl, user)) {
+    can('read', 'Article', { id: article.id });
+  }
+});
+
+const ability = articleAbility(user1, article1);
+ability.can('read', 'Article', article1);
+```
+
+The library is agnostic to how you get the policy rules. You can choose to hard-code them like the above, load them from the database, or even design a DSL to author them.
+
+#### 2. Authorization-as-a-Service
+
+The more sophisticated form of decoupled authorization is a standalone service. By definition, it's decoupled from the application's database and fully manages its own state. Authorization-as-a-Service was popularized by Google's [Zanzibar paper](https://research.google/pubs/zanzibar-googles-consistent-global-authorization-system/), designed to authorize billions of users to hundreds of interrelated services.
+
+Let's use [warrant.dev](https://warrant.dev) as an example. The system provides a set of REST APIs for you to define object types and access policies (called warrants). The general process is first to create object types using HTTP POST:
+
+```json
+{
+  "type": "article",
+  "relations": {
+    "viewer": {}
+  }
+}
+```
+
+Then create warrants:
+
+```json
+{
+  "objectType": "article",
+  "objectId": "1",
+  "relation": "viewer",
+  "subject": {
+    "objectType": "user",
+    "objectId": "d6ed6474-784e-407e-a1ea-42a91d4c52b9"
+  }
+}
+```
+
+Then you can make authorization verification requests:
+
+```json
+{
+  "warrants": [
+    {
+      "objectType": "article",
+      "objectId": "1",
+      "relation": "viewer",
+      "subject": {
+        "objectType": "user",
+        "objectId": "d6ed6474-784e-407e-a1ea-42a91d4c52b9"
+      }
+    }
+  ]
+}
+```
+
+#### Pros & Cons
+
+Decoupling authorization from the application's data model has several advantages:
+
+- **Separation of concerns**: you can let these two things evolve relatively independently and swap one implementation without affecting the other.
+    
+- **Isomorphic authorization**: libraries like [CASL](https://casl.js.org/) allow you to model authorization the same way in the back and frontend. Although you cannot trust the frontend's judgement for access control, it's very useful for rending UI components conditionally. Another scenario is authorizing access to first-party (database) and third-party (API) data. Using a database-independent authorization allows you to achieve a homogeneous implementation.
+    
+There are prices to pay when using decoupled authorization:
+
+- **Synchronization**: no matter whether you use a library or a service, you need to pull out information from your database and sync it to the authorization side, introducing extra complexity.
+    
+- **Performance**: although Authz libraries and services can be highly optimized for performance, in many cases, you can't avoid reading data out of the database and then discard it due to policy filtering.
+
+It should be noted that some decoupled solutions, like [CASL](https://casl.js.org/) and [cerbos](https://cerbos.dev), try to mitigate the performance problem by providing a way to generate data query filters (usually by integrating with ORM) based on authorization rules. You can merge the filters into the database query to achieve optimal performance.
+
+## Decision Factors
+
+The biggest difference between coupled and decoupled authorization is whether the database’s power can be leveraged to evaluate access policies.
+
+**Signs for choosing decoupled:**
+
+- You don’t need to load a large number of rows from the database to evaluate policies.
+- Your Authz requirements are too complicated to be translated to database queries.
+- The database you use doesn’t have an Authz solution available.
+
+**Signs for considering coupled:**
+
+- Access policy evaluation requires lots of database interactions so it’s best to let it happen at the database/ORM level.
+- Your data models evolve fast, and so do the Authz rules related to them. Having them tightly coupled ensures they’re always consistent.
+
+## The Quadrant
+
+We’ve seen how coupled and decoupled authorization solutions work together with their tradeoffs. To help you navigate through choices in each category, I’ve collected some noteworthy products/projects below. Please note that some products listed here, e.g., Hasura, are much more than authorization. But since AuthZ is the center pillar that supports its other features, I’ve included them, too. You can find a catalog at the end of this post.
+
+![Updated quadrant](updated-quadrant.png)
+
+In the [next part](/blog/how-to-authz-2/), we'll focus on the other axis of the quadrant: authorization embedded inside the application vs. as a separate service.
diff --git a/blog/how-to-authz/updated-quadrant.png b/blog/how-to-authz/updated-quadrant.png
new file mode 100644
index 00000000..2beb586a
Binary files /dev/null and b/blog/how-to-authz/updated-quadrant.png differ
diff --git a/blog/js-fullstack/cover.png b/blog/js-fullstack/cover.png
new file mode 100644
index 00000000..27afa93d
Binary files /dev/null and b/blog/js-fullstack/cover.png differ
diff --git a/blog/js-fullstack/index.md b/blog/js-fullstack/index.md
new file mode 100644
index 00000000..322dfe16
--- /dev/null
+++ b/blog/js-fullstack/index.md
@@ -0,0 +1,106 @@
+---
+title: 'PHP: Laravel, Ruby: Rails, JavaScript:?'
+description: Explain the histrocial reason why JavaScript ecosystem lacks a full-stack framework like Laravel or Rails and the contemporary endeavor for challenge the status quo.  
+tags: [webdev, javascript, php, rails]
+authors: jiasheng
+date: 2024-05-28
+image: ./cover.png
+---
+
+# PHP: Laravel, Ruby: Rails, JavaScript:?
+![Cover Image](cover.png)
+Recently, there has been a heated discussion on Twitter between JS developers and Laravel and Rails developers. It started with a lengthy tweet from Taylor Otwell, author of [Laravel](https://laravel.com/):
+<!-- truncate -->
+<div align="center">
+[![Taylor Otwell](https://github.com/zenstackhq/zenstack-docs/assets/16688722/67ecca64-e6e4-4033-bd29-e87dd3e408a1)](https://x.com/taylorotwell/status/1791468060903096422)
+</div>
+
+In short, he suggested that the whole JavaScript ecosystem lacks a real "full stack" framework like Laravel or Rails, which could allow a single developer to build the next GitHub, AirBnb, or Shopify.
+
+I deeply empathize with this, as I share the same goal when building ZenStack, the typescript toolkit on top of Prisma ORM. In fact, I have often heard that kind of word from our community:
+
+![community-chat](https://github.com/zenstackhq/zenstack/assets/16688722/a3c3a03a-b5af-49a6-ba79-0c8c845828d2)
+
+No one can deny the popularity and fast-paced growth of the JS ecosystem, even among non-members. So why is it?
+
+## The Historical Reason
+
+> Men make their own history, but they do not make it as they please; they do not make it under self-selected circumstances, but under circumstances existing already, given and transmitted from the past --Karl Marx
+
+Both PHP and Ruby were designed as server-side languages from the start. PHP was created in 1994 to build dynamic web pages, while Ruby, which appeared in the mid-1990s, was designed for general-purpose programming.
+
+Given their server-side origins, PHP and Ruby were naturally suited for comprehensive frameworks that could handle all aspects of web development, from routing and controllers to database interactions and templating engines. This led to the creation of frameworks like Laravel and Rails to offer a complete, opinionated way to build web apps.
+
+Contrastingly, JavaScript was born as a client-side scripting language for web browsers. It had nothing to do with the backend until 2009, when Node.js was introduced. If you've heard of the "Browser Wars" between Netscape Navigator and Internet Explorer, you're likely aware of the ongoing chaos in the frontend, which continues to drive front-end developers crazy today in the name of browser compatibility. As a result, the early web was about piecing together disparate technologies. Therefore, JavaScript developers became accustomed to modularity, allowing flexibility to mix and match libraries and tools for survival. That's why NPM, which emerged alongside Node.js, has grown at an astonishing rate, quickly becoming the largest software registry in the world.
+
+This different circumstances led to the different developer cultures:
+
+-   **PHP/Ruby devs:** "Give me a framework that just works. I want conventions, stability, and a clear path to shipping."
+-   **JS devs:** "Don't box me in! I want flexibility, the latest tools, and the freedom to build my way, even if it means more work upfront."
+
+As a result, even after expanding to the backend world, a monolithic, "one-size-fits-all" approach could hardly fly in the Javascript ecosystem.
+
+## Contemporary Endeavor
+
+On one hand, this culture leads to constant evolution, keeping the entire ecosystem exciting and innovative. However, it also results in more decision fatigue and a steeper learning curve for newcomers.
+
+"Where there's muck, there's brass." Some individuals embarked on an adventurous journey to build a Rails-like, battery-included framework to challenge the status quo. Here are some popular examples:
+
+-   [RedwoodJS](https://redwoodjs.com/)
+    
+    Full-stack JavaScript framework that integrates React, GraphQL, and Prisma. It simplifies development with a unified setup and automatic code generation, perfect for scalable applications.
+-   [Biltz.js](https://blitzjs.com/)
+    
+    Blitz.js extends Next.js into a full-stack framework featuring a zero-API data layer and Prisma for database access. It aims to simplify development by allowing direct server-side code calls from the frontend.
+-   [AdonisJS](https://adonisjs.com/)
+    
+    AdonisJS is a TypeScript-first web framework for building web apps and API servers. it offers a rich set of features out-of-the-box, including an ORM, authentication, and a powerful CLI, making it ideal for developers seeking a comprehensive and structured development environment.
+
+Will they become the Laravel or Rails of the JS world? It's probably too early to say, but at least RedwoodJS shows great momentum:
+
+![RedwoodJS-trending](https://github.com/zenstackhq/zenstack/assets/16688722/a9876a0b-0096-4a46-915a-a7c4edc13d30)
+
+Another bunch of guys are trying to solve this problem by providing “start kits” with **opinionated** battery-included toolkits. Among these, the most popular one is [Create-T3-App](https://create.t3.gg/), which combines Next.js, tRPC, Tailwind CSS, and other powerful tools to give you a solid foundation for building typesafe web applications.
+
+Interestingly, Theo, the creator of T3, seems to be pessimistic about this whole endeavor of JavaScript world:
+
+<div align='center'>
+[![theo](https://github.com/zenstackhq/zenstack-docs/assets/16688722/eb1df393-65d3-490a-8d1e-8395c0b80233)](https://x.com/t3dotgg/status/1792136001345003748)
+</div>
+
+## Optimistic Future
+
+> Any application that can be written in JavaScript, will eventually be written in JavaScript. — Jeff Atwood
+
+While I'm not entirely convinced by Atwood's law, I do envision a promising future for JavaScript in the field of web development. The reason is simple:
+
+**It’s the first time in history that the whole web app could be developed with one programming language.**
+
+This is a significant benefit, particularly for novice developers. Thanks to TypeScript's excellent type inference system, we are not only capable of doing it but also willing to do so.
+
+A common critique from Laravel or Rails users is that these frameworks lack a conventional method for modeling the relationship between different entities in your system like the below:
+<div align='center'>
+[![critique](https://github.com/zenstackhq/zenstack-docs/assets/16688722/49167aff-e37e-4603-a042-d1d0f2cf1d28)](https://x.com/chantastic/status/1791531154212033004)
+</div>
+
+While it may not have reached the level of Laravel or Rails, the current efforts in the JS world have recognized this issue. If you look at the toolkit of the solution mentioned above, you'll find a common name: [Prisma](https://www.prisma.io/)
+
+If you haven’t heard about Prisma, it is a modern TypeScript-first ORM that allows you to manage database schemas easily, make queries and mutations with great flexibility, and ensure excellent type safety. This empowers JavaScript developers with a level of data handling sophistication and ease of relationship modeling traditionally found in Laravel and Rails, much like Laravel’s Eloquent ORM.
+
+The [ZenStack](https://zenstack.dev/) toolkit I’m building on top of Prisma aims to narrow down the gap further. It adds an Authorization layer on top of the schema and then automatically generates both APIs and frontend hooks for you. So, put simply, once you're done with your schema, you're almost done with your backend. You can then choose whatever frontend framework, like React, Vue, or Svelte, to get your UI done.
+
+![ZenStack-schema](https://github.com/zenstackhq/zenstack/assets/16688722/43feb3ae-6d97-4ecb-b856-3695040eea7c)
+
+## Begin With the End in Mind
+
+Will JavaScript ever have its Laravel/Rails moment? Personally, I believe, or at least hope, that having standardized conventions leads to global optimization for the entire ecosystem. However, given the history and culture of JavaScript, achieving this may take a significant amount of time. It's unclear whether AI will accelerate this process or completely overturn it.
+
+So, it seems we just have to wait and see. However, let's not get lost in this debate and forget our original intention, as Lee Robinson says:
+
+<div align='center'>
+[![lee](https://github.com/zenstackhq/zenstack-docs/assets/16688722/97bb6b0e-ccd6-4f28-a824-b871abf6d802)](https://x.com/leeerob/status/1792215708715122752)
+</div>
+
+So, I will quote a statement from the [W3C Web Platform Design Principles](https://www.w3.org/TR/design-principles/#priority-of-constituencies) at the end:
+
+> User needs come before the needs of web page authors, which come before the needs of user agent implementors, which come before the needs of specification writers, which come before theoretical purity.
diff --git a/blog/json-typing/cover.png b/blog/json-typing/cover.png
new file mode 100644
index 00000000..4042098a
Binary files /dev/null and b/blog/json-typing/cover.png differ
diff --git a/blog/json-typing/index.md b/blog/json-typing/index.md
new file mode 100644
index 00000000..58c45b68
--- /dev/null
+++ b/blog/json-typing/index.md
@@ -0,0 +1,169 @@
+---
+title: "Typing Those JSON Fields? Yes, You Can!"
+description: This post introduces the new strongly typed JSON field feature in ZenStack.
+tags: [prisma]
+authors: yiming
+date: 2024-11-06
+image: ./cover.png
+---
+
+# Typing Those JSON Fields? Yes, You Can!
+
+![Cover Image](cover.png)
+
+SQL databases provide us with many benefits, the most important of which is strong schema enforcement. Yes, you pay the cost of migration when the schema changes, but the gain is far more significant - your code is clean because it can assume all data are in correct shapes.
+
+However, once in a while, we want to break free from such strong guarantees for valid reasons. You may have some tiny objects that you want to attach to the main entities (e.g., metadata of an image) without formalizing them into a separate table. Or you need to store records with many possible sparse fields but want to avoid creating wide tables.
+
+<!-- truncate -->
+
+Prisma's JSON type provides a generic escape hatch for such scenarios. It allows storing arbitrary data and gives you a generic `JsonValue` type in the query results.
+
+```zmodel title="schema.prisma"
+model Image {
+  id Int @id @default(autoincrement())
+  metadata Json
+}
+```
+
+```ts title="main.ts"
+type Metadata {
+  width: number
+  height: number
+  format: string
+}
+
+const image = await prisma.image.findFirstOrThrow();
+// an explicit cast into the desired type
+const metadata = image.metadata as Metadata;
+console.log('Image dimensions:', metadata.width, 'by', metadata.height);
+```
+
+This is not always ideal because, in practice, many people use JSON type in a "controlled" way - only data of specific fixed shapes are stored in a field. So, regaining some of the strong typing capabilities would be very beneficial.
+
+ZenStack's new "strongly typed JSON field" feature is designed to address this need. It allows you to define shapes of JSON data in the schema, and "fixes" `PrismaClient` to return data with correct types. The feature is in preview and only supports PostgreSQL for now.
+
+## Using strongly typed JSON fields
+
+The first step is to use the new `type` keyword to define the shape of the JSON data in the ZModel schema:
+
+```zmodel title="schema.zmodel"
+type Metadata {
+  width Int
+  height Int
+  format String
+}
+
+model Image {
+  id Int @id @default(autoincrement())
+  metadata Metadata @json
+}
+```
+
+Types have a structure similar to models but are not mapped to database tables. They only exist for typing and validation purposes. You can not have relations to other models in types. However, you can include fields of other types to form a nested structure.
+
+When you run `zenstack generate`, the compiler will transform the typed JSON fields back into the regular Prisma `Json` type:
+
+```zmodel title="schema.prisma"
+model Image {
+  id Int @id @default(autoincrement())
+  metadata Json
+}
+```
+
+So, where did the `Metadata` type go? It's compiled into a TypeScript type declaration, which is used to type the query results when you use the ZenStack-enhanced `PrismaClient`:
+
+```ts title="main.ts"
+import { enhance } from '@zenstackhq/runtime';
+
+const db = enhance(prisma);
+const image = await db.image.findFirstOrThrow();
+
+// image.metadata is now directly typed as { width: number, height: number, format: string }
+console.log('Image dimensions:', image.metadata.width, 'by', image.metadata.height);
+```
+
+When you create or update, the input is also properly typed so you get nice auto-completion and typechecking for the payload:
+
+```ts title="main.ts"
+await db.image.create({
+  data: {
+    metadata: {
+      width: 1920,
+      height: '1080', // <- type error here
+      format: 'jpeg'
+    }
+  }
+});
+```
+
+Straightforward, isn't it? But the feature doesn't stop here.
+
+## How about some runtime validation?
+
+For mutations, ZenStack also validates the input data's shape at runtime by deriving a Zod schema from the type declaration. You can also add additional constraints to fields the same way you can do with models:
+
+```zmodel title="schema.zmodel"
+type Metadata {
+  width Int @gt(0) @lt(10000)
+  height Int @gt(0) @lt(10000)
+  format String
+}
+```
+
+Mutation calls violating these constraints will be rejected:
+
+```ts title="main.ts"
+await db.image.create({
+  data: {
+    metadata: {
+      width: 1920,
+      height: 10800, // <- runtime error here
+      format: 'jpeg'
+    }
+  }
+});
+```
+
+```plain
+Error calling enhanced Prisma method `image.create`: denied by policy: image entities failed 'create' check, 
+input failed validation: Validation error: Number must be less than 10000 at "metadata.height"
+```
+
+## Is it really type-safe?
+
+JSON fields are meant to hold arbitrary data types, so there isn't really a way to guarantee data consistency. As such, to preserve enough flexibility, ZenStack doesn't validate if the query results comply with the type declaration. This effectively means you can't trust the TypeScript typings alone if you know the column contains mixed data.
+
+One way to mitigate the problem is to validate the data with the generated Zod schemas explicitly:
+
+```ts
+import { MetadataSchema } from '@zenstackhq/runtime/zod/models';
+
+const image = await db.image.findFirstOrThrow();
+const metadata = MetadataSchema.parse(image.metadata);
+```
+
+## Next steps
+
+One area that's not addressed by this feature yet is the filtering part. The `where` clause still follows Prisma's [Json filter format](https://www.prisma.io/docs/orm/prisma-client/special-fields-and-types/working-with-json-fields#filter-on-a-json-field-simple):
+
+```ts title="main.ts"
+// find images with width greater than 102
+const images = await db.image.findMany({
+  where: {
+    metadata: { path: ['width'], gt: 1024 }
+  }
+});
+```
+
+We can potentially "enhance" that part to provide a typed experience like:
+
+```ts title="main.ts"
+const images = await db.image.findMany({
+  where: {
+    metadata: { width: { gt: 1024 } }
+  }
+});
+```
+
+Is it useful, or can it be confusing (as it looks the same as relation filters)? Let us know by leaving a comment below. You can also learn more about this feature in the [official guide](https://zenstack.dev/docs/guides/typing-json).
diff --git a/blog/microservice/index.md b/blog/microservice/index.md
index ac252f2d..45bb90a8 100644
--- a/blog/microservice/index.md
+++ b/blog/microservice/index.md
@@ -1,7 +1,7 @@
 ---
 title: Where Did Microservices Go
 description: Microservices have undergone a shift due to a combination of lessons learned and the emergence of new technologies.
-tags: [zenstack, Microservices]
+tags: [zenstack, microservices, fullstack, nextjs]
 authors: jiasheng
 date: 2023-04-22
 image: ./cover.jpg
diff --git a/blog/model-authz/cover.png b/blog/model-authz/cover.png
new file mode 100644
index 00000000..95c38799
Binary files /dev/null and b/blog/model-authz/cover.png differ
diff --git a/blog/model-authz/index.md b/blog/model-authz/index.md
new file mode 100644
index 00000000..db8c37d2
--- /dev/null
+++ b/blog/model-authz/index.md
@@ -0,0 +1,610 @@
+---
+title: Modeling Authorization in Prisma - No Theory, Just Code
+description: Code samples for modeling various access control patterns with Prisma and ZenStack.
+tags: [orm, prisma, authorization]
+authors: yiming
+date: 2024-01-15
+image: ./cover.png
+---
+
+# Modeling Authorization in Prisma - No Theory, Just Code
+
+![Cover Image](cover.png)
+
+Authorization is a special topic for software development. You'll get many theories about different patterns if you do a search: their pros and cons. However, it's surprisingly difficult to find concrete examples to follow. It's mainly because the detailed approach highly depends on your specific application: its domain models, unique security requirements, and its choice of framework.
+
+This post aims to fill the gap by targeting [Prisma](https://prisma.io) - the most popular ORM for TypeScript developers. By narrowing it down to a specific toolkit and language, we can explain the concepts more efficiently using code instead of words.
+
+<!--truncate-->
+
+## Before We Start
+
+It's assumed that you know the basics of using Prisma. Prisma is an excellent ORM. But it doesn't have a built-in authorization solution. To supplement that, we'll use [ZenStack](https://zenstack.dev) throughout the samples. ZenStack is a toolkit that supercharges Prisma in many ways. One of the features is to provide a declarative way to model authorization. Its modeling language, ZModel, is a superset of Prisma Schema Language, so it should be easily understandable to people familiar with Prisma.
+
+A few quick notes about the syntax:
+
+1. Access control rules are expressed with `@@allow` and `@@deny` custom attributes.
+2. Rule types include "create", "read", "update", and "delete". You can use "all" to denote all. You can also use a comma-separated list to specify multiple types.
+3. The special `auth()` function represents the current user.
+4. The special `things?[condition]` syntax reads as "if any item in 'things' matches 'condition'".
+
+At the runtime, the access control rules can be automatically enforced by using an "enhanced" `PrismaClient`, which can be created with a one-liner. The `user` field passed in the second argument provides value for the aforementioned `auth()` function.
+
+```ts
+const db = enhance(prisma, { user: currentUser });
+```
+
+Now we have all the knowledge ready. Let's dive into the code.
+
+## Authorization Patterns
+
+### Access Control List (ACL)
+
+ACL is the simplest form of authorization. It's a list of permissions directly attached to each resource. For example, a document can have a list of users who can read it.
+
+#### Schema
+
+```zmodel
+model User {
+  id Int @id @default(autoincrement())
+  name String
+  access Access[]
+  ownedResources Resource[]
+}
+
+model Access {
+  id Int @id @default(autoincrement())
+  user User @relation(fields: [userId], references: [id])
+  userId Int
+  resource Resource @relation(fields: [resourceId], references: [id])
+  resourceId Int
+
+  // view permission
+  view Boolean?
+
+  // manage permission
+  manage Boolean?
+
+  // resource owner has full control over its access list
+  @@allow('all', resource.owner == auth())
+}
+
+model Resource {
+  id Int @id @default(autoincrement())
+  name String
+  owner User @relation(fields: [ownerId], references: [id])
+  ownerId Int
+  access Access[]
+
+  // owner has full control
+  @@allow('all', owner == auth())
+
+  // readable if there exists a "read" permission for the current user
+  @@allow('read', access?[user == auth() && view])
+
+  // writeable if there exists a "manage" permission for the current user
+  @@allow('update,delete', access?[user == auth() && manage])
+}
+```
+
+#### ERD
+
+```mermaid
+erDiagram
+    User {
+        int id
+        string name
+    }
+    Access {
+        int id
+        boolean view
+        boolean manage
+    }
+    Resource {
+        int id
+        string name
+    }
+    User ||--o{ Access : has
+    Resource ||--o{ Access : has
+```
+
+#### Sample Code
+
+```ts
+// create two users
+const emily = await prisma.user.create({
+  data: { name: 'Emily' },
+});
+
+const adam = await prisma.user.create({
+  data: { name: 'Adam' },
+});
+
+// create an enhanced PrismaClient for each user
+const emilyDb = enhance(prisma, { user: emily });
+const adamDb = enhance(prisma, { user: adam });
+
+// create a resource with Emily's identity
+const res = await emilyDb.resource.create({
+  data: { name: 'resource1', owner: { connect: { id: emily.id } } },
+});
+console.log('Resource created by Emily:', inspect(res));
+
+// Adam should see an empty list because he's not in the ACL
+let allResourcesByAdam = await adamDb.resource.findMany();
+expect(allResourcesByAdam).toHaveLength(0);
+
+// Emily should be able to grant Adam access
+await emilyDb.resource.update({
+  where: { id: res.id },
+  data: {
+    access: {
+      create: { user: { connect: { id: adam.id } }, view: true },
+    },
+  },
+});
+console.log('Resource access granted to Adam with view access');
+
+// Adam should see the resource now
+allResourcesByAdam = await adamDb.resource.findMany();
+expect(allResourcesByAdam).toHaveLength(1);
+
+// ❌ update will fail with an error because Adam doesn't have "manage" permission
+await expect(adamDb.resource.update({
+  where: { id: res.id },
+  data: { name: 'resource2' },
+})).rejects.toThrow();
+```
+
+### Role-Based Access Control (RBAC)
+
+RBAC is a widely used authorization pattern. Compared to ACL, an intermediary "role" layer is added to allow more flexibility. For example, an application can have a "manager" role with "manage" permission and a "staff" role with "view" permission.
+
+#### Schema
+
+```zmodel
+model User {
+  id Int @id @default(autoincrement())
+  name String
+  roles Role[]
+}
+
+model Role {
+  id Int @id @default(autoincrement())
+  name String
+  users User[] // users belonging to the role
+  permissions Permission[] // permissions assigned to the role
+}
+
+model Permission {
+  id Int @id @default(autoincrement())
+  name String // "view" or "manage", use an enum if database supports it
+  roles Role[]
+}
+
+model Resource {
+  id Int @id @default(autoincrement())
+  name String
+
+  @@allow('read', auth().roles?[permissions?[name == 'view']])
+  @@allow('all', auth().roles?[permissions?[name == 'manage']])
+}
+```
+
+#### ERD
+
+```mermaid
+erDiagram
+    User {
+        int id
+        string name
+    }
+    Role {
+        int id
+        string name
+    }
+    Permission {
+        int id
+        string name
+    }
+    Resource {
+        int id
+        string name
+    }
+    User ||..o{ Role : has
+    Role ||--o{ Permission : has
+```
+
+#### Sample Code
+
+```ts
+const prisma = new PrismaClient();
+
+// create a "view" permission and a "manage" permission
+const viewPerm = await prisma.permission.create({
+  data: { name: 'view' },
+});
+const managePerm = await prisma.permission.create({
+  data: { name: 'manage' },
+});
+
+// create a "manager" role and a "staff" role
+const managerRole = await prisma.role.create({
+  data: {
+    name: 'manager',
+    permissions: { connect: [{ id: managePerm.id }] },
+  },
+});
+const staffRole = await prisma.role.create({
+  data: {
+    name: 'staff',
+    permissions: { connect: [{ id: viewPerm.id }] },
+  },
+});
+
+// create two users, Emily (manager) and Adam (staff)
+// note that we need to make sure "roles.permissions" are included in the
+// returned result because they are used in the access rules
+const emily = await prisma.user.create({
+  data: { name: 'Emily', roles: { connect: { id: managerRole.id } } },
+  include: { roles: { include: { permissions: true } } },
+});
+const adam = await prisma.user.create({
+  data: { name: 'Adam', roles: { connect: { id: staffRole.id } } },
+  include: { roles: { include: { permissions: true } } },
+});
+
+// create an enhanced PrismaClient for each user
+const emilyDb = enhance(prisma, { user: emily });
+const adamDb = enhance(prisma, { user: adam });
+
+// ❌ Adam shouldn't be able to create a resource
+await expect(
+  adamDb.resource.create({ data: { name: 'resource1' } })
+).rejects.toThrow();
+
+// Emily should be able to create a resource
+const res = await emilyDb.resource.create({ data: { name: 'resource1' } });
+console.log('Resource created by Emily:', inspect(res));
+
+// Adam should be able to read the resource
+const resByAdam = await adamDb.resource.findUnique({ where: { id: res.id } });
+console.log('Resource read by Adam:', inspect(resByAdam));
+expect(resByAdam).toBeTruthy();
+
+// ❌ Adam shouldn't be able to delete the resource
+await expect(
+  adamDb.resource.delete({ where: { id: res.id } })
+).rejects.toThrow();
+
+// Emily should be able to delete the resource
+await emilyDb.resource.delete({ where: { id: res.id } });
+console.log('Resource deleted by Emily');
+```
+
+### Attribute-Based Access Control (ABAC)
+
+ABAC is a more "fine-grained" authorization pattern. It allows you to define access control rules based on the attributes of the resource and the user. For example, a user can create a document if he has enough reputation or can read a document if the document is published.
+
+#### Schema
+
+```zmodel
+model User {
+  id Int @id @default(autoincrement())
+  name String
+  reputation Int @default(1) // controls if a user can create a resource
+  ownedResources Resource[]
+}
+
+model Resource {
+  id Int @id @default(autoincrement())
+  name String
+  published Boolean @default(false)
+  owner User @relation(fields: [ownerId], references: [id])
+  ownerId Int
+
+  // user can create resources if he has enough reputation
+  @@allow('create', auth().reputation >= 100)
+
+  // published resources can be read by anyone
+  @@allow('read', published)
+
+  // owner has full access
+  @@allow('read,update,delete', owner == auth())
+}
+```
+
+#### ERD
+
+```mermaid
+erDiagram
+    User {
+        int id
+        string name
+        int reputation
+    }
+    Resource {
+        int id
+        string name
+        boolean published
+    }
+    User ||--o{ Resource : owns
+```
+
+#### Sample Code
+
+```ts
+// create two users
+const emily = await prisma.user.create({
+    data: { name: 'Emily', reputation: 100 },
+});
+const adam = await prisma.user.create({
+    data: { name: 'Adam', reputation: 5 },
+});
+
+// create an enhanced PrismaClient for each user
+const emilyDb = enhance(prisma, { user: emily });
+const adamDb = enhance(prisma, { user: adam });
+
+// Emily can create a resource because she has enough reputation
+const res = await emilyDb.resource.create({
+  data: { name: 'resource1', owner: { connect: { id: emily.id } } },
+});
+console.log('Resource created by Emily:', inspect(res));
+
+// Adam can't create a resource because he has insufficient reputation
+await expect(
+  adamDb.resource.create({
+    data: { name: 'resource2', owner: { connect: { id: adam.id } } },
+  })
+).rejects.toThrow();
+
+// Adam shouldn't see the resource because it's not published
+let allResourcesByAdam = await adamDb.resource.findMany();
+console.log('All resources read by Adam:', inspect(allResourcesByAdam));
+expect(allResourcesByAdam).toHaveLength(0);
+
+// Emily should be able to publish the resource
+await emilyDb.resource.update({
+  where: { id: res.id },
+  data: { published: true },
+});
+console.log('Resource published by Emily');
+
+// ❌ Adam should now be able to see the resource
+allResourcesByAdam = await adamDb.resource.findMany();
+console.log('All resources read by Adam:', inspect(allResourcesByAdam));
+expect(allResourcesByAdam).toHaveLength(1);
+```
+
+### Multi-Tenancy
+
+Multi-tenancy is a sophisticated authorization pattern that involves storing data for multiple tenants in the same database and enforcing logical segregation. It's widely used in SaaS applications. For example, an application can host many organizations. Users can join multiple organizations and have different roles in each of them. The user's organization membership and role determine what he can do to the organization's resources.
+
+#### Schema
+
+```zmodel
+model User {
+  id Int @id @default(autoincrement())
+  name String
+  reputation Int @default(1)
+  memberships OrgMember[]
+  ownedResources Resource[]
+
+  // can be created by anyone, even not logged in
+  @@allow('create', true)
+
+  // can be read by users sharing any org
+  @@allow('read', memberships?[org.members?[user == auth()]])
+
+  // full access by oneself
+  @@allow('all', auth() == this)
+}
+
+model Org {
+  id Int @id @default(autoincrement())
+  name String
+  members OrgMember[]
+  resources Resource[]
+
+  // require login
+  @@deny('all', auth() == null)
+
+  // everyone can create an org
+  @@allow('create', true)
+
+  // any user in the org can read the org
+  @@allow('read', members?[user == auth()])
+
+  // org admin can update and delete
+  @@allow('update,delete', members?[user == auth() && role == 'ADMIN'])
+}
+
+model OrgMember {
+  id Int @id @default(autoincrement())
+  org Org @relation(fields: [orgId], references: [id])
+  orgId Int
+  user User @relation(fields: [userId], references: [id])
+  userId Int
+  role String // "ADMIN", "MEMBER"
+
+  // require login
+  @@deny('all', auth() == null)
+
+  // org admin can create/update/delete
+  @@allow('create,update,delete', org.members?[user == auth() && role == 'ADMIN'])
+
+  // user can read members of an org if he's a member of it
+  @@allow('read', org.members?[user == auth()])
+}
+
+model Resource {
+  id Int @id @default(autoincrement())
+  name String
+  public Boolean @default(false)
+  owner User @relation(fields: [ownerId], references: [id])
+  ownerId Int
+  org Org @relation(fields: [orgId], references: [id])
+  orgId Int
+
+  // require login
+  @@deny('all', auth() == null)
+
+  // can be read by owner or org members (if public) 
+  @@allow('read', owner == auth() || (org.members?[user == auth()] && public))
+
+  // when create, owner must be set to current user, and user must be in the org
+  @@allow('create', owner == auth() && org.members?[user == auth()])
+
+  // can be updated by owner
+  // update is not allowed to change owner
+  @@allow('update', owner == auth() && future().owner == owner)
+
+  // can be deleted by owner
+  @@allow('delete', owner == auth())
+}
+```
+
+#### ERD
+
+```mermaid
+erDiagram
+    User {
+        int id
+        string name
+    }
+    Org {
+        int id
+        string name
+    }
+    OrgMember {
+        int id
+        string role
+    }
+    Resource {
+        int id
+        string name
+        boolean public
+    }
+    User ||..o{ OrgMember : "belongs to"
+    Org ||--o{ OrgMember : has
+    User ||--o{ Resource : owns
+    Org ||--o{ Resource : has
+```
+
+#### Sample Code
+
+```ts
+// create users
+const emily = await prisma.user.create({
+  data: { name: 'Emily' },
+});
+const adam = await prisma.user.create({
+  data: { name: 'Adam' },
+});
+const joe = await prisma.user.create({
+  data: { name: 'Joe' },
+});
+
+// create two orgs
+
+// Emily as admin of org Apple
+const apple = await prisma.org.create({
+  data: {
+    name: 'Apple',
+    members: {
+      create: [
+        {
+          user: { connect: { id: emily.id } },
+          role: 'ADMIN',
+        },
+      ],
+    },
+  },
+});
+
+// Joe as admin of org Microsoft
+const microsoft = await prisma.org.create({
+  data: {
+    name: 'Microsoft',
+    members: {
+      create: [{ user: { connect: { id: joe.id } }, role: 'ADMIN' }],
+    },
+  },
+});
+
+// create an enhanced PrismaClient for each user
+const emilyDb = enhance(prisma, { user: emily });
+const adamDb = enhance(prisma, { user: adam });
+const joeDb = enhance(prisma, { user: joe });
+
+// Emily should be able to add Adam to the org
+await emilyDb.org.update({
+  where: { id: apple.id },
+  data: {
+    members: {
+      create: [
+        { user: { connect: { id: adam.id } }, role: 'MEMBER' },
+      ],
+    },
+  },
+});
+
+// Adam shouldn't be able to add Joe to the org because he's not admin
+await expect(
+  adamDb.org.update({
+    where: { id: apple.id },
+    data: {
+      members: {
+        create: [
+          { user: { connect: { id: joe.id } }, role: 'MEMBER' },
+        ],
+      },
+    },
+  })
+).rejects.toThrow();
+
+// Emily should be able to create a resource in org Apple
+const res = await emilyDb.resource.create({
+  data: {
+    name: 'resource1',
+    public: true,
+    org: { connect: { id: apple.id } },
+    owner: { connect: { id: emily.id } },
+  },
+});
+console.log('Resource created by Emily:', inspect(res));
+
+// Emily shouldn't be able to create a resource in org Microsoft
+await expect(
+  emilyDb.resource.create({
+    data: {
+      name: 'resource2',
+      org: { connect: { id: microsoft.id } },
+      owner: { connect: { id: emily.id } },
+    },
+  })
+).rejects.toThrow();
+
+// the resource is readable to Adam
+const resByAdam = await adamDb.resource.findUnique({
+  where: { id: res.id },
+});
+console.log('Resource read by Adam:', inspect(resByAdam));
+expect(resByAdam).toBeTruthy();
+
+// the resource is not readable to Joe
+const resByJoe = await joeDb.resource.findUnique({
+  where: { id: res.id },
+});
+console.log('Resource read by Joe:', inspect(resByJoe));
+expect(resByJoe).toBeNull();
+```
+
+## Conclusion
+
+Authorization can be a very fuzzy topic for people new to the challenge. What the heck is RBAC, ABAC, FGAC, ReBAC, PBAC, ...? Hopefully, the samples in this post can help you get more concrete ideas about implementing the most commonly used patterns. In reality, you'll rarely find an application that uses a pattern exactly as described above. Authorization is often highly application-specific, and there's nothing wrong if you need to make a variation of a pattern or mix a few to achieve your goal.
+
+You can find the source code at [https://github.com/zenstackhq/authz-modeling-samples](https://github.com/zenstackhq/authz-modeling-samples).
+
+I hope you also enjoy how ZenStack allows you to express authorization rules declaratively. Its syntax provides great flexibility to traverse relationships and access the current user to define sophisticated rules. Please visit [documentation](https://zenstack.dev/docs) to learn more. By the way, don't forget to give it a ⭐️ on [GitHub](https://github.com/zenstackhq/zenstack).
diff --git a/blog/modern-sql-ai/cover.png b/blog/modern-sql-ai/cover.png
new file mode 100644
index 00000000..afe85ecb
Binary files /dev/null and b/blog/modern-sql-ai/cover.png differ
diff --git a/blog/modern-sql-ai/index.md b/blog/modern-sql-ai/index.md
new file mode 100644
index 00000000..825bfb24
--- /dev/null
+++ b/blog/modern-sql-ai/index.md
@@ -0,0 +1,159 @@
+---
+title: 'How Modern SQL Databases Are Changing Web Development - #4 Into the AI Era'
+description: An introduction to how modern SQL databases are adapting the AI era.
+tags: [ai, database, sql]
+authors: yiming
+date: 2023-12-29
+image: ./cover.png
+---
+
+# How Modern SQL Databases Are Changing Web Development - #4 Into the AI Era
+
+![Cover Image](cover.png)
+
+SQL databases have been powering the web since their inception. The combination of strict schema, ACID transactions, and strong integrity makes it still the best storage choice for many apps even today. Database systems, albeit sounding unfathomable and dull, have been a highly vibrant domain in the past few years. Lots of talents are devoted to it, open source projects are created one after another, companies have been striving to build profitable businesses, and VCs have been making bets on those who look like the next unicorns.
+
+Under the flourishing surface, the new generation of modern SQL databases is renovating this 50-year-old technology and gradually reshaping how we build web applications - what we do with it, how we manage it, and the way we program against it. This series of articles tries to explore this topic from multiple fronts, including:
+
+- [#1 Serverless & Edge](/blog/modern-sql-serverless)
+- [#2 New Ways to Program](/blog/modern-sql-programming)
+- [#3 Better Developer Experience](/blog/modern-sql-dx)
+- **#4 Into The AI Era (this post)**
+
+Now we've come to the last part of this series. Let's get hold of the hottest trend of the year - AI, and see how modern databases enable easier development of AI-powered applications.
+
+<!-- truncate -->
+
+## LLM? Not So Fast!
+
+Before ChatGPT showed the world how powerful a large language model can be, AI, or more precisely, machine learning, has been there for over 60 years. It has had its ups and downs and has made excellent progress in many domains, including computer vision, natural language processing, speech recognition, etc.
+
+Unlike GPT, traditional machine learning methods often require training custom models for solving specific problems. While ML practitioners like talking about learning algorithms, the truth is that most of their time is spent on data wrangling, and this gets SQL databases into the picture. Preparing data for model training requires heavy preprocessing of raw data: eliminating noise, filling missing values, merging multiple pieces of data, normalizing values, extracting features, and so on. Filtering, joining, and transforming data are the tasks that databases are most adept at.
+
+Some excellent tools were created to represent these tasks "naturally" in SQL and even let most of the computation happen inside the database. [PostgresML](https://postgresml.org/) is a great example. It's built above PostgreSQL and provides a set of functions that allow you to train and use machine learning models with SQL. Here's how you can train a classification model for the classic handwritten digit recognition problem:
+
+1. Loading a dataset as a table
+
+    ```sql
+    SELECT * FROM pgml.load_dataset('digits');
+    ```
+
+2. Training a classifier using "xgboost" algorithm
+   
+    ```sql
+    SELECT * FROM pgml.train(
+        'Handwritten Digit Image Classifier',
+        algorithm => 'xgboost',
+        'classification',
+        'pgml.digits',
+        'target'
+    );
+    ```
+
+3. Making predictions using the trained model
+
+    ```sql
+    SELECT
+        target,
+        pgml.predict('Handwritten Digit Image Classifier', image) AS prediction
+    FROM pgml.digits 
+    LIMIT 10;
+    ```
+
+Although such a model can also be easily trained with other programming languages, PostgresML makes it possible to get the job done without the data ever leaving your database. Plus, if you're a SQL lover and proficient in preparing the training data using it, you can have your entire preprocessing-training-predicting pipeline written in SQL.
+
+## LLM & RAG: A Bit of Background
+
+Fast forward to the post-GPT era, and many people equate AI to LLM today. To understand the database's role in this new context, let's first have a quick review of the landscape of LLM.
+
+LLMs are pre-trained machine learning models specialized for generating text output based on text input. They are called "large" because the models have a very large number of internal parameters and are trained with a very large amount of data to ensure adequate performance. Because the models are pre-trained, developers can use them directly without worrying about the training process. Instead of fighting with feature engineering and training algorithms, they now focus on prompt engineering.
+
+However, being pre-trained also means that the models are not tailored to any specific task. One "deep customization" technique is called Retrieval Augmented Generation (RAG). Suppose you have a knowledge base of documents for a domain; adopting RAG involves the following steps:
+
+- Calculate embeddings for each document (using an existing model or APIs like OpenAI).
+- Store the embeddings in a database that allows fast similarity-based search.
+- Given a user query, calculate its embedding and find the most similar documents in the database.
+- Use the matching document to augment the input query as additional context and feed it to the LLM.
+
+> Embeddings are vector representations of text data.
+
+![Retrieval Augmented Generation](rag.png)
+
+As you can see, databases still play an essential role in this architecture, but in a very different way.
+
+## Vector Search
+
+A RAG implementation's quality and performance highly depend on the similarity-based search of embeddings. The challenge arises from the fact that embeddings are usually high-dimensional vectors, and the knowledge base may have many documents. It's not surprising that the popularity of LLM catalyzed the development of specialized vector databases like [Pinecone](https://www.pinecone.io/) and [Weaviate](https://weaviate.io/). However, SQL databases are also evolving to meet the new challenge.
+
+PostgreSQL's [pgvector](https://github.com/pgvector/pgvector) extension is probably the most widely used SQL solution for storing and searching vector data today. The extension introduces a "vector" type specialized for storing high-dimensional vector data. It allows you to create vector indices (in "IVFFlat" or "HNSW" format for different indexing/searching performance tradeoffs) and leverage them to do various types of similarity searches.
+
+```sql
+-- Create a table with a vector column
+CREATE TABLE items (id bigserial PRIMARY KEY, embedding vector(3));
+
+-- Insert vectors
+INSERT INTO items (embedding) VALUES ('[1,2,3]'), ('[4,5,6]');
+
+-- Create an "HNSW" index for L2 distance search
+CREATE INDEX ON items USING hnsw (embedding vector_l2_ops);
+
+-- Get the nearest neighbors by L2 distance
+SELECT * FROM items ORDER BY embedding <-> '[3,1,2]' LIMIT 5;
+```
+
+SQL databases are not explicitly optimized for vector searches; benchmarks show it's usually slower than vector databases. However, there are still good reasons to use them:
+
+- You can filter with both vector and non-vector data simultaneously.
+- You can combine full-text search and vector-based search.
+- You can have the matching document's content directly returned in the search result without making a separate fetch request.
+- You can leverage the database's security mechanisms (roles, row-level-security, etc.) to segregate data across tenants. 
+
+## SQL as the Facade of LLM Workflows
+
+Besides storing and searching vector data, there are also other remarkable innovations that let you describe your entire LLM workflows using SQL - conceptually similar to how end-to-end machine learning tasks are done with PostgresML.
+
+[Mindsdb](https://mindsdb.com) is a good example. It abstracts everything related to an AI workflow as "virtual tables". For example, you can import OpenAI API as a "virtual table":
+
+```sql
+CREATE ML_ENGINE openai_engine
+FROM openai
+USING
+    api_key = 'your-openai-api-key';
+
+CREATE MODEL my_openai_model
+PREDICT answer
+USING
+    engine = 'openai',
+    question_column = 'question',
+    model_name = 'openai_model_name'
+    api_key = 'YOUR_OPENAI_API_KEY;
+```
+
+Then query it to get answers to questions:
+
+```sql
+SELECT question, answer
+FROM my_openai_model
+WHERE question = 'Where is Stockholm located?';
+```
+
+And get back result like:
+
+| question                   |answer                         |
+| ---------------------------|-------------------------------|
+|Where is Stockholm located? |Stockholm is located in Sweden.|
+
+It's essentially a platform for integrating data, APIs, and LLMs and using SQL to declaratively orchestrate them to create high-level workflows like RAG, chatbot, text summarization, etc. 
+
+## Wrap Up
+
+AI is everywhere. It's encouraging us to rethink every aspect of software development, and database is no exception. We should expect to see SQL databases - one of the most time-tested supporting pillars of software engineering, continue exploring this new frontier and being reshaped along the way.
+
+---
+
+### The Full Series of *How Modern SQL Databases Are Changing Web Development*
+
+- [#1 Serverless & Edge](/blog/modern-sql-serverless)
+- [#2 New Ways to Program](/blog/modern-sql-programming)
+- [#3 Better Developer Experience](/blog/modern-sql-dx)
+- [#4 Into the AI Era](/blog/modern-sql-ai)
diff --git a/blog/modern-sql-ai/rag.png b/blog/modern-sql-ai/rag.png
new file mode 100644
index 00000000..156e3d8d
Binary files /dev/null and b/blog/modern-sql-ai/rag.png differ
diff --git a/blog/modern-sql-dx/cover.png b/blog/modern-sql-dx/cover.png
new file mode 100644
index 00000000..d73b7f03
Binary files /dev/null and b/blog/modern-sql-dx/cover.png differ
diff --git a/blog/modern-sql-dx/data-branching.png b/blog/modern-sql-dx/data-branching.png
new file mode 100644
index 00000000..14e191dd
Binary files /dev/null and b/blog/modern-sql-dx/data-branching.png differ
diff --git a/blog/modern-sql-dx/index.md b/blog/modern-sql-dx/index.md
new file mode 100644
index 00000000..da3f8f5d
--- /dev/null
+++ b/blog/modern-sql-dx/index.md
@@ -0,0 +1,99 @@
+---
+title: 'How Modern SQL Databases Are Changing Web Development - #3 Better Developer Experience'
+description: An introduction to how modern SQL databases are improving developer experiences.
+tags: [database, sql, dx]
+authors: yiming
+date: 2023-12-08
+image: ./cover.png
+---
+
+# How Modern SQL Databases Are Changing Web Development - #3 Better Developer Experience
+
+![Cover Image](cover.png)
+
+SQL databases have been powering the web since their inception. The combination of strict schema, ACID transactions, and strong integrity makes it still the best storage choice for many apps even today. Database systems, albeit sounding unfathomable and dull, have been a highly vibrant domain in the past few years. Lots of talents are devoted to it, open source projects are created one after another, companies have been striving to build profitable businesses, and VCs have been making bets on those who look like the next unicorns.
+
+Under the flourishing surface, the new generation of modern SQL databases is renovating this 50-year-old technology and gradually reshaping how we build web applications - what we do with it, how we manage it, and the way we program against it. This series of articles tries to explore this topic from multiple fronts, including:
+
+- [#1 Serverless & Edge](/blog/modern-sql-serverless)
+- [#2 New Ways to Program](/blog/modern-sql-programming)
+- **#3 Better Developer Experience (this post)**
+- [#4 Into The AI Era](/blog/modern-sql-ai)
+
+Let’s discuss how DX (developer experience) is becoming increasingly crucial in database designs. <!-- truncate -->In the old days, databases were passive participants in software development - they were reliable and delivered what they promised but didn’t get deeply involved in the dev flow. However, things are changing rapidly.
+
+## Provisioning in the Cloud
+
+I like how Neon put it in [simple words](https://neon.tech/blog/what-is-a-serverless-database#:~:text=At%20Neon%2C%20we%20believe%20that%20the%20database%20should%20just%20be%20a%20URL):
+
+> We believe that the database should just be a URL. 
+
+They indeed are. Creating database instances in the cloud has never been easier. You can get an instance up and running within a minute. No more dealing with VPC networking, firewalls, connection pooling, etc. Copy the connection string into your code, and it just works. You also immediately have access to a dashboard for data/schema management and usage analytics.
+
+There have been many security concerns about using cloud-based databases directly exposed to the public network. Although more mechanisms are built to mitigate, like storing keys in vaults, scanning commits for accidentally checked-in keys, encryption at rest and in transit, etc., most concerns remain valid today. However, the trend is that cloud-native databases are gaining more and more trust and adoption.
+
+Beneath the simple surface, cloud database providers have been working hard to automate many things for you:
+
+- Scaling up and down? Don’t worry about it. When idle, the services can shrink to [literally zero CPU usage](https://neon.tech/docs/reference/glossary#scale-to-zero) and rapidly scale up as traffic surges.
+- Bottomless storage, so running out of disk space is never a worry anymore.
+- Sharding has always been a headache to manage and used to require lots of application code changes. Now, it can be achieved in a [much less intrusive way](https://planetscale.com/docs/concepts/sharding).
+- Not proficient with managing indexes? The services monitor query patterns and auto-tune indexes for you.
+
+More and more heavy-lifting work gets done internally inside the system, so as a developer, you can better focus on writing good code and building an excellent application.
+
+## Branching the Database With Your Code
+
+It’s the start of a week, and you’re excited about working on that new feature. You created a feature branch and figured you need to make some database schema changes. How do you test it? Create a new database somewhere? How do you get some production-like test data? These extra tasks distract you and slow you down. You wish you could branch the production database as easily as you create a Git branch. Or maybe you can?
+
+One of the most incredible features provided by modern cloud databases is branching. Yes, branching of schema, branching of data, and Git-like merge flow. 
+
+### Schema branching
+
+Schema branching allows multiple "versions" of table schemas inside one database. Similar to how developers use Git to manage source code, there’s always a "main" branch that serves production requests, and you can create branches off it at any time at a very low cost. 
+
+PlanetScale released its [branching feature](https://planetscale.com/docs/concepts/branching) back in 2021. The entire workflow is very similar to how you use Git to manage source code: create a branch to experiment with schema changes, and when you’re ready, you can make a "merge request" to deploy the changes back to production.
+
+![Schema Deploy Request](schema-deploy-request.png)
+
+### Data branching
+
+Branching schema is cool, but can I also fork data at the same time? Both [PlanetScale](https://planetscale.com/docs/concepts/data-branching) and [Neon](https://neon.tech/docs/introduction/branching) offer such a feature, but they have very different implementations.
+
+PlanetScale adopted a conventional approach. When creating a branch, you can (optionally) choose a snapshot to restore data to the new branch. It is just simple snapshot restoration, so it can take a while if the dataset is large.
+
+Neon’s data branching approach is much nicer: it leverages the underlying copy-on-write (COW) storage, so creating a new branch with data has almost zero cost. Afterward, the two branches will have separate update streams, but they continue sharing lots of data.
+
+![Data Branching](data-branching.png)
+
+It’s worth noting that being able to branch production data for testing easily doesn’t mean you should just do it. It poses a significant risk of leaking sensitive user data. You should consider using tools like [Snaplet](https://www.snaplet.dev/) to transform and anonymize sensitive columns.
+
+### Point-In-Time Recovery (PITR)
+
+One interesting "side effect" of copy-on-write storage like Neon uses is that you can achieve [PITR](https://neon.tech/docs/guides/branching-pitr) effortlessly. Just create a branch in the past (yes, why not? COW means you can time-travel in data), verify the data, and then promote that branch to production. That’s it! You’re a younger version of yourself now.
+
+## Smoother Schema Migration
+
+As a developer, if you ask me about the most stressful part of working with SQL databases, I’ll answer "schema migration" without a second thought.
+
+I’ve been through multiple incidents where everything worked fine in the testing environment but ended up locking the production database for minutes when deployed. A category of open-source tools called OSC (Online Schema Change) exists to mitigate such pain, like [gh-ost](https://github.com/github/gh-ost) used by GitHub and [OSC](https://github.com/facebookincubator/OnlineSchemaChange) used by Meta. They work by creating a set of "ghost tables" to apply the migrations, copy over old data from the original tables, and catch up with new writes simultaneously. When all old data is migrated, you can trigger a cutover to make the "ghost tables" production. Check the post below for a great introduction and comparison:
+
+[How Online Schema Change tools work](https://planetscale.com/docs/learn/how-online-schema-change-tools-work)
+
+PlanetScale packages such capability nicely into their branch - merge request - deploy workflow. When you enable the ["Safe Migrations"](https://planetscale.com/docs/concepts/safe-migrations) feature, deploying schema changes from a development branch to the production results in an asynchronous and non-blocking process - involving creating ghost tables and synchronizing data automatically. Your original production branch will continue working without downtime, and the final cutover will happen instantly.
+
+No more stressful schema changes!
+
+## Wrap Up
+
+Many developers have mixed feelings about SQL databases. You have to master it to build great applications. However, it always felt like a disconnected piece in the development workflow. Many deployment horror stories have their roots in such disconnect.
+
+Modern cloud databases strive to improve the developer experience by making databases a more integral part of the entire development workflow - more streamlined, safer, and conceptually consistent. Many such endeavors are still in their early stage, but they look like how most of us will work with databases in the future.
+
+---
+
+### The Full Series of *How Modern SQL Databases Are Changing Web Development*
+
+- [#1 Serverless & Edge](/blog/modern-sql-serverless)
+- [#2 New Ways to Program](/blog/modern-sql-programming)
+- [#3 Better Developer Experience](/blog/modern-sql-dx)
+- [#4 Into The AI Era](/blog/modern-sql-ai)
diff --git a/blog/modern-sql-dx/schema-deploy-request.png b/blog/modern-sql-dx/schema-deploy-request.png
new file mode 100644
index 00000000..a43cc250
Binary files /dev/null and b/blog/modern-sql-dx/schema-deploy-request.png differ
diff --git a/blog/modern-sql-programming/index.md b/blog/modern-sql-programming/index.md
index 9031dd83..fc66837e 100644
--- a/blog/modern-sql-programming/index.md
+++ b/blog/modern-sql-programming/index.md
@@ -18,7 +18,7 @@ Under the flourishing surface, the new generation of modern SQL databases is ren
 - [#1 Serverless & Edge](/blog/modern-sql-serverless)
 - **#2 New Ways to Program (this post)**
 - #3 Integration with Development Flow (coming soon)
-- #4 AI Capabilities (coming soon)
+- [#4 Into The AI Era](/blog/modern-sql-ai)
 
 Let’s talk about how we program against the database today.
 
@@ -107,3 +107,5 @@ Despite being around for half a century, the core of RDBMS hasn’t changed much
 
 - [#1 Serverless & Edge](/blog/modern-sql-serverless)
 - [#2 New Ways to Program](/blog/modern-sql-programming)
+- [#3 Better Developer Experience](/blog/modern-sql-dx)
+- [#4 Into The AI Era](/blog/modern-sql-ai)
diff --git a/blog/modern-sql-serverless/index.md b/blog/modern-sql-serverless/index.md
index 85d3e4bb..fe4660e8 100644
--- a/blog/modern-sql-serverless/index.md
+++ b/blog/modern-sql-serverless/index.md
@@ -17,8 +17,8 @@ Under the flourishing surface, the new generation of modern SQL databases is ren
 
 - **#1 Serverless & Edge (this post)**
 - [#2 New Ways to Program](/blog/modern-sql-programming)
-- #3 Integration with Development Flow (coming soon)
-- #4 AI Capabilities (coming soon)
+- [#3 Better Developer Experience](/blog/modern-sql-dx)
+- [#4 Into The AI Era](/blog/modern-sql-ai)
 
 Let's talk about "Serverless" and "Edge-Ready" today.
 
@@ -96,3 +96,5 @@ Meanwhile, being able to query from the edge doesn't mean you should just do it.
 
 - [#1 Serverless & Edge](/blog/modern-sql-serverless)
 - [#2 New Ways to Program](/blog/modern-sql-programming)
+- [#3 Better Developer Experience](/blog/modern-sql-dx)
+- [#4 Into The AI Era](/blog/modern-sql-ai)
diff --git a/blog/multi-tenant/index.md b/blog/multi-tenant/index.md
index ef64128b..b64065e2 100644
--- a/blog/multi-tenant/index.md
+++ b/blog/multi-tenant/index.md
@@ -1,5 +1,5 @@
 ---
-title: Multi-Tenancy Implementation Approaches With Prisma and Zenstack
+title: Multi-Tenancy Implementation Approaches With Prisma and ZenStack
 description: Compare and discuss different approaches for implementing Multi-Tenant architecture
 tags: [zenstack, prisma, saas, architecture]
 authors: jiasheng
@@ -7,7 +7,7 @@ date: 2023-03-18
 image: ./cover.png
 ---
 
-# Multi-Tenancy Implementation Approaches With Prisma and Zenstack
+# Multi-Tenancy Implementation Approaches With Prisma and ZenStack
 
 ![Cover Image](cover.png)
 
@@ -325,4 +325,4 @@ The tenant isolation is still handled seamlessly in the background.
 
 Consider the benefits of having a newly hired developer who is unfamiliar with the multi-tenant concept. Despite their lack of understanding, they can still perform their job without causing any issues.
 
-If you like it, join our [Discord](https://go.zenstack.dev/chat) or follow our [GitHub](https://github.com/zenstackhq/zenstack). You will see more features and usages there.
+If you like it, join our [Discord](https://discord.gg/Ykhr738dUe) or follow our [GitHub](https://github.com/zenstackhq/zenstack). You will see more features and usages there.
diff --git a/blog/next-chapter-1/cover.png b/blog/next-chapter-1/cover.png
new file mode 100644
index 00000000..b0c79887
Binary files /dev/null and b/blog/next-chapter-1/cover.png differ
diff --git a/blog/next-chapter-1/index.md b/blog/next-chapter-1/index.md
new file mode 100644
index 00000000..0e67563b
--- /dev/null
+++ b/blog/next-chapter-1/index.md
@@ -0,0 +1,132 @@
+---
+title: ZenStack - The Next Chapter (Part I. Overview)
+description: Looking into the future of ZenStack.
+tags: [zenstack, v3]
+authors: yiming
+image: ./cover.png
+date: 2025-04-08
+---
+
+# ZenStack - The Next Chapter (Part I. Overview)
+
+![Cover Image](./cover.png)
+
+Back in late 2022, when Jiasheng and I discussed how to make building web apps less painful, we initially thought of building something for people without a programming background. We eventually couldn't convince ourselves it could work, so we decided to take a step back and try to make developers' lives easier. That "step back" became the start of [ZenStack](https://zenstack.dev).
+
+The past two and half years have been full of joy and fulfillment. By building the tool, seeing how people use it, and learning what works and what doesn't, we've got a better understanding of the intricacy of "easy". What people need is not just writing less code or shipping faster, but rather an inexplicable balance between low cognition burden and high flexibility. We are probably on the right track to solving the problem, but there's still a long way ahead. While considering what to do in V3, we think it's a good time to better align ZenStack's architecture with the ultimate goal.
+
+<!-- truncate -->
+
+## A retrospect
+
+ZenStack started its journey as a powerful extension package to Prisma ORM. It's been advocating a model-first approach, which is to use a rich and coherent schema as the core of an application and, from it, automatically derive as many workpieces as possible - access control, RESTful APIs, frontend hooks, Zod schemas, etc. As we went deeper into this path, we started to feel more and more constrained by our foundation - Prisma. Here are some of the most important limitations.
+
+### 1. You can't control the SQL to be executed
+
+One of the most significant extensions ZenStack made to Prisma is access control. It works by injecting into PrismaClient's queries. For example, with the following schema
+
+```tsx
+model User {
+  id Int @id
+  posts[] Post
+  // user can be read if he has at least one published post
+  @@allow('read', posts?[published == true])
+}
+```
+
+, a query like the following
+
+```tsx
+db.user.findFirst()
+```
+
+becomes this after ZenStack’s injection:
+
+```tsx
+db.user.findFirst({ where: { posts: { some: { published: true } } } })
+```
+
+It all works great until you run into a requirement that [can't be expressed with a Prisma query](https://github.com/prisma/prisma/issues/8935), like:
+
+```sql
+model User {
+  ...
+  // A user is readable if he has more than one post
+  // The policy rule is pseudo and currently unsupported in ZenStack
+  @@allow('read', count(posts) > 1)
+}
+```
+
+But it can be easily achieved with SQL:
+
+```sql
+SELECT u.*
+FROM "User" u
+JOIN "Post" p ON u."id" = p."authorId"
+GROUP BY u."id"
+HAVING COUNT(p."id") > 1;
+```
+
+Since Prisma doesn't provide a way to intercept and alter the SQL (or its equivalence) before execution, we don't have the option to inject at a lower level.
+
+### 2. Lacking a (good enough) escape hatch
+
+Prisma's query API is great, but when your requirements outgrow its capability, you'll need to resort to SQL. This is not ideal for several reasons:
+
+- Writing SQL can be challenging for developers and feels like a considerable DX degradation compared to the awesome fully-typed query API
+- SQL codes are quite often not portable between different database types
+- People have repeatedly asked if "ZenStack supports access control for raw SQL or typed-SQL". We don't want to get into the business of parsing and injecting multiple dialects of SQL.
+
+Instead, a query-builder style API can probably cover most use cases where raw SQL is needed and provides a much better DX experience while making injection a lot easier.
+
+### 3. Excessive stack complexity
+
+Sitting above Prisma, ZenStack needs to interface with Prisma at multiple levels:
+
+- Transpiling ZModel to Prisma Schema
+- Manipulating Prisma-generated TypeScript types
+- Generating more code (Zod, frontend hooks, etc., based on [Prisma’s DMMF](https://github.com/prisma/prisma/blob/main/ARCHITECTURE.md#the-dmmf-or-data-model-meta-format))
+- Intercepting PrismaClient APIs at runtime
+
+Besides implementation complexity, a significant price that this design pays is the slow code generation process for large schemas.
+
+Despite these claims, we don't intend to say Prisma is incompetent in any way. Quite on the contrary, we love it, learned a lot from it, and think it's the best in class for many typical applications. We're simply reaching the conclusion that Prisma is not the best abstraction level that ZenStack builds above to achieve our vision of the project.
+
+## What's next?
+
+The two primary goals we want to achieve with V3 are:
+
+- Better flexibility
+- Snappier DX
+
+The plan is to migrate away from Prisma and reimplement the core ORM part with [Kysely](https://kysely.dev/). We know it sounds very bold and even scary for current ZenStack users. What about backward compatibility? Why Kysely?
+
+### Backward compatibility
+
+Despite the big refactor, the following compatibility guarantees will be kept:
+
+1. ZModel will be fully backward compatible.
+2. The new ORM CRUD API will be fully compatible with `PrismaClient`.
+3. Migrations previously generated with Prisma will continue to work (we'll likely continue using Prisma for database migration for a while).
+
+These should minimize the need for code changes during the upgrade. However, it doesn't mean no changes at all. For example, if you reference Prisma-generated TypeScript types explicitly, such references may need to be updated.
+
+One fuzzy area is Prisma's client extensions. Given the many limitations of its design, we're not sure if making a compatible implementation is a good idea. We'll share more thoughts about extensibility in the follow-up posts.
+
+We also understand that not everyone can jump onto the new major version soon after it's out. The support of V2 will continue for an extended period of time, until V3 is ready for adoption by the majority.
+
+### Why Kysely?
+
+Kysely is a very popular, strongly typed SQL query builder. Using it as the database access layer can address several limitations mentioned previously.
+
+1. ZenStack can provide strongly typed ORM API (as `PrismaClient`) as well as strongly typed low-level query builder API. A query builder can almost express everything SQL can, with a much better DX. This is similar to what [Drizzle](https://orm.drizzle.team/) offers.
+   
+2. Access control injection (and other query transformations) can be done at Kysely's query tree level, which offers much more flexibility than injecting `PrismaClient`.
+   
+3. Kysely's expression builder can be used as a generic extensibility mechanism (more about this in the [next post](../next-chapter-2/index.md)).
+
+One question we repeatedly got was, "Why not build above Drizzle, given its rising popularity?". Drizzle is an excellent ORM that addresses some of Prisma's issues. However, the primary decision factor is the abstraction level. ZenStack needs a simple yet flexible database access layer as a foundation. Kysely satisfies this criterion perfectly, while Prisma, Drizzle, and other ORMs are too high-level and comprehensive.
+
+## Tell us what you think
+
+Such a big refactor is always challenging and exciting at the same time. We believe it's necessary and will pave the road for ZenStack's future - a versatile ORM, a secured data layer, an API generator, and an indispensable tool for full-stack development. [Let us know your thoughts!](https://discord.com/channels/1035538056146595961/1352359627525718056)
diff --git a/blog/next-chapter-2/index.md b/blog/next-chapter-2/index.md
new file mode 100644
index 00000000..3f1edf5c
--- /dev/null
+++ b/blog/next-chapter-2/index.md
@@ -0,0 +1,186 @@
+---
+title: ZenStack - The Next Chapter (Part II. An Extensible ORM)
+description: This post explores how ZenStack V3 will become a more extensible ORM.
+tags: [zenstack, v3]
+authors: yiming
+image: ../next-chapter-1/cover.png
+date: 2025-04-10
+---
+
+# ZenStack - The Next Chapter (Part II. An Extensible ORM)
+
+![Cover Image](../next-chapter-1/cover.png)
+
+In the [previous post](../next-chapter-1/index.md), we discussed the general plan for ZenStack v3 and the big refactor. This post will explore the extensibility opportunities the new architecture brings to the core ORM.
+
+<!-- truncate -->
+
+## Query Builder
+
+While continuing to provide the fully typed CRUD API like `PrismaClient` (`findMany`, `create`, etc.), using [Kysely](https://kysely.dev/) as the underlying data access layer allows us to easily offer a low-level, query-builder style API too.
+
+```ts
+// CRUD API (the same as PrismaClient)
+await client.user.findMany({ include: { posts: true } });
+
+// Query builder API (backed by Kysely)
+await client.$qb.selectFrom('User')
+  .leftJoin('Post', 'Post.authorId', 'User.id')
+  .select(['User.id', 'User.email', 'Post.title'])
+  .execute()
+```
+
+Both the CRUD API and the query builder API are automatically inferred from the ZModel schema, so you don't need any extra configuration to use them, and they're always consistent. Given Kysely query builder's awesome flexibility, we believe you'll rarely need to resort to raw SQL anymore.
+
+What's even more powerful is that you can blend query builder into CRUD calls. For complex queries, you can still enjoy the terse syntax of the CRUD API, and mix in the query builder for extra expressiveness. Here's an example:
+
+```ts
+await client.user.findMany({
+  where: {
+    age: { gt: 18 },
+    // "eb" is a Kysely expression builder
+    $expr: (eb) => eb('email', 'like', '%@zenstack.dev')
+  }
+});
+```
+
+It's similar to the long-awaited [whereRaw](https://github.com/prisma/prisma/issues/5560) feature request in Prisma, but better thanks to Kysely's type-safe query builder. You can implement arbitrarily complex filters involving joins or subqueries with the `$expr` clause.
+
+Kysely's query builder syntax may take some time to get used to, but once you get the hang of it, it's pleasant to write and incredibly powerful.
+
+## Computed Fields
+
+One major limitation of Prisma and ZenStack v2 is the lack of real "computed fields". Prisma's client extension allows you to [add custom fields to models](https://www.prisma.io/docs/orm/prisma-client/queries/computed-fields), but it's purely client-side. It's good for simple computations like combining `firstName` and `lastName` into full name, but you can't do anything that needs database-side computation, like adding a `postCount` field to return the number of posts a user has. 
+
+ZenStack v3 is determined to solve this problem. It'll introduce a new `@computed` attribute that allows you to define computed fields in ZModel.
+
+```zmodel
+model User {
+  id Int @id
+  posts[] Post
+  postCount Int @computed
+}
+```
+
+Of course, you'll need to provide an "implementation" for computing the field, on the database side. Again, Kysely's query builder is perfect for this. When creating a ZenStack client instance, you'll need to provide a callback that returns a Kysely expression builder for each computed field.
+
+```ts
+import { ZenStackClient } from '@zenstackhq/runtime';
+
+const client = new ZenStackClient({
+  computed: {
+    user: {
+      // select count(*) as postCount from Post where Post.authorId = User.id
+      postCount: (eb) =>
+        eb
+          .selectFrom('Post')
+          .whereRef('Post.authorId', '=', 'User.id')
+          .select(({ fn }) => fn.countAll<number>().as('postCount'))
+    }
+  }
+});
+```
+
+Then, when you query the `User` model, the `postCount` field will be returned in the result. You can also use it to filter, sort, etc., just like any other field.
+
+```ts
+// find users with more than 10 posts and sort by post count
+const users = await client.user.findMany({
+  where: { postCount: { gt: 10 } },
+  orderBy: { postCount: 'desc' }
+});
+```
+
+Since the fields are declared in ZModel, you can use it in access policies as well:
+
+```zmodel
+model User {
+  ...
+  @@deny('delete', postCount > 0)
+}
+```
+
+Another benefit of having the computed fields declared in ZModel is that it'll be visible to all tools that consume the schema. For example, The OpenAPI spec generator can include them as fields in the generated APIs.
+
+## Custom Procedures
+
+An ORM provides a set of data access primitives that allow applications to compose them into higher-level operations with business meaning. Such composition can be encapsulated in many ways: utility functions, application services, database stored procedures, etc. ZenStack v3 will introduce a new `proc` construct to allow defining such encapsulation in ZModel.
+
+```zmodel
+model User {
+  id Int @id
+  email String @unique
+  name String?
+}
+
+type SignUpInput {
+  email String
+  name String?
+}
+
+proc signUp(args: SignUpInput): User
+```
+
+Again, when creating a ZenStack client instance, you must provide implementations for the procedures.
+
+```ts
+import { ZenStackClient } from '@zenstackhq/runtime';
+
+const client = new ZenStackClient({
+  procs: {
+    signUp: async (client, args) => {
+      // create user
+      const user = await client.user.create({ data: { email: args.email, name: args.name } });
+      // send a welcome email
+      await sendWelcomeEmail(user.email);
+      return user;
+    }
+  }
+});
+```
+
+Then, you can call the type-safe procedures just like any other CRUD operation:
+
+```ts
+const user = await client.$procs.signUp({ email, name });
+```
+
+You may wonder why we bother to declare the procedure in ZModel. Again, the purpose is to make them visible to upstream tools. For example, ZenStack's auto CRUD Http API can expose them as endpoints:
+
+```bash
+POST /api/$procs/signUp
+{
+  "email": ...,
+  "name": ...
+}
+```
+
+And, for the frontend, TanStack query hooks can be used to call the procedures:
+
+```tsx
+import { useHooks } from '@zenstackhq/tanstack-query/react';
+import { schema } from '~/schema';
+
+export default function SignUp() {
+  const crud = useHooks(schema);
+  const { mutateAsync: signUp } = crud.$procs.signUp();
+
+  const handleSubmit = async (data) => {
+    await signUp(data);
+  };
+
+  return (
+    <form onSubmit={handleSubmit}>
+      {/* form fields */}
+    </form>
+  );
+}
+```
+
+Combined with access policies, custom procedures provide a powerful way to encapsulate complex business logic and expose them as APIs with minimum effort.
+
+## Conclusion
+
+ZenStack v3 will be a big step forward in terms of extensibility and flexibility. The new architecture allows us to provide a more powerful and expressive ORM experience while still maintaining simplicity and ease of use.
+
+In the [next post](../next-chapter-3/index.md), we'll continue to explore how the new plugin system will allow you to make deep customizations to ZenStack in a clean and maintainable way.
diff --git a/blog/next-chapter-3/index.md b/blog/next-chapter-3/index.md
new file mode 100644
index 00000000..8ccda0d5
--- /dev/null
+++ b/blog/next-chapter-3/index.md
@@ -0,0 +1,194 @@
+---
+title: ZenStack - The Next Chapter (Part III. New Plugin System)
+description: This post explores the new plugin system of ZenStack v3.
+tags: [zenstack, v3]
+authors: yiming
+image: ../next-chapter-1/cover.png
+date: 2025-04-11
+---
+
+# ZenStack - The Next Chapter (Part III. New Plugin System)
+
+![Cover Image](../next-chapter-1/cover.png)
+
+In the [previous post](../next-chapter-2/index.md), we discussed the new extensibility opportunities of the core ORM by adopting Kysely. In this post, we'll continue exploring the plan for v3's new plugin system that allows you to deeply customize ZenStack's behavior in a clean and maintainable way.
+
+<!-- truncate -->
+
+## Plugin Composition
+
+ZenStack v2 provided a rudimentary [plugin system](../../docs/the-complete-guide/part2) that allows you to participate in the process of `zenstack generate`. It's sufficient for use cases like generating OpenAPI specs or TanStack Query hooks. However, there's no well-defined way to extend the ORM's runtime behavior in a pluggable way.
+
+V3 aims to provide a more complete plugin system that allows you to contribute at the schema, generation, and runtime levels. A plugin can include the following parts:
+
+1. A `plugin.zmodel` file that can define attributes, functions, procedures, etc.
+  V2 already offers this. When running `zenstack generate`, all ZModel contributions from plugins will be merged with the user ZModel files.
+
+1. A generator function that's called during generation
+  V2 already offers this. The function will be called at `zenstack generate` time and given the ZModel AST (and potentially the Prisma DMMF too, TBD) as input. The generator can interpret attributes, functions, etc. defined in `plugin.zmodel`.
+
+1. A runtime plugin class that implements various callbacks
+  This provides great flexibility for a plugin to hook into the ORM's lifecycle at various levels - more about this in the next section.
+
+## Runtime Plugin
+
+A runtime plugin is an object satisfying the following interface. It may look a bit complex because it contains callbacks for different purposes. Don't worry, we'll dissect them shortly.
+
+```ts
+interface RuntimePlugin {
+  /**
+   * Plugin ID.
+   */
+  id: string;
+
+  /**
+   * Intercepts an ORM query.
+   */
+  onQuery?: (
+    args: PluginContext,
+    proceed: ProceedQueryFunction
+  ) => Promise<unknown>;
+
+  /**
+   * Kysely query transformation.
+   */
+  transformKyselyQuery?: (
+    args: PluginTransformKyselyQueryArgs
+  ) => RootOperationNode;
+
+  /**
+   * Kysely query result transformation.
+   */
+  transformKyselyResult?: (
+    args: PluginTransformKyselyResultArgs
+  ) => Promise<QueryResult<UnknownRow>>;
+
+  /**
+   * This callback determines whether a mutation should be intercepted, and if so,
+   * what data should be loaded before and after the mutation.
+   */
+  mutationInterceptionFilter?: (
+    args: MutationHooksArgs
+  ) => MaybePromise<MutationInterceptionFilterResult>;
+
+  /**
+   * Called before an entity is mutated.
+   */
+  beforeEntityMutation?: (
+    args: PluginBeforeEntityMutationArgs
+  ) => MaybePromise<void>;
+
+  /**
+   * Called after an entity is mutated.
+   */
+  afterEntityMutation?: (
+    args: PluginAfterEntityMutationArgs
+  ) => MaybePromise<void>;
+}
+```
+
+To install a plugin, simply call the client's `$use` method to pass in its definition.
+
+```ts
+const client = new ZenStackClient(schema)
+  .$use({
+    id: 'my-plugin',
+    onQuery: (args, proceed) => ...
+  });
+```
+
+### ORM Query Interception
+
+A high-level way of hooking into the ORM's lifecycle is to intercept the CRUD calls: `create`, `update`, `findMany`, etc. The `args` parameter contains the model (e.g., "User"), the operation (e.g., "findMany"), and the arguments (e.g.: `{ where: { id: 1 } }`). The `proceed` parameter is an async function that triggers the CRUD's execution. Things you can do include:
+
+1. Executing arbitrary code before and after calling `proceed`.
+2. Altering the query arguments.
+3. Transforming the query results.
+4. Overriding the call completely without calling `proceed`.
+
+Here's an example of logging slow queries:
+
+```ts
+const client = new ZenStackClient(schema)
+  .$use({
+    id: 'slow-query-logger',
+    onQuery: async (args, proceed) => {
+      const start = Date.now();
+      const result = await proceed(args.queryArgs);
+      const duration = Date.now() - start;
+      if (duration > 1000) {
+        logger.log(`Slow query: ${args.model}, ${args.operation}, ${JSON.stringify(args.queryArgs)}`);
+      }
+      return result;
+    }
+  });
+```
+
+The plugin can also have side effects by making extra ORM calls (the `args` parameter includes the current `ZenStackClient` instance), or even start a transaction to group multiple operations.
+
+ORM query interception is useful for many scenarios, but it doesn't intercept CRUD made with the query builder API:
+
+```ts
+// the following call will not trigger the plugin's `onQuery` callback
+await client.$qb.selectFrom('User')
+  .leftJoin('Post', 'Post.authorId', 'User.id')
+  .select(['User.id', 'User.email', 'Post.title'])
+  .execute();
+```
+
+To ubiquitously handle all database operations, whether originating from ORM call or query builder, use the Kysely transformers explained in the next section.
+
+### Kysely Transformation
+
+Kysely has a built-in [plugin mechanism](https://kysely.dev/docs/plugins) that allows you to transform the SQL-like query tree before execution and transform the query result before returning to the caller. ZenStack v3 will leverage it directly in its plugin system. Here's an example for automatically attaching prefixes to id fields during insert:
+
+```ts
+import { OperationNodeTransformer } from 'kysely';
+
+const client = new ZenStackClient(schema)
+  .$use({
+    id: 'id-prefixer',
+    transformKyselyQuery: ({node}) => {
+      if (!InsertQueryNode.is(node)) {
+        return node;
+      } else {
+        return new IdPrefixTransformer().transform(node);
+      }
+    }
+  });
+
+// a transformer that recursively visit the node and prefix primary key
+// assignment values
+class IdPrefixTransformer extends OperationNodeTransformer {
+  ...
+}
+```
+
+No matter how you access the database with ZenStack, using the ORM API or query builder, eventually, an operation is transformed into a Kysely query tree and then executed. Intercepting at the Kysely level allows you to preprocess and post-process all database queries uniformly.
+
+### Entity Mutation Hooks
+
+Sometimes, you don't care about what SQL is executed, but instead, you want to tap into entity mutation events: entities created, updated, or deleted. You can intercept entity mutations via [ORM Query Interception](#orm-query-interception) or [Kysely Transformation](#kysely-transformation), but it can be rather complex to implement.
+
+The entity mutation hooks are designed to make this scenario easy. It allows you to directly provide callbacks invoked before and after a mutation happens. There are a few complexities to consider, though:
+
+1. Accessing the entities pre/post mutation
+
+  It's often desirable to be able to inspect entities impacted by the mutation (before and after). However, loading the entities can be expensive, especially for mutations affecting many rows.
+
+2. Side effects and transaction
+   
+  There can be cases where you want to have database side effects in your callbacks, and you may wish your side effects and the original mutation to happen atomically. However, unconditionally employing a transaction for every mutation can bring unnecessary overhead.
+
+To mitigate these problems, a runtime plugin allows you to provide an additional `mutationInterceptionFilter` callback to "preflight" an interception. The callback gives you access to the mutation that's about to happen and lets you return several pieces of information that control the interception:
+
+- If the mutation should be intercepted at all
+- If the pre-mutation entities should be loaded and passed to the `beforeEntityMutation` call
+- If the post-mutation entities should be loaded and passed to the `afterEntityMutation` call
+- If a transaction should be used to wrap around the before/after hooks call and the mutation itself
+
+By carefully implementing the "preflight" callback, you can minimize the performance impact caused by the mutation hooks.
+
+## Conclusion
+
+ZenStack v3's core part will focus on providing the database access primitives, and most of the upper-level features will be implemented as plugins. This can include access control, soft delete, encryption, etc. An emphasis on extensibility will allow developers to adapt the ORM to the exact needs of their applications.
diff --git a/blog/no-api/cover.png b/blog/no-api/cover.png
new file mode 100644
index 00000000..508554dc
Binary files /dev/null and b/blog/no-api/cover.png differ
diff --git a/blog/no-api/index.md b/blog/no-api/index.md
new file mode 100644
index 00000000..4f4940b7
--- /dev/null
+++ b/blog/no-api/index.md
@@ -0,0 +1,188 @@
+---
+description: This post introduces how modern tools and frameworks help you derive web APIs without explicitly implementing them.
+tags: [api]
+authors: yiming
+date: 2024-03-31
+image: ./cover.png
+---
+
+# The Many Ways Not to Build an API
+
+![Cover image](cover.png)
+
+Building an API is one of the most important things one learns when getting into backend development. There are many good reasons why the entire industry pays so much attention to this topic - styles, transport, security, extensibility, documentation, testing, etc. A good set of APIs allow your application to work great not only through the UI, but also in headless mode, enabling endless integration opportunities for your users.
+
+<!-- truncate -->
+
+However, there are probably more good reasons that you shouldn't spend too much time on this topic. Especially when you're just starting out, your frontend is likely to be the only consumer of your API, and for most startup apps that situation can last for quite a while, if not forever.
+
+In this post, let's explore some of the alternative ways of having APIs without explicitly building them.
+
+## SSR Data Loader
+
+If you've used some of the modern full-stack frameworks like Next.js, Remix, or Nuxt, you're probably familiar with how SSR should be implemented. Here's an example using Next.js (with the old pages router):
+
+```tsx
+import type { InferGetServerSidePropsType, GetServerSideProps } from 'next'
+ 
+type Repo = {
+  name: string
+  stargazers_count: number
+}
+
+export const getServerSideProps = (async () => {
+  const res = await fetch('https://api.github.com/repos/vercel/next.js')
+  const repo: Repo = await res.json()
+  return { props: { repo } }
+}) satisfies GetServerSideProps<{ repo: Repo }>
+
+export default function Page({
+  repo,
+}: InferGetServerSidePropsType<typeof getServerSideProps>) {
+  return (
+    <main>
+      <p>{repo.stargazers_count}</p>
+    </main>
+  )
+}
+```
+
+In such a page implementation, the backend and frontend code are colocated, and there's an implicit network boundary. What happens during the initial rendering is that the framework executes the data loader function (`getServerSideProps`) on the server side, fetches the data, and renders the page into HTML with the data. The data is also serialized into the HTML so that the client-side React runtime can "hydrate" and make the page interactive.
+
+During a route transition, the data loader will solely behave as a JSON data API. The client-side code fetches it to get the data for rendering the new page.
+
+If we understand API as something that serves data to the client, SSR data loaders are indeed APIs with an implicit contract and serving just one single page.
+
+## React Server Component
+
+SSR is helpful, but it's coarse-grained at the page level. React Server Component (RSC) took a step further by pushing such implicit APIs to the component level. Here's an example using Next.js's app router:
+
+```tsx
+export function ServerComponent() {
+    console.log('This will only be printed on the server');
+    return (
+        <div>
+            <h1>Server Component</h1>
+            <p>My secret key: {process.env.MY_SECRET_ENV}</p>
+        </div>
+    );
+}
+```
+
+RSC is a complex topic with many detailed differences from SSR. For example, its code is guaranteed to run only on the server side, and, as a consequence, it sends its rendering result to the client instead of JSON data. However, conceptually, it's also an implicit kind of API that serves data to the client. I find such a mental model easier to understand, too.
+
+## Functions as API
+
+I'm talking about RPC here. RPC is an ancient technology, almost as old as computer networking. If you had done any [DCOM](https://en.wikipedia.org/wiki/Distributed_Component_Object_Model) programming on Windows, I bet you hate how awkward it was. 
+
+Surprisingly, thanks to the power of TypeScript, new Node.js RPC frameworks like [tRPC](https://trpc.io) and [ts-rest](https://ts-rest.com/) now provide extremely pleasant and snappy developer experiences. 
+
+Here's an example using tRPC:
+
+```ts
+// server code
+
+const appRouter = router({
+  user: {
+    // list users
+    list: publicProcedure.query(() => db.user.findMany()),
+
+    // create a user
+    create: publicProcedure
+      .input(z.object({ name: z.string() }))
+      .mutation(async ({ input }) => db.user.create(input))
+  },
+});
+
+// Export type router type signature, this is used by the client.
+export type AppRouter = typeof appRouter;
+
+const server = createHTTPServer({
+  router: appRouter,
+});
+
+server.listen(3000);
+```
+
+```ts
+// client code
+
+// Initialize the tRPC client
+const trpc = createTRPCClient<AppRouter>({
+  links: [
+    httpBatchLink({
+      url: 'http://localhost:3000',
+    }),
+  ],
+});
+
+const users = await trpc.user.list.query();
+console.log('Users:', users);
+
+const createdUser = await trpc.user.create.mutate({ name: 'trpc lover' });
+console.log('Created user:', createdUser);
+```
+
+As you can see, the non-business-logic boilerplate has been reduced to a minimum. You just write functions. The entire RPC process happens transparently. And the TypeScript type system ensures that the client and server are always in sync.
+
+## Database schema as API
+
+As much as we feel ashamed to admit it, most web apps are CRUD apps. Simple as it may sound, many CRUD API implementations are bloated. I still remember my typical NestJS GraphQL APIs: a schema, a controller, a resolver, a DTO, a database repository, ... so much work for so little fun, and you can sense how much duplication there is.
+
+A more sensible approach is to let the database schema drive the entire thing, since CRUD is all about the database anyway. Mapping a database schema into a web API is not difficult, but the challenge is how to secure it. There are two different strategies to it.
+
+If you use PostgreSQL and are proficient with using its row-level security feature, you can choose from several tools/services built above RLS, including [Supabase](https://supabase.com), [PostgREST](https://postgrest.org/), and [PostGraphile](https://www.graphile.org/postgraphile/). They all provide a way to expose database CRUD as a web API, assuming you've configured the RLS rules to properly secure the access.
+
+Another strategy is to model access control declaratively and enforce it in the application layer. [ZenStack](https://zenstack.dev/) (built above Prisma ORM) and [Hasura](https://hasura.io/) are good examples of this approach. The following code shows how access policies are defined with ZenStack and how a secured CRUD API can be derived automatically.
+
+```zmodel
+model User {
+    id Int @id() @default(autoincrement())
+    email String @unique()
+    posts Post[]
+
+    // make user profile public
+    @@allow('read', true)
+}
+
+model Post {
+    id Int @id() @default(autoincrement())
+    title String
+    published Boolean @default(false)
+    author User? @relation(fields: [authorId], references: [id])
+    authorId Int?
+
+    // author has full access
+    @@allow('all', auth() == author)
+
+    // logged-in users can view published posts
+    @@allow('read', auth() != null && published)
+}
+```
+
+```ts
+// The following code shows how to serve an automatic CRUD API with Express.js
+
+import { PrismaClient } from '@prisma/client';
+import { enhance } from '@zenstackhq/runtime';
+import { ZenStackMiddleware } from '@zenstackhq/server/express';
+import express from 'express';
+
+const prisma = new PrismaClient();
+const app = express();
+
+// serve CRUD API at "/api/model"
+app.use(
+    '/api/model',
+    ZenStackMiddleware({
+        // getSessionUser extracts the current session user from the request, its
+        // implementation depends on your auth solution
+        getPrisma: (request) => enhance(prisma, { user: getSessionUser(request) }),
+    })
+);
+```
+
+
+## Conclusion
+
+We are currently experiencing a boom in new tooling. The zeitgeist is straight-to-the-point, more streamlined, pleasant-to-write, and type-safety. The way we think about APIs and implement them is definitely at the center of this reform. I hope this post gives you a glimpse of the future, even if some of the ideas may seem too bold for now.
\ No newline at end of file
diff --git a/blog/ocp/cover.jpg b/blog/ocp/cover.jpg
new file mode 100644
index 00000000..7298b17c
Binary files /dev/null and b/blog/ocp/cover.jpg differ
diff --git a/blog/ocp/index.md b/blog/ocp/index.md
new file mode 100644
index 00000000..c1be131f
--- /dev/null
+++ b/blog/ocp/index.md
@@ -0,0 +1,563 @@
+---
+description: Use a real example to illustrate how to achieve good design by applying polymorphism from the database to the UI.
+tags: [orm, database, oop, design, typescript, polymorphism]
+authors: jiasheng
+image: ./cover.jpg
+date: 2024-03-28
+---
+
+# End-To-End Polymorphism: From Database to UI, Achieving SOLID Design
+![Cover image](cover.jpg)
+
+## Polymorphism Is the Key To Open-Closed Principle
+
+The three fundamental pillars of Object-Oriented Programming(OOP) are **Encapsulation**, **Inheritance**, and **Polymorphism**.  Polymorphism is likely the least-mentioned concept, possibly because this term is not frequently used in daily life. However, it is actually the most important one because it is the key to achieving compliance with the Open-Closed Principle (OCP) in OOP.  
+<!--truncate-->
+The Open-Closed Principle is one of the five [SOLID](https://en.wikipedia.org/wiki/SOLID_(object-oriented_design)) principles of object-oriented design. It states that software entities should be open for extension but closed for modification. This means that you should be able to extend the behavior of a system without modifying existing code.  I always treat this as one of the ultimate goals in software design as it fosters flexibility, extensibility, and maintainability for the system. 
+
+I have no doubt that you have already benefited from it in your backend or frontend code. But have you considered database modeling? What’s benefit you could get from it? Let me illustrate it with a real example, from the database to the UI.
+
+
+## A Classical Feed System
+
+Let's use typical news feed system as an example, which you can think of as a simplified version of Twitter.
+
+1. You can create or delete a post.
+2. You can publish or unpublish a post. When published, other logged-in users can view it.
+3. You can like or unlike a post. Each post displays its total like count and its author.
+
+You know what, there's already one there 😉
+
+[https://github.com/zenstackhq/docs-tutorial-nextjs](https://github.com/zenstackhq/docs-tutorial-nextjs)
+
+It is built using Next.js and [ZenStack](https://zenstack.dev/?utm_campaign=devto&utm_medium=organic&utm_content=ocp). 
+
+> If you haven’t heard about ZenStack, it supercharges Prisma ORM with a fine-grained Authorization layer, auto-generated type-safe APIs/hooks to unlock its full potential for full-stack development.
+> 
+
+The content now only supports post with text. Here comes the polymorphic part, let’s try to add support for image and video. Regardless of the programming language you're using, you could definitely model it in your code like this:
+
+```mermaid
+classDiagram
+    Content <|-- Post
+    Content <|-- Image
+    Content <|-- Video
+    Content : +String id
+    Content : +Date created
+    Content : +Date updated
+    Content: +bool published
+    Content: +string AuthorId
+    class Post{
+      +String title
+      +String content
+    }
+    class Image{
+      +string url
+      +int size
+    }
+    class Video{
+      +string url
+      +int duration
+    }
+```
+
+However, it might not be that straightforward at the database layer.
+
+## Database Modeling
+
+Thanks to Prisma, the entity used to model the database appears similar to the Type or Class used in the code. Here is the definition for the existing `Post` :
+
+```tsx
+model Post {
+  id String @id @default(cuid())
+  createdAt DateTime @default(now())
+  updatedAt DateTime @updatedAt
+  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)
+}
+```
+
+> The bottom part is the access control policy, with which it could directly generate the secured type-safe front-end hooks.
+> 
+
+Unfortunately [Prisma hasn’t supported polymorphism yet.](https://github.com/prisma/prisma/issues/1644) As such, you can't use inheritance to model the entity in the same way as in your programming language, as depicted in the above class diagram. The good news is that we could imitate it using table inheritance.
+
+### Table Inheritance
+
+Without inheritance, what else can we rely on to establish relationships between different models? Of course, it is the foreign key, the reason we refer to it as a relational database.  So let’s refactor our existing `Post` model as below:
+
+```tsx
+model Content {
+  id String @id @default(cuid())
+  createdAt DateTime @default(now())
+  updatedAt DateTime @updatedAt
+  published Boolean @default(false)
+  author User @relation(fields: [authorId], references: [id])
+  authorId String
+  contentType String // discriminator indicates the specific type 
+  likes Like[] // like is added here for all kinds of content
+  post Post?  // 1-1 relation with Post
+  // author has full access
+  @@allow('all', auth() == author)
+
+  // logged-in users can view published posts
+  @@allow('read', auth() != null && published)
+}
+
+model Post {
+  id String @id @default(cuid())
+  title String
+  content Content @relation(fields: [id], references: [id], onDelete: Cascade, onUpdate: Cascade)
+
+  // author has full access
+  @@allow('create,delete', auth() == content.author)
+  // logged-in users can view 
+  @@allow('read', auth() != null)
+}
+```
+
+- `Content` would be the base model with the common data and access policy for all the different kinds of feed type.
+- `contentType` is added to indicate the concrete type that this content record represents. The value should be the model name of the concrete type, such as `Post`.
+- Each concrete feed type model like `Post` has a one-to-one relation with `Content`.  It has its own specific data like `title` for a post feed.
+- In concrete type,  the access policy needs to reference the `Content` model to check the author because it has been moved there.
+- The new One-to-Many relation with `Like` model is added to the `Content` model.
+
+So let’s try to add a new `Image` feed type, here is what we need to change:
+
+```tsx
+model Image {
+  id String @id @default(cuid())
+  url String
+  content Content @relation(fields: [id], references: [id], onDelete: Cascade, onUpdate: Cascade)
+
+  // if 
+  @@allow('create,delete', auth() == content.author)
+  // logged-in users can view 
+  @@allow('read', auth() != null)
+}
+
+model Content {
+   ...
+   image Image?  // add 1-1 relation with image 
+   ...
+}
+```
+
+Thanks to the table inheritance, we don’t have to duplicate all the common fields that have been moved into the `Content` model.   Having said that, you can still see some repetitive thing need to be done for every new concrete type:
+
+- ***Need to add the `content` relation field for each new type.***
+- ***Need to add the reversed relation field in `Content` model.***
+- ***Has to define the same access policy rule for each concrete type.***
+
+<aside>
+💡 pain-point 1: I will show you how to address them later. 😉
+
+</aside>
+
+## Backend
+
+Nothing needs to do here. Seriously, it's not a joke. ZenStack directly generates API and frontend hooks according to the schema.  So let’s jump to the frontend. 
+
+## Frontend
+
+### Use `Content` to handle all the common behavior
+
+- Read all the content
+    
+    ```tsx
+      // list all contents that're visible to the current user
+      const { data: contents } = useFindManyContent(
+        {
+          include: {
+            author: true,
+            post: true,   
+            image: true,
+            ... /// list all the concrete content type here**
+          },
+          orderBy: { createdAt: "desc" },
+        },
+        // fetch only when user's logged in
+        { disabled: !session?.user }
+      );
+    ```
+    
+    The `useFindManyContent` is auto-generated by ZenStack, and it inherits the best type-safety DX Prisma client API provided.   For example,  you can use `include` to get the concrete content type data in a single query.  And the best part is that the return type will match exactly according to your query:
+    
+    ![content-type](https://github.com/zenstackhq/zenstack/assets/16688722/eadbb827-67c3-467d-9910-f3549ccad497)
+    
+    The benefit is that if you try to access the field from a concrete type without **include** it in this query,  you will see the error instantly:
+    
+    ![error](https://github.com/zenstackhq/zenstack/assets/16688722/e7688459-4159-41c4-9e47-b00289ab4140)
+    
+    ***On the other hand, when adding a new concrete type, you always need to modify this query to include the new type***
+    
+    <aside>
+    💡 pain-point 2: I will show you how to address them later. 😉
+    
+    </aside>
+    
+- Update content
+    
+    ```tsx
+      async function onTogglePublished(content: Content) {
+        await updateContent({
+          where: { id: content.id },
+          data: { published: !content.published },
+        });
+      }
+      
+      async function onToggleLike(content: Content, isLiked: boolean) {
+        if (isLiked) {
+          await deleteLike({
+            where: {
+              authorId: user.id,
+            },
+          });
+        } else {
+          await createLike({
+            data: {
+              contentId: content.id,
+              authorId: user.id,
+            },
+          });
+        }
+      }
+    ```
+    
+- Delete content
+    
+    ```tsx
+      async function onDelete(content: Content) {
+        await deleteContent({ where: { id: content.id } });
+      }
+    ```
+    
+    Thanks to the Cascade relation, we can simply delete the content, and database will take care of deleting the concrete type. 
+    
+
+### Handle Concrete Type
+
+- Create
+    
+    ```tsx
+      async function onCreatePost() {
+        const title = prompt("Enter post title");
+        if (title) {
+          await createPost({
+            data: {
+              title,
+              content: {
+                create: {
+                  contentType: "Post",
+                  authorId: user?.id,
+                },
+              },
+            },
+          });
+        }
+      }
+    ```
+    
+    When creating a concrete content, you first need to create the `Content` type to maintain the foreign relation. Thankfully, due to the fine-grained API, you don't have to do this separately.
+    
+    ***Have you noticed that you need to hard-coded the correct `contentType` here?***
+    
+    <aside>
+    💡 pain-point 3: I will show you how to address them later. 😉
+    
+    </aside>
+    
+
+- Render the concrete content
+    
+    ```tsx
+            {contents?.map((content) => {
+              let concreteContent = <></>;
+              if (content.contentType == "Post") {
+                concreteContent = <span>{content.post?.title}</span>;
+              } else if (content.contentType == "Image") {
+                concreteContent = (
+                  <Image src= {content.image?.url || ""} alt={content.id} />
+                );
+              }
+              ...
+             }
+    ```
+    
+    Since we have retrieved all the content data above, we can use `contentType` discriminator to decide how to render it. 
+    
+    ***Despite its simplicity, the if-else branch logic in the code is very error-prone. You have to ensure that the data accessed, such as `content.post?.title`, matches the literal string `"Post"` specified in the if condition. TypeScript has no knowledge of this, which is why the nullable operator is necessary whenever accessing the concrete type field.***
+    
+    <aside>
+    💡 pain-point 4: I will show you how to address them later. 😉
+    
+    </aside>
+    
+
+## Polymorphism at Database
+
+By utilizing table inheritance, we managed to avoid repetition to some extent. However, do you still remember the issues we encountered? Would they have been resolved if polymorphism were available at the database level?  
+
+Action speaks louder than words, let's do it! Although Prisma has not yet supported polymorphism, ZenStack, as a believer in Prisma, has taken the initiative to add it in the V2 beta version through a unique approach. You can find all the details in the document below:
+
+[Polymorphic Relations | ZenStack](https://zenstack.dev/docs/next/guides/polymorphism)
+
+### Real Inheritance Between models
+
+With the `extend` keyword introduced by ZenStack,  now in schema you can use real inheritance between the models:
+
+```tsx
+model Content {
+  id String @id @default(cuid())
+  createdAt DateTime @default(now())
+  updatedAt DateTime @updatedAt
+  published Boolean @default(false)
+  author User @relation(fields: [authorId], references: [id])
+  authorId String
+  contentType String
+  likes Like[]
+  @@delegate(contentType)
+
+  // author has full access
+  @@allow('all', auth() == author)
+
+  // logged-in users can view published posts
+  @@allow('read', auth() != null && published)
+}
+
+model Post extends Content {
+  title String
+}
+
+model Image extends Content {
+  url String
+}
+```
+
+Let's review the pain-point 1 we had using the new approach.
+
+- ~~Need to add the `content` relation field for each new type.~~
+- ~~Need to add the reversed relation field in `Content` model.~~
+- ~~Has to define the same access policy rule for each concrete type.~~
+
+<aside>
+✅ pain-point 1 is resolved.
+
+</aside>
+
+The practical benefit it brings is that when you need to add a new Type, such as Video, here is all you need to change:
+
+```tsx
+model Video extends Content {
+  url String
+  duration Int
+}
+```
+
+The Open-Closed Principle is perfectly achieved here.
+
+### Union Type
+
+Let's return to the place where we retrieve all the contents:
+
+```tsx
+  // list all contents that're visible to the current user
+  const { data: contents } = useFindManyContent(
+    {
+      include: {
+        author: true,
+        likes: true,
+      },
+      orderBy: { createdAt: "desc" },
+    },
+    // fetch only when user's logged in
+    { disabled: !session?.user }
+  );
+```
+
+Have you noticed the difference? We no longer need to specify all the concrete types in the `include` properties. We have once again adhered to the Open-Closed Principle. ✌️
+
+<aside>
+✅ pain-point 2 is resolved
+
+</aside>
+
+How this is achieved?  if you look at  the return type of `useFindManyContent`  you will know why:
+
+![FindMany-type](https://github.com/zenstackhq/zenstack/assets/16688722/52bf5d56-c7b4-458f-af23-981e171d311c)
+
+In generated types/functions, `Content` is actually a union type of all the delegated concrete types:
+
+```tsx
+Type Content = ({ contentType: 'Post' } & Post) 
+  | ({ contentType: 'Image' } & Image) 
+  | ...;
+```
+
+> This is another missing feature of Prisma: [https://github.com/prisma/prisma/issues/2505](https://github.com/prisma/prisma/issues/2505)
+> 
+
+So that TypeScript's type narrowing can work its magic for the following code:
+
+```tsx
+      if (content.contentType == "Post") {
+        concreteContent = <span>{content.title}</span>;
+      } else if (content.contentType == "Image") {
+        concreteContent = (
+          <Image src= {content.url || ""} alt={content.id} />
+        );
+      } 
+      ...
+```
+
+Right, this is the rendering logic we see above. All the field access here is completely typesafe now. 
+
+<aside>
+✅ pain-point 4 is resolved.
+
+</aside>
+
+There's one pain-point remaining when creating content. Let's examine what it looks like now:
+
+```tsx
+  async function onCreatePost() {
+    const title = prompt("Enter post title");
+    if (title) {
+      await createPost({ data: { title, authorId: user.id } });
+    }
+  }
+```
+
+Now you can totally forget about the existence of `Content` at all, just create a `Post` as it is.
+
+<aside>
+✅ pain-point 3 is resolved.
+
+</aside>
+
+How is this achieved? Besides the `extend` keyword, a new attribute `@@delegate(contentType)` has been added to mark the base model. It delegates field access to concrete models as needed. For example, when trying to create a `Post` instance, it knows it has to first create an associated `Content` and set the `contentType` to `Post`.
+
+## Polymorphic UI Components
+
+All the pain-points have been resolved, what else is left?  Let’s take a look at the render part:
+
+```tsx
+      if (content.contentType == "Post") {
+        concreteContent = <span>{content.title}</span>;
+      } else if (content.contentType == "Image") {
+        concreteContent = (
+          <Image src= {content.url || ""} alt={content.id} />
+        );
+      } 
+      ...
+```
+
+Although it's now type-safe, this typical `if-else` branch definitely violates the Open-Closed Principle. Whenever you add a new type, you must add a new `else-if` statement here.  Let’s see how we can use polymorphism to address it.
+
+Firstly,  let’s create a UI component for each concrete type.
+
+```tsx
+export const Post = ({ content }: { content: Content }) => {
+  const post = content as Post;
+  return <span>{post.title}</span>;
+};
+
+export const Image = ({ content }: { content: Content }) => {
+  const image = content as Image;
+  return (<Image src= {image.url || ""} alt={image.id} />);
+};
+```
+
+The `content` prop contains all the information for this specific type instance, which can be used for rendering.
+
+Consider how you typically eliminate large `if-else` statements in coding. A straightforward approach is to use a dictionary instead:
+
+```tsx
+const componentLookup: {
+  [key: string]: (content: { content: Content }) => JSX.Element;
+} = {
+  Post
+  Image
+  // Add other components here...
+};
+
+const PolymorphicContent = ({
+  componentName,
+  content,
+}: {
+  componentName: string;
+  content: Content;
+}) => {
+  const Component = componentLookup[componentName];
+  return Component ? <Component content={content} /> : null;
+};
+```
+
+For the rendering part, we can allow `PolymorphicContent` to decide which component to render based on the `contentType`.
+
+```tsx
+   <div>
+      <PolymorphicContent
+        componentName={content.contentType}
+        content={content}
+      ></PolymorphicContent>
+      <p className="text-lg"> by {content.author.email}</p>
+    </div>
+```
+
+Now, whenever you add a new concrete type, all you need to do, besides creating a new component, is to add the component to the `componentLookup`. Isn’t it good enough?
+
+No, it still violates the Open-Closed Principle as you must modify the existing code, and ensure the Component has the same name as your concrete type.  Let’s see how we can resolve it.
+
+### Ultimate Polymorphism
+
+To avoid manually defining the `componentLookup` dictionary, we can utilize the `import` mechanism of TypeScript to do that for us.    
+
+Let's create new `content-components.tsx` file and transfer the `Post` and `Image` UI components into it. After doing so, you can use the `import *`  to get that dictionary.
+
+```tsx
+import * as ContentComponents from "./content-components";
+```
+
+Awesome! Let’s make a final tweak to get to the Eden：
+
+```tsx
+const PolymorphicContent = ({
+  content,
+}: {
+  content: Content;
+}) => {
+  const Component = ContentComponents[content.contentType];
+  return Component ? <Component content={content} /> : null;
+};
+```
+
+We can directly use **`content.contentType`** to get the appropriate UI component. It is a union type of all the specific type names:
+
+```tsx
+type contentType = 'Post' | 'Video' | ...
+```
+
+Now, you don't need to worry about mismatches between the UI component name and the concrete type name defined in the schema. For instance, if you add a new `Video` type to the schema, but you haven't added a UI component with the same name in `content-components.tsx`, TypeScript is smart enough to let you know it:
+
+```tsx
+Property 'Video' does not exist on type 'typeof import("/content-components")'.
+```
+
+![Eden](https://github.com/zenstackhq/zenstack/assets/16688722/519287e4-ed01-4e39-a4a6-195b4d8c9033)
+
+## Conclusion
+
+I must say thank you for walking this long road with me to reach here. However, I believe this is not even worth mentioning compared to the lifelong path of pursuing excellence in software design as a good software engineer.   
+
+So, here's the final complete project for you, hope it can be some help for you:
+
+[GitHub - zenstackhq/docs-tutorial-nextjs at polymorphic](https://github.com/zenstackhq/docs-tutorial-nextjs/tree/polymorphic)
diff --git a/blog/polymorphism/cover.png b/blog/polymorphism/cover.png
new file mode 100644
index 00000000..87f9103d
Binary files /dev/null and b/blog/polymorphism/cover.png differ
diff --git a/blog/polymorphism/index.md b/blog/polymorphism/index.md
new file mode 100644
index 00000000..82ef4c23
--- /dev/null
+++ b/blog/polymorphism/index.md
@@ -0,0 +1,456 @@
+---
+description: This post explores different patterns for implementing polymorphism in ORMs and how ZenStack can add the missing feature into Prisma.
+tags: [prisma, orm, database, polymorphism]
+authors: yiming
+date: 2023-12-21
+image: ./cover.png
+---
+
+# Tackling Polymorphism in Prisma
+
+![Cover image](cover.png)
+
+> EDIT 4/25/2024: the feature has been released in [ZenStack V2](/docs/upgrade-v2).
+
+Prisma is a beloved ORM for NodeJS developers. As with every popular open-source project, it has a long wishlist. Here are two prominent examples:
+
+- [Support for a Union type #2505](https://github.com/prisma/prisma/issues/2505)
+- [Support for Polymorphic Associations #1644](https://github.com/prisma/prisma/issues/1644)
+
+They are about the same thing and have received at least 1000 reactions in total. The ask is for modeling an inheritance hierarchy in the database. ORM’s responsibility is to fix the gap between the two world views: "table + relations" and "object-oriented". Polymorphism is an obvious missing piece in its mission.
+
+<!--truncate-->
+
+We at [ZenStack](https://zenstack.dev) are Prisma believers. Otherwise, we wouldn’t have taken the trouble to build a whole new OSS project above it. Our unique approach allows us to pick up where Prisma left off, and in this post, I’ll explore how to "add" this essential feature.
+
+## An example use case
+
+In object-oriented programming, polymorphism is the ability to let specialized types share data and behavior from their common generalized forms. The definition applies to database modeling as well. Suppose you’re designing a CMS system with models like `Image`, `Video`, `Post`, `Page`, `Form`, etc. They each have unique characteristics but share several common traits, like the owner, time of creation, title, etc. It makes good sense to extract a common `Asset` base type:
+
+```mermaid
+---
+title: CMS System
+---
+classDiagram
+    Asset <|-- Image
+    Asset <|-- Video
+    Asset <|-- Post
+    Asset <|-- Page
+    class Asset {
+        String title
+        User owner
+        Date createdAt
+        Int viewCount
+    }
+    class Image {
+        String format
+        Bytes data
+    }
+    class Video {
+        Int duration
+        String url
+    }
+    class Post {
+        String content
+    }
+    class Page {
+        String html
+    }
+```
+
+With such a hierarchy, we can avoid repeating the common fields in each model. Moreover, the `Asset` abstraction allows us to query across different concrete types. For example, you can list and paginate all assets a user owns.
+
+With Prisma, we would like to be able to program against the hierarchy in the following ways:
+
+```ts
+// get a page of assorted types of assets for a user
+const assets = await db.asset.findMany({ 
+	where: { owner: { id: userId } }, skip: 10, take: 10
+});
+
+// get a list of specific type of assets for a user
+const videos = await db.video.findMany({ where: { owner: { id: userId } } });
+
+// aggregation on the base assets
+const totalViews = await db.asset.aggregate({ _sum: { viewCount: true } });
+```
+
+## Patterns for polymorphism in databases
+
+There has been a long history of studying how to organize polymorphic models in databases and how to expose them from ORMs. The approaches can be summarized into three patterns.
+
+### 1. Concrete Table Inheritance
+
+This is the most straightforward pattern as it does no abstraction at the database level. There'll be no table for `Asset`. Each concrete asset type has its own table, which contains all fields (including the common ones) of that type. The ORM, however, can generate the `Asset` type as a pure abstraction, allowing you to view or update only the common traits.
+
+```mermaid
+erDiagram
+    User {
+    }
+    Image {
+        id Int PK
+        String title
+        createdAt Date
+        viewCount Int
+        format String
+        data Bytes
+				ownerId Int FK
+    }
+    User ||--o{ Image: owns
+    Video {
+        id Int PK
+        String title
+        createdAt Date
+        viewCount Int
+				duration Int
+        url String
+				ownerId Int FK
+    }
+    User ||--o{ Video: owns
+    Post {
+        id Int PK
+        String title
+        createdAt Date
+        viewCount Int
+				content String
+				ownerId Int FK
+    }
+    User ||--o{ Post: owns
+    Page {
+        id Int PK
+        String title
+        createdAt Date
+        viewCount Int
+				html String
+				ownerId Int FK
+    }
+    User ||--o{ Page: owns
+```
+
+This approach has a significant drawback: since there are no relations among the concrete tables, there's no efficient way to "show a list of all assets", especially if you want to paginate the result.
+
+Also, from the ORM point of view, the `User` model is "polluted" by the concrete types as it's directly connected to them without an intermediary.
+
+
+### 2. Single Table Inheritance
+
+Another simple solution is to merge all fields of all types into one wide table and use a `type` field to record the concrete type as a discriminator.
+
+```mermaid
+erDiagram
+    User {
+	  }
+	  Asset {
+        id Int PK
+		    String title
+		    createdAt Date
+        viewCount Int
+		    type String
+		    format String
+		    data
+		    duration
+		    url
+		    content
+		    html
+		    fields
+		    ownerId Int FK
+	  }
+	  User ||--o{ Asset: owns
+```
+
+The downside of this approach is that you'll end up with wide rows with many null fields. The problem gets worse if there are many concrete types with many different attributes.
+
+### 3. Delegated Types
+
+"Delegated Types" is a more sophisticated approach that stores both the generic and the concrete types as tables and uses one-to-one relationships to connect them. It's called "delegated" because the accesses are delegated to the concrete tables when fetching information specific to concrete types.
+
+```mermaid
+erDiagram
+		User {
+		}
+    Asset {
+        id Int PK
+				String title
+        createdAt Date
+        viewCount Int
+				type String
+				ownerId Int FK
+		}
+		User ||--o{ Asset: owns
+    Image {
+        id Int PK
+        format String
+        data Bytes
+        assetId Int FK
+    }
+		Image ||--|| Asset: delegates
+    Video {
+        id Int PK
+				duration Int
+        url String
+        assetId Int FK
+    }
+		Video ||--|| Asset: delegates
+    Post {
+        id Int PK
+				content String
+        assetId Int FK
+    }
+		Post ||--|| Asset: delegates
+    Page {
+        id Int PK
+				html String
+        assetId Int FK
+    }
+		Page ||--|| Asset: delegates
+```
+
+This approach avoids the drawbacks of the previous two patterns. Each table contains precisely the information it needs to have. The shared `Asset` table allows listing and pagination at the generalized level. The foreign keys between `Asset` and concrete types also allow for efficiently fetching specialized fields through join. Of course, join is not free and incurs a performance penalty.
+
+## How does ZenStack enhance Prisma?
+This post explores how ZenStack can add the missing "polymorphism" feature to Prisma. Before getting into details, let me first briefly explain what ZenStack is.
+
+
+ZenStack is a toolkit that stretches Prisma's power to full-stack development. At its most fundamental level, it extends Prisma at two levels - schema and runtime.
+
+### Schema language
+
+ZenStack introduced a schema language called "ZModel" - a superset of Prisma schema. It adds new language constructs for better extensibility, for example, custom attributes, model inheritance, etc. At compile time, ZenStack transforms ZModel to a standard Prisma schema, which can be used to generate `PrismaClient` and migration records using the standard prisma CLI.
+
+```zmodel
+abstract model Asset {
+  id Int
+  createdAt DateTime @default(now)
+  viewCount Int @default(0)
+  owner User @relation(fields: [ownerId], references: [id])
+  ownerId Int
+
+  // custom attributes for defining access policies
+  @@allow('read', true)
+  @@allow('all', owner == auth())
+}
+
+// model inheritance with "extends"
+model Video extends Asset {
+  duration Int
+  url String
+}
+```
+
+You'll see how we use them to express polymorphism shortly.
+
+### Runtime behavior
+
+Extending schema language is only useful when it makes a difference at the runtime. ZenStack creates an "enhanced" `PrismaClient` at runtime by wrapping a proxy around the standard `PrismaClient` and injecting new behaviors - for example, the automatic enforcement of access policies (as shown in the example above).
+
+```ts
+import { Prisma } from '@prisma/client';
+import { enhance } from '@zenstackhq/runtime';
+import { getCurrentUser } from './auth';
+
+const prisma = new PrismaClient();
+
+// `db` has the same typing as `prisma`, but it automatically
+// enforces access policies
+const db = enhance(prisma, { user: getCurrentUser() });
+```
+
+Later, we'll explore leveraging the runtime enhancement to achieve polymorphism.
+
+## Implementing "Delegated Types" in ZenStack
+
+Among the three patterns for modeling polymorphism, "Delegated Types" is the most flexible. For the remaining part of the post, let's explore how to implement it "the Prisma way".
+
+### 1. Modeling polymorphism
+
+The most natural way to model inheritance is with inheritance. ZModel already allows you to declare abstract models and inherit from them. However, it's simply a syntactic sugar for inserting all fields of the base model into the sub-one. We'll reach the "Concrete Table Inheritance" pattern if we use it to implement polymorphism.
+
+We can make a tweak and use a variant form of inheritance:
+
+```zmodel
+model Asset {
+  id Int
+  createdAt DateTime @default(now)
+  viewCount Int @default(0)
+  owner User @relation(fields: [ownerId], references: [id])
+  ownerId Int
+  type String @discriminator
+  
+  @@delegate
+}
+
+model Video extends Asset {
+  duration Int
+  url String
+}
+```
+
+A few quick notes:
+
+- The `Asset` model is NOT abstract, indicating it's mapped to a table.
+- The `@@delegate` attribute marks the base model to delegate field access to sub-models when needed.
+- The `@discriminator` attribute marks the `type` field to store the concrete type an `Asset` entity is linked to. This field will be managed automatically.
+- The concrete models, like `Video`, become "delegatee" by inheriting from `Asset`.
+
+### 2. The diverging "physical" and "logical" models
+
+The challenge with implementing "Delegated Types" is that the physical model in the database differs from the logical model developers need. At the physical level, Asset and Video should establish a one-to-one relationship, like:
+
+```zmodel
+model Asset {
+  // Asset fields
+  ...
+
+  // relationships to concrete models
+  video Video?
+  image Image?
+  ...
+}
+
+model Video {
+  id Int @id()
+  duration Int
+  url String
+
+  // one-to-one relationship to Asset
+  asset Asset @relation(fields: [assetId], references: [id])
+  assetId Int @unique
+}
+```
+
+However, from the programming point of view, we don't want to see the relation between `Asset` and `Video`. Instead, we'd like to see a `Video` model that includes all fields from Asset. This is how polymorphism is supposed to behave in the OO world.
+
+```zmodel
+model Asset {
+  // Asset fields
+  ...
+}
+
+model Video {
+  id Int @id()
+
+  // inherited from Asset
+  createdAt DateTime @default(now)
+  viewCount Int @default(0)
+  owner User @relation(fields: [ownerId], references: [id])
+  ownerId Int
+
+  // Video specifics
+  duration Int
+  url String
+}
+```
+
+How do we fulfill these two diverging needs 🤔?
+
+Recall that ZenStack transforms the ZModel into a standard Prisma schema and then lets the Prisma CLI process it. We can let it generate two Prisma schemas when delegate types are used - one for the physical and the other for the logical. 
+
+The physical schema will be used to create migration files and a regular `PrismaClient` for database access.
+
+![Physical model](physical-model.png)
+
+The logical one is for generating an "enhanced" `PrismaClient` typing that provides merged types for concrete models.
+
+![Logical model](logical-model.png)
+
+### 3. Runtime delegation
+
+Separating "physical" and "logical" Prisma schemas solves our typing needs. However, at runtime, the enhanced `PrismaClient` must also behave accordingly. As mentioned, ZenStack uses the `enhance()` API to create a proxy around the standard `PrismaClient`, allowing it to alter its runtime behavior and take care of the separate "physical" tables.
+
+Here are a few examples:
+
+- Creating a concrete entity
+  
+    ```ts
+    await db.video.create({ 
+      data: { ownerId: userId, duration: 100, url: '...' }
+    });
+    ```
+  
+    is transformed to:
+
+    ```ts
+    await prisma.asset.create({
+      data: {
+        ownerId: userId,
+        type: 'Video',
+        video: {
+          create: { data: { duration: 100, url: '...'} }
+        }
+      }
+    });
+    ```
+
+- Reading a concrete entity
+
+    ```ts
+    const video = await db.video.findFirst();
+    ```
+
+    is transformed to (conceptually):
+
+    ```ts
+    const { asset, ...video } = await prisma.video.findFirst({ 
+      include: { asset: true } 
+    });
+
+    // merge fields of base and concrete models
+    const video = { ...asset, ...video };
+    ```
+
+- Reading a base entity
+
+    ```ts
+    const asset = await db.asset.findFirst();
+    ```
+
+    is transformed to (conceptually):
+
+    ```ts
+    const { video, image, /* other types */, ..._asset } = 
+      await prisma.asset.findFirst({
+        include: { video: true, image: true, ... }
+    });
+
+    // merge concrete model fields
+    const asset = {
+      ..._asset, 
+	    ...(_asset.type === 'Video' ? video: _asset.type === 'Image' ? image) 
+    };
+    ```
+
+### 4. Achieving perfect typing
+
+The "logical" `PrismaClient` helps us put a "polymorphic" overlay above the physical table schema. However, it's far from perfect. For example, `db.asset.create()` method shouldn't exist. You are not supposed to create a standalone base entity. Although we can reject it with a runtime error, it'd be much nicer if we could exclude it from typing in the first place.
+
+Another example is reading with `db.asset` (e.g., `db.asset.findFirst()`) returns model type Asset. This is not wrong, but it'd be much easier to use if a discriminated union type is returned instead:
+
+```ts
+type AssetUnion = 
+  ({ type: 'Video' } & Video) 
+  | ({ type: 'Image' } & Image) 
+  | ...;
+```
+
+So that TypeScript's type narrowing can work its magic for the following code:
+
+```ts
+// asset is typed as AssetUnion
+const asset = await db.asset.findFirst();
+
+if (asset.type === 'Video') {
+  console.log(asset.duration);
+} else if (asset.type === 'Image') {
+  console.log(asset.format);
+} else {
+  ...
+}
+```
+
+How do we achieve this? We used a separate Prisma schema to generate our "logical" `PrismaClient` typing, and nothing is stopping us from post-processing the generated types. We can customize the types either with TypeScript's type manipulation or just directly "edit" the ".d.ts" files using "ts-morph".
+
+## Wrap up
+
+Polymorphism is a long-standing feature request, and I'm excited about addressing this issue from an external perspective. Do you find the proposed design here plausible? The feature is in the POC stage, and we're confident about landing it in ZenStack within Q1 of 2024. Please leave your thoughts about the proposal in this [GitHub issue](https://github.com/zenstackhq/zenstack/issues/430) or simply join our [Discord](https://discord.gg/Ykhr738dUe) to chat. It's always easier to make changes before a feature is released.
+
+If you use Prisma in a non-trivial way, [ZenStack](https://zenstack.dev) is worth your time for a closer look. You may find some of its enhancements to Prisma beneficial to your project.
diff --git a/blog/polymorphism/logical-model.png b/blog/polymorphism/logical-model.png
new file mode 100644
index 00000000..6e6c764b
Binary files /dev/null and b/blog/polymorphism/logical-model.png differ
diff --git a/blog/polymorphism/physical-model.png b/blog/polymorphism/physical-model.png
new file mode 100644
index 00000000..0f31e5a9
Binary files /dev/null and b/blog/polymorphism/physical-model.png differ
diff --git a/blog/prisma-auth/index.md b/blog/prisma-auth/index.md
index 1532cb4d..502b613e 100644
--- a/blog/prisma-auth/index.md
+++ b/blog/prisma-auth/index.md
@@ -48,7 +48,7 @@ If we enforce it at the database level, we don't need to ever worry about it aga
 
 If you're familiar with PostgreSQL, you probably immediately think of [row-level security](https://www.postgresql.org/docs/15/ddl-rowsecurity.html). However, it's Postgres only and requires in-depth knowledge of SQL. If you use Prisma ORM, you can achieve a better result with ZenStack in a database-independent and easy-to-adopt way.
 
-I assume you’ve used Prisma before or already have a basic understanding of it. If not, here is a greatly simplified [crash course](https://zenstack.dev/docs/intro/prisma-crash-course) to get you started.
+I assume you’ve used Prisma before or already have a basic understanding of it. If not, here is a greatly simplified [crash course](/docs/the-complete-guide/part1/prisma) to get you started.
 
 ## Quick Overview
 
@@ -277,7 +277,7 @@ model Post {
 
 Check out the following post for more details:
 
-[Soft Delete: Implementation Issues in Prisma and Solution in Zenstack](https://zenstack.dev/blog/soft-delete)
+[Soft Delete: Implementation Issues in Prisma and Solution in ZenStack](https://zenstack.dev/blog/soft-delete)
 
 ## Advanced Topics
 
diff --git a/blog/react-admin/cover.png b/blog/react-admin/cover.png
new file mode 100644
index 00000000..5be7ec12
Binary files /dev/null and b/blog/react-admin/cover.png differ
diff --git a/blog/react-admin/forbidden.png b/blog/react-admin/forbidden.png
new file mode 100644
index 00000000..7a11d9ad
Binary files /dev/null and b/blog/react-admin/forbidden.png differ
diff --git a/blog/react-admin/index.md b/blog/react-admin/index.md
new file mode 100644
index 00000000..89517215
--- /dev/null
+++ b/blog/react-admin/index.md
@@ -0,0 +1,551 @@
+---
+title: Building an Admin Console With Minimum Code Using React-Admin, Prisma, and ZenStack
+description: This post introduces how to build a fully-secured admin console with minimum code using React-Admin, Prisma, and ZenStack.
+tags: [react, prisma, react-admin]
+authors: yiming
+date: 2024-03-11
+image: ./cover.png
+---
+
+# Building an Admin Console With Minimum Code Using React-Admin, Prisma, and ZenStack
+
+![Cover Image](cover.png)
+
+Building a customer-facing application is exciting. But it's not much fun when it comes to the admin console part. However, almost every serious app requires some sort of admin console for operation needs. It doesn't need to be slick in design or have blazing-fast performance. The main focus should be reliability, cost-effectiveness, and extensibility.
+
+There are many different types of admin consoles. In this post, we'll discuss the most common ones: those that allow non-technical people to make changes to the database and ensure proper permission management at the same time.
+
+<!-- truncate -->
+
+To build such an admin console, you have three choices:
+
+1. Don't build it. Just use a database editor (like phpMyAdmin, Prisma Studio, etc.) for the job.
+2. Quickly construct one from scratch by combining high-level libraries and tools.
+3. Build it the same way as you would build the customer-facing app.
+
+Each choice has its unique pros and cons. This post will focus on the 2nd, which tends to have the best balance between cost and quality for most real-world applications.
+
+## A quick look into the libraries we'll use
+
+### React-Admin
+
+[React-Admin](https://marmelab.com/react-admin/) is a React-based frontend framework for building admin applications that talk to a backend data API. It offers a pluggable mechanism for easily adapting to the specific API style of your backend.
+
+### Prisma
+
+[Prisma](https://www.prisma.io) is a modern TypeScript-first ORM that allows you to manage database schemas easily, make queries and mutations with great flexibility, and ensure excellent type safety.
+
+### ZenStack
+
+[ZenStack](https://zenstack.dev) is a toolkit built above Prisma that adds access control, automatic CRUD web API, etc. It unleashes the ORM's full power for full-stack development.
+
+## The target app
+
+I'll use a fantasy content publishing app as an example to facilitate our discussion. The app is about creating blog posts, making editorial changes, and publishing them. It involves three user roles:
+
+- **Author**: who can create posts and submit them for editorial review.
+- **Editor**: who can make editorial changes (to other people's posts) and publish posts.
+- **Admin**: who can do anything.
+
+A blog post can be in one of the following statuses:
+
+- **Draft**
+   
+   The author is working on it privately. Only the author and admin can access it.
+
+- **Submitted**
+   
+   The author has submitted it for review. Editors can read and update it.
+
+- **Published**
+   
+   The post is published and readable to all. Editors can unpublish it.
+
+Of course, admin users can do anything to posts of any status.
+
+## Hooking things up
+
+An admin console is a full-stack web app, so the easiest way to build it is to use a full-stack framework that couples the frontend and backend in a single project. I'll use Next.js in this post, but you can also choose to go with a decoupled frontend and backend (e.g., a Vite React SPA and an ExpressJS backend). The fundamentals stay unchanged.
+
+You can find the link to the completed project's GitHub repo at the end of the post.
+
+### Scaffolding the app
+
+I used [create-t3-app](https://create.t3.gg/) to scaffold the Next.js app, with TypeScript, Prisma, and "app router" enabled in the options. You can also use [create-next-app](https://www.npmjs.com/package/create-next-app) for the job and install Prisma manually.
+
+Also, install react-admin, ZenStack, and a few other dependencies:
+
+```bash
+npm install react-admin jsonwebtoken
+npm install -D tsx
+npx zenstack@latest init
+npm install @zenstackhq/server
+```
+
+One thing to notice is that the `zenstack init` command copies `prisma/schema.prisma` into `/schema.zmodel`. The ZModel language is the DSL ZenStack uses to model both the database schema and other stuff (like access control policies). ZModel is a superset of Prisma Schema. For now, we'll just author it like we would with a Prisma schema.
+
+Just remember when you need to make database schema changes:
+1. Always edit `schema.zmodel`.
+2. Run `npx zenstack generate` instead of `npx prisma generate`
+
+### Database schema
+
+Here's the schema for our app:
+
+```zmodel
+generator client {
+    provider = "prisma-client-js"
+}
+
+datasource db {
+    provider = "sqlite"
+    url      = "file:./db.sqlite"
+}
+
+model User {
+    id Int @id @default(autoincrement())
+    createdAt DateTime @default(now())
+    updatedAt DateTime @updatedAt
+    email String @unique
+    password String @password @omit
+    name String?
+    role String @default('Author') // Author, Editor, Admin
+    posts Post[]
+
+    @@allow('all', true)
+}
+
+model Post {
+    id Int @id @default(autoincrement())
+    createdAt DateTime @default(now())
+    updatedAt DateTime @updatedAt
+    title String
+    content String
+    status String @default('Draft') // Draft, Submitted, Published
+    user User @relation(fields: [userId], references: [id], onDelete: Cascade)
+    userId Int
+
+    @@allow('all', true)
+}
+```
+
+A few quick notes:
+
+1. The `@password` attribute marks the `password` field to be automatically hashed (using [bcryptjs](https://www.npmjs.com/package/bcryptjs)) before saving to the database. The `@omit` attribute marks the field to be dropped before an entity is returned for a query. These are extensions ZenStack made to Prisma.
+   
+2. The `@@allow` attribute defines access policies that verdict the operations allowed. We'll allow anyone to do anything for now. Access control is the most significant extension ZenStack made to Prisma. We'll revisit it soon.
+
+### Building authentication
+
+React-Admin provides built-in authentication (AuthN) flow and UI. You only need to implement an "auth provider" to adapt to your backend's AuthN mechanism. For simplicity, we'll use a simple JWT-based authentication design with email and password as credentials. To do that, first, create an API handler at `/src/app/api/auth/login/route.ts`:
+
+```ts title="/src/app/api/auth/login/route.ts"
+import { compare } from "bcryptjs";
+import { sign } from "jsonwebtoken";
+import { db } from "~/server/db";
+
+export async function POST(request: Request) {
+  const { email, password }: { email: string; password: string } =
+    await request.json();
+
+  const user = await db.user.findUnique({ where: { email } });
+  if (!user) {
+    return new Response("invalid email and password combination", {
+      status: 401,
+    });
+  }
+
+  if (!(await compare(password, user.password))) {
+    return new Response("invalid email and password combination", {
+      status: 401,
+    });
+  }
+
+  return Response.json({
+    id: user.id,
+    email: user.email,
+    token: sign(
+      { sub: user.id.toString(), email: user.email, role: user.role },
+      process.env.JWT_SECRET!,
+    ),
+  });
+}
+```
+Then, implement a React-Admin [auth provider](https://marmelab.com/react-admin/AuthProviderWriting.html), which defines how the frontend interacts with the backend for authentication:
+
+```ts title="/src/lib/auth-provider.ts"
+import type { AuthProvider } from "react-admin";
+
+const authProvider: AuthProvider = {
+  login: ({ username, password }) => {
+    return fetch("/api/auth/login", {
+      method: "POST",
+      // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
+      body: JSON.stringify({ email: username, password }),
+    })
+      .then((resp) => {
+        if (resp.status < 200 || resp.status >= 300) {
+          throw new Error(resp.statusText);
+        }
+        return resp.json();
+      })
+      .then((auth) => {
+        localStorage.setItem("auth", JSON.stringify(auth));
+      });
+  },
+
+  logout: () => {
+    localStorage.removeItem("auth");
+    return Promise.resolve();
+  },
+
+  checkAuth: () =>
+    localStorage.getItem("auth") ? Promise.resolve() : Promise.reject(),
+
+  checkError: (error) => {
+    // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
+    const status = error.status;
+    if (status === 401) {
+      localStorage.removeItem("auth");
+      return Promise.reject();
+    }
+    // other error code (404, 500, etc): no need to log out
+    return Promise.resolve();
+  },
+
+  getIdentity: () => {
+    const auth = localStorage.getItem("auth");
+    if (!auth) {
+      return Promise.reject("not authenticated");
+    }
+    try {
+      // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
+      const { id, email }: { id: number; email: string } = JSON.parse(auth);
+      return Promise.resolve({ id, email });
+    } catch (error) {
+      return Promise.reject(error);
+    }
+  },
+
+  getPermissions: () => Promise.resolve(""),
+};
+
+export default authProvider;
+```
+
+The auth provider will be used when we build the CRUD UI with React-Admin.
+
+### Building data provider
+
+Our admin console is essentially about making queries and mutations to the underlying database. To achieve that, we'll need to complete two pieces of work:
+
+1. backend: a CRUD API
+2. frontend: a [data provider](https://marmelab.com/react-admin/DataProviderWriting.html) to consume the backend API
+
+ZenStack makes the backend part almost effortless by providing an automatic RESTful API based on the database schema. Here's how to install it as a Next.js API handler ("/src/app/api/model/[...path]/router.ts"):
+
+```ts title="/src/app/api/model/[...path]/router.ts"
+import { type AuthUser, enhance } from "@zenstackhq/runtime";
+import RestApiHandler from "@zenstackhq/server/api/rest";
+import { NextRequestHandler } from "@zenstackhq/server/next";
+import { type JwtPayload, verify } from "jsonwebtoken";
+import type { NextRequest } from "next/server";
+import { db } from "~/server/db";
+
+// the `getPrisma` callback returns a `PrismaClient` instance used for accessing the database
+// for a request, here we return a ZenStack-enhanced `PrismaClient` to enforce access control
+async function getPrisma(req: NextRequest) {
+  let user: AuthUser | undefined;
+  const auth = req.headers.get("authorization");
+  if (auth) {
+    const token = auth.split("Bearer ")[1];
+    if (token) {
+      const decoded = verify(token, process.env.JWT_SECRET!) as JwtPayload;
+      user = {
+        id: parseInt(decoded.sub!),
+        role: decoded.role as string,
+      };
+    }
+  }
+
+  // use the user identity extracted from the JWT token to access the database
+  return enhance(db, { user });
+}
+
+const handler = NextRequestHandler({
+  getPrisma,
+  handler: RestApiHandler({ endpoint: "http://localhost:3000" }),
+  useAppDir: true,
+});
+
+export {
+  handler as DELETE,
+  handler as GET,
+  handler as PATCH,
+  handler as POST,
+  handler as PUT,
+};
+```
+
+With the above code, you have a complete set of CRUD APIs served at "/api/model". For example, you can list all users with `GET /api/model/user`. See the full specification of the CRUD API [here](https://zenstack.dev/docs/reference/server-adapters/api-handlers/rest). As you can see, the code expects a JWT token in the `Authorization` header, and you'll see how the frontend sends it when we get to the part of building CRUD UI with React-Admin.
+
+Now, let's tackle the React-Admin data provider part, which is pretty straightforward. For brevity, I'm only showing partial code here, but you can find the complete implementation at the end of this post.
+
+```ts title="/src/lib/data-provider.ts"
+type FetchFn = (url: string, init: RequestInit) => Promise<Response>;
+
+export function createDataProvider(
+  url: string,
+  customFetch?: FetchFn,
+): DataProvider {
+  const fetcher = customFetch ?? fetch;
+
+  // make the API fetch
+  const doFetch = async (url: string, init: RequestInit) => {
+    const resp = await fetcher(url, init);
+    if (resp.status < 200 || resp.status >= 300) {
+      throw new HttpError(resp.statusText, resp.status);
+    }
+    return await resp.json();
+  };
+
+  // convert React-Admin query params to URL search params
+  const getListQuerySearchParams = (
+    params: GetListParams | GetManyReferenceParams,
+  ) => {
+    const searchParams = new URLSearchParams();
+    if (params.filter) {
+      Object.entries<any>(params.filter).forEach(([key, value]) => {
+        searchParams.set(`filter[${key}]`, value.toString());
+      });
+    }
+
+    searchParams.set(
+      "sort",
+      params.sort.order === "ASC" ? params.sort.field : `-${params.sort.field}`,
+    );
+
+    searchParams.set(
+      "page[offset]",
+      ((params.pagination.page - 1) * params.pagination.perPage).toString(),
+    );
+    searchParams.set("page[limit]", params.pagination.perPage.toString());
+
+    return searchParams.toString();
+  };
+
+  // convert the API response to React-Admin query result
+  const makeListQueryResult = (
+    data: any[],
+    meta: { total: number },
+    params: GetListParams | GetManyReferenceParams,
+  ) => {
+    return {
+      data: data.map((item: any) => ({ id: item.id, ...item.attributes })),
+      total: meta.total,
+      pageInfo: {
+        hasNextPage:
+          meta.total > params.pagination.page * params.pagination.perPage,
+        hasPreviousPage: params.pagination.page > 1,
+      },
+    };
+  };
+
+  return {
+    getList: async (resource, params) => {
+      const reqUrl = `${url}/${resource}?${getListQuerySearchParams(params)}`;
+      const { data, meta } = await doFetch(reqUrl, {
+        method: "GET",
+      });
+      return makeListQueryResult(data, meta, params);
+    },
+
+    getOne: async (resource, params) => {
+      const reqUrl = `${url}/${resource}/${params.id}`;
+      const { data } = await doFetch(reqUrl, {
+        method: "GET",
+      });
+      return {
+        data: { id: data.id, ...data.attributes },
+      };
+    },
+
+    ...
+
+  } satisfies DataProvider;
+}
+```
+
+Now we've got all the infrastructure we need. Time to move on to building the UI now!
+
+### Building the CRUD UI
+
+This is where React-Admin shines - it makes building CRUD UI really, really easy. Here's how it goes:
+
+```ts title="/src/components/AdminApp.tsx"
+const AdminApp = () => (
+  <Admin dataProvider={dataProvider} authProvider={authProvider}>
+    <Resource
+      name="user"
+      list={ListGuesser}
+      edit={EditGuesser}
+      create={UserCreate}
+      hasCreate={true}
+      recordRepresentation="name"
+    />
+    <Resource
+      name="post"
+      list={PostList}
+      edit={EditGuesser}
+      create={PostCreate}
+      hasCreate={true}
+      recordRepresentation="title"
+    />
+  </Admin>
+);
+
+export const UserCreate = () => (
+  <Create>
+    <SimpleForm>
+      <TextInput source="email" type="email" validate={required()} />
+      <PasswordInput source="password" validate={required()} />
+      <TextInput source="name" validate={required()} />
+      <SelectInput
+        source="role"
+        choices={[
+          { id: "Author", name: "Author" },
+          { id: "Editor", name: "Editor" },
+          { id: "Admin", name: "Admin" },
+        ]}
+        defaultValue={"Author"}
+      />
+    </SimpleForm>
+  </Create>
+);
+
+const PostList = () => (
+  <List>
+    <Datagrid rowClick="edit">
+      <TextField source="id" />
+      <TextField source="title" />
+      <TextField source="content" />
+      <DateField source="createdAt" />
+      <DateField source="updatedAt" />
+      <ReferenceField source="userId" reference="user" />
+    </Datagrid>
+  </List>
+);
+
+export const PostCreate = () => (
+  <Create>
+    <SimpleForm>
+      <ReferenceInput source="userId" reference="user">
+        <SelectInput />
+      </ReferenceInput>
+      <TextInput source="title" validate={required()} />
+      <TextInput source="content" multiline rows={5} validate={required()} />
+      <SelectInput
+        source="status"
+        choices={[
+          { id: "Draft", name: "Draft" },
+          { id: "Submitted", name: "Submitted" },
+          { id: "Published", name: "Published" },
+        ]}
+        defaultValue={"Draft"}
+      />
+    </SimpleForm>
+  </Create>
+);
+
+export default AdminApp;
+```
+
+And the initialization of `dataProvider` looks like the following:
+
+```ts
+const dataProvider = createDataProvider("/api/model", (url, init) => {
+  // fetch JWT token from localStorage and send it in the `Authorization` header
+  const auth = localStorage.getItem("auth");
+  if (auth) {
+    const { token } = JSON.parse(auth);
+    if (init.headers instanceof Headers) {
+      init.headers.set("Authorization", `Bearer ${token}`);
+    } else {
+      init.headers = new Headers({ Authorization: `Bearer ${token}` });
+    }
+  }
+  return fetch(url, init);
+});
+```
+
+React-Admin simplifies constructing CRUD UI by offering high-level components that automatically talk to the backend API via the data provider. In many cases, the `Guesser` components already work great, but you can easily create custom ones by composing `Grid`, `List`, `Input`, etc.
+
+You should be able to log in and CRUD users and posts now.
+
+![CRUD UI](./react-admin-crud.gif)
+
+## What about authorization?
+
+Having CRUD to work is great. But we're still missing one significant piece in the puzzle: authorization. ZenStack makes implementing AuthZ a breeze by allowing you to define rules declaratively in the schema.
+
+Remember when building the CRUD API handler, we created an "enhanced" `PrismaClient` to access the database for the current request? The enhanced client automatically enforces access control. However, it's our responsibility to define the policies. Let's start with protecting the `User` model.
+
+```zmodel title="schema.zmodel"
+model User {
+  ...
+
+  // all logged in users can read all users
+  @@allow('read', auth() != null)
+ 
+  // a user can update himself, and an admin can update any user
+  @@allow('all', auth() == this || auth().role == 'Admin')
+}
+```
+
+Straightforward, right? In the policy rules, the special `auth()` function represents the current requesting user, which is the one that we extracted from the JWT token and passed to the `enhance` call previously.
+
+Then, let's get to the `Post` part. There are more requirements to cover:
+
+```zmodel title="schema.zmodel"
+model Post {
+  ...
+
+  // admins have full access to all posts
+  @@allow('all', auth().role == 'Admin')
+
+  // authors can create new posts for themselves with "Draft" status
+  @@allow('create', auth() == user && status == 'Draft')
+
+  // post owner can read their own posts
+  @@allow('read', auth() == user)
+
+  // non-draft posts can be read by all logged in users
+  @@allow('read', auth() != null && status != 'Draft')
+
+  // update rules for "Author"
+  @@allow('update',
+      auth().role == 'Author' // user is an author
+      && auth() == user // user is the post owner
+      && status != 'Published' // published posts can't be updated
+      && future().status != 'Published' // can't change status to "Published"
+  )
+
+  // update rules for "Editor"
+  @@allow('update', auth().role == 'Editor' && status != 'Draft')
+
+  // owner or admin can delete a post if it's not published
+  @@allow('delete', auth() == user && status != 'Published')
+}
+```
+
+A new feature here is the `future()` function. An "update" operation involves a pre-state and a post-state. By default, fields in the policy rules refer to the pre-state, but the `future()` function allows you to refer to the post-state.
+
+If you rerun `npx zenstack generate` and restart the dev server, the access policies will take effect. You'll notice some of the operations are rejected, for example, if you try to set a post's status to "Published" as an author.
+
+![Operation forbidden](./forbidden.png)
+
+## Conclusion
+
+I hope you enjoyed the reading and found the approach interesting. Building an admin console is often a less rewarding task because it doesn't directly add customer value. However, by combining the right tools, you can make it more fun and productive. In this post, we've only touched the surface of what React-Admin and ZenStack can do. They both have much more to offer, and I encourage you to explore them further.
+
+The completed sample project is here:: [https://github.com/ymc9/react-admin-blog](https://github.com/ymc9/react-admin-blog).
diff --git a/blog/react-admin/react-admin-crud.gif b/blog/react-admin/react-admin-crud.gif
new file mode 100644
index 00000000..6018a4ac
Binary files /dev/null and b/blog/react-admin/react-admin-crud.gif differ
diff --git a/blog/react-table/cover.png b/blog/react-table/cover.png
new file mode 100644
index 00000000..c292c724
Binary files /dev/null and b/blog/react-table/cover.png differ
diff --git a/blog/react-table/index.md b/blog/react-table/index.md
new file mode 100644
index 00000000..a28868fb
--- /dev/null
+++ b/blog/react-table/index.md
@@ -0,0 +1,349 @@
+---
+title: "Rendering Prisma Queries With React Table: The Low-Code Way"
+description: This post introduces a low-code approach to rendering database rows loaded with Prisma ORM as a table component using React Table.
+tags: [prisma,react-table,react-query,zenstack]
+authors: yiming
+date: 2024-07-21
+image: ./cover.png
+---
+
+# Rendering Prisma Queries With React Table: The Low-Code Way
+
+![Cover Image](cover.png)
+
+[React Table](https://tanstack.com/table), or more precisely, TanStack Table is a headless table UI library. If you're new to this kind of product, you'll probably ask, "What the heck is headless UI"? Isn't UI all about **the head**, after all? It all starts to make sense until you try something like React Table. 
+
+<!--truncate-->
+
+For two reasons, tables are one of the nastiest things to build in web UI. First, they are difficult to render well, considering the amount of data they can hold, the interactions they allow, and the need to adapt to different screen sizes. Second, their state is complex: sorting, filtering, pagination, grouping, etc. React Table's philosophy is to solve the second problem well and leave the first entirely to you. It manages the state and logic of a table but doesn't touch the rendering part because:
+
+> Building UI is a very branded and custom experience, even if that means choosing a design system or adhering to a design spec. - tanstack.com
+
+Tables are most commonly used to render database query results — in modern times, the output of an ORM. In this post, I'll introduce a way of connecting [Prisma](https://prisma.io) - the most popular TypeScript ORM, to React Table, with the help of [React Query](https://tanstack.com/query) and [ZenStack](/). You'll be amazed by how little code you need to write to render a full-fledged table UI.
+
+## A full-stack setup
+
+We need a full-stack application to query a database and render the UI. In this example, I'll use Next.js as the framework, although the approach can be applied to other similar frameworks (like Nuxt, SvelteKit, etc.), or to an application with decoupled front-end and back-end.
+
+We can easily create a new project with `npx create-next-app`. After that, we need to install several dependencies:
+
+- Prisma - the ORM
+- ZenStack - a full-stack toolkit built above Prisma
+- React Query - the data-fetching library
+- React Table - the headless table library
+
+We'll also use the legendary "North Wind" trading dataset (created by Microsoft many, many years ago) to feed our database. Here's its ERD:
+
+```mermaid
+erDiagram
+	Category {
+		Int id PK
+		String name
+		String description
+	}
+	Customer {
+		String id PK
+		String name
+		String contactName
+		String contactTitle
+		String address
+		String city
+		String postalCode
+		String country
+	}
+	Employee {
+		Int id PK
+		String lastName
+		String firstName
+		String title
+		String region
+	}
+	OrderDetail {
+		Int orderId
+		Int productId
+		Float unitPrice
+		Int quantity
+		Float discount
+	}
+	Order {
+		Int id PK
+		String customerId
+		Int employeeId
+		DateTime orderDate
+		DateTime shippedDate
+	}
+	Product {
+		Int id PK
+		String name
+		Int categoryId
+		Float unitPrice
+	}
+	Order ||--|{ OrderDetail : details
+	Order }o--|| Customer : customer
+	Order }o--|| Employee : employee
+	OrderDetail }o--|| Product : product
+	Product }o--|| Category : category
+
+```
+
+A [Prisma schema file](https://github.com/ymc9/react-query-table-zenstack/blob/main/prisma/schema.prisma) is authored to reflect this database structure.
+
+## The "free lunch" API
+
+SQL databases are not meant to be consumed from the frontend. You need an API to mediate. You can build such an API in many ways, but here we'll use [ZenStack](https://zenstack.dev) to "unbuild" it. ZenStack is a full-stack toolkit built above Prisma, and one of the cool things it does is to automagically derive a backend API from the schema.
+
+Setting ZenStack up is straightforward:
+
+1. Run `npx zenstack init` to prep the project. It copies the `schema.prisma` file into `schema.zmodel` - which is the schema file used by ZenStack. ZModel is a superset of Prisma schema.
+  
+2. Whenever you make changes to `schema.zmodel`, run `npx zenstack generate` to regenerate the Prisma schema and `PrismaClient`.
+
+ZenStack can provide a full set of CRUD API with Next.js in a few lines of code:
+
+```ts title="src/app/api/model/[...path]/route.ts"
+import { prisma } from '@/server/db';
+import { NextRequestHandler } from '@zenstackhq/server/next';
+
+const handler = NextRequestHandler({
+  getPrisma: () => prisma,
+  useAppDir: true,
+});
+
+export {
+  handler as DELETE,
+  handler as GET,
+  handler as PATCH,
+  handler as POST,
+  handler as PUT,
+};
+```
+
+Now you have a set of APIs mounted at `/api/model` that mirrors `PrismaClient`:
+
+- GET `/api/model/order/findMany?q=...`
+- GET `/api/model/order/count?q=...`
+- PUT `/api/model/order/update`
+- ...
+
+The query parameters and body also follow the corresponding `PrismaClient` method parameters.
+
+I know a big **🚨 NO THIS IS NOT SECURE 🚨** is flashing in your mind. Hold on, we'll get to that later.
+
+## The "free lunch" hooks
+
+Having a free API is cool, but writing `fetch` to call it is cumbersome. How about some free query hooks? Yes, add the `@zenstackhq/tanstack-query` plugin to the ZModel schema, and you'll have a set of fully typed React Query hooks generated for each model:
+
+```zmodel title="schema.zmodel"
+plugin hooks {
+  provider = '@zenstackhq/tanstack-query'
+  target = 'react'
+  output = 'src/hooks'
+}
+```
+
+The hooks call into the APIs we installed in the previous section, and they also precisely mirror `PrismaClient`'s signature:
+
+```ts
+import { useFindManyOrder } from '@/hooks/order';
+
+// data is typed as `(Order & { details: OrderDetail[]; customer: Customer })[]`
+const { data, isLoading, error } = useFindManyOrder({
+  where: { ... },
+  orderBy: { ... },
+  include: { details: true, customer: true }
+});
+```
+
+Please note that although React Query and React Table are both from TanStack, you don't have to use them together. React Table is agnostic to the data fetching mechanism. They just happen to play very well together.
+
+## Finally, let's build the table
+
+Creating a basic table is straightforward, You define the columns and then initialize a table instance with data. We'll see how to do it by building a table to display order details.
+
+```tsx
+// the relation fields included when querying `OrderDetail`
+const queryInclude = {
+  include: {
+    order: { include: { employee: true } },
+    product: { include: { category: true } },
+  },
+} satisfies Prisma.OrderDetailFindManyArgs;
+
+// create a column helper to simplify the column definition
+// The `Prisma.OrderDetailGetPayload<typeof queryInclude>` type gives us
+// the shape of the query result
+const columnHelper =
+  createColumnHelper<Prisma.OrderDetailGetPayload<typeof queryInclude>>();
+
+const columns = [
+  columnHelper.accessor('order.id', { header: () => <span>Order ID</span> }),
+
+  columnHelper.accessor('order.orderDate', {
+      cell: (info) => info.getValue()?.toLocaleDateString(),
+      header: () => <span>Date</span>,
+  }),
+
+  // other columns ...
+
+  columnHelper.accessor('order.employee.firstName', {
+      header: () => <span>Employee</span>,
+  }),
+];
+
+export const OrderDetails = () => {
+  // fetch data with query hooks
+  const { data } = useFindManyOrderDetail({
+    ...queryInclude,
+  });
+  
+  // create a table instance
+  const table = useReactTable({
+    data: orders ?? [],
+    columns,
+    getCoreRowModel: getCoreRowModel(),
+  });
+}
+```
+
+We can then render the table with some basic tsx:
+
+```tsx
+export const OrderDetails = () => {
+  ...
+
+  return (
+    <table>
+      <thead>
+        {table.getHeaderGroups().map((headerGroup) => (
+          <tr key={headerGroup.id}>
+            {headerGroup.headers.map((header) => (
+              <th key={header.id}>
+                {flexRender(
+                  header.column.columnDef.header,
+                  header.getContext()
+                )}
+              </th>
+            ))}
+          </tr>
+        ))}
+      </thead>
+      <tbody>
+        {table.getRowModel().rows.map((row) => (
+          <tr key={row.id}>
+            {row.getVisibleCells().map((cell) => (
+              <td key={cell.id}>
+                {flexRender(
+                  cell.column.columnDef.cell,
+                  cell.getContext()
+                )}
+              </td>
+            ))}
+          </tr>
+        ))}
+      </tbody>
+  );
+}
+```
+
+With the help of column definitions, React Table knows how to fetch data for a cell and transform it as needed. You only need to focus on properly laying the table out.
+
+![Simple table](table-1.png)
+
+What's cool about React Table is that you don't need to flatten the nested query result into tabular form. The columns can be defined to reach into deeply nested objects.
+
+## Making it fancier
+
+Tables allow you to do many things besides viewing data. Let's use pagination as an example to demonstrate how to enable such interaction in our setup.
+
+React Query has built-in support from front-end pagination. However, since we're rendering database tables, we want the pagination to run at the backend. First, we define a pagination state and set the table to use manual pagination mode (meaning that we handle the pagination ourselves):
+
+```tsx
+
+// highlight-start
+// pagination state
+const [pagination, setPagination] = useState<PaginationState>({
+  pageIndex: 0,
+  pageSize: PAGE_SIZE,
+});
+// highlight-end
+
+// highlight-start
+// fetch total row count
+const { data: count } = useCountOrderDetail();
+// highlight-end
+
+const table = useReactTable({
+  ...
+
+  // highlight-start
+  // pagination
+  manualPagination: true,
+  onPaginationChange: setPagination,
+  pageCount: Math.ceil((count ?? 0) / PAGE_SIZE),
+
+  // state
+  state: { agination },
+  // highlight-end    
+});
+```
+
+Also, update the hooks call to respect the pagination state:
+
+```tsx
+const { data } = useFindManyOrderDetail({
+  ...queryInclude,
+  // highlight-start
+  skip: pagination.pageIndex * pagination.pageSize,
+  take: pagination.pageSize,
+  // highlight-end
+});
+```
+
+Finally, add navigation buttons:
+
+```tsx
+<div>
+  <button onClick={() => table.previousPage()} disabled={!table.getCanPreviousPage()}>
+    Prev
+  </button>
+  <button onClick={() => table.nextPage()} disabled={!table.getCanNextPage()}>
+    Next
+  </button>
+  <span className="ml-2">
+    Page {table.getState().pagination.pageIndex + 1} of{' '}
+    {table.getPageCount().toLocaleString()}
+  </span>
+</div>
+```
+
+This part well demonstrates the value of "headless" UI. You don't need to manage detailed pagination state anymore. Instead, provide the bare minimum logic and let React Table handle the rest. Sorting can be implemented similarly. Check out the link at the end of this post for the complete code.
+
+![Table with pagination](table-2.png)
+
+## Tons of flexibility
+
+We've got a pretty cool table end-to-end working now, with roughly 200 lines of code. Less code is only one of the benefits of this combination. It also provides excellent flexibility in every layer of the stack:
+
+- **Prisma's query**
+
+    Prisma is known for its concise yet powerful query API. It allows you to do complex joins and aggregations without writing SQL. In our example, our table shows data from five tables, and we barely noticed the complexity.
+
+- **ZenStack's access control**
+
+    Remember I said we'll get back to the security issue? A real-world API must have an authorization mechanism with it. ZenStack's real power lies in its ability to define access control rules in the data schema. You can define rules like rejecting anonymous users or showing only the orders of the current login employee, etc. Read more details [here](/docs/the-complete-guide/part1/access-policy/).
+
+- **React Query's fetching**
+
+    React Query provides great flexibility around how data is fetched, cached, and invalidated. Leverage its power to build a highly responsive UI and reduce the load on the database at same time.
+
+- **React Table's state management**
+  
+    React Table has every aspect of a table's state organized for you. It provides a solid pattern to follow without limiting how you render the table UI.
+
+## Conclusion
+
+The evolution of dev tools is like a pendulum swinging backward and forward between simplicity and flexibility. All these years of wisdom have distilled into awesome tools like React Table and React Query, which seem to have found a good balance. They are not the simplest to pick up, but they are simple enough yet wonderfully flexible.
+
+--- 
+
+The complete sample code: [https://github.com/ymc9/react-query-table-zenstack](https://github.com/ymc9/react-query-table-zenstack)
\ No newline at end of file
diff --git a/blog/react-table/table-1.png b/blog/react-table/table-1.png
new file mode 100644
index 00000000..9da32408
Binary files /dev/null and b/blog/react-table/table-1.png differ
diff --git a/blog/react-table/table-2.png b/blog/react-table/table-2.png
new file mode 100644
index 00000000..fe1e43e8
Binary files /dev/null and b/blog/react-table/table-2.png differ
diff --git a/blog/redwood-auth/index.md b/blog/redwood-auth/index.md
index 32e602fd..a3ab8e97 100644
--- a/blog/redwood-auth/index.md
+++ b/blog/redwood-auth/index.md
@@ -217,11 +217,11 @@ Authorization is a very challenging topic, no matter what framework you choose t
 
 >[https://github.com/zenstackhq/sample-redwood-blog](https://github.com/zenstackhq/sample-redwood-blog)
 
-If you'd like to learn more about ZenStack, check out this comprehensive introduction [here](https://zenstack.dev/docs/intro/).
+If you'd like to learn more about ZenStack, check out this comprehensive introduction [here](https://zenstack.dev/docs).
 
 We created ZenStack based on this belief:
 
 > Since authorization is mainly about data, it should be modeled together with data and enforced close to the data.
 
-Do you share the same view? Yes or no, join our [Discord community](https://go.zenstack.dev/chat) and let us know your thoughts!
+Do you share the same view? Yes or no, join our [Discord community](https://discord.gg/Ykhr738dUe) and let us know your thoughts!
 
diff --git a/blog/refine-dev-backend/access-denied.png b/blog/refine-dev-backend/access-denied.png
new file mode 100644
index 00000000..672c17d3
Binary files /dev/null and b/blog/refine-dev-backend/access-denied.png differ
diff --git a/blog/refine-dev-backend/cover.png b/blog/refine-dev-backend/cover.png
new file mode 100644
index 00000000..78735c2f
Binary files /dev/null and b/blog/refine-dev-backend/cover.png differ
diff --git a/blog/refine-dev-backend/create-button-disabled.png b/blog/refine-dev-backend/create-button-disabled.png
new file mode 100644
index 00000000..ea4bf3c1
Binary files /dev/null and b/blog/refine-dev-backend/create-button-disabled.png differ
diff --git a/blog/refine-dev-backend/create-refine-app.png b/blog/refine-dev-backend/create-refine-app.png
new file mode 100644
index 00000000..17e0185a
Binary files /dev/null and b/blog/refine-dev-backend/create-refine-app.png differ
diff --git a/blog/refine-dev-backend/crud-ui.png b/blog/refine-dev-backend/crud-ui.png
new file mode 100644
index 00000000..ef7045ae
Binary files /dev/null and b/blog/refine-dev-backend/crud-ui.png differ
diff --git a/blog/refine-dev-backend/index.md b/blog/refine-dev-backend/index.md
new file mode 100644
index 00000000..ed8cf64c
--- /dev/null
+++ b/blog/refine-dev-backend/index.md
@@ -0,0 +1,381 @@
+---
+title: Low-Code Backend Solution for Refine.dev Using Prisma and ZenStack
+description: This post introduces how to build a fully-secured backend API for refine.dev projects with minimum code using Prisma and ZenStack.
+tags: [refine, react, authorization]
+authors: yiming
+date: 2024-05-27
+image: ./cover.png
+---
+
+# Low-Code Backend Solution for Refine.dev Using Prisma and ZenStack
+
+![Cover Image](cover.png)
+
+[Refine.dev](https://refine.dev/) is a very powerful and popular React-based framework for building web apps with less code. It focuses on providing high-level components and hooks to cover common use cases like authentication, authorization, and CRUD. One of the main reasons for its popularity is that it allows easy integration with many different kinds of backend systems via a flexible adapter design.
+
+This post will focus on the most important type of integration: database CRUD. I'll show how easy it is, with the help of Prisma and ZenStack, to turn your database schema into a fully secured API that powers your refine app. You'll see how we start by defining the data schema and access policies, derive an automatic CRUD API from it, and finally integrate with the Refine app via a "Data Provider."
+
+<!-- truncate -->
+
+## A quick overview of the tools
+
+### Prisma
+
+[Prisma](https://www.prisma.io) is a modern TypeScript-first ORM that allows you to manage database schemas easily, make queries and mutations with great flexibility, and ensure excellent type safety.
+
+### ZenStack
+
+[ZenStack](https://zenstack.dev) is a toolkit built above Prisma that adds access control, automatic CRUD web API, etc. It unleashes the ORM's full power for full-stack development.
+
+### Auth.js
+
+[Auth.js](https://authjs.dev/) (successor of NextAuth) is a flexible authentication library that supports many authentication providers and strategies. Although you can use many external services for auth, simply storing everything inside your database is often the easiest way to get started.
+
+## A blogging app
+
+I'll use a simple blogging app as an example to facilitate the discussion. We'll first focus on implementing the authentication and CRUD with essential access control and then expand to more advanced topics.
+
+You can find the link to the completed project's GitHub repo at the end of the post.
+
+### Scaffolding the app
+
+The `create-refine-app` CLI provides several handy templates to scaffold a new app. We'll use the "Next.js" one so that we can easily contain both the frontend and backend in the same project. Most of the ideas in this post can be applied to a standalone backend project as well.
+
+![create-refine-app](create-refine-app.png)
+
+We also need to install Prisma and NextAuth:
+
+```bash
+npm install --save-dev prisma
+npm install @prisma/client next-auth@beta
+```
+
+Finally, we'll create the database schema for our app:
+
+```zmodel title="prisma/schema.prisma"
+datasource db {
+  provider = "sqlite"
+  url      = "file:./dev.db"
+}
+
+generator client {
+  provider = "prisma-client-js"
+}
+
+model User {
+  id            String    @id() @default(cuid())
+  name          String?
+  email         String?   @unique()
+  emailVerified DateTime?
+  image         String?
+  createdAt     DateTime  @default(now())
+  updatedAt     DateTime  @updatedAt()
+  accounts      Account[]
+  sessions      Session[]
+  password      String
+  posts         Post[]
+}
+
+model Post {
+  id        String   @id() @default(cuid())
+  createdAt DateTime @default(now())
+  updatedAt DateTime @updatedAt()
+  title     String
+  content   String
+  status    String   @default("draft")
+  author    User     @relation(fields: [authorId], references: [id])
+  authorId  String
+}
+
+model Account {
+  ...
+}
+
+model Session {
+  ...
+}
+
+model VerificationToken {
+  ...
+}
+```
+
+:::info
+The `Account`, `Session`, and `VerificationToken` models are [required by Auth.js](https://authjs.dev/getting-started/adapters/prisma#schema).
+:::
+
+### Building authentication
+
+The focus of this post will be data access and access control. However, they are only possible with an authentication system in place. We'll use simple credential-based authentication in this app. The implementation involves creating an Auth.js configuration, installing an API route to handle auth requests, and implementing a Refine "Authentication Provider".
+
+I won't elaborate on the details of this part, but you can find the completed code [here](https://github.com/ymc9/refine-nextjs-zenstack/tree/main/src/providers/auth-provider). It should get the registration, login, and session management parts working.
+
+### Set up access control
+
+There are many ways to implement access control. People typically put the check in the API layer with imperative code. ZenStack offers a unique and powerful way to do it declaratively inside the database schema. Let's see how it works.
+
+First, let's initialize the project for ZenStack:
+
+```bash
+npx zenstack@latest init
+```
+
+It'll install a few dependencies and copies over the `prisma/schema.prisma` file to `/schema.zmodel`. ZModel is a superset of Prisma Schema Language that adds more features like access control.
+
+Next, we'll add policy rules to the schema:
+
+```zmodel title="schema.zmodel"
+model User {
+  ...
+
+  // everybody can signup
+  @@allow('create', true)
+
+  // full access by self
+  @@allow('all', auth() == this)
+}
+
+
+model Post {
+  ...
+
+  // allow read for all signin users
+  @@allow('read', auth() != null && status == 'published')
+
+  // full access by author
+  @@allow('all', author == auth())
+}
+```
+
+As you can see, the overall schema still looks very similar to the original Prisma schema. The `@@allow` directive defines access control rules. The `auth()` function returns the current authenticated user. We'll see how it's connected with the authentication system next.
+
+The most straightforward way to use ZenStack is to create an "enhancement" wrapper around the Prisma client. First, run the CLI to generate JS modules that support the enforcement of policies:
+
+```bash
+npx zenstack generate
+```
+
+Then, you can call the `enhance` API to create an enhanced PrismaClient.
+
+```ts
+const session = await auth();
+const user = session?.user?.id ? { id: session.user.id } : undefined;
+const db = enhance(prisma, { user });
+```
+
+Besides the `prisma` instance, the `enhance` function also takes a second argument that contains the current user. The user object provides value to the `auth()` function call in the schema at runtime.
+
+The enhanced PrismaClient has the same API as the original one, but it will enforce the policy rules automatically for you.
+
+### Automatic CRUD API
+
+Having the ORM instance enhanced with access control capabilities is great. We can now implement CRUD APIs without writing imperative authorization code as long as we use the enhanced client. However, wouldn't it be even cooler if the CRUD APIs were automatically derived from the schema?
+
+ZenStack makes it possible by providing a set of server adapters for popular Node.js frameworks. Using it with Next.js is easy. You'll only need to create an API route handler:
+
+```ts title="src/app/api/model/[...path]/route.ts"
+import { auth } from '@/auth';
+import { prisma } from '@/db';
+import { enhance } from '@zenstackhq/runtime';
+import { NextRequestHandler } from '@zenstackhq/server/next';
+
+// create an enhanced Prisma client with user context
+async function getPrisma() {
+  const session = await auth();
+  const user = session?.user?.id ? { id: session.user.id } : undefined;
+  return enhance(prisma, { user });
+}
+
+const handler = NextRequestHandler({ getPrisma, useAppDir: true });
+
+export {
+    handler as DELETE,
+    handler as GET,
+    handler as PATCH,
+    handler as POST,
+    handler as PUT,
+};
+```
+
+You then have a set of CRUD APIs served at "/api/model/[Model Name]/...". The APIs closely resemble PrismaClient's API:
+  - `/api/model/post/findMany`
+  - `/api/model/post/create`
+  - ...
+
+You can find the detailed API specification [here](https://zenstack.dev/docs/reference/server-adapters/api-handlers/rpc).
+
+### Implementing a data provider
+
+We've got the backend APIs ready. Now, the only missing piece is a Refine "Data Provider", which talks to the API to fetch and update data. The following code snippet shows how the `getList` method is implemented. Refine's data provider's data structure is conceptually very close to Prisma, and we only need to do some lightweighted translation:
+
+```ts title="src/providers/data-provider/index.ts"
+export const dataProvider: DataProvider = {
+
+  getList: async function <TData extends BaseRecord = BaseRecord>(
+      params: GetListParams
+  ): Promise<GetListResponse<TData>> {
+    const queryArgs: any = {};
+
+    // filtering
+    if (params.filters && params.filters.length > 0) {
+      const filters = params.filters.map((filter) =>
+          transformFilter(filter)
+      );
+      if (filters.length > 1) {
+          queryArgs.where = { AND: filters };
+      } else {
+          queryArgs.where = filters[0];
+      }
+    }
+
+    // sorting
+    if (params.sorters && params.sorters.length > 0) {
+      queryArgs.orderBy = params.sorters.map((sorter) => ({
+          [sorter.field]: sorter.order,
+      }));
+    }
+
+    // pagination
+    if (
+      params.pagination?.mode === 'server' &&
+      params.pagination.current !== undefined &&
+      params.pagination.pageSize !== undefined
+    ) {
+      queryArgs.take = params.pagination.pageSize;
+      queryArgs.skip =
+          (params.pagination.current - 1) * params.pagination.pageSize;
+    }
+
+    // call the API to fetch data and count
+    const [data, count] = await Promise.all([
+      fetchData(params.resource, '/findMany', queryArgs),
+      fetchData(params.resource, '/count', queryArgs),
+    ]);
+
+    return { data, total: count };
+  },
+
+  ...
+};
+```
+
+With the data provider in place, we now have a fully working CRUD UI.
+
+![CRUD UI](crud-ui.png)
+
+You can sign up for two accounts and verify that the access control rules are working as expected - draft posts are only visible to the author.
+
+### Bonus: guarding UI with permission checker
+
+Let's add one more challenge to the problem: the users of our app will have two roles:
+
+- Reader: can only read published posts
+- Writer: can create new posts
+
+Our schema needs to be updated accordingly:
+
+```zmodel title="schema.zmodel"
+model User {
+  ...
+
+  role          String    @default('Reader')
+}
+
+model Post {
+  ...
+
+  // allow read for all signin users
+  @@allow('read', auth() != null && status == 'published')
+
+  // allow "Writer" users to create
+  @@allow('create', auth().role == 'Writer')
+
+  // full access by author
+  @@allow('read,update,delete', author == auth())
+}
+```
+
+Now, if you try to create a new post with a "Reader" account, you'll see the following error:
+
+![Access denied](access-denied.png)
+
+The operation is denied correctly according to the rules. However, it's not an entirely user-friendly experience. It'd be nice to prevent the "Create" button from appearing in the first place. This can be achieved by combining two additional features from Refine and ZenStack:
+
+- Refine allows you to implement an "Access Control Provider" to verdict whether the current user has permission to perform an action.
+- ZenStack's enhanced PrismaClient has an extra `check` API for inferring permission based on the policy rules. The `check` API is also available in the automatic CRUD API.
+
+:::info
+ZenStack's `check` API doesn't query the database. It's based on logical inference from the policy rules. See more details [here](https://zenstack.dev/docs/guides/check-permission).
+:::
+
+Let's see how these two pieces are put together. First, implement an `AccessControlProvider`:
+
+```ts title="src/providers/access-control-provider/index.ts"
+export const accessControlProvider: AccessControlProvider = {
+  can: async ({ resource, action }: CanParams): Promise<CanReturnType> => {
+    if (action === 'create') {
+      // make a request to "/api/model/:resource/check?q={operation:'create'}"
+      let url = `/api/model/${resource}/check`;
+      url +=
+        '?q=' +
+        encodeURIComponent(
+            JSON.stringify({
+                operation: 'create',
+            })
+        );
+      const resp = await fetch(url);
+      if (!resp.ok) {
+        return { can: false };
+      } else {
+        const { data } = await resp.json();
+        return { can: data };
+      }
+    }
+
+    return { can: true };
+  },
+
+  options: {
+    buttons: {
+        enableAccessControl: true,
+        hideIfUnauthorized: false,
+    },
+    queryOptions: {},
+  },
+};
+```
+
+Then, register the provider to the top-level `Refine` component:
+
+```tsx title="src/app/layout.tsx"
+<Refine 
+  accessControlProvider={ accessControlProvider }
+  ...
+/>
+```
+
+You'll immediately notice the difference that, with a "Reader" user, the "Create" button is grayed out and disabled.
+
+![Create button disabled](create-button-disabled.png)
+
+However, you can still directly navigate to the "/blog-post/create" URL to access the create form. We can prevent that by using Refine's `CanAccess` component to guard it:
+
+```tsx title="src/app/blog-post/create/page.tsx"
+<CanAccess
+    resource="post"
+    action="create"
+    fallback={<div>Not Allowed</div>}
+>
+    <Create ... />
+</CanAccess>
+```
+
+Mission accomplished! We've also done it elegantly without hard coding any permission logic in the UI. Everything about access control is still centralized in the ZModel schema.
+
+## Conclusion
+
+Refine.dev is a great tool for building complex UI without writing complex code. Combined with the superpowers of Prisma and ZenStack, we've now got a full-stack, low-code solution with excellent flexibility.
+
+--- 
+
+The completed sample project is here: [https://github.com/ymc9/refine-nextjs-zenstack](https://github.com/ymc9/refine-nextjs-zenstack).
diff --git a/blog/saas-backend/index.md b/blog/saas-backend/index.md
index 8bf61b59..433d4359 100644
--- a/blog/saas-backend/index.md
+++ b/blog/saas-backend/index.md
@@ -1,7 +1,7 @@
 ---
 title: 'How To Build a Scalable SaaS Backend in 10 Minutes With 100 Lines of Code'
 description: Use schema as the single source of truth for the SaaS backend
-tags: [zenstack, saas, backend, access-control, nodejs, typescript]
+tags: [zenstack, saas, backend, authorization, nodejs]
 authors: jiasheng
 date: 2023-06-21
 image: ./cover.png
@@ -69,14 +69,14 @@ Let’s take a look at all the permissions of the Post and how they could be exp
     
 the owner must be set to the current user, and the organization must be set to one that the current user belongs to.
 ```tsx
-@@allow('create', owner == auth() && org.members?[this == auth()])
+@@allow('create', owner == auth() && org.members?[id == auth().id])
 ```
 -   Update
 
     only the owner can update it and is not allowed to change the organization or owner
 
     ```tsx
-    @@allow('update', owner == auth() && org.future().members?[this == auth()] && future().owner == owner)
+    @@allow('update', owner == auth() && org.future().members?[id == auth().id] && future().owner == owner)
     ```
 -   Read
 
@@ -86,7 +86,7 @@ the owner must be set to the current user, and the organization must be set to o
         ```
     -   allow the member of the organization to read it if it’s public
         ```tsx
-        @@allow('read', isPublic && org.members?[this == auth()])
+        @@allow('read', isPublic && org.members?[id == auth().id])
         ```
     -   allow the group members to read it
         ```tsx
@@ -117,15 +117,15 @@ abstract model organizationBaseEntity {
     groups Group[]
 
     // when create, owner must be set to current user, and user must be in the organization
-    @@allow('create', owner == auth() && org.members?[this == auth()])
+    @@allow('create', owner == auth() && org.members?[id == auth().id])
     // only the owner can update it and is not allowed to change the owner
-    @@allow('update', owner == auth() && org.members?[this == auth()] && future().owner == owner)
+    @@allow('update', owner == auth() && org.members?[id == auth().id] && future().owner == owner)
     // allow owner to read
     @@allow('read', owner == auth())
     // allow shared group members to read it
-    @@allow('read', groups?[users?[this == auth()]])
+    @@allow('read', groups?[users?[id == auth().id]])
     // allow organization to access if public
-    @@allow('read', isPublic && org.members?[this == auth()])
+    @@allow('read', isPublic && org.members?[id == auth().id])
     // can not be read if deleted
     @@deny('all', isDeleted == true)
 }
@@ -152,7 +152,7 @@ model ToDo extends organizationBaseEntity {
 All the multi-tenant, soft delete and sharing features will just work automatically. Additionally, if any specialized access control logic is required for **`ToDo`**, such as allowing shared individuals to update it, you can effortlessly add the corresponding policy rule within the **`ToDo`** model without concerns about breaking existing functionality:
 
 ```tsx
-@@allow('update', groups?[users?[this== auth()]] )
+@@allow('update', groups?[users?[id == auth().id]] )
 ```
 
 ## How much Typescript/JavaScript code do I need to write
@@ -206,8 +206,8 @@ localhost:3000/api/post/slack
 
 After that, if you try to access the Post endpoint again, the result won’t contain the “Join Slack” post anymore. If you are interested in how it works under the hook, check out another post for it:
 
-[Soft Delete: Implementation Issues in Prisma and Solution in Zenstack](https://zenstack.dev/blog/soft-delete)
+[Soft Delete: Implementation Issues in Prisma and Solution in ZenStack](https://zenstack.dev/blog/soft-delete)
 
 ## Last
 
-The SaaS product [MermaidChart](https://www.mermaidchart.com/) has recently launched its Teams feature powered by ZenStack. If you want to know about their experience of adopting ZenStack, you are welcome to join our [Discord](https://go.zenstack.dev/chat). For other information and examples, check out our [official website](https://zenstack.dev/).
+The SaaS product [MermaidChart](https://www.mermaidchart.com/) has recently launched its Teams feature powered by ZenStack. If you want to know about their experience of adopting ZenStack, you are welcome to join our [Discord](https://discord.gg/Ykhr738dUe). For other information and examples, check out our [official website](https://zenstack.dev/).
diff --git a/blog/saas-demo.mdx b/blog/saas-demo.mdx
index 366be841..478da495 100644
--- a/blog/saas-demo.mdx
+++ b/blog/saas-demo.mdx
@@ -926,7 +926,7 @@ Now Bob could access ZenStack’s team space home page by accessing its URL [htt
 
 I hope now it already can show you the benefit of [ZenStack](https://zenstack.dev/) in using the data model as the single source of truth to handle access control.
 
-Feel free to contact us on our [Discord](https://go.zenstack.dev/chat) or [GitHub](https://github.com/zenstackhq/zenstack) if you have any questions about it.
+Feel free to contact us on our [Discord](https://discord.gg/Ykhr738dUe) or [GitHub](https://github.com/zenstackhq/zenstack) if you have any questions about it.
 
 ## Next
 
diff --git a/blog/secure-api/index.md b/blog/secure-api/index.md
index 6d249266..77b3f5bd 100644
--- a/blog/secure-api/index.md
+++ b/blog/secure-api/index.md
@@ -149,7 +149,7 @@ When the application code uses the ORM to talk to the database, proper filters a
 -   When you do `db.chat.findMany()`, only chats related to the current user are returned.
 -   When you do `db.chat.create({ fromUserId: 1, toUserId: 2, subject: 'hello' })`, the ORM will reject your request if the current user does not have ID 1.
 
-Check for more information about ZenStack [here](https://zenstack.dev/docs/intro).
+Check for more information about ZenStack [here](https://zenstack.dev/docs).
 
 ### Pros
 
diff --git a/blog/soft-delete.mdx b/blog/soft-delete.mdx
index 1dd943a6..8f03b88e 100644
--- a/blog/soft-delete.mdx
+++ b/blog/soft-delete.mdx
@@ -6,7 +6,7 @@ authors: jiasheng
 date: 2023-02-18
 ---
 
-# Soft Delete: Implementation Issues in Prisma and Solution in Zenstack
+# Soft Delete: Implementation Issues in Prisma and Solution in ZenStack
 
 ![Cover Image](https://user-images.githubusercontent.com/16688722/219857601-3bb070ab-2117-4334-896b-98039455b2bb.jpg)
 
@@ -234,7 +234,7 @@ Then when using ZenStack to do a query, it will automatically exclude soft-delet
 
 To be honest, we were not aware that ZenStack had problem of supporting it either until a customer reported an issue on our Discord platform. Upon fixing the issue, we gained insight into its complexity and why Prisma had not supported it, given the existing code and usage. Fortunately, as ZenStack has less legacy, we were able to resolve the issue quickly.
 
-Conversely, we had fewer real-world use cases. So if you guys meet any case that we missed, we would appreciate it if you could create an issue in [Github](https://github.com/zenstackhq/zenstack/issues) or directly throw it to our [Discord](https://go.zenstack.dev/chat). Let’s make ZenStack better together!
+Conversely, we had fewer real-world use cases. So if you guys meet any case that we missed, we would appreciate it if you could create an issue in [Github](https://github.com/zenstackhq/zenstack/issues) or directly throw it to our [Discord](https://discord.gg/Ykhr738dUe). Let’s make ZenStack better together!
 
 ---
 
diff --git a/blog/stackauth-multitenancy/cover.png b/blog/stackauth-multitenancy/cover.png
new file mode 100644
index 00000000..80fd8315
Binary files /dev/null and b/blog/stackauth-multitenancy/cover.png differ
diff --git a/blog/stackauth-multitenancy/index.mdx b/blog/stackauth-multitenancy/index.mdx
new file mode 100644
index 00000000..e89dfb53
--- /dev/null
+++ b/blog/stackauth-multitenancy/index.mdx
@@ -0,0 +1,312 @@
+---
+title: "Building Multi-Tenant Apps Using StackAuth's \"Teams\" and Next.js"
+description: StackAuth's "Teams" feature provides a powerful pre-built tenant management experience. Let's see how we can easily create a full-fledged multi-tenant application with it.
+tags: [auth, stack-auth, multi-tenancy]
+authors: yiming
+date: 2024-12-07
+image: ./cover.png
+---
+
+# Building Multi-Tenant Apps Using StackAuth's \"Teams\" and Next.js
+
+![Cover Image](cover.png)
+
+Building a full-fledged multi-tenant application can be very challenging. Besides having a flexible sign-up and sign-in system, you also need to implement several other essential pieces:
+
+- Creating and managing tenants
+- User invitation flow
+- Managing roles and permissions
+- Enforcing data segregation and access control throughout the entire application
+
+It sounds like lots of work, and it indeed is. You may have done this multiple times if you're a veteran SaaS developer.
+
+<!--truncate-->
+
+[StackAuth](https://stack-auth.com) is an open-source authentication and user management platform designed to integrate seamlessly into Next.js projects. Its combination of frontend/backend APIs and pre-built UI components dramatically simplifies the integration of such capabilities into your application. Similarly, its newer "Teams" feature provides an excellent starting point for creating multi-tenant applications. In this post, we'll explore leveraging it to build a non-trivial one while trying to keep our code simple and clean.
+
+## The goal and the stack
+
+The target application we'll build is a Todo List. Its core functionalities are simple: creating lists and managing todos within them. However, the focus will be on the multi-tenancy and access control aspects:
+
+- **Team management**
+  
+  Users can create teams and invite others to join. They can manage members and set their roles.
+
+- **Current context**
+  
+  Users can choose a team to be the current context.
+
+- **Data segregation**
+  
+  Only data within the current team can be accessed.
+
+- **Role-based access control**
+  
+  - Admin members have full access to all data within their team.
+  - Regular members have full access to the todo lists they own.
+  - Regular members can view the other members' todo lists and manage their content, as long as the list is not private.
+
+The essential weapons we'll use to build the app are:
+
+- [Next.js](https://nextjs.org/): the full-stack framework
+- [StackAuth](https://stack-auth.com/): user authentication and team management
+- [Prisma](https://prisma.io): the ORM that we use to talk to the database
+- [ZenStack](https://zenstack.dev): the authorization layer above Prisma that handles data segregation and access control
+
+You can find the link of the completed project at the end of the post.
+
+## Adding team management
+
+I assume you've created a Next.js project and completed the steps as described StackAuth's [setup guide](https://docs.stack-auth.com/getting-started/setup). Verify the basic sign-up/sign-in flow is working. Also, in StackAuth's management console, enable "Client-Side Team Creation" and "Automatic Team Creation" options in the "Team Settings" section.
+
+![Team Settings](./team-settings.png)
+
+Now, we can add the "SelectedTeamSwitcher" component into the layout.
+
+```tsx title="src/app/layout.tsx"
+// highlight-next-line
+import { SelectedTeamSwitcher } from "@stackframe/stack";
+...
+
+export default function RootLayout({ children }: { children: React.ReactNode }) {
+  return (
+    <html lang="en">
+      <body>
+        <StackProvider app={stackServerApp}>
+          <StackTheme>
+            <header>
+              // highlight-next-line
+              <SelectedTeamSwitcher />
+            </header>
+            <main>{children}</main>
+          </StackTheme>
+        </StackProvider>
+      </body>
+    </html>
+  );
+}
+```
+
+With this one-liner, you'll have a set of fully working UI components for managing teams and choosing an active one!
+
+![Team Management](./team-management.png)
+
+Although StackAuth made it effortless to add "teams" feature into an app, it's up to you to determine how to use the user and team information to control data access. We'll see how to connect it with Prisma/ZenStack to achieve proper authorization.
+
+## Setting up the database
+
+Our user and team data are stored on StackAuth's side. We need to store the todo lists and items in our own database. In this section, we'll set up Prisma and ZenStack and create the database schema.
+
+Let's start with installing the necessary packages:
+
+```bash
+npm install --save-dev prisma zenstack
+npm install @prisma/client @zenstackhq/runtime
+```
+
+Then we can create the database schema. Please note that we're creating a **schema.zmodel** file (as a replacement of "schema.prisma"). The [ZModel language](/docs/the-complete-guide/part1/zmodel) is a superset of Prisma schema language, allowing you to model both the data schema and access control policies. In this section, we'll only focus on the data modeling part.
+
+```zmodel title="/schema.zmodel"
+datasource db {
+  provider = "postgresql"
+  url      = env("DATABASE_URL")
+}
+
+generator js {
+  provider = "prisma-client-js"
+}
+
+// Todo list
+model List {
+  id        String        @id @default(cuid())
+  createdAt DateTime      @default(now())
+  title     String
+  private   Boolean       @default(false)
+  orgId     String?
+  ownerId   String
+  todos     Todo[]
+}
+
+// Todo item
+model Todo {
+  id          String    @id @default(cuid())
+  title       String
+  completedAt DateTime?
+  list        List      @relation(fields: [listId], references: [id], onDelete: Cascade)
+  listId      String
+}
+```
+
+You can then generate a regular Prisma schema file and push the schema to the database:
+
+```bash
+# The `zenstack generate` command generates the "prisma/schema.prisma" file and runs "prisma generate"
+npx zenstack generate
+npx prisma db push
+```
+
+Finally, create a "src/server/db.ts" file to export the Prisma client:
+
+```ts title="src/server/db.ts"
+import { PrismaClient } from "@prisma/client";
+export const prisma = new PrismaClient();
+```
+
+## Implementing access control
+
+As mentioned, ZenStack allows you to model both data and access control in a single schema. Let's see how we can entirely implement our authorization requirements with it. The rules are defined with the `@@allow` and `@@deny` attributes. Access is rejected by default unless explicitly granted with an `@@allow` rule.
+
+Although authorization is a distinct concept from authentication, it usually depends on authentication to work. For example, to determine if the current user has access to a list, a verdict must be made based on the user's id, current team, and role in the team. To access such information, let's first declare a type to express it:
+
+```zmodel title="/schema.zmodel"
+// The shape of `auth()`
+type Auth {
+  // Current user's ID
+  userId         String  @id
+
+  // User's current team ID
+  currentTeamId   String?
+
+  // User's role in the current team
+  currentTeamRole String?
+
+  @@auth
+}
+```
+
+Then you can use the special `auth()` function in access policy rules to access the current user's information. Let's use the `List` model as an example to demonstrate how the rules are defined.
+
+```zmodel title="/schema.zmodel"
+model List {
+  ...
+
+  // deny anonymous access
+  @@deny('all', auth() == null)
+
+  // tenant segregation: deny access if the user's current org doesn't match
+  @@deny('all', auth().currentOrgId != orgId)
+
+  // owner/admin has full access
+  @@allow('all', auth().userId == ownerId || auth().currentOrgRole == 'org:admin')
+
+  // can be read by org members if not private
+  @@allow('read', !private)
+
+  // when create, owner must be set to current user
+  @@allow('create', ownerId == auth().userId)
+}
+```
+
+The last piece of the puzzle is, as you may already be wondering, where the value of `auth()` comes from? At runtime, ZenStack offers an `enhance()` API to create an enhanced `PrismaClient` (a lightweighted wrapper) that automatically enforces the access policies. You pass in a user context (usually fetched from the authentication provider) when calling `enhance()`, and that context provides the value for `auth()`.
+
+We'll see how it works in detail in the next section.
+
+## Finally, the UI
+
+Before diving into creating the UI, let's first make a helper to get an enhanced `PrismaClient` for the current user, team, and role.
+  
+```ts title="src/server/db.ts"
+import { enhance } from "@zenstackhq/runtime";
+import { stackServerApp } from "~/stack";
+
+export async function getUserDb() {
+  const stackAuthUser = await stackServerApp.getUser();
+  const currentTeam = stackAuthUser?.selectedTeam;
+
+  // by default StackAuth's team members have "admin" or "member" role
+  const perm =
+    currentTeam && (await stackAuthUser.getPermission(currentTeam, "admin"));
+
+  const user = stackAuthUser
+    ? {
+        userId: stackAuthUser.id,
+        currentTeamId: stackAuthUser.selectedTeam?.id,
+        currentTeamRole: perm ? "admin" : "member",
+      }
+    : undefined; // anonymous
+  return enhance(prisma, { user });
+}
+```
+
+Let's build the UI using [React Server Components](https://nextjs.org/docs/app/building-your-application/rendering/server-components) (RSC) and [Server Actions](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations). We'll also consistently use the `getUserDb()` helper to access the database with access control enforcement.
+
+Here's the RSC that renders the todo lists for the current user (with styling omitted):
+
+```tsx title="src/components/TodoList.tsx"
+// Component showing Todo list for the current user
+
+export default async function TodoLists() {
+  const db = await getUserDb();
+
+  // enhanced PrismaClient automatically filters out
+  // the lists that the user doesn't have access to
+  const lists = await db.list.findMany({
+    orderBy: { updatedAt: "desc" },
+  });
+
+  return (
+    <div>
+      <div>
+        {/* client component for creating a new List */}
+        <CreateList />
+
+        <ul>
+          {lists?.map((list) => (
+            <Link href={`/lists/${list.id}`} key={list.id}>
+              <li>{list.title}</li>
+            </Link>
+          ))}
+        </ul>
+      </div>
+    </div>
+  );
+}
+```
+
+A client component that creates a new list by calling into a server action:
+
+```tsx title="src/components/CreateList.tsx"
+"use client";
+
+import { createList } from "~/app/actions";
+
+export default function CreateList() {
+  function onCreate() {
+    const title = prompt("Enter a title for your list");
+    if (title) {
+      createList(title);
+    }
+  }
+
+  return (
+    <button onClick={onCreate}>
+      Create a list
+    </button>
+  );
+}
+```
+
+```ts title="src/app/actions.ts"
+'use server';
+
+import { revalidatePath } from "next/cache";
+import { getUserDb } from "~/server/db";
+
+export async function createList(title: string) {
+  const db = await getUserDb();
+  await db.list.create({ data: { title } });
+  revalidatePath("/");
+}
+```
+
+<div align="center">
+    <img src={require('./list-ui.gif').default} width="640" />
+</div>
+
+The components that manage Todo items are not shown for brevity, but the ideas are similar. You can find the fully completed code [here](https://github.com/ymc9/stackauth-zenstack-multitenancy).
+
+## Conclusion
+
+Authentication and authorization are two cornerstones of most applications. They can be especially challenging to build for multi-tenant ones. This post demonstrated how the work can be significantly simplified and streamlined by combining StackAuth's "Teams" feature and ZenStack's access control capabilities. The end result is a secure application with great flexibility and little boilerplate code.
+
+StackAuth also supports defining [custom permissions](https://docs.stack-auth.com/concepts/permissions) for teams. Although not covered in this post, with some tweaking, you should be able to leverage it to define access policies. That way, you can manage permissions with StackAuth's dashboard and have ZenStack enforce them at runtime.
diff --git a/blog/stackauth-multitenancy/list-ui.gif b/blog/stackauth-multitenancy/list-ui.gif
new file mode 100644
index 00000000..6e45cd49
Binary files /dev/null and b/blog/stackauth-multitenancy/list-ui.gif differ
diff --git a/blog/stackauth-multitenancy/team-management.png b/blog/stackauth-multitenancy/team-management.png
new file mode 100644
index 00000000..a43085bb
Binary files /dev/null and b/blog/stackauth-multitenancy/team-management.png differ
diff --git a/blog/stackauth-multitenancy/team-settings.png b/blog/stackauth-multitenancy/team-settings.png
new file mode 100644
index 00000000..75eb58d6
Binary files /dev/null and b/blog/stackauth-multitenancy/team-settings.png differ
diff --git a/blog/start-with-why/index.md b/blog/start-with-why/index.md
index 0aa6578d..bdb439fb 100644
--- a/blog/start-with-why/index.md
+++ b/blog/start-with-why/index.md
@@ -60,7 +60,7 @@ We are grateful for the opportunities we had with Microsoft and Ptmind, where we
 We hope with the help of our tools:
 
 -   Experienced professionals will continue enjoying their knowledge and skills but will be able to move a lot faster as solo entrepreneurs or with a small team.
--   For people new to software development, it \*\*serves as an easy ladder that reduces frustration, keeps things safe, and helps people stay oriented in the vast amount of knowledge and choices in the software industry.
+-   For people new to software development, it serves as an easy ladder that reduces frustration, keeps things safe, and helps people stay oriented in the vast amount of knowledge and choices in the software industry.
 
 ## What
 
diff --git a/blog/supabase-alternative/cover.png b/blog/supabase-alternative/cover.png
new file mode 100644
index 00000000..841adc74
Binary files /dev/null and b/blog/supabase-alternative/cover.png differ
diff --git a/blog/supabase-alternative/index.md b/blog/supabase-alternative/index.md
new file mode 100644
index 00000000..38359c8e
--- /dev/null
+++ b/blog/supabase-alternative/index.md
@@ -0,0 +1,455 @@
+---
+title: Supabase RLS Alternative
+description: Show the limitation of Supabase RLS(Row Level Security) with a multi-tenancy SaaS example and introduce ZenStack as an alternative.
+tags: [supabase, rls, auth, authorization, baas, database, firebase, zenstack]
+authors: jiasheng
+date: 2024-07-24
+image: ./cover.png
+---
+
+# Supabase RLS Alternative
+
+![Cover Image](cover.png)
+
+## A Short History of BaaS
+
+In the early days of web and mobile app development, building a backend from scratch was laborious and error-prone. Developers had to manage servers, databases, and infrastructure and ensure scalability while writing the core business logic of their applications.  Then came BaaS(Backend-as-a-Service), promising to liberate developers from this burden.
+
+<!--truncate-->
+### Firebase: The Pioneer
+
+Firebase was one of the first BaaS platforms to gain widespread adoption. It was acquired by Google, and at Google I/O in **2016**, it announced an expansion of its services to become a unified BaaS platform for mobile developers. Firebase quickly became popular among developers for its ease of use and integration with Google's ecosystem.  
+
+However, as projects grew in complexity,  so did concerns about vendor lock-in and data control.  Its rigid data models and scalability issues led developers to seek more flexible and robust alternatives.
+
+### Supabase: The Open-Source Contender
+
+In response to these limitations, Supabase was founded in **2020**, positioning itself as the “open-source Firebase alternative.” Built on top of PostgreSQL, it offered a more flexible and powerful database solution while remaining open-source. Unsurprisingly, it soon became the new spokesperson for BaaS.
+
+## ACL(Access Control Layer) is the Core
+
+While BaaS promises a simple abstraction of connecting the frontend to the database,  it actually rejects another old-school promise:  
+
+                             **You never expose the database directly to the frontend**
+
+The hero behind it is the ACL, or more specifically, authorization. It is the gatekeeper that stands between your database and potentially malicious actors. It ensures that users can only read or write data they are authorized to access. 
+
+### Firebase: Security Rules
+
+Firebase introduces a simple DSL to enforce access control.
+
+```tsx
+service cloud.firestore {
+  match /databases/{database}/documents {
+    match /posts/{postId} {
+      allow read: if true;
+      allow write: if request.auth != null && request.auth.uid == resource.data.authorId;
+    }
+  }
+}
+```
+
+### Supabase: RLS(Row Level Security)
+
+Since Supabase is built upon Postgres,  it could leverage PostgreSQL's robust RLS to handle access control.  
+
+```sql
+-- owner has full access to her own posts
+CREATE POLICY post_owner_policy ON post
+    USING (owner = current_user);
+```
+
+Since RLS is written in SQL, it allows more fine-grained access control. Developers can define policies that determine which rows of data a user can access based on their role or other attributes.  Theoretically, it could express any authorization pattern you use, like RBAC, ABAC, PBAC, etc. 
+
+## Multi-Tenancy SaaS Example
+
+Multi-tenancy is the classical pattern used in SaaS applications. An application can host many organizations, and users can join organizations and access resources based on their permissions. Let’s use a ToDo app to illustrate.
+
+### Database Model
+
+```mermaid
+erDiagram
+    Space {
+        String id
+        String name
+    }
+    SpaceUser {
+        String id
+        String spaceId
+        String userId
+    }
+    User {
+        String id
+        String email
+    }
+    List {
+        String id
+        String spaceId
+        String ownerId
+        String title
+        Boolean private
+    }
+    Todo {
+        String id
+        String ownerId
+        String listId
+        String title
+    
+    }
+		Space ||--o{ SpaceUser: has
+    SpaceUser }o--|| User : has
+    User ||--o{ List : owns
+    User ||--o{ Todo : owns
+    List }o--|| Space : belongs_to
+    List ||--o{ Todo : contains
+```
+
+- The `User` and `Space` are many-to-many relations through the relation table `SpaceUser`.
+- A `Todo` belongs to a `User`, and a `List`
+- A `List` belongs to the `User`, and a `Space`
+
+### RLS rules for List
+
+Let’s go through the access control requirements for `List` model and the corresponding RLS rules. 
+
+- Create
+    - owner must be set to the current user.
+    - user must be in the space.
+    
+    ```sql
+    create policy "list_create"
+    on "public"."List"
+    to public
+    with check (
+      ((auth.uid() = ("ownerId")::uuid) AND (EXISTS ( SELECT 1
+       FROM "SpaceUser"
+      WHERE (("SpaceUser"."spaceId" = "List"."spaceId") AND (("SpaceUser"."userId")::uuid = auth.uid())))))
+    );
+    ```
+    
+- Read
+    - can be read by the owner.
+    - can be read by space members if not private.
+    
+    ```sql
+    create policy "list_read"
+    on "public"."List"
+    to authenticated
+    using (
+       ((auth.uid() = ("ownerId")::uuid) OR ((NOT private) AND (EXISTS (SELECT 1
+       FROM "SpaceUser"
+      WHERE (("SpaceUser"."spaceId" = "List"."spaceId") AND (("SpaceUser"."userId")::uuid = auth.uid()))))))
+    );
+    ```
+    
+- Update
+    - only the owner is allowed to update
+    - owner must be in the space of the current list
+    - it doesn’t allow to change owner
+    
+    ```sql
+    create policy "list_update"
+    on "public"."List"
+    to authenticated
+    using (
+      ((auth.uid() = ("ownerId")::uuid) AND (EXISTS ( SELECT 1
+       FROM "SpaceUser"
+      WHERE (("SpaceUser"."spaceId" = "List"."spaceId") AND (("SpaceUser"."userId")::uuid = auth.uid())))))
+    with check (
+      (auth.uid() = ("ownerId")::uuid)
+    );
+    ```
+    
+- Delete
+    - can be deleted by owner
+    
+    ```sql
+    create policy "list.delete"
+    on "public"."List"
+    to authenticated
+    using (
+      (auth.uid() = ("ownerId")::uuid)
+    );
+    ```
+    
+
+Supabase provides a RESTful API using [PostgREST](https://postgrest.org/). However, without RLS, you will expose your database to the frontend. With the RLS policies created above, it’s safe to expose the API to the public because each user can only access the data allowed by the policy. For example, if you try to get all the `List` items using the API below, you will only receive the ones you are allowed to read by the read policy:
+
+```tsx
+curl '{SUPABASE_PROJECT_URL}/rest/v1/List?select=*' \
+-H "apikey: SUPABASE_ANON_KEY" \
+-H "Authorization: Bearer USER_JWT_TOKEN"
+```
+
+Everything looks perfect, especially if you are familiar with SQL. So why do I need alternatives?
+
+![perfect-meme](https://github.com/user-attachments/assets/08ac420c-4f2b-4147-b4b5-a6c766ea61a3)
+## The Problems of Supabase RLS
+
+### 1. Separation from Application Logic
+
+As a modern developer, you know the sense of control when you can launch with a one-click. The prerequisite is to keep everything within the codebase. It's not just about convenience; it's about maintaining a single source of truth, ensuring consistency, and streamlining your workflow. 
+
+However, for RLS, you have to define authorization directly in the database, not in your source code. Of course, you could store it as an SQL file in your codebase, but you need to rely on SQL migration to ensure consistency. I think it’s the same reason why you seldom see people using stored procedures of databases nowadays despite all the benefits they offer.
+
+What makes the consistency even worse is that you have to duplicate the policy filters in the application code. For example, if you are using the Supabase JS SDK, you have to use the two queries below to get the result:
+```tsx
+// First, get the user's spaceIds
+const { data: userSpaces } = await supabase.from('SpaceUser').select('spaceId').eq('userId', userId);
+
+// Extract spaceIds from the result
+const userSpaceIds = userSpaces.map((space) => space.spaceId);
+
+// Now, query the List table
+const { data, error } = await supabase
+    .from('List')
+    .select('*')
+```
+Why? Because otherwise, you might experience 20x slower query performance, according to the official benchmark of Supabase. 😲
+
+[Add filters to every query | Supabase Docs](https://supabase.com/docs/guides/database/postgres/row-level-security#add-filters-to-every-query)
+
+### 2. Poor DX
+
+If you are an SQL expert, you can ignore this one.  
+
+Writing SQL itself is not considered a good developer experience (DX) for many developers, with the majority adopting ORM. Moreover, you have to write it in a UI box without the help of Intellisense and often get an obscure error after clicking the save button:
+
+![supabase-error](https://github.com/user-attachments/assets/60896619-cc51-41d8-8b35-57690dd267e4)
+
+The more challenging part is testing and debugging. Honestly, I have no idea about the best practice for it; if you have any tips, please share them in the comments.
+
+### 3. Scalability
+
+You might feel the aforementioned RLS for `List` is clear and straightforward, but that’s only for one table. If the access control policy for `Todo` is the same as `List`, which is a very common case, what policy do you need to create for `Todo`?  The answer is that you have to duplicate all the policies of the `list` for `Todo`. That’s definitely not DRY(Don’t repeat yourself). 
+
+If you don't think it's a big deal, imagine you're lucky enough to grow your Todo SaaS into a team collaboration platform that manages various entities like dashboards, tasks, bugs, projects, etc.
+
+### 4. Maintainability
+
+Let’s say we get a feature request that if a team member can see a Todo list, he will have full access to all the Todos under it, even the ones not owned by him.  
+
+Do you know what change you need to make? You need to delete all the policies copied from the `List` mentioned above and then create a new policy below:
+
+```sql
+create policy "Todo"
+on "public"."Todo"
+to authenticated
+using (
+  ((EXISTS ( SELECT 1
+   FROM "List"
+  WHERE (("List".id = "Todo"."listId") AND (("List"."ownerId")::uuid = auth.uid())))) OR (EXISTS ( SELECT 1
+   FROM (("List"
+     JOIN "Space" ON (("List"."spaceId" = "Space".id)))
+     JOIN "SpaceUser" ON (("SpaceUser"."spaceId" = "Space".id)))
+  WHERE (("List".id = "Todo"."listId") AND (("SpaceUser"."userId")::uuid = auth.uid()) AND (NOT "List".private)))))
+)
+```
+
+Do you think you can write this kind of policy all by yourselves? I actually asked ChatGPT to write it myself. Even if it's not a problem for you, think about how another team member might feel when he sees it for the first time. 
+
+And don't forget to duplicate this change in your application code for performance's sake. 😂
+
+## Alternative Solution
+
+Remember in 2016 when Google I/O Firebase announced its expansion to BaaS? In the same year, another product with an interesting name joined this journey: Graphcool.
+
+Maybe you haven't heard about it because it was sunsetted in 2020, the same year Supabase came out (what a coincidence!).   Why was it sunsetted?  See the detailed explanation on its home page:  
+
+[https://www.graph.cool/](https://www.graph.cool/)
+
+TLDR: The team felt that BaaS was too limited for developers to build the next generation of web applications, so they pivoted it to the [Prisma ORM](https://www.prisma.io/orm).
+
+It simplifies database interactions by providing a type-safe query builder, seamless migrations, and an intuitive data modeling language.  While Prisma ORM does provide more flexibility compared to BaaS, it intentionally misses the access control layer as an ORM. Consequently, you have to switch back to implementing Authorization logic at the application level.
+
+Is it possible to bring back the convenience of not writing code for the Authorization like BaaS while maintaining the flexibility of a custom backend?
+
+That's why we built [ZenStack](https://zenstack.dev/) on top of Prisma ORM, adding the missing authorization layer and auto-generating type-safe APIs/hooks. It gives you the same convenience as using BaaS while maintaining flexibility with everything in your codebase.  
+
+Let’s cut the crap and see the code directly.  Below are the equivalent ZenStack schema definitions of `List` and `Todo` you need to write for the ToDo apps. 
+
+```tsx
+abstract model BaseEntity {
+    id        String   @id @default(uuid())
+    space     Space    @relation(fields: [spaceId], references: [id], onDelete: Cascade)
+    spaceId   String
+    owner     User     @relation(fields: [ownerId], references: [id], onDelete: Cascade)
+    ownerId   String   @default(auth().id)
+
+    // can be read by owner or space members 
+    @@allow('read', owner == auth() || (space.members?[user == auth()]))
+
+    // when create, owner must be set to current user, and user must be in the space
+    @@allow('create', owner == auth() && space.members?[user == auth()])
+
+    // when create, owner must be set to current user, and user must be in the space
+    // update is not allowed to change owner
+    @@allow('update', owner == auth() && space.members?[user == auth()] && future().owner == owner)
+
+    // can be deleted by owner
+    @@allow('delete', owner == auth())
+}
+
+/**
+ * Model for a Todo list
+ */
+model List extends BaseEntity {
+    title   String  @length(1, 100)
+    private Boolean @default(false)
+    todos   Todo[]
+
+    // can't be read by others if it's private
+    @@deny('read', private == true && owner != auth())
+}
+
+/**
+ * Model for a single Todo
+ */
+model Todo {
+    id          String    @id @default(uuid())
+    owner       User      @relation(fields: [ownerId], references: [id], onDelete: Cascade)
+    ownerId     String    @default(auth().id)
+    list        List      @relation(fields: [listId], references: [id], onDelete: Cascade)
+    listId      String
+    title       String    @length(1, 100)
+    completedAt DateTime?
+
+    // same as its parent list
+    @@allow('all', check(list))
+}
+```
+
+Keep in mind that with this schema done, although you still need a server to deploy, you hardly need to write any backend code.  ZenStack introspects the schema and installs CRUD APIs to the framework of your choice.  Thanks to the access policy defined in the model, the APIs are fully secure and can be directly exposed to the public.  You can also generate the OpenAPI(swagger) specification. 
+
+In fact, you don’t even need to be aware of the API, as ZenStack generates fully typed client hooks that call into the generated APIs.  You can directly use those hooks with intrinsic automatic invalidation and optimistic update support.
+
+![fontend-query](https://github.com/zenstackhq/zenstack/assets/16688722/93eaf485-33d1-4512-9c12-58197dedd691)
+
+What about the problems of RLS?  Let’s go through them one by one.
+
+### 1. Single Source of Truth
+
+The access policies are now defined alongside the database models.  The schema becomes the single source of truth of your backend, enabling an easier understanding of the system as a whole.
+
+Moreover, whether calling from the frontend or backend, you don’t need to use any filter regarding the authorization rules, which will be injected into queries automatically by the ZenStack runtime. The application code you need to write is clean and clear:
+
+```tsx
+   // frontend query:
+   // ZenStack generated hooks only returns the data user is allowed to read
+   const { data: lists } = useFindManyList();    
+   ...
+   
+   // server props
+   export const getServerSideProps: GetServerSideProps<Props> = async ({ req, res, params }) => {
+    const db = await getEnhancedPrisma({ req, res });
+    // ZenStack enhanced Prisma client only returns the data user is allowed to read
+    const lists = await db.list.findMany();
+    return {
+        props: { lists },
+    };
+};
+```
+
+> Remember the complex query you need to write for the RLS case mentioned above?
+
+### 2. Good DX
+
+ZenStack comes with a [VSCode extension](https://marketplace.visualstudio.com/items?itemName=zenstack.zenstack). All the basic features of IntelliSense, like autocomplete,  Inline error reporting, Go-to definition, finding the reference, and auto-format, 
+
+work as usual. 
+
+![zmodel-autocomplete](https://github.com/user-attachments/assets/316ccc5c-ea79-4614-a13b-3654554e560d)
+If you have GitHub Copilot, there is a big chance that you don’t have to write the policy yourself:
+
+![zmodel-copilot](https://github.com/user-attachments/assets/b9f100fd-adec-403a-82ef-658aca5b351d)
+
+For testing and debugging, you can enable ZenStack's debug logging by setting a simple flag. Then, you can see all the Prisma queries ZenStack actually calls to the database from the console log:
+
+```tsx
+prisma:info [policy] `findMany` list:
+{
+  where: {
+    AND: [
+      { NOT: { OR: [] } },
+      {
+        OR: [
+          { owner: { is: { id: 1 } } },
+          {
+            AND: [
+              {
+                space: {
+                  members: {
+                    some: { user: { is: { id: 1 } } }
+                  }
+                }
+              },
+              { NOT: { private: true } }
+            ]
+          }
+        ]
+      }
+    ]
+  }
+}
+```
+
+> Since all the code is actually running on your server, you can set breakpoints in the generated code to debug it step by step.
+> 
+
+ZenStack also provides a REPL CLI for interactive query execution. You can quickly switch between different user contexts and see how the access policies affect the result.
+
+<iframe width="560" height="315" src="https://www.youtube.com/embed/yJr8zZVj-JA?si=MNeraOCjo__hgFKK" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
+
+### 3. Scalability
+
+Have you noticed that for `Todo` there is only one line of the policy rule?
+
+```tsx
+    // same as its parent list
+    @@allow('all', check(list))
+```
+
+There is no need to duplicate the policies of `List` as needed for RLS.  
+
+Furthermore, if you need to add a new top-level entity like `Bug`, all you need is to add the below model in the schema:
+
+```tsx
+model Bug extends BaseEntity {
+    title    String @length(1, 100)
+    priority Int
+}
+```
+
+What about the policy rules for it? Thanks to model inheritance, it could inherit all the policies from the abstract base model `BaseEntity`. 
+
+As you can see, ZenStack has provided several features to keep schemas DRY and achieve better scalability. 
+
+### 4. Maintainability
+
+Implementing the requirement change request mentioned above is just adding one parameter 
+
+```tsx
+// full access if the parent list is readable
+// @@allow('all', check(list))
+   @@allow('all', check(list, 'read'))
+```
+
+Nothing explains better than code here.
+
+Here is the complete runnable project for this SaaS ToDo app: 
+
+[https://github.com/zenstackhq/sample-todo-nextjs](https://github.com/zenstackhq/sample-todo-nextjs)
+
+## The Art of Trade-off
+
+As developers, we know better than others that every sword has two blades.  For all the problems I mentioned with RLS above, the opposite side also shows its advantage.  For example:
+
+- Although the policy is separate from the application, you don’t have to redeploy the application to let the new policy take effect.
+- While RLS's SQL policy may seem less intuitive than ZenStack’s, it is more flexible and has a better ecosystem.
+
+Software engineering is an art of trade-offs. It involves balancing time and space, stability and flexibility, performance and code complexity, and more. What ZenStack provides is just another alternative; it's up to you to balance these trade-offs and make it an art.
+
+Someone has completed the artwork in production 😉
+
+> We've launched MermaidChart's team feature using ZenStack. Much cleaner and easier to maintain than writing RLS policies or application level checks that will surely leak after some time.  
+— Sidharth [MermaidChart](https://www.mermaidchart.com/)
diff --git a/blog/supabase.mdx b/blog/supabase.mdx
index 5cf9b547..ebb5aa0b 100644
--- a/blog/supabase.mdx
+++ b/blog/supabase.mdx
@@ -97,7 +97,7 @@ const { data: todos, error } = await supabase.from('todos').select('*');
 
 The overall idea is very similar to how [PostgREST](https://postgrest.org/) works. In fact, Supabase's RESTful layer is built with PostgREST. Please check out the following post if you're interested.
 
-{% embed https://dev.to/zenstack/backend-less-web-development-with-postgrest-3ma8 %}
+[Modern Web Architecture Without a Backend — Using PostgREST](/blog/postgrest)
 
 ###Realtime
 
diff --git a/blog/todo-saas/index.md b/blog/todo-saas/index.md
index f5a0fdb2..aa022127 100644
--- a/blog/todo-saas/index.md
+++ b/blog/todo-saas/index.md
@@ -452,4 +452,4 @@ How many places do you think we need to change? Actually just one in the `List`
 
 I hope this tutorial can show you the benefit of [ZenStack](https://zenstack.dev/) in using the data model as the single source of truth to handle access control.
 
-Feel free to contact us on our [Discord](https://go.zenstack.dev/chat) or [GitHub](https://github.com/zenstackhq/zenstack) if you have any questions about it.
+Feel free to contact us on our [Discord](https://discord.gg/Ykhr738dUe) or [GitHub](https://github.com/zenstackhq/zenstack) if you have any questions about it.
diff --git a/blog/v2-stories/cover.jpg b/blog/v2-stories/cover.jpg
new file mode 100644
index 00000000..716101b2
Binary files /dev/null and b/blog/v2-stories/cover.jpg differ
diff --git a/blog/v2-stories/index.md b/blog/v2-stories/index.md
new file mode 100644
index 00000000..6b60939b
--- /dev/null
+++ b/blog/v2-stories/index.md
@@ -0,0 +1,150 @@
+---
+title: Stories Behind ZenStack V2
+description: the main features of ZenStack V2 and the stories behind it.
+tags: [zenstack, polymorphism, edge, prisma, vscode]
+authors: jiasheng
+date: 2024-04-26
+image: ./cover.jpg
+---
+
+# Stories Behind ZenStack V2!
+
+![Cover Image](cover.jpg)
+
+After polishing ZenStack V2 in the future branch for more than two months, we are happy to make it official now. I would like to take this opportunity to briefly show you the main features of V2 and the stories behind it.
+<!--truncate-->
+
+## Polymorphic Relations
+
+This feature is actually the reason we created V2. It's one of the most desired features of Prisma, which you can see from the reactions to the two related GitHub issues:
+
+- [Support for a Union type #2505](https://github.com/prisma/prisma/issues/2505)
+- [Support for Polymorphic Associations #1644](https://github.com/prisma/prisma/issues/1644)
+
+Some folks even think that an ORM is incomplete without polymorphism support.
+
+As the believer and enhancer of Prisma, one of our strategies is to pick up where Prisma has left.  So, it did come to our radar at the early stage:
+
+[Support for Polymorphic Associations #430](https://github.com/zenstackhq/zenstack/issues/430)
+
+It quickly became one of the most desired features of ZenStack too.  Not only for the reactions themselves but also because more issues actually depend on it: 
+
+- [ZenStack does not let me define multiple fields that are referencing the same model #653](https://github.com/zenstackhq/zenstack/issues/653)
+- [Support implicit inversed relationship #613](https://github.com/zenstackhq/zenstack/issues/613)
+
+However, we know it’s a non-trivial task.  Therefore, instead of rushing into the implementation, we wrote a blog post first to explain our approach and send it to the communities of both Prisma and ZenStack to get their feedback:
+
+[Tackling Polymorphism in Prisma](https://zenstack.dev/blog/polymorphism)
+
+The feedback has exceeded our expectations. One guy even refers to it as **Christmas magic**:
+
+[![github-comments](https://github.com/zenstackhq/zenstack/assets/16688722/efb6fef1-c5be-4a72-8a84-837e405f2da0)](https://github.com/prisma/prisma/issues/1644#issuecomment-1867913252)
+
+Gaining enough confidence, we began working on the implementation. Our confidence was further boosted when Prisma mentioned it as a third-party solution in its official blog, even though the feature was still in the alpha stage:
+
+[Table inheritance | Prisma Documentation](https://www.prisma.io/docs/orm/prisma-schema/data-model/table-inheritance#third-party-solutions)
+
+Now, you can fully enjoy it with the latest version of ZenStack:
+
+[Polymorphic Relations](https://zenstack.dev/docs/guides/polymorphism)
+
+Why should you use it? Check out the below post to illustrate the benefit with a real example:
+
+[End-To-End Polymorphism: From Database to UI, Achieving SOLID Design](https://zenstack.dev/blog/ocp)
+
+## Edge Support
+
+This is a surprising gift we received from Prisma.  Previously, when a user asked if ZenStack could run on Edge, we would direct them to the [Prisma Accelerate](https://www.prisma.io/data-platform/accelerate) proxy, as it was the only way to do it.  However, not all users are willing to use another service. This situation is frustrating for us because, unlike resolving polymorphism, we can't fully address it from ZenStack unless reimplementing the entire Prisma engine.   
+
+Fortunately, Prisma sent us [a fantastic gift](https://www.prisma.io/blog/prisma-orm-support-for-edge-functions-is-now-in-preview) at the start of 2024. Without hesitation, we began tweaking ZenStack to ensure its compatibility with the edge runtime.  Even though the workload is minimal compared to polymorphism, we gained substantial knowledge throughout the process. If you're also planning to adapt to the Edge runtime, we hope our experience can save you some time:
+
+[Adapting ZenStack to the Edge: Our Struggles and Learnings](https://zenstack.dev/blog/adapt-to-edge)
+
+By the way, Edge support is in preview for both Prisma and ZenStack, so if you run into any issues, feel free to contact us via [Twitter](https://twitter.com/zenstackhq), [Discord](https://discord.gg/Ykhr738dUe), or [GitHub](https://github.com/zenstackhq/zenstack). 
+
+## `Auth()` in `Default()`
+
+This is a very special feature.  The brilliant idea came from one of our users at a very early time:
+
+[Support for auth() in @default annotation #310](https://github.com/zenstackhq/zenstack/issues/310)
+
+The best part is that right before we started to consider whether to implement it in V2,  another user sent out a PR for it:
+
+[Support for auth() in @default attribute #958](https://github.com/zenstackhq/zenstack/pull/958)
+
+This is the first feature of ZenStack that is fully implemented by the community. We would like to express our gratitude by sharing the author’s [GitHub profile link](https://github.com/Azzerty23) here.
+
+It once again shows the benefit of using declarative schema to reduce the duplication of imperative code:
+
+- before
+    ```jsx
+    //schema.zmodel
+    model Post {
+      ...
+      owner User @relation(fields: [ownerId], references: [id])
+      ownerId Int
+    }
+    
+    //xxx.ts
+    const db = enhance(prisma, { user });
+    await db.post.create({
+      data: {
+       owner: { connect: { id: user.id } },
+       title: 'Post1'
+      }
+    })
+    ```
+- after
+    ```jsx
+    //schema.zmodel
+    model Post {
+      ...
+      owner User @relation(fields: [ownerId], references: [id])
+      ownerId Int @default(auth().id) // <- assign ownerId automatically
+    }
+    
+    //xxx.ts
+    const db = enhance(prisma, { user });
+    await db.post.create({ data: { title: 'Post1' } });
+    ```
+
+## VSCode Auto-Formatting
+
+This is the feature I regret the most. In fact, I would call it a fix instead of a feature because I did implement it in V1:
+
+[![twitter-link](https://github.com/zenstackhq/zenstack/assets/16688722/e7aff6aa-568a-4edf-aec9-44e741f2ca22)](https://twitter.com/jiashenggo/status/1720036341642154260)
+
+I mentioned “minimize disruptions for prisma users”, but you know what? It was actually a lie. Although I tried to make ZModel look as same as Prisma, it used a different indentation style:
+
+![style-compare](https://github.com/zenstackhq/zenstack/assets/16688722/560d8544-8197-4996-890f-ca42b2613e5c)
+
+Which one do you think looks better? 😉 I convinced myself that the left one was better due to its consistency with TS style. Why need consistency with TS? Because it includes the access policy code.   
+
+It turns out that all of that was just an excuse to avoid making changes.  Even when I realized it soon, I found another excuse that the change would disturb existing users.  You can see how skilled our mind is at finding excuses to avoid work.  However, it struggles to estimate the actual workload accurately because it attempts to avoid it.  
+
+The total time I spent to implement it is just a couple of hours which even includes flags both in VSCode extension and CLI to give the user the option to preserve the old style if he likes:
+
+![vscode-option](https://github.com/zenstackhq/zenstack/assets/16688722/c83a2aeb-cb7f-4f1a-b966-6943db558527)
+
+```bash
+Usage: zenstack format [options]
+
+Format a ZenStack schema file.
+
+Options:
+  --no-prisma-style  do not use prisma style
+```
+
+Don't let inertia obscure the truth; use your heart to find the right path and pursue it.
+
+# Final Words
+
+You can find the complete change detail in the below post:
+
+[Upgrading to V2](https://zenstack.dev/docs/upgrade-v2)
+
+In the Game of Thrones series finale, Tyrion Lannister said the following words:
+
+> What unites people? Armies? Gold? Flags? Stories. There's nothing in the world more powerful than a good story.
+
+These features will probably be forgotten as time passes or even not used at all by most people. But I hope the stories behind them could still have some meaning for you.
diff --git a/docs/_components/_zenstack-init-tips.md b/docs/_components/_zenstack-init-tips.md
index 63905c2b..1639b721 100644
--- a/docs/_components/_zenstack-init-tips.md
+++ b/docs/_components/_zenstack-init-tips.md
@@ -1,6 +1,6 @@
 import ZenStackInit from './_zenstack-init.md'
 
-:::tip
+:::info
 
 <ZenStackInit />
 
diff --git a/docs/community-packages.md b/docs/community-packages.md
new file mode 100644
index 00000000..2cc6d3ff
--- /dev/null
+++ b/docs/community-packages.md
@@ -0,0 +1,16 @@
+---
+sidebar_position: 7
+sidebar_label: Community Packages
+---
+
+# Community Packages
+
+Our wonderful community has created the following packages that extend ZenStack's functionality.
+
+- [ZenStack GraphQL Plugin](https://github.com/hakutakuAi/zenstack-graphql) by [hakutakuAi](https://github.com/hakutakuAi)
+  
+    This plugin generates a complete GraphQL schema with relations, filtering, sorting, and pagination. Saving you hours of boilerplate code to match your database models with you graphql server.
+
+- [ZenStack UI](https://github.com/kirankunigiri/zenstack-ui) by [Kiran Kunigiri](https://github.com/kirankunigiri)
+
+    Zenstack UI is a React UI library for automating form and list generation with Zenstack. It utilizes Zenstack metadata to automatically generate forms and perform required queries and mutations.
diff --git a/docs/faq.md b/docs/faq.md
index c8a3ffce..db01d75a 100644
--- a/docs/faq.md
+++ b/docs/faq.md
@@ -3,7 +3,7 @@ description: ZenStack FAQ.
 
 slug: /faq
 sidebar_label: FAQ
-sidebar_position: 8
+sidebar_position: 100
 ---
 
 # 🙋🏻 FAQ
@@ -15,7 +15,7 @@ No, it is not implemented with Postgres RLS. Instead, ZenStack applies access co
 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.
+1. Collocating access control rules with the data model.
 
 ### Does using ZenStack introduce more connections to my databases?
 
@@ -23,7 +23,7 @@ No. It's common to create enhanced PrismaClient wrappers per request (for bindin
 
 ### 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.
+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](./reference/server-adapters/next) and [Fastify](./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?
 
@@ -33,8 +33,8 @@ To use ZenStack in a monorepo setup (e.g., pnpm workspace), run `zenstack init`
 
 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)
+-   [Custom attributes](./reference/zmodel-language#custom-attributes-and-functions)
+-   [Plugins](./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`.
 
@@ -44,7 +44,7 @@ We will continue improving the parity between ZModel and Prisma Schema regarding
 
 ### Does ZenStack require a specific Prisma version?
 
-No. ZenStack references Prisma as a peer dependency and should work with Prisma 4.8.0 and above.
+No. ZenStack references Prisma as a peer dependency and works with Prisma 5.0.0 and above.
 
 ### Does ZenStack work with Prisma migration?
 
@@ -52,4 +52,41 @@ Yes. When you run `zenstack generate` it generates a standard Prisma schema file
 
 ### Are there any other IDE integrations besides VSCode?
 
-Unfortunately for the time being, VSCode is the only officially supported IDE. There have been many asks about JetBrains. ZenStack's language tooling is built with [Langium](https://github.com/langium/langium), and we'll need to wait for the resolution of [this issue](https://github.com/langium/langium/issues/999) to evaluate JetBrains support.
\ No newline at end of file
+Yes, ZenStack has integrations with VSCode and JetBrains IDEs. Please see [here](./guides/ide) for ore details.
+
+### Does ZenStack work with Prisma Client Extensions?
+
+The short answer is it works in most cases. Please refer to [this guide](./guides/client-extensions) for more details.
+
+### Does the order in which access policies are defined matter?
+
+No. See [here](./the-complete-guide/part1/4-access-policy/4.1-model-level.md#evaluation-of-model-level-policies) for how access polices are evaluated.
+
+### Is Prisma's new "prisma-client" generator supported?
+
+Yes, since v2.16.0. The "prisma-client" generator is introduced in [Prisma 6.6](https://github.com/prisma/prisma/releases/tag/6.6.0). When it's used, Prisma requires you to specify an output folder explicitly, and will generate TypeScript source files (instead of compiled JavaScript files) into the folder. The files should be compiled with your source tree, and you should import `PrismaClient` from that folder instead of `@prisma/client`.
+
+Similarly, you'll need to use the "--output" option when running `zenstack generate` to generate ZenStack files into your source tree and get them compiled together with your source code. And you should import the `enhance` function from there.
+
+ZModel:
+```zmodel
+generator client {
+  provider = "prisma-client"
+  output   = "../generated/prisma/client"
+  moduleFormat = "cjs"
+}
+```
+
+Running `zenstack generate`:
+```bash
+npx zenstack generate --output ./generated/zenstack
+```
+
+App code:
+```ts
+import { PrismaClient } from './generated/prisma/client';
+import { enhance } from './generated/zenstack/enhance';
+
+const prisma = new PrismaClient();
+const db = enhance(prisma, ...);
+```
diff --git a/docs/guides/authentication/iron-session.md b/docs/guides/authentication/iron-session.md
deleted file mode 100644
index 98c0d966..00000000
--- a/docs/guides/authentication/iron-session.md
+++ /dev/null
@@ -1,107 +0,0 @@
----
-description: Integrating with iron-session.
-sidebar_position: 4
-sidebar_label: iron-session
----
-
-# Integrating With iron-session
-
-[Iron-session](https://www.npmjs.com/package/iron-session) is a lightweight authentication toolkit. It works with Next.js, Express, and Node.js HTTP servers.
-
-### Implement auth endpoints
-
-Unlike NextAuth, iron-session requires you to implement auth-related API endpoints by yourself. Usually, you need to have at least these three endpoints: `api/auth/login`, `/api/auth/logout`, and `/api/auth/user`.
-
-Implementing these endpoints can be done with a standard Prisma client. Here we show simple code snippets for how to do it in Next.js projects.
-
--   `/api/auth/login`
-
-```ts title='/src/pages/api/auth/login.ts'
-import { NextApiHandler } from 'next/types';
-import { withIronSessionApiRoute } from 'iron-session/next';
-import * as bcrypt from 'bcryptjs';
-import { sessionOptions } from '../lib/sessionOptions';
-import { prisma } from '../lib/db';
-
-const loginRoute: NextApiHandler = async (req, res) => {
-    const { email, password } = req.body;
-
-    const user = await prisma.user.findUnique({ where: { email } });
-    if (!user || !bcrypt.compareSync(password, user.password)) {
-        res.status(401).json({
-            message: 'invalid email and password combination',
-        });
-        return;
-    }
-
-    delete user.password;
-    req.session.user = user;
-    await req.session.save();
-
-    res.json(user);
-};
-
-export default withIronSessionApiRoute(loginRoute, sessionOptions);
-```
-
--   `/api/auth/logout`
-
-```ts title='/src/pages/api/auth/logout.ts'
-import { NextApiHandler } from 'next/types';
-import { withIronSessionApiRoute } from 'iron-session/next';
-import { sessionOptions } from '../lib/sessionOptions';
-
-const logoutRoute: NextApiHandler = async (req, res) => {
-    req.session.destroy();
-    res.json({});
-};
-
-export default withIronSessionApiRoute(logoutRoute, sessionOptions);
-```
-
--   `/api/auth/user`
-
-```ts title='/src/pages/api/auth/user.ts'
-import { NextApiHandler } from 'next/types';
-import { withIronSessionApiRoute } from 'iron-session/next';
-import { sessionOptions } from '../lib/sessionOptions';
-import { prisma } from '../lib/db';
-
-const userRoute: NextApiHandler = async (req, res) => {
-    if (req.session?.user) {
-        // fetch user from db for fresh data
-        const user = await prisma.user.findUnique({
-            where: { email: req.session.user.email },
-        });
-        if (!user) {
-            res.status(401).json({ message: 'invalid login status' });
-            return;
-        }
-
-        delete user.password;
-        res.json(user);
-    } else {
-        res.status(401).json({ message: 'invalid login status' });
-    }
-};
-
-export default withIronSessionApiRoute(userRoute, sessionOptions);
-```
-
-### Create an enhanced Prisma client
-
-You can create an enhanced Prisma client which automatically validates access policies, field validation rules etc., during CRUD operations. For more details, please refer to [ZModel Language](/docs/reference/zmodel-language) reference.
-
-To create such a client with a standard setup, call the `enhance` API with a regular Prisma client and the current user (fetched from iron-session). Here's an example:
-
-```ts
-import type { NextApiRequest, NextApiResponse } from 'next';
-import { enhance } from '@zenstackhq/runtime';
-import { prisma } from '../lib/db';
-
-async function getPrisma(req: NextApiRequest, res: NextApiResponse) {
-    // create a wrapper of Prisma client that enforces access policy,
-    // data validation, and @password, @omit behaviors
-    return enhance(prisma, { user: req.session?.user });
-}
-```
diff --git a/docs/guides/check-permission.md b/docs/guides/check-permission.md
new file mode 100644
index 00000000..5ac31721
--- /dev/null
+++ b/docs/guides/check-permission.md
@@ -0,0 +1,201 @@
+---
+description: Checking permissions without hitting the database.
+sidebar_position: 15
+---
+
+# Checking Permissions Without Hitting the Database (Preview)
+
+## Introduction
+
+ZenStack's access policies provide a protection layer around Prisma's CRUD operations and filter/deny access to the data automatically. However, there are cases where you simply want to check if an operation is permitted without actually executing it. For example, you might want to show or hide a button based on the user's permission.
+
+Of course, you can determine the permission by executing the operation to see if it's allowed (try reading data, or mutating inside a transaction then aborting). But this comes with the cost of increased database load, slower UI rendering, and data pollution risks.
+
+Another choice is to implement permission checking logic directly inside your frontend code. However, it'll be much nicer if the access policies in ZModel can be reused, so it stays as the single source of truth for access control.
+
+This guide introduces how to use ZenStack's `check` API to check permissions without accessing the database. The feature is in preview, and feedback is highly appreciated.
+
+:::danger
+
+Permission checking is an approximation and can be over-permissive. You MUST NOT trust it and circumvent the real access control mechanism (e.g., calling raw Prisma CRUD operations without further authorization checks).
+
+:::
+
+## Understanding the Problem
+
+ZenStack's access policies are by design coupled with the data model, which implies that to check permission precisely, you'll have to evaluate it against the actual data. In reality, what you often need is an approximation, or in other words, a "weak" check. For example, you may want to check if the current user, given his role, can read entities of a particular model, and if so, render the corresponding part of UI. You don't really want to guarantee that the user is allowed to read every row of that model. What you care about is if he's potentially allowed.
+
+With this in mind, "checking permission" is equivalent to answering the following question:
+
+> Assuming we can have arbitrary rows of data in the database, can the access policies for the given operation possibly evaluate to `TRUE` for the current user?
+
+The problem then becomes a [Boolean Satisfiability Problem](https://en.wikipedia.org/wiki/Boolean_satisfiability_problem). We can treat model fields as "variables" and use a [SAT Solver](https://en.wikipedia.org/wiki/SAT_solver) to find a solution for those variables that satisfy the access policies. If a solution exists, then the permission is possible.
+
+Let's make the discussion more concrete by looking at an example:
+
+```zmodel
+model Post {
+  id Int @id @default(autoincrement())
+  title String
+  author User @relation(fields: [authorId], references: [id])
+  authorId Int
+  published Boolean @default(false)
+
+  @@allow('read', published || authorId == auth().id)
+}
+```
+
+The "read" policy rule can be converted to a boolean formula like:
+
+```mermaid
+flowchart LR
+  OR((OR)) --> A["[published] == true"]
+  OR((OR)) --> B["[authorId] == context.user.id"]
+```
+
+:::info
+
+- The `context` object is the second argument you pass to the `enhance` API call.
+- A name wrapped with square brackets represents a named variable in a boolean formula.
+
+:::
+
+To check if a user can read posts, we simply need to find a solution for the `published` and `authorId` variables that make the boolean formula evaluate to `TRUE`.
+
+## Using the `check` API
+
+ZenStack adds a `check` API to every model in the enhanced PrismaClient. The feature is still in preview, so you need to explicitly opt in by turning on the "generatePermissionChecker" flag on the "@core/enhancer" plugin in ZModel:
+
+```zmodel
+
+plugin enhancer {
+  provider = '@core/enhancer'
+  generatePermissionChecker = true
+}
+
+```
+
+Then, rerun `zenstack generate`, and the `check` API will be available on each model with the following signature (using the `Post` model as an example):
+
+```ts
+type CheckArgs = {
+  /**
+   * The operation to check for
+   */
+  operation: 'create' | 'read' | 'update' | 'delete';
+
+  /**
+   * The optional additional constraints to impose on the model fields
+   */
+  where?: { id?: number; title?: string; published?: boolean; authorId?: number };
+}
+
+check(args: CheckArgs): Promise<boolean>;
+```
+
+Let's see how to use it to check `Post` readability for different use cases. Just to recap, the boolean formula for the "read" policy is:
+
+```mermaid
+flowchart LR
+  OR((OR)) --> A["[published] == true"]
+  OR((OR)) --> B["[authorId] == context.user.id"]
+```
+
+### 1. Can an anonymous user read posts?
+
+The scenario is to determine if the `Posts` UI tab should be visible when the current user is not logged in. We can do the checking as follows:
+
+```ts
+const db = enhance(prisma); // enhance without a user context
+await canRead = await db.post.check({ operation: 'read' });
+```
+
+The result will be `true` with the following variable assignments:
+
+- `published -> true`
+- `authorId -> 0`
+
+Note that the `authorId` variable can actually be any integer.
+
+### 2. Can an anonymous user read unpublished posts?
+
+The scenario is to determine if the `Drafts` UI tab should be visible when the current user is not logged in.
+
+```ts
+const db = enhance(prisma); // enhance without a user context
+await canRead = await db.post.check({ operation: 'read', where: { published: false } });
+```
+
+We're now adding an additional constraint `published == false` that the solver needs to consider besides the original formula:
+
+```mermaid
+flowchart LR
+  OR((OR)) --> A["[published] == true"]
+  OR((OR)) --> B["[authorId] == context.user.id"]
+  AND((AND)) --> C["[published] == false"]
+  AND((AND)) --> OR
+  style C stroke-dasharray: 5, 5
+```
+
+The result will be `false` because there are no assignments of the `published` and `authorId` variables that satisfy the formula. Note that the `context.user.id` value is undefined thus cannot be equal to `authorId`.
+
+### 3. Can `user#1` read unpublished posts
+
+The scenario is to determine if the `Drafts` UI tab should be visible for a currently logged-in user.
+
+```ts
+const db = enhance(prisma, { user: { id: 1 } }); // enhance with user context
+await canRead = await db.post.check({ operation: 'read', where: { published: false } });
+```
+
+We're now providing a value `1` to `context.user.id`, and the formula becomes:
+
+```mermaid
+flowchart LR
+  OR((OR)) --> A["[published] == true"]
+  OR((OR)) --> B["[authorId] == 1"]
+  AND((AND)) --> C["[published] == false"]
+  AND((AND)) --> OR
+  style C stroke-dasharray: 5, 5
+```
+
+The result will be `true` with the following variable assignments:
+
+- `published -> false`
+- `authorId -> 1`
+
+## Server Adapters and Hooks
+
+The `check` API is also available in the [RPC API Handler](../reference/server-adapters/api-handlers/rpc) and can be used with all [server adapters](../category/server-adapters).
+
+The [@zenstackhq/tanstack-query](../reference/plugins/tanstack-query) and [@zenstackhq/swr](../reference/plugins/swr) plugins have been updated to generate `useCheck[Model]` hooks for checking permissions in the frontend.
+
+```ts
+import { useCheckPost } from '~/lib/hooks';
+
+const { data: canReadDrafts } = useCheckPost({
+  operation: 'read',
+  where: { published: false } 
+});
+```
+
+## Limitations
+
+ZenStack uses the [logic-solver](https://www.npmjs.com/package/logic-solver) package for SAT solving. The solver is lightweighted, but only supports boolean and bits (non-negative integer) types. This resulted in the following limitations:
+
+- Only `Boolean`, `Int`, `String`, and enum types are supported.
+- Functions (e.g., `startsWith`, `contains`, etc.) are not supported.
+- Array fields are not supported.
+- Relation fields are not supported.
+- Collection predicates are not supported.
+- The API is not supported on edge runtime (e.g., Cloudflare Workers or Vercel Edge).
+
+You can still use the `check` API even if your access policies use these unsupported features. Boolean components containing unsupported features are ignored during SAT solving by being converted to free variables, which can be assigned either `true` or `false` in a solution.
+
+## Notes About Anonymous Context
+
+Access policy rules often use `auth()` and members of `auth()` (e.g., `auth().role`) in them. When a PrismaClient is enhanced in an anonymous context (calling `enhance` without context user object), neither `auth()` nor its members are available. In such cases, the following evaluation rules apply:
+
+- `auth() == null` evaluates to `true`.
+- `auth() != null` evaluates to `false`.
+- Any other form of boolean component involving `auth()` or its members evaluates to `false`.
diff --git a/docs/guides/client-extensions.md b/docs/guides/client-extensions.md
new file mode 100644
index 00000000..aa875e68
--- /dev/null
+++ b/docs/guides/client-extensions.md
@@ -0,0 +1,118 @@
+---
+description: Using ZenStack with Prisma Client Extensions.
+sidebar_position: 10
+---
+
+# Using With Prisma Client Extensions
+
+[Prisma Client Extension](https://www.prisma.io/docs/orm/prisma-client/client-extensions) is a mechanism for extending the interface and functionality of Prisma Client.
+
+We'll use the following ZModel as a reference throughout this guide:
+
+```zmodel
+model Post {
+  id Int @id @default(autoincrement())
+  title String
+  content String?
+  published Boolean @default(false)
+
+  @@allow('read', published)
+}
+```
+
+## Enhancing a Prisma Client with extensions installed
+
+This should be the most common way of using ZenStack with client extensions. You install client extensions to the global `PrismaClient` instance when the application starts and then call `enhance` with the extended client on a per-request basis. It will generally work as you would intuitively expect.
+
+Let's look at a few examples.
+
+### 1. Model-level methods
+
+```ts
+const extendedPrisma = prisma.$extends({
+  model: {
+    post: {
+      async getFeeds() {
+          const context = Prisma.getExtensionContext(this);
+          return context.findMany();
+      },
+    },
+  },
+});
+
+const db = enhance(extendedPrisma);
+const feeds = await db.post.getFeeds();
+```
+
+The `getFeeds()` call will return only published posts. The `context` variable fetched with `Prisma.getExtensionContext(this)` is enhanced by ZenStack.
+
+### 2. Query-level overrides
+
+```ts
+const extendedPrisma = prisma.$extends({
+  query: {
+    post: {
+      findMany({ query, args }) {
+        console.log('Query args:', inspect(args, { depth: null }));
+        return query(args);
+      },
+    },
+  },
+});
+
+const enhanced = enhance(extendedPrisma);
+const posts = await enhanced.post.findMany();
+```
+
+The `findMany` call returns only published posts. The printed log will demonstrate that the `args` is injected by ZenStack to include access policy filters.
+
+You can also override mutation methods and it generally works. However, it must be noted that ZenStack enforces mutation policies before or after the mutation is executed (in the case of "after", a transaction is used to roll back if the policy fails), depending on the access policies and the mutation input. This means that if you alter the mutation args in a client extension to make it conform to the access policy, it may not work because ZenStack intercepts the mutation call first, and may decide to reject it based on the original args.
+
+### 3. Computed fields
+
+```ts
+const extendedPrisma = prisma.$extends({
+  result: {
+    post: {
+      myTitle: {
+        needs: { title: true },
+        compute(post) {
+          return 'MyTitle: ' + post.title;
+        },
+      },
+    },
+  },
+});
+
+const enhanced = enhance(extendedPrisma);
+const post = await enhanced.post.findFirst();
+```
+
+Computed fields will work as expected. ZenStack enhancement works transparently with it.
+
+:::danger
+
+Currently there's a limitation that computed fields are not governed by field-level access policies. This means that if you have a computed field that depends on a field that the current user cannot read, the computed field will still be calculated and returned.
+
+:::
+
+## Installing extensions to an enhanced Prisma Client
+
+Since v2.9.0, installing client extensions to an enhanced Prisma Client should generate the same result as doing it the opposite way - i.e., call `enhance` on an extended Prisma Client, as explained in the previous section.
+
+```ts
+const db = enhance(prisma);
+const extended = db.$extends({
+  model: {
+    post: {
+      async getFeeds() {
+          const context = Prisma.getExtensionContext(this);
+          return context.findMany();
+      },
+    },
+  },
+});
+
+const feeds = await extended.post.getFeeds();
+// `feeds` will only contain published posts
+```
diff --git a/docs/guides/debugging.md b/docs/guides/debugging.md
index 03133862..fc08366e 100644
--- a/docs/guides/debugging.md
+++ b/docs/guides/debugging.md
@@ -1,5 +1,5 @@
 ---
-sidebar_position: 7
+sidebar_position: 4
 ---
 # Debugging and Troubleshooting
 
@@ -59,7 +59,7 @@ The `zenstack` CLI provides a REPL environment for you to execute queries with t
 
 <iframe width="800" height="450" src="https://www.youtube.com/embed/yJr8zZVj-JA?si=kACpOQH57XINBCR7" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
 
-Please find more details about the REPL in the [CLI reference](/docs/reference/cli#repl).
+Please find more details about the REPL in the [CLI reference](../reference/cli#repl).
 
 ---
 
diff --git a/docs/guides/deploy.md b/docs/guides/deploy.md
index 2fd7c9df..3f7ee204 100644
--- a/docs/guides/deploy.md
+++ b/docs/guides/deploy.md
@@ -1,5 +1,5 @@
 ---
-sidebar_position: 8
+sidebar_position: 13
 ---
 # Deploying to Production
 
@@ -27,10 +27,11 @@ There might be cases where you don't want to run the `zenstack` CLI during the d
 npx zenstack generate --output ./zenstack
 ```
 
-At the runtime, when calling `enhance()`, you need to pass in an extra `loadPath` option to point to the directory where the supporting modules are located:
+
+Then, instead of importing the `enhance` API from `@zenstackhq/runtime`, import it directly from the output location:
 
 ```ts
-const db = enhance(prisma, { user }, { loadPath: './zenstack' })
+import { enhance } from './zenstack/enhance';
 ```
 
 In this way, all information ZenStack needs at runtime is already in the source tree, and you don't need to run `zenstack generate` during deployment.
diff --git a/docs/guides/edge.md b/docs/guides/edge.md
new file mode 100644
index 00000000..59fa85e7
--- /dev/null
+++ b/docs/guides/edge.md
@@ -0,0 +1,126 @@
+---
+description: Guide for deploying ZenStack to edge runtime.
+sidebar_position: 14
+---
+
+# Deploying to Edge Runtime
+
+## Introduction
+
+Edge Runtime is a kind of JavaScript runtime environment that allows code to run closer to the user's location, typically at the "edge" of the network. It can help improve the performance of web applications by reducing latency and increasing the speed of content delivery.
+
+ZenStack is tested to work with Vercel Edge Runtime and Cloudflare Workers. Please [let us know](https://discord.gg/Ykhr738dUe) if you would like to see support for other environments.
+
+
+## Edge-Compatible Database Drivers
+
+ZenStack is built above Prisma, and Prisma's edge support was added recently and still evolving. The first thing you need to check is to make sure you use a edge-compatible database driver and Prisma adapter, or a traditional database with [Prisma Accelerate](https://www.prisma.io/docs/accelerate). Please check [this documentation](https://www.prisma.io/docs/orm/prisma-client/deployment/edge/overview) for more details.
+
+## Vercel Edge Runtime
+
+You can enable edge runtime for a route by exporting a const named `runtime`. For example, for a RSC page:
+
+```ts
+import { createPrisma } from '~/lib/db';
+import { getSessionUser } from '~/lib/auth';
+
+export default async function Page() {
+  ...
+
+  // IMPORTANT: in edge environment you must create a new `PrismaClient` for each request
+  // https://github.com/prisma/prisma/issues/20566#issuecomment-2021594203
+  const prisma = createPrisma();
+  const db = enhance(prisma, { user: getSessionUser() });
+  const posts = await db.post.findMany();
+  ...
+}
+
+export const runtime = 'edge';
+```
+
+The page component (which is an RSC) will run on the server side in edge environment. You can use a compatible `PrismaClient` enhanced by ZenStack in its implementation.
+
+You can also run ZenStack's [Next.js server adapter](../reference/server-adapters/next) on the edge:
+
+```ts title='/src/app/api/model/[...path]/route.ts'
+import { NextRequestHandler } from '@zenstackhq/server/next';
+import { createPrisma } from '~/lib/db';
+import { getSessionUser } from '~/lib/auth';
+
+function getEnhancedPrisma(req: Request) {
+  // IMPORTANT: in edge environment you must create a new `PrismaClient` for each request
+  // https://github.com/prisma/prisma/issues/20566#issuecomment-2021594203
+  const prisma = createPrisma();
+  return enhance(prisma, { user: getSessionUser(req) });
+}
+
+const handler = NextRequestHandler({ getPrisma: (req) => getEnhancedPrisma(req), useAppDir: true });
+
+export {
+  handler as DELETE,
+  handler as GET,
+  handler as PATCH,
+  handler as POST,
+  handler as PUT,
+};
+
+export const runtime = 'edge';
+export const dynamic = 'force-dynamic';
+```
+
+:::warning DO NOT REUSE Prisma Client
+
+When targeting the edge runtime, it's important that you create a new `PrismaClient` instance for each request. Otherwise you can get unexpected behavior or errors. See [this comment](https://github.com/prisma/prisma/issues/20566#issuecomment-2021594203) for more details.
+
+:::
+
+You can use the "v2-edge" branch of [https://github.com/zenstackhq/sample-todo-nextjs-tanstack](https://github.com/zenstackhq/sample-todo-nextjs-tanstack/tree/v2-edge) as a reference.
+
+### Caveats
+
+`@zenstackhq/runtime` depends on `bcryptjs` which is not fully compatible with Vercel Edge Runtime. You may get the following error during compilation:
+
+```
+./node_modules/bcryptjs/dist/bcrypt.js
+Module not found: Can't resolve 'crypto' in '.../node_modules/bcryptjs/dist'
+```
+
+To workaround this problem, add the following to the "package.json" file to avoid explicitly resolving the "crypto" module:
+
+```json
+{
+  ...
+  "browser": {
+    "crypto": false
+  }
+}
+```
+
+## Cloudflare Workers
+
+You can use ZenStack-enhanced `PrismaClient` in Cloudflare Workers. Here's an example for using with a Neon database. Please make sure to import `enhance` from `@zenstackhq/runtime/edge` instead of `@zenstackhq/runtime`.
+
+```ts
+import { PrismaClient } from '@prisma/client';
+import { PrismaNeon } from '@prisma/adapter-neon';
+import { Pool } from '@neondatabase/serverless';
+import { enhance } from '@zenstackhq/runtime/edge';
+import { getSessionUser } from './auth';
+
+export default {
+  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
+    const neon = new Pool({ connectionString: env.DATABASE_URL });
+    const adapter = new PrismaNeon(neon);
+    const prisma = new PrismaClient({ adapter });
+    const db = enhance(prisma, { user: getSessionUser(request) });
+    const posts = await db.post.findMany();  
+    return Response.json(posts);
+  }
+};
+```
+
+:::info
+Cloudflare's [D1 database](https://developers.cloudflare.com/d1/) is not yet supported by ZenStack due to its lack of support for interactive transactions.
+:::
+
+If you need a more sophisticated backend running on Cloudflare Workers, [Hono](https://hono.dev/) is a great choice. Check [how you can use ZenStack with Hono](../reference/server-adapters/hono).
diff --git a/docs/guides/existing-prisma.md b/docs/guides/existing-prisma.md
index 52821463..244860cf 100644
--- a/docs/guides/existing-prisma.md
+++ b/docs/guides/existing-prisma.md
@@ -1,6 +1,6 @@
 ---
 description: Steps for migrating from existing Prisma projects.
-sidebar_position: 3
+sidebar_position: 2
 ---
 
 # Migrating Existing Prisma Projects
@@ -29,7 +29,7 @@ The command does the following things:
     npx zenstack@latest init --package-manager pnpm
     ```
 
-If the `init` command doesn't suit your needs, manually doing these steps is just fine. See [ZenStack CLI](/docs/reference/cli) for more details about using the CLI.
+If the `init` command doesn't suit your needs, manually doing these steps is just fine. See [ZenStack CLI](../reference/cli) for more details about using the CLI.
 
 ## Prisma generators' triple slash hack
 
diff --git a/docs/guides/field-encryption.md b/docs/guides/field-encryption.md
new file mode 100644
index 00000000..b449a116
--- /dev/null
+++ b/docs/guides/field-encryption.md
@@ -0,0 +1,161 @@
+---
+description: Encrypting Fields
+sidebar_position: 6
+---
+
+# Encrypting Fields
+
+> *This awesome feature is contributed by [Eugen Istoc](https://github.com/genu) and inspired by [prisma-field-encryption](https://github.com/47ng/prisma-field-encryption).*
+
+ZenStack's field encryption feature helps you add an extra layer of protection to sensitive data stored in your database.
+
+## Basic Usage
+
+To use the feature, simply mark the fields you want to encrypt with the `@encrypted` attribute in ZModel:
+
+```zmodel
+model User {
+  id         String @id @default(cuid())
+  someSecret String @encrypted
+}
+```
+
+When calling `enhance()` to create an enhanced PrismaClient, you need to pass an extra `encryption` settings to provide the encryption key if you use the default encryption:
+
+```ts
+function getEncryptionKey(): Uint8Array {
+    // return a 32-byte key
+}
+
+const db = enhance(prisma, { user }, {
+  encryption: {
+    encryptionKey: getEncryptionKey()
+  }
+});
+```
+
+Or, if you choose to use custom encryption, pass your custom `encrypt` and `decrypt` functions:
+
+```ts
+async function myEncrypt(model: string, field: FieldInfo, plain: string) {
+  ...
+}
+
+async function myDecrypt(model: string, field: FieldInfo, cipher: string) {
+  ...
+}
+
+const db = enhance(prisma, { user }, {
+  encryption: {
+    encrypt: myEncrypt,
+    decrypt: myDecrypt
+  }
+});
+```
+
+The encryption feature requires the "encryption" enhancement kind to be enabled. If you manually specify enhancement kinds, make sure it's included:
+
+```ts
+const db = enhance(prisma, { user }, {
+  kinds: ['policy', 'encryption'],
+  ...
+});
+```
+
+When you use the enhanced PrismaClient, the encryption and decryption process happens transparently:
+- When writing, the field value will be encrypted before being stored in the database.
+- When reading, the field value will be decrypted before being returned to you.
+
+## Default Encryption
+
+The default encryption uses the [AES-GCM](https://en.wikipedia.org/wiki/Galois/Counter_Mode) algorithm to encrypt and decrypt the data with a 32-byte symmetric key. A random 12-byte [IV](https://en.wikipedia.org/wiki/Initialization_vector) is generated and used for each encryption operation, ensuring the encryption result is non-deterministic.
+
+The data stored in the encrypted field consists of the following two parts (both base-64 encoded and joined with a "."):
+
+1. Metadata object with the following fields: 
+   - Version byte (v): indicating the encryption version
+   - Algorithm (a): AES-GCM
+   - Key digest (k): a digest of the encryption key used
+2. Encrypted data: IV bytes concatenated with the encrypted data bytes
+
+### Key Rotation
+
+Key rotation is a technique for enhancing security by periodically changing the encryption key. The default encryption supports key rotation by allowing you to specify multiple decryption keys:
+
+```ts
+const db = enhance(prisma, { user }, {
+  encryption: {
+    encryptionKey: encryptionKey,
+    decryptionKeys: [oldKey1, oldKey2, ...]
+  }
+});
+```
+
+When reading an encrypted field, the decryption key with a digest matching the encryption key digest (extracted from the encrypted data) will be used to decrypt the data. In the rare case when multiple keys have matching digests, each key will be tried in the order they are provided until the data is successfully decrypted.
+
+Please note that the `encryptionKey` setting value will be automatically used as a decryption key, so you don't need to include it in the `decryptionKeys` setting.
+
+:::warning FIELDS FAIL TO DECRYPT
+
+When a field value fails to decrypt, the default encryption returns the cipher text as is. This allows incremental adoption of encryption, as you can enable the feature and then asynchronously encrypt existing data. During this period, plain and encrypted data will coexist in the same field.
+
+Let us know if you have security concerns about this behavior, and we can consider making it configurable in the future.
+
+:::
+
+### Data Migration
+
+When you enable encryption on an existing field with data, you need to migrate the old data into encrypted form. Although ZenStack doesn't have a built-in data migration feature, it provides the infrastructure needed to implement a script.
+
+**1. Asynchronously encrypt existing data**
+
+As mentioned in the previous section, the way the default encryption handles decryption failures allows you to encrypt existing data asynchronously without taking your service down.
+
+**2. Using the default encrypter**
+
+The `@zenstackhq/runtime` package exports an `Encrypter` class that you can use to encrypt data into the format compatible with what the enhanced PrismaClient expects:
+
+```ts
+
+import { Encrypter } from '@zenstackhq/runtime/encryption';
+
+async function main() {
+  const encrypter = new Encrypter(encryptionKey);
+
+  let row = await getNextRow();
+  while (row) {
+    row.someSecret = await encrypter.encrypt(row.someSecret);
+    await saveRow(row);
+    row = await getNextRow();
+  }
+}
+
+main();
+```
+
+## Custom Encryption
+
+You can implement a custom encryption/decryption by providing your own `encrypt` and `decrypt` functions. By doing so, you'll be fully responsible for managing keys, encoding and decoding the encrypted data and implementing key rotation.
+
+```ts
+async function myEncrypt(model: string, field: FieldInfo, plain: string) {
+  ...
+}
+
+async function myDecrypt(model: string, field: FieldInfo, cipher: string) {
+  ...
+}
+
+const db = enhance(prisma, { user }, {
+  encryption: {
+    encrypt: myEncrypt,
+    decrypt: myDecrypt
+  }
+});
+```
+
+## Limitations
+
+- Only string fields are supported for encryption.
+- Encrypted fields cannot be used in access policies.
+- Although not explicitly disallowed, you should not use an encrypted field as an index, for filtering or sorting.
diff --git a/docs/guides/ide.md b/docs/guides/ide.md
new file mode 100644
index 00000000..799a3521
--- /dev/null
+++ b/docs/guides/ide.md
@@ -0,0 +1,47 @@
+---
+description: IDE extensions
+sidebar_position: 3
+---
+
+# Installing IDE Extensions
+
+## VSCode
+
+ZenStack VSCode extension provides syntax highlighting and error checking to improve the efficiency of your modeling work. It offers functionalities similar to the Prisma VSCode extension, plus support for added features like access policies.
+
+### Features
+
+- Syntax highlighting
+- Error highlighting
+- Go to definition
+- Auto-completion
+- Hover documentation
+- Formatting
+
+### Installation
+
+You can install it by searching "ZenStack Language Tools" inside VSCode or directly from [here](https://marketplace.visualstudio.com/items?itemName=zenstack.zenstack).
+
+[![VSCode Screenshot](/img/vscode-screenshot.png)](https://marketplace.visualstudio.com/items?itemName=zenstack.zenstack)
+
+## JetBrains IDEs
+
+ZenStack provides a plugin for JetBrains IDEs. The extension is based on JetBrain platform's new [Language Server Protocol (LSP) support](https://blog.jetbrains.com/platform/2023/07/lsp-for-plugin-developers/). The extension only offers preliminary features today, but please expect improvements in the future as the LSP support matures.
+
+:::info
+JetBrain's LSP support is not available to the community edition of the IDEs, so is the ZenStack plugin.
+:::
+
+### Features
+
+- Syntax highlighting
+- Error highlighting
+- Go to definition
+- Auto-completion (partial)
+- Formatting
+
+### Installation
+
+You can install it by searching "ZenStack Language Tools" inside JetBrain IDE's plugins marketplace or directly from [here](https://plugins.jetbrains.com/plugin/23397-zenstack-language-tools).
+
+[![JetBrains Screenshot](/img/jetbrains-screenshot.png)](https://plugins.jetbrains.com/plugin/17210-zenstack-language-tools)
diff --git a/docs/guides/multiple-schema.md b/docs/guides/multiple-schema.md
index 956d5d0c..e2faad19 100644
--- a/docs/guides/multiple-schema.md
+++ b/docs/guides/multiple-schema.md
@@ -1,14 +1,14 @@
 ---
 description: ZenStack offers increased flexibility in managing your schema file through multiple schema files and model inheritance.
-sidebar_position: 5
+sidebar_position: 8
 ---
-# Managing Large Schema
+# Multiple Schema Files and Model Inheritance
 
 As your business expands, your schema file grows with it. ZenStack offers increased flexibility in managing your schema file through multiple schema files and model inheritance. An example project is provided below for your reference:
 
 [https://github.com/zenstackhq/sample-multiple-schema](https://github.com/zenstackhq/sample-multiple-schema) 
 
-## Model Inheritance
+## Abstract Model Inheritance
 
 You can use abstract model to include common fields that would be shared by multiple models.  By extending abstract model, you can keep your schema DRY(Don’t repeat yourself).  The abstract model can contain both fields and attributes like a normal model, but it is erased after generation and does not appear in the generated Prisma schema file.
 
@@ -75,7 +75,21 @@ model Comment {
 }
 ```
 
-## Multiple Schemas
+:::info Inheriting from multiple bases
+
+You can inherit from multiple abstract models from a single model. For example:
+
+```zmodel
+abstract model Base1 { ... }
+
+abstract model Base2 { ... }
+
+model Post extends Base1, Base2 { ... }
+```
+:::
+
+
+## Multiple Schema Files
 
 You can split your schema file into multiple files and use `import` statements to include the file you needed.  When running `zenstack generate`, it recursively traverses all the imported models and combine them into a single model.  For instance, you can split the above schema file into two files below:
 
@@ -114,3 +128,5 @@ You can split your schema file into multiple files and use `import` statements t
         postId Int
     }
     ```
+
+You can also import schema files from NPM packages by prefixing the import path with a package name (with or without organization scope). See [the docs](../reference/zmodel-language#import) for more details. ZenStack user [TGTGamer](https://github.com/TGTGamer) made [this cool project](https://github.com/Eventiva/Eventiva) demonstrating a component-based organization of a complex schema.
diff --git a/docs/guides/polymorphism.md b/docs/guides/polymorphism.md
new file mode 100644
index 00000000..2d27a823
--- /dev/null
+++ b/docs/guides/polymorphism.md
@@ -0,0 +1,293 @@
+---
+description: Polymorphic Relations
+sidebar_position: 5
+---
+
+# Polymorphic Relations
+
+## Introduction
+
+Modeling relations in Prisma is straightforward: you define fields referencing the model of the other side of the relation, and associate them with the `@relation` attribute, e.g.:
+
+```zmodel
+model User {
+  id Int @id @default(autoincrement())
+  posts Post[]
+}
+
+model Post {
+  id Int @id @default(autoincrement())
+  createdAt DateTime @default(now())
+  updatedAt DateTime @updatedAt
+  title String
+  owner User @relation(fields: [ownerId], references: [id])
+  ownerId Int
+}
+```
+
+However, if the `User` model has relations with many models, it'll need to declare relation field (like the `posts` field above) for each of them. It's a very common pattern in applications where a model type is polymorphically related to many other types of models. For example, a user can own posts, videos, comments, etc. In such cases, the schema will look like this:
+
+```zmodel
+model User {
+  ...
+  posts Post[]
+  videos Video[]
+  ...
+}
+
+model Post {
+  ...
+  title String
+}
+
+model Video {
+  ...
+  duration Int
+}
+```
+
+The way how it's modeled in Prisma is awkward for several reasons:
+
+1. The `User` model needs to be updated every time a new polymorphic model is added.
+2. There's no way to query the polymorphic models uniformly with sorting and pagination.
+3. You can't easily model access policies consistently across the polymorphic models.
+
+There are several Prisma issues related to this missing feature:
+
+- [Support for a Union type](https://github.com/prisma/prisma/issues/2505)
+- [Support for Polymorphic Associations](https://github.com/prisma/prisma/issues/1644)
+
+## ZenStack Implementation
+
+One of the main things ZenStack does is to "enhance" `PrismaClient`. So it's natural to support polymorphism as an enhancement. There are several strategies to implement polymorphism in the database as summarized in [this blog post](/blog/polymorphism). ZenStack chooses to take the "delegated types" design, which has a good balance between flexibility and efficiency.
+
+### Usage
+
+#### Modeling
+
+To define a polymorphic hierarchy, you first define a base model, and them inherit from it. The base model should be marked with the `@@delegate` attribute. The attribute requires a parameter referencing a discriminator field - used for storing the concrete model type it delegates. Here's an example:
+
+```zmodel
+model User {
+  id Int @id @default(autoincrement())
+  contents Content[]
+}
+
+model Content {
+  id Int @id @default(autoincrement())
+  createdAt DateTime @default(now())
+  updatedAt DateTime @updatedAt
+  published Boolean @default(false)
+  owner User @relation(fields: [ownerId], references: [id])
+  ownerId Int
+  contentType String
+
+  @@delegate(contentType)
+}
+
+model Post extends Content {
+  title String
+}
+
+model Video extends Content {
+  name String
+  duration Int
+}
+```
+
+:::info Difference from abstract inheritance
+ZenStack v1 already supports [inheriting from abstract models](./multiple-schema#abstract-model-inheritance). Abstract inheritance merely copies over the fields and attributes from the base model to the child. It doesn't really provide a polymorphic hierarchy.
+:::
+
+:::info Discriminator field
+
+When a concrete model entity is created, the discriminator field will be automatically set to the model name. You can also use en enum for the discriminator field, and in that case, the enum fields must match the concrete model names.
+
+```zmodel
+enum ContentType {
+  Post
+  Video
+}
+
+model Content {
+  ...
+  @@delegate(contentType)
+}
+```
+
+:::
+
+#### Querying
+
+To use polymorphic relations, you should create an enhanced `PrismaClient` with the "delegate" enhancement kind enabled - either by a simple `enhance` call with all kinds enabled:
+
+```ts
+const db = enhance(prisma, { user });
+```
+
+, or by explicitly specifying the "delegate" kind:
+
+```ts
+const db = enhance(prisma, { user }, { kinds: ['delegate', 'policy'] });
+```
+
+You can then work with the inheritance hierarchy pretty much like how you deal with polymorphism in OOP:
+
+```ts
+// create a user
+const user = await db.user.create({ data: { id: 1 } });
+
+// create a concrete `Post` model
+const post = await db.post.create({
+  data: {
+    owner: { connect: { id: user.id } }, 
+    title: 'Post1' 
+  },
+});
+
+// create a concrete `Video` model
+const video = await db.video.create({
+  data: {
+    owner: { connect: { id: user.id } },
+    name: 'Video1',
+    duration: 100,
+  }
+});
+
+// query with concrete model, will return fields from both base and concrete models
+// [ 
+//   { id: 1, ownerId: 1, contentType: 'Post', title: 'Post1', published: false, createdAt: ..., updatedAt: ... },
+//   { id: 2, ownerId: 1, contentType: 'Video', name: 'Video1', duration: 100, published: false, createdAt: ..., updatedAt: ... }
+// ]
+console.log('All posts:', inspect(await db.post.findMany()));
+
+// query with base model, will also return fields from both base and concrete models, but with base's typing
+// [ 
+//   { id: 1, ownerId: 1, contentType: 'Post', title: 'Post1', published: false, createdAt: ..., updatedAt: ... },
+//   { id: 2, ownerId: 1, contentType: 'Video', name: 'Video1', duration: 100, published: false, createdAt: ..., updatedAt: ... }
+// ]
+console.log('All contents:', inspect(await db.content.findMany()));
+
+// you can use the discriminator field to help TypeScript narrow down the typing
+const firstContent = await db.content.findFirstOrThrow();
+if (firstContent.contentType === 'Post') {
+  console.log('Post title:', firstContent.title);
+} else {
+  console.log('Video name:', firstContent.name);
+}
+
+// set all contents as published from the user
+await db.user.update({
+  where: { id: user.id },
+  data: {
+    contents: { 
+      updateMany: { where: {}, data: { published: true } } 
+    },
+  },
+});
+
+// delete with base model
+await db.content.deleteMany();
+
+// querying with concrete model again will get an empty array 
+// because of the cascaded deletion
+console.log('All posts after delete:', inspect(await db.post.findMany()));
+```
+
+:::danger
+
+You should never manipulate the concrete models with a raw `PrismaClient` or one without the "delegate" enhancement kind enabled. This may cause corruption to the relation between the base and concrete entities.
+
+:::
+
+### Access Policies
+
+In an delegated inheritance hierarchy, all access policies from the base models are inherited to the sub models. This ensures that when you directly manipulate base model's fields with a sub model, the access policies on the base are effective.
+
+Here's how you can add policies to the sample schema:
+
+```zmodel
+model User {
+  id Int @id @default(autoincrement())
+  contents Content[]
+
+  @@allow('create,read', true)
+  @@allow('all', auth() == this)
+}
+
+model Content {
+  id Int @id @default(autoincrement())
+  createdAt DateTime @default(now())
+  updatedAt DateTime @updatedAt
+  owner User @relation(fields: [ownerId], references: [id], onDelete: Cascade)
+  ownerId Int
+  published Boolean @default(false)
+  contentType String
+
+  @@delegate(contentType)
+
+  @@allow('read', published)
+  @@allow('all', auth() == owner)
+}
+
+model Post extends Content {
+  title String
+}
+
+model Video extends Content {
+  name String
+  duration Int
+}
+```
+
+### Polymorphic TypeScript Types
+
+The TS types generated by Prisma into the `@prisma/client` module have the database representation's typing and do not provide the "polymorphic view". For example, the following code doesn't compile:
+
+```ts
+import type { Prisma } from '@prisma/client';
+
+const where: Prisma.PostWhereInput = {};
+console.log(where.published); // error: Property 'published' does not exist on type 'PostWhereInput'
+```
+
+To use the TS types with polymorphic view, import the `Prisma` type from the `@zenstackhq/runtime/models` module instead. This module contains types generated by ZenStack that comply with the ZModel schema.
+
+```ts
+import type { Prisma } from '@zenstackhq/runtime/models';
+
+const where: Prisma.PostWhereInput = {}
+console.log(where.published); // `PostWhereInput` has the `published` field
+```
+
+### Sample Project
+
+- Simple TypeScript script sample: [https://github.com/zenstackhq/v2-polymorphism](https://github.com/zenstackhq/v2-polymorphism)
+- Full-stack blog app sample (use `polymorphic` branch): [https://github.com/zenstackhq/docs-tutorial-nextjs/tree/polymorphic](https://github.com/zenstackhq/docs-tutorial-nextjs/tree/polymorphic)
+
+### Inner Workings
+
+ZenStack works with two versions of `PrismaClient` to achieve polymorphism:
+
+- **Logical view**
+  
+  It's the `PrismaClient` an end-developer sees when using the enhanced client. The logical client copies all fields of the base models to the sub models to give you the experience of inheritance. The logical client is only for providing polymorphic typing, and it's not mapped to the database schema or used for real database operations.
+
+  The logical `PrismaClient` is derived from a Prisma schema generated into "node_modules/.zenstack/delegate.schema".
+  
+- **Physical view**
+  
+  It's the `PrismaClient` ZenStack internally uses when working with the database. It's derived from the Prisma schema that's normally generated as the output of `zenstack generate`. The physical client also aligns with your database schema: fields of base and sub models only reside in their respective tables without duplication, and they are linked with foreign keys.
+
+The main thing that ZenStack does internally is to translate between these two "views". The end-developer works on the logical view, and ZenStack intercepts the Prisma calls and translate them into appropriate queries and mutations to the physical view.
+
+### Limitations
+
+- Inheriting from multiple `@delegate` models is not supported yet.
+
+- You cannot access base fields when calling `count`, `aggregate`, and `groupBy` with a concrete model. The following query is not supported:
+
+    ```ts
+    // you can't access base fields (`published` here) when aggregating
+    db.post.count({ select: { published: true } });
+    ```
diff --git a/docs/guides/prisma-pulse.md b/docs/guides/prisma-pulse.md
new file mode 100644
index 00000000..a97a9335
--- /dev/null
+++ b/docs/guides/prisma-pulse.md
@@ -0,0 +1,46 @@
+---
+description: Using ZenStack with Prisma Pulse.
+sidebar_position: 16
+---
+
+# Using with Prisma Pulse (Preview)
+
+## Introduction
+
+[Prisma Pulse](https://www.prisma.io/data-platform/pulse) is Prisma's cloud offering that allows you to easily subscribe to real-time data change events from your database. It's a great way to build real-time applications without managing a Change Data Capture (CDC) infrastructure.
+
+With Prisma Pulse, you can use the new `stream()` API to subscribe to data change events. For example:
+
+```ts
+const stream = await prisma.user.stream()
+
+for await (const event of stream) {
+  console.log('just received an event:', event)
+}
+```
+
+The `stream()` API also allows you to filter only the events you're interested in.
+
+```ts
+// Filter for new User records with a non-null value for name
+const stream = await prisma.user.stream({
+  create: {
+    name: { not: null },
+  },
+});
+```
+
+## ZenStack's access policies and Prisma Pulse
+
+TLDR: it just works!
+
+ZenStack's access policies seamlessly work with the `stream()` API without additional configuration. You can simply use an enhanced Prisma client to subscribe to data change events, and the access policies will be enforced automatically.
+
+```ts
+const db = enhance(prisma, { user: session.user });
+
+// The event stream is automatically filtered by the access policies
+const stream = await db.user.stream(...);
+```
+
+Only events that satisfy the "read" policies will be returned. If there are field-level "read" policies, fields that do not satisfy the policies will be stripped from the event payload. Fields marked with `@omit` are also automatically removed.
diff --git a/docs/guides/redwood.md b/docs/guides/redwood.md
new file mode 100644
index 00000000..1e41339d
--- /dev/null
+++ b/docs/guides/redwood.md
@@ -0,0 +1,189 @@
+---
+description: Guide for using ZenStack with RedwoodJS.
+sidebar_position: 12
+---
+
+# Using With RedwoodJS
+
+[RedwoodJS](https://redwoodjs.com/) is an opinionated full-stack framework that combines a set of tools and libraries and helps you build GraphQL-based applications quickly without struggling with tooling.
+
+ZenStack provides a plugin package to help you easily integrate with RedwoodJS projects. After setting it up, you can define access policies in ZModel and use the enhanced `PrismaClient` to enforce the policies in your services automatically.
+
+## Details
+
+### Setting up
+
+You can prepare your RedwoodJS project for ZenStack by running the following command at the root of your project:
+
+```bash
+yarn rw setup package @zenstackhq/redwood
+```
+
+The setup command will:
+
+1. Update "redwood.toml" to allow the ZenStack CLI plugin
+   
+    ```yaml
+    [[experimental.cli.plugins]]
+    package = "@zenstackhq/redwood"
+    enabled = true
+    ```
+
+2. Install ZenStack dependencies to the "api" package
+   
+    - `zenstack`: the main CLI
+    - `@zenstackhq/runtime`: the runtime library for creating enhanced `PrismaClient`
+    - `@zenstackhq/redwood`: custom CLI and runtime for RedwoodJS
+
+3. Prepare ZModel schema
+
+    Your Prisma schema file "api/db/schema.prisma" will be copied to "api/db/schema.zmodel". Moving forward, you should edit "schema.zmodel" to update the database schema and access policies. The "schema.prisma" file will be regenerated when you run `yarn rw @zenstackhq generate`.
+
+4. Register the location of "schema.zmodel" and "schema.prisma" in "api/package.json"
+
+    ```json title="api/package.json"
+    {
+      ...
+      "zenstack": {
+        "schema": "db/schema.zmodel",
+        "prisma": "db/schema.prisma"
+      }
+    }
+    ```
+   
+5. Install a GraphQLYoga plugin to the GraphQL handler
+   
+    ```ts title="api/src/functions/graphql.[ts|js]"
+    import { useZenStack } from '@zenstackhq/redwood'
+    import { db } from 'src/lib/db'
+    import { createGraphQLHandler } from '@redwoodjs/graphql-server'
+    ...
+
+    export const handler = createGraphQLHandler({
+      ...
+      extraPlugins: [useZenStack(db)],
+    })
+    ```
+
+    The `useZenStack` plugin creates an enhanced `PrismaClient` for the current requesting user and stores it as `db` field in the global GraphQL context. You can use it in your service code via `context.db`. For example, the service for listing blog posts can be implemented as follows: only posts readable to the current user will be returned.
+
+    ```ts title="api/src/services/posts/posts.js"
+    export const posts = (...args) => {
+      return context.db.post.findMany()
+    }
+    ```
+
+    The plugin, by default, uses `context.currentUser` to get the current requesting user. You can customize it by passing in a function as the second argument to calculate a custom user object based on `context.currentUser`. E.g.:
+
+    ```ts
+    useZenStack(db, async (currentUser) => {
+      const typedUser = currentUser as { id: string };
+      const dbUser = await db.user.findUnique({ 
+        where: { id: typedUser.id },
+        // select more fields
+        select: { id: true, role: true }
+      });
+      return dbUser;
+    });
+    ```
+
+    See [here](../the-complete-guide/part1/access-policy/current-user) for more details about accessing the current user.
+   
+6. Eject service templates
+
+    The setup runs `yarn rw setup generator service` to eject template files used by `yarn rw g service` command. It also modifies the templates to use `context.db` instead of `db` to access the database with automatic access policy enforcement.
+
+### Modeling data and access policies
+
+ZenStack's ZModel language is a superset of Prisma schema language. You should use it to define both the data schema and access policies. [The Complete Guide](../the-complete-guide/part1/) of ZenStack is the best way to learn how to author ZModel schemas. Here's a quick example to show how you can define access policies for the blog post sample used throughout the official RedwoodJS tutorial:
+
+```zmodel title="api/db/schema.zmodel"
+model Post {
+  id        Int      @id @default(autoincrement())
+  title     String
+  body      String
+  comments  Comment[]
+  user      User     @relation(fields: [userId], references: [id])
+  userId    Int
+  createdAt DateTime @default(now())
+  published Boolean @default(true)
+
+  // 🔐 Admin user can do everything to his own posts
+  @@allow('all', auth().roles == 'admin' && auth() == user)
+
+  // 🔐 Posts are visible to everyone if published
+  @@allow('read', published)
+}
+```
+
+You should run the following command after updating "schema.zmodel":
+
+```bash
+yarn rw @zenstackhq generate
+```
+
+The command does the following things:
+
+1. Regenerate "schema.prisma"
+2. Run `prisma generate` to regenerate `PrismaClient`
+3. Generate supporting JS modules for enforcing access policies at runtime
+
+
+<!-- You can also use the
+
+```bash
+yarn rw @zenstackhq sample
+```
+
+command to browse a list of sample schemas and create from them. -->
+
+### Development workflow
+
+The workflow of using ZenStack is very similar to using Prisma in RedwoodJS projects. The two main differences are:
+
+1. Code generation
+
+    You should run `yarn rw @zenstackhq generate` instead of `yarn rw prisma generate`. The ZenStack's "generate" command internally regenerates the Prisma schema from the ZModel schema, runs `prisma generate` automatically, and outputs supporting modules for access policy enforcement.
+
+2. Database access in services
+
+    In your service code, you should use `context.db` instead of `db` for accessing the database. The setup procedure prepared a customized service code template. When you run `yarn rw g service`, the generated code will already use `context.db`.
+
+Other Prisma-related workflows like generation migration or pushing schema to the database stay unchanged.
+
+### Deployment
+
+You should run the "generate" command in your deployment script before `yarn rw deploy`. For example, to deploy to Vercel, the command can be:
+
+```bash
+yarn rw @zenstackhq generate && yarn rw deploy vercel
+```
+
+:::info Notes about Netlify
+
+ZenStack generates supporting JS modules into the `node_modules/.zenstack` folder, and the folder needs to be accessible at the runtime. Netlify detects the dependencies it needs to bundle by inspecting an application's root package.json file. This will result in the `.zenstack` folder being ignored. 
+
+To fix this problem, add the following section to your `netlify.toml` file:
+
+```toml
+[functions]
+  included_files = ["node_modules/.zenstack/*"]
+```
+
+:::
+
+### Using the `@zenstackhq` CLI plugin
+
+The `@zenstackhq/redwood` package registers a set of custom commands to the RedwoodJS CLI under the `@zenstackhq` namespace. You can run it with:
+
+```bash
+yarn rw @zenstackhq <cmd> [options] 
+```
+
+The plugin is a simple wrapper of the standard `zenstack` CLI, similar to how RedwoodJS wraps the standard `prisma` CLI. It's equivalent to running `npx zenstack ...` inside the "api" directory.
+
+See the [CLI references](../reference/cli) for the complete list of commands.
+
+## Sample application
+
+You can find a completed multi-tenant Todo application built with RedwoodJS and ZenStack at: [https://github.com/zenstackhq/sample-todo-redwood](https://github.com/zenstackhq/sample-todo-redwood).
diff --git a/docs/guides/supabase-security.md b/docs/guides/supabase-security.md
new file mode 100644
index 00000000..830caefd
--- /dev/null
+++ b/docs/guides/supabase-security.md
@@ -0,0 +1,100 @@
+---
+description: Guide to securely integrating Supabase with ZenStack - how to prevent unauthorized access.
+sidebar_position: 11
+sidebar_label: Secure Supabase with ZenStack
+---
+
+
+# Ensuring Security When Integrating Supabase with ZenStack
+
+Integrating Supabase with ZenStack can be a great choice as Supabase provides authentication as well as database hosting and file uploads.
+
+However, it is essential to take some precautions when using ZenStack with Supabase to ensure that the ZenStack authorization layer cannot be bypassed via requests to the Supabase API url.
+
+This article will help you to understand the risk, how to test if your database is exposed, and how to prevent it.
+
+## 1.  Understanding the Risk
+
+Supabase allows anonymous requests to the Supabase rest API access the database by default, assuming Row Level Security (RLS) is in place. 
+
+ZenStack's authorization can effectively serve as an in-application security layer, but **direct HTTP requests to your Supabase API could still leave your database exposed.**
+
+This poses a potential risk because Supabase API URL and anon key are both public and can easily be found eg by inspecting the url/headers of a Supabase authentication network request.
+
+
+## 2. How to test if your database is exposed to direct Supabase API requests
+
+Use Bash or an API client (such as postman or VS Code thunder client) to send a HTTP request with these details:
+
+```bash
+curl '{SUPABASE_PROJECT_URL}/rest/v1/{DATABASE_TABLE}?select=*' \
+-H "apikey: SUPABASE_ANON_KEY" \
+-H "Authorization: Bearer SUPABASE_ANON_KEY"
+```
+
+For example:
+
+```bash
+curl 'https://jypbzsozorjnogibmfhu.supabase.co/rest/v1/User?select=*' \
+-H "apikey: eyOiJIUzJhbGciI1..." \
+-H "Authorization: Bearer eyOiJIUzJhbGciI1..."
+```
+
+Note:
+- Your Supabase project URL should look like this: `https://projectid.supabase.co`
+- You can find both of these values inside Supabase dashboard > settings > api
+
+As previously mentioned, all the detailed needed to make this request are public and could be accessed eg by inspecting the url/headers of a Supabase authentication network request.
+
+If your database is secure, you should receive a permission denied error, such as:
+
+```json
+{
+   "code": "42501",
+   "details": null,
+   "hint": null,
+   "message": "permission denied for table User"
+}
+```
+
+If you have RLS enabled on the table, you may get a response with an empty array instead of a permission denied error. This means RLS filtering is effective, and it's preventing you from reading rows that are not granted access. However, it's still recommended to fully revoke anonymous access permissions for better security
+
+## 3. How to prevent direct Supabase API requests
+
+### Revoke default & current privileges
+
+Start by revoking all privileges from the `anon` role to prevent unauthenticated access, and changing the defauts to ensure future objects are also protected.
+
+In the Supabase dashboard inside the SQL Editor tab, execute the following:
+
+```sql
+-- Revoke current privileges
+REVOKE SELECT ON ALL TABLES IN SCHEMA public FROM anon;
+REVOKE USAGE ON ALL SEQUENCES IN SCHEMA public FROM anon;
+REVOKE EXECUTE ON ALL FUNCTIONS IN SCHEMA public FROM anon;
+
+-- Revoke default privileges for future objects
+ALTER DEFAULT PRIVILEGES IN SCHEMA public REVOKE ALL ON TABLES FROM anon;
+ALTER DEFAULT PRIVILEGES IN SCHEMA public REVOKE ALL ON FUNCTIONS FROM anon;
+ALTER DEFAULT PRIVILEGES IN SCHEMA public REVOKE ALL ON SEQUENCES FROM anon;
+```
+
+Note: If you are using Supabase cli for local development, then create a migration file with `pnpm supabase db migration new disable-anon-access` to prevent accidental rollbacks. (this creates a new local sql migration file, 'disable-anon-access' can be replaced)
+
+### Verify Role Configurations
+
+In the Supabase dashboard under the Database > Roles section, confirm the following:
+
+**Anon Role:**  All permissions should be disabled.
+**Authenticated Role:** All permissions should be also disabled here.
+**Postgres Role:** Ensure it retains the ability to bypass RLS for necessary backend tasks.
+
+
+## Conclusion
+
+By disabling permissions for `anon` and `authenticated` roles, you prevent direct public access to your database, relying instead on your application logic and ZenStack's authorization to control access.
+
+Remember to regularly test your application security to ensure effective and appropriate access control for your application.
+
+
+
diff --git a/docs/guides/trpc.mdx b/docs/guides/trpc.mdx
index 8b194adb..f4d8174b 100644
--- a/docs/guides/trpc.mdx
+++ b/docs/guides/trpc.mdx
@@ -1,9 +1,11 @@
 ---
 description: Guide for using ZenStack with tRPC.
-sidebar_position: 6
+sidebar_position: 9
 ---
 
 import InitTips from '../_components/_zenstack-init-tips.md';
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
 
 # Using With tRPC
 
@@ -33,15 +35,60 @@ First install the trpc package:
 npm install -D @zenstackhq/trpc@latest
 ```
 
+Then, add the following plugin configuration to your ZModel schema. Note that the configuration is different for tRPC v10 and v11:
+
+<Tabs>
+
+<TabItem value="v10" label={`tRPC v10`}>
+
 ```zmodel title='/schema.zmodel'
+plugin trpc {
+    provider = '@zenstackhq/trpc'
+    output = 'src/server/routers/generated'
+}
+```
+</TabItem>
+
+<TabItem value="v11" label={`tRPC v11 (preview)`}>
+
+To use with tRPC v11, you need to explicitly set the "version" option to "v11" and add two extra options to let ZenStack know where to import the tRPC's router factory (named `createTRPCRouter`) and procedure factory (named `procedure`) from. For example, with the following settings:
 
+```zmodel title='/schema.zmodel'
 plugin trpc {
     provider = '@zenstackhq/trpc'
     output = 'src/server/routers/generated'
+    version = 'v11'
+    importCreateRouter = "../../trpc"
+    importProcedure = "../../trpc"
+}
+```
+
+The generated tRPC routers will contain code like the following:
+
+```ts
+import { createTRPCRouter } from "../../trpc";
+import { procedure } from "../../trpc";
+
+export default function createRouter() {
+  return createTRPCRouter({
+    findMany: procedure.input(...).query(...) => ...
+    ...
+  });
 }
 
 ```
 
+:::info Why is v11 more complicated?
+
+tRPC v11 removed several public TypeScript types that our previous way of code generation relied on. To get around this, we have to resort to letting the user tell us where to import the factories from so we can use them without explicitly typing them.
+
+:::
+
+</TabItem>
+
+</Tabs>
+
+
 ### 3. Setting up the tRPC context
 
 Usually in your tRPC project, you have a function to create a tRPC context. You need to make sure the context contains a `prisma` field that is an instance of Prisma client. The generated tRPC routers use that to talk to the database.
@@ -73,14 +120,43 @@ npx zenstack generate
 
 You should find a bunch of tRPC routers generated in the output folder, one per each data model. A `createRouter` helper function is also generated, which returns a router instance for all models. You can use it as your top-level tRPC router, or merge it with other routers to form a more complex setup.
 
+<Tabs>
+
+<TabItem value="v10" label={`tRPC v10`}>
+
 ```ts title='/src/server/routers/_app.ts'
 import { createRouter } from './generated/routers';
 
 const t = initTRPC.context<Context>().create();
 
 export const appRouter = createRouter(t.router, t.procedure);
+export type AppRouter = typeof appRouter;
+```
+
+</TabItem>
+
+<TabItem value="v11" label={`tRPC v11`}>
+
+```ts title='/src/server/routers/_app.ts'
+import { createRouter } from './generated/routers';
+
+const t = initTRPC.context<Context>().create();
+
+export const procedure = t.publicProcedure;
+export const createTRPCRouter = t.createRouter;
+
+...
 
+export const appRouter = createRouter();
 export type AppRouter = typeof appRouter;
 ```
 
+</TabItem>
+
+</Tabs>
+
 _NOTE_: The ZenStack trpc plugin is based on the awesome work by [Omar Dulaimi](https://github.com/omar-dulaimi/prisma-trpc-generator).
+
+---
+
+Please refer to the [@zenstackhq/trpc](../reference/plugins/trpc) plugin documentation for more details. You can also use the [sample-todo-trpc](https://github.com/zenstackhq/sample-todo-trpc) project as a reference.
diff --git a/docs/guides/typing-json.md b/docs/guides/typing-json.md
new file mode 100644
index 00000000..4987f19a
--- /dev/null
+++ b/docs/guides/typing-json.md
@@ -0,0 +1,212 @@
+---
+description: Typing JSON fields
+sidebar_position: 5
+---
+
+# Typing JSON Fields
+
+> The design and implementation are inspired by [prisma-json-types-generator](https://github.com/arthurfiorette/prisma-json-types-generator).
+
+Relational databases provide the benefit of strong schema enforcement, but sometimes, you want to step out of that guardrail and have more flexibility around data modeling. Prisma's JSON type allows you to store arbitrary JSON data in a column. It can be a good fit for use cases like:
+
+- You want to store objects along with the main entity, but it's not worth creating a separate table for them.
+- Your objects have many possible fields, but you don't want to create tables with wide columns.
+- Your object's structure is not fixed and can change over time in a non-backward-compatible way.
+
+JSON fields provide great flexibility but at the cost of losing strong typing and validation. ZenStack's strongly typed JSON field feature helps you regain the lost benefits.
+
+## Defining types
+
+To type JSON fields, you'll first need to create type declarations in your ZModel schema. For example, you can define a user profile type like:
+
+```zmodel
+type Profile {
+  name String
+  age Int
+}
+```
+
+Types can include fields of other types too:
+
+```zmodel
+type Address {
+  state String
+  city String
+  zip String
+}
+
+type Profile {
+  name String
+  age Int
+  address Address?
+}
+```
+
+Type declarations are similar to model declarations with the following differences:
+
+- Types are not mapped to database tables.
+- Types cannot inherit from other types yet. This will be supported in future releases.
+- Types cannot have relations with models (since they aren't tables anyway).
+
+## Defining strongly-typed JSON fields
+
+You can then use these types to define strongly typed JSON fields in data models:
+
+```zmodel
+model User {
+  id String @id @default(cuid())
+  profile Profile @json
+
+  @@allow('all', true)
+}
+```
+
+:::info
+
+Strongly typed JSON fields must be annotated with the `@json` attribute. The purpose is to make them easily distinguishable from relational fields.
+
+The feature is only supported for PostgreSQL database for now.
+
+:::
+
+## `PrismaClient` typing
+
+ZenStack's enhanced `PrismaClient` provides type-safe mutation input and query results for strongly typed JSON fields.
+
+```ts
+import { enhance } from '@zenstackhq/runtime';
+import { prisma } from '~/db';
+
+const db = enhance(prisma);
+
+// The following create call results in a type error because of the
+// incorrect type of the `age` field
+await db.user.create({
+  data: {
+    profile: { name: 'Alice', age: '30' /* incorrect type */ }
+  }
+});
+
+// The query result is typed as:
+// {
+//   id: string;
+//   profile: {
+//     name: string;
+//     age: number;
+//     address?: {
+//       state: string;
+//       city: string;
+//       zip: string;
+//     } | null;
+//   };
+// }
+await user = await db.user.findFirstOrThrow();
+```
+
+The query also deals with transforming JSON fields into proper JavaScript types. For example, `DateTime` field values will be parsed from strings into `Date` objects.
+
+Please note that ZenStack **DOES NOT** validate if the query result data conforms to the JSON field types. If you have non-conforming existing data, you'll have to do additional checking or transformation, instead of trusting the TypeScript typing. We thought this was a reasonable trade-off to preserve the flexibility of JSON fields.
+
+:::info
+Prisma filter clauses are not enhanced, which means when you filter by the JSON fields, you'll still need to use [Prisma's JSON filter syntax](https://www.prisma.io/docs/orm/prisma-client/special-fields-and-types/working-with-json-fields#filter-on-a-json-field-simple).
+
+In the future, we may provide a more natural filtering experience, like:
+
+```ts
+await db.user.findMany({
+  where: {
+    profile: { name: 'Alice' }
+  }
+});
+```
+
+However, it can potentially confuse code readers on whether the filter happens on a JSON field or a relational field (which involves joins). [Let us know your thoughts](https://discord.gg/Ykhr738dUe) on this!
+
+:::
+
+## Mutation payload validation
+
+During mutation, the enhanced `PrismaClient` validates if the input data conforms to the JSON field type. Even if you bypass TypeScript's type checking, ZenStack will reject an incorrect payload with a runtime error.
+
+```ts
+await db.user.create({
+  data: {
+    email: 'abc',
+    profile: { name: 'Alice', age: '30' /* incorrect type */ },
+  },
+} as any // bypass TypeScript type checking
+);
+```
+
+```plain
+Error calling enhanced Prisma method `user.create`: denied by policy: user 
+entities failed 'create' check, input failed validation: Validation error: 
+Expected number, received string at "profile.age"
+```
+
+You can also annotate the type declaration with additional [validation attributes](../reference/zmodel-language#data-validation):
+
+```zmodel
+type Profile {
+  name String
+  age Int @gte(18) @lt(150) // must be between 18 and 150
+  address Address?
+}
+```
+
+Such validations will also be enforced during mutation. For example, the following call will be rejected:
+
+```ts
+await db.user.create({
+  data: {
+    email: 'abc',
+    profile: { name: 'Alice', age: 16 },
+  },
+});
+```
+
+```plain
+Error calling enhanced Prisma method `user.create`: denied by policy: user
+entities failed 'create' check, input failed validation: Validation error:
+Number must be greater than or equal to 18 at "profile.age"
+```
+
+## TypeScript types
+
+ZenStack generates TypeScript types for the "type" declarations. You can import them from `@zenstackhq/runtime/models`:
+
+```ts
+import type { Profile } from '@zenstackhq/runtime/models';
+
+const profile: Profile = {
+  name: 'Alice',
+  age: 30,
+  address: { state: 'WA', city: 'Seattle', zip: '98019' }
+};
+```
+
+## Zod schemas
+
+ZenStack also generates Zod schemas for the "type" declarations. You can import them from `@zenstackhq/runtime/zod/models`:
+
+```ts
+import { ProfileSchema } from '@zenstackhq/runtime/zod/models';
+
+const profile = ProfileSchema.parse({
+  name: 'Alice',
+  age: 30,
+  address: { state: 'WA', city: 'Seattle', zip: '98019' }
+});
+```
+
+## Limitations
+
+This feature is in preview and has the following limitations:
+
+1. Only PostgreSQL database is supported.
+1. Types cannot inherit from other types.
+1. Models cannot inherit from types.
+1. Types and abstract models are conceptually similar and they should probably be consolidated.
+1. JSON field members cannot be used in access policies.
+
+We would like to continue gathering feedback and address these limitations in future releases.
diff --git a/docs/guides/vscode.md b/docs/guides/vscode.md
deleted file mode 100644
index d06b0225..00000000
--- a/docs/guides/vscode.md
+++ /dev/null
@@ -1,12 +0,0 @@
----
-description: VSCode extension
-sidebar_position: 4
----
-
-# Installing VSCode Extension
-
-ZenStack VSCode extension provides syntax highlighting and error checking to improve the efficiency of your modeling work. It offers functionalities similar to the Prisma VSCode extension, plus support for added features like access policies.
-
-You can install it by searching "ZenStack Language Tools" inside VSCode or directly from [here](https://marketplace.visualstudio.com/items?itemName=zenstack.zenstack).
-
-[![VSCode Screenshot](/img/vscode-screenshot.png)](https://marketplace.visualstudio.com/items?itemName=zenstack.zenstack)
diff --git a/docs/quick-start/_access-policy.md b/docs/quick-start/_access-policy.md
index 78525599..d4531a41 100644
--- a/docs/quick-start/_access-policy.md
+++ b/docs/quick-start/_access-policy.md
@@ -1,16 +1,16 @@
-:::tip
+:::info
 
-By default, all operations are denied for a model. You can use `@@allow` attribute to open up some permissions.
+By default, all operations are denied for a model. You can use the `@@allow` attribute to open up some permissions.
 
-`@@allow` takes two parameters, the first is operation: create/read/update/delete. You can use a comma separated string to pass multiple operations, or use 'all' to abbreviate all operations. The second parameter is a boolean expression verdicting if the rule should be activated.
+`@@allow` takes two parameters, the first is operation: create/read/update/delete. You can use a comma separated string to pass multiple operations, or use 'all' to abbreviate all operations. The second parameter is a boolean expression that verdicts if the rule should be activated.
 
 Similarly, `@@deny` can be used to explicitly turn off some operations. It has the same syntax as `@@allow` but the opposite effect.
 
-Whether an operation is permitted is determined as the follows:
+Whether an operation is permitted is determined as follows:
 
 1. If any `@@deny` rule evaluates to true, it's denied.
-1. If any `@@allow` rule evaluates to true, it's allowed.
-1. Otherwise, it's denied.
+2. If any `@@allow` rule evaluates to true, it's allowed.
+3. Otherwise, it's denied.
 
-Check out [Understanding Access Policies](/docs/the-complete-guide/part1/access-policy) for more details.
+Check out [Understanding Access Policies](../the-complete-guide/part1/access-policy) for more details.
 :::
diff --git a/docs/quick-start/_auth_default.md b/docs/quick-start/_auth_default.md
new file mode 100644
index 00000000..9a80ac84
--- /dev/null
+++ b/docs/quick-start/_auth_default.md
@@ -0,0 +1,5 @@
+:::info
+
+The "{props.field}" field as a `@default()` attribute with `auth().id` value. The field will be automatically assigned with the current user's ID when creating a new record. See [here](../the-complete-guide/part1/access-policy/current-user#using-auth-in-default) for more details.
+
+:::
diff --git a/docs/quick-start/_zenstack-init-tips.md b/docs/quick-start/_zenstack-init-tips.md
index fde01723..472efd3f 100644
--- a/docs/quick-start/_zenstack-init-tips.md
+++ b/docs/quick-start/_zenstack-init-tips.md
@@ -1,4 +1,4 @@
-:::tip
+:::info
 
 The command installs a few NPM dependencies. If the project already has a Prisma schema at `prisma/schema.prisma`, it's copied over to `schema.zmodel`. Otherwise, a sample `schema.zmodel` file is created.
 
diff --git a/docs/quick-start/authentication/_category_.yml b/docs/quick-start/authentication/_category_.yml
new file mode 100644
index 00000000..8532cae7
--- /dev/null
+++ b/docs/quick-start/authentication/_category_.yml
@@ -0,0 +1,7 @@
+position: 9
+label: Integrating With Authentication
+collapsible: true
+collapsed: true
+link:
+    type: generated-index
+    title: Integrating With Authentication
diff --git a/docs/quick-start/authentication/auth0.md b/docs/quick-start/authentication/auth0.md
new file mode 100644
index 00000000..7b4ed9b6
--- /dev/null
+++ b/docs/quick-start/authentication/auth0.md
@@ -0,0 +1,181 @@
+---
+description: Integrating with Auth0.
+sidebar_position: 5
+sidebar_label: Auth0
+---
+
+# Integrating With Auth0
+
+> This guide is contributed by [Sinan Güçlü](https://github.com/Driptap).
+
+This guide provides simple examples of using [Auth0](https://auth0.com/) authentication with ZenStack. While Auth0 offers various authentication methods, this guide assumes you understand Auth0's authentication basics and can obtain a user object after authentication.
+
+## The basic premise of applying a custom session object to ZenStack.
+
+This section explains how to apply a custom session object to ZenStack by creating a user object and providing it to the enhance function when creating the Prisma client.
+
+Create a user object and provide it to the `enhance` function when creating the Prisma client.
+
+```ts
+export const getPrisma = async (req) => {
+  const user = await getAuthenticatedAuth0User(req);
+  return enhance(user);
+};
+```
+
+You can provide a type in the ZModel to express what the contents of the user object is.
+
+```zmodel
+type Auth {
+  id         String @id
+  specialKey String
+  @@auth
+}
+```
+
+## Adding Auth0 authentication
+
+You can authenticate an Auth0 user and extract information from the authentication and apply it to the ZenStack session.
+
+Here's an example using a JWT:
+
+```ts
+export const getPrismaJWT = async (req) => {
+  try {
+    const jwks = jose.createRemoteJWKSet(new URL(process.env.AUTH0_JWKS_URI));
+    const token = toString(req.headers.get('authorization')).replace('Bearer ', '');
+    const res = await jose.jwtVerify(token, jwks, {
+      issuer: `${process.env.AUTH0_ISSUER_BASE_URL}/`,
+      audience: process.env.AUTH0_AUDIENCE,
+      algorithms: ['RS256'],
+    });
+  
+    const userId = res.payload.sub;
+    const user = {
+      id: userId,
+      specialKey: res.payload.metadata.specialKey
+    };
+  
+    return enhance(prisma, {user});
+  catch (err) {
+    // unauthenticated error
+  }  
+};
+```
+
+This would populate your `auth()` in the Zmodel with the object you've just created from auth0; enabling checks like:
+
+```zmodel
+@@allow('read, update, create', auth().id == this.id)
+```
+
+or
+
+```zmodel
+@@allow('read, update, create', auth().specialKey == 'SUPERMAN')
+```
+
+You can add what you need to this variable and set the types for it as referred to in the *Enhancing the prisma client* section.
+
+
+## Working along side a user model
+
+You may want to keep a record of User's in your own database.
+
+When a user doesn't exist in your database, your application can trigger an onboarding flow to create one:
+```ts
+const currentUser = async (req) => {
+  // Get your auth0 auth session
+  const session = await getSession(req); 
+
+  if (!session?.user.sub) { 
+    // Throw an error if the user isn't authenticated
+    throw new Error('UNAUTHENTICATED');
+  }
+
+  // Find the user in the db
+  const dbUser = await prisma.user.findUnique({ 
+    where: { id: session.user.sub },
+  }); 
+  
+  return {
+    id: session.user.sub,
+    // If the user doesn't exist in the database, this variable 
+    // can be set in the session
+    dbUserExists: !isNull(dbUser),
+  };
+};
+
+// Create the client using the currentUser
+export const getPrisma = async (req) => {
+  const user = await currentUser(req);
+  return enhance(user);
+};
+```
+
+You can use the result of this token to redirect a user to an onboarding flow, such as a signup form:
+
+```ts
+app.get('/', async (req, res) => {
+  const user = await currentUser(req);
+  if (!user.dbUserExists) {
+    res.redirect('/onboarding');
+  }
+  res.send('hello user');
+});
+```
+
+and create a user record using the ID from the Auth0 session, here's a small example using the Auth0 React SDK:
+
+```ts
+import React from "react";
+import { useAuth0 } from "@auth0/auth0-react";
+
+const Profile = () => {
+  const { user, isAuthenticated, isLoading } = useAuth0();
+
+  const createUser = useCallback(async (event: FormEvent<HTMLFormElement>) => {
+    const formData = new FormData(event.currentTarget);
+    const name = formData.get('name');
+
+    try {
+      // create a new user
+      await fetch('/api/create-user', {
+          method: 'POST',
+          headers: {
+            'Content-Type': 'application/json'
+          },
+          body: JSON.stringify({
+            id: user.sub,
+            name: name,
+          }),
+        });
+    } catch(error){...}
+
+  return <UserForm onSubmit={createUser}/>
+};
+```
+After this, the user id stored in your database is the same with Auth0 identifier. 
+
+In this case, the Auth type is what provide authentication, not the User model, for example: 
+
+```zmodel
+// Specify the auth type
+type Auth {
+  id             String      @id
+  // And decorate it with @@auth to tell ZenStack to use this
+  // as the session object  
+  @@auth
+}
+
+// add your user model as a regular model
+model User {
+  id               String          @id
+  name             String?
+  email            String?
+
+  // You can now use the Auth object, populated by Auth0, to write policies
+  @@allow('create, read, update, delete', auth().id == this.id)
+}
+```
+
diff --git a/docs/quick-start/authentication/better-auth.md b/docs/quick-start/authentication/better-auth.md
new file mode 100644
index 00000000..f5c53c62
--- /dev/null
+++ b/docs/quick-start/authentication/better-auth.md
@@ -0,0 +1,129 @@
+---
+description: Integrating with Better Auth
+sidebar_position: 2
+sidebar_label: Better Auth
+---
+
+# Integrating With Better Auth
+
+[Better Auth](https://better-auth.com/) is an emerging open-source TypeScript authentication framework that offers a comprehensive set of features and great extensibility.
+
+## Preparation
+
+Follow Better Auth's installation [guide](https://www.better-auth.com/docs/installation#configure-database) using the Prisma adapter:
+
+```tsx
+import { betterAuth } from "better-auth";
+import { prismaAdapter } from "better-auth/adapters/prisma";
+import { PrismaClient } from "@prisma/client";
+ 
+const prisma = new PrismaClient();
+export const auth = betterAuth({
+    database: prismaAdapter(prisma, {
+        provider: "sqlite", // or "mysql", "postgresql", ...etc
+    }),
+});
+```
+
+Running Better Auth CLI to generate the Auth-related model in the Prisma schema:
+
+```bash
+npx @better-auth/cli generate
+```
+
+## Integrate with ZenStack
+
+Follow the [installation guide](https://zenstack.dev/docs/install) to install ZenStack in your project. 
+
+Integration with ZenStack is all about obtaining the user's identity and utilizing it to create an enhanced `PrismaClient`. On the server side,  Better Auth exposes that through the `api` object of the `auth` instance.  For example, here is how to get that in Next.js:
+
+```tsx
+import { betterAuth } from "better-auth";
+import { headers } from "next/headers";
+ 
+export const auth = betterAuth({
+    //...
+})
+ 
+// calling get session on the server
+const {session} = await auth.api.getSession({
+    headers: await headers() // some endpoint might require headers
+});
+
+// get the userId from session data
+const userId = session.userId;
+```
+
+Then you can pass it to ZenStack's [`enhance()`](https://zenstack.dev/docs/reference/runtime-api#enhance) API to create an enhanced `PrismaClient` that automatically enforces access policies.
+
+```tsx
+const db = enhance(prisma, { user: {id: userId} });
+```
+
+## Organization Plugin Support
+
+Better Auth has a powerful plugin system that allows you to add new features that contribute extensions across the entire stack - data model, backend API, and frontend hooks. A good example is the [Organization plugin](https://www.better-auth.com/docs/plugins/organization), which sets the foundation for implementing multi-tenant apps with access control.
+
+After enabling the Organization plugin and running the CLI to generate the additional models and fields in the schema, you can use the code below on the server side to get the organization info together with the user identity:
+
+```tsx
+
+  let organizationRole: string | undefined = undefined;
+  const organizationId = session.activeOrganizationId;
+  const org = await auth.api.getFullOrganization({ headers: reqHeaders });
+  if (org?.members) {
+    const myMember = org.members.find(
+        (m) => m.userId === session.userId
+    );
+    organizationRole = myMember?.role;
+  }
+  
+  // user identity with organization info
+  const userContext = {
+    userId: session.userId,
+    organizationId,
+    organizationRole,
+  };
+```
+
+:::info
+The Better Auth CLI will only update the `schema.prisma` file, if you add the Organization plugin after installing the ZenStack, you need to copy the change to `schema.zmodel` too. 
+:::
+
+Then you can pass the full `userContext` object to the `enhanced` client: 
+
+```tsx
+const db = enhance(prisma, { user: userContext });
+```
+
+The user context will be accessible in ZModel policy rules via the special `auth()` function. To get it to work, let's add a type in ZModel to define the shape of `auth()`:
+
+```tsx
+type Auth {
+  userId           String  @id
+  organizationId   String?
+  organizationRole String?
+  @@auth
+}
+```
+
+Here is how you could access it in the access policies:
+
+```tsx
+model ToDo {
+  ...
+  organization   Organization? @relation(fields: [organizationId], references: [id], onDelete: Cascade)
+  organizationId String?       @default(auth().organizationId) @allow('update', false)
+
+  // deny access that don't belong to the user's active organization
+  @@deny('all', auth().organizationId != organizationId)
+  
+  // full access to: list owner, org owner, and org admins
+  @@allow('all', 
+    auth().userId == ownerId ||
+    auth().organizationRole == 'owner' ||
+    auth().organizationRole == 'admin')
+}
+```
+
+You can find a complete sample of a multi-tenant Todo app code [here](https://github.com/ymc9/better-auth-zenstack-multitenancy).
\ No newline at end of file
diff --git a/docs/quick-start/authentication/clerk.md b/docs/quick-start/authentication/clerk.md
new file mode 100644
index 00000000..7adafed7
--- /dev/null
+++ b/docs/quick-start/authentication/clerk.md
@@ -0,0 +1,98 @@
+---
+description: Integrating with Clerk.
+sidebar_position: 3
+sidebar_label: Clerk
+---
+
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
+# Integrating With Clerk
+
+[Clerk](https://clerk.com/) is a comprehensive authentication and user management platform, providing both APIs and pre-made UI components.
+
+### Set up Clerk
+
+First, follow Clerk's [quick start guides](https://clerk.com/docs/quickstarts/overview) to set up your project.
+
+### Adjust your ZModel
+
+Since Clerk manages both user authentication and storage, you don't need to store users in your database anymore. However, you still need to provide a type that the `auth()` function can resolve to. Instead of using a regular model, we can declare a `type` instead:
+
+You can include any field you want in the `User` type, as long as you provide the same set of fields in the context object used for creating the enhanced Prisma client.
+
+The following code shows an example blog post schema:
+
+```zmodel
+type User {
+  id String @id
+}
+
+model Post {
+  id String @id @default(cuid())
+  createdAt DateTime @default(now())
+  updatedAt DateTime @updatedAt
+  title String
+  published Boolean @default(false)
+  authorId String // stores Clerk's user ID
+
+  // author has full access
+  @@allow('all', auth() != null && auth().id == authorId)
+
+  // logged-in users can view published posts
+  @@allow('read', auth() != null && published)
+}
+```
+
+If you choose to [synchronize user data to your database](https://clerk.com/docs/users/sync-data-to-your-backend), you can define `User` as a regular `model` since it's then backed by a database table.
+
+### Create an enhanced Prisma client
+
+You can create an enhanced Prisma client that automatically validates access policies, field validation rules, etc., during CRUD operations. For more details, please refer to [ZModel Language](../../reference/zmodel-language) reference.
+
+To create such a client with a standard setup, call the `enhance` API with a regular Prisma client and the current user (fetched from Clerk). Here's an example:
+
+<Tabs>
+
+<TabItem value="app-router" label="Next.js App Router">
+
+```ts
+import { enhance } from '@zenstackhq/runtime';
+import { auth } from "@clerk/nextjs/server";
+import { prisma } from '../lib/db';
+
+async function getPrisma() {
+  const authObject = await auth();
+  // create a wrapper of Prisma client that enforces access policy
+  return enhance(prisma, {
+    user: authObject.userId ? { id: authObject.userId } : undefined,
+  });
+}
+```
+
+</TabItem>
+
+<TabItem value="react" label="Next.js Pages Router">
+
+```ts
+import type { NextApiRequest } from 'next';
+import { enhance } from '@zenstackhq/runtime';
+import { getAuth } from '@clerk/nextjs/server';
+import { prisma } from '../lib/db';
+
+async function getPrisma(req: NextApiRequest) {
+  const auth = getAuth(req);
+  // create a wrapper of Prisma client that enforces access policy
+  return enhance(prisma, { user: auth ? { id: auth.userId } : undefined });
+}
+```
+
+</TabItem>
+
+</Tabs>
+
+---
+
+You can find a working sample project for Next.js [here](https://github.com/zenstackhq/docs-tutorial-clerk). Use the "main" branch for app router, and the "pages-router" branch for pages router.
+
+Check out [this blog post](../../../blog/clerk-multitenancy/) for a more sophisticated multi-tenant setup.
diff --git a/docs/quick-start/authentication/custom.md b/docs/quick-start/authentication/custom.md
new file mode 100644
index 00000000..848980ff
--- /dev/null
+++ b/docs/quick-start/authentication/custom.md
@@ -0,0 +1,55 @@
+---
+description: Integrating with a custom authentication system.
+sidebar_position: 100
+sidebar_label: Custom Authentication
+---
+
+# Integrating With a Custom Authentication System
+
+You may be using an authentication provider that's not mentioned in the guides. Or you may have a custom-implemented one. Integrating ZenStack with any authentication system is pretty straightforward. This guide will provide the general steps to follow.
+
+## Determining what's needed for access control
+
+The bridge that connects authentication and authorization is the `auth()` function that represents the authenticated current user. Based on your requirements, you should determine what fields are needed from it. The `auth()` object must at least contain an id field that uniquely identifies the current user. If you do RBAC, you'll very likely need an `auth().role` field available. Or even an `auth().permissions` field for fine-grained control.
+
+The `auth()` call needs to be resolved to a "type" in ZModel. If you store user data in your database, you may already have a "User" model that carries all the fields you need to access.
+
+```zmodel
+model User {
+  id          String @id
+  role        String
+  permissions String[]
+  ...
+}
+```
+
+ZenStack picks up the model named "User" automatically to resolve `auth()` unless another model or type is specifically appointed (by using the `@@auth` attribute). For example, if you're not storing user data locally, you can define a "type" to resolve `auth()`. This way, you can provide typing without being backed by a database table.
+
+```zmodel
+type Auth {
+  id          String @id
+  role        String
+  permissions String[]
+  @@auth
+}
+```
+
+Just remember that any thing that you access from `auth().` must be resolved.
+
+## Fetching the current user along with the additional information
+
+At runtime in your backend code, you need to provide a value for the `auth()` call to ZenStack, so it can use it to evaluate access policies. How this is done is solely dependent on your authentication mechanism. Here are some examples:
+
+1. If you use JWT tokens, you can issue tokens with user id and other fields embedded, then validate and extract them from the request.
+2. If you use a dedicated authentication service, you can call it to get the current user's information.
+
+You must ensure that whatever approach you use, the user information you get can be trusted and free of tampering.
+
+## Creating an enhanced PrismaClient
+
+Finally, you can call the `enhance` API to create an enhanced PrismaClient with the user information and enjoy automatically enforced access policies.
+
+```ts
+const user = await getCurrentUser(); // your implementation
+const db = enhance(prisma, { user });
+```
diff --git a/docs/quick-start/authentication/lucia.md b/docs/quick-start/authentication/lucia.md
new file mode 100644
index 00000000..7310c476
--- /dev/null
+++ b/docs/quick-start/authentication/lucia.md
@@ -0,0 +1,158 @@
+---
+description: Integrating with Lucia.
+sidebar_position: 6
+sidebar_label: Lucia
+---
+
+# Integrating With Lucia (Deprecated)
+
+:::info
+Lucia's is [not in active development anymore](https://github.com/lucia-auth/lucia/discussions/1714).
+:::
+
+[Lucia](https://lucia-auth.com/) is an auth library for your server that abstracts away the complexity of handling sessions. It is a good choice if you need to add custom logic to the auth flow or use email and password authentication.  
+
+To get access policies to work, ZenStack needs to be connected to the authentication system to get the user's identity. This guide introduces tasks required for integrating ZenStack with Lucia Auth. You can find a complete example [here](https://github.com/zenstackhq/sample-luciaAuth-nextjs). 
+
+## Data model requirement
+
+Lucia needs to store your users and sessions in the database. So, your ZModel definition needs to include these two models. Here is the sample schema:
+
+```zmodel title='/schema.zmodel'
+model User {
+  id       String    @id @default(uuid())
+  userName String    @unique
+  password String    @omit
+  sessions Session[]
+
+  @@allow('read', true)
+  @@allow('all', auth() == this)
+}
+
+model Session {
+  id        String   @id
+  userId    String
+  expiresAt DateTime
+
+  user      User     @relation(references: [id], fields: [userId], onDelete: Cascade)
+}
+```
+
+The data field names and types in the `session` model must exactly match the ones in the above. While you can change the model names, the relation name in the session model (`Session.user`) must be the camel-case version of the user model name. For example, if the user model was named `AuthUser`, the relation must be named `Session.authUser`.
+
+## Prisma adapter
+
+Lucia connects to your database via an adapter, which provides a set of basic, standardized querying methods that Lucia can use.
+
+Lucia has its own Prisma adapter `@lucia-auth/adapter-prisma`. Since ZenStack is based on Prisma, you can use PrismaAdapter for the job:
+
+```tsx title='/lib/auth.ts'
+import { PrismaAdapter } from "@lucia-auth/adapter-prisma";
+import { PrismaClient } from "@prisma/client";
+
+const client = new PrismaClient();
+
+const adapter = new PrismaAdapter(client.session, client.user);
+```
+
+## Validate requests
+
+Create `validateRequest()`. This will check for the session cookie, validate it, and set a new cookie if necessary. Make sure to catch errors when setting cookies and wrap the function with `cache()` to prevent unnecessary database calls.  To learn more, see Lucia’s  [Validating requests](https://lucia-auth.com/guides/validate-session-cookies/nextjs-app) page.
+
+```tsx title='/lib/auth.ts'
+export const validateRequest = cache(
+  async (): Promise<{ user: User; session: Session } | { user: null; session: null }> => {
+    const sessionId = cookies().get(lucia.sessionCookieName)?.value ?? null;
+    if (!sessionId) {
+      return {
+        user: null,
+        session: null
+      };
+    }
+
+    const result = await lucia.validateSession(sessionId);
+    // next.js throws when you attempt to set cookie when rendering page
+    try {
+      if (result.session && result.session.fresh) {
+        const sessionCookie = lucia.createSessionCookie(result.session.id);
+        cookies().set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes);
+      }
+      if (!result.session) {
+        const sessionCookie = lucia.createBlankSessionCookie();
+        cookies().set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes);
+      }
+    } catch {}
+    return result;
+  }
+);
+```
+
+This function can be used in server components and form actions to get the current session and user.
+
+```tsx title='/app/page.tsx'
+const { user } = await validateRequest();
+
+if (!user) {
+  return redirect("/login");
+}
+```
+
+## Create an enhanced Prisma client
+
+You can create an enhanced Prisma client which automatically validates access policies, field validation rules etc., during CRUD operations. For more details, please refer to [ZModel Language](https://zenstack.dev/docs/reference/zmodel-language) reference.
+
+To create such a client,  simply call the `validateRequest` function to get the current user id and then call the `enhance` API to pass the user identity.
+
+```tsx title='/lib/db.ts'
+import { PrismaClient } from '@prisma/client';
+import { validateRequest } from './auth';
+import { enhance } from '@zenstackhq/runtime';
+
+export const prisma = new PrismaClient();
+
+export async function getEnhancedPrisma(): Promise<PrismaClient> {
+  const { user } = await validateRequest();
+  // create a wrapper of Prisma client that enforces access policy,
+  // data validation, and @password, @omit behaviors
+  return enhance(prisma, { user: {id: user?.id!}});
+}
+```
+
+## Expose more user data
+
+By default, Lucia will not expose any database columns to the `User` type. To add a `userName` field to it, use the `getUserAttributes()` option.
+
+```tsx title='/lib/auth.ts'
+declare module "lucia" {
+  interface Register {
+    Lucia: typeof lucia;
+    DatabaseUserAttributes: DatabaseUserAttributes
+  }
+}
+
+interface DatabaseUserAttributes {
+  userName: string;
+}
+
+export const lucia = new Lucia(adapter, {
+  getUserAttributes: (attributes) => {
+    return {
+      userName: attributes.userName
+    };
+  }
+});
+```
+
+Then, you can directly access it from the result returned by `validateRequest` function:
+
+```tsx title='/app/page.tsx'
+export default async function Page() {
+  const { user } = await validateRequest();	
+  return (
+    <>
+      <h1>Hi, {user.userName}!</h1>
+      <p>Your user ID is {user.id}.</p>
+    </>
+  );
+}
+```
diff --git a/docs/quick-start/authentication/next-auth.md b/docs/quick-start/authentication/next-auth.md
new file mode 100644
index 00000000..ab5b026d
--- /dev/null
+++ b/docs/quick-start/authentication/next-auth.md
@@ -0,0 +1,88 @@
+---
+description: Integrating with Auth.js.
+sidebar_position: 1
+sidebar_label: Auth.js (NextAuth)
+---
+
+# Integrating With Auth.js (NextAuth)
+
+[Auth.js](https://authjs.dev/) is a comprehensive framework for implementing authentication in full-stack projects. It offers a pluggable mechanism for configuring identity providers and storage systems.
+
+How authentication is integrated with ZenStack is centered around extracting the current user from the authentication system and using it to create an enhanced `PrismaClient`. This guide briefly introduces the general flow. It uses Next.js (app router) to illustrate, but the same principles apply to other frameworks.
+
+You can find a complete sample project [here](https://github.com/zenstackhq/sample-todo-nextjs-tanstack).
+
+## Preparation
+
+It's assumed that you've already set up a Next.js project following Auth.js's [guide](https://authjs.dev/getting-started), including creating an auth configuration, setting up auth providers, installing a storage adapter, and configuring Next.js middleware.
+
+Follow the [installation guide](https://zenstack.dev/docs/install) to install ZenStack in your project.
+
+Since you're using ZenStack in the project, you'll almost certainly use the [Prisma Adapter](https://authjs.dev/getting-started/adapters/prisma) with Auth.js. Please follow its guide to configure the ZModel schema properly.
+
+## Integrating with ZenStack
+
+Auth.js provides a unified `auth()` backend API (returned by the [`NextAuth`](https://authjs.dev/reference/nextjs#nextauthresult) function) to retrieve the current validated login user. You can pass it to ZenStack's [`enhance()`](../../reference/runtime-api.md#enhance) API to create an enhanced `PrismaClient` that automatically enforces access policies.
+
+The following sections illustrate various ways of using it.
+
+### Using enhanced `PrismaClient` in server components
+
+```ts title='src/app/some-page/page.tsx'
+import { auth } from '~/auth';
+import { prisma } from '~/db';
+import Post from '~/components/Post';
+
+export default function Page() {
+  const authObj = await auth();
+  const db = enhance(prisma, { user: authObj?.user });
+  const posts = await db.post.findMany()
+
+  return (
+    <div>
+      {posts.map((post) => (
+        <Post key={post.id} value={post} />
+      ))}
+    </div>
+  );
+}
+```
+
+### Using enhanced `PrismaClient` in route handler
+
+```ts title='src/app/api/list-posts/route.ts'
+import { auth } from '~/auth';
+import { prisma } from '~/db';
+
+export async function GET(request: Request) {
+  const authObj = await auth();
+  const db = enhance(prisma, { user: authObj?.user });
+  return db.post.findMany()
+}
+```
+
+### Mounting automatic CRUD API
+
+You also can use ZenStack's [Next.js server adapter](../../reference/server-adapters/next.mdx) to serve a complete set of CRUD API automatically.
+
+```ts title='src/app/api/model/[...path]/route.ts'
+import { enhance } from '@zenstackhq/runtime';
+import { NextRequestHandler } from '@zenstackhq/server/next';
+import { auth } from '~/auth';
+import { prisma } from '~/db';
+
+async function getPrisma() {
+  const authObj = await auth();
+  return enhance(prisma, { user: authObj?.user });
+}
+
+const handler = NextRequestHandler({ getPrisma, useAppDir: true });
+
+export {
+  handler as DELETE,
+  handler as GET,
+  handler as PATCH,
+  handler as POST,
+  handler as PUT
+};
+```
diff --git a/docs/quick-start/authentication/supabase.md b/docs/quick-start/authentication/supabase.md
new file mode 100644
index 00000000..7d1be034
--- /dev/null
+++ b/docs/quick-start/authentication/supabase.md
@@ -0,0 +1,104 @@
+---
+description: Integrating with Supabase Auth.
+sidebar_position: 4
+sidebar_label: Supabase Auth
+---
+
+# Integrating With Supabase Auth
+
+[Supabase](https://supabase.com) is a comprehensive Backend-as-a-Service that offers database, authentication, and blob services.
+
+To get access policies to work, ZenStack needs to be connected to the authentication system to get the user's identity. If you use Supabase as your authentication provider, this document will guide you through integrating ZenStack with it. 
+
+### Using Prisma with Supabase
+
+:::info
+This section is only relevant if you're also using Supabase's Database service as the underlying Postgres database of Prisma/ZenStack.
+:::
+
+This section is not directly to integrating authentication, but since ZenStack is based on Prisma, understanding how Prisma and Supabase should be set up appropriately when Supabase Auth is involved is important.
+
+Supabase Auth stores user data in a separate Postgres schema called "auth". Since that schema is managed by Supabase, it's good idea not to directly import it into ZModel and use it in your application, since Supabase team may decide to change table schemas at any time. Instead, a better approach is to define your own `User` model in ZModel, and use database triggers to synchronize user data from Supabase Auth to your `User` table.
+
+**Schema:**
+
+```zmodel title="schema.zmodel"
+model User {
+    id String @id @db.Uuid
+    posts Post[]
+    ... // other fields
+
+    @@allow('read', true)
+}
+
+model Post {
+    id String @id @db.Uuid
+    title String
+    author User @relation(fields: [authorId], references: [id])
+    authorId String @db.Uuid
+
+    @@allow('all', author == auth())
+}
+```
+
+**Database trigger (for synchronizing new users):**
+
+You can check [here](https://supabase.com/docs/guides/database/postgres/triggers) for a general introduction about triggers in Supabase. The following code shows how to use it to synchronize new users:
+
+```sql
+-- inserts a row into public."User"
+create function public.handle_new_user()
+returns trigger
+language plpgsql
+security definer set search_path = public
+as $$
+begin
+  insert into public."User" (id)
+  values (new.id);
+  return new;
+end;
+$$;
+
+-- trigger the function every time a user is created
+create trigger on_auth_user_created
+  after insert on auth.users
+  for each row execute procedure public.handle_new_user();
+```
+
+Similarly you can implement more triggers to synchronize user update and delete. Find more information about this approach [here](https://supabase.com/docs/guides/auth/managing-user-data#using-triggers).
+
+With these in place, your own `User` table will be always in sync with Supabase Auth, and in your application, your other entities will have relations to the `User` table instead of pointing to Supabase Auth's internal tables.
+
+### Creating an enhanced Prisma client
+
+To get ZenStack's access policies to work, you need to create an enhanced `PrismaClient` with a current user context. The way to fetch "current user" on the server side depends on the type of server you're using. Supabase provides a set of [auth helpers](https://supabase.com/docs/guides/auth/auth-helpers) for different frameworks. For example, if you're following [this guide](https://supabase.com/docs/guides/auth/auth-helpers/nextjs) to use Supabase Auth with Next.js (app router), you can create an enhanced `PrismaClient` using `@supabase/auth-helpers-nextjs` like the following:
+
+```ts
+import { enhance } from '@zenstackhq/runtime';
+import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs';
+import { prisma } from '@lib/db';
+
+// create a wrapper of Prisma client that enforces access policy,
+// data validation, and @password, @omit behaviors
+async function getPrisma() {
+    const supabase = createRouteHandlerClient({ cookies });
+    const { data: user } = await supabase.auth.getUser();
+    const uid = user.user?.id;
+    // TODO: if you need to access fields other than just "id" in access policies, 
+    // you can do a database query here to fetch the full user record
+    const contextUser = uid ? { id: uid } : undefined;
+    return enhance(prisma, { user: contextUser });
+}
+```
+
+:::warning
+
+It may be tempting to call Supabase's `getSession` API to get the current user. However, the data returned is not validated on the server side, so it should not be trusted. You can find more details in [this GitHub discussion](https://github.com/orgs/supabase/discussions/23224). Calling `getUser` instead guarantees that the return user identity is validated remotely, although it incurs an extra network request to Supabase. Supabase may resolve the performance issue in the future.
+
+Special thanks to [@bbozzay](https://github.com/bbozzay) for bringing this issue to our attention!
+
+:::
+
+You can then use this enhanced Prisma client for CRUD operations that you desire to be governed by the access policies you defined in your data models.
+
+Next, make sure to read [this guide](/docs/guides/supabase-security) to ensure your database is securely protected from HTTP requests to the supabase API.
diff --git a/docs/quick-start/backend.mdx b/docs/quick-start/backend.mdx
index cd748b55..a2e754bd 100644
--- a/docs/quick-start/backend.mdx
+++ b/docs/quick-start/backend.mdx
@@ -1,10 +1,11 @@
 ---
 description: Steps for using ZenStack only in backend development.
-sidebar_position: 7
+sidebar_position: 8
 ---
 
 import InitTips from './_zenstack-init-tips.md';
 import AccessPolicy from './_access-policy.md';
+import AuthDefault from './_auth_default.md';
 
 # Backend-only Usage
 
@@ -39,7 +40,7 @@ Let's get started 🚀.
 Prisma has a nice sample project for Express.js. Let's use it as a starting point:
 
 ```bash
-npx degit prisma/prisma-examples/typescript/rest-express my-blog-app
+npx try-prisma@latest -t orm/express -n my-blog-app
 cd my-blog-app
 npm install
 ```
@@ -141,7 +142,8 @@ model Post {
     published Boolean @default(false)
     viewCount Int @default(0)
     author User? @relation(fields: [authorId], references: [id])
-    authorId Int?
+    // highlight-next-line
+    authorId Int? @default(auth().id)
 
     // highlight-start
     // author has full access
@@ -153,6 +155,8 @@ model Post {
 }
 ```
 
+<AuthDefault field="authorId" />
+
 <AccessPolicy />
 
 Now flush the changes to the Prisma schema and database schema:
@@ -211,12 +215,7 @@ when creating a new post:
 app.post(`/post`, async (req, res) => {
     const { title, content } = req.body;
     const result = await prisma.post.create({
-        data: {
-            title,
-            content,
-            // highlight-next-line
-            author: { connect: { id: getUserId(req) } },
-        },
+        data: { title, content },
     });
     res.json(result);
 });
@@ -311,6 +310,6 @@ Feel free to play more with updating and deleting posts with different user ids
 authorization code. Pretty cool, isn't it?
 
 If you have trouble following the building process, you can find the final result
-[here](https://github.com/zenstackhq/docs-tutorial-express). For more details about ZenStack, please refer to the [Reference](/docs/category/reference) and [Guides](/docs/category/recipes) parts of the documentation.
+[here](https://github.com/zenstackhq/docs-tutorial-express). For more details about ZenStack, please refer to the [Reference](../category/reference) and [Guides](../category/recipes) parts of the documentation.
 
 Have fun building cool stuff 🚀!
diff --git a/docs/quick-start/nestjs.mdx b/docs/quick-start/nestjs.mdx
new file mode 100644
index 00000000..2712fc36
--- /dev/null
+++ b/docs/quick-start/nestjs.mdx
@@ -0,0 +1,476 @@
+---
+title: NestJS
+description: Steps for using ZenStack in a NestJS application.
+sidebar_position: 7
+---
+
+import InitTips from './_zenstack-init-tips.md';
+import AccessPolicy from './_access-policy.md';
+
+# Get Started With NestJS
+
+NestJS is one of the most popular Node.js/TypeScript backend frameworks for building APIs. ZenStack provides a module for easily integrating with NestJS applications that use Prisma ORM. With the integration, you'll have access to an enhanced Prisma service with built-in access control, while continue enjoying the same Prisma APIs that you're familiar with.
+
+Let's see how it works by creating a simple blogging API. You can find the final build result [here](https://github.com/zenstackhq/docs-tutorial-nestjs).
+
+## Requirements
+
+Our target app should meet the following requirements:
+
+1. Users can create posts for themselves.
+1. Post owner can update their own posts.
+1. Users cannot make changes to posts that do not belong to them.
+1. Published posts can be viewed by everyone.
+
+Let's get started 🚀.
+
+## Prerequisite
+
+1. Make sure you have Node.js 18 or above installed.
+1. Install the [VSCode extension](https://marketplace.visualstudio.com/items?itemName=zenstack.zenstack) for editing data models.
+
+## Building the app
+
+### 1. Create a new NestJS project
+
+```bash
+npx @nestjs/cli@latest new -p npm my-blog-app
+cd my-blog-app
+```
+
+### 2. Set up Prisma
+
+```bash
+npm install -D prisma
+npx prisma init
+```
+
+This will create a Prisma schema under `prisma/schema.prisma`. Replace its content with the following:
+
+```zmodel title="/prisma/schema.prisma"
+datasource db {
+  provider = "sqlite"
+  url = "file:./dev.db"
+}
+
+generator client {
+  provider = "prisma-client-js"
+}
+
+model User {
+  id Int @id() @default(autoincrement())
+  name String
+  posts Post[]
+}
+
+model Post {
+  id Int @id() @default(autoincrement())
+  createdAt DateTime @default(now())
+  updatedAt DateTime @updatedAt()
+  title String
+  published Boolean @default(false)
+  author User? @relation(fields: [authorId], references: [id])
+  authorId Int?
+}
+```
+
+Now, generate `PrismaClient` and push the schema to the database:
+
+```bash
+npx prisma generate
+npx prisma db push
+```
+
+Create a `PrismaService` which will be injected into the API controllers later.
+
+```ts title="/src/prisma.service.ts"
+import { Injectable, OnModuleInit } from '@nestjs/common';
+import { PrismaClient } from '@prisma/client';
+
+@Injectable()
+export class PrismaService extends PrismaClient implements OnModuleInit {
+  async onModuleInit() {
+    await this.$connect();
+  }
+}
+```
+
+Finally, add the `PrismaService` to the app module as a provider:
+
+```ts title="/src/app.module.ts"
+import { PrismaService } from './prisma.service';
+
+@Module({
+  imports: [],
+  controllers: [AppController],
+  providers: [PrismaService],
+})
+export class AppModule {}
+```
+
+### 3. Create CRUD controllers
+
+Now let's create the CRUD API controller for `User` and `Post` models. In a real application, you'll want to have `UserService` and `PostService` to encapsulate database operations. For simplicity, we'll put everything in the controller here.
+
+```ts title="/src/app.controller.ts"
+import { Body, Controller, Get, Param, Post, Put } from '@nestjs/common';
+import { PrismaService } from './prisma.service';
+
+@Controller()
+export class AppController {
+  constructor(private readonly prismaService: PrismaService) {}
+
+  @Post('users')
+  async signup(@Body() userData: { name: string }) {
+    return this.prismaService.user.create({ data: userData });
+  }
+
+  @Get('posts')
+  async getAllPosts() {
+    return this.prismaService.post.findMany();
+  }
+
+  @Post('posts')
+  async createDraft(@Body() postData: { title: string; authorId: number }) {
+    return this.prismaService.post.create({
+      data: postData,
+    });
+  }
+
+  @Put('posts/publish/:id')
+  async publishPost(@Param('id') id: string) {
+    return this.prismaService.post.update({
+      where: { id: Number(id) },
+      data: { published: true },
+    });
+  }
+}
+```
+
+Now, we can start the dev server:
+
+```bash
+npm run start:dev
+```
+
+Let's make a few requests to create a user and two posts:
+
+```bash
+curl -X POST -H "Content-Type: application/json" -d '{"name": "Joey"}' localhost:3000/users
+curl -X POST -H "Content-Type: application/json" -d '{"title": "My first post", "authorId": 1}' localhost:3000/posts
+curl -X POST -H "Content-Type: application/json" -d '{"title": "My second post", "authorId": 1}' localhost:3000/posts
+curl localhost:3000/posts
+```
+
+The result should look like:
+
+```json
+[
+  {
+    "authorId" : 1,
+    "createdAt" : "2024-03-27T18:16:27.289Z",
+    "id" : 1,
+    "published" : false,
+    "title" : "My first post",
+    "updatedAt" : "2024-03-27T18:16:27.289Z"
+  },
+  {
+    "authorId" : 1,
+    "createdAt" : "2024-03-27T18:16:35.302Z",
+    "id" : 2,
+    "published" : false,
+    "title" : "My second post",
+    "updatedAt" : "2024-03-27T18:16:35.302Z"
+  }
+]
+```
+
+### 4. Set up authentication
+
+Our basic CRUD APIs are up and running. However it's not secured yet. Protecting an API involves two parts: authentication (identifying who's making the request) and authorization (deciding if the requester is allowed to perform the operation).
+
+Let's deal with authentication first. NestJS has [detailed documentation](https://docs.nestjs.com/security/authentication) for implementing authentication. In this guide, we'll simply use a fake one that directly passes user ID in a HTTP header. To allow services and controllers to access the authenticatd user, we'll use the [nestjs-cls](https://www.npmjs.com/package/nestjs-cls) package to put the user information into Node.js's [AsyncLocalStorage](https://nodejs.org/api/async_context.html#async_context_class_asynclocalstorage).
+
+First, install the `nestjs-cls` package:
+
+```bash
+npm install nestjs-cls
+```
+
+Then, mount the CLS module:
+
+```ts title="/src/app.module.ts"
+import { ClsModule, ClsService } from 'nestjs-cls';
+
+@Module({
+  imports: [
+    // highlight-start
+    ClsModule.forRoot({
+      global: true,
+      middleware: {
+        mount: true,
+      },
+    }),
+    // highlight-end
+  ],
+  ...
+})
+export class AppModule {}
+```
+
+Now, let's create a NestJS interceptor to extract the user ID from the HTTP header and put it into the CLS context:
+
+```ts title="/src/auth.interceptor.ts"
+import {
+  CallHandler,
+  ExecutionContext,
+  Injectable,
+  NestInterceptor,
+} from '@nestjs/common';
+import { ClsService } from 'nestjs-cls';
+
+@Injectable()
+export class AuthInterceptor implements NestInterceptor {
+  constructor(private readonly cls: ClsService) {}
+
+  async intercept(context: ExecutionContext, next: CallHandler) {
+    const request = context.switchToHttp().getRequest();
+    const userId = request.headers['x-user-id'];
+    if (userId) {
+      this.cls.set('auth', { id: Number(userId) });
+    }
+    return next.handle();
+  }
+}
+```
+
+Then, add the interceptor to `AppModule`:
+
+```ts title="/src/app.module.ts"
+import { APP_INTERCEPTOR } from '@nestjs/core';
+import { AuthInterceptor } from './auth.interceptor';
+
+@Module({
+  ...
+
+  providers: [
+    PrismaService,
+    // highlight-start
+    {
+      provide: APP_INTERCEPTOR,
+      useClass: AuthInterceptor,
+    },
+    // highlight-end
+  ],
+})
+export class AppModule {}
+```
+
+Now we will be able to inject the `ClsService` into the controllers and services as needed to fetch the current authenticated user.
+
+### 5. Set up ZenStack
+
+ZenStack allows you to define access policies inside your data schema. Let's install it first.
+
+```bash
+npx zenstack@latest init
+```
+
+<InitTips />
+
+Add the following access policies to the `User` and `Post` models:
+
+```zmodel title='/schema.zmodel'
+model User {
+  id Int @id() @default(autoincrement())
+  name String
+  posts Post[]
+
+  // highlight-start
+  // anyone can sign up, and user profiles are public
+  @@allow('create,read', true)
+
+  // users have full access to their own profile
+  @@allow('all', auth() == this)
+  // highlight-end
+}
+
+model Post {
+  id Int @id() @default(autoincrement())
+  createdAt DateTime @default(now())
+  updatedAt DateTime @updatedAt()
+  title String
+  published Boolean @default(false)
+  author User? @relation(fields: [authorId], references: [id])
+  authorId Int?
+
+  // highlight-start
+  // author has full access
+  @@allow('all', auth() == author)
+
+  // published posts are readable to all
+  @@allow('read', published)
+  // highlight-end
+}
+```
+
+<AccessPolicy />
+
+Now regenerate `PrismaClient` and other supporting files needed by ZenStack:
+
+```bash
+npx zenstack generate
+```
+
+### 6. Use ZenStack in the controller
+
+One of the main things ZenStack does is to create an "enhanced" `PrismaClient` that automatically enforces access policies. To do that, simply call the `enhance` API with an existing client and a user context:
+
+```ts
+const enhancedPrisma = enhance(prisma, { user: ... });
+```
+
+In a NestJS application, since everything is a dependency injection, we need to create the enhanced client in a DI-compatible way. Fortunately, ZenStack offers a module to make such integration easy. First, install the server adapter package:
+
+```bash
+npm install @zenstackhq/server@latest
+```
+
+Then, register the `ZenStackModule` onto the app module:
+
+```ts title="/src/app.module.ts"
+import { ZenStackModule } from '@zenstackhq/server/nestjs';
+import { enhance } from '@zenstackhq/runtime';
+
+@Module({
+  imports: [
+    ...
+
+    // highlight-start
+    ZenStackModule.registerAsync({
+      useFactory: (prisma: PrismaService, cls: ClsService) => {
+        return {
+          getEnhancedPrisma: () => enhance(prisma, { user: cls.get('auth') }),
+        };
+      },
+      inject: [PrismaService, ClsService],
+      extraProviders: [PrismaService],
+    }),
+    // highlight-end
+  ],
+
+  ...
+})
+export class AppModule {}
+```
+
+Note that the `ZenStackModule` registration is done with a factory function that returns a config used for creating an enhanced prisma service. The config contains a callback function where you should create and return an enhanced `PrismaClient`. It'll be called each time a Prisma method is invoked. It's important to fetch the auth data inside the callback so that it correctly returns the data bound to the current request context.
+
+:::info
+The enhanced clients are lightweighted Javascript proxies. They are cheap to create and don't incur new connections to the database.
+:::
+
+The `ZenStackModule` provides an enhanced `PrismaService` with the token name `ENHANCED_PRISMA`. You can use both the regular `PrismaService` and enhanced one in your services and controllers. To use the regular prisma client, simply inject the `PrismaService` as usual. To use the enhanced one, inject it with token name `ENHANCED_PRISMA`.
+
+Let's change our controller to use the enhanced prisma service:
+
+```ts title="/src/app.controller.ts"
+import { ENHANCED_PRISMA } from '@zenstackhq/server/nestjs';
+
+@Controller()
+export class AppController {
+  constructor(
+    // highlight-next-line
+    @Inject(ENHANCED_PRISMA) private readonly prismaService: PrismaService,
+  ) {}
+
+  ...
+}
+```
+
+### 7. Test the secured API
+
+Now, restart the dev server, and let's make a few requests to see if the access policies are enforced.
+
+- Listing posts without a user identity should return an empty array:
+
+    ```bash
+    curl localhost:3000/posts
+    ```
+
+    ```
+    []
+    ```
+
+- Listing posts with a user identity should return all posts owned by the user:
+
+    ```bash
+    curl -H "x-user-id:1" localhost:3000/posts
+    ```
+
+    ```json
+    [
+      {
+        "authorId" : 1,
+        "createdAt" : "2024-03-27T18:16:27.289Z",
+        "id" : 1,
+        "published" : false,
+        "title" : "My first post",
+        "updatedAt" : "2024-03-27T18:16:27.289Z"
+      },
+      {
+        "authorId" : 1,
+        "createdAt" : "2024-03-27T18:16:35.302Z",
+        "id" : 2,
+        "published" : false,
+        "title" : "My second post",
+        "updatedAt" : "2024-03-27T18:16:35.302Z"
+      }
+    ]
+    ```
+
+- Published posts are readable to all:
+
+    First, publish a post with its owner's identity.
+
+    ```
+    curl -X PUT -H "x-user-id:1" localhost:3000/posts/publish/1
+    ```
+
+    ```json
+    {
+      "authorId" : 1,
+      "createdAt" : "2024-03-27T18:16:27.289Z",
+      "id" : 1,
+      "published" : true,
+      "title" : "My first post",
+      "updatedAt" : "2024-03-27T18:42:19.043Z"
+    }
+    ```
+
+    Then, list all posts without a user identity:
+
+    ```bash
+    curl localhost:3000/posts
+    ```
+
+    ```json
+    {
+      "authorId" : 1,
+      "createdAt" : "2024-03-27T18:16:27.289Z",
+      "id" : 1,
+      "published" : true,
+      "title" : "My first post",
+      "updatedAt" : "2024-03-27T18:42:19.043Z"
+    }
+    ```
+
+## Wrap up
+
+🎉 Congratulations! You've made a simple but secure blogging API without writing any
+authorization code. Pretty cool, isn't it?
+
+If you have trouble following the building process, you can find the final result
+[here](https://github.com/zenstackhq/docs-tutorial-nestjs). For more details about ZenStack, please refer to the [Reference](../category/reference) and [Guides](../category/recipes) parts of the documentation.
+
+Have fun building cool stuff 🚀!
diff --git a/docs/quick-start/nextjs-app-router.mdx b/docs/quick-start/nextjs-app-router.mdx
new file mode 100644
index 00000000..3db51cbe
--- /dev/null
+++ b/docs/quick-start/nextjs-app-router.mdx
@@ -0,0 +1,712 @@
+---
+title: Next.js (app router)
+description: Step-by-step guide for building a blogging app with Next.js (app router).
+sidebar_position: 1
+---
+
+import Requirements from './_requirements.md';
+import Prerequisite from './_prerequisite.md';
+import InitTips from './_zenstack-init-tips.md';
+import PasswordOmit from './_password-omit.md';
+import AccessPolicy from './_access-policy.md';
+import AuthDefault from './_auth_default.md';
+
+# Get Started With Next.js (app router)
+
+Let's have some fun by creating a simple blogging app. You'll see how effortless it is to have a secure backend service without actually coding it.
+
+You can find the final build result [here](https://github.com/zenstackhq/docs-tutorial-nextjs-app-dir).
+
+## Requirements
+
+<Requirements />
+
+## Prerequisite
+
+<Prerequisite />
+
+## Building the app
+
+### 1. Create a new project
+
+The easiest way to create a Next.js project with boilerplates is with `create-t3-app`. Run the following command to create a new project with Prisma, NextAuth and TailwindCSS.
+
+```bash
+npx create-t3-app@7.39.3 --prisma --nextAuth --tailwind --appRouter --CI my-blog-app
+cd my-blog-app
+```
+
+Remove `DISCORD_CLIENT_ID` and `DISCORD_CLIENT_SECRET` related code from `src/env.js`, since we're not going to use Discord for authentication. After that, start the dev server:
+
+```bash
+npm run dev
+```
+
+If everything works, you should have a running Next.js app at [http://localhost:3000](http://localhost:3000).
+
+<div align="center">
+    <img src="/img/t3app.png" width="800" />
+</div>
+
+### 2. Initialize the project for ZenStack
+
+Let's run the `zenstack` CLI to prepare your project for using ZenStack.
+
+```bash
+npx zenstack@latest init
+```
+
+<InitTips />
+
+### 3. Preparing the User model for authentication
+
+First, in `schema.zmodel`, make a few changes to the `User` model:
+
+```zmodel title='/schema.zmodel'
+model User {
+  id            String    @id @default(cuid())
+  name          String?
+  email         String?   @unique
+  emailVerified DateTime?
+  // highlight-next-line
+  password      String @password @omit
+  image         String?
+  accounts      Account[]
+  sessions      Session[]
+  posts         Post[]
+
+  // highlight-start
+  // everyone can signup, and user profile is also publicly readable
+  @@allow('create,read', true)
+
+  // only the user can update or delete their own profile
+  @@allow('update,delete', auth() == this)
+  // highlight-end
+}
+```
+
+For simplicity, we'll use username/password-based authentication in this project. In the code above,
+we added a `password` field to support it, together with two access policy rules to control the
+permissions of this model.
+
+<PasswordOmit />
+
+<AccessPolicy />
+
+Now run `zenstack generate` and `prisma db push` to flush the changes to the Prisma schema and database:
+
+```bash
+npx zenstack generate && npx prisma db push
+```
+
+### 4. Configure NextAuth to use credential-based auth
+
+First, we need to install type definitions for bcryptjs
+```bash
+npm install --save @types/bcryptjs
+```
+
+Now let's update `/src/server/auth/config.ts` to the content below to
+use credentials auth and JWT-based session:
+
+```ts title='/src/server/auth/config.ts'
+import { PrismaAdapter } from '@auth/prisma-adapter';
+import { type PrismaClient } from '@prisma/client';
+import { type DefaultSession, type NextAuthConfig } from 'next-auth';
+import { compare } from 'bcryptjs';
+import CredentialsProvider from 'next-auth/providers/credentials';
+import { db } from '~/server/db';
+
+/**
+ * Module augmentation for `next-auth` types. Allows us to add custom properties to the `session`
+ * object and keep type safety.
+ *
+ * @see https://next-auth.js.org/getting-started/typescript#module-augmentation
+ */
+declare module 'next-auth' {
+    interface Session extends DefaultSession {
+        user: {
+            id: string;
+        };
+    }
+}
+
+/**
+ * Options for NextAuth.js used to configure adapters, providers, callbacks, etc.
+ *
+ * @see https://next-auth.js.org/configuration/options
+ */
+export const authConfig = {
+    session: {
+        strategy: 'jwt',
+    },
+    providers: [
+        CredentialsProvider({
+            credentials: {
+                email: { type: 'email' },
+                password: { type: 'password' },
+            },
+            authorize: authorize(db),
+        }),
+    ],
+    adapter: PrismaAdapter(db),
+    callbacks: {
+        session: ({ session, token }) => {
+            if (session.user) {
+                // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+                session.user.id = token.sub!;
+            }
+            return session;
+        },
+    },
+} satisfies NextAuthConfig;
+
+function authorize(prisma: PrismaClient) {
+    return async (credentials: Partial<Record<'email' | 'password', unknown>> | undefined) => {
+        if (!credentials) {
+            throw new Error('Missing credentials');
+        }
+
+        if (typeof credentials.email !== 'string') {
+            throw new Error('"email" is required in credentials');
+        }
+
+        if (typeof credentials.password !== 'string') {
+            throw new Error('"password" is required in credentials');
+        }
+        const maybeUser = await prisma.user.findFirst({
+            where: { email: credentials.email },
+            select: { id: true, email: true, password: true },
+        });
+        if (!maybeUser?.password) return null;
+        // verify the input password with stored hash
+        const isValid = await compare(credentials.password, maybeUser.password);
+
+        if (!isValid) return null;
+        return { id: maybeUser.id, email: maybeUser.email };
+    };
+}
+```
+
+### 5. Mount CRUD service & generate hooks
+
+ZenStack has built-in support for Next.js and can provide database CRUD services
+automagically, so you don't need to write it yourself.
+
+First install the `@zenstackhq/server`, `@tanstack/react-query`, and `@zenstackhq/tanstack-query` packages:
+
+```bash
+npm install @zenstackhq/server@latest @zenstackhq/tanstack-query@latest @tanstack/react-query
+```
+
+Let's mount it to the `/api/model/[...path]` endpoint. Create a `/src/app/api/model/[...path]/route.ts`
+file and fill in the content below:
+
+```ts title='/src/app/api/model/[...path]/route.ts'
+import { enhance } from '@zenstackhq/runtime';
+import { NextRequestHandler } from '@zenstackhq/server/next';
+import { db } from '~/server/db';
+import { auth } from '~/server/auth';
+
+// create an enhanced Prisma client with user context
+async function getPrisma() {
+    const authObj = await auth();
+    return enhance(db, { user: authObj?.user });
+}
+
+const handler = NextRequestHandler({ getPrisma, useAppDir: true });
+
+export { handler as DELETE, handler as GET, handler as PATCH, handler as POST, handler as PUT };
+
+```
+
+The `/api/model` route is now ready to access database query and mutation requests.
+However, manually calling the service will be tedious. Fortunately, ZenStack can
+automatically generate React data query hooks for you.
+
+Let's enable it by adding the following snippet at the top level to `schema.zmodel`:
+
+```zmodel title='/schema.zmodel'
+plugin hooks {
+  provider = '@zenstackhq/tanstack-query'
+  target = 'react'
+  version = 'v5'
+  output = "./src/lib/hooks"
+}
+```
+
+Now run `zenstack generate` again; you'll find the hooks generated under `/src/lib/hooks` folder:
+
+```bash
+npx zenstack generate
+```
+
+Now we're ready to implement the signup/signin flow.
+
+### 6. Set up context providers
+
+NextAuth and React Query require context providers to be set up. Create a `/src/app/providers.tsx` file with the following content:
+
+```tsx title='/src/app/providers.tsx'
+"use client";
+
+import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
+import { SessionProvider } from "next-auth/react";
+import type { ReactNode } from "react";
+
+const queryClient = new QueryClient();
+
+export default function Providers({ children }: { children: ReactNode }) {
+  return (
+    <QueryClientProvider client={queryClient}>
+      <SessionProvider>{children}</SessionProvider>
+    </QueryClientProvider>
+  );
+}
+```
+
+Then, update `/src/app/layout.tsx` to install the query client provider:
+
+```ts title='/src/app/layout.tsx'
+"use client";
+
+import "~/styles/globals.css";
+
+import { Inter } from "next/font/google";
+import Providers from "./providers";
+
+const inter = Inter({
+  subsets: ["latin"],
+  variable: "--font-sans",
+});
+
+export default function RootLayout({
+  children,
+}: {
+  children: React.ReactNode;
+}) {
+  return (
+    <html lang="en">
+      <body className={`font-sans ${inter.variable}`}>
+        <Providers>{children}</Providers>
+      </body>
+    </html>
+  );
+}
+```
+
+:::info
+We've only done the minimum setup to get React Query to work with NextJS's app router. For more more in-depth background and setup, please refer to React Query's official documentation about [Advanced Server Rendering](https://tanstack.com/query/latest/docs/framework/react/guides/advanced-ssr).
+:::
+
+### 7. Implement Signup/Signin
+
+Now let's implement the signup/signin pages. First, create a new page `/src/app/signup/page.tsx`:
+
+```tsx title='/src/app/signup/page.tsx'
+"use client";
+
+import type { NextPage } from "next";
+import { signIn } from "next-auth/react";
+import { useRouter } from "next/navigation";
+import { useState, type FormEvent } from "react";
+import { useCreateUser } from "~/lib/hooks";
+
+const Signup: NextPage = () => {
+  const [email, setEmail] = useState("");
+  const [password, setPassword] = useState("");
+  const { mutateAsync: signup } = useCreateUser();
+  const router = useRouter();
+
+  async function onSignup(e: FormEvent) {
+    e.preventDefault();
+    try {
+      await signup({ data: { email, password } });
+      // eslint-disable-next-line @typescript-eslint/no-explicit-any
+    } catch (err: any) {
+      console.error(err);
+      // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
+      if (err.info?.prisma && err.info?.code === "P2002") {
+        // P2002 is Prisma's error code for unique constraint violations
+        alert("User already exists");
+      } else {
+        alert("An unknown error occurred");
+      }
+      return;
+    }
+
+    // signin to create a session
+    await signIn("credentials", { redirect: false, email, password });
+    router.push("/");
+  }
+
+  return (
+    <div className="flex min-h-screen flex-col items-center justify-center bg-gradient-to-b from-[#2e026d] to-[#15162c]">
+      <h1 className="text-5xl font-extrabold text-white">Sign up</h1>
+      <form
+        className="mt-16 flex flex-col gap-8 text-2xl"
+        onSubmit={(e) => void onSignup(e)}
+      >
+        <div>
+          <label htmlFor="email" className="inline-block w-32 text-white">
+            Email
+          </label>
+          <input
+            id="email"
+            type="email"
+            value={email}
+            onChange={(e) => setEmail(e.currentTarget.value)}
+            className="ml-4 w-72 rounded border p-2 bg-white"
+          />
+        </div>
+        <div>
+          <label htmlFor="password" className="inline-block w-32 text-white ">
+            Password
+          </label>
+          <input
+            id="password"
+            type="password"
+            value={password}
+            onChange={(e) => setPassword(e.currentTarget.value)}
+            className="ml-4 w-72 rounded border p-2 bg-white"
+          />
+        </div>
+        <input
+          type="submit"
+          value="Create account"
+          className="cursor-pointer rounded border border-gray-500 py-4 text-white"
+        />
+      </form>
+    </div>
+  );
+};
+
+export default Signup;
+```
+
+In the code above, we used the auto-generated `useCreateUser` hooks to create new `User` entities.
+
+:::tip
+
+1. The services backing the hooks are governed by the access policies we defined. Here the `create`
+   call can succeed because we explicitly allowed it in the `User` model. By default, all operations
+   are forbidden.
+1. The `password` field is automatically hashed. You can confirm it using a sqlite inspection tool
+   to browse the `prisma/db.sqlite` database file.
+
+:::
+
+Try visiting [http://localhost:3000/signup](http://localhost:3000/signup) and creating a new user.
+It should look like this:
+
+<div align="center">
+    <img src="/img/tutorial-signup-form.png" width="800" />
+</div>
+
+Similarly, create the signin page `/src/app/signin/page.tsx`:
+
+```tsx title='/src/app/signin/page.tsx'
+"use client";
+
+import type { NextPage } from "next";
+import { signIn } from "next-auth/react";
+import { useRouter } from "next/navigation";
+import { useState, type FormEvent } from "react";
+
+const Signin: NextPage = () => {
+  const [email, setEmail] = useState("");
+  const [password, setPassword] = useState("");
+  const router = useRouter();
+
+  async function onSignin(e: FormEvent) {
+    e.preventDefault();
+
+    const result = await signIn("credentials", {
+      redirect: false,
+      email,
+      password,
+    });
+
+    if (result?.ok) {
+      router.push("/");
+    } else {
+      alert("Signin failed");
+    }
+  }
+
+  return (
+    <div className="flex min-h-screen flex-col items-center justify-center bg-gradient-to-b from-[#2e026d] to-[#15162c]">
+      <h1 className="text-5xl font-extrabold text-white">Login</h1>
+      <form
+        className="mt-16 flex flex-col gap-8 text-2xl"
+        onSubmit={(e) => void onSignin(e)}
+      >
+        <div>
+          <label htmlFor="email" className="inline-block w-32  text-white">
+            Email
+          </label>
+          <input
+            id="email"
+            type="email"
+            value={email}
+            onChange={(e) => setEmail(e.currentTarget.value)}
+            className="ml-4 w-72 rounded border p-2 bg-white"
+          />
+        </div>
+        <div>
+          <label htmlFor="password" className="inline-block w-32  text-white">
+            Password
+          </label>
+          <input
+            id="password"
+            type="password"
+            value={password}
+            onChange={(e) => setPassword(e.currentTarget.value)}
+            className="ml-4 w-72 rounded border p-2 bg-white"
+          />
+        </div>
+        <input
+          type="submit"
+          value="Sign me in"
+          className="cursor-pointer rounded border border-gray-500 py-4 text-white"
+        />
+      </form>
+    </div>
+  );
+};
+
+export default Signin;
+```
+
+### 8. Prepare the Post model
+
+Now let's update the `Post` model to add access policies.
+
+```zmodel title='/schema.zmodel'
+model Post {
+  id Int @id @default(autoincrement())
+  name String
+  createdAt DateTime @default(now())
+  updatedAt DateTime @updatedAt
+  // highlight-next-line
+  published Boolean @default(false)
+
+  createdBy User @relation(fields: [createdById], references: [id])
+  // highlight-next-line
+  createdById String @default(auth().id)
+
+  @@index([name])
+
+  // highlight-start
+  // author has full access
+  @@allow('all', auth() == createdBy)
+
+  // logged-in users can view published posts
+  @@allow('read', auth() != null && published)
+  // highlight-end
+}
+```
+
+<AuthDefault field="createdById" />
+
+Don't forget to regenerate and push schema changes to the database:
+
+```bash
+npx zenstack generate && npx prisma db push
+```
+
+### 9. Build up the home page
+
+Now let's replace `/src/app/page.tsx` with the content below and use it for viewing and managing posts.
+
+```tsx title='/src/app/page.tsx'
+"use client";
+
+import type { Post } from "@prisma/client";
+import { type NextPage } from "next";
+import { signOut, useSession } from "next-auth/react";
+import Link from "next/link";
+import { useRouter } from "next/navigation";
+import {
+  useFindManyPost,
+  useCreatePost,
+  useUpdatePost,
+  useDeletePost,
+} from "../lib/hooks";
+
+type AuthUser = { id: string; email?: string | null };
+
+const Welcome = ({ user }: { user: AuthUser }) => {
+  const router = useRouter();
+  async function onSignout() {
+    await signOut({ redirect: false });
+    router.push("/signin");
+  }
+  return (
+    <div className="flex gap-4">
+      <h3 className="text-lg">Welcome back, {user?.email}</h3>
+      <button
+        className="text-gray-300 underline"
+        onClick={() => void onSignout()}
+      >
+        Signout
+      </button>
+    </div>
+  );
+};
+
+const SigninSignup = () => {
+  return (
+    <div className="flex gap-4 text-2xl">
+      <Link href="/signin" className="rounded-lg border px-4 py-2">
+        Signin
+      </Link>
+      <Link href="/signup" className="rounded-lg border px-4 py-2">
+        Signup
+      </Link>
+    </div>
+  );
+};
+
+const Posts = ({ user }: { user: AuthUser }) => {
+  // Post crud hooks
+  const { mutateAsync: createPost } = useCreatePost();
+  const { mutateAsync: updatePost } = useUpdatePost();
+  const { mutateAsync: deletePost } = useDeletePost();
+
+  // list all posts that're visible to the current user, together with their authors
+  const { data: posts } = useFindManyPost({
+    include: { createdBy: true },
+    orderBy: { createdAt: "desc" },
+  });
+
+  async function onCreatePost() {
+    const name = prompt("Enter post name");
+    if (name) {
+      await createPost({ data: { name } });
+    }
+  }
+
+  async function onTogglePublished(post: Post) {
+    await updatePost({
+      where: { id: post.id },
+      data: { published: !post.published },
+    });
+  }
+
+  async function onDelete(post: Post) {
+    await deletePost({ where: { id: post.id } });
+  }
+
+  return (
+    <div className="container flex flex-col text-white">
+      <button
+        className="rounded border border-white p-2 text-lg"
+        onClick={() => void onCreatePost()}
+      >
+        + Create Post
+      </button>
+
+      <ul className="container mt-8 flex flex-col gap-2">
+        {posts?.map((post) => (
+          <li key={post.id} className="flex items-end justify-between gap-4">
+            <p className={`text-2xl ${!post.published ? "text-gray-400" : ""}`}>
+              {post.name}
+              <span className="text-lg"> by {post.createdBy.email}</span>
+            </p>
+            <div className="flex w-32 justify-end gap-1 text-left">
+              <button
+                className="underline"
+                onClick={() => void onTogglePublished(post)}
+              >
+                {post.published ? "Unpublish" : "Publish"}
+              </button>
+              <button className="underline" onClick={() => void onDelete(post)}>
+                Delete
+              </button>
+            </div>
+          </li>
+        ))}
+      </ul>
+    </div>
+  );
+};
+
+const Home: NextPage = () => {
+  const { data: session, status } = useSession();
+
+  if (status === "loading") return <p>Loading ...</p>;
+
+  return (
+    <main className="flex min-h-screen flex-col items-center justify-center bg-gradient-to-b from-[#2e026d] to-[#15162c]">
+      <div className="container flex flex-col items-center justify-center gap-12 px-4 py-16 text-white">
+        <h1 className="text-5xl font-extrabold">My Awesome Blog</h1>
+
+        {session?.user ? (
+          // welcome & blog posts
+          <div className="flex flex-col">
+            <Welcome user={session.user} />
+            <section className="mt-10">
+              <Posts user={session.user} />
+            </section>
+          </div>
+        ) : (
+          // if not logged in
+          <SigninSignup />
+        )}
+      </div>
+    </main>
+  );
+};
+
+export default Home;
+```
+
+## Verifying the result
+
+Restart the dev server and try creating a few posts. You should see something like the following:
+
+<div align="center">
+    <img src="/img/tutorial-blog-posts.png" width="800" />
+</div>
+
+The code looks a bit long because we tucked all UI components directly into the page.
+As you can see, querying and mutating `Post` entities are fairly straightforward,
+the generated hooks. When mutation happens, e.g. a new post is created, data refetching
+is also triggered automatically.
+
+Try opening an incognito browser window and signing up for a separate account. You should
+find that the published posts are visible, as we specified with access policies.
+
+<div align="center">
+    <img src="/img/tutorial-blog-posts-readable.png" width="800" />
+</div>
+
+Since we haven't hidden "Unpublish" and "Delete" buttons for posts not owned by the current
+user, you can still click them even for posts not owned to you, but it will end up with a
+403 error:
+
+![network error](/img/tutorial-post-update-denied.png)
+
+```json
+{
+    "prisma": true,
+    "rejectedByPolicy": true,
+    "code": "P2004",
+    "message": "denied by policy: post entities failed 'update' check, 1 entities failed policy check"
+}
+```
+
+You can catch the error and render a nice message to the user.
+
+## Wrap up
+
+🎉 Congratulations! You've made a simple blogging app without writing a single line of
+backend code. Pretty cool, isn't it?
+
+If you have trouble following the building process, you can find the final result
+[here](https://github.com/zenstackhq/docs-tutorial-nextjs-app-dir). For more details about ZenStack,
+please refer to the [Reference](../category/reference) and [Guides](../category/recipes)
+parts of the documentation.
+
+Have fun building cool stuff 🚀!
diff --git a/docs/quick-start/nextjs.mdx b/docs/quick-start/nextjs.mdx
index 27a54481..8d357f18 100644
--- a/docs/quick-start/nextjs.mdx
+++ b/docs/quick-start/nextjs.mdx
@@ -1,7 +1,7 @@
 ---
-title: Next.js
-description: Step-by-step guide for building a blogging app with Next.js.
-sidebar_position: 1
+title: Next.js (pages router)
+description: Step-by-step guide for building a blogging app with Next.js (pages router).
+sidebar_position: 2
 ---
 
 import Requirements from './_requirements.md';
@@ -9,6 +9,7 @@ import Prerequisite from './_prerequisite.md';
 import InitTips from './_zenstack-init-tips.md';
 import PasswordOmit from './_password-omit.md';
 import AccessPolicy from './_access-policy.md';
+import AuthDefault from './_auth_default.md';
 
 # Get Started With Next.js
 
@@ -31,8 +32,13 @@ You can find the final build result [here](https://github.com/zenstackhq/docs-tu
 The easiest way to create a Next.js project with boilerplates is with `create-t3-app`. Run the following command to create a new project with Prisma, NextAuth and TailwindCSS.
 
 ```bash
-npx create-t3-app@latest --prisma --nextAuth --tailwind --CI my-blog-app
+npx create-t3-app@7.39.3 --prisma --nextAuth --tailwind --CI my-blog-app
 cd my-blog-app
+```
+
+Remove `DISCORD_CLIENT_ID` and `DISCORD_CLIENT_SECRET` related code from `src/env.js`, since we're not going to use Discord for authentication. After that, start the dev server:
+
+```bash
 npm run dev
 ```
 
@@ -67,6 +73,7 @@ model User {
   image         String?
   accounts      Account[]
   sessions      Session[]
+  posts         Post[]
 
   // highlight-start
   // everyone can signup, and user profile is also publicly readable
@@ -94,53 +101,45 @@ npx zenstack generate && npx prisma db push
 
 ### 4. Configure NextAuth to use credential-based auth
 
-Now let's update `/src/server/auth.ts` to the content below to
+First, we need to install type definitions for bcryptjs
+```bash
+npm install --save @types/bcryptjs
+```
+
+Now let's update `/src/server/auth/config.ts` to the content below to
 use credentials auth and JWT-based session:
 
-```ts title='/src/server/auth.ts'
-import { PrismaAdapter } from '@next-auth/prisma-adapter';
-import type { PrismaClient } from '@prisma/client';
+```ts title='/src/server/auth/config.ts'
+import { PrismaAdapter } from '@auth/prisma-adapter';
+import { type PrismaClient } from '@prisma/client';
+import { type DefaultSession, type NextAuthConfig } from 'next-auth';
 import { compare } from 'bcryptjs';
-import type { GetServerSidePropsContext } from 'next';
-import NextAuth, { getServerSession, type DefaultSession, type NextAuthOptions } from 'next-auth';
 import CredentialsProvider from 'next-auth/providers/credentials';
-import { db } from './db';
+import { db } from '~/server/db';
+
 /**
- * Module augmentation for `next-auth` types.
- * Allows us to add custom properties to the `session` object and keep type
- * safety.
+ * Module augmentation for `next-auth` types. Allows us to add custom properties to the `session`
+ * object and keep type safety.
  *
  * @see https://next-auth.js.org/getting-started/typescript#module-augmentation
- **/
+ */
 declare module 'next-auth' {
     interface Session extends DefaultSession {
         user: {
             id: string;
-        } & DefaultSession['user'];
+        };
     }
 }
 
 /**
- * Options for NextAuth.js used to configure adapters, providers, callbacks,
- * etc.
+ * Options for NextAuth.js used to configure adapters, providers, callbacks, etc.
  *
  * @see https://next-auth.js.org/configuration/options
- **/
-export const authOptions: NextAuthOptions = {
+ */
+export const authConfig = {
     session: {
         strategy: 'jwt',
     },
-    // Include user.id on session
-    callbacks: {
-        session({ session, token }) {
-            if (session.user) {
-                session.user.id = token.sub!;
-            }
-            return session;
-        },
-    },
-    // Configure one or more authentication providers
-    adapter: PrismaAdapter(db),
     providers: [
         CredentialsProvider({
             credentials: {
@@ -150,46 +149,43 @@ export const authOptions: NextAuthOptions = {
             authorize: authorize(db),
         }),
     ],
-};
+    adapter: PrismaAdapter(db),
+    callbacks: {
+        session: ({ session, token }) => {
+            if (session.user) {
+                // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+                session.user.id = token.sub!;
+            }
+            return session;
+        },
+    },
+} satisfies NextAuthConfig;
 
 function authorize(prisma: PrismaClient) {
-    return async (credentials: Record<'email' | 'password', string> | undefined) => {
-        if (!credentials) throw new Error('Missing credentials');
-        if (!credentials.email) throw new Error('"email" is required in credentials');
-        if (!credentials.password) throw new Error('"password" is required in credentials');
+    return async (credentials: Partial<Record<'email' | 'password', unknown>> | undefined) => {
+        if (!credentials) {
+            throw new Error('Missing credentials');
+        }
+
+        if (typeof credentials.email !== 'string') {
+            throw new Error('"email" is required in credentials');
+        }
+
+        if (typeof credentials.password !== 'string') {
+            throw new Error('"password" is required in credentials');
+        }
         const maybeUser = await prisma.user.findFirst({
             where: { email: credentials.email },
             select: { id: true, email: true, password: true },
         });
-        if (!maybeUser || !maybeUser.password) return null;
+        if (!maybeUser?.password) return null;
         // verify the input password with stored hash
         const isValid = await compare(credentials.password, maybeUser.password);
+
         if (!isValid) return null;
         return { id: maybeUser.id, email: maybeUser.email };
     };
 }
-
-/**
- * Wrapper for `getServerSession` so that you don't need to import the
- * `authOptions` in every file.
- *
- * @see https://next-auth.js.org/configuration/nextjs
- **/
-export const getServerAuthSession = (ctx: {
-    req: GetServerSidePropsContext['req'];
-    res: GetServerSidePropsContext['res'];
-}) => {
-    return getServerSession(ctx.req, ctx.res, authOptions);
-};
-
-export default NextAuth(authOptions);
-```
-
-Finally, add a `NEXTAUTH_SECRET` environment variable in .env file and set it to an arbitrary
-value (use a complex secret in production and don't check it into git):
-
-```plain title='/.env'
-  NEXTAUTH_SECRET=abc123
 ```
 
 ### 5. Mount CRUD service & generate hooks
@@ -197,31 +193,30 @@ value (use a complex secret in production and don't check it into git):
 ZenStack has built-in support for Next.js and can provide database CRUD services
 automagically, so you don't need to write it yourself.
 
-First install the `@zenstackhq/server` and `@zenstackhq/swr` packages:
+First install the `@zenstackhq/server`, `@tanstack/react-query`, and `@zenstackhq/tanstack-query` packages:
 
 ```bash
-npm install @zenstackhq/server swr
-npm install -D  @zenstackhq/swr
+npm install @zenstackhq/server@latest @zenstackhq/tanstack-query@latest @tanstack/react-query
 ```
 
 Let's mount it to the `/api/model/[...path]` endpoint. Create a `/src/pages/api/model/[...path].ts`
 file and fill in the content below:
 
 ```ts title='/src/pages/api/model/[...path].ts'
-import { NextRequestHandler } from '@zenstackhq/server/next';
 import { enhance } from '@zenstackhq/runtime';
-import type { NextApiRequest, NextApiResponse } from 'next';
-import { getServerAuthSession } from '../../../server/auth';
-import { prisma } from '../../../server/db';
-
-async function getPrisma(req: NextApiRequest, res: NextApiResponse) {
-    const session = await getServerAuthSession({ req, res });
-    // create a wrapper of Prisma client that enforces access policy,
-    // data validation, and @password, @omit behaviors
-    return enhance(prisma, { user: session?.user });
+import { NextRequestHandler } from '@zenstackhq/server/next';
+import { db } from '~/server/db';
+import { auth } from '~/server/auth';
+
+// create an enhanced Prisma client with user context
+async function getPrisma() {
+    const authObj = await auth();
+    return enhance(db, { user: authObj?.user });
 }
 
-export default NextRequestHandler({ getPrisma });
+const handler = NextRequestHandler({ getPrisma, useAppDir: true });
+
+export { handler as DELETE, handler as GET, handler as PATCH, handler as POST, handler as PUT };
 ```
 
 The `/api/model` route is now ready to access database query and mutation requests.
@@ -232,7 +227,9 @@ Let's enable it by adding the following snippet at the top level to `schema.zmod
 
 ```zmodel
 plugin hooks {
-  provider = '@zenstackhq/swr'
+  provider = '@zenstackhq/tanstack-query'
+  target = 'react'
+  version = 'v5'
   output = "./src/lib/hooks"
 }
 ```
@@ -247,84 +244,116 @@ Now we're ready to implement the signup/signin flow.
 
 ### 6. Implement Signup/Signin
 
+First, NextAuth and React Query require context providers to be set up. Let's add it to `/src/pages/_app.tsx`:
+
+```tsx title='/src/pages/_app.tsx'
+import { type AppType } from "next/app";
+import { type Session } from "next-auth";
+import { SessionProvider } from "next-auth/react";
+import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
+
+import "../styles/globals.css";
+
+const queryClient = new QueryClient();
+
+const MyApp: AppType<{ session: Session | null }> = ({
+  Component,
+  pageProps: { session, ...pageProps },
+}) => {
+  return (
+    <QueryClientProvider client={queryClient}>
+      <SessionProvider session={session}>
+        <Component {...pageProps} />
+      </SessionProvider>
+    </QueryClientProvider>
+  );
+};
+
+export default MyApp;
+```
+
+
 Now let's implement the signup/signin pages. First, create a new page `/src/pages/signup.tsx`:
 
 ```tsx title='/src/pages/signup.tsx'
 /* eslint-disable @typescript-eslint/no-explicit-any */
 /* eslint-disable @typescript-eslint/no-unsafe-member-access */
-import type { NextPage } from 'next';
-import { signIn } from 'next-auth/react';
-import Router from 'next/router';
-import { useState, type FormEvent } from 'react';
-import { useMutateUser } from '../lib/hooks';
+import type { NextPage } from "next";
+import { signIn } from "next-auth/react";
+import Router from "next/router";
+import { useState, type FormEvent } from "react";
+import { useCreateUser } from "../lib/hooks";
 
 const Signup: NextPage = () => {
-    const [email, setEmail] = useState('');
-    const [password, setPassword] = useState('');
-    const { createUser: signup } = useMutateUser();
-
-    async function onSignup(e: FormEvent) {
-        e.preventDefault();
-        try {
-            await signup({ data: { email, password } });
-        } catch (err: any) {
-            console.error(err);
-            if (err.info?.prisma && err.info?.code === 'P2002') {
-                // P2002 is Prisma's error code for unique constraint violations
-                alert('User alread exists');
-            } else {
-                alert('An unknown error occurred');
-            }
-            return;
-        }
-
-        // signin to create a session
-        await signIn('credentials', { redirect: false, email, password });
-        await Router.push('/');
+  const [email, setEmail] = useState("");
+  const [password, setPassword] = useState("");
+  const { mutateAsync: signup } = useCreateUser();
+
+  async function onSignup(e: FormEvent) {
+    e.preventDefault();
+    try {
+      await signup({ data: { email, password } });
+    } catch (err: any) {
+      console.error(err);
+      if (err.info?.prisma && err.info?.code === "P2002") {
+        // P2002 is Prisma's error code for unique constraint violations
+        alert("User alread exists");
+      } else {
+        alert("An unknown error occurred");
+      }
+      return;
     }
 
-    return (
-        <div className="flex min-h-screen flex-col items-center justify-center bg-gradient-to-b from-[#2e026d] to-[#15162c]">
-            <h1 className="text-5xl font-extrabold text-white">Sign up</h1>
-            <form className="mt-16 flex flex-col gap-8 text-2xl" onSubmit={(e) => void onSignup(e)}>
-                <div>
-                    <label htmlFor="email" className="inline-block w-32 text-white">
-                        Email
-                    </label>
-                    <input
-                        id="email"
-                        type="email"
-                        value={email}
-                        onChange={(e) => setEmail(e.currentTarget.value)}
-                        className="ml-4 w-72 rounded border p-2"
-                    />
-                </div>
-                <div>
-                    <label htmlFor="password" className="inline-block w-32 text-white ">
-                        Password
-                    </label>
-                    <input
-                        id="password"
-                        type="password"
-                        value={password}
-                        onChange={(e) => setPassword(e.currentTarget.value)}
-                        className="ml-4 w-72 rounded border p-2"
-                    />
-                </div>
-                <input
-                    type="submit"
-                    value="Create account"
-                    className="cursor-pointer rounded border border-gray-500 py-4 text-white"
-                />
-            </form>
+    // signin to create a session
+    await signIn("credentials", { redirect: false, email, password });
+    await Router.push("/");
+  }
+
+  return (
+    <div className="flex min-h-screen flex-col items-center justify-center bg-gradient-to-b from-[#2e026d] to-[#15162c]">
+      <h1 className="text-5xl font-extrabold text-white">Sign up</h1>
+      <form
+        className="mt-16 flex flex-col gap-8 text-2xl"
+        onSubmit={(e) => void onSignup(e)}
+      >
+        <div>
+          <label htmlFor="email" className="inline-block w-32 text-white">
+            Email
+          </label>
+          <input
+            id="email"
+            type="email"
+            value={email}
+            onChange={(e) => setEmail(e.currentTarget.value)}
+            className="ml-4 w-72 rounded border p-2 bg-white"
+          />
         </div>
-    );
+        <div>
+          <label htmlFor="password" className="inline-block w-32 text-white">
+            Password
+          </label>
+          <input
+            id="password"
+            type="password"
+            value={password}
+            onChange={(e) => setPassword(e.currentTarget.value)}
+            className="ml-4 w-72 rounded border p-2 bg-white"
+          />
+        </div>
+        <input
+          type="submit"
+          value="Create account"
+          className="cursor-pointer rounded border border-gray-500 py-4 text-white"
+        />
+      </form>
+    </div>
+  );
 };
 
 export default Signup;
 ```
 
-In the code above, we used the auto-generated `useMutateUser` hooks to create new `User` entities.
+In the code above, we used the auto-generated `useCreateUser` hooks to create new `User` entities.
 
 :::tip
 
@@ -346,118 +375,105 @@ It should look like this:
 Similarly, create the signin page `/src/pages/signin.tsx`:
 
 ```tsx title='/src/pages/signin.tsx'
-import type { NextPage } from 'next';
-import { signIn } from 'next-auth/react';
-import Router from 'next/router';
-import { useState, type FormEvent } from 'react';
+import type { NextPage } from "next";
+import { signIn } from "next-auth/react";
+import Router from "next/router";
+import { useState, type FormEvent } from "react";
 
 const Signin: NextPage = () => {
-    const [email, setEmail] = useState('');
-    const [password, setPassword] = useState('');
+  const [email, setEmail] = useState("");
+  const [password, setPassword] = useState("");
 
-    async function onSignin(e: FormEvent) {
-        e.preventDefault();
+  async function onSignin(e: FormEvent) {
+    e.preventDefault();
 
-        const result = await signIn('credentials', {
-            redirect: false,
-            email,
-            password,
-        });
+    const result = await signIn("credentials", {
+      redirect: false,
+      email,
+      password,
+    });
 
-        if (result?.ok) {
-            await Router.push('/');
-        } else {
-            alert('Signin failed');
-        }
+    if (result?.ok) {
+      await Router.push("/");
+    } else {
+      alert("Signin failed");
     }
-
-    return (
-        <div className="flex min-h-screen flex-col items-center justify-center bg-gradient-to-b from-[#2e026d] to-[#15162c]">
-            <h1 className="text-5xl font-extrabold text-white">Login</h1>
-            <form className="mt-16 flex flex-col gap-8 text-2xl" onSubmit={(e) => void onSignin(e)}>
-                <div>
-                    <label htmlFor="email" className="inline-block w-32  text-white">
-                        Email
-                    </label>
-                    <input
-                        id="email"
-                        type="email"
-                        value={email}
-                        onChange={(e) => setEmail(e.currentTarget.value)}
-                        className="ml-4 w-72 rounded border p-2"
-                    />
-                </div>
-                <div>
-                    <label htmlFor="password" className="inline-block w-32  text-white">
-                        Password
-                    </label>
-                    <input
-                        id="password"
-                        type="password"
-                        value={password}
-                        onChange={(e) => setPassword(e.currentTarget.value)}
-                        className="ml-4 w-72 rounded border p-2"
-                    />
-                </div>
-                <input
-                    type="submit"
-                    value="Sign me in"
-                    className="cursor-pointer rounded border border-gray-500 py-4 text-white"
-                />
-            </form>
+  }
+
+  return (
+    <div className="flex min-h-screen flex-col items-center justify-center bg-gradient-to-b from-[#2e026d] to-[#15162c]">
+      <h1 className="text-5xl font-extrabold text-white">Login</h1>
+      <form
+        className="mt-16 flex flex-col gap-8 text-2xl"
+        onSubmit={(e) => void onSignin(e)}
+      >
+        <div>
+          <label htmlFor="email" className="inline-block w-32 text-white">
+            Email
+          </label>
+          <input
+            id="email"
+            type="email"
+            value={email}
+            onChange={(e) => setEmail(e.currentTarget.value)}
+            className="ml-4 w-72 rounded border p-2 bg-white"
+          />
         </div>
-    );
+        <div>
+          <label htmlFor="password" className="inline-block w-32 text-white">
+            Password
+          </label>
+          <input
+            id="password"
+            type="password"
+            value={password}
+            onChange={(e) => setPassword(e.currentTarget.value)}
+            className="ml-4 w-72 rounded border p-2 bg-white"
+          />
+        </div>
+        <input
+          type="submit"
+          value="Sign me in"
+          className="cursor-pointer rounded border border-gray-500 py-4 text-white"
+        />
+      </form>
+    </div>
+  );
 };
 
 export default Signin;
 ```
 
-### 7. Prepare the Blog model
+### 7. Prepare the Post model
 
-Now let's create a `Blog` model. We'll use it to store blog posts.
+Now let's update the `Post` model to add access policies.
 
 ```zmodel title='/schema.zmodel'
 model Post {
-  id        String @id @default(cuid())
+  id Int @id @default(autoincrement())
+  name String
   createdAt DateTime @default(now())
   updatedAt DateTime @updatedAt
-  title     String
+  // highlight-next-line
   published Boolean @default(false)
-  author    User @relation(fields: [authorId], references: [id])
-  authorId  String
 
+  createdBy User @relation(fields: [createdById], references: [id])
+  // highlight-next-line
+  createdById String @default(auth().id)
+
+  @@index([name])
 
+  // highlight-start
   // author has full access
-  @@allow('all', auth() == author)
+  @@allow('all', auth() == createdBy)
 
   // logged-in users can view published posts
   @@allow('read', auth() != null && published)
+  // highlight-end
 }
 ```
 
-`User` and `Post` model has a one-to-many relation. We can establish it by adding
-a `posts` relation field to the `User` model.
-
-```zmodel title='/schema.zmodel'
-model User {
-    id            String    @id @default(cuid())
-    name          String?
-    email         String?   @unique
-    emailVerified DateTime?
-    password      String @password @omit
-    // highlight-next-line
-    posts         Post[]
-    image         String?
-    accounts      Account[]
-    sessions      Session[]
-
-    // everyone can signup, and user profile is also publicly readable
-    @@allow('create,read', true)
-
-    // only the user can update or delete their own profile
-    @@allow('update,delete', auth() == this)
-}
-```
+<AuthDefault field="createdById" />
 
 Don't forget to regenerate and push schema changes to the database:
 
@@ -475,7 +491,7 @@ import { type NextPage } from 'next';
 import { signOut, useSession } from 'next-auth/react';
 import Link from 'next/link';
 import Router from 'next/router';
-import { useFindManyPost, useMutatePost } from '../lib/hooks';
+import { useCreatePost, useDeletePost, useFindManyPost, useUpdatePost } from '../lib/hooks';
 
 type AuthUser = { id: string; email?: string | null };
 
@@ -485,9 +501,9 @@ const Welcome = ({ user }: { user: AuthUser }) => {
         await Router.push('/signin');
     }
     return (
-        <div className="flex gap-4">
-            <h3 className="text-lg">Welcome back, {user?.email}</h3>
-            <button className="text-gray-300 underline" onClick={() => void onSignout()}>
+        <div className='flex gap-4'>
+            <h3 className='text-lg'>Welcome back, {user?.email}</h3>
+            <button className='text-gray-300 underline' onClick={() => void onSignout()}>
                 Signout
             </button>
         </div>
@@ -496,31 +512,33 @@ const Welcome = ({ user }: { user: AuthUser }) => {
 
 const SigninSignup = () => {
     return (
-        <div className="flex gap-4 text-2xl">
-            <Link href="/signin" className="rounded-lg border px-4 py-2">
+        <div className='flex gap-4 text-2xl'>
+            <Link href='/signin' className='rounded-lg border px-4 py-2'>
                 Signin
             </Link>
-            <Link href="/signup" className="rounded-lg border px-4 py-2">
+            <Link href='/signup' className='rounded-lg border px-4 py-2'>
                 Signup
             </Link>
         </div>
     );
 };
 
-const Posts = ({ user }: { user: AuthUser }) => {
+const Posts = () => {
     // Post crud hooks
-    const { createPost, updatePost, deletePost } = useMutatePost();
+    const { mutateAsync: createPost } = useCreatePost();
+    const { mutateAsync: updatePost } = useUpdatePost();
+    const { mutateAsync: deletePost } = useDeletePost();
 
     // list all posts that're visible to the current user, together with their authors
     const { data: posts } = useFindManyPost({
-        include: { author: true },
+        include: { createdBy: true },
         orderBy: { createdAt: 'desc' },
     });
 
     async function onCreatePost() {
-        const title = prompt('Enter post title');
-        if (title) {
-            await createPost({ data: { title, authorId: user.id } });
+        const name = prompt('Enter post name');
+        if (name) {
+            await createPost({ data: { name } });
         }
     }
 
@@ -536,23 +554,23 @@ const Posts = ({ user }: { user: AuthUser }) => {
     }
 
     return (
-        <div className="container flex flex-col text-white">
-            <button className="rounded border border-white p-2 text-lg" onClick={() => void onCreatePost()}>
+        <div className='container flex flex-col text-white'>
+            <button className='rounded border border-white p-2 text-lg' onClick={() => void onCreatePost()}>
                 + Create Post
             </button>
 
-            <ul className="container mt-8 flex flex-col gap-2">
+            <ul className='container mt-8 flex flex-col gap-2'>
                 {posts?.map((post) => (
-                    <li key={post.id} className="flex items-end justify-between gap-4">
+                    <li key={post.id} className='flex items-end justify-between gap-4'>
                         <p className={`text-2xl ${!post.published ? 'text-gray-400' : ''}`}>
-                            {post.title}
-                            <span className="text-lg"> by {post.author.email}</span>
+                            {post.name}
+                            <span className='text-lg'> by {post.createdBy.email}</span>
                         </p>
-                        <div className="flex w-32 justify-end gap-1 text-left">
-                            <button className="underline" onClick={() => void onTogglePublished(post)}>
+                        <div className='flex w-32 justify-end gap-1 text-left'>
+                            <button className='underline' onClick={() => void onTogglePublished(post)}>
                                 {post.published ? 'Unpublish' : 'Publish'}
                             </button>
-                            <button className="underline" onClick={() => void onDelete(post)}>
+                            <button className='underline' onClick={() => void onDelete(post)}>
                                 Delete
                             </button>
                         </div>
@@ -569,16 +587,16 @@ const Home: NextPage = () => {
     if (status === 'loading') return <p>Loading ...</p>;
 
     return (
-        <main className="flex min-h-screen flex-col items-center justify-center bg-gradient-to-b from-[#2e026d] to-[#15162c]">
-            <div className="container flex flex-col items-center justify-center gap-12 px-4 py-16 text-white">
-                <h1 className="text-5xl font-extrabold">My Awesome Blog</h1>
+        <main className='flex min-h-screen flex-col items-center justify-center bg-gradient-to-b from-[#2e026d] to-[#15162c]'>
+            <div className='container flex flex-col items-center justify-center gap-12 px-4 py-16 text-white'>
+                <h1 className='text-5xl font-extrabold'>My Awesome Blog</h1>
 
                 {session?.user ? (
                     // welcome & blog posts
-                    <div className="flex flex-col">
+                    <div className='flex flex-col'>
                         <Welcome user={session.user} />
-                        <section className="mt-10">
-                            <Posts user={session.user} />
+                        <section className='mt-10'>
+                            <Posts />
                         </section>
                     </div>
                 ) : (
@@ -637,7 +655,7 @@ backend code. Pretty cool, isn't it?
 
 If you have trouble following the building process, you can find the final result
 [here](https://github.com/zenstackhq/docs-tutorial-nextjs). For more details about ZenStack,
-please refer to the [Reference](/docs/category/reference) and [Guides](/docs/category/recipes)
+please refer to the [Reference](../category/reference) and [Guides](../category/recipes)
 parts of the documentation.
 
 Have fun building cool stuff 🚀!
diff --git a/docs/quick-start/nuxt.mdx b/docs/quick-start/nuxt.mdx
index 1109ab62..af6028f5 100644
--- a/docs/quick-start/nuxt.mdx
+++ b/docs/quick-start/nuxt.mdx
@@ -1,13 +1,14 @@
 ---
 title: Nuxt
 description: Step-by-step guide for building a blogging app with Nuxt V3.
-sidebar_position: 2
+sidebar_position: 3
 ---
 
 import Requirements from './_requirements.md';
 import Prerequisite from './_prerequisite.md';
 import InitTips from './_zenstack-init-tips.md';
 import AccessPolicy from './_access-policy.md';
+import AuthDefault from './_auth_default.md';
 
 # Get Started With Nuxt (V3)
 
@@ -32,7 +33,7 @@ Although authentication is not the focus of the project, we need a basic signup/
 First, make a copy of the project using [degit](https://github.com/Rich-Harris/degit), install dependencies, and start the dev server:
 
 ```bash
-npx degit nuxt/examples/auth/local my-nuxt-blog
+npx degit nuxt/examples/examples/auth/local my-nuxt-blog
 cd my-nuxt-blog
 npm install
 npm run dev
@@ -134,7 +135,7 @@ model Post {
     content String
     published Boolean @default(false)
     author User @relation(fields: [authorId], references: [id])
-    authorId String
+    authorId String @default(auth().id)
 
     // allow read for all signin users
     @@allow('read', auth() != null && published)
@@ -144,6 +145,8 @@ model Post {
 }
 ```
 
+<AuthDefault field="authorId" />
+
 Notice how the access policies are defined in the schema to control who has what access to the data? To enforce these policies, we'll need to create an "enhanced" PrismaClient, like:
 
 ```ts
@@ -185,8 +188,7 @@ The CRUD APIs for all your models are now mounted to the `/api/model` endpoint.
 We can write frontend code to consume the CRUD APIs, but that would be quite boring. Instead, let's use a plugin to generate them for us. First, install the `@zenstackhq/tanstack-query` plugin package:
 
 ```bash
-npm install @tanstack/vue-query --force
-npm install -D @zenstackhq/tanstack-query
+npm install @zenstackhq/tanstack-query @tanstack/vue-query
 ```
 
 :::info
@@ -302,13 +304,11 @@ import { useFindManyPost, useCreatePost } from '~/lib/hooks';
 
 const title = ref('');
 const createPost = useCreatePost();
-const { session } = useAuth();
 
 const onCreatePost = async () => {
     try {
         await createPost.mutateAsync({
             data: {
-                author: { connect: { id: session.value!.id } },
                 title: title.value,
                 content: 'My awesome content',
             },
@@ -362,6 +362,6 @@ You may have noticed that a user can publish, unpublish, or delete other user's
 
 🎉 Congratulations! You've made a simple blogging app without writing a single backend API. Pretty cool, isn't it?
 
-If you have trouble following the building process, you can find the final result [here](https://github.com/zenstackhq/docs-tutorial-nuxt). For more details about ZenStack, please refer to the [Reference](/docs/category/reference) and [Guides](/docs/category/recipes) parts of the documentation.
+If you have trouble following the building process, you can find the final result [here](https://github.com/zenstackhq/docs-tutorial-nuxt). For more details about ZenStack, please refer to the [Reference](../category/reference) and [Guides](../category/recipes) parts of the documentation.
 
 Have fun building cool stuff 🚀!
diff --git a/docs/quick-start/remix-run.mdx b/docs/quick-start/remix-run.mdx
index 05f403f2..7e525824 100644
--- a/docs/quick-start/remix-run.mdx
+++ b/docs/quick-start/remix-run.mdx
@@ -1,13 +1,14 @@
 ---
 title: Remix.run
 description: Step-by-step guide for building a blogging app with Remix.run.
-sidebar_position: 4
+sidebar_position: 5
 ---
 
 import Requirements from './_requirements.md';
 import Prerequisite from './_prerequisite.md';
 import InitTips from './_zenstack-init-tips.md';
 import AccessPolicy from './_access-policy.md';
+import AuthDefault from './_auth_default.md';
 
 # Get Started With Remix.run
 
@@ -71,7 +72,8 @@ model Post {
   createdAt DateTime @default(now())
   updatedAt DateTime @updatedAt
   user User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade)
-  userId String
+  // highlight-next-line
+  userId String @default(auth().id)
 
   // highlight-start
   // author has full access
@@ -83,6 +85,8 @@ model Post {
 }
 ```
 
+<AuthDefault field="userId" />
+
 <AccessPolicy />
 
 Also we need to rename the relation in the `User` model from `notes` to `posts`.
@@ -229,15 +233,7 @@ import type { Post } from '@prisma/client';
 
 export function createPost({ body, title, userId }: Post & { userId: User['id'] }) {
     return getEnhancedPrisma(userId).post.create({
-        data: {
-            title,
-            body,
-            user: {
-                connect: {
-                    id: userId,
-                },
-            },
-        },
+        data: { title, body },
     });
 }
 ```
@@ -523,7 +519,7 @@ You can catch the error and render a nice message to the user.
 
 If you have trouble following the building process, you can find the final result
 [here](https://github.com/zenstackhq/docs-tutorial-remix). For more details about ZenStack,
-please refer to the [Reference](/docs/category/reference) and [Guides](/docs/category/recipes)
+please refer to the [Reference](../category/reference) and [Guides](../category/recipes)
 parts of the documentation.
 
 Have fun building cool stuff 🚀!
diff --git a/docs/quick-start/sveltekit.mdx b/docs/quick-start/sveltekit.mdx
index db8fe2f4..e9cecf75 100644
--- a/docs/quick-start/sveltekit.mdx
+++ b/docs/quick-start/sveltekit.mdx
@@ -1,7 +1,7 @@
 ---
 title: SvelteKit
 description: Step-by-step guide for building a blogging app with SvelteKit.
-sidebar_position: 3
+sidebar_position: 4
 ---
 
 import Requirements from './_requirements.md';
@@ -9,6 +9,7 @@ import Prerequisite from './_prerequisite.md';
 import InitTips from './_zenstack-init-tips.md';
 import PasswordOmit from './_password-omit.md';
 import AccessPolicy from './_access-policy.md';
+import AuthDefault from './_auth_default.md';
 
 # Get Started With SvelteKit
 
@@ -85,7 +86,7 @@ model Post {
     title String @length(1, 256)
     published Boolean @default(false)
     author User? @relation(fields: [authorId], references: [id])
-    authorId String?
+    authorId String? @default(auth().id)
 
     // allow read for all signin users
     @@allow('read', auth() != null && published)
@@ -95,6 +96,8 @@ model Post {
 }
 ```
 
+<AuthDefault field="authorId" />
+
 <PasswordOmit />
 
 <AccessPolicy />
@@ -525,9 +528,7 @@ export const actions: Actions = {
             return fail(400, { missing: true });
         }
 
-        await locals.db.post.create({
-            data: { title, author: { connect: { id: locals.user?.id } } }
-        });
+        await locals.db.post.create({ data: { title } });
 
         return { success: true };
     },
@@ -610,15 +611,15 @@ In a real application you can catch the error and render a proper message to the
 
 In this tutorial, we used ZenStack only on the SSR side (loader and actions) of SvelteKit, but its power doesn't stop there. There're two more things worth your attention that enable more advanced scenarios:
 
-- The [`@zenstackhq/server/sveltekit`](/docs/reference/server-adapters/sveltekit) server adapter provides a SvelteKit hooks handler that installs an automatic CRUD API for database operations.
+- The [`@zenstackhq/server/sveltekit`](../reference/server-adapters/sveltekit) server adapter provides a SvelteKit hooks handler that installs an automatic CRUD API for database operations.
 
-- The [`@zenstackhq/tanstack-query`](/docs/reference/plugins/tanstack-query) plugin provides automatic generation of client-side query and mutation hooks for consuming the afore-mentioned CRUD API.
+- The [`@zenstackhq/tanstack-query`](../reference/plugins/tanstack-query) plugin provides automatic generation of client-side query and mutation hooks for consuming the afore-mentioned CRUD API.
 
 These two features combined allow you to build a typical SPA (CSR + API). You can also freely mix and match SSR and CSR as you wish.
 
 If you have trouble following the building process, you can find the final result
 [here](https://github.com/zenstackhq/docs-tutorial-sveltekit). For more details about ZenStack,
-please refer to the [Reference](/docs/category/reference) and [Guides](/docs/category/recipes)
+please refer to the [Reference](../category/reference) and [Recipes](../category/recipes)
 parts of the documentation.
 
 Have fun building cool stuff 🚀!
diff --git a/docs/reference/cli.md b/docs/reference/cli.md
index fab75775..e6188f8b 100644
--- a/docs/reference/cli.md
+++ b/docs/reference/cli.md
@@ -25,11 +25,26 @@ Commands:
   generate [options]     Generates RESTful API and Typescript client for your data model.
   repl [options]         Start a REPL session.
   format [options]       Format a ZenStack schema file.
-  help [command]         display help for command
+  check [options]        Check a ZenStack schema file for syntax or semantic errors.
+  help [command]         Display help for a command.
 ```
 
 ## Sub Commands
 
+### info
+
+Get information of installed ZenStack and related packages.
+
+```bash
+zenstack info [options] [path]
+```
+
+#### Arguments
+
+| Name | Description  | Default        |
+| ---- | ------------ | -------------- |
+| path | Project path | current folder |
+
 ### init
 
 Initializes an existing project to use ZenStack.
@@ -85,12 +100,23 @@ zenstack generate [options]
 | Name                  | Description                                      | Default                |
 | --------------------- | ------------------------------------------------ | ---------------------- |
 | --schema              | schema file (with extension .zmodel)             | ./schema.zmodel        |
-| -p, --package-manager | package manager to use: "npm", "yarn", or "pnpm" | auto detect            |
 | -o, --output &lt;path&gt;   | default output directory for TS/JS files generated by built-in plugins   | node_modules/.zenstack |
+| --with-plugins        | only run specific plugins                        |                        |
+| --without-plugins     | exclude specific plugins                         |                        |
 | --no-default-plugins  | do not automatically run built-in plugins        | false                  |
 | --no-compile          | do not compile the output of built-in plugins    | false                  |
 | --no-version-check    | do not check for new versions of ZenStack        | false                  |
 
+You can also specify the ZModel schema location in the "package.json" file of your project like the following:
+
+```json title="package.json"
+{
+  "zenstack": {
+    "schema": "./db/schema.zmodel"
+  }
+}
+```
+
 #### Examples
 
 Generate with default settings.
@@ -99,10 +125,10 @@ Generate with default settings.
 npx zenstack generate
 ```
 
-Generate with custom package manager and schema location.
+Generate with custom schema location.
 
 ```bash
-npx zenstack generate -p pnpm --schema src/my.zmodel
+npx zenstack generate --schema src/my.zmodel
 ```
 
 ### repl
@@ -131,7 +157,8 @@ You don't need to `await` the Prisma method call result. The REPL session will a
 | ----- | ------------------- | ------- |
 | --debug | Enable debug output. Can be toggled on the fly in the repl session with the ".debug" command. | false |
 | --table | Enable table format. Can be toggled on the fly in the repl session with the ".table" command. | false |
-| --prisma-client | Path to load PrismaClient module. | "./node_modules/.prisma/client" |
+| --prisma-client &lt;path&gt; | Path to load PrismaClient module. | "node_modules/.prisma/client" |
+| --load-path &lt;path&gt; | Path to load modules generated by ZenStack. | "node_modules/.zenstack" |
 
 #### Repl Commands
 
@@ -199,25 +226,26 @@ zenstack format [options]
 | --------------------- | ------------------------------------------------ | ---------------------- |
 | --schema              | schema file (with extension .zmodel)             | ./schema.zmodel        |
 
-### info
-
-Get information of installed ZenStack and related packages.
+You can also specify the ZModel schema location in the "package.json" file of your project like the following:
 
-```bash
-zenstack info [options] [path]
+```json title="package.json"
+{
+  "zenstack": {
+    "schema": "./db/schema.zmodel"
+  }
+}
 ```
 
-#### Arguments
+### check
 
-| Name | Description  | Default        |
-| ---- | ------------ | -------------- |
-| path | Project path | current folder |
-
-<!-- ### Config file
+Check a ZenStack schema file for syntax or semantic errors. You can use the CLI's exit code to determine if the schema is valid.
 
-ZenStack CLI loads configuration from `zenstack.config.json` file if it finds one. The config file path can be overridden with `--config` option.
+```bash
+zenstack check [options]
+```
 
-The config file is a JSON object with the following properties:
+#### Options
 
-| Name                 | Description                                                         | Default              |
-| -------------------- | ------------------------------------------------------------------- | -------------------- | -->
+| Name                  | Description                                      | Default                |
+| --------------------- | ------------------------------------------------ | ---------------------- |
+| --schema              | schema file (with extension .zmodel)             | ./schema.zmodel        |
diff --git a/docs/reference/enhanced-types.md b/docs/reference/enhanced-types.md
new file mode 100644
index 00000000..52f959b8
--- /dev/null
+++ b/docs/reference/enhanced-types.md
@@ -0,0 +1,38 @@
+---
+description: Enhancements to the types generated by Prisma
+sidebar_position: 4
+sidebar_label: Enhanced Prisma Types
+---
+
+# Enhanced Prisma Types
+
+When you call the [`enhance`](./runtime-api#enhance) API, ZenStack not only enhances `PrismaClient`'s runtime behavior, but also alters its types as needed. For example, when you use [polymorphic models](../guides/polymorphism), ZenStack translates the inheritance hierarchy into a proper Prisma schema following the [Multi-table inheritance](https://www.prisma.io/docs/orm/prisma-schema/data-model/table-inheritance#multi-table-inheritance-mti) pattern, however it keeps the typing of the enhanced `PrismaClient` simple and provides a real "polymorphic" DX.
+
+You usually don't need to explicitly refer to the enhanced Prisma types, but when you need, import them from the `@zenstackhq/runtime/models` module instead of `@prisma/client`.
+
+```ts
+import type { Post, Prisma } from '@zenstackhq/runtime/models'
+import { getEnhancedPrisma } from '~/db'
+
+const db = getEnhancedPrisma();
+
+// explicitly refer to the enhanced model type
+const post: Post = await db.post.findUnique({...});
+
+// explicitly refer to the enhanced input type
+const createInput: Prisma.PostCreateInput = { ... };
+await db.post.create({ data: createInput });
+```
+
+You can also use the `Enhanced` typing utility to infer the enhanced `PrismaClient` from its original typing:
+
+```ts
+import { PrismaClient } from '@prisma/client';
+import type { Enhanced } from '@zenstackhq/runtime';
+
+const prisma = new PrismaClient();
+
+type EnhancedPrismaClient = Enhanced<typeof prisma>;
+```
+
+The `prisma` instance can be a raw `PrismaClient`, or one with Prisma Client Extensions installed.
diff --git a/docs/reference/error-handling.md b/docs/reference/error-handling.md
index d07bb750..07d31586 100644
--- a/docs/reference/error-handling.md
+++ b/docs/reference/error-handling.md
@@ -1,6 +1,6 @@
 ---
 description: Error handling
-sidebar_position: 6
+sidebar_position: 8
 ---
 
 # Error Handling
@@ -10,7 +10,7 @@ ZenStack's enhancements to Prisma clients are transparent proxies, so normal err
 -   [Handling exceptions and errors](https://www.prisma.io/docs/concepts/components/prisma-client/handling-exceptions-and-errors)
 -   [Error message reference](https://www.prisma.io/docs/reference/api-reference/error-reference)
 
-The enhanced Prisma client can throw extra errors when an operation is rejected by [access policies](/docs/reference/zmodel-language#access-policy) or its data fails [validation rules](/docs/reference/zmodel-language#field-validation). To keep a consistent programming experience, a `PrismaClientKnownRequestError` is thrown with code [`P2004`](https://www.prisma.io/docs/reference/api-reference/error-reference#p2004) is used in such cases:
+The enhanced Prisma client can throw extra errors when an operation is rejected by [access policies](./zmodel-language#access-policy) or its data fails [validation rules](./zmodel-language#data-validation). To keep a consistent programming experience, a `PrismaClientKnownRequestError` is thrown with code [`P2004`](https://www.prisma.io/docs/reference/api-reference/error-reference#p2004) is used in such cases:
 
 ```ts
 throw new PrismaClientKnownRequestError(message, {
@@ -36,7 +36,7 @@ The error contains a `meta` field providing more information about the error. It
 
     - *DATA_VALIDATION_VIOLATION*
     
-        CRUD failed because of a [field validation](/docs/reference/zmodel-language#field-validation) error.
+        CRUD failed because of a [data validation](./zmodel-language#data-validation) error.
 
 - `zodErrors`
   
diff --git a/docs/reference/limitations.md b/docs/reference/limitations.md
index 96860e6d..96d02bc0 100644
--- a/docs/reference/limitations.md
+++ b/docs/reference/limitations.md
@@ -13,10 +13,28 @@ This section lists the current limitations of ZenStack.
 
 As a workaround, use [interactive transactions](https://www.prisma.io/docs/concepts/components/prisma-client/transactions#interactive-transactions) instead.
 
+### Minimum transaction isolation level
+
+To ensure access policies are properly enforced, the database's transaction isolation level should be set to at least [Repeatable Read](https://en.wikipedia.org/wiki/Isolation_(database_systems)#Repeatable_reads). This can be done by changing the settings of the database, or providing the `isolationLevel` option when creating `PrismaClient`:
+
+```ts
+const prisma = new PrismaClient({
+  transactionOptions: {
+    isolationLevel: Prisma.TransactionIsolationLevel.RepeatableRead,
+  },
+});
+```
+
+If you don't want to change the global settings, alternatively you can set the `transactionIsolationLevel` option when calling ZenStack's `enhance` API. All the transactions initiated internally by ZenStack will use the specified isolation level.
+
+```ts
+const db = enhance(prisma, { user }, { transactionIsolationLevel: 'RepeatableRead' });
+```
+
 ### 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.
 
-### Edge runtime is not supported
+### Cloudflare D1 database is not supported
 
-Prisma by default doesn't support running in edge runtime environments (e.g. [Vercel Edge Runtime](https://edge-runtime.vercel.app/)). Its [Accelerate](https://www.prisma.io/data-platform/accelerate) commercial service does support edge, but ZenStack is not compatible with it yet. We plan to support it in the near future.
+Prisma doesn't support interactive transactions with D1 databases. ZenStack relies on the feature to enforce access policies in certain cases.
diff --git a/docs/reference/plugins/_category_.yml b/docs/reference/plugins/_category_.yml
index a2d8b377..4a2a7dc0 100644
--- a/docs/reference/plugins/_category_.yml
+++ b/docs/reference/plugins/_category_.yml
@@ -1,4 +1,4 @@
-position: 4
+position: 6
 label: Plugins
 collapsible: true
 collapsed: true
diff --git a/docs/reference/plugins/_fine-grained-optimistic.md b/docs/reference/plugins/_fine-grained-optimistic.md
new file mode 100644
index 00000000..4e2c4e09
--- /dev/null
+++ b/docs/reference/plugins/_fine-grained-optimistic.md
@@ -0,0 +1,25 @@
+Automatic optimistic update is convenient, but there might be cases where you want to explicitly control how the update happens. You can use the `optimisticUpdateProvider` callback to fully customize how each query cache entry should be optimistically updated. When the callback is set, it takes precedence over the automatic optimistic logic.
+
+```ts
+useCreatePost({
+  optimisticUpdateProvider: ({ queryModel, queryOperation, queryArgs, currentData, mutationArgs }) => {
+    return { kind: 'Update', data: ... /* computed result */ };
+  }
+});
+```
+
+The callback is invoked for each query cache entry and receives the following arguments in a property bag:
+
+- `queryModel`: The model of the query, e.g., `Post`.
+- `queryOperation`: The operation of the query, e.g., `findMany`, `count`.
+- `queryArgs`: The arguments of the query, e.g., `{ where: { published: true } }`.
+- `currentData`: The current cache data.
+- `mutationArgs`: The arguments of the mutation, e.g., `{ data: { title: 'My awesome post' } }`.
+
+It should return a result object with the following fields:
+
+- `kind`: The kind of the optimistic update.
+    - `Update`: update the cache using the computed data
+    - `Skip`: skip the update
+    - `ProceedDefault`: use the default automatic optimistic behavior.
+- `data`: The computed data to update the cache with. Only used when `kind` is `Update`.
diff --git a/docs/reference/plugins/_optimistic-behavior.md b/docs/reference/plugins/_optimistic-behavior.md
index 32463b03..6c54ff85 100644
--- a/docs/reference/plugins/_optimistic-behavior.md
+++ b/docs/reference/plugins/_optimistic-behavior.md
@@ -1,3 +1,4 @@
 - `create` mutation inserts item to the head of the query results of the corresponding `useFindMany` queries.
 - `update` mutation updates the item in the query results of `useFindXXX` queries and their nested reads by matching the item's ID.
+- `upsert` mutation first tries to update the item in the query results of `useFindXXX` queries and their nested reads by matching the item's ID. If the item is not found, it inserts the item to the head of the query results.
 - `delete` mutation removes the item from the query results of the corresponding `useFindMany` queries and sets `null` to `useFindUnique` and `useFindFirst` query results, by matching the item's ID.
diff --git a/docs/reference/plugins/enhancer.md b/docs/reference/plugins/enhancer.md
new file mode 100644
index 00000000..e3bae22f
--- /dev/null
+++ b/docs/reference/plugins/enhancer.md
@@ -0,0 +1,49 @@
+---
+description: Built-in plugin for generating JavaScript modules that support runtime enhancement of Prisma Client.
+sidebar_position: 2
+---
+
+# @core/enhancer
+
+The `@core/enhancer` plugin generates JavaScript modules that support runtime enhancement of Prisma Client. The generated modules include the following:
+
+- enhance
+
+    The `enhance` API that creates a proxy around a `PrismaClient` instance to extend its behavior. When using the default output location, you can import this API from `@zenstackhq/runtime`.
+
+- model-meta
+
+    Lightweight runtime representation of ZModel's AST.
+
+- policy
+  
+    Partial Prisma query input objects and input checker functions compiled from access policy expressions.
+
+:::info
+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.
+:::
+
+### Installation
+
+This plugin is built-in to ZenStack and does not need to be installed separately.
+
+### Options
+
+| Name   | Type   | Description      | Required | Default                    |
+| ------ | ------ | ---------------- | -------- | -------------------------- |
+| output | String | Output directory (relative to the path of ZModel). This option will be deprecated in future releases in favor of the "--output" CLI option of `zenstack generate`. | No       | node_modules/.zenstack |
+| preserveTsFiles | Boolean | If the generated TS files should be preserved (after compiled to JS) | No | false |
+
+### Custom output directory and compilation options
+
+You can use the "--output" option of the `zenstack generate` CLI to specify a custom output location for the generated enhancer code:
+
+```bash
+zenstack generate --output src/lib/zenstack
+```
+
+If you want to have the code generated as TypeScript (instead of compiled to JavaScript), you can add the "--no-compile" option:
+
+```bash
+zenstack generate --no-compile --output src/lib/zenstack
+```
diff --git a/docs/reference/plugins/openapi.mdx b/docs/reference/plugins/openapi.mdx
index 2dfc5c98..01b1a390 100644
--- a/docs/reference/plugins/openapi.mdx
+++ b/docs/reference/plugins/openapi.mdx
@@ -15,17 +15,19 @@ npm install --save-dev @zenstackhq/openapi
 
 ### Options
 
-| Name            | Type   | Description                                                                   | Required | Default                |
-| --------------- | ------ | ----------------------------------------------------------------------------- | -------- | ---------------------- |
-| output          | String | Output file path (with suffix .yaml or .json, relative to the path of ZModel)                                 | Yes      |                        |
-| flavor          | String | API flavor: "rpc" or "rest". See [here](/docs/reference/server-adapters/api-handlers/) for more details.                                       | No       | rpc                   |
-| specVersion     | String | OpenAPI specification version                                                 | No       | 3.1.0                  |
-| title           | String | API title                                                                     | No       | ZenStack Generated API |
-| version         | String | API version                                                                   | No       | 1.0.0                  |
-| prefix          | String | API path prefix, e.g., '/api'                                                 | No       |                        |
-| description     | String | API description                                                               | No       |                        |
-| summary         | String | API summary                                                                   | No       |                        |
-| securitySchemes | Object | Security schemes for the API. See [here](#security-schemes) for more details. | No       |                        |
+| Name             | Type    | Description                                                                   | Required | Default                |
+| ---------------- | ------- | ----------------------------------------------------------------------------- | -------- | ---------------------- |
+| output           | String  | Output file path (with suffix .yaml or .json, relative to the path of ZModel)                                 | Yes      |                        |
+| flavor           | String  | API flavor: "rpc" or "rest". See [here](../server-adapters/api-handlers/) for more details.                                       | No       | rpc                   |
+| specVersion      | String  | OpenAPI specification version                                                 | No       | 3.1.0                  |
+| title            | String  | API title                                                                     | No       | ZenStack Generated API |
+| version          | String  | API version                                                                   | No       | 1.0.0                  |
+| prefix           | String  | API path prefix, e.g., '/api'                                                 | No       |                        |
+| description      | String  | API description                                                               | No       |                        |
+| summary          | String  | API summary                                                                   | No       |                        |
+| securitySchemes  | Object  | Security schemes for the API. See [here](#security-schemes) for more details. | No       |                        |
+| omitInputDetails | Boolean | **Only valid for "rpc" flavor.** If true, the output spec will not contain detailed structures for query/mutation input fields like `where`, `select`, `data`, etc. These fields will be typed as generic objects. Use this option to reduce the size of the generated spec. | No       | false                  |
+| modelNameMapping | Object  | **Only effective for "rest" flavor.** See [here](#model-name-mapping) for more details. | No       |                        |
 
 #### API flavor
 
@@ -40,7 +42,7 @@ The `flavor` option controls the style of API endpoints and data format of the g
     RESTful resource-centric endpoints using [JSON:API](https://jsonapi.org/) as transportation format
 
 
-The two flavors correspond to the two style of APIs supported by ZenStack server adapters. See [here](/docs/reference/server-adapters/api-handlers/) for more details.
+The two flavors correspond to the two style of APIs supported by ZenStack server adapters. See [here](../server-adapters/api-handlers/) for more details.
 
 #### Security schemes
 
@@ -63,6 +65,22 @@ The names of the configured security schemes will be added to the root `security
 
 You can override the security scheme for a specific model or operation by using the `@@openapi.meta` attribute.
 
+#### Model name mapping
+
+The `modelNameMapping` option is an object that maps ZModel model names to OpenAPI resource names. This is useful for example when you want to use plural names in URL endpoints. It's only effective for the `rest` flavor. The mapping can be partial. You only need to specify the model names that you want to override.
+
+```zmodel
+plugin openapi {
+    provider = '@zenstackhq/openapi'
+    prefix = '/api'
+    modelNameMapping = {
+        User: 'users'
+    }
+}
+```
+
+With the above configuration, the `User` operations will be generated under `/api/users` instead of `/api/user`.
+
 ### Attributes
 
 -   `@@openapi.meta`
diff --git a/docs/reference/plugins/prisma.md b/docs/reference/plugins/prisma.md
index b855c4c3..9d99a8c9 100644
--- a/docs/reference/plugins/prisma.md
+++ b/docs/reference/plugins/prisma.md
@@ -21,7 +21,9 @@ This plugin is built-in to ZenStack and does not need to be installed separately
 | -------------- | ------- | ------------------------------------------------- | -------- | ---------------------- |
 | output         | String  | Output file path (relative to the path of ZModel)                                  | No       | ./prisma/schema.prisma |
 | generateClient | Boolean | Whether to run `prisma generate` after generation | No       | true                   |
-| format         | Boolean | Whether to run `prisma format` after generation   | No       | false                  |
+| generateArgs | String | Command line arguments appended to the `prisma generate` call, e.g., "--no-engine". Please note that the "--schema" option is automatically passed, and you shouldn't provide it explicitly. | No       |                    |
+| format         | Boolean | Whether to run `prisma format` after generation   | No       | true                  |
+| customAttributesAsComments | Boolean | Whether to generate non-Prisma attributes (e.g., `@@allow`) as comments in the Prisma schema | No | false |
 
 ### Example
 
@@ -29,6 +31,5 @@ This plugin is built-in to ZenStack and does not need to be installed separately
 plugin prisma {
     provider = '@core/prisma'
     output = './src/prisma/schema.prisma'
-    format = true
 }
 ```
diff --git a/docs/reference/plugins/swr.mdx b/docs/reference/plugins/swr.mdx
index 6cffb3a5..bdcab130 100644
--- a/docs/reference/plugins/swr.mdx
+++ b/docs/reference/plugins/swr.mdx
@@ -6,14 +6,15 @@ sidebar_position: 5
 import Invalidation from './_invalidation.md';
 import OptimisticBehavior from './_optimistic-behavior.md';
 import OptmisticLimitation from './_optimistic-limitation.md';
+import FineGrainedOptimistic from './_fine-grained-optimistic.md';
 
 # @zenstackhq/swr
 
 :::info
-If you're looking for generating hooks for [Tanstack Query](https://tanstack.com/query/latest), please checkout the [`@zenstackhq/tanstack-query`](/docs/reference/plugins/tanstack-query) plugin.
+If you're looking for generating hooks for [Tanstack Query](https://tanstack.com/query/latest), please checkout the [`@zenstackhq/tanstack-query`](./tanstack-query.mdx) plugin.
 :::
 
-The `@zenstackhq/swr` plugin generates [SWR](https://swr.vercel.app/) hooks that call into the CRUD services provided by the [server adapters](/docs/category/server-adapters).
+The `@zenstackhq/swr` plugin generates [SWR](https://swr.vercel.app/) hooks that call into the CRUD services provided by the [server adapters](../../category/server-adapters).
 
 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).
 
@@ -22,7 +23,7 @@ To use the generated hooks, you need to install "swr" version 2.0.0 or above.
 ### Installation
 
 ```bash
-npm install --save-dev @zenstackhq/swr
+npm install @zenstackhq/swr
 ```
 
 ### Context Provider
@@ -69,7 +70,36 @@ export default MyApp;
 | Name    | Type   | Description                                             | Required | Default |
 | ------- | ------ | ------------------------------------------------------- | -------- | ------- |
 | output  | String | Output directory (relative to the path of ZModel)                                        | Yes      |         |
-| useSuperJson  | Boolean | Use [superjson](https://github.com/blitz-js/superjson) for data serialization                                        | No      | false        |
+
+### Hooks Signature
+
+The generated hooks have the following signature convention.
+
+- **Query Hooks**
+
+    ```ts
+    function use[Operation][Model](args?, options?);
+    ```
+
+    - `[Operation]`: query operation. E.g., "FindMany", "FindUnique", "Count".
+    - `[Model]`: the name of the model. E.g., "Post".
+    - `args`: Prisma query args. E.g., `{ where: { published: true } }`.
+    - `options`: swr options.
+
+    The `data` field returned by the hooks call contains the Prisma query result.
+
+- **Mutation Hooks**
+
+    ```ts
+    function use[Operation][Model](options?);
+    ```
+
+    - `[Operation]`: mutation operation. E.g., "Create", "UpdateMany".
+    - `[Model]`: the name of the model. E.g., "Post".
+    - `options`: swr options.
+
+    The `trigger` function returned with the hooks call takes the corresponding Prisma mutation args as input. E.q., `{ data: { title: 'Post1' } }`.
+
 
 ### Example
 
@@ -147,7 +177,7 @@ const Posts = ({ userId }: { userId: string }) => {
 };
 ```
 
-### Automatic Optimistic Update (Preview)
+### Automatic Optimistic Update
 
 Optimistic update is a technique that allows you to update the data cache immediately when a mutation executes while waiting for the server response. It helps achieve a more responsive UI. SWR provides the [infrastructure for implementing it](https://swr.vercel.app/docs/mutation#optimistic-updates).
 
@@ -187,7 +217,11 @@ const { data } = useFindManyPost(
 
 When a query opts out, it won't be updated by a mutation, even if the mutation is set to update optimistically.
 
-#### Using Infinite Query
+### Fine-Grained Optimistic Update
+
+<FineGrainedOptimistic />
+
+### Using Infinite Query
 
 See [SWR's documentation](https://swr.vercel.app/docs/pagination) for more details.
 
diff --git a/docs/reference/plugins/tanstack-query.mdx b/docs/reference/plugins/tanstack-query.mdx
index 20880b2c..9f54ef75 100644
--- a/docs/reference/plugins/tanstack-query.mdx
+++ b/docs/reference/plugins/tanstack-query.mdx
@@ -8,14 +8,15 @@ import TabItem from '@theme/TabItem';
 import Invalidation from './_invalidation.md';
 import OptimisticBehavior from './_optimistic-behavior.md';
 import OptmisticLimitation from './_optimistic-limitation.md';
+import FineGrainedOptimistic from './_fine-grained-optimistic.md';
 
 # @zenstackhq/tanstack-query
 
 :::info
-If you're looking for generating hooks for [SWR](https://swr.vercel.app/), please checkout the [`@zenstackhq/swr`](/docs/reference/plugins/swr) plugin.
+If you're looking for generating hooks for [SWR](https://swr.vercel.app/), please checkout the [`@zenstackhq/swr`](./swr) plugin.
 :::
 
-The `@zenstackhq/tanstack-query` plugin generates [Tanstack Query](https://tanstack.com/query/latest) hooks that call into the CRUD services provided by the [server adapters](/docs/category/server-adapters). The plugin currently supports React and Svelte. Vue support is coming soon.
+The `@zenstackhq/tanstack-query` plugin generates [Tanstack Query](https://tanstack.com/query/latest) hooks that call into the CRUD services provided by the [server adapters](../../category/server-adapters). The plugin currently supports React and Svelte. Vue support is coming soon.
 
 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).
 
@@ -29,12 +30,44 @@ npm install --save-dev @zenstackhq/tanstack-query
 
 ### Options
 
-| Name    | Type   | Description                                             | Required | Default |
-| ------- | ------ | ------------------------------------------------------- | -------- | ------- |
-| output  | String | Output directory (relative to the path of ZModel)                                        | Yes     |         |
-| target  | String | Target framework to generate for. Choose from "react", "vue", and "svelte".              | Yes     |         |
-| version | String | Version of TanStack Query to generate for. Choose from "v4" and "v5".                    | No      | v4      |
-| useSuperJson  | Boolean | Use [superjson](https://github.com/blitz-js/superjson) for data serialization     | No      | false   |
+| Name     | Type    | Description                                             | Required | Default |
+| -------- | ------- | ------------------------------------------------------- | -------- | ------- |
+| output   | String  | Output directory (relative to the path of ZModel)                                        | Yes     |         |
+| target   | String  | Target framework to generate for. Choose from "react", "vue", and "svelte".              | Yes     |         |
+| version  | String  | Version of TanStack Query to generate for. Choose from "v4" and "v5".                    | No      | v5      |
+| portable | Boolean | Include TypeScript types needed to compile the generated code in the output directory. Useful when you output into another project that doesn't reference Prisma and ZenStack. You'll still need to install the "@zenstackhq/tanstack-query" package in that project. | No      | false   |
+
+### Hooks Signature
+
+The generated hooks have the following signature convention.
+
+- **Query Hooks**
+
+    ```ts
+    function use[Operation][Model](args?, options?);
+    ```
+
+    - `[Operation]`: query operation. E.g., "FindMany", "FindUnique", "Count".
+    - `[Model]`: the name of the model. E.g., "Post".
+    - `args`: Prisma query args. E.g., `{ where: { published: true } }`.
+    - `options`: tanstack-query options.
+
+    The hook function returns a standard TanStack Query [`useQuery`](https://tanstack.com/query/latest/docs/framework/react/reference/useQuery) result, plus an added `queryKey` field.
+    
+    - The `data` field contains the Prisma query result.
+    - The `queryKey` field is the query key used to cache the query result. It can be used to manipulate the cache directly or [cancel the query](#query-cancellation).
+
+- **Mutation Hooks**
+
+    ```ts
+    function use[Operation][Model](options?);
+    ```
+
+    - `[Operation]`: mutation operation. E.g., "Create", "UpdateMany".
+    - `[Model]`: the name of the model. E.g., "Post".
+    - `options`: TanStack-Query options.
+
+    The hook function returns a standard TanStack Query [`useMutation`](https://tanstack.com/query/latest/docs/framework/react/reference/useMutation) result. The `mutate` and `mutateAsync` functions returned take the corresponding Prisma mutation args as input. E.q., `{ data: { title: 'Post1' } }`.
 
 ### Context Provider
 
@@ -52,7 +85,7 @@ Example for using the context provider:
 
 <Tabs>
 
-<Tab value="react" label="React">
+<TabItem value="react" label="React">
 
 ```tsx title='_app.tsx'
 import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
@@ -84,9 +117,9 @@ function MyApp({ Component, pageProps: { session, ...pageProps } }: AppProps) {
 export default MyApp;
 ```
 
-</Tab>
+</TabItem>
 
-<Tab value="vue" label="Vue">
+<TabItem value="vue" label="Vue">
 
 ```html title='App.vue'
 <script setup lang="ts">
@@ -112,9 +145,9 @@ provideHooksContext({
     <!-- App Content -->
 </template>
 ```
-</Tab>
+</TabItem>
 
-<Tab value="svelte" label="Svelte">
+<TabItem value="svelte" label="Svelte">
 
 ```svelte title='+layout.svelte'
 <script lang="ts">
@@ -146,10 +179,35 @@ provideHooksContext({
     </QueryClientProvider>
 </div>
 ```
-</Tab>
+</TabItem>
 
 </Tabs>
 
+:::info Notes about Next.js app router
+
+Next.js's app router renders components as React Server Components (RSC) by default. React context providers are not supported in RSC. If you're configuring the context providers in such a setup, please make sure to wrap them inside a client component, or set the containing layout as client component.
+
+```tsx title='providers.tsx'
+'use client';
+
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
+import { Provider as ZenStackHooksProvider } from "@/hooks/generated";
+import type { ReactNode } from 'react';
+
+const queryClient = new QueryClient();
+
+export default function Providers({ children }: { children: ReactNode }) {
+    return (
+        <QueryClientProvider client={queryClient}>
+            <ZenStackHooksProvider value={{ endpoint: '/api/model' }}>
+                {children}
+            </ZenStackHooksProvider>
+        </QueryClientProvider>
+    );
+}
+```
+:::
+
 ### Example
 
 Here's a quick example with a ReactJs blogging app. You can find a fully functional Todo app example [here](https://github.com/zenstackhq/sample-todo-nextjs-tanstack).
@@ -214,7 +272,7 @@ function App({ Component, pageProps: { session, ...pageProps } }: AppProps) {
 
 </TabItem>
 
-<Tab value="vue" label="Vue">
+<TabItem value="vue" label="Vue">
 
 ```ts title='main.ts'
 import { createApp } from 'vue';
@@ -239,7 +297,7 @@ provideHooksContext({
     <!-- App Content -->
 </template>
 ```
-</Tab>
+</TabItem>
 
 <TabItem value="svelte" label="Svelte">
 
@@ -364,6 +422,18 @@ const onCreate = () => {
     </div>
 </template>
 ```
+:::tip
+ If the query parameters are not constant, you need to wrap the query in a `computed` to make it reactive.
+```ts
+const queryParams = computed(() => ({
+    where: { 
+        space: { slug: route.params.slug as string }, 
+        id: meQuery.data.value?.userId 
+    },
+}));
+const { data: posts } = useFindManyPost(queryParams);
+```
+:::
 
 </TabItem>
 
@@ -406,20 +476,19 @@ const onCreate = () => {
 </TabItem>
 </Tabs>
 
-### Automatic Optimistic Update (Preview)
+### Automatic Optimistic Update
 
 Optimistic update is a technique that allows you to update the data cache immediately when a mutation executes while waiting for the server response. It helps achieve a more responsive UI. TanStack Query provides the [infrastructure for implementing it](https://tanstack.com/query/v5/docs/react/guides/optimistic-updates).
 
-The ZenStack-generated mutation hooks allow you to opt-in to "automatic optimistic update" by passing an extra `optimisticUpdate` argument when calling the hook. When the mutation executes, it analyzes the current queries in the cache and tries to find the ones that need to be updated. When the mutation settles (either succeeded or failed), the queries are invalidated to trigger a re-fetch.
+The ZenStack-generated mutation hooks allow you to opt-in to "automatic optimistic update" by passing the `optimisticUpdate` option when calling the hook. When the mutation executes, it analyzes the current queries in the cache and tries to find the ones that need to be updated. When the mutation settles (either succeeded or failed), the queries are invalidated to trigger a re-fetch.
 
 Here's an example:
 
 ```ts
-// arguments are mutation options, invalidate, and optimisticUpdate
-const create = useCreatePost(undefined, true, true);
+const { mutate: create } = useCreatePost({ optimisticUpdate: true });
 
 function onCreatePost() {
-    create.mutate({ ... })
+    create({ ... })
 }
 ```
 
@@ -435,21 +504,24 @@ When `mutate` executes, if there are active queries like `useFindManyPost()`, th
 
 #### Opt-out
 
-By default, all queries opt into automatic optimistic update. You can opt-out on a per-query basis by passing `false` to the `optimisticUpdate` argument.
+By default, all queries opt into automatic optimistic update. You can opt-out on a per-query basis by passing `false` to the `optimisticUpdate` option.
 
 ```ts
-// arguments are query args, query options, and optimisticUpdate
-const { data } = useFindManyPost({ where: { published: true } }, undefined, false);
+const { data } = useFindManyPost({ where: { published: true } }, { optimisticUpdate: false });
 ```
 
 When a query opts out, it won't be updated by a mutation, even if the mutation is set to update optimistically.
 
-#### Using Infinite Query
+### Fine-Grained Optimistic Update
+
+<FineGrainedOptimistic />
+
+### Using Infinite Query
 
 <Tabs>
 <TabItem value="react" label="React">
 
-See [Tanstack Query documentation](https://tanstack.com/query/v4/docs/react/guides/infinite-queries) for more details.
+Here's a quick example of using infinite query to load a list of posts with infinite pagination. See [Tanstack Query documentation](https://tanstack.com/query/v4/docs/react/guides/infinite-queries) for more details.
 
 ```tsx title='/src/components/Posts.tsx'
 import { useInfiniteFindManyPost } from '../lib/hooks';
@@ -504,7 +576,7 @@ const Posts = () => {
 
 <TabItem value="vue" label="Vue">
 
-See [Tanstack Query documentation](https://tanstack.com/query/v4/docs/vue/guides/infinite-queries) for more details.
+Here's a quick example of using infinite query to load a list of posts with infinite pagination. See [Tanstack Query documentation](https://tanstack.com/query/v4/docs/vue/guides/infinite-queries) for more details.
 
 ```html title='/src/components/Posts.vue'
 <script setup lang="ts">
@@ -557,7 +629,7 @@ const { data, hasNextPage, fetchNextPage } = useInfiniteFindManyPost(
 
 <TabItem value="svelte" label="Svelte">
 
-See [Tanstack Query documentation](https://tanstack.com/query/v4/docs/svelte/examples/svelte/load-more-infinite-scroll) for more details.
+Here's a quick example of using infinite query to load a list of posts with infinite pagination. See [Tanstack Query documentation](https://tanstack.com/query/v4/docs/svelte/examples/svelte/load-more-infinite-scroll) for more details.
 
 ```svelte title='/src/components/Posts.svelte'
 <script lang="ts">
@@ -618,11 +690,10 @@ See [Tanstack Query documentation](https://tanstack.com/query/v4/docs/svelte/exa
 
 :::info
 
-The automatic invalidation is enabled by default, and you can use the `invalidateQueries` argument to opt-out and handle revalidation by yourself.
+The automatic invalidation is enabled by default, and you can use the `invalidateQueries` option to opt-out and handle revalidation by yourself.
 
 ```ts
-// arguments are mutation options and invalidateQueries
-useCreatePost(undefined, false);
+useCreatePost({ invalidateQueries: false });
 ```
 :::
 
@@ -647,3 +718,36 @@ will be:
 ```
 
 You can use the generated `getQueryKey` function to compute it.
+
+The query hooks also return the query key as part of the result object.
+
+```ts
+const { data, queryKey } = useFindUniqueUser({ where: { id: '1' } });
+```
+
+### Query Cancellation
+
+You can use TanStack Query's [`queryClient.cancelQueries`](https://tanstack.com/query/latest/docs/reference/QueryClient#queryclientcancelqueries) API to cancel a query. The easiest way to do this is to use the `queryKey` returned by the query hook.
+
+```ts
+const queryClient = useQueryClient();
+const { queryKey } = useFindUniqueUser({ where: { id: '1' } });
+
+function onCancel() {
+    queryClient.cancelQueries({ queryKey, exact: true });
+}
+```
+
+When a cancellation occurs, the query state is reset and the ongoing `fetch` call to the CRUD API is aborted.
+
+## FAQ
+
+### Next.js error "createContext" is not a function
+
+If you see an error like:
+
+```plain
+Error in Next.js: "TypeError: (0 , react__WEBPACK_IMPORTED_MODULE_0__.createContext) is not a function"
+```
+
+It's usually due to you're using the hooks in a React server component. Make sure to add the `'use client;'` directive at the top of your component file.
diff --git a/docs/reference/plugins/trpc.md b/docs/reference/plugins/trpc.md
index 2385d075..92f43aea 100644
--- a/docs/reference/plugins/trpc.md
+++ b/docs/reference/plugins/trpc.md
@@ -8,7 +8,7 @@ sidebar_position: 7
 The `@zenstackhq/trpc` plugin generates a [tRPC router](https://trpc.io/docs/router) to make database CRUD calls. You can use it directly as your main router or merge it with other routers to create a more complex setup. The router syntactically mirrors the APIs of a standard Prisma client, including the function names and shapes of parameters (hooks directly use types generated by Prisma). 
 
 :::info
-The `@zenstackhq/trpc` plugin depends on the [`@core/zod`](/docs/reference/plugins/zod) plugin to generate Zod schemas for the routers' input, and will enable that plugin automatically if it's not already enabled.
+The `@zenstackhq/trpc` plugin depends on the [`@core/zod`](./zod) plugin to generate Zod schemas for the routers' input, and will enable that plugin automatically if it's not already enabled.
 :::
 
 This plugin is based on [prisma-trpc-generator](https://github.com/omar-dulaimi/prisma-trpc-generator). Thanks to [Omar Dulaimi](https://github.com/omar-dulaimi) for making this happen!
@@ -24,9 +24,11 @@ npm install --save-dev @zenstackhq/trpc
 | Name   | Type   | Description      | Required | Default |
 | ------ | ------ | ---------------- | -------- | ------- |
 | output | String | Output directory (relative to the path of ZModel) | Yes      | |
-| generateModels | String, String[] | Array or comma separated string for the models to generate routers for. | No      | All models |
+| version | String | tRPC version to target - "v10" or "v11". "v11" support is still in preview. | No      | v10 |
+| importCreateRouter | String | Only needed when "version" is set to "v11". This option tells the code generator where to import the `createTRPCRouter` tRPC router factory object from. | Yes (v11) | |
+| importProcedure | String | Only needed when "version" is set to "v11". This option tells the code generator where to import the `procedure` tRPC procedure factory object from. | Yes (v11) | || generateModels | String, String[] | Array or comma separated string for the models to generate routers for. | No      | All models |
 | generateModelActions | String, String[] | Array or comma separated string for actions to generate for each model: `create`, `findUnique`, `update`, etc. | No      | All supported Prisma actions |
-| generateClientHelpers | String, String[] | Array or comma separated string for the types of client helpers to generate. Supported values: "react" or "next". See [here](#client-helpers) for more details. | No      | |
+| generateClientHelpers | String, String[] | Array or comma separated string for the types of client helpers to generate. Supported values: "react", "next", or "nuxt". See [here](#client-helpers) for more details. | No      | |
 | zodSchemasImport | String | Import path for the generated zod schemas. The trpc plugin relies on the `@core/zod` plugin to generate zod schemas for input validation. If you set a custom output location for the zod schemas, you can use this option to override the import path. | No      | @zenstackhq/runtime/zod |
 
 :::info
@@ -35,13 +37,13 @@ When `@core/zod` plugin is automatically enabled by the `@zenstackhq/trpc` plugi
 
 ## Dependencies
 
-- [`@core/zod`](/docs/reference/plugins/zod)
+- [`@core/zod`](./zod)
 
 ### Details
 
 #### Preparing tRPC Context
 
-The generated tRPC routers require a `prisma` field in the context and you need to make sure to include it when creating the context. You can use any PrismaClient instance for it, but most likely you want to use one created with [`enhance`](/docs/reference/runtime-api#enhance) so that the client enforces access control policies.
+The generated tRPC routers require a `prisma` field in the context and you need to make sure to include it when creating the context. You can use any PrismaClient instance for it, but most likely you want to use one created with [`enhance`](../runtime-api#enhance) so that the client enforces access control policies.
 
 ```ts
 export const createContext = async ({ req, res }: CreateNextContextOptions) => {
@@ -88,7 +90,7 @@ plugin trpc {
 }
 ```
 
-Use value "next" if you're using tRPC with Next.js, or "react" if using it with react-query. With that option on, the plugin generates an extra "client" folder containing helpers that do the type fixing. The only code change you need to make is instead of calling the `createTRPCNext` or `createTRPCReact` API, you call the one generated instead.
+Use value "next" if you're using tRPC with Next.js, or "react" if using it with react-query, or "nuxt" for [trpc-nuxt](https://trpc-nuxt.vercel.app/). With that option on, the plugin generates an extra "client" folder containing helpers that do the type fixing. The only code change you need to make is instead of calling the `createTRPCNext`/`createTRPCReact`/`createTRPCNuxtClient` API, you call the one generated instead.
 
 Here's an example of how to use it with Next.js:
 
@@ -99,6 +101,16 @@ export const trpc = createTRPCNext<AppRouter>({
 });
 ```
 
+:::info
+If your generated trpc procedures are installed at a non-top-level path, you can specify the path with the second type parameter of the `createTRPCNext`, `createTRPCReact`, or `createTRPCNuxtClient` function.
+
+```ts
+// if the generated procedures are installed at `/crud` under the router tree
+export const trpc = createTRPCNext<AppRouter, 'crud'>({
+    ...
+});
+:::
+
 ### Example
 
 Here's an example with a blogging app:
@@ -145,4 +157,4 @@ export const appRouter = createCRUDRouter(t.router, t.procedure);
 export type AppRouter = typeof appRouter;
 ```
 
-Check out the [Using With tRPC](/docs/guides/trpc) guide for more details about using ZenStack in a tRPC project.
+Check out the [Using With tRPC](../../guides/trpc) guide for more details about using ZenStack in a tRPC project.
diff --git a/docs/reference/plugins/zod.md b/docs/reference/plugins/zod.md
index 0a7c4050..39145cdc 100644
--- a/docs/reference/plugins/zod.md
+++ b/docs/reference/plugins/zod.md
@@ -6,11 +6,11 @@ sidebar_position: 4
 # @core/zod
 
 The `@core/zod` plugin generates [Zod](https://github.com/colinhacks/zod) schemas for models and input arguments of Prisma CRUD operations. The plugin is automatically enabled when any of the following conditions meets:
-- If any model carries [field validation attributes](/docs/reference/zmodel-language#field-validation).
-- If any plugin that depends on it is used, e.g., the [tRPC plugin](/docs/reference/plugins/trpc).
+- If any model carries [field validation attributes](../zmodel-language#data-validation).
+- If any plugin that depends on it is used, e.g., the [tRPC plugin](./trpc).
 
 :::info
-You need to explicitly enable `@core/zod` plugin if you use the [Express.js](/docs/reference/server-adapters/express) or [Fastify](/docs/reference/server-adapters/fastify) server adapter with `zodSchemas` option enabled.
+You need to explicitly enable `@core/zod` plugin if you use the [Express.js](../server-adapters/express) or [Fastify](../server-adapters/fastify) server adapter with `zodSchemas` option enabled.
 :::
 
 By default, the Zod schemas are generated into `node_modules/.zenstack/zod` directory, and are reexported through `@zenstackhq/runtime/zod`. If you configure the plugin to output to a custom location, you can just directly import from there.
@@ -19,23 +19,125 @@ The generated schemas have the following three parts:
 
 - `zod/models`
     
-    The schema for validating the models, containing field's typing and [validation rules](/docs/reference/zmodel-language#field-validation). Relation fields and foreign key fields are ignored. For each model, three schemas are generated respectively:
-  
+    The schemas for validating the models and input data for creating and updating the models. They contain field's typing and [validation rules](../zmodel-language#data-validation). The module exports several Zod schemas for each model for different use cases. To facilitate the discussion, let's use the following model as an example:
+
+    ```zmodel
+    model Post {
+      id Int @id @default(autoincrement())
+      title String @length(10, 255)
+      published Boolean @default(false)
+      createdAt DateTime @default(now())
+      updatedAt DateTime @updatedAt
+      author User @relation(fields: [authorId], references: [id])
+      authorId Int
+
+      @@validate(regex(title, "^[A-Za-z0-9 ]+$"))
+    }
+    ```
+    
     - *[Model]Schema*
 
-        The schema for validating the model itself. All non-optional fields are required.
+        The schema for validating the model itself, including all scalar fields, foreign key fields, and relation fields. All relation fields are optional.
+
+        ```ts
+        const PostSchema = z.object({
+          id: z.number(),
+          title: z.string().min(10).max(255),
+          published: z.boolean(),
+          createdAt: z.date(),
+          updatedAt: z.date(),
+          author: z.record(z.unknown()).optional(),
+          authorId: z.number(),
+        });
+        ```
+
+    - *[Model]CreateScalarSchema*
+
+        The schema for validating the input for creating the model. It only includes scalar fields. All fields are required unless it's optional in the model or has a default value.
+
+        ```ts
+        const PostCreateScalarSchema = z.object({
+          id: z.number().optional(),
+          title: z.string().min(10).max(255),
+          published: z.boolean().optional(),
+          createdAt: z.date().optional(),
+          updatedAt: z.date().optional(),
+        });
+        ```
 
     - *[Model]CreateSchema*
-
-        The schema for validating the data for creating the model. Similar to "[Model]Schema" but all fields with default values are marked optional.
+  
+        The schema for validating the input for creating the model. It's similar to `[Model]CreateScalarSchema` but includes foreign key fields. You can use it to validate create input that involves setting foreign keys.
+
+        ```ts
+        const PostCreateSchema = z.object({
+          id: z.number().optional(),
+          title: z.string().min(10).max(255),
+          published: z.boolean().optional(),
+          createdAt: z.date().optional(),
+          updatedAt: z.date().optional(),
+          authorId: z.number(),
+        });
+        ```
+
+    - *[Model]UpdateScalarSchema*
+    
+        The schema for validating the input for updating the model. It only includes scalar fields. All fields are optional (since in Prisma's semantic, all updates are patching).
+
+        ```ts
+        const PostUpdateScalarSchema = z.object({
+          id: z.number(),
+          title: z.string().min(10).max(255),
+          published: z.boolean(),
+          createdAt: z.date(),
+          updatedAt: z.date(),
+        }).partial();
+        ```
 
     - *[Model]UpdateSchema*
     
-        The schema for validating the data for updating the model. Similar to "[Model]Schema" but all fields are marked optional.
+        Similar to `[Model]UpdateScalarSchema` but includes foreign key fields (all optional). You can use it to validate update input that involves setting foreign keys.
+
+        ```ts
+        const PostUpdateSchema = z.object({
+          id: z.number(),
+          title: z.string().min(10).max(255),
+          published: z.boolean(),
+          createdAt: z.date(),
+          updatedAt: z.date(),
+          authorId: z.number(),
+        }).partial();
+        ```
+
+    - *[Model]???WithoutRefinementSchema*
+  
+        This includes `[Model]WithoutRefinementSchema`, `[Model]CreateWithoutRefinementSchema`, and `[Model]UpdateWithoutRefinementSchema`. These are only generated when a model contains `@@validate` model-level data validation rules.
+
+        The `@@validate` rules are translated into `z.refine()` calls. For example, for the `Post` model, the following refinement function is generated:
+
+        ```ts
+        function refinePost<T, D extends z.ZodTypeDef>(schema: z.ZodType<T, D, T>) {
+          return schema.refine((value: any) => new RegExp('^[A-Za-z0-9 ]+$').test(value?.title));
+        }
+        ```
+
+        However, zod has a limitation that, when you call `.refine()` on a `ZodObject` schema, the result won't be an object schema anymore, which means that you can't use `.omit()`, `.partial()`, etc. `ZodObject` methods to further tune it for your needs anymore. That's why a series of `WithoutRefinementSchema` schemas are exported. They are the Zod schemas prior to calling the `refine()` method. If you need to make changes to the schema while still preserving the `@@validate` rules, you can manipulate the `WithoutRefinementSchema` schemas and then call the `refine` function manually on the result. E.g.:
+
+        ```ts
+        const myPostCreateSchema = refinePost(PostCreateWithoutRefinementSchema.omit({id: true}));
+        ```
+
+    - *[Model]PrismaCreateSchema*
+  
+        Used internally by ZenStack.
+
+    - *[Model]PrismaUpdateSchema*
+  
+        Used internally by ZenStack.
 
 - `zod/input`
 
-    The schema for validating the input arguments of Prisma CRUD operations. You usually won't use them directly. The [tRPC plugin](/docs/reference/plugins/trpc) relies on them to validate the input arguments in the generated routers.
+    The schema for validating the input arguments of Prisma CRUD operations. You usually won't use them directly. The [tRPC plugin](./trpc) relies on them to validate the input arguments in the generated routers.
 
 - `zod/objects`
 
@@ -49,12 +151,13 @@ This plugin is built-in to ZenStack and does not need to be installed separately
 
 | Name   | Type   | Description      | Required | Default                    |
 | ------ | ------ | ---------------- | -------- | -------------------------- |
-| output | String | Output directory (relative to the path of ZModel) | No       | node_modules/.zenstack/zod |
+| output | String | Output directory (relative to the path of ZModel). This option will be deprecated in future releases in favor of the "--output" CLI option of `zenstack generate`. | No       | node_modules/.zenstack/zod |
 | modelOnly | Boolean | Only generate schemas for the models, but not for the Prisma CRUD input arguments | No | false |
 | generateModels | String, String[] | Array or comma separated string for the models to generate routers for. | No      | All models |
 | compile | Boolean | If the generated TS code should be compiled to JS | No | true |
-| preserveTsFiles | Boolean | If the generated TS files should be preserved (after compiled to JS) | No | true if `compile` is set to false, otherwise false |
+| preserveTsFiles | Boolean | If the generated TS files should be preserved (after compiled to JS) | No | false |
 | noUncheckedInput | Boolean | Disables schema generation for Prisma's ["Unchecked"](https://github.com/prisma/prisma/discussions/10121#discussioncomment-1621254) input types | No | false |
+| mode | String | Controls if the generated schemas should reject, strict, or passthrough unknown fields. Possible values: "strict", "strip", "passthrough" | No | "strict" |
 
 :::info
 When the `generateModels` option is used to specify a list of models to generate, the plugin will also recursively traverse and include all models that are referenced by the specified models. This can result in more code being generated than you expect.
@@ -78,12 +181,14 @@ import { PostCreateSchema } from '@zenstackhq/runtime/zod/models';
 PostCreateSchema.parse(data);
 ```
 
-You can turn off the `compile` option and use a custom `output` location if you want the generated Zod schema to be compiled along with your own Typescript project:
+You can use the "--output" option of the `zenstack generate` CLI to specify a custom output location for the generated Zod schemas:
 
-```zmodel title='/schema.zmodel'
-plugin zod {
-  provider = '@core/zod'
-  output = 'src/lib/zod'
-  compile = false
-}
+```bash
+zenstack generate --output src/lib/zenstack
+```
+
+If you want to have the code generated as TypeScript (instead of compiled to JavaScript), you can add the "--no-compile" option:
+
+```bash
+zenstack generate --no-compile --output src/lib/zenstack
 ```
diff --git a/docs/reference/prisma-client-ext.md b/docs/reference/prisma-client-ext.md
new file mode 100644
index 00000000..39e0d240
--- /dev/null
+++ b/docs/reference/prisma-client-ext.md
@@ -0,0 +1,61 @@
+---
+description: APIs ZenStack adds to the PrismaClient
+sidebar_position: 5
+sidebar_label: Added PrismaClient APIs
+---
+
+# Added PrismaClient APIs
+
+ZenStack's enhancement to PrismaClient not only alters its existing APIs' behavior, but also adds new APIs.
+
+### check
+
+#### Scope
+
+This API is added to each model in the PrismaClient.
+
+:::warning
+The API is not supported on edge runtime (e.g., Cloudflare Workers or Vercel Edge). You'll get a runtime error when calling it.
+:::
+
+#### Description
+
+Checks if the current user is allowed to perform the specified operation on the model based on the access policies in ZModel. The check is done via pure logical inference and doesn't query the database. It currently only deals with model-level policies and field-level ones are ignored.
+
+Please refer to [Checking Permissions Without Hitting the Database](../guides/check-permission) for more details.
+
+:::danger
+
+Permission checking is an approximation and can be over-permissive. You MUST NOT trust it and circumvent the real access control mechanism (e.g., calling raw Prisma CRUD operations without further authorization checks).
+
+:::
+
+#### Signature
+
+```ts
+type CheckArgs = {
+  /**
+   * The operation to check for
+   */
+  operation: 'create' | 'read' | 'update' | 'delete';
+
+  /**
+   * The optional additional constraints to impose on the model fields
+   */
+  where?: { ... };
+}
+
+check(args: CheckArgs): Promise<boolean>;
+```
+
+#### Example
+
+```ts
+const db = enhance(prisma, { user: getCurrentUser() });
+
+// check if the current user can read published posts
+await canRead = await db.post.check({
+  operation: 'read',
+  where: { published: true }
+});
+```
diff --git a/docs/reference/runtime-api.md b/docs/reference/runtime-api.md
index 75e5ab02..10d7cf39 100644
--- a/docs/reference/runtime-api.md
+++ b/docs/reference/runtime-api.md
@@ -8,26 +8,18 @@ sidebar_label: Runtime API
 
 This document provides references for runtime APIs exported from the `@zenstackhq/runtime` package.
 
-## Prisma Enhancement API
-
-Enhancement APIs create wrappers around a Prisma client to add additional behavior. These wrappers can be freely combined to fine-tune what behavior to include.
-
 ### enhance
 
 #### Description
 
-Calling `enhance` is the simplest way to include all essential enhancements offered by ZenStack, including access policies, field validation, field omission, and password hashing. It's equivalent to calling:
-
-```ts
-withOmit(withPassword(withPolicy(prisma, options)));
-```
+Creates an enhanced wrapper for a `PrismaClient`. The return value has the same APIs as the original `PrismaClient`.
 
 #### Signature
 
 ```ts
 function enhance<DbClient extends object>(
     prisma: DbClient,
-    context?: WithPolicyContext,
+    context?: EnhancementContext,
     options?: EnhancementOptions
 ): DbClient;
 ```
@@ -41,7 +33,7 @@ The PrismaClient instance to enhance.
 The context to for evaluating access policies with the following typing.
 
 ```ts
-type WithPolicyContext = {
+type EnhancementContext = {
     user?: Record<string, unknown>
 };
 ```
@@ -55,213 +47,84 @@ type WithPolicyContext = {
 Options with the following typing.
 
 ```ts
-type EnhancementOptions = {
-    loadPath?: string;
-    policy?: PolicyDef;
-    modelMeta?: ModelMeta;
-    logPrismaQuery?: boolean;
-};
-```
-
-| Field          | Description                                                                                      | Default                                                                         |
-| -------------- | ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------- |
-| loadPath       | Path for loading CLI-generated code, including model metadata, access policies, and zod schemas. | node_modules/.zenstack                                                          |
-| policy         | The access policy data, generated by `@core/access-policy` plugin.                               | Loaded from the default location or the path specified by the "loadPath" option |
-| modelMeta      | The model metadata, generated by `@core/model-meta` plugin.                                      | Loaded from the default location or the path specified by the "loadPath" option |
-| zodSchemas     | The zod schemas, generated by `@core/zod` plugin.                                                | Loaded from the default location or the path specified by the "loadPath" option |
-| logPrismaQuery | Whether to log queries sent to Prisma client. Log will be emitted with "info" level, so please make sure you [turn that level on](https://www.prisma.io/docs/concepts/components/prisma-client/working-with-prismaclient/logging#log-to-stdout) when creating Prisma client | false                        |
-
-#### Example
-
-```ts
-const session = getSession();
-const enhancedClient = enhance(prisma, { user: session.user });
-```
-
-### withPresets
-
-#### Description
-
-:::warning
-This API is equivalent to [`enhance`](#enhance) and will be deprecated soon.
-:::
-
-### withPolicy
-
-Use `withPolicy` to include enhancements of access policy (`@@allow` and `@@deny`) and field validation (`@length`, `@email`, etc.).
-
-Access policies and validation rules are processed by the `@core/access-policy` plugin, which transforms the attributes into objects that can be loaded at runtime by default into `node_modules/.zenstack` folder. `withPolicy` function also loads from there by default.
-
-If your access policy rules use the `auth()` function, evaluating it requires access to the current user's identity. You need to pass in a context user object that at least contains the user id.
-
-#### Signature
-
-```ts
-function withPolicy<DbClient extends object>(
-    prisma: DbClient,
-    context?: WithPolicyContext,
-    options?: WithPolicyOptions
-): DbClient;
-```
-
-##### Parameter `prisma`
-
-The PrismaClient instance to enhance.
-
-##### Parameter `context`
-
-The context to for evaluating access policies with the following typing.
+type TransactionIsolationLevel =
+    | 'ReadUncommitted'
+    | 'ReadCommitted'
+    | 'RepeatableRead'
+    | 'Snapshot'
+    | 'Serializable';
+
+type SimpleEncryption = {
+    encryptionKey: Uint8Array;
+    decryptionKeys?: Uint8Array[];
+}
 
-```ts
-type WithPolicyContext = {
-    user?: Record<string, unknown>
+type CustomEncryption = {
+    encrypt: (model: string, field: FieldInfo, plain: string) => Promise<string>;
+    decrypt: (model: string, field: FieldInfo, cipher: string) => Promise<string>;
 };
-```
-
-| Field | Description | Default |
-| ----- | ----------- | ------- |
-| user  | The user object that provides value for the `auth()` function call in access policies. If provided. Its shape should be consistent with the `User` model in your ZModel, with all fields optional except for id field(s). Pass `undefined` to represent an anonymous user, and the `auth()` function call will evaluate to `null` in that case. | undefined |
-
-##### Parameter `options`
 
-Options with the following typing.
-
-```ts
-type WithPolicyOptions = {
-    loadPath?: string;
-    policy?: PolicyDef;
-    modelMeta?: ModelMeta;
-    logPrismaQuery?: boolean;
+type ValidationOptions = {
+    inputOnlyValidationForUpdate?: boolean;
 };
-```
-
-| Field          | Description                                                                                      | Default                                                                         |
-| -------------- | ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------- |
-| loadPath       | Path for loading CLI-generated code, including model metadata, access policies, and zod schemas. | node_modules/.zenstack                                                          |
-| policy         | The access policy data, generated by `@core/access-policy` plugin.                               | Loaded from the default location or the path specified by the "loadPath" option |
-| modelMeta      | The model metadata, generated by `@core/model-meta` plugin.                                      | Loaded from the default location or the path specified by the "loadPath" option |
-| zodSchemas     | The zod schemas, generated by `@core/zod` plugin.                                                | Loaded from the default location or the path specified by the "loadPath" option |
-| logPrismaQuery | Whether to log queries sent to Prisma client. Log will be emitted with "info" level, so please make sure you [turn that level on](https://www.prisma.io/docs/concepts/components/prisma-client/working-with-prismaclient/logging#log-to-stdout) when creating Prisma client | false                        |
-
-#### Example
-
-```zmodel
-// ZModel
-model Post {
-    id String @id
-    title String
-    author User @relation(fields: [authorId], references: [id])
-    authorId String
 
-    @@allow('read', auth() == author)
-}
-```
-
-```ts
-const session = getSession();
-const enhanced = withPolicy(prisma, { user: session.user });
-// only posts belonging to the current user are returned
-const posts = enhanced.post.findMany();
-```
-
-### withOmit
-
-Use `withOmit` function to include support for the `@omit` attribute. Fields marked with the attribute will be removed from the entities when they're returned.
-
-#### Signature
-
-```ts
-function withOmit<DbClient extends object>(
-    prisma: DbClient,
-    options?: WithOmitOptions
-): DbClient;
-```
-
-##### Parameter `prisma`
-
-The PrismaClient instance to enhance.
-
-##### Parameter `options`
-
-Options with the following typing.
-
-```ts
-type WithOmitOptions = {
-    loadPath?: string;
-    modelMeta?: ModelMeta;
+type EnhancementOptions = {
+    kinds?: EnhancementKind[];
+    logPrismaQuery?: boolean;
+    errorTransformer?: ErrorTransformer;
+    transactionMaxWait?: number;
+    transactionTimeout?: number;
+    transactionIsolationLevel?: TransactionIsolationLevel;
+    encryption?: SimpleEncryption | CustomEncryption;
+    validation?: ValidationOptions;
 };
 ```
 
-| Field          | Description                                                                                      | Default                                                                         |
-| -------------- | ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------- |
-| loadPath       | Path for loading CLI-generated code, including model metadata, access policies, and zod schemas. | node_modules/.zenstack                                                          |
-| modelMeta      | The model metadata, generated by `@core/model-meta` plugin. Only need to pass it if you configured the plugin to generate into custom location.        | Loaded from default location |
+| Field                     | Description                                                                                      | Default                                                                         |
+| ------------------------- | ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------- |
+| kinds                     | The kinds of enhancements to apply. By default all enhancements are applied. See [the next section](#enhancement-kinds) for more details. | All enhancement kinds                                                          |
+| logPrismaQuery            | Whether to log queries sent to Prisma client. Log will be emitted with "info" level, so please make sure you [turn that level on](https://www.prisma.io/docs/concepts/components/prisma-client/working-with-prismaclient/logging#log-to-stdout) when creating Prisma client | false                        |
+| errorTransformer          | A function for transforming error thrown by the enhanced `PrismaClient` into a custom one. |                         |
+| transactionMaxWait        | The `maxWait` option (in ms) passed to `prisma.$transaction()` call for transactions initiated by ZenStack. |  Database default                       |
+| transactionTimeout        | The `timeout` option (in ms) passed to `prisma.$transaction()` call for transactions initiated by ZenStack. |  Database default                       |
+| transactionIsolationLevel | The `isolationLevel` option passed to `prisma.$transaction()` call for transactions initiated by ZenStack. |  Database default                       |
+| encryption | Field encryption settings. Only required when using the [field encryption](../guides/field-encryption.md) feature. |                         |
+| validation.inputOnlyValidationForUpdate | By default, ZenStack validates an entity after "update" operation to ensure the final result satisfies validation rules as a whole. This implies if the record under update doesn't satisfy the rules prior to update, the update operation will fail even if the fields causing validation errors are not affected by the operation. You can set this option to `true` to let ZenStack only validate data contained in the input args. | false |
 
-#### Example
-
-```zmodel
-// ZModel
-model User {
-    id String @id
-    email String
-    password String @omit
-}
-```
-
-```ts
-const enhanced = withOmit(prisma);
-// password field is removed from user entities
-const user = enhanced.user.findMany();
-```
+#### Enhancement Kinds
 
-### withPassword
+Here are the kinds of enhancements available:
 
-Use `withPassword` function to include support for the `@password` attribute. Fields marked with the attribute will be automatically hashed (using [bcryptjs](https://www.npmjs.com/package/bcryptjs)) before being stored.
+- `policy`
+  
+    Enforces model-level and field-level access policies defined with `@@allow`, `@@deny`, `@allow`, and `@deny`.
 
-#### Signature
+- `validation`
+  
+    Validates create and update input data against rules defined with [data validation attributes](../reference/zmodel-language#data-validation).
+  
+- `delegate`
+  
+    Support for modeling [polymorphic relations](../guides/polymorphism) with delegated types pattern.
+  
+- `password`
+  
+    Automatically hashes fields marked with the `@password` attribute using `bcryptjs` before saving to the database.
+  
+- `omit`
 
-```ts
-function withPassword<DbClient extends object = any>(
-    prisma: DbClient,
-    options?: WithPasswordOptions
-): DbClient;
-```
+    Automatically omits fields marked with the `@omit` attribute from read results.
 
-##### Parameter `prisma`
+- `encryption`
 
-The PrismaClient instance to enhance.
-
-##### Parameter `options`
-
-Options with the following typing.
-
-```ts
-type WithPasswordOptions = {
-    loadPath?: string;
-    modelMeta?: ModelMeta;
-};
-```
-
-| Field          | Description                                                                                      | Default                                                                         |
-| -------------- | ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------- |
-| loadPath       | Path for loading CLI-generated code, including model metadata, access policies, and zod schemas. | node_modules/.zenstack                                                          |
-| modelMeta      | The model metadata, generated by `@core/model-meta` plugin.                                      | Loaded from the default location or the path specified by the "loadPath" option |
+    Transparently encrypt and decrypt fields marked with the `@encrypted` attribute. See [this guide](../guides/field-encryption.md) for more details.
 
 #### Example
 
-```zmodel
-// ZModel
-model User {
-    id String @id
-    email String
-    password String @password
-}
-```
-
 ```ts
-const enhanced = withPassword(prisma);
-// password field is hashed before stored into the database
-const user = enhanced.user.create({
-    data: { email: 'foo@bar.com', password: 'mysecurepassword' },
-});
+const session = getSession();
+const enhancedClient = enhance(prisma,
+  { user: session.user },
+  { kinds: ['policy', 'password']}
+);
 ```
diff --git a/docs/reference/server-adapters/_category_.yml b/docs/reference/server-adapters/_category_.yml
index 21ccbbc0..f6e05f3e 100644
--- a/docs/reference/server-adapters/_category_.yml
+++ b/docs/reference/server-adapters/_category_.yml
@@ -1,4 +1,4 @@
-position: 5
+position: 7
 label: Server Adapters
 collapsible: true
 collapsed: true
diff --git a/docs/reference/server-adapters/_error-handling.md b/docs/reference/server-adapters/_error-handling.md
index 6c103350..db2a9b05 100644
--- a/docs/reference/server-adapters/_error-handling.md
+++ b/docs/reference/server-adapters/_error-handling.md
@@ -1,3 +1,3 @@
 ### Error Handling
 
-Refer to the specific sections for [RPC Handler](/docs/reference/server-adapters/api-handlers/rpc#http-status-code-and-error-responses) and [RESTful Handler](/docs/reference/server-adapters/api-handlers/rest#error-handling).
+Refer to the specific sections for [RPC Handler](./api-handlers/rpc#http-status-code-and-error-responses) and [RESTful Handler](./api-handlers/rest#error-handling).
diff --git a/docs/reference/server-adapters/_options.mdx b/docs/reference/server-adapters/_options.mdx
index 9a4d63e0..dee02d69 100644
--- a/docs/reference/server-adapters/_options.mdx
+++ b/docs/reference/server-adapters/_options.mdx
@@ -4,7 +4,19 @@ import CodeBlock from '@theme/CodeBlock';
 
     <blockquote>{props.getPrisma}</blockquote>
 
-    A callback for getting a PrismaClient instance for talking to the database. Usually you'll use an enhanced instance created with ZenStack's [`enhance`](/docs/reference/runtime-api#enhance) API to ensure access policies are enforced.
+    A callback for getting a PrismaClient instance for talking to the database. Usually you'll use an enhanced instance created with ZenStack's [`enhance`](../runtime-api#enhance) API to ensure access policies are enforced.
+
+-   logger (optional)
+
+    <blockquote>{'LoggerConfig'}</blockquote>
+
+    Configuration for customizing logging behavior.
+
+-   modelMeta (optional)
+
+    <blockquote>{'ModelMeta'}</blockquote>
+
+    Model metadata. By default loaded from the `node_module/.zenstack/model-meta` module. You can pass it in explicitly if you configured ZenStack to output to a different location. E.g.: `require('output/model-meta').default`.
 
 -   zodSchemas (optional)
 
@@ -21,12 +33,6 @@ import CodeBlock from '@theme/CodeBlock';
 
     Not passing this option or passing in `undefined` disables input validation.
 
--   useSuperJson (optional)
-
-    <blockquote>boolean</blockquote>
-
-    Whether to use [superjson](https://github.com/blitz-js/superjson) for data serialization. Defaults to `false`.
-
 -   handler (optional)
 
     <blockquote>{'(req: RequestContext) => Promise<Response>'}</blockquote>
@@ -40,11 +46,11 @@ import CodeBlock from '@theme/CodeBlock';
         The RPC-style handler can be created like:
 
         ```ts
-        import RPCApiHandler from '@zenstackhq/server/api/rpc';
+        import { RPCApiHandler } from '@zenstackhq/server/api';
         const handler = RPCApiHandler();
         ```
 
-        For more details, please check out [RPC API Handler](/docs/reference/server-adapters/api-handlers/rpc).
+        For more details, please check out [RPC API Handler](./api-handlers/rpc).
 
     - RESTful
 
@@ -53,8 +59,8 @@ import CodeBlock from '@theme/CodeBlock';
         The RESTful-style handler can be created like:
 
         ```ts
-        import RestApiHandler from '@zenstackhq/server/api/rest';
+        import { RestApiHandler } from '@zenstackhq/server/api';
         const handler = RestApiHandler({ endpoint: 'http://myhost/api' });
         ```
 
-        For more details, please check out [RESTful API Handler](/docs/reference/server-adapters/api-handlers/rest).
+        For more details, please check out [RESTful API Handler](./api-handlers/rest).
diff --git a/docs/reference/server-adapters/_using-api.mdx b/docs/reference/server-adapters/_using-api.mdx
index df3ee3ff..1d16241c 100644
--- a/docs/reference/server-adapters/_using-api.mdx
+++ b/docs/reference/server-adapters/_using-api.mdx
@@ -7,17 +7,17 @@ The APIs can be used in the following three ways:
 
     ZenStack provides plugins to generate client hooks from the ZModel targeting the most popular frontend data fetching libraries: [TanStack Query](https://tanstack.com/query/latest) and [SWR](https://swr.vercel.app/). The generated hooks can be used to make API calls to the server adapters. Refer to the follow docs for detailed usage:
 
-    - [`@zenstackhq/tanstack-query`](/docs/reference/plugins/tanstack-query)
-    - [`@zenstackhq/swr`](/docs/reference/plugins/swr)
+    - [`@zenstackhq/tanstack-query`](../plugins/tanstack-query)
+    - [`@zenstackhq/swr`](../plugins/swr)
     <br/><br/>
 
     :::info
-    The generated client hooks assumes the server adapter uses [RPC-style API handler](/docs/reference/server-adapters/api-handlers/rpc) (which is the default setting).
+    The generated client hooks assumes the server adapter uses [RPC-style API handler](./api-handlers/rpc) (which is the default setting).
     :::
 
 1. With direct HTTP calls
 
-    You can make direct HTTP calls to the server adapter using your favorite client libraries like `fetch` or `axios`. Refer to the documentation of the [API Handlers](/docs/reference/server-adapters/api-handlers/) for the API endpoints and data formats.
+    You can make direct HTTP calls to the server adapter using your favorite client libraries like `fetch` or `axios`. Refer to the documentation of the [API Handlers](./api-handlers/) for the API endpoints and data formats.
 
     Here's an example using `fetch`:
 
@@ -134,4 +134,4 @@ The APIs can be used in the following three ways:
 
 1. With third-party client generators
 
-    ZenStack provides an [OpenAPI](/docs/reference/plugins/openapi) plugin for generating Open API 3.x specification from the ZModel. The generated OpenAPI spec can be used to generate client libraries for various languages and frameworks. For example, you can use [openapi-typescript](https://github.com/drwpow/openapi-typescript) to generate a typescript client.
+    ZenStack provides an [OpenAPI](../plugins/openapi) plugin for generating Open API 3.x specification from the ZModel. The generated OpenAPI spec can be used to generate client libraries for various languages and frameworks. For example, you can use [openapi-typescript](https://github.com/drwpow/openapi-typescript) to generate a typescript client.
diff --git a/docs/reference/server-adapters/api-handlers/index.mdx b/docs/reference/server-adapters/api-handlers/index.mdx
index b247e03f..df4074ec 100644
--- a/docs/reference/server-adapters/api-handlers/index.mdx
+++ b/docs/reference/server-adapters/api-handlers/index.mdx
@@ -27,5 +27,5 @@ The following diagram illustrates their relationships with server adapters.
 
 Checkout the reference pages for detailed information about these API handlers:
 
-- [RPC API Handler](/docs/reference/server-adapters/api-handlers/rpc)
-- [RESTful API Handler](/docs/reference/server-adapters/api-handlers/rest)
+- [RPC API Handler](./rpc)
+- [RESTful API Handler](./rest)
diff --git a/docs/reference/server-adapters/api-handlers/rest.mdx b/docs/reference/server-adapters/api-handlers/rest.mdx
index cf645826..867e7a43 100644
--- a/docs/reference/server-adapters/api-handlers/rest.mdx
+++ b/docs/reference/server-adapters/api-handlers/rest.mdx
@@ -12,22 +12,31 @@ import DataTypeSerialization from './_data_type_serialization.md';
 
 ## Introduction
 
-The RESTful-style API handler exposes CRUD APIs as RESTful endpoints using [JSON:API](https://jsonapi.org/) as transportation format. The API handler is not meant to be used directly; instead, you should use it together with a [server adapter](/docs/category/server-adapters) which handles the request and response API for a specific framework.
+The RESTful-style API handler exposes CRUD APIs as RESTful endpoints using [JSON:API](https://jsonapi.org/) as transportation format. The API handler is not meant to be used directly; instead, you should use it together with a [server adapter](../../../category/server-adapters) which handles the request and response API for a specific framework.
 
 It can be created as the following:
 
 <Tabs>
 <TabItem value="next" label="Next.js">
 
-```ts title='/src/pages/api/model/[...path].ts'
+```ts title='/src/app/api/model/[...path]/route.ts'
 import { NextRequestHandler } from '@zenstackhq/server/next';
-import RestApiHandler from '@zenstackhq/server/api/rest';
-import { getPrisma } from '../../lib/db';
+import { RestApiHandler } from '@zenstackhq/server/api';
+import { getPrisma } from '~/lib/db';
 
-export default NextRequestHandler({ 
+const handler = NextRequestHandler({
     getPrisma,
-    handler: RestApiHandler({ endpoint: 'http://myhost/api' }) 
+    useAppDir: true,
+    handler: RestApiHandler({ endpoint: 'http://myhost/api' })    
 });
+
+export {
+  handler as GET,
+  handler as POST,
+  handler as PUT,
+  handler as PATCH,
+  handler as DELETE,
+};
 ```
 
 </TabItem>
@@ -35,11 +44,11 @@ export default NextRequestHandler({
 <TabItem value="sveltekit" label="SvelteKit">
 
 ```ts title='/src/hooks.server.ts'
-import zenstack from '@zenstackhq/server/sveltekit';
-import RestApiHandler from '@zenstackhq/server/api/rest';
+import { SvelteKitHandler } from '@zenstackhq/server/sveltekit';
+import { RestApiHandler } from '@zenstackhq/server/api';
 import { getPrisma } from './lib/db';
 
-export const handle = zenstack.SvelteKitHandler({
+export const handle = SvelteKitHandler({
     prefix: '/api/model',
     getPrisma,
     handler: RestApiHandler({ endpoint: 'http://myhost/api/model' })
@@ -50,8 +59,15 @@ export const handle = zenstack.SvelteKitHandler({
 
 <TabItem value="nuxt" label="Nuxt">
 
-```
-🚧 Coming soon
+```ts title='/server/api/model/[...].ts'
+import { createEventHandler } from '@zenstackhq/server/nuxt';
+import { RestApiHandler } from '@zenstackhq/server/api';
+import { getPrisma } from './lib/db';
+
+export default createEventHandler({
+    handler: RestApiHandler({ endpoint: 'http://myhost/api/model' })
+    getPrisma
+});
 ```
 
 </TabItem>
@@ -68,6 +84,37 @@ The factory function accepts an options object with the following fields:
 
   Optional. A `number` field representing the default page size for listing resources and relationships. Defaults to 100. Set to Infinity to disable pagination.
 
+- modelNameMapping
+
+  Optional. An `Record<string, string>` value that provides a mapping from model names (as defined in ZModel) to URL path names. This is useful for example when you want to use plural names in URL endpoints:
+  
+  ```ts
+  // endpoint for accessing User model will then be ".../users"
+  RestApiHandler({
+      modelNameMapping: {
+          User: 'users'
+      }
+  })
+  ```
+
+  The mapping can be partial. You only need to specify the model names that you want to override. If a mapping is provided, only the mapped url path is valid, and accessing to unmapped path will be denied.
+
+- externalIdMapping
+
+  Optional. An `Record<string, string>` value that provides a mapping from model names (as defined in ZModel) to unique contraint name. This is useful when you for example want to expose natural keys in place of a surrogate keys:
+
+  ```ts
+  // Expose tags by unique name and not by ID, ie. /tag/blue intead of /tag/id
+  RestApiHandler({
+      externalIdMapping: {
+          Tag: 'name'
+      }
+  })
+  ```
+
+  Currentlly it is not possible to use custom index names. This also works for compound unique contraints just like for [compound IDs](#compound-id-fields).
+
+
 ## Endpoints and Features
 
 The RESTful API handler conforms to the the [JSON:API](https://jsonapi.org/format/) v1.1 specification for its URL design and input/output format. The following sections list the endpoints and features are implemented. The examples refer to the following schema modeling a blogging app:
@@ -118,6 +165,7 @@ GET /:type
 - 400: The request was malformed.
 - 403: The request was forbidden.
 - 404: The requested resource type does not exist.
+- 422: The request violated data validation rules.
 
 #### Examples
 
@@ -680,6 +728,44 @@ Relationships can also be manipulated directly. See [Manipulating Relationships]
     ```
 
 
+### Upserting a resource
+
+JSON:API didn't specify a convention for "upsert" operations. ZenStack uses a variation of the "create" operation to represent "upsert", and uses the request meta to indicate the intention. See details in [examples](#examples-10).
+
+```
+POST /:type
+```
+
+#### Status codes
+
+- 201: The request was successful and the resource was created.
+- 200: The request was successful and the resource was updated.
+- 400: The request was malformed.
+- 403: The request was forbidden.
+- 404: The requested resource type does not exist.
+
+#### Examples
+
+```json
+POST /user
+{
+    "data": {
+        "type": "user",
+        "attributes": {
+            "id": 1,
+            "name": "Emily",
+            "email": "emily@zenstack.dev"
+        }
+    },
+    "meta": {
+        "operation": "upsert",
+        "matchFields": ["id"],
+    }
+}
+```
+
+The `meta.operation` field must be "upsert", and the `meta.matchFields` field must be an array of field names that are used to determine if the resource already exists. If an existing resource is found, "update" operation is conducted, otherwise "create". The `meta.matchFields` fields must be unique fields, and they must have corresponding entries in `data.attributes`.
+
 ### Deleting a resource
 
 A resource can be deleted using the following endpoint:
@@ -756,7 +842,7 @@ PATCH /:type/:id/relationships/:relationship
 1. Replacing a to-one relationship
 
     ```json
-    PUT /post/1/author
+    PUT /post/1/relationships/author
     {
         "data": { "type": "user", "id": "2" }
     }
@@ -774,12 +860,56 @@ PATCH /:type/:id/relationships/:relationship
 1. Clearing a to-one relationship
 
     ```json
-    PUT /post/1/author
+    PUT /post/1/relationships/author
     {
         "data": null
     }
     ```
 
+## Compound ID Fields
+
+Prisma allows a model to have compound ID fields, e.g.:
+
+```zmodel
+model Post {
+    id1 Int
+    id2 Int
+    @@id([id1, id2])
+}
+```
+
+The JSON:API specification doesn't have a native way to represent compound IDs. To mitigate this limitation, when returning an entity with compound IDs, ZenStack synthesizes an "id" field to carry the ID values joined with underscore:
+
+```json
+{
+    "data": {
+        "id": "1_2",
+        "attributes": {
+            "id1": 1,
+            "id2": 2,
+            ...
+        },
+        "links" : {
+            "self" : "http://localhost:3100/api/model/post/1_2"
+        },
+        ...
+    }
+}
+```
+
+You can use this ID value convension in places where an ID is needed, e.g., reading a single entity.
+
+```ts
+GET /post/1_2
+```
+
+Limitations:
+
+1. Joining ID values with underscore implies that the ID values themselves cannot contain underscores. We'll make the separator configurable in the future.
+1. Prisma allows you to create a name for the compound ID field. Such usage is not yet supported by the RESTful API handler.
+
+> *Special thanks to [Thomas Sunde Nielsen](https://github.com/thomassnielsen) for implementing this feature!*
+
 ## Serialization
 
 ZenStack uses [superjson](https://github.com/blitz-js/superjson) to serialize and deserialize data. Superjson generates two parts during serialization:
diff --git a/docs/reference/server-adapters/api-handlers/rpc.mdx b/docs/reference/server-adapters/api-handlers/rpc.mdx
index 8fd4c47b..ecfd1c96 100644
--- a/docs/reference/server-adapters/api-handlers/rpc.mdx
+++ b/docs/reference/server-adapters/api-handlers/rpc.mdx
@@ -12,22 +12,31 @@ import DataTypeSerialization from './_data_type_serialization.md';
 
 ## Introduction
 
-The RPC-style API handler exposes CRUD endpoints that fully mirror [PrismaClient's query API](https://www.prisma.io/docs/reference/api-reference/prisma-client-reference#model-queries). Consuming the APIs feels like making RPC calls to a PrismaClient then. The API handler is not meant to be used directly; instead, you should use it together with a [server adapter](/docs/category/server-adapters) which handles the request and response API for a specific framework.
+The RPC-style API handler exposes CRUD endpoints that fully mirror [PrismaClient's query API](https://www.prisma.io/docs/reference/api-reference/prisma-client-reference#model-queries). Consuming the APIs feels like making RPC calls to a PrismaClient then. The API handler is not meant to be used directly; instead, you should use it together with a [server adapter](../../../category/server-adapters) which handles the request and response API for a specific framework.
 
 It can be created and used as the following:
 
 <Tabs>
 <TabItem value="next" label="Next.js">
 
-```ts title='/src/pages/api/model/[...path].ts'
+```ts title='/src/app/api/model/[...path]/route.ts'
 import { NextRequestHandler } from '@zenstackhq/server/next';
-import RPCApiHandler from '@zenstackhq/server/api/rpc';
-import { getPrisma } from '../../lib/db';
+import { RestApiHandler } from '@zenstackhq/server/api';
+import { getPrisma } from '~/lib/db';
 
-export default NextRequestHandler({ 
-    getPrisma, 
+const handler = NextRequestHandler({
+    getPrisma,
+    useAppDir: true,
     handler: RPCApiHandler() // you can also omit it since `RPCApiHandler` is the default
 });
+
+export {
+  handler as GET,
+  handler as POST,
+  handler as PUT,
+  handler as PATCH,
+  handler as DELETE,
+};
 ```
 
 </TabItem>
@@ -35,11 +44,11 @@ export default NextRequestHandler({
 <TabItem value="sveltekit" label="SvelteKit">
 
 ```ts title='/src/hooks.server.ts'
-import zenstack from '@zenstackhq/server/sveltekit';
-import RPCApiHandler from '@zenstackhq/server/api/rpc';
+import { SvelteKitHandler } from '@zenstackhq/server/sveltekit';
+import { RPCApiHandler } from '@zenstackhq/server/api';
 import { getPrisma } from './lib/db';
 
-export const handle = zenstack.SvelteKitHandler({
+export const handle = SvelteKitHandler({
     prefix: '/api/model',
     handler: RPCApiHandler(), // you can also omit it since `RPCApiHandler` is the default
     getPrisma
@@ -50,8 +59,15 @@ export const handle = zenstack.SvelteKitHandler({
 
 <TabItem value="nuxt" label="Nuxt">
 
-```
-🚧 Coming soon
+```ts title='/server/api/model/[...].ts'
+import { createEventHandler } from '@zenstackhq/server/nuxt';
+import { RPCApiHandler } from '@zenstackhq/server/api';
+import { getPrisma } from './lib/db';
+
+export default createEventHandler({
+    handler: RPCApiHandler(), // you can also omit it since `RPCApiHandler` is the default
+    getPrisma
+});
 ```
 
 </TabItem>
@@ -220,6 +236,10 @@ The following part explains how the `meta` information is included for different
 
     _Http method:_ `DELETE`
 
+-   **[model]/check**
+
+    _Http method:_ `GET`
+
 ## HTTP Status Code and Error Responses
 
 ### Status code
@@ -229,6 +249,7 @@ The HTTP status code used by the endpoints follows the following rules:
 - `create` and `createMany` use `201` for success. Other endpoints use `200`.
 - `403` is used for to indicate the request is denied due to lack of permissions, usually caused by access policy violation.
 - `400` is used for invalid requests, e.g., malformed request body.
+- `422` is used for data validation errors.
 - `500` is used for other unexpected errors.
 
 ### Error response format
diff --git a/docs/reference/server-adapters/elysia.mdx b/docs/reference/server-adapters/elysia.mdx
new file mode 100644
index 00000000..41582343
--- /dev/null
+++ b/docs/reference/server-adapters/elysia.mdx
@@ -0,0 +1,69 @@
+---
+title: Elysia
+description: Adapter for integrating with Elysia
+sidebar_position: 8
+---
+
+import ErrorHandling from './_error-handling.md';
+import AdapterOptions from './_options.mdx';
+import UsingAPI from './_using-api.mdx'
+
+# Elysia Adapter
+
+The `@zenstackhq/server/elysia` module provides a quick way to install a CRUD middleware onto an [Elysia](https://elysiajs.com/) app. Combined with ZenStack's power of enhancing Prisma with access policies, you can achieve a secure data backend without manually coding it.
+
+This feature is contributed by [@rodrigoburigool](https://github.com/rodrigoburigool).
+
+### Installation
+
+```bash
+bun install @zenstackhq/server
+```
+
+### Mounting the API
+
+You can use the `createElysiaHandler` API to create an Elysia request handler that handles CRUD requests automatically:
+
+```ts
+import { PrismaClient } from '@prisma/client';
+import { Elysia, Context } from 'elysia';
+import { enhance } from '@zenstackhq/runtime';
+import { createElysiaHandler } from '@zenstackhq/server/elysia';
+
+const prisma = new PrismaClient();
+
+const app = new Elysia({ prefix: '/api' });
+
+// install the CRUD middleware under route "/api/crud"
+app.group('/crud', (app) =>
+    app.use(
+        createElysiaHandler({
+            getPrisma: (context) => enhance(prisma, { user: getCurrentUser(context) }),
+            basePath: '/api/crud',
+        })
+    )
+);
+
+function getCurrentUser(context: Context) {
+    // the implementation depends on your authentication mechanism
+    ...
+}
+
+app.listen(3000);
+```
+
+The middleware factory takes the following options to initialize:
+
+<AdapterOptions getPrisma='(ctx: Context) => unknown | Promise<unknown>' />
+
+- basePath (optional)
+
+    <blockquote>string</blockquote>
+
+    Optional base path to strip from the request path before passing to the API handler. E.g., if your CRUD handler is mounted at `/api/crud`, set this field to `'/api/crud'`.
+
+### Using the API
+
+<UsingAPI />
+
+<ErrorHandling />
diff --git a/docs/reference/server-adapters/hono.mdx b/docs/reference/server-adapters/hono.mdx
new file mode 100644
index 00000000..67fdea3e
--- /dev/null
+++ b/docs/reference/server-adapters/hono.mdx
@@ -0,0 +1,57 @@
+---
+title: Hono
+description: Adapter for integrating with Hono
+sidebar_position: 7
+---
+
+import ErrorHandling from './_error-handling.md';
+import AdapterOptions from './_options.mdx';
+import UsingAPI from './_using-api.mdx'
+
+# Hono Adapter
+
+The `@zenstackhq/server/hono` module provides a quick way to install API routes onto a [Hono](https://hono.dev/) project for database CRUD operations. 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.
+
+### Installation
+
+```bash
+npm install @zenstackhq/server
+```
+
+### Mounting the API
+
+You can use the `createHonoHandler` API to create a [Hono middleware](https://hono.dev/docs/getting-started/basic#using-middleware) that handles CRUD requests automatically:
+
+```ts
+import { PrismaClient } from '@prisma/client';
+import { enhance } from '@zenstackhq/runtime';
+import { createHonoHandler } from '@zenstackhq/server/hono';
+import { Context, Hono } from 'hono';
+
+const prisma = new PrismaClient();
+const app = new Hono();
+
+app.use(
+    '/api/model/*',
+    createHonoHandler({
+        getPrisma: (ctx) => {
+            return enhance(prisma, { user: getCurrentUser(ctx) });
+        },
+    })
+);
+
+function getCurrentUser(ctx: Context) {
+    // the implementation depends on your authentication mechanism
+    ...
+}
+```
+
+The middleware factory takes the following options to initialize:
+
+<AdapterOptions getPrisma='(ctx: Context) => unknown | Promise<unknown>' />
+
+### Using the API
+
+<UsingAPI />
+
+<ErrorHandling />
diff --git a/docs/reference/server-adapters/nestjs.mdx b/docs/reference/server-adapters/nestjs.mdx
new file mode 100644
index 00000000..1d558d3d
--- /dev/null
+++ b/docs/reference/server-adapters/nestjs.mdx
@@ -0,0 +1,268 @@
+---
+title: NestJS
+description: Adapter for integrating with NestJS
+sidebar_position: 6
+---
+
+import ErrorHandling from './_error-handling.md';
+import AdapterOptions from './_options.mdx';
+import UsingAPI from './_using-api.mdx';
+
+# NestJS Adapter
+
+The `@zenstackhq/server/nestjs` module provides a quick way to install a ZenStack-enhanced Prisma service as a dependency injection provider onto a [NestJS](https://nestjs.com/) application.
+
+### Installation
+
+```bash
+npm install @zenstackhq/server
+```
+
+### Registering the provider
+
+You can register the enhanced Prisma service by importing the `ZenStackModule` NestJS module.
+
+```ts
+import { ZenStackModule } from '@zenstackhq/server/nestjs';
+import { enhance } from '@zenstackhq/runtime';
+import { PrismaService } from './prisma.service';
+
+@Module({
+  imports: [
+    ZenStackModule.registerAsync({
+      useFactory: (prisma: PrismaService) => {
+        return {
+          getEnhancedPrisma: () => enhance(prisma, { user: ... }),
+        };
+      },
+      inject: [PrismaService],
+      extraProviders: [PrismaService],
+    }),
+  ],
+})
+export class AppModule {}
+```
+
+The `registerAsync` API takes as input a factory function that returns a config used for creating an enhanced prisma service. The config contains a callback function where you should create and return an enhanced `PrismaClient`. It'll be called each time a Prisma method is invoked.
+
+You'll usually pass in a user context when calling `enhance` inside the callback. The way how the user context is fetched depends on your authentication mechanism. You can check the [NestJS quick start guide](../../quick-start/nestjs) for a reference solution.
+
+### Using the enhanced Prisma service
+
+Inside your NestJS controllers or services, you can inject the enhanced Prisma service and use it as you would with the regular Prisma service. Just use the special token name `ENHANCED_PRISMA` when injecting the service.
+
+```ts
+import { ENHANCED_PRISMA } from '@zenstackhq/server/nestjs';
+
+@Controller()
+export class MyController {
+  constructor(
+    @Inject(ENHANCED_PRISMA) private readonly prismaService: PrismaService,
+  ) {}
+
+  ...
+}
+```
+
+You can still use the regular Prisma service by injecting as usual.
+
+###  Using the API handler service to automate request handling
+
+This module also provides an `ApiHandlerService` that can be used to automate the request handling using API handlers. This is useful if you want to handle requests in a more structured way, such as using a controller or middleware. Before using it, you should ensure `ENHANCED_PRISMA` is registered in the module. For example:
+
+```ts
+import { ZenStackModule, ApiHandlerService } from '@zenstackhq/server/nestjs';
+import { enhance } from '@zenstackhq/runtime';
+import { PrismaService } from './prisma.service';
+
+@Module({
+  imports: [
+    // Register the ZenStack module
+    // The module exports the ENHANCED_PRISMA token and could be used in the ApiHandlerService
+    ZenStackModule.registerAsync({
+      useFactory: (prisma: PrismaService) => {
+        return {
+          getEnhancedPrisma: () => enhance(prisma, { user: ... }),
+        };
+      },
+      inject: [PrismaService],
+      extraProviders: [PrismaService],
+    }),
+  ],
+  providers: [ApiHandlerService]
+})
+export class AppModule {}
+```
+
+Then, you can inject the `ApiHandlerService` into your code and use it to handle requests. The service provides a method `handleRequest` to handle request using API handler automatically.
+
+RPC API Handler:
+
+```ts
+import { Controller, Get, Post } from '@nestjs/common';
+import { ApiHandlerService } from '@zenstackhq/server/nestjs';
+
+@Controller('post')
+export class PostController {
+  constructor(private readonly apiHandlerService: ApiHandlerService) {}
+
+  // should align with the route generated by API handler
+  @Get('findMany')
+  async findMany() {
+    return this.apiHandlerService.handleRequest()
+  }
+
+  @Post('create')
+  async create() {
+    return this.apiHandlerService.handleRequest()
+  }
+}
+```
+
+RESTful API Handler:
+
+```ts
+import { Controller, Get, Post } from '@nestjs/common';
+import { ApiHandlerService } from '@zenstackhq/server/nestjs';
+import RESTApiHandler from '@zenstackhq/server/api/rest';
+
+const ENDPOINT = 'http://localhost';
+
+@Controller('post')
+export class PostController {
+  constructor(private readonly apiHandlerService: ApiHandlerService) {}
+
+  // should align with the route generated by API handler
+  @Get()
+  async list() {
+    return this.apiHandlerService.handleRequest(
+      {
+        handler: RESTApiHandler({
+          endpoint: ENDPOINT,
+        }),
+      }
+    )
+  }
+
+  @Post()
+  async create() {
+    return this.apiHandlerService.handleRequest(
+      {
+        handler: RESTApiHandler({
+          endpoint: ENDPOINT,
+        }),
+      }
+    )
+  }
+}
+```
+
+### API reference
+
+#### `ZenStackModule.registerAsync`
+
+##### Signature
+
+```ts
+registerAsync(options: ZenStackModuleAsyncOptions): DynamicModule;
+```
+
+##### Parameter `options`
+
+```ts
+interface ZenStackModuleAsyncOptions {
+  /**
+   * Optional list of imported modules that export the providers which are
+   * required in this module.
+   */
+  imports?: Array<Type<any> | DynamicModule | Promise<DynamicModule> | ForwardReference>;
+
+  /**
+   * Whether the module is global-scoped.
+   */
+  global?: boolean;
+
+  /**
+   * The token to export the enhanced Prisma service. Default is `'ENHANCED_PRISMA'`.
+   */
+  exportToken?: string;
+
+  /**
+   * The factory function to create the enhancement options.
+   */
+  useFactory: (...args: unknown[]) => Promise<ZenStackModuleOptions> | ZenStackModuleOptions;
+
+  /**
+   * The dependencies to inject into the factory function.
+   */
+  inject?: FactoryProvider['inject'];
+
+  /**
+   * Extra providers to facilitate dependency injection.
+   */
+  extraProviders?: Provider[];
+}
+```
+
+```ts
+interface ZenStackModuleOptions {
+  /**
+   * A callback for getting an enhanced `PrismaClient`.
+   */
+  getEnhancedPrisma: (model?: string | symbol) => unknown;
+}
+```
+
+#### `ApiHandlerService.handleRequest`
+
+##### Signature
+
+```ts
+handleRequest(options?: ApiHandlerOptions): Promise<unknown>;
+```
+
+##### Parameter `options`
+
+```ts
+interface ApiHandlerOptions {
+  /**
+   * Logger settings
+   */
+  logger?: LoggerConfig;
+
+  /**
+   * Model metadata. By default loaded from the `node_module/.zenstack/model-meta`
+   * module. You can pass it in explicitly if you configured ZenStack to output to
+   * a different location.
+   */
+  modelMeta?: ModelMeta;
+
+  /**
+   * Zod schemas for validating request input. Pass `true` to load from standard location
+   * (need to enable `@core/zod` plugin in schema.zmodel) or omit to disable input validation.
+   */
+  zodSchemas?: ZodSchemas | boolean;
+
+  /**
+   * Api request handler function. Can be created using `@zenstackhq/server/api/rest` or `@zenstackhq/server/api/rpc` factory functions.
+   * Defaults to RPC-style API handler.
+   */
+  handler?: HandleRequestFn;
+
+  /**
+   * The base URL for the API handler. This is used to determine the base path for the API requests.
+   * If you are using the ApiHandlerService in a route with a prefix, you should set this to the prefix.
+   * 
+   * e.g.  
+   * without baseUrl(API handler default route):
+   * - RPC API handler: [model]/findMany
+   * - RESTful API handler: /:type
+   * 
+   * with baseUrl(/api/crud):
+   * - RPC API handler: /api/crud/[model]/findMany
+   * - RESTful API handler: /api/crud/:type
+   */
+  baseUrl?: string;
+}
+```
+
diff --git a/docs/reference/server-adapters/next.mdx b/docs/reference/server-adapters/next.mdx
index d3b99add..48647a64 100644
--- a/docs/reference/server-adapters/next.mdx
+++ b/docs/reference/server-adapters/next.mdx
@@ -28,40 +28,14 @@ You can use it to create a request handler in an API endpoint like:
 
 <Tabs>
 
-<TabItem value="pages" label={`For "pages" routes`}>
-
-```ts title='/src/pages/api/model/[...path].ts'
-import { NextRequestHandler } from '@zenstackhq/server/next';
-import type { NextApiRequest, NextApiResponse } from 'next';
-import { enhance } from '@zenstackhq/runtime';
-import { prisma } from '../../../lib/db.ts';
-import { getSessionUser } from '../../../lib/auth.ts';
-
-// create an enhanced Prisma client with user context
-function getPrisma(req: NextApiRequest, res: NextApiResponse) {
-    // getSessionUser extracts the current session user from the request, its
-    // implementation depends on your auth solution
-    return enhance(prisma, { user: getSessionUser(req, res) });
-}
-
-// create the request handler with the `getPrisma` hook
-export default NextRequestHandler({ getPrisma });
-```
-
-The Next.js API route handler takes the following options to initialize:
-
-<AdapterOptions getPrisma='(req: NextApiRequest, res: NextApiResponse) => unknown | Promise<unknown>' />
-
-</TabItem>
-
-<TabItem value="appdir" label={`For Next.js 13 "app" routes`}>
+<TabItem value="appdir" label={`App Router`}>
 
 ```ts title='/src/app/api/model/[...path]/route.ts'
 import { NextRequestHandler } from '@zenstackhq/server/next';
 import type { NextRequest } from "next/server";
 import { enhance } from '@zenstackhq/runtime';
-import { prisma } from '../../../lib/db.ts';
-import { getSessionUser } from '../../../lib/auth.ts';
+import { prisma } from '~/lib/db.ts';
+import { getSessionUser } from '~/lib/auth.ts';
 
 // create an enhanced Prisma client with user context
 function getPrisma(req: NextRequest) {
@@ -81,24 +55,35 @@ export {
 };
 ```
 
-:::info
-Next.js 13 has issues handling `@zenstackhq/runtime` and `@zenstackhq/server` packages when compiling server components. Please add the following config section to your `next.config.mjs` to exclude them:
-
-```js title='next.config.mjs'
-const config = {
-    experimental: {
-        serverComponentsExternalPackages: [
-            "@zenstackhq/runtime",
-            "@zenstackhq/server",
-        ],
-    },
+The Next.js API route handler takes the following options to initialize:
+
+<AdapterOptions getPrisma='(req: NextRequest) => unknown | Promise<unknown>' />
+
+</TabItem>
+
+<TabItem value="pages" label={`Pages Router`}>
+
+```ts title='/src/pages/api/model/[...path].ts'
+import { NextRequestHandler } from '@zenstackhq/server/next';
+import type { NextApiRequest, NextApiResponse } from 'next';
+import { enhance } from '@zenstackhq/runtime';
+import { prisma } from '~/lib/db.ts';
+import { getSessionUser } from '~/lib/auth.ts';
+
+// create an enhanced Prisma client with user context
+function getPrisma(req: NextApiRequest, res: NextApiResponse) {
+    // getSessionUser extracts the current session user from the request, its
+    // implementation depends on your auth solution
+    return enhance(prisma, { user: getSessionUser(req, res) });
 }
+
+// create the request handler with the `getPrisma` hook
+export default NextRequestHandler({ getPrisma });
 ```
-:::
 
 The Next.js API route handler takes the following options to initialize:
 
-<AdapterOptions getPrisma='(req: NextRequest) => unknown | Promise<unknown>' />
+<AdapterOptions getPrisma='(req: NextApiRequest, res: NextApiResponse) => unknown | Promise<unknown>' />
 
 </TabItem>
 
diff --git a/docs/reference/zmodel-language.md b/docs/reference/zmodel-language.md
index a48af5a7..49988dce 100644
--- a/docs/reference/zmodel-language.md
+++ b/docs/reference/zmodel-language.md
@@ -11,7 +11,7 @@ toc_max_heading_level: 3
 
 **ZModel**, the modeling DSL of ZenStack, is the main concept you'll deal with when using this toolkit. The ZModel syntax is a superset of [Prisma Schema](https://www.prisma.io/docs/concepts/components/prisma-schema). Therefore, every valid Prisma schema is a valid ZModel.
 
-:::tip
+:::info
 
 We made that choice to extend the Prisma schema for several reasons:
 
@@ -23,14 +23,14 @@ We made that choice to extend the Prisma schema for several reasons:
 
 :::
 
-However, the standard capability of Prisma schema doesn't allow us to build the functionalities we want in a natural way, so we made a few extensions to the language by adding the following:
+However, the standard capability of Prisma schema doesn't allow us to build the functionalities we want in a natural way, so we made several extensions to the language by adding the following:
 
 1. Custom attributes
 1. Custom attribute functions
 1. Built-in attributes and functions for defining access policies
 1. Built-in attributes for defining field validation rules
 1. Utility attributes like `@password` and `@omit`
-1. Multi-schema files support 
+1. Multi-schema files support
 
 Some of these extensions have been asked for by the Prisma community for some time, so we hope that ZenStack can be helpful even just as an extensible version of Prisma.
 
@@ -42,11 +42,17 @@ ZModel allows to import other ZModel files. This is useful when you want to spli
 ### Syntax
 
 ```zmodel
-import [File_PATH]
+import [IMPORT_SPECIFICATION]
 ```
 
-- **[File_PATH]**: 
-    Path to the ZModel file to be imported. Can be either a relative path or an absolute path, without .zmodel extension. Once a file is imported, all the declarations in that file will be included in the building process.
+- **[IMPORT_SPECIFICATION]**: 
+    Path to the ZModel file to be imported. It can be:
+    
+    - An absolute path, e.g., "/path/to/user".
+    - A relative path, e.g., "./user".
+    - A module resolved to an installed NPM package, e.g., "my-package/base".
+
+    If the import specification doesn't end with ".zmodel", the resolver will automatically append it. Once a file is imported, all the declarations in that file will be included in the building process.
 
 ### Examples
 
@@ -238,13 +244,14 @@ Models represent the business entities of your application. A model inherits all
 -  **[abstract]**:
 
     Optional. If present, the model is marked as abstract would not be mapped to a database table. Abstract models are only used as base classes for other models.
+
 -   **[NAME]**:
 
     Name of the model. Needs to be unique in the entire model. Needs to be a valid identifier matching regular expression `[A-Za-z][a-za-z0-9_]\*`.
 
 -   **[FIELD]**:
 
-    Arbitrary number of fields. See [next section](#field) for details.
+    Arbitrary number of fields. See [Field](#field) for details.
 
 -   **[ABSTRACT_MODEL_NAME]**:
     
@@ -254,7 +261,7 @@ Models represent the business entities of your application. A model inherits all
 
 A model must include a field marked with `@id` attribute. The `id` field serves as a unique identifier for a model entity and is mapped to the database table's primary key.
 
-See [here](/docs/reference/zmodel-language#attribute) for more details about attributes.
+See [here](#attribute) for more details about attributes.
 
 ### Example
 
@@ -280,6 +287,49 @@ model User {
 }
 ```
 
+## Type
+
+Types provide a way to modeling object shapes without mapping them to a database table. The use cases of "types" include:
+
+- Typing JSON fields in models.
+- Typing [plugin](#plugin) options.
+- Interfacing with data models from external sources (auth providers like [Clerk](https://clerk.com), payment providers like [Stripe](https://stripe.com), etc.).
+
+### Syntax
+```zmodel
+type [NAME] {
+    [FIELD]*
+}
+```
+
+-   **[NAME]**:
+
+    Name of the model. Needs to be unique in the entire model. Needs to be a valid identifier matching regular expression `[A-Za-z][a-za-z0-9_]\*`.
+
+-   **[FIELD]**:
+
+    Arbitrary number of fields. See [Field](#field) for details.
+
+### Example
+
+```zmodel
+type Profile {
+    email String @email
+    name String
+}
+
+model User {
+    id String @id
+    profile Profile? @json
+}
+```
+
+### Limitations
+
+- Inheritance is not supported for types yet. A type cannot inherit from another type. A model cannot extend a type.
+- Type cannot have type-level attributes like `@@validate`. However, fields in a type can have field-level attributes.
+
+We may address these limitations in future versions.
 
 ## Attribute
 
@@ -297,7 +347,7 @@ id String @[ATTR_NAME](ARGS)?
 
 -   **[ATTR_NAME]**
 
-Attribute name. See [below](#built-in-attributes) for a full list of attributes.
+Attribute name. See [below](#predefined-attributes) for a full list of attributes.
 
 -   **[ARGS]**
 
@@ -315,7 +365,7 @@ model Model {
 
 -   **[ATTR_NAME]**
 
-Attribute name. See [below](#built-in-attributes) for a full list of attributes.
+Attribute name. See [below](#predefined-attributes) for a full list of attributes.
 
 -   **[ARGS]**
 
@@ -646,6 +696,44 @@ attribute @omit()
 
 Indicates that the field should be omitted when read from the generated services. Commonly used together with `@password` attribute.
 
+##### @json
+
+```zmodel
+attribute @json()
+```
+
+##### @encrypted
+
+```zmodel
+attribute @encrypted()
+```
+
+Marks a field to be encrypted during write and decrypted when read.
+
+```zmodel
+model User {
+    id Int @id
+    someSecret String @encrypted
+}
+```
+
+Using the attribute requires the "encryption" enhancement kind to be enabled when calling `enhance`, and the `encryption` settings provided in the enhancement options:
+
+```ts
+
+function getEncryptionKey(): Uint8Array {
+    // return a 32-byte key
+}
+
+const db = enhance(prisma, { user }, {
+    encryption: {
+        encryptionKey: getEncryptionKey()
+    }
+});
+```
+
+See [Encrypting Field](../guides/field-encryption.md) for more details.
+
 ##### @prisma.passthrough
 
 ```zmodel
@@ -669,7 +757,7 @@ model User {
 }
 ```
 
-wil be translated to the following Prisma schema:
+will be translated to the following Prisma schema:
 
 ```zmodel
 model User {
@@ -678,6 +766,21 @@ model User {
 }
 ```
 
+##### @meta
+
+```zmodel
+attribute @meta(_ name: String, _ value: Any)
+```
+
+Adds arbitrary metadata to a field. The metadata can be accessed by custom plugins for code generation, or at runtime from the `modelMeta` object exported from `@zenstackhq/runtime/model-meta`. The `value` parameter can be an arbitrary literal expression, including object literals.
+
+```zmodel
+model User {
+    id Int @id
+    name String @meta(name: "description", value: "The name of the user")
+}
+```
+
 #### Model attributes
 
 ##### @@id
@@ -790,7 +893,7 @@ _Params_:
 
 | Name      | Description                                                                                                                                                              |
 | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
-| operation | Comma separated list of operations to control, including `"create"`, `"read"`, `"update"`, and `"delete"`. Pass` "all"` as an abbriviation for including all operations. |
+| operation | Comma separated list of operations to control, including `"create"`, `"read"`, `"update"`, and `"delete"`. Pass` "all"` as an abbreviation for including all operations. |
 | condition | Boolean expression indicating if the operations should be denied                                                                                                         |
 
 ##### @@auth
@@ -799,7 +902,21 @@ _Params_:
 attribute @@auth()
 ```
 
-Specify the model for resolving `auth()` function call in access policies. By default, the model named "User" is used. You can use this attribute to override the default behavior. A Zmodel can have at most one model with this attribute. 
+Specify the model for resolving `auth()` function call in access policies. By default, the model named "User" is used. You can use this attribute to override the default behavior. A Zmodel can have at most one model with this attribute.
+
+##### @@auth
+
+```zmodel
+attribute @@delegate(_ discriminator: FieldReference)
+```
+
+Marks a model to be a delegated type. Used for [modeling a polymorphic hierarchy](../guides/polymorphism).
+
+_Params_:
+
+| Name          | Description                                                                                                                  |
+| ------------- | ---------------------------------------------------------------------------------------------------------------------------- |
+| discriminator | A `String` or `enum` field in the same model used to store the name of the concrete model that inherit from this base model. |
 
 ##### @@prisma.passthrough
 
@@ -825,7 +942,7 @@ model User {
 }
 ```
 
-wil be translated to the following Prisma schema:
+will be translated to the following Prisma schema:
 
 ```zmodel
 model User {
@@ -835,6 +952,21 @@ model User {
 }
 ```
 
+##### @@meta
+
+```zmodel
+attribute @@meta(_ name: String, _ value: Any)
+```
+
+Adds arbitrary metadata to a model. The metadata can be accessed by custom plugins for code generation, or at runtime from the `modelMeta` object exported from `@zenstackhq/runtime/model-meta`. The `value` parameter can be an arbitrary literal expression, including object literals.
+
+```zmodel
+model User {
+    id Int @id
+    @@meta('description', 'This is a user model')
+}
+```
+
 ### Predefined attribute functions
 
 ##### uuid()
@@ -848,10 +980,26 @@ Generates a globally unique identifier based on the UUID spec.
 ##### cuid()
 
 ```zmodel
-function cuid(): String {}
+function cuid(version: Int?): String {}
 ```
 
-Generates a globally unique identifier based on the [CUID](https://github.com/ericelliott/cuid) spec.
+Generates a unique identifier based on the [CUID](https://github.com/ericelliott/cuid) spec. Pass `2` as an argument to use [cuid2](https://github.com/paralleldrive/cuid2).
+
+##### nanoid()
+
+```zmodel
+function nanoid(length: Int?): String {}
+```
+
+Generates an identifier based on the [nanoid](https://github.com/ai/nanoid) spec.
+
+##### ulid()
+
+```zmodel
+function ulid(): String {}
+```
+
+Generates a unique identifier based on the [ULID](https://github.com/ulid/spec) spec.
 
 ##### now()
 
@@ -894,6 +1042,64 @@ function future(): Any {}
 
 Gets the "post-update" state of an entity. Only valid when used in a "update" access policy. Read more about access policies [here](#access-policy).
 
+##### check()
+
+```zmodel
+function check(field: FieldReference, operation String?): Boolean {}
+```
+
+Checks if the current user can perform the given operation on the given field.
+
+_Params_
+
+-  `field`: The field to check access for. Must be a relation field.
+-  `operation`: The operation to check access for. Can be "read", "create", "update", or "delete". If the operation is not provided, it defaults the operation of the containing policy rule.
+  
+_Example_
+
+```zmodel
+// delegating a single operation kind
+model Post {
+    id Int @id
+    author User @relation(fields: [authorId], references: [id])
+    authorId Int
+
+    // delegate "read" check to the author, equivalent to
+    //     @@allow('read', check(author))
+    @@allow('read', check(author, 'read'))
+}
+```
+
+```zmodel
+// delegating all operations
+model Post {
+    id Int @id
+    author User @relation(fields: [authorId], references: [id])
+    authorId Int
+
+    // delegate all access policies to the author, equivalent to:
+    //     @@allow('read', check(author))
+    //     @@allow('create', check(author))
+    //     @@allow('update', check(author))
+    //     @@allow('delete', check(author))
+    @@allow('all', check(author))
+}
+```
+
+```zmodel
+// delegating field access control
+model Post {
+    id Int @id
+    title String @allow('update', check(author))
+    author User @relation(fields: [authorId], references: [id])
+    authorId Int
+}
+```
+
+:::info
+The `check()` function only supports singular relation fields and cannot be used with "to-many" relations. We may add support for it in the future.
+:::
+
 ##### contains()
 
 ```zmodel
@@ -954,7 +1160,7 @@ Check if the given field (list) contains every element of the search list.
 
 Equivalent to Prisma's [hasEvery](https://www.prisma.io/docs/reference/api-reference/prisma-client-reference#hasevery) operator.
 
-##### hasSome
+##### hasSome()
 
 ```zmodel
 function hasSome(field: Any[], search: Any[]): Boolean {}
@@ -964,7 +1170,7 @@ Check if the given field (list) contains at least one element of the search list
 
 Equivalent to Prisma's [hasSome](https://www.prisma.io/docs/reference/api-reference/prisma-client-reference#hassome) operator.
 
-##### isEmpty
+##### isEmpty()
 
 ```zmodel
 function isEmpty(field: Any[]): Boolean {}
@@ -974,6 +1180,26 @@ Check if the given field (list) is empty.
 
 Equivalent to Prisma's [isEmpty](https://www.prisma.io/docs/reference/api-reference/prisma-client-reference#isempty) operator.
 
+##### currentModel()
+
+```zmodel
+function currentModel(casing: String?): String {}
+```
+
+Can only be used in access policy expressions. Return the name of the model for which the policy rule is defined. If the rule is inherited to a sub model, this function returns the name of the sub model.
+
+The optional "casing" parameter can be used to control the casing of the returned name. Valid values are "original", "upper", "lower", "capitalize", "uncapitalize". Defaults to "original".
+
+##### currentOperation()
+
+```zmodel
+function currentOperation(casing: String?): String {}
+```
+
+Can only be used in access policy expressions. Return the operation for which the policy rule is defined for - "create", "read", "update", or "delete". Note that a rule with "all" operation is expanded to "create", "read", "update", and "delete" rules, and the function returns corresponding value for each expanded version.
+
+The optional "casing" parameter can be used to control the casing of the returned name. Valid values are "original", "upper", "lower", "capitalize", "uncapitalize". Defaults to "original".
+
 ### Examples
 
 Here're some examples on using field and model attributes:
@@ -1019,7 +1245,7 @@ You can find examples of custom attributes and functions in [ZModel Standard Lib
 
 ## Field
 
-Fields are typed members of models.
+Fields are typed members of models and types.
 
 ### Syntax
 
@@ -1029,13 +1255,21 @@ model Model {
 }
 ```
 
+Or
+
+```zmodel
+type Type {
+    [FIELD_NAME] [FIELD_TYPE] (FIELD_ATTRIBUTES)?
+}
+```
+
 -   **[FIELD_NAME]**
 
     Name of the field. Needs to be unique in the containing model. Needs to be a valid identifier matching regular expression `[A-Za-z][a-za-z0-9_]\*`.
 
 -   **[FIELD_TYPE]**
 
-    Type of the field. Can be a scalar type or a reference to another model.
+    Type of the field. Can be a scalar type, a reference to another model if the field belongs to a [model](#model), or a reference to another type if it belongs to a [type](#type).
 
     The following scalar types are supported:
 
@@ -1174,7 +1408,7 @@ A relation field referencing its own model is called "self-relation". ZModel's r
 
 ### Referential action
 
-When defining a relation, you can specify what happens when one side of a relation is updated or deleted. See [Referential action](/docs/reference/zmodel-language#referential-action) for details.
+When defining a relation, you can specify what happens when one side of a relation is updated or deleted. See [Referential action](#referential-action) for details.
 
 ## Access policy
 
@@ -1219,15 +1453,16 @@ The signatures of the attributes are:
 -   `@allow`
 
     ```zmodel
-        attribute @allow(_ operation: String, _ condition: Boolean)
+        attribute @allow(_ operation: String, _ condition: Boolean, _ override: Boolean?)
     ```
 
     _Params_:
 
-    | Name      | Description                                                                                                                                                              |
-    | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
-    | operation | Comma separated list of operations to control, including `"read"` and `"update"`. Pass` "all"` as an abbreviation for including all operations. |
-    | condition | Boolean expression indicating if the operations should be allowed                                                                                                        |
+    | Name      | Description                                                                                                                                                              | Default |
+    | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------- |
+    | operation | Comma separated list of operations to control, including `"read"` and `"update"`. Pass` "all"` as an abbreviation for including all operations.                          |         |
+    | condition | Boolean expression indicating if the operations should be allowed                                                                                                        |         |
+    | override  | Boolean indicating if the field-level policy should override model-level ones. See [here](../the-complete-guide/part1/access-policy/field-level#overriding-model-level-policies) for more details. | false   |
 
 -   `@deny`
 
@@ -1371,7 +1606,21 @@ A field can also contain an arbitrary number of policy rules. The logic of combi
 
 Please note the difference between model-level and field-level rules. Model-level access are by-default denied, while field-level access are by-default allowed.
 
-### Pre-update vs. post-update
+### "Create" rules
+
+The "create" policy rules should be understood as: **if an entity were to be created, would it satisfy the rules**. Or, in other words, the rules are checked "post create".
+
+An entity creating process works like the following:
+
+1. Initiate a transaction and create the entity.
+2. In the same transaction, try to read the created entity with the "create" rules as filter, and see if it succeeds.
+3. If the read fails, the transaction is rolled back; otherwise it's committed.
+
+The "post-create check" semantic allows the rules to access relations of the entity being created since they are only accessible after the create happens. For simple cases, ZenStack may apply optimizations to reject a create request without initiating a transaction, but generally speaking the "post-create check" semantic is the correct way to think about it.
+
+We may introduce a "pre-create" policy type in the future.
+
+### "Pre-update" vs." post-update" rules
 
 When an access policy rule is applied to a mutate operation, the entities under operation have a "pre" and "post" state. For a "create" rule, its "pre" state is empty, so the rule implicitly refers to the "post" state. For a "delete" rule, its "post" state is empty, so the rule implicitly refers to the "pre" state.
 
@@ -1488,7 +1737,7 @@ model User {
 
 Data validation is used for attaching constraints to field values. Unlike access policies, field validation rules cannot access the current user with the `auth()` function and are only checked for 'create' and 'update' operations. The main purpose of field validation is to ensure data integrity and consistency, not for access control.
 
-The [`@core/zod`](/docs/reference/plugins/zod) plugin recognizes the validation attributes and includes them into the generated Zod schemas.
+The [`@core/zod`](./plugins/zod) plugin recognizes the validation attributes and includes them into the generated Zod schemas.
 
 ### Field-level validation attributes
 
@@ -1528,6 +1777,22 @@ The following attributes can be used to attach validation rules to individual fi
 
     Validates a string field value matches a regex.
 
+-   `@trim(_ value: String)`
+
+    Trims whitespace.
+
+-   `@lower(_ value: String)`
+
+    Converts to lowercase.
+
+-   `@upper(_ value: String)`
+
+    Converts to uppercase.
+
+:::info
+Attributes `@trim`, `@lower`, and `@upper` are actually "transformation" instead of "validation". They make sure the values are transformed before storing into the database.
+:::
+
 #### Number
 
 -   `@gt(_ value: Int, _ message: String?)`
@@ -1548,10 +1813,10 @@ The following attributes can be used to attach validation rules to individual fi
 
 ### Model-level validation attributes
 
-You can use the `@@validate` attribute to attach validation rules to a model. 
+You can use the `@@validate` attribute to attach validation rules to a model. Use the `message` parameter to provide an optional custom error message, and the `path` parameter to provide an optional path to the field that caused the error.
 
 ```
-@@validate(_ value: Boolean, _ message: String?)
+@@validate(_ value: Boolean, _ message: String?, _ path: String[]?)
 ```
 
 Model-level rules can reference multiple fields, use relation operators (`==`, `!=`, `>`, `>=`, `<`, `<=`) to compare fields, use boolean operators (`&&`, `||`, and `!`) to compose conditions, and can use the following functions to evaluate conditions for fields:
@@ -1687,3 +1952,36 @@ model Profile {
     userId String @unique
 }
 ```
+
+## Comments
+
+ZModel supports both line comments (starting with `//`) and block comments (starting with `/*` and ending with `*/`). Comments on declarations (models, enums, fields, etc.) starting with triple slashes (`///`) are treated as documentation:
+
+- They show up as hover tooltips in IDEs.
+- They are passed along to the generated Prisma schema.
+
+```zmodel
+/// A user model
+model User {
+    id String @id
+
+    /// The user's email
+    email String @unique
+}
+```
+
+You can also use JSDoc-style comments as documentation, however they are not passed along to the generated Prisma schema.
+
+```zmodel
+/**
+ * A user model
+ */
+model User {
+    id String @id
+
+    /**
+     * The user's email
+     */
+    email String @unique
+}
+```
diff --git a/docs/samples.md b/docs/samples.md
new file mode 100644
index 00000000..9c3608f9
--- /dev/null
+++ b/docs/samples.md
@@ -0,0 +1,87 @@
+---
+sidebar_position: 8
+sidebar_label: Sample Projects
+---
+
+# Sample Projects
+
+The ZenStack team maintains the following three series of sample projects.
+
+- **Schema Samples**
+
+    A set of ZModel schemas demonstrating how to model different types of authorization patterns.
+
+- **Multi-Tenant Todo App**
+
+    This series demonstrates a complete implementation of a multi-tenant, SaaS-like application with a relatively complex authorization model. It is also the sample we used throughout [The Complete Guide](./the-complete-guide/).
+
+- **Simple Blog App**
+
+    This series is for those who want to quickly understand the structure of a project using ZenStack, without the burden of comprehending a complex application. These samples are used in the [Quick Start](./category/quick-start) guides.
+
+We also actively collect and share community-contributed samples.
+
+### Schema Samples
+
+The [sample repo](https://github.com/zenstackhq/authz-modeling-samples) includes the following patterns:
+
+- ACL
+- RBAC
+- ABAC
+- Multi-Tenancy
+
+You can use [this blog post](https://zenstack.dev/blog/model-authz) as an introduction.
+
+### Multi-Tenant Todo Samples
+
+-   [Next.js (App Router) + NextAuth + TanStack Query](https://github.com/zenstackhq/sample-todo-nextjs-tanstack)
+-   [Next.js (Pages Router) + NextAuth + SWR](https://github.com/zenstackhq/sample-todo-nextjs)
+-   [Next.js (Pages Router) + NextAuth + tRPC](https://github.com/zenstackhq/sample-todo-trpc)
+-   [Nuxt V3 + TanStack Query](https://github.com/zenstackhq/sample-todo-nuxt)
+-   [SvelteKit + TanStack Query](https://github.com/zenstackhq/sample-todo-sveltekit)
+-   [RedwoodJS](https://github.com/zenstackhq/sample-todo-redwood)
+
+### Blog Samples
+
+-   [Next.js (App Router) + SWR](https://github.com/zenstackhq/docs-tutorial-nextjs-app-dir)
+-   [Next.js (Pages Router) + SWR](https://github.com/zenstackhq/docs-tutorial-nextjs)
+-   [Next.js (App Route) + tRPC](https://github.com/zenstackhq/sample-blog-nextjs-app-trpc)
+-   [Nuxt V3 + TanStack Query](https://github.com/zenstackhq/docs-tutorial-nuxt)
+-   [SvelteKit](https://github.com/zenstackhq/docs-tutorial-sveltekit)
+-   [Remix](https://github.com/zenstackhq/docs-tutorial-remix)
+-   [Express Backend API](https://github.com/zenstackhq/docs-tutorial-express)
+-   [NestJS Backend API](https://github.com/zenstackhq/docs-tutorial-nestjs)
+-   [Clerk Integration](https://github.com/zenstackhq/docs-tutorial-clerk)
+
+### Community Samples
+
+- [Monorepo Todo App](https://github.com/ErikDakoda/sample-todo-nextjs-turbo) by [ErikDakoda](https://github.com/ErikDakoda)
+
+    - PNPM workspace
+    - Turborepo
+    - Next.js + NextAuth
+
+- [Warehouse Management System](https://github.com/Eliav2/zenstack-warehouse-demo) by [Eliav2](https://github.com/Eliav2)
+
+    - Decoupled frontend and backend with monorepo
+    - Backend: ZenStack + Express + GitHub SSO
+    - Frontend: React + TanStack-Query + TanStack-Router + MUI5 + Vite
+
+- [Cloudflare Pages + Worker](https://github.com/kirankunigiri/zenstack-ui) by [Kiran](https://github.com/kirankunigiri)
+  
+    - Decoupled frontend and backend on Cloudflare
+    - Hono for backend (Cloudflare Worker)
+    - React SPA for frontend (Cloudflare Pages)
+    - This repo is also a cool demo of the [ZenStack UI](https://www.npmjs.com/package/zenstack-ui) package made by Kiran.
+ 
+- [Multiboard](https://github.com/olliethedev/multiboard) by [olliethedev](https://github.com/olliethedev)
+
+    - Minimal Kanban task mangement platform
+    - Authentication with Better Auth
+    - Login + Logout + Account UI with Better Auth UI
+    - Multi-tenant organizations
+    - Next.js + shadcn/ui
+    - Emails with Resend
+    - Forms with AutoForm
+    - Api Docs with Scalar
+  
diff --git a/docs/the-complete-guide/epilogue.md b/docs/the-complete-guide/epilogue.md
index 97c74006..4d0e1473 100644
--- a/docs/the-complete-guide/epilogue.md
+++ b/docs/the-complete-guide/epilogue.md
@@ -22,8 +22,8 @@ As its name suggests, we want the project to help bring simplicity, tranquility,
 
 ### What's Next
 
-- The [Quick Start](/docs/category/quick-start) documents guide you through creating a simple app using ZenStack with various frameworks. They are fast-paced and can get you up running in minutes.
-- The [Recipes](docs/category/recipes) include guides for achieving specific tasks. For example, you may want to learn how to [deploy to production](docs/guides/deploy).
+- The [Quick Start](../category/quick-start) documents guide you through creating a simple app using ZenStack with various frameworks. They are fast-paced and can get you up running in minutes.
+- The [Recipes](../category/recipes) include guides for achieving specific tasks. For example, you may want to learn how to [deploy to production](../guides/deploy).
 - The best way to get support or send feedback is to join our [Discord server](https://discord.gg/Ykhr738dUe). The community is responsive and helpful.
 - But you're also welcome to create issues and feature requests directly on [GitHub](https://github.com/zenstackhq/zenstack).
 - Finally, don't forget to [⭐️Star Us⭐️](https://github.com/zenstackhq/zenstack). We appreciate it!
diff --git a/docs/the-complete-guide/index.md b/docs/the-complete-guide/index.md
index 8c8acd33..5e295a6e 100644
--- a/docs/the-complete-guide/index.md
+++ b/docs/the-complete-guide/index.md
@@ -45,7 +45,7 @@ The extension to Prisma ORM is the foundation of ZenStack and enables all other
 
 In this part of the guide, we'll finish building the schema of the sample project - including the data models, access policies, and data validation rules. There's no API or UI yet, but the business logic will be mostly complete.
 
-[Go to Part I](/docs/the-complete-guide/part1)
+[Go to Part I](./part1/index.md)
 
 ### Part II: Plugin System
 
@@ -53,7 +53,7 @@ ZenStack adopts a plugin-based architecture for great extensibility. The plugin
 
 In this part of the guide, you'll see how to use plugins to achieve various goals, and we'll also build a simple plugin for our Todo app from scratch.
 
-[Go to Part II](/docs/the-complete-guide/part2)
+[Go to Part II](./part2/index.md)
 
 ### Part III: Automatic CRUD API
 
@@ -63,7 +63,7 @@ Since ZenStack already secures data access at the ORM layer, as we learned from
 
 In this part of the guide, we'll add a CRUD API layer to our Todo app and turn it into a fully functional backend service.
 
-[Go to Part III](/docs/the-complete-guide/part3)
+[Go to Part III](./part3/index.md)
 
 ### Part IV: Frontend Data Query
 
@@ -73,4 +73,4 @@ ZenStack can generate hooks code that talks to the automatic CRUD API introduced
 
 In this part of the guide, we'll use the generated data query hooks to build a frontend UI for our Todo app. We'll finally complete our full-stack Todo application!
 
-[Go to Part IV](/docs/the-complete-guide/part4)
+[Go to Part IV](./part4/index.md)
diff --git a/docs/the-complete-guide/part1/0-prisma.md b/docs/the-complete-guide/part1/0-prisma.md
index e455a467..05cb0909 100644
--- a/docs/the-complete-guide/part1/0-prisma.md
+++ b/docs/the-complete-guide/part1/0-prisma.md
@@ -5,7 +5,7 @@ sidebar_label: 0. Prisma Crash Course
 # Prisma Crash Course
 
 :::info
-Feel free to skip to the [next chapter](/docs/the-complete-guide/part1/zmodel) if you are already familiar with Prisma.
+Feel free to skip to the [next chapter](./zmodel) if you are already familiar with Prisma.
 :::
 
 ZenStack is built above Prisma ORM, so it'll be important to have a basic understanding of it.
diff --git a/docs/the-complete-guide/part1/1-zmodel.md b/docs/the-complete-guide/part1/1-zmodel.md
index 6ff61b45..98365704 100644
--- a/docs/the-complete-guide/part1/1-zmodel.md
+++ b/docs/the-complete-guide/part1/1-zmodel.md
@@ -24,7 +24,7 @@ model User {
 }
 ```
 
-It works, but it's ugly and gets no protection from the compiler. The model can become messy if it's littered with such hacks everyone. One of the biggest reasons for introducing the ZModel language is to systematically remove this obstacle so that we have a solid foundation for adding custom semantics into the schema down the road.
+It works, but it's ugly and gets no protection from the compiler. The model can become messy if it's littered with such hacks everywhere. One of the biggest reasons for introducing the ZModel language is to systematically remove this obstacle so that we have a solid foundation for adding custom semantics into the schema down the road.
 
 Here's a quick example of the custom `@password` and `@omit` attributes ZModel added for automatically hashing passwords upon saving and omitting them from query results:
 
@@ -81,7 +81,7 @@ model Comment extends Base {
 
 #### A Better Plugin System
 
-Prisma allows you to write custom generators. However, the generator development API is undocumented and difficult to understand. ZenStack provides a plugin system that enables you to generate custom artifacts with a simple API and object model. In fact, almost all the features of ZenStack itself are implemented as plugins. [Part II](/docs/the-complete-guide/part2) of this guide is dedicated to covering the plugin system.
+Prisma allows you to write custom generators. However, the generator development API is undocumented and difficult to understand. ZenStack provides a plugin system that enables you to generate custom artifacts with a simple API and object model. In fact, almost all the features of ZenStack itself are implemented as plugins. [Part II](../part2) of this guide is dedicated to covering the plugin system.
 
 ### ZModel Structure
 
@@ -147,7 +147,7 @@ plugin trpc {
 }
 ```
 
-[Part II](/docs/the-complete-guide/part2) of this guide will cover the plugin system in detail.
+[Part II](../part2) of this guide will cover the plugin system in detail.
 
 #### Models
 
@@ -173,8 +173,24 @@ model User {
 
 ### IDE Support
 
-ZenStack offers a VSCode extension to support authoring ZModel files - syntax highlighting, auto-completion, and error checking. You can install it from the [VSCode Marketplace](https://marketplace.visualstudio.com/items?itemName=zenstack.zenstack). Although it still lacks some features, the goal is to make it on par with the Prisma VSCode extension. We're also actively looking to support JetBrains IDEs. Please follow [this discussion](https://github.com/zenstackhq/zenstack/discussions/414) for updates.
+ZenStack comes with a [VSCode extension](https://marketplace.visualstudio.com/items?itemName=zenstack.zenstack) and a [JetBrains IDE plugin](https://plugins.jetbrains.com/plugin/23397-zenstack-language-tools). You can find more information about IDE support [here](../../guides/ide).
+
+:::tip Documentation
+
+ZModel supports both line comments (`//`) and block comments (`/* ... */`). Comments starting with triple slashes (`///`) are treated as documentation: they show up as hover tooltip in IDEs, and they are passed along to the generated Prisma schema.
+
+```zmodel
+/// A user model
+model User {
+    id String @id
+    
+    /// The user's email
+    email String @unique
+}
+```
+
+:::
 
 ### Full Documentation
 
-Check out the [ZModel Language](/docs/reference/zmodel-language) reference documentation for a complete language description.
+Check out the [ZModel Language](../../reference/zmodel-language) reference documentation for a complete language description.
diff --git a/docs/the-complete-guide/part1/2-cli.md b/docs/the-complete-guide/part1/2-cli.md
index 61aa3d8b..c6ee51b7 100644
--- a/docs/the-complete-guide/part1/2-cli.md
+++ b/docs/the-complete-guide/part1/2-cli.md
@@ -10,7 +10,7 @@ import ZenStackInit from '../../_components/_zenstack-init.md';
 
 The `zenstack` CLI is the primary tool for using ZenStack in a project. The CLI is intentionally designed to have similar commands to the `prisma` CLI.
 
-In this chapter, we'll use the CLI to scaffold our Todo sample project. You can find the complete documentation in the [CLI reference guide](/docs/reference/cli).
+In this chapter, we'll use the CLI to scaffold our Todo sample project. You can find the complete documentation in the [CLI reference guide](../../reference/cli).
 
 ### 🛠️ Creating Our Sample Project
 
@@ -25,7 +25,7 @@ In this chapter, we'll use the CLI to scaffold our Todo sample project. You can
 You can initialize an existing TypeScript project using the `zenstack init` command. To save some typing, we'll first use the "try-prisma" utility to create a simple TypeScript CLI project.
 
 ```bash
-npx try-prisma@latest -p . -n my-todo-app -t typescript/script --install npm
+npx try-prisma@latest -n my-todo-app -t orm/script --install npm
 cd my-todo-app
 ```
 
@@ -143,9 +143,9 @@ You should see output like the following in your terminal:
 
 ```
 ⌛️ ZenStack CLI vx.y.z, running plugins
-✔ Running plugin Prisma
-✔ Running plugin Model Metadata
-✔ Running plugin Access Policy
+✔ Generating Prisma schema
+✔ Generating PrismaClient enhancer
+✔ Generating Zod schemas
 
 👻 All plugins completed successfully!
 Don't forget to restart your dev server to let the changes take effect.
@@ -348,7 +348,7 @@ prisma.space.findFirst({ include: { members: true } })
 }
 ```
 
-The REPL also provides a `db` variable for querying data with access control. We'll get back to it in [chapter 4](/docs/the-complete-guide/part1/access-policy).
+The REPL also provides a `db` variable for querying data with access control. We'll get back to it in [chapter 4](./access-policy).
 
 You can exit the REPL with the `.exit` command.
 
@@ -369,7 +369,7 @@ Installed ZenStack Packages:
     zenstack                    x.y.z
     @zenstackhq/runtime         x.y.z
 ✔ Checking npm registry
-You are using the latest version of Zenstack.
+You are using the latest version of ZenStack.
 ```
 
 ### ZenStack CLI & Prisma CLI
@@ -381,7 +381,7 @@ The ZenStack CLI is not meant for replacing the Prisma CLI. After `zenstack gene
 - Launching Prisma Studio with `prisma studio`
 - ...
 
-The Prisma development workflow that you're already familiar with should work seamlessly with ZenStack. See [here](/docs/guides/dev-workflow) for a detailed explanation of the development workflow.
+The Prisma development workflow that you're already familiar with should work seamlessly with ZenStack. See [here](../../guides/dev-workflow) for a detailed explanation of the development workflow.
 
 <ThemedImage
     alt="ZModel Generation"
diff --git a/docs/the-complete-guide/part1/3-enhancement.md b/docs/the-complete-guide/part1/3-enhancement.md
index 58a0987b..710d6caa 100644
--- a/docs/the-complete-guide/part1/3-enhancement.md
+++ b/docs/the-complete-guide/part1/3-enhancement.md
@@ -52,7 +52,7 @@ A few extra notes about enhanced Prisma Client:
 
 - **Using Prisma Client Extensions with enhanced client**
   
-  Enhanced Prisma Client is a transparent proxy, so it has the same API as the original Prisma Client. You can enhance a Prisma Client with Client Extensions installed or install Client Extensions on an enhanced Prisma Client - both work.
+  Enhanced Prisma Client can work with [Prisma Client Extensions](https://www.prisma.io/docs/orm/prisma-client/client-extensions) with some caveats. Please refer to [Using With Prisma Client Extensions](../../guides/client-extensions) for more details.
 
 - **Using the original client and an enhanced one together**
   
@@ -82,6 +82,16 @@ We try to make the enhanced Prisma Client as compatible as possible with the ori
 You should fall back to using the original Prisma Client in such cases.
 :::
 
+### Enhancement Kinds
+
+ZenStack can enhance Prisma Client in several ways. When you call the `enhance` API to create an enhanced client, all enhancement kinds are enabled by default. You can use the `kinds` option to fine tune which ones to enable:
+
+```ts
+const db = enhance(prisma, { user: getSessionUser() }, { kinds: ['policy'] });
+```
+
+See [here](../../reference/runtime-api#enhancement-kinds) for more details.
+
 ### 🛠️ Using Enhanced Prisma Client In REPL
 
 We saw in the previous chapter that in the REPL environment, you can use the built-in `prisma` variable to access Prisma Client directly. Another variable named `db` gives you access to an enhanced Prisma Client.
@@ -108,17 +118,21 @@ It works but gives an empty array. Why? With an enhanced Prisma Client, all oper
 This part is for those interested in the inner workings of ZenStack. It's not necessary to understand it to use ZenStack.
 :::
 
-If you know the inner workings of Prisma Client, you'll find ZenStack shares some similarities.. When `zenstack generate` is run, besides generating the `prisma/schema.prisma` file, it also runs several other plugins that transform different pieces of information in the ZModel into Javascript code that can be efficiently loaded and executed at runtime. The `enhance` API relies on the generated code to get its job done.
+If you know the inner workings of Prisma Client, you'll find ZenStack shares some similarities.. When `zenstack generate` is run, besides generating the `prisma/schema.prisma` file, it also runs several other plugins that transform different pieces of information in the ZModel into Javascript code that can be efficiently loaded and executed at runtime. The `enhance` API from`@zenstackhq/runtime` relies on the generated Javascript modules to get its job done.
+
+- `enhance`
+
+    Contains the function that enhances a `PrismaClient`. The `enhance` API from `@zenstackhq/runtime` simply reexports this function.
 
-- `model-meta.js`
+- `model-meta`
 
     Lightweight representation of ZModel's AST.
 
-- `policy.js`
+- `policy`
   
     Partial Prisma query input objects compiled from access policy expressions.
 
-- `zod/**/*.js|ts`
+- `zod/**`
 
     Zod schemas for validating input data according to ZModel.
 
@@ -128,10 +142,10 @@ The generation by default outputs to the `node_modules/.zenstack` folder. You ca
 npx zenstack generate -o lib/zenstack
 ```
 
-The `enhance` API, by default, loads the generated code from the `node_modules/.zenstack` folder. If you use a custom output location, make sure to use the `loadPath` option to specify it.
+When using a custom output location, you can't use the `enhance` API from `@zenstackhq/runtime` directly. Instead, import directly from the output location:
 
 ```ts
-const db = enhance(prisma, { user: getSessionUser() }, { loadPath: 'lib/zenstack' });
+import { enhance } from 'lib/zenstack/enhance';
 ```
 
 ### Next
diff --git a/docs/the-complete-guide/part1/4-access-policy/4.1-model-level.md b/docs/the-complete-guide/part1/4-access-policy/4.1-model-level.md
index ce225fc9..d9f197f5 100644
--- a/docs/the-complete-guide/part1/4-access-policy/4.1-model-level.md
+++ b/docs/the-complete-guide/part1/4-access-policy/4.1-model-level.md
@@ -5,7 +5,7 @@ slug: model-level
 
 # Model-Level Policies
 
-The most frequently used access policies are those declared at the model level - written using `@@allow` and `@@deny` model-level attributes.
+The most frequently used access policies are those declared at the model level - written using the `@@allow` and `@@deny` model-level attributes.
 
 :::info Model-level vs field-level attributes
 
@@ -69,7 +69,7 @@ Each of the CRUD operation types governs a set of Prisma Client methods, as foll
 
 - **read**
   
-    `findUnique`, `findUniqueOrThrow`, `findFirst`, `findFirstOrThrow`, `count`, `aggregate`, and `groupBy`.
+    `findMany`, `findUnique`, `findUniqueOrThrow`, `findFirst`, `findFirstOrThrow`, `count`, `aggregate`, and `groupBy`.
     
     The "read" operation also determines whether the value returned from `create`, `update` and `delete` method can be read.
 
@@ -114,6 +114,8 @@ Does `Post#1` need to satisfy its "update" policies?
 
 The trick is to consider which side of the relation will have a foreign key update. In this example, the foreign key `authorId` of `Post#1` will be updated, so `Post#1` needs to satisfy its "update" policies, and `User`'s "update" policies are not relevant.
 
+A special case is Prisma's [implicit many-to-many relation](https://www.prisma.io/docs/orm/prisma-schema/data-model/relations/many-to-many-relations#implicit-many-to-many-relations). Since there are no explicit foreign keys in this case, manipulating such a relation requires both sides to satisfy the "update" policies.
+
 :::
 
 ### How Do Policies Affect Prisma Client's Behavior?
diff --git a/docs/the-complete-guide/part1/4-access-policy/4.2-authn-authz.md b/docs/the-complete-guide/part1/4-access-policy/4.2-authn-authz.md
index 05a6ceca..2375759c 100644
--- a/docs/the-complete-guide/part1/4-access-policy/4.2-authn-authz.md
+++ b/docs/the-complete-guide/part1/4-access-policy/4.2-authn-authz.md
@@ -28,13 +28,13 @@ Here are some popular authentication solutions in the Javascript/Typescript ecos
 - [Firebase Authentication](https://firebase.google.com/docs/auth)
 - [Lucia](https://lucia-auth.com/)
 
-You can find how to integrate some of these solutions in the [Integration With Authentication](/docs/category/integrating-with-authentication) guides.
+You can find how to integrate some of these solutions in the [Integration With Authentication](../../../category/integrating-with-authentication) guides.
 
 ### Authorization
 
 Authorization is the process of determining whether a user has permission to perform a specific action. ZenStack's core part is an authorization solution that directly couples with the database. It controls what CRUD actions a user can perform on a table or a field.
 
-There are three commonly used patterns for modeling authorization in applications:
+Some commonly used patterns for modeling authorization are listed below (with increasing flexibility and complexity):
 
 1. Access Control List (ACL)
 
@@ -48,4 +48,8 @@ There are three commonly used patterns for modeling authorization in application
 
     Permissions are defined over attributes of the user and the resource. For example, a user can be assigned with 'read' permission to a `Post` row if the `Post` is published and the user is a subscribed member.
 
+4. Relation-Based Access Control (ReBAC)
+   
+    Permissions are defined by the presence of relationships between the subjects and resources. For example, a user can edit a `Post` if the user has an "edit" relationship with the `Post`.
+
 A complex application often needs to use a combination of these patterns. ZenStack's access policy system is designed to be easy to use yet have excellent flexibility to tackle these challenges.
diff --git a/docs/the-complete-guide/part1/4-access-policy/4.3-current-user.md b/docs/the-complete-guide/part1/4-access-policy/4.3-current-user.md
index bce25e0a..cbc341ad 100644
--- a/docs/the-complete-guide/part1/4-access-policy/4.3-current-user.md
+++ b/docs/the-complete-guide/part1/4-access-policy/4.3-current-user.md
@@ -44,6 +44,23 @@ model MyUser {
 }
 ```
 
+:::info What if I don't store users in the database?
+
+If you use an external authentication service like Clerk or Supabase Auth, you can choose not to store users in the database, since the auth services are already doing this for you. However, in ZModel, the `auth()` function call always needs to be resolved to a typing.
+
+The solution is to use a `type` instead of a `model` to define the shape of `auth()`. A `type` can contain fields as models do, the it's not mapped to a database table, and only serves the purpose of typing.
+
+```zmodel
+type User {
+    id String @id
+    ...
+}
+```
+
+Since `User` is not mapped to a database table, you can't have other models to have relations to it. Instead, you can store the user id provided by the authentication service in your models, which is like a "foreign key" but pointing to an external system.
+
+:::
+
 ### Providing Current User
 
 Since ZenStack is not an authentication solution, it doesn't know who the current user is. It's up to the developer to get it from the authentication side and pass it to ZenStack (when calling the `enhance` function).
@@ -79,6 +96,8 @@ const user = await prisma.user.findUniqueOrThrow({ where: { id: userId }, select
 const db = enhance(prisma, { user });
 ```
 
+In the future, we may introduce a new option to control if the missing fields should be automatically fetched from the database.
+
 :::
 
 ### Writing Conditions With `auth()`
@@ -170,9 +189,43 @@ The `expression?[condition]` syntax used above is called "Collection Predicate E
 
 :::
 
-### 🛠️ Adding User-Based Access Control
+### Using `auth()` in `@default()`
+
+Another very useful place to use `auth()` is in the `@default()` attribute. It's common to require a resource's owner to be the current user when creating an entity. You can use a "create" rule to enforce that:
+
+```zmodel
+model Resource {
+    ...
+    owner User @relation(fields: [ownerId], references: [id])
+    ownerId Int
 
-Add the following policies to the schema:
+    @@allow('create', owner == auth())
+}
+```
+
+The policy ensures that the caller can't set a resource's owner to someone else. However, when making a create call, you'll still need to connect the owner:
+  
+```ts
+await db.resource.create({ data: { ..., owner: { connect: { id: currentUser.id } } } });
+```
+
+This can be simplified by using `auth()` in the `@default()` attribute:
+
+```zmodel
+model Resource {
+    ...
+    owner User @relation(fields: [ownerId], references: [id])
+    ownerId Int @default(auth().id)
+
+    @@allow('create', owner == auth())
+}
+```
+
+Now, when creating a resource, you don't need to explicitly make the owner connection.
+
+### 🛠️ Adding User-Based Access Control {#add-user-based-access-control}
+
+Add the following policies and default values to the schema:
 
 ```zmodel title="schema.zmodel"
 model User {
@@ -185,9 +238,17 @@ model User {
   @@allow('all', auth() == this)
 }
 
+model Space {
+  ...
+
+  ownerId Int @default(auth().id)
+}
+
 model List {
   ...
 
+  ownerId Int @default(auth().id)
+
   // owner has full access
   @@allow('all', auth() == owner)
 }
@@ -195,6 +256,8 @@ model List {
 model Todo {
   ...
 
+  ownerId Int @default(auth().id)
+
   // owner and list owner has full access
   @@allow('all', auth() == owner || auth() == list.owner)
 }
@@ -254,7 +317,7 @@ db.user.findMany();
 We can also create Todo List now:
 
 ```js
-db.list.create({ data: { title: 'Grocery', owner: { connect: { email: 'rachel@zenstack.dev' } }, space: { connect: { slug: 'central-perk' } } } })
+db.list.create({ data: { title: 'Grocery', space: { connect: { slug: 'central-perk' } } } })
 ```
 
 ```js
@@ -269,6 +332,10 @@ db.list.create({ data: { title: 'Grocery', owner: { connect: { email: 'rachel@ze
 }
 ```
 
+:::tip
+Notice that we don't need to connect the `owner` relation when creating? The `@default(auth().id)` attribute takes care of it.
+:::
+
 However, as user Rachel, we can't create a Todo List on behalf of Joey because it violates the `@@allow('all', auth() == owner)` rule:
 
 ```js
diff --git a/docs/the-complete-guide/part1/4-access-policy/4.4-relations.md b/docs/the-complete-guide/part1/4-access-policy/4.4-relations.md
index 8578e9d9..5cc0031e 100644
--- a/docs/the-complete-guide/part1/4-access-policy/4.4-relations.md
+++ b/docs/the-complete-guide/part1/4-access-policy/4.4-relations.md
@@ -18,16 +18,16 @@ Accessing *-to-one relation is straightforward. You simply reference the field d
 
 ```zmodel
 model List {
-    id Int
-    private Boolean
+  id Int
+  private Boolean
 }
 
 model Todo {
-    id Int
-    list List @relation(...)
+  id Int
+  list List @relation(...)
 
-    // `list` references a to-one relation
-    @@allow('update', !list.private)
+  // `list` references a to-one relation
+  @@allow('update', !list.private)
 }
 ```
 
@@ -79,58 +79,64 @@ model Space {
 }
 
 model SpaceUser {
-    ...
+  ...
+
+  // require login
+  @@deny('all', auth() == null)
 
-    // require login
-    @@deny('all', auth() == null)
+  // space owner can add any one
+  @@allow('create', space.owner == auth())
+  
+  // space admin can add anyone but not himself
+  @@allow('create', auth() != user && space.members?[user == auth() && role == 'ADMIN'])
 
-    // space owner and admins have full access
-    @@allow('all', space.owner == auth() || space.members?[user == auth() && role == 'ADMIN'])
+  // space admin can update/delete
+  @@allow('update,delete', space.members?[user == auth() && role == 'ADMIN'])
 
-    // user can read members of spaces that he's a member of
-    @@allow('read', space.members?[user == auth()])
+  // user can read members of spaces that he's a member of
+  @@allow('read', space.members?[user == auth()])
 }
 
 model User {
-    ...
+  ...
 
-    // everyone can sign up
-    @@allow('create', true)
+  // everyone can sign up
+  @@allow('create', true)
 
-    // full access by oneself
-    @@allow('all', auth() == this)
+  // full access by oneself
+  @@allow('all', auth() == this)
 
-    // can be read by users sharing any space
-    @@allow('read', spaces?[space.members?[user == auth()]])
+  // can be read by users sharing any space
+  @@allow('read', spaces?[space.members?[user == auth()]])
 }
 
 model List {
-    ...
+  ...
 
-    // require login
-    @@deny('all', auth() == null)
+  // require login
+  @@deny('all', auth() == null)
 
-    // can be read by space members if not private
-    @@allow('read', owner == auth() || (space.members?[user == auth()] && !private))
+  // can be read by space members if not private
+  @@allow('read', owner == auth() || (space.members?[user == auth()] && !private))
 
-    // when create, owner must be set to current user, and user must be in the space
-    @@allow('create,update', owner == auth() && space.members?[user == auth()])
+  // when create, owner must be set to current user, and user must be in the space
+  @@allow('create,update', owner == auth() && space.members?[user == auth()])
 
-    // can be deleted by owner
-    @@allow('delete', owner == auth())
+  // can be deleted by owner
+  @@allow('delete', owner == auth())
 }
 
 model Todo {
-    ...
+  ...
 
-    // require login
-    @@deny('all', auth() == null)
+  // require login
+  @@deny('all', auth() == null)
 
-    // owner has full access
-    @@allow('all', list.owner == auth())
+  // owner has full access
+  @@allow('all', list.owner == auth())
 
-    // space members have full access if the parent List is not private
-    @@allow('all', list.space.members?[user == auth()] && !list.private)
+  // space members have full access if the parent List is not private
+  @@allow('all', list.space.members?[user == auth()] && !list.private)
 }
 ```
 
@@ -163,3 +169,37 @@ db.list.findMany()
 ```
 
 We queried with user Joey and now can get the `List` created by Rachel because Joey is a member of the same space.
+
+:::info A note about "create" rules
+
+You might have noticed we have two "create" rules in the `SpaceUser` model.
+
+```zmodel
+// space owner can add any one
+@@allow('create', space.owner == auth())
+
+// space admin can add anyone but not himself
+@@allow('create', auth() != user 
+    && space.members?[user == auth() && role == 'ADMIN'])
+```
+
+One might be tempting to use one simple rule like the following to allow space admins to create new memberships.
+
+```zmodel
+@@allow('create', space.members?[user == auth() && role == 'ADMIN'])
+```
+
+However, it'll be problematic due to "create" rule's semantic - **if an entity were to be created, would it satisfy the rules**? Or, in more details, a create process works like the following:
+
+1. Initiate a transaction and create the entity.
+2. In the same transaction, try to read the created entity with the "create" rules as filter, and see if it succeeds.
+3. If the read fails, the transaction is rolled back; otherwise it's committed.
+
+The "post-create check" semantic allows the rules to access relations of the entity being created. For simple cases, ZenStack may apply optimizations to reject a create request without initiating a transaction, but generally speaking the "post-create check" semantic is the correct way to think about it. We may introduce a "pre-create" policy type in the future.
+
+With these in mind, if we were to use the simple rule, a user can add himself to any space as "ADMIN", because after the create happens, the new membership would satisfy the rule. By splitting the rule into two, we can prevent this from happening:
+
+- Space owner can add anyone (including himself) into the space.
+- Space admin can add anyone but not himself.
+
+:::
diff --git a/docs/the-complete-guide/part1/4-access-policy/4.5-expr-func.md b/docs/the-complete-guide/part1/4-access-policy/4.5-expr-func.md
index f45b9f02..0dd9498e 100644
--- a/docs/the-complete-guide/part1/4-access-policy/4.5-expr-func.md
+++ b/docs/the-complete-guide/part1/4-access-policy/4.5-expr-func.md
@@ -77,7 +77,7 @@ Used to invoke a function. E.g, `auth()` is an Invocation Expression.
 
 ### Functions
 
-You can find the detailed signature of each function in the [Predefined attribute functions](/docs/reference/zmodel-language#predefined-attribute-functions) document.
+You can find the detailed signature of each function in the [Predefined attribute functions](../../../reference/zmodel-language#predefined-attribute-functions) document.
 
 #### auth()
 
diff --git a/docs/the-complete-guide/part1/4-access-policy/4.7-field-level.md b/docs/the-complete-guide/part1/4-access-policy/4.7-field-level.md
index 35ce2051..9498c90d 100644
--- a/docs/the-complete-guide/part1/4-access-policy/4.7-field-level.md
+++ b/docs/the-complete-guide/part1/4-access-policy/4.7-field-level.md
@@ -60,7 +60,9 @@ model Post {
 }
 ```
 
-In the example above, if a `Post` is published, although the model-level policy denies updates, an EDITOR user can still update the `published` field because of the overriding setting on the field-level policy. Without the override flag, the update operation will be denied.
+In the example above, if a `Post` is published, although the model-level policy denies updates, an EDITOR user can still update the `published` field (and only this field) because of the overriding setting on the field-level policy. Without the override flag, the update operation will be denied.
+
+You can use this feature with "read" policies, too. If a field has an override "read" policy, the field can be read if it's explicitly selected in the query, even if the model-level policy denies access.
 
 ### 🛠️ Adding Field-Level Policies
 
@@ -69,12 +71,12 @@ Back to where we left off in the previous chapter, let's tighten up our schema a
 ```zmodel title="schema.zmodel"
 model List {
     ...
-    ownerId Int @deny('update', true)
+    ownerId Int @default(auth().id) @deny('update', true)
 }
 
 model Todo {
     ...
-    ownerId Int @deny('update', true)
+    ownerId Int @default(auth().id) @deny('update', true)
 }
 ```
 
diff --git a/docs/the-complete-guide/part1/4-access-policy/4.8-keep-policies-dry.md b/docs/the-complete-guide/part1/4-access-policy/4.8-keep-policies-dry.md
new file mode 100644
index 00000000..b2944816
--- /dev/null
+++ b/docs/the-complete-guide/part1/4-access-policy/4.8-keep-policies-dry.md
@@ -0,0 +1,65 @@
+---
+sidebar_label: 4.8 Keeping the Policies DRY
+slug: keep-policies-dry
+---
+
+# Keeping the Policies DRY
+
+ZenStack's access policies provide you with a powerful tool to model authorization in a intuitive and declarative way. However, as your application grows, you may find yourself repeating the same access policy patterns across multiple models. In this section, we'll review a typical type of duplication and show you how to avoid it by delegating to related models.
+
+### Parent-Child Duplication
+
+If your application has a hierarchy of models, you'll quite often notice that the child models repeat the access policies of their parent. This can be found in our Todo schema, where `List` and `Todo` models have a parent-child relationship:
+
+```zmodel
+model List {
+  ...
+
+  // require login
+  @@deny('all', auth() == null)
+
+  // can be read by space members if not private
+  @@allow('read', owner == auth() || (space.members?[user == auth()] && !private))
+
+  // when create, owner must be set to current user, and user must be in the space
+  @@allow('create,update', owner == auth() && space.members?[user == auth()])
+
+  ...
+}
+
+model Todo {
+  ...
+
+  // require login
+  @@deny('all', auth() == null)
+
+  // owner has full access
+  @@allow('all', list.owner == auth())
+
+  // space members have full access if the parent List is not private
+  @@allow('all', list.space.members?[user == auth()] && !list.private)
+}
+```
+
+Although not a 100% match, the rules of `Todo` closely resemble those of `List`. How can we avoid such duplication and stay DRY?
+
+### Delegating Access Control to a Relation
+
+If we carefully inspect the policies, we'll find the rules of `Todo` can summarized into a single statement:
+
+> One has full access to a `Todo` entity if he can read its parent `List` entity.
+
+ZenStack provides a `check()` attribute function for you to delegate access control of the current model to a relation. Let's refactor the `Todo` model to use this feature 🛠️:
+
+```zmodel
+model Todo {
+    ...
+
+    // full access if the parent list is readable
+    @@allow('all', check(list, 'read'))
+}
+```
+
+When authorizing an operation on a `Todo` (e.g., "update"), ZenStack will make the decision based on if the related `List` entity is readable. If you adjust the rules on `List` in the future, the `Todo` model automatically follows without needing any changes.
+
+The `check()` API can be used in both model-level and field-level policies. See the [API Reference](../../../reference/zmodel-language#check) for more details.
\ No newline at end of file
diff --git a/docs/the-complete-guide/part1/4-access-policy/index.md b/docs/the-complete-guide/part1/4-access-policy/index.md
index f5e34f8e..7673d701 100644
--- a/docs/the-complete-guide/part1/4-access-policy/index.md
+++ b/docs/the-complete-guide/part1/4-access-policy/index.md
@@ -16,7 +16,7 @@ The features that ZenStack's access policy provides include:
 - Pre and post-update rules
 - Functions that help you write advanced rules
 
-:::info Secure By Default
+:::info Secure by Default
 
 When using an enhanced Prisma Client, the first thing that may surprise you is that you can't read or write any data. For example, given the following model:
 
@@ -44,7 +44,7 @@ The reason is that, with an enhanced client, all CRUD operations are denied by d
 Enforcing access policies causes an enhanced Prisma Client to behave differently from the original:
 
 - Read operations can return fewer rows than with a raw Prisma Client.
-- Write operations can fail with an error if policies are violated. See [Error Handling](/docs/reference/error-handling) for more details.
+- Write operations can fail with an error if policies are violated. See [Error Handling](../../../reference/error-handling) for more details.
 
 ### ZenStack Access Policy vs Postgres Row-Level Security
 
diff --git a/docs/the-complete-guide/part1/5-data-validation.md b/docs/the-complete-guide/part1/5-data-validation.md
index 567d03d1..e4ace726 100644
--- a/docs/the-complete-guide/part1/5-data-validation.md
+++ b/docs/the-complete-guide/part1/5-data-validation.md
@@ -23,12 +23,13 @@ Data validation and access policy may have some similarities, but there are some
 1. Access policies are usually defined regarding the current user, while validation rules are defined against the data itself.
 2. Access policies are mostly evaluated on the database side (through injection into Prisma queries), while data validation is evaluated entirely on the application side.
 3. Access policies govern CRUD operations, while data validation only covers "create" and "update" actions.
+4. Data validation only works with simple fields, not with relations.
 
 :::
 
 ### Field-Level Validation
 
-ZenStack provides a set of field-level attributes for defining validation rules. You can find the full list of such attributes [here](/docs/reference/zmodel-language#field-level-validation-attributes). Here are a few usage examples:
+ZenStack provides a set of field-level attributes for defining validation rules. You can find the full list of such attributes [here](../../reference/zmodel-language#field-level-validation-attributes). Here are a few usage examples:
 
 ```zmodel
 model User {
@@ -52,7 +53,7 @@ model User {
 
 ### Model-Level Validation
 
-You can use the model-level `@@validate` attribute to define validation rules that involve multiple fields. A set of helper functions are provided for building complex validation expressions. You can find the full list of such functions [here](/docs/reference/zmodel-language#model-level-validation-attributes). Here's an example:
+You can use the model-level `@@validate` attribute to define validation rules that involve multiple fields. A set of helper functions are provided for building complex validation expressions. You can find the full list of such functions [here](../../reference/zmodel-language#model-level-validation-attributes). Here's an example:
 
 ```zmodel
 model User {
@@ -80,11 +81,15 @@ We can use data validation to improve our app's robustness in many places. Two s
    
    ```zmodel
     model Space {
-         ...
-         slug String @unique @regex('^[0-9a-zA-Z_\-]{4,16}$')
+        ...
+        slug String @unique @regex('^[0-9a-zA-Z]{4,16}$')
     }
     ```
 
+:::info
+The argument of the `@regex` attribute is a regular expression string, so regex character escapes like `\d`, `\w` need to be double escaped as `\\d`, `\\w`.
+:::
+
 Rerun generation and start REPL:
 
 ```bash
diff --git a/docs/the-complete-guide/part1/6-other-enhancements.md b/docs/the-complete-guide/part1/6-other-enhancements.md
index 444d4436..c7692932 100644
--- a/docs/the-complete-guide/part1/6-other-enhancements.md
+++ b/docs/the-complete-guide/part1/6-other-enhancements.md
@@ -20,7 +20,7 @@ model User {
 
 Under the hood, ZenStack uses [bcryptjs](https://github.com/dcodeIO/bcrypt.js/tree/master) to generate a hash.
 
-See [here](/docs/reference/zmodel-language#password) for more details about the `@password` attribute.
+See [here](../../reference/zmodel-language#password) for more details about the `@password` attribute.
 
 #### 🛠️ Adding User Password
 
diff --git a/docs/the-complete-guide/part1/8-conclusion.md b/docs/the-complete-guide/part1/8-conclusion.md
index 09f2cf1a..00f5ebc3 100644
--- a/docs/the-complete-guide/part1/8-conclusion.md
+++ b/docs/the-complete-guide/part1/8-conclusion.md
@@ -10,4 +10,12 @@ In this part of the guide, we've focused on the core layer of ZenStack - an enha
 
 The design choice of enhancing Prisma Client makes ZenStack framework-agnostic. It can be used wherever Prisma is used - a CLI application, a Node.js backend, or a full-stack application - as a non-intrusive drop-in replacement.
 
-You can find the completed sample project up to the end of this part in the "part1" branch of [the-complete-guide-sample](https://github.com/zenstackhq/the-complete-guide-sample/tree/part1).
+As you have seen, the ZModel language provides great flexibility in modeling the authorization aspects of your application. If you want to learn how commonly used patterns like RBAC, ABAC, etc., can be modeled in ZModel, this blog post can be a good reference: [Modeling Authorization in Prisma - No Theory, Just Code](https://zenstack.dev/blog/model-authz).
+
+---
+
+The completed sample project up to the end of this part can be found in the "part1" branch of the [the-complete-guide-sample](https://github.com/zenstackhq/the-complete-guide-sample/tree/part1) repo. You can clone it and check out to that branch with the following command:
+
+```bash
+git clone --branch part1 https://github.com/zenstackhq/the-complete-guide-sample.git my-todo-app
+```
diff --git a/docs/the-complete-guide/part1/index.md b/docs/the-complete-guide/part1/index.md
index 7bbd9da3..1eafd2e1 100644
--- a/docs/the-complete-guide/part1/index.md
+++ b/docs/the-complete-guide/part1/index.md
@@ -14,6 +14,7 @@ As the creators of ZenStack, we love Prisma and enjoy its excellent developer ex
 - Custom attributes and functions
 - Multi-file schema
 - Model inheritance
+- Polymorphic relations
 - ...
 
 Essentially, your schema becomes the single source of truth of data and rules, and at runtime, your database client not only handles CRUD operations but also does it in a secure way.
diff --git a/docs/the-complete-guide/part1/under-the-hood.md b/docs/the-complete-guide/part1/under-the-hood.md
index b64231bf..22b5b17e 100644
--- a/docs/the-complete-guide/part1/under-the-hood.md
+++ b/docs/the-complete-guide/part1/under-the-hood.md
@@ -16,115 +16,100 @@ This document explains how these extensions work so that you can make a more inf
 
 ## ZModel Language
 
-ZenStack implemented the ZModel DSL from scratch, including the CLI and the VSCode extension, using the fantastic language toolkit [Langium](https://langium.org/). The DLS includes a plugin system, allowing a modular and extensible way to generate different artifacts from the schema. The core functionality of the toolkit is supported by the following three core plugins:
+ZenStack implemented the ZModel DSL from scratch, including the CLI and the VSCode extension, using the fantastic language toolkit [Langium](https://langium.org/). The DLS includes a plugin system, allowing a modular and extensible way to generate different artifacts from the schema. The core functionality of the toolkit is supported by the following core plugins:
 
 -   Prisma: `@core/prisma`
 
-    The Prisma plugin generates the Prisma schema and Prisma client from the ZModel schema. The Prisma schema can then be used for common Prisma tasks like `db push`, `migrate dev`, etc.
+    The `@core/prisma` plugin generates the Prisma schema and Prisma client from the ZModel schema. The Prisma schema can then be used for common Prisma tasks like `db push`, `migrate dev`, etc.
 
--   Model-meta: `@core/model-meta`
+-   Enhancer: `@core/enhancer`
 
-    The model-meta plugin generates the model metadata, which provides basic information about models and fields at runtime. The metadata is much more lightweight than the whole ZModel AST and is much cheaper to load.
+    The `@core/enhancer` plugin generates several Javascript modules to support ZenStack's runtime enhancements to Prisma clients. The modules include:
 
-    The default output location is `node_modules/.zenstack/model-meta.ts`.
+    - Model metadata
+    
+        Provides basic information about models and fields at runtime. The metadata is much more lightweight than the whole ZModel AST and is much cheaper to load.
 
--   Policy: `@core/access-policy`
+        The default output location is `node_modules/.zenstack/model-meta.js`.
 
-    The access policy plugin converts access policy rules (expressed with `@@allow` and `@@deny` attributes) into checker functions. The functions take a context object as input and return partial Prisma query objects, which will be injected into Prisma query arguments at runtime. The context object contains the following properties:
+    - Access policies
 
-    -   `user`: the current user, which serves as the return value of [`auth()`](/docs/the-complete-guide/part1/access-policy/current-user) in the policy rules.
-    -   `preValue`: the previous value of an entity before update (for supporting the [`future()`](/docs/the-complete-guide/part1/access-policy/post-update) function in the policy rules).
+        Javascript function representation of access policy rules (expressed with `@@allow` and `@@deny` attributes). The functions take a context object as input and return partial Prisma query objects, which will be injected into Prisma query arguments at runtime. The context object contains the following properties:
 
-    The default output location is `node_modules/.zenstack/policy.ts`.
+        -   `user`: the current user, which serves as the return value of [`auth()`](./access-policy/current-user) in the policy rules.
+        -   `preValue`: the previous value of an entity before update (for supporting the [`future()`](./access-policy/post-update) function in the policy rules).
 
-    For example, for the following `Post` model:
+        The default output location is `node_modules/.zenstack/policy.js`.
 
-    ```zmodel
-    model Post {
-        id        String @id @default(cuid())
-        createdAt DateTime @default(now())
-        updatedAt DateTime @updatedAt
-        title     String
-        published Boolean @default(false)
-        author    User @relation(fields: [authorId], references: [id])
-        authorId  String
+        For example, for the following `Post` model:
 
-        // author has full access
-        @@allow('all', auth() == author)
+        ```zmodel
+        model Post {
+            id        String @id @default(cuid())
+            createdAt DateTime @default(now())
+            updatedAt DateTime @updatedAt
+            title     String
+            published Boolean @default(false)
+            author    User @relation(fields: [authorId], references: [id])
+            authorId  String
 
-        // logged-in users can view published posts
-        @@allow('read', auth() != null && published)
-    }
-    ```
-
-    , the following checker functions are generated for "read" and "update" respectively:
+            // author has full access
+            @@allow('all', auth() == author)
 
-    ```ts
-    function Post_read(context: QueryContext) {
-        const user = hasAllFields(context.user, ['id']) ? context.user : null;
-        return {
-            OR: [
-                user == null
-                    ? { OR: [] } // false condition
-                    : {
-                          author: {
-                              is: {
-                                  id: user.id,
+            // logged-in users can view published posts
+            @@allow('read', auth() != null && published)
+        }
+        ```
+
+        , the following checker functions are generated for "read" and "update" respectively:
+
+        ```ts
+        function Post_read(context: QueryContext) {
+            const user = hasAllFields(context.user, ['id']) ? context.user : null;
+            return {
+                OR: [
+                    user == null
+                        ? { OR: [] } // false condition
+                        : {
+                              author: {
+                                  is: {
+                                      id: user.id,
+                                  },
                               },
                           },
-                      },
-                {
-                    AND: [
-                        user == null ? { OR: [] } : { AND: [] },
-                        {
-                            published: true,
-                        },
-                    ],
-                },
-            ],
-        };
-    }
+                    {
+                        AND: [
+                            user == null ? { OR: [] } : { AND: [] },
+                            {
+                                published: true,
+                            },
+                        ],
+                    },
+                ],
+            };
+        }
 
-    function Post_update(context: QueryContext) {
-        const user = hasAllFields(context.user, ['id']) ? context.user : null;
-        return user == null
-            ? { OR: [] } // false condition
-            : {
-                  author: {
-                      is: {
-                          id: user.id,
+        function Post_update(context: QueryContext) {
+            const user = hasAllFields(context.user, ['id']) ? context.user : null;
+            return user == null
+                ? { OR: [] } // false condition
+                : {
+                      author: {
+                          is: {
+                              id: user.id,
+                          },
                       },
-                  },
-              };
-    }
-    ```
+                  };
+        }
+        ```
 
 ## Runtime Enhancements
 
-The primary responsibility of ZenStack's runtime is to create _enhanced_ Prisma client instances:
-
--   `enhance` creates an enhanced client that includes all behaviors below.
--   `withPolicy` creates an enhanced client that enforces access policies expressed with `@@allow` and `@@deny` attributes.
--   `withPassword` creates an enhanced client that automatically hashes fields marked with the `@password` attribute before storing them in the database.
--   `withOmit` creates an enhanced client that automatically strips fields marked with the `@omit` attribute before returning to the caller.
-
-### Proxies
-
-Runtime enhancements are achieved by creating transparent proxies around raw Prisma clients. The proxies intercept all Prisma client methods, inject into query arguments, and manipulate the query results returned by the client. The proxies work independently from each other so that they can be freely combined. In fact, the `enhance` helper is a direct combination of `withPassword`, `withOmit`, and `withPolicy`.
-
-```ts
-export function enhance<DbClient extends object>(
-    prisma: DbClient,
-    context?: WithPolicyContext,
-    options?: EnhancementOptions
-) {
-    return withPolicy(withOmit(withPassword(prisma, modelMeta), modelMeta), context, policy, modelMeta);
-}
-```
+The primary responsibility of ZenStack's runtime is to create _enhanced_ Prisma client instances. Runtime enhancements are achieved by creating transparent proxies around raw Prisma clients. The proxies intercept all Prisma client methods, inject into query arguments, and manipulate the query results returned by the client.
 
-### Access Policies
+### Access Policy
 
-Access policies, enabled by the `withPolicy` enhancer, are the most complex parts of the system. Part of the complexity comes from the great flexibility Prisma offers in querying and mutating data. For example, to enforce "read" rules on a `Post` model, we need to consider several possibilities:
+Access policy is the most complex part of the system. Part of the complexity comes from the great flexibility Prisma offers in querying and mutating data. For example, to enforce "read" rules on a `Post` model, we need to consider several possibilities:
 
 ```ts
 // a direct where condition
@@ -197,7 +182,7 @@ We need the following measures to enforce access policies systematically:
     prisma.user.update({ data: { ... }, include: { posts: { where: { /* read conditions */ } } } });
     ```
 
-1. **Inject filter conditions into the "where" clause of "mutate many"**
+2. **Inject filter conditions into the "where" clause of "mutate many"**
 
     This covers cases like `updateMany` and `deleteMany`:
 
@@ -232,7 +217,7 @@ We need the following measures to enforce access policies systematically:
     });
     ```
 
-1. **Post-read check for entities fetched as a to-one relation**
+3. **Post-read check for entities fetched as a to-one relation**
 
     To-one relation is a special case for reading because there's no way to do filtering at the read time: you either include it or not. So, we need to do a post-read check to ensure the fetched entity can be read.
 
@@ -247,7 +232,7 @@ We need the following measures to enforce access policies systematically:
     }
     ```
 
-1. **Transaction-protected mutations**
+4. **Transaction-protected mutations**
 
     Policies that do "post-mutation" checks, including "create" and "post-update" ("update" rule calling [`future()`](#the-future-function) function) rules, are protected by a transaction. The mutation is conducted first, and then post-mutation checks are performed. If any of the checks fail, the transaction rolls back.
 
diff --git a/docs/the-complete-guide/part2/1-using-plugins.md b/docs/the-complete-guide/part2/1-using-plugins.md
index 93fe8385..ce07c265 100644
--- a/docs/the-complete-guide/part2/1-using-plugins.md
+++ b/docs/the-complete-guide/part2/1-using-plugins.md
@@ -29,4 +29,4 @@ plugin openapi {
 
 A plugin declaration requires a mandatory `provider` field that specifies the package name or Javascript module path of the plugin (which the `zenstack` CLI imports directly with `require()`). It can also contain other mandatory and optional fields specific to the plugin.
 
-When you run `zenstack generate`, the plugin will be executed and generate the output files.
+When you run `npx zenstack generate`, the plugin will be executed and generate the output files.
diff --git a/docs/the-complete-guide/part2/2-builtin-plugins.md b/docs/the-complete-guide/part2/2-builtin-plugins.md
index fc0f4e33..b063dd6f 100644
--- a/docs/the-complete-guide/part2/2-builtin-plugins.md
+++ b/docs/the-complete-guide/part2/2-builtin-plugins.md
@@ -12,19 +12,15 @@ Most of the core functionalities of ZenStack are implemented as or supported by
 
 Here's a quick overview of them:
 
-- [@core/prisma](/docs/reference/plugins/prisma)
+- [@core/prisma](../../reference/plugins/prisma)
 
     Transforms ZModel to Prisma schema and runs `prisma generate` to generate Prisma Client.
 
-- [@core/model-meta](/docs/reference/plugins/model-meta)
+- [@core/enhancer](../../reference/plugins/enhancer)
 
-    Transforms ZModel to lightweight Javascript to be used at runtime.
+    Generates Javascript modules that support ZenStack's runtime enhancements, like access policy enforcement.
 
-- [@core/access-policy](/docs/reference/plugins/access-policy)
-
-    Transforms access policies into partial Prisma query objects for injecting Prisma queries at runtime.
-
-- [@core/zod](/docs/reference/plugins/zod)
+- [@core/zod](../../reference/plugins/zod)
 
     Transforms ZModel into [Zod](https://zod.dev/) schemas for validating input data at runtime.
 
@@ -41,18 +37,18 @@ plugin prisma {
 
 Besides core plugins, the ZenStack team maintains a set of useful but non-essential plugins. They are implemented in separate npm packages under the `@zenstackhq` organization.
 
-- [@zenstackhq/tanstack-query](/docs/reference/plugins/tanstack-query)
+- [@zenstackhq/tanstack-query](../../reference/plugins/tanstack-query)
 
-    Generates frontend data query hooks for targeting [Tanstack Query](https://tanstack.com/query). In [Part III](/docs/the-complete-guide/part3/) and [Part IV](/docs/the-complete-guide/part4/) of this guide, you'll learn more about using ZenStack for full-stack development.
+    Generates frontend data query hooks for targeting [Tanstack Query](https://tanstack.com/query). In [Part III](../part3/) and [Part IV](../part4/) of this guide, you'll learn more about using ZenStack for full-stack development.
 
-- [@zenstackhq/swr](/docs/reference/plugins/swr)
+- [@zenstackhq/swr](../../reference/plugins/swr)
 
-    Generates frontend data query hooks for targeting [SWR](https://swr.vercel.app/). In [Part III](/docs/the-complete-guide/part3/) and [Part IV](/docs/the-complete-guide/part4/) of this guide, you'll learn more about using ZenStack for full-stack development.
+    Generates frontend data query hooks for targeting [SWR](https://swr.vercel.app/). In [Part III](../part3/) and [Part IV](../part4/) of this guide, you'll learn more about using ZenStack for full-stack development.
 
-- [@zenstackhq/trpc](/docs/reference/plugins/trpc)
+- [@zenstackhq/trpc](../../reference/plugins/trpc)
 
     Generates [tRPC](https://trpc.io/) routers for database queries and mutations.
   
-- [@zenstackhq/openapi](/docs/reference/plugins/openapi)
+- [@zenstackhq/openapi](../../reference/plugins/openapi)
 
     Generates [OpenAPI](https://www.openapis.org/) specs from the ZModel schema.
diff --git a/docs/the-complete-guide/part2/3-writing-plugins.md b/docs/the-complete-guide/part2/3-writing-plugins.md
index 6cb81f93..149e7094 100644
--- a/docs/the-complete-guide/part2/3-writing-plugins.md
+++ b/docs/the-complete-guide/part2/3-writing-plugins.md
@@ -8,17 +8,18 @@ The ZModel schema is the single source of truth for your data and access control
 
 ### Structure of a Plugin
 
-A plugin is a Node.js module that has two exports:
+A plugin is a Node.js module that has the following exports:
 
-1. A named export `name` that specifies the name of the plugin used for logging and error reporting.
-2. A default function export containing the plugin logic.
+1. [Required] A named export `name` that specifies the name of the plugin used for logging and error reporting.
+2. [Optional] A named export `description` that contains a short human-readable description of the plugin.
+3. [Required] A default function export containing the plugin logic.
 
 Here's what a bare minimum plugin looks like:
 
 ```ts
-import type { DMMF } from '@prisma/generator-helper';
-import { type PluginOptions } from '@zenstackhq/sdk';
-import { type Model } from '@zenstackhq/sdk/ast';
+import type { PluginOptions } from '@zenstackhq/sdk';
+import type { DMMF } from '@zenstackhq/sdk/prisma';
+import type { Model } from '@zenstackhq/sdk/ast';
 
 export const name = 'My Plugin';
 
@@ -104,6 +105,19 @@ model Foo {
 }
 ```
 
+### Execution Order
+
+By default, when you run `zenstack generate`, ZenStack's built-in plugins are executed before all user plugins, ensuring Prisma schema is generated and the DMMF can be passed to the user plugins. When necessary (although rare), you can front-load your plugin and let it run before the built-in ones by setting the `preprocessor` option to `true` in the plugin declaration:
+
+```zmodel
+plugin myplugin {
+    provider = './myplugin'
+    preprocessor = true
+}
+```
+
+> This option is contributed by [chunkerchunker](https://github.com/chunkerchunker).
+
 ### Publishing a Plugin
 
 If your plugin is specific to your project, you can implement it inside the project and load it with a relative path in the ZModel. No publishing is needed. We'll see how to do that in the next section.
@@ -289,12 +303,11 @@ npx zenstack generate
 You should see the following output and the `schema.md` file generated in the current directory:
 
 ```
-⌛️ ZenStack CLI v1.3.1, running plugins
-✔ Running plugin Prisma
-✔ Running plugin Model Metadata
-✔ Running plugin Access Policy
-✔ Running plugin Zod
-✔ Running plugin Markdown
+⌛️ ZenStack CLI vx.y.z, running plugins
+✔ Generating Prisma schema
+✔ Generating PrismaClient enhancer
+✔ Generating Zod schemas
+✔ Running plugin markdown
 
 👻 All plugins completed successfully!
 Don't forget to restart your dev server to let the changes take effect.
diff --git a/docs/the-complete-guide/part2/4-conclusion.md b/docs/the-complete-guide/part2/4-conclusion.md
index 0264944b..02ddbf5d 100644
--- a/docs/the-complete-guide/part2/4-conclusion.md
+++ b/docs/the-complete-guide/part2/4-conclusion.md
@@ -8,6 +8,10 @@ In this part of the guide, we've focused on the plugin system of ZenStack. We've
 
 ZModel contains a formal representation of how data is organized and its constraints. Besides serving as database schema and access control, it's an excellent source for powering other aspects of your app. Don't hesitate to write your own plugins when you feel the need.
 
-In [Part III](/docs/the-complete-guide/part3/) and [Part IV](/docs/the-complete-guide/part4/) of the guide, you'll see in action how we use plugins to turn our Todo project from a schema to a full-stack application without writing much code.
+In [Part III](../part3/) and [Part IV](../part4/) of the guide, you'll see in action how we use plugins to turn our Todo project from a schema to a full-stack application without writing much code.
 
-You can find the completed sample project up to the end of this part in the "part2" branch of [the-complete-guide-sample](https://github.com/zenstackhq/the-complete-guide-sample/tree/part2).
+The completed sample project up to the end of this part can be found in the "part2" branch of the [the-complete-guide-sample](https://github.com/zenstackhq/the-complete-guide-sample/tree/part2) repo. You can clone it and check out to that branch with the following command:
+
+```bash
+git clone --branch part2 https://github.com/zenstackhq/the-complete-guide-sample.git my-todo-app
+```
diff --git a/docs/the-complete-guide/part2/index.md b/docs/the-complete-guide/part2/index.md
index 4602d696..d5285944 100644
--- a/docs/the-complete-guide/part2/index.md
+++ b/docs/the-complete-guide/part2/index.md
@@ -13,15 +13,15 @@ A plugin can include the following parts:
 
 - A function that takes the ZModel AST as input and executes custom logic
 
-    The function usually transforms the AST to another format. The [@core/prisma](/docs/reference/plugins/prisma) built-in plugin is a good example. It strips out all ZenStack-specific syntax from the ZModel schema and generates a standard Prisma schema file that can be used with Prisma's toolchain.
+    The function usually transforms the AST to another format. The [@core/prisma](../../reference/plugins/prisma) built-in plugin is a good example. It strips out all ZenStack-specific syntax from the ZModel schema and generates a standard Prisma schema file that can be used with Prisma's toolchain.
 
 - Custom attributes and functions to extend the ZModel language
 
-    For example, the [@zenstackhq/openapi](/docs/reference/plugins/openapi) plugin provides attributes for controlling how an OpenAPI spec is generated from the ZModel schema. The attributes are commonly interpreted by the generator function.
+    For example, the [@zenstackhq/openapi](../../reference/plugins/openapi) plugin provides attributes for controlling how an OpenAPI spec is generated from the ZModel schema. The attributes are commonly interpreted by the generator function.
 
 - Runtime modules that are shipped with the app during deployment
 
-    The [@zenstackhq/tanstack-query](/docs/reference/plugins/tanstack-query) plugin is a good example. Besides generating frontend data query hooks from the schema, it also provides a runtime part that facilitates the execution of the hooks.
+    The [@zenstackhq/tanstack-query](../../reference/plugins/tanstack-query) plugin is a good example. Besides generating frontend data query hooks from the schema, it also provides a runtime part that facilitates the execution of the hooks.
 
 :::info ZenStack Plugin vs Prisma Generator
 
@@ -33,7 +33,7 @@ If you're experienced with Prisma, you've probably used some generators, like [p
 
 - Extending the schema language
 
-    Prisma's schema language is not open to extension, and the community has been using the [triple slash hack](/docs/guides/existing-prisma#prisma-generators-triple-slash-hack) to inject custom attributes. ZenStack allows plugins to introduce custom attributes and functions directly and type-check their usage at generation time.
+    Prisma's schema language is not open to extension, and the community has been using the [triple slash hack](../../guides/existing-prisma#prisma-generators-triple-slash-hack) to inject custom attributes. ZenStack allows plugins to introduce custom attributes and functions directly and type-check their usage at generation time.
 
 Plugins and generators are not mutually exclusive. ZenStack fully supports Prisma generators, and you can use them alongside plugins.
 
@@ -45,6 +45,10 @@ In this part of the guide, we'll explore the following topics:
 2. An overview of the built-in plugins
 3. How to write your own plugins
 
-As with Part I, we'll continue evolving our Todo project and see how to create a helpful custom plugin. If you're unfamiliar with the project, please first revisit the [Sample Project](/docs/the-complete-guide/#sample-project) part first. You can use the "part1" branch of [the-complete-guide-sample](https://github.com/zenstackhq/the-complete-guide-sample/tree/part1) as a starting point.
+As with Part I, we'll continue evolving our Todo project and see how to create a helpful custom plugin. If you're unfamiliar with the project, please first revisit the [Sample Project](..#sample-project) part first. You can use the "part1" branch of the [the-complete-guide-sample](https://github.com/zenstackhq/the-complete-guide-sample/tree/part1) repo as a starting point, and clone it and check out to that branch with the following command:
+
+```bash
+git clone --branch part1 https://github.com/zenstackhq/the-complete-guide-sample.git my-todo-app
+```
 
 Let's roll on.
diff --git a/docs/the-complete-guide/part3/1-api-flavor/1.1-rpc.md b/docs/the-complete-guide/part3/1-api-flavor/1.1-rpc.md
index 58716706..7bfb6147 100644
--- a/docs/the-complete-guide/part3/1-api-flavor/1.1-rpc.md
+++ b/docs/the-complete-guide/part3/1-api-flavor/1.1-rpc.md
@@ -44,6 +44,6 @@ DELETE /api/model/post/delete
 }
 ```
 
-You can find the complete API reference [here](/docs/reference/server-adapters/api-handlers/rpc).
+You can find the complete API reference [here](../../../reference/server-adapters/api-handlers/rpc).
 
-In [Part IV](/docs/the-complete-guide/part4/) of this guide, you'll see how to use the auto-generated frontend data query hooks to consume the API, so you don't need to construct the HTTP calls manually.
+In [Part IV](../../part4/) of this guide, you'll see how to use the auto-generated frontend data query hooks to consume the API, so you don't need to construct the HTTP calls manually.
diff --git a/docs/the-complete-guide/part3/1-api-flavor/1.2-rest.md b/docs/the-complete-guide/part3/1-api-flavor/1.2-rest.md
index 1e803f66..1b05bbda 100644
--- a/docs/the-complete-guide/part3/1-api-flavor/1.2-rest.md
+++ b/docs/the-complete-guide/part3/1-api-flavor/1.2-rest.md
@@ -86,4 +86,4 @@ PATCH /api/model/post/1
 DELETE /api/model/post/1
 ```
 
-You can find the complete API reference [here](/docs/reference/server-adapters/api-handlers/rest).
+You can find the complete API reference [here](../../../reference/server-adapters/api-handlers/rest).
diff --git a/docs/the-complete-guide/part3/2-server-adapters.md b/docs/the-complete-guide/part3/2-server-adapters.md
index 64bd090b..c90893e1 100644
--- a/docs/the-complete-guide/part3/2-server-adapters.md
+++ b/docs/the-complete-guide/part3/2-server-adapters.md
@@ -10,11 +10,12 @@ ZenStack is a toolkit but not a framework. It doesn't come with its own web serv
 
 Server adapters are framework-specific. ZenStack ships a `@zenstackhq/server` package that contains adapters for the most popular backend and full-stack frameworks:
 
-- [Express](/docs/reference/server-adapters/express)
-- [Fastify](/docs/reference/server-adapters/fastify)
-- [Next.js](/docs/reference/server-adapters/next)
-- [Nuxt](/docs/reference/server-adapters/nuxt)
-- [SvelteKit](/docs/reference/server-adapters/sveltekit)
+- [Express](../../reference/server-adapters/express)
+- [Fastify](../../reference/server-adapters/fastify)
+- [NestJS](../../reference/server-adapters/nestjs)
+- [Next.js](../../reference/server-adapters/next)
+- [Nuxt](../../reference/server-adapters/nuxt)
+- [SvelteKit](../../reference/server-adapters/sveltekit)
   
 Check out their respective documentation to learn more details.
 
@@ -74,7 +75,7 @@ Make a request in another terminal to verify everything is working:
 curl http://localhost:3000
 ```
 
-> { "greet": "Hello World!" }
+> &#123; "greet": "Hello World!" &#125;
 
 #### 3. Adding ZenStack server adapter (RPC flavor)
 
@@ -171,7 +172,7 @@ We've configured the server adapter to use a vanilla Prisma Client for now for q
 
 #### 4. Making access policies work
 
-To make access policies work, we need to create an enhanced Prisma Client, and to do that, we need to be able to get the current user from the request. Since we haven't implemented authentication yet, we'll use a special `x-user-id` header to simulate and pass the requesting user's ID. It's definitely not a secure implementation, but it's sufficient for demonstration. We'll hook up a real authentication system in [Part IV](/docs/the-complete-guide/part4/).
+To make access policies work, we need to create an enhanced Prisma Client, and to do that, we need to be able to get the current user from the request. Since we haven't implemented authentication yet, we'll use a special `x-user-id` header to simulate and pass the requesting user's ID. It's definitely not a secure implementation, but it's sufficient for demonstration. We'll hook up a real authentication system in [Part IV](../part4/).
 
 Replace the content of `main.ts` with the following code:
 
@@ -234,7 +235,7 @@ curl "http://localhost:3000/api/rpc/list/findFirst" -H "x-user-id: 1"
 }
 ```
 
-You can try other operations with different user identities. The service's behavior should be consistent with what we've seen in the REPL with the enhanced Prisma in [Part I](/docs/the-complete-guide/part1/access-policy/current-user#%EF%B8%8F-adding-user-based-access-control-to-our-todo-app).
+You can try other operations with different user identities. The service's behavior should be consistent with what we've seen in the REPL with the enhanced Prisma in [Part I](../part1/access-policy/current-user#add-user-based-access-control).
 
 #### 5. Trying Out The RESTful API Flavor
 
diff --git a/docs/the-complete-guide/part3/3-serialization.md b/docs/the-complete-guide/part3/3-serialization.md
index 98e62202..cb8bb811 100644
--- a/docs/the-complete-guide/part3/3-serialization.md
+++ b/docs/the-complete-guide/part3/3-serialization.md
@@ -15,7 +15,7 @@ ZenStack solves this problem by using [superjson](https://github.com/blitz-js/su
 
 The benefit of this design is that if the data is fully JSON-serializable (not involving the types mentioned above), the wire format is just plain JSON serialization without any pollution.
 
-You can find more details about serialization in the [RPC API Handler](/docs/reference/server-adapters/api-handlers/rpc#serialization) and [RESTful API Handler](/docs/reference/server-adapters/api-handlers/rest#serialization) documentation.
+You can find more details about serialization in the [RPC API Handler](../../reference/server-adapters/api-handlers/rpc#serialization) and [RESTful API Handler](../../reference/server-adapters/api-handlers/rest#serialization) documentation.
 
 ### 🛠️ Observing Serialization Behavior
 
diff --git a/docs/the-complete-guide/part3/4-customization.md b/docs/the-complete-guide/part3/4-customization.md
index f612c551..c8163ea9 100644
--- a/docs/the-complete-guide/part3/4-customization.md
+++ b/docs/the-complete-guide/part3/4-customization.md
@@ -41,7 +41,7 @@ Since server adapters are just lightweight wrappers around API handlers, you can
 The following code demonstrates how to add the 'x-requested-by' header with this approach:
 
 ```ts
-import RESTHandler from '@zenstackhq/server/api/rest';
+import { RPCApiHandler } from '@zenstackhq/server/api';
 
 app.use('/api/rpc-custom', async (req: Request, res: Response) => {
     const url = req.protocol + '://' + req.get('host') + req.originalUrl;
diff --git a/docs/the-complete-guide/part3/5-documentation.md b/docs/the-complete-guide/part3/5-documentation.md
index de88ffaf..f76bad2e 100644
--- a/docs/the-complete-guide/part3/5-documentation.md
+++ b/docs/the-complete-guide/part3/5-documentation.md
@@ -6,7 +6,7 @@ sidebar_label: 5. API Documentation
 
 An API is only complete when documented. Similar to the API itself, the documentation can also be derived from the ZModel.
 
-In [Part II](/docs/the-complete-guide/part2/) of the guide, we've learned how to use plugins. ZenStack ships the [@zenstackhq/openapi](/docs/reference/plugins/openapi) plugin to generate [OpenAPI](https://www.openapis.org/) specs from the ZModel schema. Like the API handlers, the openapi plugin also supports two flavors: "rpc" and "rest".
+In [Part II](../part2/) of the guide, we've learned how to use plugins. ZenStack ships the [@zenstackhq/openapi](../../reference/plugins/openapi) plugin to generate [OpenAPI](https://www.openapis.org/) specs from the ZModel schema. Like the API handlers, the openapi plugin also supports two flavors: "rpc" and "rest".
 
 Once the documentation is generated, it's easy to render it using tools like [Swagger UI](https://swagger.io/tools/swagger-ui/) and [Redocly](https://redocly.com/).
 
diff --git a/docs/the-complete-guide/part3/6-conclusion.md b/docs/the-complete-guide/part3/6-conclusion.md
index 40859573..824915bf 100644
--- a/docs/the-complete-guide/part3/6-conclusion.md
+++ b/docs/the-complete-guide/part3/6-conclusion.md
@@ -8,4 +8,8 @@ In this part of the guide, we've focused on ZenStack's powers for deriving CRUD
 
 In the next part, we'll see how effortless it is to consume the APIs from a frontend application and eventually turn our Todo project into a full-stack application.
 
-You can find the completed sample project up to the end of this part in the "part3" branch of [the-complete-guide-sample](https://github.com/zenstackhq/the-complete-guide-sample/tree/part3).
+The completed sample project up to the end of this part can be found in the "part3" branch of the [the-complete-guide-sample](https://github.com/zenstackhq/the-complete-guide-sample/tree/part3) repo. You can clone it and check out to that branch with the following command:
+
+```bash
+git clone --branch part3 https://github.com/zenstackhq/the-complete-guide-sample.git my-todo-app
+```
diff --git a/docs/the-complete-guide/part3/index.md b/docs/the-complete-guide/part3/index.md
index 4390a3e2..5748675a 100644
--- a/docs/the-complete-guide/part3/index.md
+++ b/docs/the-complete-guide/part3/index.md
@@ -11,6 +11,10 @@ One interesting side effect of this extension is that we can automatically deriv
 
 In this part of the guide, we'll learn how such automatic APIs work - the general architecture, the different flavors of APIs, and how they integrate into the framework of your choice.
 
-We'll also continue evolving our Todo project into a fully functional backend service. If you're unfamiliar with the project, please first revisit the [Sample Project](/docs/the-complete-guide/#sample-project) part. You can use the "part2" branch of [the-complete-guide-sample](https://github.com/zenstackhq/the-complete-guide-sample/tree/part2) as a starting point.
+We'll also continue evolving our Todo project into a fully functional backend service. If you're unfamiliar with the project, please first revisit the [Sample Project](..#sample-project) part. You can use the "part2" branch of [the-complete-guide-sample](https://github.com/zenstackhq/the-complete-guide-sample/tree/part2) repo as a starting point, and clone it and check out to that branch with the following command:
+
+```bash
+git clone --branch part2 https://github.com/zenstackhq/the-complete-guide-sample.git my-todo-app
+```
 
 Let's get started.
diff --git a/docs/the-complete-guide/part4/1-prepare-project.md b/docs/the-complete-guide/part4/1-prepare-project.md
index b99bb5d8..b9a931bf 100644
--- a/docs/the-complete-guide/part4/1-prepare-project.md
+++ b/docs/the-complete-guide/part4/1-prepare-project.md
@@ -73,7 +73,7 @@ npx zenstack generate && npx prisma db push
 
 :::tip
 
-If you ran into any trouble creating the project, you can also use the "part4-start" branch of [https://github.com/zenstackhq/the-complete-guide-sample](https://github.com/zenstackhq/the-complete-guide-sample) as the starting point and continue from there.
+If you ran into any trouble creating the project, you can also use the "part4-start" branch of [https://github.com/zenstackhq/the-complete-guide-sample](https://github.com/zenstackhq/the-complete-guide-sample/tree/part4-start) as the starting point and continue from there.
 
 :::
 
@@ -217,7 +217,8 @@ import { db } from '~/server/db';
 
 async function getPrisma() {
   const session = await getServerSession(authOptions);
-  return enhance(db, { user: session?.user });
+  const user = session ? { id: session.user.id } : undefined;
+  return enhance(db, { user });
 }
 
 const handler = NextRequestHandler({ getPrisma, useAppDir: true });
@@ -233,22 +234,6 @@ The crucial part is that we use an enhanced PrismaClient with the server adapter
 
 In the next chapter, we'll learn how to use a plugin to generate frontend data query hooks that help us consume it.
 
-Finally, make a change to the `next.config.js` file to exclude the `@zenstackhq/runtime` package from the server component bundler:
-
-```js title="next.config.js"
-const config = {
-  experimental: {
-    serverComponentsExternalPackages: ['@zenstackhq/runtime']
-  }
-};
-```
-
-:::info Why is this needed?
-
-Next.js's server component bundler automatically bundles dependencies, but it has some restrictions on the set of Node.js features a package can use. The `@zenstackhq/runtime` package makes [unsupported](https://nextjs.org/docs/app/api-reference/next-config-js/serverComponentsExternalPackages) `require()` calls. We'll try to make it compatible in a future release. 
-
-:::
-
 ### 5. Compile the Project
 
 Compile the project and see if everything is working correctly:
diff --git a/docs/the-complete-guide/part4/2-generate-hooks.md b/docs/the-complete-guide/part4/2-generate-hooks.md
index 09a6cc29..cefec1fb 100644
--- a/docs/the-complete-guide/part4/2-generate-hooks.md
+++ b/docs/the-complete-guide/part4/2-generate-hooks.md
@@ -64,12 +64,11 @@ Now add the plugin to the ZModel schema:
 plugin hooks {
   provider = '@zenstackhq/tanstack-query'
   target = 'react'
-  version = 'v5'
   output = 'src/lib/hooks'
 }
 ```
 
-TanStack Query is a multi-framework library and supports React, Vue, and Svelte. The "target" field specifies the frontend framework to generate hooks for. The "version" field specifies the version of TanStack Query to target. Here, we're using the latest "v5" version.
+TanStack Query is a multi-framework library and supports React, Vue, and Svelte. The "target" field specifies the frontend framework to generate hooks for.
 
 Then, rerun generation and observe that the hooks code is generated in the `src/lib/hooks` directory.
 
@@ -101,7 +100,6 @@ The generated hooks allow you to query and mutate data without worrying about th
       mutate({
         data: { 
           title: 'My List',
-          owner: { connect: { id: userId } },
           todos: {
             create: [
               { name: 'Todo 1' },
diff --git a/docs/the-complete-guide/part4/4-invalidation.md b/docs/the-complete-guide/part4/4-invalidation.md
index 8cbdf853..956d6bfb 100644
--- a/docs/the-complete-guide/part4/4-invalidation.md
+++ b/docs/the-complete-guide/part4/4-invalidation.md
@@ -141,10 +141,10 @@ You've probably already noticed this: after creating a space, you don't need to
 
 :::tip Opt-out of automatic invalidation
 
-When calling a mutation hook, you can pass an extra `invalidateQueries` argument as `false` to opt out of automatic invalidation. For example:
+When calling a mutation hook, you can pass an `invalidateQueries` option as `false` to opt out of automatic invalidation. For example:
 
 ```ts
-const { mutate: createSpace } = useCreateSpace(undefined, false);
+const { mutate: createSpace } = useCreateSpace({ invalidateQueries: false });
 ```
 
 With this change, the created space won't appear in the list until you refresh the page.
@@ -164,61 +164,59 @@ import { useParams } from 'next/navigation';
 import { useCreateList, useFindManyList, useFindUniqueSpace } from '~/lib/hooks';
 
 export default function SpaceHome() {
-    const { slug } = useParams<{ slug: string }>();
-    console.log('slug', slug);
+  const { slug } = useParams<{ slug: string }>();
 
-    const { data: session } = useSession();
+  const { data: session } = useSession();
+
+  const { data: space } = useFindUniqueSpace({ where: { slug } }, { enabled:!!session?.user });
+
+  const { data: lists } = useFindManyList(
+    {
+      where: { space: { slug } },
+      include: { owner: true },
+      orderBy: { updatedAt: 'desc' },
+    },
+    { enabled: !!session?.user },
+  );
 
-    const { data: space } = useFindUniqueSpace({ where: { slug } }, { enabled: !!session?.user });
+  const { mutate: createList } = useCreateList();
 
-    const { data: lists } = useFindManyList(
-        {
-            where: { space: { slug } },
-            include: { owner: true },
-            orderBy: { updatedAt: 'desc' },
+  function onCreateList() {
+    const title = prompt('Enter a title for your list');
+    if (title) {
+      createList({
+        data: {
+          title,
+          space: { connect: { id: space?.id } },
         },
-        { enabled: !!session?.user },
-    );
-
-    const { mutate: createList } = useCreateList();
-
-    function onCreateList() {
-        const title = prompt('Enter a title for your list');
-        if (title) {
-            createList({
-                data: {
-                    title,
-                    space: { connect: { id: space?.id } },
-                    owner: { connect: { id: session?.user.id } },
-                },
-            });
-        }
+      });
     }
+  }
 
-    if (!session?.user || !space || !lists) return null;
-
-    return (
-        <div className="container mx-auto mt-16">
-            <h1 className="text-center text-3xl">
-                Welcome to Space <span className="italic">{space.name}</span>
-            </h1>
-            <div className="p-8">
-                <button className="btn btn-primary btn-wide" onClick={onCreateList}>
-                    Create a list
-                </button>
-
-                <ul className="mt-8 flex flex-wrap gap-6">
-                    {lists?.map((list) => (
-                        <Link href={`/spaces/${slug}/${list.id}`} key={list.id}>
-                            <li className="flex h-32 w-72 items-center justify-center rounded-lg border text-2xl">
-                                {list.title}
-                            </li>
-                        </Link>
-                    ))}
-                </ul>
-            </div>
-        </div>
-    );
+  if (!session?.user || !space || !lists) return null;
+
+  return (
+    <div className="container mx-auto mt-16">
+      <h1 className="text-center text-3xl">
+        Welcome to Space <span className="italic">{space.name}</span>
+      </h1>
+      <div className="p-8">
+        <button className="btn btn-primary btn-wide" onClick={onCreateList}>
+          Create a list
+        </button>
+
+        <ul className="mt-8 flex flex-wrap gap-6">
+          {lists?.map((list) => (
+            <Link href={`/spaces/${slug}/${list.id}`} key={list.id}>
+              <li className="flex h-32 w-72 items-center justify-center rounded-lg border text-2xl">
+                {list.title}
+              </li>
+            </Link>
+          ))}
+        </ul>
+      </div>
+    </div>
+  );
 }
 ```
 
diff --git a/docs/the-complete-guide/part4/5-optimistic-update.md b/docs/the-complete-guide/part4/5-optimistic-update.md
index b92d1c63..a7d083e8 100644
--- a/docs/the-complete-guide/part4/5-optimistic-update.md
+++ b/docs/the-complete-guide/part4/5-optimistic-update.md
@@ -8,7 +8,7 @@ Optimistic update is a technique that allows us to update the UI immediately aft
 
 Libraries like TanStack Query and SWR provides the framework for developers to implement optimistic update. The approach is essentially to update the query cache immediately after the user action and revert or invalidate the cache after the server response comes back (either success or error). However, similar to the query invalidation problem we discussed in the previous chapter, the challenge is identifying which queries need to be updated and how.
 
-ZenStack supports automatic optimistic update for the hooks it generates. It uses a set of rules to update queries and aims to cover the most common use cases. You can find more on how it works [here](/docs/reference/plugins/tanstack-query#details-of-the-optimistic-behavior).
+ZenStack supports automatic optimistic update for the hooks it generates. It uses a set of rules to update queries and aims to cover the most common use cases. You can find more on how it works [here](../../reference/plugins/tanstack-query#details-of-the-optimistic-behavior).
 
 :::info Automatic optimistic update is an approximation
 
@@ -178,18 +178,14 @@ It's obviously very sluggish. It's not something a user wants to pay for.
 Let's make some small changes to enable automatic optimistic update. In `src/pages/spaces/[slug]/[listId]/index.tsx`, change the `useCreateTodo` call to the following:
 
 ```tsx
-// the three arguments are: 
-//     - mutation options
-//     - auto invalidate (default true)
-//     - auto optimistic-update (default false)
-const { mutate: create } = useCreateTodo(undefined, true, true);
+const { mutate: create } = useCreateTodo({ optimisticUpdate: true });
 ```
 
 And likewise, change the `useUpdateTodo` and `useDeleteTodo` calls in `Todo.tsx` to the following:
 
 ```tsx
-const update = useUpdateTodo(undefined, true, true); // optimistic update
-const del = useDeleteTodo(undefined, true, true); // optimistic update
+const update = useUpdateTodo({ optimisticUpdate: true });
+const del = useDeleteTodo({ optimisticUpdate: true });
 ```
 
 Now, if we repeat the same test with network throttling, we'll see a much better experience:
@@ -198,13 +194,12 @@ Now, if we repeat the same test with network throttling, we'll see a much better
 
 :::tip Opt-out of automatic optimistic update
 
-When you enable automatic optimistic update for a mutation, it applies to all queries that may be impacted. If this is not desirable, you can pass an extra `optimisticUpdate` argument as `false` to opt out of automatic optimistic update. For example:
+When you enable automatic optimistic update for a mutation, it applies to all queries that may be impacted. If this is not desirable, you can pass the `optimisticUpdate` option as `false` to opt out of automatic optimistic update per query. For example:
 
 ```ts
 const { data: todos } = useFindManyTodo(
     { where: { listId } },
-    { enabled: !!session?.user },
-    false, // <- opt-out automatic optimistic update
+    { enabled: !!session?.user, optimisticUpdate: false },
 );
 ```
 
diff --git a/docs/upgrade.md b/docs/upgrade.md
new file mode 100644
index 00000000..dbc05616
--- /dev/null
+++ b/docs/upgrade.md
@@ -0,0 +1,317 @@
+---
+description: How to upgrade to ZenStack v2
+
+slug: /upgrade-v2
+sidebar_label: Upgrading to V2
+sidebar_position: 9
+---
+
+import useBaseUrl from '@docusaurus/useBaseUrl';
+import ThemedImage from '@theme/ThemedImage';
+
+# Upgrading to V2
+
+We're excited to announce the release of ZenStack V2 🎉!
+
+While V1 focused on implementing an access control layer around Prisma, V2 is more ambitious and desires to solve some deeper problems in modeling a database-centric application while continuing to improve the overall developer experience.
+
+This document outlines the first batch of new features, together with breaking changes and upgrade instructions. More goodies will land in the future V2 series releases. Enjoy, and please [share your feedback](https://discord.gg/Ykhr738dUe)!
+
+## What's New
+
+### 1. Polymorphic Relations
+
+Polymorphic relations allows you to model relations over an "abstract interface". This feature is inspired by the following Prisma issues:
+
+- [Support for a Union type](https://github.com/prisma/prisma/issues/2505)
+- [Support for Polymorphic Associations](https://github.com/prisma/prisma/issues/1644)
+
+See the [Polymorphic Relations](./guides/polymorphism) recipe for more information.
+
+### 2. Using `auth()` in `@default()`
+
+You can now use the `auth()` function inside the `@default()` attribute. A very common use case is to automatically assign foreign key field when creating entities with relation with the current user. In ZenStack V1, you needed to explicitly assign foreign key value even though the enhanced `PrismaClient` already implies a current user:
+
+```zmodel title="schema.zmodel"
+model User {
+  id Int @default(autoincrement())
+}
+
+model Post {
+  id Int @default(autoincrement())
+  title String
+  owner User @relation(fields: [ownerId], references: [id])
+  ownerId Int
+}
+```
+
+```ts
+const db = enhance(prisma, { user });
+await db.post.create({
+  data: {
+   owner: { connect: { id: user.id } },
+   title: 'Post1'
+  }
+})
+```
+
+With this feature, you can update the schema to:
+
+```zmodel title="schema.zmodel"
+model User {
+  id Int @default(autoincrement())
+}
+
+model Post {
+  id Int @default(autoincrement())
+  title String
+  owner User @relation(fields: [ownerId], references: [id])
+  ownerId Int @default(auth().id) // <- assign ownerId automatically
+}
+```
+
+and when creating a `Post`, you don't need to explicitly connect `owner` anymore.
+
+```ts
+const db = enhance(prisma, { user });
+await db.post.create({ data: { title: 'Post1' } });
+```
+
+### 3. Fine-grained Optimistic Updates
+
+Previously the ZenStack-generated data query hooks (TanStack Query and SWR) support automatic optimistic updates. When a mutation executes, the hooks analyzes what queries are potentially affected and try to compute an optimistically updated piece of data, and use it to update the query cache. This feature is very useful in many cases, but it can't cover all cases. For example, if you create an entity and connect it to a related entity at the same time, the automatic optimistic logic doesn't know how to compute the relation (if it's used in a related query).
+
+In V2, we introduce a `optimisticUpdateProvider` callback for the caller to decide how to compute the optimistic data for a mutation, for every query cache entry. Here's how to use it:
+
+```ts
+useCreatePost({
+  optimisticUpdateProvider: ({ queryModel, queryOperation, queryArgs, currentData, mutationArgs }) => {
+    return { kind: 'Update', data: ... /* computed result */ };
+  }
+});
+```
+
+See more information [here]:
+
+The callback is invoked for each query cache entry. You can use the return value to control if to use the optimistic data you computed, skip the update, or leave it to the automatic logic.
+
+### 4. Prisma-Like Schema Formatting
+
+We've heard your feedback: the way how Prisma formats the schema code makes it more readable. Now ZenStack's IDE extensions and the CLI `format` command resemble Prisma's behavior and format fields into a tabular form.
+
+<ThemedImage
+    alt="ZModel Formatting"
+    sources={{
+        light: useBaseUrl('/img/zmodel-format-light.png'),
+        dark: useBaseUrl('/img/zmodel-format-dark.png'),
+    }}
+/>
+
+You can switch back to the old behavior in the extension settings (VSCode only).
+
+### 5. Edge Runtime Support
+
+We've updated the `@zenstackhq/runtime` package to be compatible with Vercel Edge Runtime and Cloudflare Workers. See [this documentation](./guides/edge) for more details.
+
+### 6. Permission Checker API (Preview)
+
+ZenStack's access policies prevent unauthorized users to query or mutate data. However, there are cases where you simply want to check if an operation is permitted without actually executing it. For example, you might want to show or hide a button based on the user's permission.
+
+The new permission checker API allows to check a user's permission without querying the database.
+
+```ts
+const db = enhance(prisma, { user: getCurrentUser() });
+
+// check if the current user can read published posts
+await canRead = await db.post.check({
+  operation: 'read',
+  where: { published: true }
+});
+```
+
+Please check [this guide](./guides/check-permission) for more details.
+
+## Upgrading
+
+### NPM Packages
+
+To upgrade, update your project's dependencies of `zenstack` and `@zenstackhq/*` packages to the `@latest` tag.
+
+```bash
+npm i -D zenstack@latest
+npm i @zenstackhq/runtime@latest
+...
+```
+
+### IDE Extensions
+
+Please upgrade VSCode extension and JetBrains plugin to the latest version.
+
+## Breaking Changes
+
+The following sections list breaking changes introduced in ZenStack V2 and guide for upgrading your project.
+
+### 1. ZModel Schema
+
+- #### No more access to declarations from indirectly imported schemas
+
+  V1 had an unintended behavior that you can access a declaration without importing the ZModel file where it's declared, as long as the schema is indirectly imported.
+
+  The behavior is changed in V2. If you use a declaration from another ZModel, you'll need to explicitly import it.
+
+  Please note that this implies if you use `auth()` function in access policies, you'll need to import the ZModel where the `User` model (or the model marked with `@@auth`) so that `auth()` can be resolved. If you feel it causes extra imports in too many schema files, please leave your feedback in [this GitHub issue](https://github.com/zenstackhq/zenstack/issues/1388).
+
+### 2. Runtime
+
+- #### Unified `enhance` API
+
+  In V1, there were several `withXXX` APIs (like `withPolicy`, `withOmit`, etc.) that help you create enhanced PrismaClient instances with specific enhancements. These APIs are now deprecated and unified to the single `enhance` API. You can use the `kinds` option to control what enhancements to apply:
+
+  ```ts
+  const db = enhance(prisma, { user }, { kinds: ['policy', 'omit'] })
+  ```
+
+  By default, all enhancements are enabled.
+
+- #### Changes to the `enhance` API
+
+  One of the main changes in V2 is that the `enhance` API is now generated, by default into the `node_modules/.zenstack` package, together with other supporting modules. The `@zenstackhq/runtime/enhance` module is simply a reexport of `.zenstack/enhance`. This change allows us to customize the API of the enhanced `PrismaClient` based on the enhancements enabled.
+
+  The change also simplifies the way how the `enhance` API is used when you specify a custom output location (usually for checking in the generated files with the source tree). For example, if you use the "--output" CLI switch to output to "./.zenstack" folder:
+
+  ```bash
+  npx zenstack generate --output ./.zenstack
+  ```
+
+  You can import the `enhance` API directly from the output location and use it without any other changes:
+
+  ```ts
+  import { enhance } from './.zenstack/enhance';
+
+  const db = enhance(...);
+  ```
+
+  Several options of the `enhance` API are also removed because they are no more needed:
+
+  - policy
+  - modelMeta
+  - zodSchemas
+  - loadPath
+    
+  These options were for guiding ZenStack to load the generated modules from a custom location. They are not needed anymore because the generated `enhance` API can always load them from relative paths.
+
+  Another major benefit of generating `enhance` is the `user` context object is now strongly typed. The CLI statically analyzes the ZModel schema to identify the fields accessed through `auth()` (including multi-level accesses into relation fields), and use that information to type the `user` object. This helps you to identify missing or incorrectly typed fields and avoid unexpected runtime behavior.
+
+- #### Strict typing for the user context
+
+  The `enhance` API now analyzes the fields accessed through `auth()` in ZModel access policies and use that to derive a strong typed `user` context. For example, if your ZModel looks like:
+
+  ```zmodel
+  model Post {
+    id Int @id
+    ...
+    @@allow('all', auth().role == ADMIN)
+  }
+  ```
+
+  You'll get an compile-time error if you pass an `user` object without the `role` field:
+
+  ```ts
+  const db = enhance(prisma, { user: { id: userId }}); // <- `role` field is required
+  ```
+
+- #### Prisma version below 5.0.0 is not supported anymore
+
+  Supporting both Prisma V4 and V5 caused quite some complexities. We decided to require Prisma 5.0.0 and above for ZenStack V2. This also makes it possible to make ZenStack's runtime compatible with Edge environments (TBD).
+
+### 3. CLI
+
+- #### Removed support of CLI config file
+
+  The "--config" switch and the "zenstack.config.json" file are removed. They weren't doing anything useful and were only kept in V1 for backward compatibility reasons.
+
+  We may introduce a new config file format in the future.
+
+### 4. Server Adapter
+
+- #### HTTP status code `422` is used to represent data validation errors
+
+  In V1, when a [data validation](../docs/reference/zmodel-language.md#data-validation) error happens (due to violation of rules represented by `@email`, `@length`, `@@validate` etc.), the server adapters used `403` to represent such error. This is changed in V2 to use `422` to align with the [HTTP status code definition](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422).
+
+- #### The deprecated `useSuperJSON` initialization options is removed
+
+  The server adapters always use SuperJSON for serialization and deserialization.
+
+### 5. Plugins
+
+:::warning
+All plugins now automatically clean up the output directory before generating new files. Please make sure not to mix manually created files with the generated ones to to avoid data loss.
+:::
+
+#### 5.1 Zod Plugin
+
+- ##### Changes to the optionality of `[Model]Schema` schema
+
+  In V1, the generated `[Model]Schema` schema has all fields marked optional. This is changed in V2 to respect the optionality of fields as they are declared in ZModel.
+
+#### 5.2. SWR Plugin
+
+- ##### Legacy mutation functions are removed
+
+  In V1, the SWR plugin used to generate a set of legacy mutation functions via the `useMutate[Model]` hook. These functions (together with the hook) are removed in V2. You should use the individual mutation hooks instead.
+
+  Old code:
+
+  ```ts
+  import { useMutatePost } from '@lib/hooks';
+
+  const { createPost } = useMutatePost();
+  await createPost({ data: {...} });
+  ```
+
+  New code:
+
+  ```ts
+  import { useCreatePost } from '@lib/hooks';
+
+  const { trigger: createPost, isMutating } = useCreatePost();
+  await createPost({ data: {...} })
+  ```
+
+- ##### The `initialData` query option is removed
+
+  Use the `fallbackData` option instead.
+
+#### 5.3. TanStack Query Plugin
+
+- ##### Default target version is now "v5".
+
+  In V1 the default target version was TanStack Query V4.
+
+- ##### Generated hooks got simplified parameters
+
+  The query and mutation hooks generated in V1 had a few parameters like `invalidateQueries` and `optimisticUpdate`. These parameters are merged into the `options` parameter. The `options` parameter is used for configuring both tanstack-query and the additional behavior of ZenStack.
+
+  Old code:
+
+  ```ts
+  useFindManyPost(
+    { where: { ... } }, /* query args */
+    undefined, /* tanstack-query options */
+    false /* opt-out optimistic update*/
+  );
+
+  useCreatePost(
+    undefined /* tanstack-query options */,
+    false, /* whether to automatically invalidate related queries */
+    true /* whether to optimistically update related queries */
+  )
+  ```
+
+  New code:
+
+  ```ts
+  useFindManyPost({ where: { ... } }, { optimisticUpdate: false });
+
+  useCreatePost({ invalidateQueries: false, optimisticUpdate: true });
+  ```
diff --git a/docs/vscode-prerelease.png b/docs/vscode-prerelease.png
new file mode 100644
index 00000000..602667e1
Binary files /dev/null and b/docs/vscode-prerelease.png differ
diff --git a/docs/welcome.md b/docs/welcome.md
index 1f0cf318..5078e9fa 100644
--- a/docs/welcome.md
+++ b/docs/welcome.md
@@ -9,18 +9,19 @@ sidebar_position: 1
 
 Hey 👋, I'm glad you found us here.
 
-If you are using TypeScript to build APIs or web apps above a SQL database, you are in the right place. ZenStack is an open-source toolkit built above [Prisma](https://prisma.io) - the most popular ORM for Node.js. ZenStack pushes Prisma's power to a new level and boosts the development efficiency of every layer of your stack - from access control, to API development, and all the way up to the frontend.
+If you are using TypeScript to build APIs or web apps above a SQL database, you are in the right place. ZenStack is an open-source toolkit built above [Prisma](https://prisma.io) - the most popular ORM for TypeScript. ZenStack pushes Prisma's power to a new level and boosts the development efficiency of every layer of your stack - from access control, to API development, and all the way up to the frontend.
 
 Some of the most common use cases of ZenStack include:
 - Multi-tenant SaaS
 - Applications with complex access control requirements
 - CRUD-intensive API or web apps
+- AI agents handle CRUD database operations
 
 ZenStack is not opinionated about your choice of framework. It can be used with any of them.
 
 ## Features
 
-- ORM with built-in access control and data validation
+- ORM with built-in access control, data validation, polymorphic relations, and more
 
 - Auto-generated CRUD API - RESTful & tRPC
 
@@ -34,17 +35,17 @@ ZenStack is not opinionated about your choice of framework. It can be used with
 
 ## Find the Learning Path That Fits You
 
-ZenStack is a comprehensive toolkit that touches a wide range of topics. For most developers, we'd highly recommend you start with the [The Complete Guide](/docs/the-complete-guide) to build a solid understanding of each layer of ZenStack's functionalities. The guide is designed to lay out concepts gradually and lead you through the learning journey from using ZenStack just as a better ORM, to leveraging its total power in full-stack development. The guide is pretty long, but trust me you'll enjoy the reading 😉.
+ZenStack is a comprehensive toolkit that touches a wide range of topics. For most developers, we'd highly recommend you start with the [The Complete Guide](./the-complete-guide) to build a solid understanding of each layer of ZenStack's functionalities. The guide is designed to lay out concepts gradually and lead you through the learning journey from using ZenStack just as a better ORM, to leveraging its total power in full-stack development. The guide is pretty long, but trust me you'll enjoy the reading 😉.
 
-If you're the kind of inpatient developer who wants to get your hand dirty ASAP, you can also jump straight to the [Quick Start](/docs/category/quick-start) guide. It shows how to quickly set up a ZenStack project targeting the framework of your choice.
+If you're the kind of inpatient developer who wants to get your hand dirty ASAP, you can also jump straight to the [Quick Start](./category/quick-start) guide. It shows how to quickly set up a ZenStack project targeting the framework of your choice.
 
 <!-- It explains how to use the CLI to quickly scaffold a ZenStack project targeting the framework of your choice. -->
 
 ## Other Resources
 
-- [Recipes](/docs/category/recipes/) - Mini guides for completing specific tasks using ZenStack
-- [Sample Catalog](/docs/sample-catalog) - A catalog of sample projects using different frameworks
-- [Reference](/docs/category/reference/) - Detailed documentation for the DSL, CLI, APIs, and plugins
-- [FAQ](/docs/faq) - Frequently asked questions
+- [Recipes](./category/recipes/) - Mini guides for completing specific tasks using ZenStack
+- [Samples](./samples) - A catalog of sample projects using different frameworks
+- [Reference](./category/reference/) - Detailed documentation for the DSL, CLI, APIs, and plugins
+- [FAQ](./faq) - Frequently asked questions
   
 Ready to roll? Let's get started 🚀
\ No newline at end of file
diff --git a/docusaurus.config.js b/docusaurus.config.js
index f6c8bddf..0dd2e501 100644
--- a/docusaurus.config.js
+++ b/docusaurus.config.js
@@ -1,8 +1,7 @@
 // @ts-check
 // Note: type annotations allow type checking and IDEs autocompletion
 
-const lightCodeTheme = require('prism-react-renderer/themes/github');
-const darkCodeTheme = require('prism-react-renderer/themes/dracula');
+import { themes as prismThemes } from 'prism-react-renderer';
 
 /** @type {import('@docusaurus/types').Config} */
 const config = {
@@ -35,12 +34,19 @@ const config = {
                     sidebarPath: require.resolve('./sidebars.js'),
                     path: 'docs',
                     editUrl: 'https://github.com/zenstackhq/zenstack-docs/edit/main',
+                    lastVersion: 'current',
+                    versions: {
+                        current: {
+                            label: '2.x',
+                            banner: 'none',
+                        },
+                        '1.x': {
+                            label: '1.x',
+                            banner: 'none',
+                        },
+                    },
                 },
-                blog: {
-                    showReadingTime: true,
-                    blogSidebarTitle: 'Recent posts',
-                    blogSidebarCount: 10,
-                },
+                blog: false,
                 theme: {
                     customCss: require.resolve('./src/css/custom.css'),
                 },
@@ -65,14 +71,16 @@ const config = {
             metadata: [
                 {
                     name: 'keywords',
-                    content: 'prisma,orm,web development,webdev,full-stack',
+                    content:
+                        'authorization,auth,prisma,orm,web development,webdev,full-stack,api,type-safe,acl,rls,saas,ai',
                 },
                 {
                     name: 'description',
                     content:
-                        'ZenStack adds the missing access control layer to the awesome Prisma ORM and unleashes its full potential for full-stack development.',
+                        'A schema-first TypeScript toolkit that enhances Prisma ORM with flexible Authorization and auto-generated, type-safe APIs/hooks, simplifying full-stack development and AI friendly',
                 },
             ],
+
             navbar: {
                 title: 'ZenStack',
                 logo: {
@@ -87,22 +95,26 @@ const config = {
                         label: 'Get Started',
                     },
                     {
-                        to: 'docs/category/reference',
+                        to: 'docs/the-complete-guide',
                         position: 'left',
-                        label: 'Reference',
+                        label: 'Handbook',
                     },
                     { to: '/blog', label: 'Blog', position: 'left' },
                     {
-                        href: 'https://go.zenstack.dev/chat',
+                        href: 'https://discord.gg/Ykhr738dUe',
                         label: 'Discord',
                         position: 'right',
                     },
+                    {
+                        type: 'docsVersionDropdown',
+                        position: 'right',
+                    },
                     {
                         type: 'html',
                         position: 'left',
-                        value: `<div id="github-button">
+                        value: `<div id="github-button" class="group relative">
                         <a href='https://github.com/zenstackhq/zenstack' class="inline-flex items-center justify-center space-x-0 rounded-md text-sm  ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 h-10 px-4 py-2 group bg-gray-900 hover:bg-gray-950 transition-all duration-200 ease-in-out hover:ring-2 hover:ring-offset-2 hover:ring-gray-900 hover:no-underline">
-                            <svg
+                        <svg
                                 xmlns="http://www.w3.org/2000/svg"
                                 width="24"
                                 height="24"
@@ -118,7 +130,7 @@ const config = {
                                 <path d="M9 18c-4.51 2-5-2-7-2"></path>
                             </svg>
                             <span id="github-text" class="text-white pr-2">Star on Github</span>
-                            <span class="flex items-center ml-2 group-hover:text-yellow-500 transition-colors duration-200 ease-in-out">
+                            <span class="flex items-center">
                                 <svg
                                     xmlns="http://www.w3.org/2000/svg"
                                     width="24"
@@ -129,7 +141,7 @@ const config = {
                                     stroke-width="2"
                                     stroke-linecap="round"
                                     stroke-linejoin="round"
-                                    class=" text-yellow-500"
+                                    class=" text-yellow-500 group-hover:fill-yellow-500 transition-all duration-1000 ease-in-out"
                                 >
                                     <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
                                 </svg>
@@ -160,7 +172,7 @@ const config = {
                         items: [
                             {
                                 label: 'Discord',
-                                href: 'https://go.zenstack.dev/chat',
+                                href: 'https://discord.gg/Ykhr738dUe',
                             },
                             {
                                 label: 'Twitter',
@@ -184,11 +196,15 @@ const config = {
                 ],
                 copyright: `Copyright © ${new Date().getFullYear()} ZenStack, Inc.`,
             },
+
             image: '/img/social-cover.png',
+
             prism: {
-                theme: lightCodeTheme,
-                darkTheme: darkCodeTheme,
+                theme: prismThemes.github,
+                darkTheme: prismThemes.dracula,
+                additionalLanguages: ['json'],
             },
+
             zoom: {
                 config: {
                     // options you can specify via https://github.com/francoischalifour/medium-zoom#usage
@@ -198,6 +214,7 @@ const config = {
                     },
                 },
             },
+
             algolia: {
                 // The application ID provided by Algolia
                 appId: 'VS0AELD3AC',
@@ -234,13 +251,14 @@ const config = {
                 },
             };
         },
-    ],
-
-    scripts: [
-        {
-            src: '/_vercel/insights/script.js',
-            defer: true,
-        },
+        [
+            './src/plugins/blog-plugin.js',
+            {
+                showReadingTime: true,
+                blogSidebarTitle: 'Recent posts',
+                blogSidebarCount: 10,
+            },
+        ],
     ],
 
     markdown: {
diff --git a/package.json b/package.json
index 53e0cff4..fa70489e 100644
--- a/package.json
+++ b/package.json
@@ -15,31 +15,33 @@
         "typecheck": "tsc"
     },
     "dependencies": {
-        "@algolia/client-search": "^4.14.3",
-        "@docusaurus/core": "2.4.3",
-        "@docusaurus/preset-classic": "2.4.3",
-        "@docusaurus/theme-mermaid": "2.4.3",
-        "@mdx-js/react": "^1.6.22",
+        "@algolia/client-search": "^4.22.1",
+        "@docusaurus/core": "3.4.0",
+        "@docusaurus/preset-classic": "3.4.0",
+        "@docusaurus/theme-common": "3.4.0",
+        "@docusaurus/theme-mermaid": "3.4.0",
+        "@giscus/react": "^2.4.0",
+        "@mdx-js/react": "^3.0.1",
         "autoprefixer": "^10.4.13",
         "clsx": "^1.2.1",
-        "docusaurus-plugin-image-zoom": "^0.1.1",
         "postcss": "^8.4.21",
-        "prism-react-renderer": "^1.3.5",
+        "prism-react-renderer": "^2.3.1",
         "prism-svelte": "^0.5.0",
         "prismjs": "^1.29.0",
-        "react": "^17.0.2",
-        "react-dom": "^17.0.2",
-        "react-icons": "^4.7.1",
-        "react-simple-typewriter": "^5.0.1",
-        "tailwindcss": "^3.2.4"
+        "react": "^18.2.0",
+        "react-dom": "^18.2.0",
+        "react-icons": "^5.0.1",
+        "react-share": "^5.1.0",
+        "tailwindcss": "^3.4.11"
     },
     "devDependencies": {
-        "@docusaurus/module-type-aliases": "2.4.3",
-        "@docusaurus/types": "^2.4.3",
+        "@docusaurus/module-type-aliases": "3.4.0",
+        "@docusaurus/types": "^3.4.0",
         "@tailwindcss/typography": "^0.5.9",
-        "@tsconfig/docusaurus": "^1.0.7",
+        "@tsconfig/docusaurus": "^2.0.3",
+        "@types/node": "^20.11.17",
         "@types/react": "^18.0.26",
-        "typescript": "^4.7.4"
+        "typescript": "^5.3.3"
     },
     "browserslist": {
         "production": [
@@ -54,6 +56,7 @@
         ]
     },
     "engines": {
-        "node": ">=16.14"
-    }
+        "node": ">=18.0"
+    },
+    "packageManager": "pnpm@9.4.0+sha256.b6fd0bfda555e7e584ad7e56b30c68b01d5a04f9ee93989f4b93ca8473c49c74"
 }
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index f7fe7603..78043273 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1,1594 +1,830 @@
-lockfileVersion: '6.0'
+lockfileVersion: '9.0'
 
 settings:
   autoInstallPeers: true
   excludeLinksFromLockfile: false
 
-dependencies:
-  '@algolia/client-search':
-    specifier: ^4.14.3
-    version: 4.14.3
-  '@docusaurus/core':
-    specifier: 2.4.3
-    version: 2.4.3(@docusaurus/types@2.4.3)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.4)
-  '@docusaurus/preset-classic':
-    specifier: 2.4.3
-    version: 2.4.3(@algolia/client-search@4.14.3)(@types/react@18.0.26)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.4)
-  '@docusaurus/theme-mermaid':
-    specifier: 2.4.3
-    version: 2.4.3(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.4)
-  '@mdx-js/react':
-    specifier: ^1.6.22
-    version: 1.6.22(react@17.0.2)
-  autoprefixer:
-    specifier: ^10.4.13
-    version: 10.4.13(postcss@8.4.21)
-  clsx:
-    specifier: ^1.2.1
-    version: 1.2.1
-  docusaurus-plugin-image-zoom:
-    specifier: ^0.1.1
-    version: 0.1.1
-  postcss:
-    specifier: ^8.4.21
-    version: 8.4.21
-  prism-react-renderer:
-    specifier: ^1.3.5
-    version: 1.3.5(react@17.0.2)
-  prism-svelte:
-    specifier: ^0.5.0
-    version: 0.5.0
-  prismjs:
-    specifier: ^1.29.0
-    version: 1.29.0
-  react:
-    specifier: ^17.0.2
-    version: 17.0.2
-  react-dom:
-    specifier: ^17.0.2
-    version: 17.0.2(react@17.0.2)
-  react-icons:
-    specifier: ^4.7.1
-    version: 4.7.1(react@17.0.2)
-  react-simple-typewriter:
-    specifier: ^5.0.1
-    version: 5.0.1(react-dom@17.0.2)(react@17.0.2)
-  tailwindcss:
-    specifier: ^3.2.4
-    version: 3.2.4(postcss@8.4.21)
-
-devDependencies:
-  '@docusaurus/module-type-aliases':
-    specifier: 2.4.3
-    version: 2.4.3(react-dom@17.0.2)(react@17.0.2)
-  '@docusaurus/types':
-    specifier: ^2.4.3
-    version: 2.4.3(react-dom@17.0.2)(react@17.0.2)
-  '@tailwindcss/typography':
-    specifier: ^0.5.9
-    version: 0.5.9(tailwindcss@3.2.4)
-  '@tsconfig/docusaurus':
-    specifier: ^1.0.7
-    version: 1.0.7
-  '@types/react':
-    specifier: ^18.0.26
-    version: 18.0.26
-  typescript:
-    specifier: ^4.7.4
-    version: 4.9.4
+importers:
+
+  .:
+    dependencies:
+      '@algolia/client-search':
+        specifier: ^4.22.1
+        version: 4.22.1
+      '@docusaurus/core':
+        specifier: 3.4.0
+        version: 3.4.0(@docusaurus/types@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.3.3)
+      '@docusaurus/preset-classic':
+        specifier: 3.4.0
+        version: 3.4.0(@algolia/client-search@4.22.1)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(search-insights@2.13.0)(typescript@5.3.3)
+      '@docusaurus/theme-common':
+        specifier: 3.4.0
+        version: 3.4.0(@docusaurus/types@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.3.3)
+      '@docusaurus/theme-mermaid':
+        specifier: 3.4.0
+        version: 3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.3.3)
+      '@giscus/react':
+        specifier: ^2.4.0
+        version: 2.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@mdx-js/react':
+        specifier: ^3.0.1
+        version: 3.0.1(@types/react@18.0.26)(react@18.2.0)
+      autoprefixer:
+        specifier: ^10.4.13
+        version: 10.4.13(postcss@8.4.21)
+      clsx:
+        specifier: ^1.2.1
+        version: 1.2.1
+      postcss:
+        specifier: ^8.4.21
+        version: 8.4.21
+      prism-react-renderer:
+        specifier: ^2.3.1
+        version: 2.3.1(react@18.2.0)
+      prism-svelte:
+        specifier: ^0.5.0
+        version: 0.5.0
+      prismjs:
+        specifier: ^1.29.0
+        version: 1.29.0
+      react:
+        specifier: ^18.2.0
+        version: 18.2.0
+      react-dom:
+        specifier: ^18.2.0
+        version: 18.2.0(react@18.2.0)
+      react-icons:
+        specifier: ^5.0.1
+        version: 5.0.1(react@18.2.0)
+      react-share:
+        specifier: ^5.1.0
+        version: 5.1.0(react@18.2.0)
+      tailwindcss:
+        specifier: ^3.4.11
+        version: 3.4.11
+    devDependencies:
+      '@docusaurus/module-type-aliases':
+        specifier: 3.4.0
+        version: 3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@docusaurus/types':
+        specifier: ^3.4.0
+        version: 3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@tailwindcss/typography':
+        specifier: ^0.5.9
+        version: 0.5.9(tailwindcss@3.4.11)
+      '@tsconfig/docusaurus':
+        specifier: ^2.0.3
+        version: 2.0.3
+      '@types/node':
+        specifier: ^20.11.17
+        version: 20.11.17
+      '@types/react':
+        specifier: ^18.0.26
+        version: 18.0.26
+      typescript:
+        specifier: ^5.3.3
+        version: 5.3.3
 
 packages:
 
-  /@algolia/autocomplete-core@1.7.2:
-    resolution: {integrity: sha512-eclwUDC6qfApNnEfu1uWcL/rudQsn59tjEoUYZYE2JSXZrHLRjBUGMxiCoknobU2Pva8ejb0eRxpIYDtVVqdsw==}
-    dependencies:
-      '@algolia/autocomplete-shared': 1.7.2
-    dev: false
+  '@algolia/autocomplete-core@1.9.3':
+    resolution: {integrity: sha512-009HdfugtGCdC4JdXUbVJClA0q0zh24yyePn+KUGk3rP7j8FEe/m5Yo/z65gn6nP/cM39PxpzqKrL7A6fP6PPw==}
+
+  '@algolia/autocomplete-plugin-algolia-insights@1.9.3':
+    resolution: {integrity: sha512-a/yTUkcO/Vyy+JffmAnTWbr4/90cLzw+CC3bRbhnULr/EM0fGNvM13oQQ14f2moLMcVDyAx/leczLlAOovhSZg==}
+    peerDependencies:
+      search-insights: '>= 1 < 3'
 
-  /@algolia/autocomplete-preset-algolia@1.7.2(@algolia/client-search@4.14.3)(algoliasearch@4.14.3):
-    resolution: {integrity: sha512-+RYEG6B0QiGGfRb2G3MtPfyrl0dALF3cQNTWBzBX6p5o01vCCGTTinAm2UKG3tfc2CnOMAtnPLkzNZyJUpnVJw==}
+  '@algolia/autocomplete-preset-algolia@1.9.3':
+    resolution: {integrity: sha512-d4qlt6YmrLMYy95n5TB52wtNDr6EgAIPH81dvvvW8UmuWRgxEtY0NJiPwl/h95JtG2vmRM804M0DSwMCNZlzRA==}
     peerDependencies:
       '@algolia/client-search': '>= 4.9.1 < 6'
       algoliasearch: '>= 4.9.1 < 6'
-    dependencies:
-      '@algolia/autocomplete-shared': 1.7.2
-      '@algolia/client-search': 4.14.3
-      algoliasearch: 4.14.3
-    dev: false
 
-  /@algolia/autocomplete-shared@1.7.2:
-    resolution: {integrity: sha512-QCckjiC7xXHIUaIL3ektBtjJ0w7tTA3iqKcAE/Hjn1lZ5omp7i3Y4e09rAr9ZybqirL7AbxCLLq0Ra5DDPKeug==}
-    dev: false
+  '@algolia/autocomplete-shared@1.9.3':
+    resolution: {integrity: sha512-Wnm9E4Ye6Rl6sTTqjoymD+l8DjSTHsHboVRYrKgEt8Q7UHm9nYbqhN/i0fhUYA3OAEH7WA8x3jfpnmJm3rKvaQ==}
+    peerDependencies:
+      '@algolia/client-search': '>= 4.9.1 < 6'
+      algoliasearch: '>= 4.9.1 < 6'
 
-  /@algolia/cache-browser-local-storage@4.14.3:
-    resolution: {integrity: sha512-hWH1yCxgG3+R/xZIscmUrWAIBnmBFHH5j30fY/+aPkEZWt90wYILfAHIOZ1/Wxhho5SkPfwFmT7ooX2d9JeQBw==}
-    dependencies:
-      '@algolia/cache-common': 4.14.3
-    dev: false
+  '@algolia/cache-browser-local-storage@4.22.1':
+    resolution: {integrity: sha512-Sw6IAmOCvvP6QNgY9j+Hv09mvkvEIDKjYW8ow0UDDAxSXy664RBNQk3i/0nt7gvceOJ6jGmOTimaZoY1THmU7g==}
 
-  /@algolia/cache-common@4.14.3:
-    resolution: {integrity: sha512-oZJofOoD9FQOwiGTzyRnmzvh3ZP8WVTNPBLH5xU5JNF7drDbRT0ocVT0h/xB2rPHYzOeXRrLaQQBwRT/CKom0Q==}
-    dev: false
+  '@algolia/cache-common@4.22.1':
+    resolution: {integrity: sha512-TJMBKqZNKYB9TptRRjSUtevJeQVXRmg6rk9qgFKWvOy8jhCPdyNZV1nB3SKGufzvTVbomAukFR8guu/8NRKBTA==}
 
-  /@algolia/cache-in-memory@4.14.3:
-    resolution: {integrity: sha512-ES0hHQnzWjeioLQf5Nq+x1AWdZJ50znNPSH3puB/Y4Xsg4Av1bvLmTJe7SY2uqONaeMTvL0OaVcoVtQgJVw0vg==}
-    dependencies:
-      '@algolia/cache-common': 4.14.3
-    dev: false
+  '@algolia/cache-in-memory@4.22.1':
+    resolution: {integrity: sha512-ve+6Ac2LhwpufuWavM/aHjLoNz/Z/sYSgNIXsinGofWOysPilQZPUetqLj8vbvi+DHZZaYSEP9H5SRVXnpsNNw==}
 
-  /@algolia/client-account@4.14.3:
-    resolution: {integrity: sha512-PBcPb0+f5Xbh5UfLZNx2Ow589OdP8WYjB4CnvupfYBrl9JyC1sdH4jcq/ri8osO/mCZYjZrQsKAPIqW/gQmizQ==}
-    dependencies:
-      '@algolia/client-common': 4.14.3
-      '@algolia/client-search': 4.14.3
-      '@algolia/transporter': 4.14.3
-    dev: false
+  '@algolia/client-account@4.22.1':
+    resolution: {integrity: sha512-k8m+oegM2zlns/TwZyi4YgCtyToackkOpE+xCaKCYfBfDtdGOaVZCM5YvGPtK+HGaJMIN/DoTL8asbM3NzHonw==}
 
-  /@algolia/client-analytics@4.14.3:
-    resolution: {integrity: sha512-eAwQq0Hb/aauv9NhCH5Dp3Nm29oFx28sayFN2fdOWemwSeJHIl7TmcsxVlRsO50fsD8CtPcDhtGeD3AIFLNvqw==}
-    dependencies:
-      '@algolia/client-common': 4.14.3
-      '@algolia/client-search': 4.14.3
-      '@algolia/requester-common': 4.14.3
-      '@algolia/transporter': 4.14.3
-    dev: false
+  '@algolia/client-analytics@4.22.1':
+    resolution: {integrity: sha512-1ssi9pyxyQNN4a7Ji9R50nSdISIumMFDwKNuwZipB6TkauJ8J7ha/uO60sPJFqQyqvvI+px7RSNRQT3Zrvzieg==}
 
-  /@algolia/client-common@4.14.3:
-    resolution: {integrity: sha512-jkPPDZdi63IK64Yg4WccdCsAP4pHxSkr4usplkUZM5C1l1oEpZXsy2c579LQ0rvwCs5JFmwfNG4ahOszidfWPw==}
-    dependencies:
-      '@algolia/requester-common': 4.14.3
-      '@algolia/transporter': 4.14.3
-    dev: false
+  '@algolia/client-common@4.22.1':
+    resolution: {integrity: sha512-IvaL5v9mZtm4k4QHbBGDmU3wa/mKokmqNBqPj0K7lcR8ZDKzUorhcGp/u8PkPC/e0zoHSTvRh7TRkGX3Lm7iOQ==}
 
-  /@algolia/client-personalization@4.14.3:
-    resolution: {integrity: sha512-UCX1MtkVNgaOL9f0e22x6tC9e2H3unZQlSUdnVaSKpZ+hdSChXGaRjp2UIT7pxmPqNCyv51F597KEX5WT60jNg==}
-    dependencies:
-      '@algolia/client-common': 4.14.3
-      '@algolia/requester-common': 4.14.3
-      '@algolia/transporter': 4.14.3
-    dev: false
+  '@algolia/client-personalization@4.22.1':
+    resolution: {integrity: sha512-sl+/klQJ93+4yaqZ7ezOttMQ/nczly/3GmgZXJ1xmoewP5jmdP/X/nV5U7EHHH3hCUEHeN7X1nsIhGPVt9E1cQ==}
 
-  /@algolia/client-search@4.14.3:
-    resolution: {integrity: sha512-I2U7xBx5OPFdPLA8AXKUPPxGY3HDxZ4r7+mlZ8ZpLbI8/ri6fnu6B4z3wcL7sgHhDYMwnAE8Xr0AB0h3Hnkp4A==}
-    dependencies:
-      '@algolia/client-common': 4.14.3
-      '@algolia/requester-common': 4.14.3
-      '@algolia/transporter': 4.14.3
-    dev: false
+  '@algolia/client-search@4.22.1':
+    resolution: {integrity: sha512-yb05NA4tNaOgx3+rOxAmFztgMTtGBi97X7PC3jyNeGiwkAjOZc2QrdZBYyIdcDLoI09N0gjtpClcackoTN0gPA==}
 
-  /@algolia/events@4.0.1:
+  '@algolia/events@4.0.1':
     resolution: {integrity: sha512-FQzvOCgoFXAbf5Y6mYozw2aj5KCJoA3m4heImceldzPSMbdyS4atVjJzXKMsfX3wnZTFYwkkt8/z8UesLHlSBQ==}
-    dev: false
 
-  /@algolia/logger-common@4.14.3:
-    resolution: {integrity: sha512-kUEAZaBt/J3RjYi8MEBT2QEexJR2kAE2mtLmezsmqMQZTV502TkHCxYzTwY2dE7OKcUTxi4OFlMuS4GId9CWPw==}
-    dev: false
+  '@algolia/logger-common@4.22.1':
+    resolution: {integrity: sha512-OnTFymd2odHSO39r4DSWRFETkBufnY2iGUZNrMXpIhF5cmFE8pGoINNPzwg02QLBlGSaLqdKy0bM8S0GyqPLBg==}
 
-  /@algolia/logger-console@4.14.3:
-    resolution: {integrity: sha512-ZWqAlUITktiMN2EiFpQIFCJS10N96A++yrexqC2Z+3hgF/JcKrOxOdT4nSCQoEPvU4Ki9QKbpzbebRDemZt/hw==}
-    dependencies:
-      '@algolia/logger-common': 4.14.3
-    dev: false
+  '@algolia/logger-console@4.22.1':
+    resolution: {integrity: sha512-O99rcqpVPKN1RlpgD6H3khUWylU24OXlzkavUAMy6QZd1776QAcauE3oP8CmD43nbaTjBexZj2nGsBH9Tc0FVA==}
 
-  /@algolia/requester-browser-xhr@4.14.3:
-    resolution: {integrity: sha512-AZeg2T08WLUPvDncl2XLX2O67W5wIO8MNaT7z5ii5LgBTuk/rU4CikTjCe2xsUleIZeFl++QrPAi4Bdxws6r/Q==}
-    dependencies:
-      '@algolia/requester-common': 4.14.3
-    dev: false
+  '@algolia/requester-browser-xhr@4.22.1':
+    resolution: {integrity: sha512-dtQGYIg6MteqT1Uay3J/0NDqD+UciHy3QgRbk7bNddOJu+p3hzjTRYESqEnoX/DpEkaNYdRHUKNylsqMpgwaEw==}
 
-  /@algolia/requester-common@4.14.3:
-    resolution: {integrity: sha512-RrRzqNyKFDP7IkTuV3XvYGF9cDPn9h6qEDl595lXva3YUk9YSS8+MGZnnkOMHvjkrSCKfoLeLbm/T4tmoIeclw==}
-    dev: false
+  '@algolia/requester-common@4.22.1':
+    resolution: {integrity: sha512-dgvhSAtg2MJnR+BxrIFqlLtkLlVVhas9HgYKMk2Uxiy5m6/8HZBL40JVAMb2LovoPFs9I/EWIoFVjOrFwzn5Qg==}
 
-  /@algolia/requester-node-http@4.14.3:
-    resolution: {integrity: sha512-O5wnPxtDRPuW2U0EaOz9rMMWdlhwP0J0eSL1Z7TtXF8xnUeeUyNJrdhV5uy2CAp6RbhM1VuC3sOJcIR6Av+vbA==}
-    dependencies:
-      '@algolia/requester-common': 4.14.3
-    dev: false
+  '@algolia/requester-node-http@4.22.1':
+    resolution: {integrity: sha512-JfmZ3MVFQkAU+zug8H3s8rZ6h0ahHZL/SpMaSasTCGYR5EEJsCc8SI5UZ6raPN2tjxa5bxS13BRpGSBUens7EA==}
 
-  /@algolia/transporter@4.14.3:
-    resolution: {integrity: sha512-2qlKlKsnGJ008exFRb5RTeTOqhLZj0bkMCMVskxoqWejs2Q2QtWmsiH98hDfpw0fmnyhzHEt0Z7lqxBYp8bW2w==}
-    dependencies:
-      '@algolia/cache-common': 4.14.3
-      '@algolia/logger-common': 4.14.3
-      '@algolia/requester-common': 4.14.3
-    dev: false
+  '@algolia/transporter@4.22.1':
+    resolution: {integrity: sha512-kzWgc2c9IdxMa3YqA6TN0NW5VrKYYW/BELIn7vnLyn+U/RFdZ4lxxt9/8yq3DKV5snvoDzzO4ClyejZRdV3lMQ==}
 
-  /@ampproject/remapping@2.2.0:
+  '@alloc/quick-lru@5.2.0':
+    resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
+    engines: {node: '>=10'}
+
+  '@ampproject/remapping@2.2.0':
     resolution: {integrity: sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==}
     engines: {node: '>=6.0.0'}
-    dependencies:
-      '@jridgewell/gen-mapping': 0.1.1
-      '@jridgewell/trace-mapping': 0.3.17
-    dev: false
 
-  /@babel/code-frame@7.18.6:
-    resolution: {integrity: sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==}
+  '@babel/code-frame@7.23.5':
+    resolution: {integrity: sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==}
     engines: {node: '>=6.9.0'}
-    dependencies:
-      '@babel/highlight': 7.18.6
-    dev: false
-
-  /@babel/compat-data@7.20.10:
-    resolution: {integrity: sha512-sEnuDPpOJR/fcafHMjpcpGN5M2jbUGUHwmuWKM/YdPzeEDJg8bgmbcWQFUfE32MQjti1koACvoPVsDe8Uq+idg==}
-    engines: {node: '>=6.9.0'}
-    dev: false
 
-  /@babel/core@7.12.9:
-    resolution: {integrity: sha512-gTXYh3M5wb7FRXQy+FErKFAv90BnlOuNn1QkCK2lREoPAjrQCO49+HVSrFoe5uakFAF5eenS75KbO2vQiLrTMQ==}
+  '@babel/compat-data@7.23.5':
+    resolution: {integrity: sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==}
     engines: {node: '>=6.9.0'}
-    dependencies:
-      '@babel/code-frame': 7.18.6
-      '@babel/generator': 7.20.7
-      '@babel/helper-module-transforms': 7.20.11
-      '@babel/helpers': 7.20.7
-      '@babel/parser': 7.20.7
-      '@babel/template': 7.20.7
-      '@babel/traverse': 7.20.12
-      '@babel/types': 7.20.7
-      convert-source-map: 1.9.0
-      debug: 4.3.4
-      gensync: 1.0.0-beta.2
-      json5: 2.2.3
-      lodash: 4.17.21
-      resolve: 1.22.1
-      semver: 5.7.1
-      source-map: 0.5.7
-    transitivePeerDependencies:
-      - supports-color
-    dev: false
 
-  /@babel/core@7.20.12:
-    resolution: {integrity: sha512-XsMfHovsUYHFMdrIHkZphTN/2Hzzi78R08NuHfDBehym2VsPDL6Zn/JAD/JQdnRvbSsbQc4mVaU1m6JgtTEElg==}
+  '@babel/core@7.23.9':
+    resolution: {integrity: sha512-5q0175NOjddqpvvzU+kDiSOAk4PfdO6FvwCWoQ6RO7rTzEe8vlo+4HVfcnAREhD4npMs0e9uZypjTwzZPCf/cw==}
     engines: {node: '>=6.9.0'}
-    dependencies:
-      '@ampproject/remapping': 2.2.0
-      '@babel/code-frame': 7.18.6
-      '@babel/generator': 7.20.7
-      '@babel/helper-compilation-targets': 7.20.7(@babel/core@7.20.12)
-      '@babel/helper-module-transforms': 7.20.11
-      '@babel/helpers': 7.20.7
-      '@babel/parser': 7.20.7
-      '@babel/template': 7.20.7
-      '@babel/traverse': 7.20.12
-      '@babel/types': 7.20.7
-      convert-source-map: 1.9.0
-      debug: 4.3.4
-      gensync: 1.0.0-beta.2
-      json5: 2.2.3
-      semver: 6.3.0
-    transitivePeerDependencies:
-      - supports-color
-    dev: false
 
-  /@babel/generator@7.20.7:
-    resolution: {integrity: sha512-7wqMOJq8doJMZmP4ApXTzLxSr7+oO2jroJURrVEp6XShrQUObV8Tq/D0NCcoYg2uHqUrjzO0zwBjoYzelxK+sw==}
+  '@babel/generator@7.23.6':
+    resolution: {integrity: sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==}
     engines: {node: '>=6.9.0'}
-    dependencies:
-      '@babel/types': 7.20.7
-      '@jridgewell/gen-mapping': 0.3.2
-      jsesc: 2.5.2
-    dev: false
 
-  /@babel/helper-annotate-as-pure@7.18.6:
-    resolution: {integrity: sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==}
+  '@babel/helper-annotate-as-pure@7.22.5':
+    resolution: {integrity: sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==}
     engines: {node: '>=6.9.0'}
-    dependencies:
-      '@babel/types': 7.20.7
-    dev: false
 
-  /@babel/helper-builder-binary-assignment-operator-visitor@7.18.9:
-    resolution: {integrity: sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw==}
+  '@babel/helper-builder-binary-assignment-operator-visitor@7.22.15':
+    resolution: {integrity: sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==}
     engines: {node: '>=6.9.0'}
-    dependencies:
-      '@babel/helper-explode-assignable-expression': 7.18.6
-      '@babel/types': 7.20.7
-    dev: false
 
-  /@babel/helper-compilation-targets@7.20.7(@babel/core@7.20.12):
-    resolution: {integrity: sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==}
+  '@babel/helper-compilation-targets@7.23.6':
+    resolution: {integrity: sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==}
     engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0
-    dependencies:
-      '@babel/compat-data': 7.20.10
-      '@babel/core': 7.20.12
-      '@babel/helper-validator-option': 7.18.6
-      browserslist: 4.21.4
-      lru-cache: 5.1.1
-      semver: 6.3.0
-    dev: false
 
-  /@babel/helper-create-class-features-plugin@7.20.12(@babel/core@7.20.12):
-    resolution: {integrity: sha512-9OunRkbT0JQcednL0UFvbfXpAsUXiGjUk0a7sN8fUXX7Mue79cUSMjHGDRRi/Vz9vYlpIhLV5fMD5dKoMhhsNQ==}
+  '@babel/helper-create-class-features-plugin@7.23.10':
+    resolution: {integrity: sha512-2XpP2XhkXzgxecPNEEK8Vz8Asj9aRxt08oKOqtiZoqV2UGZ5T+EkyP9sXQ9nwMxBIG34a7jmasVqoMop7VdPUw==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-annotate-as-pure': 7.18.6
-      '@babel/helper-environment-visitor': 7.18.9
-      '@babel/helper-function-name': 7.19.0
-      '@babel/helper-member-expression-to-functions': 7.20.7
-      '@babel/helper-optimise-call-expression': 7.18.6
-      '@babel/helper-replace-supers': 7.20.7
-      '@babel/helper-skip-transparent-expression-wrappers': 7.20.0
-      '@babel/helper-split-export-declaration': 7.18.6
-    transitivePeerDependencies:
-      - supports-color
-    dev: false
 
-  /@babel/helper-create-regexp-features-plugin@7.20.5(@babel/core@7.20.12):
-    resolution: {integrity: sha512-m68B1lkg3XDGX5yCvGO0kPx3v9WIYLnzjKfPcQiwntEQa5ZeRkPmo2X/ISJc8qxWGfwUr+kvZAeEzAwLec2r2w==}
+  '@babel/helper-create-regexp-features-plugin@7.22.15':
+    resolution: {integrity: sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-annotate-as-pure': 7.18.6
-      regexpu-core: 5.2.2
-    dev: false
 
-  /@babel/helper-define-polyfill-provider@0.3.3(@babel/core@7.20.12):
-    resolution: {integrity: sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==}
+  '@babel/helper-define-polyfill-provider@0.5.0':
+    resolution: {integrity: sha512-NovQquuQLAQ5HuyjCz7WQP9MjRj7dx++yspwiyUiGl9ZyadHRSql1HZh5ogRd8W8w6YM6EQ/NTB8rgjLt5W65Q==}
     peerDependencies:
-      '@babel/core': ^7.4.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-compilation-targets': 7.20.7(@babel/core@7.20.12)
-      '@babel/helper-plugin-utils': 7.20.2
-      debug: 4.3.4
-      lodash.debounce: 4.0.8
-      resolve: 1.22.1
-      semver: 6.3.0
-    transitivePeerDependencies:
-      - supports-color
-    dev: false
+      '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0
 
-  /@babel/helper-environment-visitor@7.18.9:
-    resolution: {integrity: sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==}
+  '@babel/helper-environment-visitor@7.22.20':
+    resolution: {integrity: sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==}
     engines: {node: '>=6.9.0'}
-    dev: false
 
-  /@babel/helper-explode-assignable-expression@7.18.6:
-    resolution: {integrity: sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==}
+  '@babel/helper-function-name@7.23.0':
+    resolution: {integrity: sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==}
     engines: {node: '>=6.9.0'}
-    dependencies:
-      '@babel/types': 7.20.7
-    dev: false
 
-  /@babel/helper-function-name@7.19.0:
-    resolution: {integrity: sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==}
+  '@babel/helper-hoist-variables@7.22.5':
+    resolution: {integrity: sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==}
     engines: {node: '>=6.9.0'}
-    dependencies:
-      '@babel/template': 7.20.7
-      '@babel/types': 7.20.7
-    dev: false
 
-  /@babel/helper-hoist-variables@7.18.6:
-    resolution: {integrity: sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==}
+  '@babel/helper-member-expression-to-functions@7.23.0':
+    resolution: {integrity: sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==}
     engines: {node: '>=6.9.0'}
-    dependencies:
-      '@babel/types': 7.20.7
-    dev: false
 
-  /@babel/helper-member-expression-to-functions@7.20.7:
-    resolution: {integrity: sha512-9J0CxJLq315fEdi4s7xK5TQaNYjZw+nDVpVqr1axNGKzdrdwYBD5b4uKv3n75aABG0rCCTK8Im8Ww7eYfMrZgw==}
+  '@babel/helper-module-imports@7.22.15':
+    resolution: {integrity: sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==}
     engines: {node: '>=6.9.0'}
-    dependencies:
-      '@babel/types': 7.20.7
-    dev: false
 
-  /@babel/helper-module-imports@7.18.6:
-    resolution: {integrity: sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==}
+  '@babel/helper-module-transforms@7.23.3':
+    resolution: {integrity: sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==}
     engines: {node: '>=6.9.0'}
-    dependencies:
-      '@babel/types': 7.20.7
-    dev: false
+    peerDependencies:
+      '@babel/core': ^7.0.0
 
-  /@babel/helper-module-transforms@7.20.11:
-    resolution: {integrity: sha512-uRy78kN4psmji1s2QtbtcCSaj/LILFDp0f/ymhpQH5QY3nljUZCaNWz9X1dEj/8MBdBEFECs7yRhKn8i7NjZgg==}
+  '@babel/helper-optimise-call-expression@7.22.5':
+    resolution: {integrity: sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==}
     engines: {node: '>=6.9.0'}
-    dependencies:
-      '@babel/helper-environment-visitor': 7.18.9
-      '@babel/helper-module-imports': 7.18.6
-      '@babel/helper-simple-access': 7.20.2
-      '@babel/helper-split-export-declaration': 7.18.6
-      '@babel/helper-validator-identifier': 7.19.1
-      '@babel/template': 7.20.7
-      '@babel/traverse': 7.20.12
-      '@babel/types': 7.20.7
-    transitivePeerDependencies:
-      - supports-color
-    dev: false
 
-  /@babel/helper-optimise-call-expression@7.18.6:
-    resolution: {integrity: sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==}
+  '@babel/helper-plugin-utils@7.22.5':
+    resolution: {integrity: sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==}
     engines: {node: '>=6.9.0'}
-    dependencies:
-      '@babel/types': 7.20.7
-    dev: false
 
-  /@babel/helper-plugin-utils@7.10.4:
-    resolution: {integrity: sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==}
-    dev: false
-
-  /@babel/helper-plugin-utils@7.20.2:
-    resolution: {integrity: sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==}
+  '@babel/helper-plugin-utils@7.24.7':
+    resolution: {integrity: sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg==}
     engines: {node: '>=6.9.0'}
-    dev: false
 
-  /@babel/helper-remap-async-to-generator@7.18.9(@babel/core@7.20.12):
-    resolution: {integrity: sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==}
+  '@babel/helper-remap-async-to-generator@7.22.20':
+    resolution: {integrity: sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-annotate-as-pure': 7.18.6
-      '@babel/helper-environment-visitor': 7.18.9
-      '@babel/helper-wrap-function': 7.20.5
-      '@babel/types': 7.20.7
-    transitivePeerDependencies:
-      - supports-color
-    dev: false
 
-  /@babel/helper-replace-supers@7.20.7:
-    resolution: {integrity: sha512-vujDMtB6LVfNW13jhlCrp48QNslK6JXi7lQG736HVbHz/mbf4Dc7tIRh1Xf5C0rF7BP8iiSxGMCmY6Ci1ven3A==}
+  '@babel/helper-replace-supers@7.22.20':
+    resolution: {integrity: sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw==}
     engines: {node: '>=6.9.0'}
-    dependencies:
-      '@babel/helper-environment-visitor': 7.18.9
-      '@babel/helper-member-expression-to-functions': 7.20.7
-      '@babel/helper-optimise-call-expression': 7.18.6
-      '@babel/template': 7.20.7
-      '@babel/traverse': 7.20.12
-      '@babel/types': 7.20.7
-    transitivePeerDependencies:
-      - supports-color
-    dev: false
+    peerDependencies:
+      '@babel/core': ^7.0.0
 
-  /@babel/helper-simple-access@7.20.2:
-    resolution: {integrity: sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==}
+  '@babel/helper-simple-access@7.22.5':
+    resolution: {integrity: sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==}
     engines: {node: '>=6.9.0'}
-    dependencies:
-      '@babel/types': 7.20.7
-    dev: false
 
-  /@babel/helper-skip-transparent-expression-wrappers@7.20.0:
-    resolution: {integrity: sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==}
+  '@babel/helper-skip-transparent-expression-wrappers@7.22.5':
+    resolution: {integrity: sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==}
     engines: {node: '>=6.9.0'}
-    dependencies:
-      '@babel/types': 7.20.7
-    dev: false
 
-  /@babel/helper-split-export-declaration@7.18.6:
-    resolution: {integrity: sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==}
+  '@babel/helper-split-export-declaration@7.22.6':
+    resolution: {integrity: sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==}
     engines: {node: '>=6.9.0'}
-    dependencies:
-      '@babel/types': 7.20.7
-    dev: false
 
-  /@babel/helper-string-parser@7.19.4:
-    resolution: {integrity: sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==}
+  '@babel/helper-string-parser@7.23.4':
+    resolution: {integrity: sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==}
     engines: {node: '>=6.9.0'}
-    dev: false
 
-  /@babel/helper-validator-identifier@7.19.1:
-    resolution: {integrity: sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==}
+  '@babel/helper-validator-identifier@7.22.20':
+    resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==}
     engines: {node: '>=6.9.0'}
-    dev: false
 
-  /@babel/helper-validator-option@7.18.6:
-    resolution: {integrity: sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==}
+  '@babel/helper-validator-option@7.23.5':
+    resolution: {integrity: sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==}
     engines: {node: '>=6.9.0'}
-    dev: false
 
-  /@babel/helper-wrap-function@7.20.5:
-    resolution: {integrity: sha512-bYMxIWK5mh+TgXGVqAtnu5Yn1un+v8DDZtqyzKRLUzrh70Eal2O3aZ7aPYiMADO4uKlkzOiRiZ6GX5q3qxvW9Q==}
+  '@babel/helper-wrap-function@7.22.20':
+    resolution: {integrity: sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw==}
     engines: {node: '>=6.9.0'}
-    dependencies:
-      '@babel/helper-function-name': 7.19.0
-      '@babel/template': 7.20.7
-      '@babel/traverse': 7.20.12
-      '@babel/types': 7.20.7
-    transitivePeerDependencies:
-      - supports-color
-    dev: false
 
-  /@babel/helpers@7.20.7:
-    resolution: {integrity: sha512-PBPjs5BppzsGaxHQCDKnZ6Gd9s6xl8bBCluz3vEInLGRJmnZan4F6BYCeqtyXqkk4W5IlPmjK4JlOuZkpJ3xZA==}
+  '@babel/helpers@7.23.9':
+    resolution: {integrity: sha512-87ICKgU5t5SzOT7sBMfCOZQ2rHjRU+Pcb9BoILMYz600W6DkVRLFBPwQ18gwUVvggqXivaUakpnxWQGbpywbBQ==}
     engines: {node: '>=6.9.0'}
-    dependencies:
-      '@babel/template': 7.20.7
-      '@babel/traverse': 7.20.12
-      '@babel/types': 7.20.7
-    transitivePeerDependencies:
-      - supports-color
-    dev: false
 
-  /@babel/highlight@7.18.6:
-    resolution: {integrity: sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==}
+  '@babel/highlight@7.23.4':
+    resolution: {integrity: sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==}
     engines: {node: '>=6.9.0'}
-    dependencies:
-      '@babel/helper-validator-identifier': 7.19.1
-      chalk: 2.4.2
-      js-tokens: 4.0.0
-    dev: false
 
-  /@babel/parser@7.20.7:
-    resolution: {integrity: sha512-T3Z9oHybU+0vZlY9CiDSJQTD5ZapcW18ZctFMi0MOAl/4BjFF4ul7NVSARLdbGO5vDqy9eQiGTV0LtKfvCYvcg==}
+  '@babel/parser@7.23.9':
+    resolution: {integrity: sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==}
     engines: {node: '>=6.0.0'}
     hasBin: true
-    dependencies:
-      '@babel/types': 7.20.7
-    dev: false
 
-  /@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.18.6(@babel/core@7.20.12):
-    resolution: {integrity: sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==}
+  '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.23.3':
+    resolution: {integrity: sha512-iRkKcCqb7iGnq9+3G6rZ+Ciz5VywC4XNRHe57lKM+jOeYAoR0lVqdeeDRfh0tQcTfw/+vBhHn926FmQhLtlFLQ==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-plugin-utils': 7.20.2
-    dev: false
 
-  /@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.20.7(@babel/core@7.20.12):
-    resolution: {integrity: sha512-sbr9+wNE5aXMBBFBICk01tt7sBf2Oc9ikRFEcem/ZORup9IMUdNhW7/wVLEbbtlWOsEubJet46mHAL2C8+2jKQ==}
+  '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.23.3':
+    resolution: {integrity: sha512-WwlxbfMNdVEpQjZmK5mhm7oSwD3dS6eU+Iwsi4Knl9wAletWem7kaRsGOG+8UEbRyqxY4SS5zvtfXwX+jMxUwQ==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.13.0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-plugin-utils': 7.20.2
-      '@babel/helper-skip-transparent-expression-wrappers': 7.20.0
-      '@babel/plugin-proposal-optional-chaining': 7.20.7(@babel/core@7.20.12)
-    dev: false
 
-  /@babel/plugin-proposal-async-generator-functions@7.20.7(@babel/core@7.20.12):
-    resolution: {integrity: sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA==}
+  '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.23.7':
+    resolution: {integrity: sha512-LlRT7HgaifEpQA1ZgLVOIJZZFVPWN5iReq/7/JixwBtwcoeVGDBD53ZV28rrsLYOZs1Y/EHhA8N/Z6aazHR8cw==}
     engines: {node: '>=6.9.0'}
-    deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-async-generator-functions instead.
     peerDependencies:
-      '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-environment-visitor': 7.18.9
-      '@babel/helper-plugin-utils': 7.20.2
-      '@babel/helper-remap-async-to-generator': 7.18.9(@babel/core@7.20.12)
-      '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.20.12)
-    transitivePeerDependencies:
-      - supports-color
-    dev: false
+      '@babel/core': ^7.0.0
 
-  /@babel/plugin-proposal-class-properties@7.18.6(@babel/core@7.20.12):
-    resolution: {integrity: sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==}
+  '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2':
+    resolution: {integrity: sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==}
     engines: {node: '>=6.9.0'}
-    deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead.
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-create-class-features-plugin': 7.20.12(@babel/core@7.20.12)
-      '@babel/helper-plugin-utils': 7.20.2
-    transitivePeerDependencies:
-      - supports-color
-    dev: false
 
-  /@babel/plugin-proposal-class-static-block@7.20.7(@babel/core@7.20.12):
-    resolution: {integrity: sha512-AveGOoi9DAjUYYuUAG//Ig69GlazLnoyzMw68VCDux+c1tsnnH/OkYcpz/5xzMkEFC6UxjR5Gw1c+iY2wOGVeQ==}
-    engines: {node: '>=6.9.0'}
-    deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-static-block instead.
+  '@babel/plugin-syntax-async-generators@7.8.4':
+    resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==}
     peerDependencies:
-      '@babel/core': ^7.12.0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-create-class-features-plugin': 7.20.12(@babel/core@7.20.12)
-      '@babel/helper-plugin-utils': 7.20.2
-      '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.20.12)
-    transitivePeerDependencies:
-      - supports-color
-    dev: false
+      '@babel/core': ^7.0.0-0
 
-  /@babel/plugin-proposal-dynamic-import@7.18.6(@babel/core@7.20.12):
-    resolution: {integrity: sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==}
-    engines: {node: '>=6.9.0'}
-    deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-dynamic-import instead.
+  '@babel/plugin-syntax-class-properties@7.12.13':
+    resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-plugin-utils': 7.20.2
-      '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.20.12)
-    dev: false
 
-  /@babel/plugin-proposal-export-namespace-from@7.18.9(@babel/core@7.20.12):
-    resolution: {integrity: sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==}
+  '@babel/plugin-syntax-class-static-block@7.14.5':
+    resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==}
     engines: {node: '>=6.9.0'}
-    deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-export-namespace-from instead.
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-plugin-utils': 7.20.2
-      '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.20.12)
-    dev: false
 
-  /@babel/plugin-proposal-json-strings@7.18.6(@babel/core@7.20.12):
-    resolution: {integrity: sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==}
-    engines: {node: '>=6.9.0'}
-    deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-json-strings instead.
+  '@babel/plugin-syntax-dynamic-import@7.8.3':
+    resolution: {integrity: sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-plugin-utils': 7.20.2
-      '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.20.12)
-    dev: false
 
-  /@babel/plugin-proposal-logical-assignment-operators@7.20.7(@babel/core@7.20.12):
-    resolution: {integrity: sha512-y7C7cZgpMIjWlKE5T7eJwp+tnRYM89HmRvWM5EQuB5BoHEONjmQ8lSNmBUwOyy/GFRsohJED51YBF79hE1djug==}
-    engines: {node: '>=6.9.0'}
-    deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-logical-assignment-operators instead.
+  '@babel/plugin-syntax-export-namespace-from@7.8.3':
+    resolution: {integrity: sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-plugin-utils': 7.20.2
-      '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.20.12)
-    dev: false
 
-  /@babel/plugin-proposal-nullish-coalescing-operator@7.18.6(@babel/core@7.20.12):
-    resolution: {integrity: sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==}
+  '@babel/plugin-syntax-import-assertions@7.23.3':
+    resolution: {integrity: sha512-lPgDSU+SJLK3xmFDTV2ZRQAiM7UuUjGidwBywFavObCiZc1BeAAcMtHJKUya92hPHO+at63JJPLygilZard8jw==}
     engines: {node: '>=6.9.0'}
-    deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-nullish-coalescing-operator instead.
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-plugin-utils': 7.20.2
-      '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.20.12)
-    dev: false
 
-  /@babel/plugin-proposal-numeric-separator@7.18.6(@babel/core@7.20.12):
-    resolution: {integrity: sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==}
+  '@babel/plugin-syntax-import-attributes@7.23.3':
+    resolution: {integrity: sha512-pawnE0P9g10xgoP7yKr6CK63K2FMsTE+FZidZO/1PwRdzmAPVs+HS1mAURUsgaoxammTJvULUdIkEK0gOcU2tA==}
     engines: {node: '>=6.9.0'}
-    deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-numeric-separator instead.
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-plugin-utils': 7.20.2
-      '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.20.12)
-    dev: false
 
-  /@babel/plugin-proposal-object-rest-spread@7.12.1(@babel/core@7.12.9):
-    resolution: {integrity: sha512-s6SowJIjzlhx8o7lsFx5zmY4At6CTtDvgNQDdPzkBQucle58A6b/TTeEBYtyDgmcXjUTM+vE8YOGHZzzbc/ioA==}
-    deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-object-rest-spread instead.
+  '@babel/plugin-syntax-import-meta@7.10.4':
+    resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.12.9
-      '@babel/helper-plugin-utils': 7.20.2
-      '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.12.9)
-      '@babel/plugin-transform-parameters': 7.20.7(@babel/core@7.12.9)
-    dev: false
 
-  /@babel/plugin-proposal-object-rest-spread@7.20.7(@babel/core@7.20.12):
-    resolution: {integrity: sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==}
-    engines: {node: '>=6.9.0'}
-    deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-object-rest-spread instead.
+  '@babel/plugin-syntax-json-strings@7.8.3':
+    resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/compat-data': 7.20.10
-      '@babel/core': 7.20.12
-      '@babel/helper-compilation-targets': 7.20.7(@babel/core@7.20.12)
-      '@babel/helper-plugin-utils': 7.20.2
-      '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.20.12)
-      '@babel/plugin-transform-parameters': 7.20.7(@babel/core@7.20.12)
-    dev: false
 
-  /@babel/plugin-proposal-optional-catch-binding@7.18.6(@babel/core@7.20.12):
-    resolution: {integrity: sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==}
+  '@babel/plugin-syntax-jsx@7.23.3':
+    resolution: {integrity: sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg==}
     engines: {node: '>=6.9.0'}
-    deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-catch-binding instead.
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-plugin-utils': 7.20.2
-      '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.20.12)
-    dev: false
 
-  /@babel/plugin-proposal-optional-chaining@7.20.7(@babel/core@7.20.12):
-    resolution: {integrity: sha512-T+A7b1kfjtRM51ssoOfS1+wbyCVqorfyZhT99TvxxLMirPShD8CzKMRepMlCBGM5RpHMbn8s+5MMHnPstJH6mQ==}
-    engines: {node: '>=6.9.0'}
-    deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-chaining instead.
+  '@babel/plugin-syntax-logical-assignment-operators@7.10.4':
+    resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-plugin-utils': 7.20.2
-      '@babel/helper-skip-transparent-expression-wrappers': 7.20.0
-      '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.20.12)
-    dev: false
 
-  /@babel/plugin-proposal-private-methods@7.18.6(@babel/core@7.20.12):
-    resolution: {integrity: sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==}
-    engines: {node: '>=6.9.0'}
-    deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-methods instead.
+  '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3':
+    resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-create-class-features-plugin': 7.20.12(@babel/core@7.20.12)
-      '@babel/helper-plugin-utils': 7.20.2
-    transitivePeerDependencies:
-      - supports-color
-    dev: false
 
-  /@babel/plugin-proposal-private-property-in-object@7.20.5(@babel/core@7.20.12):
-    resolution: {integrity: sha512-Vq7b9dUA12ByzB4EjQTPo25sFhY+08pQDBSZRtUAkj7lb7jahaHR5igera16QZ+3my1nYR4dKsNdYj5IjPHilQ==}
-    engines: {node: '>=6.9.0'}
-    deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-property-in-object instead.
+  '@babel/plugin-syntax-numeric-separator@7.10.4':
+    resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-annotate-as-pure': 7.18.6
-      '@babel/helper-create-class-features-plugin': 7.20.12(@babel/core@7.20.12)
-      '@babel/helper-plugin-utils': 7.20.2
-      '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.20.12)
-    transitivePeerDependencies:
-      - supports-color
-    dev: false
 
-  /@babel/plugin-proposal-unicode-property-regex@7.18.6(@babel/core@7.20.12):
-    resolution: {integrity: sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==}
-    engines: {node: '>=4'}
-    deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-unicode-property-regex instead.
+  '@babel/plugin-syntax-object-rest-spread@7.8.3':
+    resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-create-regexp-features-plugin': 7.20.5(@babel/core@7.20.12)
-      '@babel/helper-plugin-utils': 7.20.2
-    dev: false
 
-  /@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.20.12):
-    resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==}
+  '@babel/plugin-syntax-optional-catch-binding@7.8.3':
+    resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-plugin-utils': 7.20.2
-    dev: false
 
-  /@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.20.12):
-    resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==}
+  '@babel/plugin-syntax-optional-chaining@7.8.3':
+    resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-plugin-utils': 7.20.2
-    dev: false
 
-  /@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.20.12):
-    resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==}
+  '@babel/plugin-syntax-private-property-in-object@7.14.5':
+    resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-plugin-utils': 7.20.2
-    dev: false
 
-  /@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.20.12):
-    resolution: {integrity: sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==}
+  '@babel/plugin-syntax-top-level-await@7.14.5':
+    resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==}
+    engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-plugin-utils': 7.20.2
-    dev: false
 
-  /@babel/plugin-syntax-export-namespace-from@7.8.3(@babel/core@7.20.12):
-    resolution: {integrity: sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==}
+  '@babel/plugin-syntax-typescript@7.23.3':
+    resolution: {integrity: sha512-9EiNjVJOMwCO+43TqoTrgQ8jMwcAd0sWyXi9RPfIsLTj4R2MADDDQXELhffaUx/uJv2AYcxBgPwH6j4TIA4ytQ==}
+    engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-plugin-utils': 7.20.2
-    dev: false
 
-  /@babel/plugin-syntax-import-assertions@7.20.0(@babel/core@7.20.12):
-    resolution: {integrity: sha512-IUh1vakzNoWalR8ch/areW7qFopR2AEw03JlG7BbrDqmQ4X3q9uuipQwSGrUn7oGiemKjtSLDhNtQHzMHr1JdQ==}
+  '@babel/plugin-syntax-unicode-sets-regex@7.18.6':
+    resolution: {integrity: sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
-      '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-plugin-utils': 7.20.2
-    dev: false
+      '@babel/core': ^7.0.0
 
-  /@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.20.12):
-    resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==}
+  '@babel/plugin-transform-arrow-functions@7.23.3':
+    resolution: {integrity: sha512-NzQcQrzaQPkaEwoTm4Mhyl8jI1huEL/WWIEvudjTCMJ9aBZNpsJbMASx7EQECtQQPS/DcnFpo0FIh3LvEO9cxQ==}
+    engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-plugin-utils': 7.20.2
-    dev: false
 
-  /@babel/plugin-syntax-jsx@7.12.1(@babel/core@7.12.9):
-    resolution: {integrity: sha512-1yRi7yAtB0ETgxdY9ti/p2TivUxJkTdhu/ZbF9MshVGqOx1TdB3b7xCXs49Fupgg50N45KcAsRP/ZqWjs9SRjg==}
+  '@babel/plugin-transform-async-generator-functions@7.23.9':
+    resolution: {integrity: sha512-8Q3veQEDGe14dTYuwagbRtwxQDnytyg1JFu4/HwEMETeofocrB0U0ejBJIXoeG/t2oXZ8kzCyI0ZZfbT80VFNQ==}
+    engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.12.9
-      '@babel/helper-plugin-utils': 7.20.2
-    dev: false
 
-  /@babel/plugin-syntax-jsx@7.18.6(@babel/core@7.20.12):
-    resolution: {integrity: sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==}
+  '@babel/plugin-transform-async-to-generator@7.23.3':
+    resolution: {integrity: sha512-A7LFsKi4U4fomjqXJlZg/u0ft/n8/7n7lpffUP/ZULx/DtV9SGlNKZolHH6PE8Xl1ngCc0M11OaeZptXVkfKSw==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-plugin-utils': 7.20.2
-    dev: false
 
-  /@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.20.12):
-    resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==}
+  '@babel/plugin-transform-block-scoped-functions@7.23.3':
+    resolution: {integrity: sha512-vI+0sIaPIO6CNuM9Kk5VmXcMVRiOpDh7w2zZt9GXzmE/9KD70CUEVhvPR/etAeNK/FAEkhxQtXOzVF3EuRL41A==}
+    engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-plugin-utils': 7.20.2
-    dev: false
 
-  /@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.20.12):
-    resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==}
+  '@babel/plugin-transform-block-scoping@7.23.4':
+    resolution: {integrity: sha512-0QqbP6B6HOh7/8iNR4CQU2Th/bbRtBp4KS9vcaZd1fZ0wSh5Fyssg0UCIHwxh+ka+pNDREbVLQnHCMHKZfPwfw==}
+    engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-plugin-utils': 7.20.2
-    dev: false
 
-  /@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.20.12):
-    resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==}
+  '@babel/plugin-transform-class-properties@7.23.3':
+    resolution: {integrity: sha512-uM+AN8yCIjDPccsKGlw271xjJtGii+xQIF/uMPS8H15L12jZTsLfF4o5vNO7d/oUguOyfdikHGc/yi9ge4SGIg==}
+    engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-plugin-utils': 7.20.2
-    dev: false
 
-  /@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.12.9):
-    resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==}
+  '@babel/plugin-transform-class-static-block@7.23.4':
+    resolution: {integrity: sha512-nsWu/1M+ggti1SOALj3hfx5FXzAY06fwPJsUZD4/A5e1bWi46VUIWtD+kOX6/IdhXGsXBWllLFDSnqSCdUNydQ==}
+    engines: {node: '>=6.9.0'}
     peerDependencies:
-      '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.12.9
-      '@babel/helper-plugin-utils': 7.20.2
-    dev: false
+      '@babel/core': ^7.12.0
 
-  /@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.20.12):
-    resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==}
+  '@babel/plugin-transform-classes@7.23.8':
+    resolution: {integrity: sha512-yAYslGsY1bX6Knmg46RjiCiNSwJKv2IUC8qOdYKqMMr0491SXFhcHqOdRDeCRohOOIzwN/90C6mQ9qAKgrP7dg==}
+    engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-plugin-utils': 7.20.2
-    dev: false
 
-  /@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.20.12):
-    resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==}
+  '@babel/plugin-transform-computed-properties@7.23.3':
+    resolution: {integrity: sha512-dTj83UVTLw/+nbiHqQSFdwO9CbTtwq1DsDqm3CUEtDrZNET5rT5E6bIdTlOftDTDLMYxvxHNEYO4B9SLl8SLZw==}
+    engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-plugin-utils': 7.20.2
-    dev: false
 
-  /@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.20.12):
-    resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==}
+  '@babel/plugin-transform-destructuring@7.23.3':
+    resolution: {integrity: sha512-n225npDqjDIr967cMScVKHXJs7rout1q+tt50inyBCPkyZ8KxeI6d+GIbSBTT/w/9WdlWDOej3V9HE5Lgk57gw==}
+    engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-plugin-utils': 7.20.2
-    dev: false
 
-  /@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.20.12):
-    resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==}
+  '@babel/plugin-transform-dotall-regex@7.23.3':
+    resolution: {integrity: sha512-vgnFYDHAKzFaTVp+mneDsIEbnJ2Np/9ng9iviHw3P/KVcgONxpNULEW/51Z/BaFojG2GI2GwwXck5uV1+1NOYQ==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-plugin-utils': 7.20.2
-    dev: false
 
-  /@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.20.12):
-    resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==}
+  '@babel/plugin-transform-duplicate-keys@7.23.3':
+    resolution: {integrity: sha512-RrqQ+BQmU3Oyav3J+7/myfvRCq7Tbz+kKLLshUmMwNlDHExbGL7ARhajvoBJEvc+fCguPPu887N+3RRXBVKZUA==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-plugin-utils': 7.20.2
-    dev: false
 
-  /@babel/plugin-syntax-typescript@7.20.0(@babel/core@7.20.12):
-    resolution: {integrity: sha512-rd9TkG+u1CExzS4SM1BlMEhMXwFLKVjOAFFCDx9PbX5ycJWDoWMcwdJH9RhkPu1dOgn5TrxLot/Gx6lWFuAUNQ==}
+  '@babel/plugin-transform-dynamic-import@7.23.4':
+    resolution: {integrity: sha512-V6jIbLhdJK86MaLh4Jpghi8ho5fGzt3imHOBu/x0jlBaPYqDoWz4RDXjmMOfnh+JWNaQleEAByZLV0QzBT4YQQ==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-plugin-utils': 7.20.2
-    dev: false
 
-  /@babel/plugin-transform-arrow-functions@7.20.7(@babel/core@7.20.12):
-    resolution: {integrity: sha512-3poA5E7dzDomxj9WXWwuD6A5F3kc7VXwIJO+E+J8qtDtS+pXPAhrgEyh+9GBwBgPq1Z+bB+/JD60lp5jsN7JPQ==}
+  '@babel/plugin-transform-exponentiation-operator@7.23.3':
+    resolution: {integrity: sha512-5fhCsl1odX96u7ILKHBj4/Y8vipoqwsJMh4csSA8qFfxrZDEA4Ssku2DyNvMJSmZNOEBT750LfFPbtrnTP90BQ==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-plugin-utils': 7.20.2
-    dev: false
 
-  /@babel/plugin-transform-async-to-generator@7.20.7(@babel/core@7.20.12):
-    resolution: {integrity: sha512-Uo5gwHPT9vgnSXQxqGtpdufUiWp96gk7yiP4Mp5bm1QMkEmLXBO7PAGYbKoJ6DhAwiNkcHFBol/x5zZZkL/t0Q==}
+  '@babel/plugin-transform-export-namespace-from@7.23.4':
+    resolution: {integrity: sha512-GzuSBcKkx62dGzZI1WVgTWvkkz84FZO5TC5T8dl/Tht/rAla6Dg/Mz9Yhypg+ezVACf/rgDuQt3kbWEv7LdUDQ==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-module-imports': 7.18.6
-      '@babel/helper-plugin-utils': 7.20.2
-      '@babel/helper-remap-async-to-generator': 7.18.9(@babel/core@7.20.12)
-    transitivePeerDependencies:
-      - supports-color
-    dev: false
 
-  /@babel/plugin-transform-block-scoped-functions@7.18.6(@babel/core@7.20.12):
-    resolution: {integrity: sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==}
+  '@babel/plugin-transform-for-of@7.23.6':
+    resolution: {integrity: sha512-aYH4ytZ0qSuBbpfhuofbg/e96oQ7U2w1Aw/UQmKT+1l39uEhUPoFS3fHevDc1G0OvewyDudfMKY1OulczHzWIw==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-plugin-utils': 7.20.2
-    dev: false
 
-  /@babel/plugin-transform-block-scoping@7.20.11(@babel/core@7.20.12):
-    resolution: {integrity: sha512-tA4N427a7fjf1P0/2I4ScsHGc5jcHPbb30xMbaTke2gxDuWpUfXDuX1FEymJwKk4tuGUvGcejAR6HdZVqmmPyw==}
+  '@babel/plugin-transform-function-name@7.23.3':
+    resolution: {integrity: sha512-I1QXp1LxIvt8yLaib49dRW5Okt7Q4oaxao6tFVKS/anCdEOMtYwWVKoiOA1p34GOWIZjUK0E+zCp7+l1pfQyiw==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-plugin-utils': 7.20.2
-    dev: false
 
-  /@babel/plugin-transform-classes@7.20.7(@babel/core@7.20.12):
-    resolution: {integrity: sha512-LWYbsiXTPKl+oBlXUGlwNlJZetXD5Am+CyBdqhPsDVjM9Jc8jwBJFrKhHf900Kfk2eZG1y9MAG3UNajol7A4VQ==}
+  '@babel/plugin-transform-json-strings@7.23.4':
+    resolution: {integrity: sha512-81nTOqM1dMwZ/aRXQ59zVubN9wHGqk6UtqRK+/q+ciXmRy8fSolhGVvG09HHRGo4l6fr/c4ZhXUQH0uFW7PZbg==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-annotate-as-pure': 7.18.6
-      '@babel/helper-compilation-targets': 7.20.7(@babel/core@7.20.12)
-      '@babel/helper-environment-visitor': 7.18.9
-      '@babel/helper-function-name': 7.19.0
-      '@babel/helper-optimise-call-expression': 7.18.6
-      '@babel/helper-plugin-utils': 7.20.2
-      '@babel/helper-replace-supers': 7.20.7
-      '@babel/helper-split-export-declaration': 7.18.6
-      globals: 11.12.0
-    transitivePeerDependencies:
-      - supports-color
-    dev: false
 
-  /@babel/plugin-transform-computed-properties@7.20.7(@babel/core@7.20.12):
-    resolution: {integrity: sha512-Lz7MvBK6DTjElHAmfu6bfANzKcxpyNPeYBGEafyA6E5HtRpjpZwU+u7Qrgz/2OR0z+5TvKYbPdphfSaAcZBrYQ==}
+  '@babel/plugin-transform-literals@7.23.3':
+    resolution: {integrity: sha512-wZ0PIXRxnwZvl9AYpqNUxpZ5BiTGrYt7kueGQ+N5FiQ7RCOD4cm8iShd6S6ggfVIWaJf2EMk8eRzAh52RfP4rQ==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-plugin-utils': 7.20.2
-      '@babel/template': 7.20.7
-    dev: false
 
-  /@babel/plugin-transform-destructuring@7.20.7(@babel/core@7.20.12):
-    resolution: {integrity: sha512-Xwg403sRrZb81IVB79ZPqNQME23yhugYVqgTxAhT99h485F4f+GMELFhhOsscDUB7HCswepKeCKLn/GZvUKoBA==}
+  '@babel/plugin-transform-logical-assignment-operators@7.23.4':
+    resolution: {integrity: sha512-Mc/ALf1rmZTP4JKKEhUwiORU+vcfarFVLfcFiolKUo6sewoxSEgl36ak5t+4WamRsNr6nzjZXQjM35WsU+9vbg==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-plugin-utils': 7.20.2
-    dev: false
 
-  /@babel/plugin-transform-dotall-regex@7.18.6(@babel/core@7.20.12):
-    resolution: {integrity: sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==}
+  '@babel/plugin-transform-member-expression-literals@7.23.3':
+    resolution: {integrity: sha512-sC3LdDBDi5x96LA+Ytekz2ZPk8i/Ck+DEuDbRAll5rknJ5XRTSaPKEYwomLcs1AA8wg9b3KjIQRsnApj+q51Ag==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-create-regexp-features-plugin': 7.20.5(@babel/core@7.20.12)
-      '@babel/helper-plugin-utils': 7.20.2
-    dev: false
 
-  /@babel/plugin-transform-duplicate-keys@7.18.9(@babel/core@7.20.12):
-    resolution: {integrity: sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==}
+  '@babel/plugin-transform-modules-amd@7.23.3':
+    resolution: {integrity: sha512-vJYQGxeKM4t8hYCKVBlZX/gtIY2I7mRGFNcm85sgXGMTBcoV3QdVtdpbcWEbzbfUIUZKwvgFT82mRvaQIebZzw==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-plugin-utils': 7.20.2
-    dev: false
 
-  /@babel/plugin-transform-exponentiation-operator@7.18.6(@babel/core@7.20.12):
-    resolution: {integrity: sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==}
+  '@babel/plugin-transform-modules-commonjs@7.23.3':
+    resolution: {integrity: sha512-aVS0F65LKsdNOtcz6FRCpE4OgsP2OFnW46qNxNIX9h3wuzaNcSQsJysuMwqSibC98HPrf2vCgtxKNwS0DAlgcA==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-builder-binary-assignment-operator-visitor': 7.18.9
-      '@babel/helper-plugin-utils': 7.20.2
-    dev: false
 
-  /@babel/plugin-transform-for-of@7.18.8(@babel/core@7.20.12):
-    resolution: {integrity: sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ==}
+  '@babel/plugin-transform-modules-systemjs@7.23.9':
+    resolution: {integrity: sha512-KDlPRM6sLo4o1FkiSlXoAa8edLXFsKKIda779fbLrvmeuc3itnjCtaO6RrtoaANsIJANj+Vk1zqbZIMhkCAHVw==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-plugin-utils': 7.20.2
-    dev: false
 
-  /@babel/plugin-transform-function-name@7.18.9(@babel/core@7.20.12):
-    resolution: {integrity: sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==}
+  '@babel/plugin-transform-modules-umd@7.23.3':
+    resolution: {integrity: sha512-zHsy9iXX2nIsCBFPud3jKn1IRPWg3Ing1qOZgeKV39m1ZgIdpJqvlWVeiHBZC6ITRG0MfskhYe9cLgntfSFPIg==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-compilation-targets': 7.20.7(@babel/core@7.20.12)
-      '@babel/helper-function-name': 7.19.0
-      '@babel/helper-plugin-utils': 7.20.2
-    dev: false
 
-  /@babel/plugin-transform-literals@7.18.9(@babel/core@7.20.12):
-    resolution: {integrity: sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==}
+  '@babel/plugin-transform-named-capturing-groups-regex@7.22.5':
+    resolution: {integrity: sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
-      '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-plugin-utils': 7.20.2
-    dev: false
+      '@babel/core': ^7.0.0
 
-  /@babel/plugin-transform-member-expression-literals@7.18.6(@babel/core@7.20.12):
-    resolution: {integrity: sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==}
+  '@babel/plugin-transform-new-target@7.23.3':
+    resolution: {integrity: sha512-YJ3xKqtJMAT5/TIZnpAR3I+K+WaDowYbN3xyxI8zxx/Gsypwf9B9h0VB+1Nh6ACAAPRS5NSRje0uVv5i79HYGQ==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-plugin-utils': 7.20.2
-    dev: false
 
-  /@babel/plugin-transform-modules-amd@7.20.11(@babel/core@7.20.12):
-    resolution: {integrity: sha512-NuzCt5IIYOW0O30UvqktzHYR2ud5bOWbY0yaxWZ6G+aFzOMJvrs5YHNikrbdaT15+KNO31nPOy5Fim3ku6Zb5g==}
+  '@babel/plugin-transform-nullish-coalescing-operator@7.23.4':
+    resolution: {integrity: sha512-jHE9EVVqHKAQx+VePv5LLGHjmHSJR76vawFPTdlxR/LVJPfOEGxREQwQfjuZEOPTwG92X3LINSh3M40Rv4zpVA==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-module-transforms': 7.20.11
-      '@babel/helper-plugin-utils': 7.20.2
-    transitivePeerDependencies:
-      - supports-color
-    dev: false
 
-  /@babel/plugin-transform-modules-commonjs@7.20.11(@babel/core@7.20.12):
-    resolution: {integrity: sha512-S8e1f7WQ7cimJQ51JkAaDrEtohVEitXjgCGAS2N8S31Y42E+kWwfSz83LYz57QdBm7q9diARVqanIaH2oVgQnw==}
+  '@babel/plugin-transform-numeric-separator@7.23.4':
+    resolution: {integrity: sha512-mps6auzgwjRrwKEZA05cOwuDc9FAzoyFS4ZsG/8F43bTLf/TgkJg7QXOrPO1JO599iA3qgK9MXdMGOEC8O1h6Q==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-module-transforms': 7.20.11
-      '@babel/helper-plugin-utils': 7.20.2
-      '@babel/helper-simple-access': 7.20.2
-    transitivePeerDependencies:
-      - supports-color
-    dev: false
 
-  /@babel/plugin-transform-modules-systemjs@7.20.11(@babel/core@7.20.12):
-    resolution: {integrity: sha512-vVu5g9BPQKSFEmvt2TA4Da5N+QVS66EX21d8uoOihC+OCpUoGvzVsXeqFdtAEfVa5BILAeFt+U7yVmLbQnAJmw==}
+  '@babel/plugin-transform-object-rest-spread@7.23.4':
+    resolution: {integrity: sha512-9x9K1YyeQVw0iOXJlIzwm8ltobIIv7j2iLyP2jIhEbqPRQ7ScNgwQufU2I0Gq11VjyG4gI4yMXt2VFags+1N3g==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-hoist-variables': 7.18.6
-      '@babel/helper-module-transforms': 7.20.11
-      '@babel/helper-plugin-utils': 7.20.2
-      '@babel/helper-validator-identifier': 7.19.1
-    transitivePeerDependencies:
-      - supports-color
-    dev: false
 
-  /@babel/plugin-transform-modules-umd@7.18.6(@babel/core@7.20.12):
-    resolution: {integrity: sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==}
+  '@babel/plugin-transform-object-super@7.23.3':
+    resolution: {integrity: sha512-BwQ8q0x2JG+3lxCVFohg+KbQM7plfpBwThdW9A6TMtWwLsbDA01Ek2Zb/AgDN39BiZsExm4qrXxjk+P1/fzGrA==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-module-transforms': 7.20.11
-      '@babel/helper-plugin-utils': 7.20.2
-    transitivePeerDependencies:
-      - supports-color
-    dev: false
 
-  /@babel/plugin-transform-named-capturing-groups-regex@7.20.5(@babel/core@7.20.12):
-    resolution: {integrity: sha512-mOW4tTzi5iTLnw+78iEq3gr8Aoq4WNRGpmSlrogqaiCBoR1HFhpU4JkpQFOHfeYx3ReVIFWOQJS4aZBRvuZ6mA==}
+  '@babel/plugin-transform-optional-catch-binding@7.23.4':
+    resolution: {integrity: sha512-XIq8t0rJPHf6Wvmbn9nFxU6ao4c7WhghTR5WyV8SrJfUFzyxhCm4nhC+iAp3HFhbAKLfYpgzhJ6t4XCtVwqO5A==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
-      '@babel/core': ^7.0.0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-create-regexp-features-plugin': 7.20.5(@babel/core@7.20.12)
-      '@babel/helper-plugin-utils': 7.20.2
-    dev: false
+      '@babel/core': ^7.0.0-0
 
-  /@babel/plugin-transform-new-target@7.18.6(@babel/core@7.20.12):
-    resolution: {integrity: sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==}
+  '@babel/plugin-transform-optional-chaining@7.23.4':
+    resolution: {integrity: sha512-ZU8y5zWOfjM5vZ+asjgAPwDaBjJzgufjES89Rs4Lpq63O300R/kOz30WCLo6BxxX6QVEilwSlpClnG5cZaikTA==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-plugin-utils': 7.20.2
-    dev: false
 
-  /@babel/plugin-transform-object-super@7.18.6(@babel/core@7.20.12):
-    resolution: {integrity: sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==}
+  '@babel/plugin-transform-parameters@7.23.3':
+    resolution: {integrity: sha512-09lMt6UsUb3/34BbECKVbVwrT9bO6lILWln237z7sLaWnMsTi7Yc9fhX5DLpkJzAGfaReXI22wP41SZmnAA3Vw==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-plugin-utils': 7.20.2
-      '@babel/helper-replace-supers': 7.20.7
-    transitivePeerDependencies:
-      - supports-color
-    dev: false
 
-  /@babel/plugin-transform-parameters@7.20.7(@babel/core@7.12.9):
-    resolution: {integrity: sha512-WiWBIkeHKVOSYPO0pWkxGPfKeWrCJyD3NJ53+Lrp/QMSZbsVPovrVl2aWZ19D/LTVnaDv5Ap7GJ/B2CTOZdrfA==}
+  '@babel/plugin-transform-private-methods@7.23.3':
+    resolution: {integrity: sha512-UzqRcRtWsDMTLrRWFvUBDwmw06tCQH9Rl1uAjfh6ijMSmGYQ+fpdB+cnqRC8EMh5tuuxSv0/TejGL+7vyj+50g==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.12.9
-      '@babel/helper-plugin-utils': 7.20.2
-    dev: false
 
-  /@babel/plugin-transform-parameters@7.20.7(@babel/core@7.20.12):
-    resolution: {integrity: sha512-WiWBIkeHKVOSYPO0pWkxGPfKeWrCJyD3NJ53+Lrp/QMSZbsVPovrVl2aWZ19D/LTVnaDv5Ap7GJ/B2CTOZdrfA==}
+  '@babel/plugin-transform-private-property-in-object@7.23.4':
+    resolution: {integrity: sha512-9G3K1YqTq3F4Vt88Djx1UZ79PDyj+yKRnUy7cZGSMe+a7jkwD259uKKuUzQlPkGam7R+8RJwh5z4xO27fA1o2A==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-plugin-utils': 7.20.2
-    dev: false
 
-  /@babel/plugin-transform-property-literals@7.18.6(@babel/core@7.20.12):
-    resolution: {integrity: sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==}
+  '@babel/plugin-transform-property-literals@7.23.3':
+    resolution: {integrity: sha512-jR3Jn3y7cZp4oEWPFAlRsSWjxKe4PZILGBSd4nis1TsC5qeSpb+nrtihJuDhNI7QHiVbUaiXa0X2RZY3/TI6Nw==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-plugin-utils': 7.20.2
-    dev: false
 
-  /@babel/plugin-transform-react-constant-elements@7.20.2(@babel/core@7.20.12):
-    resolution: {integrity: sha512-KS/G8YI8uwMGKErLFOHS/ekhqdHhpEloxs43NecQHVgo2QuQSyJhGIY1fL8UGl9wy5ItVwwoUL4YxVqsplGq2g==}
+  '@babel/plugin-transform-react-constant-elements@7.24.7':
+    resolution: {integrity: sha512-7LidzZfUXyfZ8/buRW6qIIHBY8wAZ1OrY9c/wTr8YhZ6vMPo+Uc/CVFLYY1spZrEQlD4w5u8wjqk5NQ3OVqQKA==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-plugin-utils': 7.20.2
-    dev: false
 
-  /@babel/plugin-transform-react-display-name@7.18.6(@babel/core@7.20.12):
-    resolution: {integrity: sha512-TV4sQ+T013n61uMoygyMRm+xf04Bd5oqFpv2jAEQwSZ8NwQA7zeRPg1LMVg2PWi3zWBz+CLKD+v5bcpZ/BS0aA==}
+  '@babel/plugin-transform-react-display-name@7.23.3':
+    resolution: {integrity: sha512-GnvhtVfA2OAtzdX58FJxU19rhoGeQzyVndw3GgtdECQvQFXPEZIOVULHVZGAYmOgmqjXpVpfocAbSjh99V/Fqw==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-plugin-utils': 7.20.2
-    dev: false
 
-  /@babel/plugin-transform-react-jsx-development@7.18.6(@babel/core@7.20.12):
-    resolution: {integrity: sha512-SA6HEjwYFKF7WDjWcMcMGUimmw/nhNRDWxr+KaLSCrkD/LMDBvWRmHAYgE1HDeF8KUuI8OAu+RT6EOtKxSW2qA==}
+  '@babel/plugin-transform-react-jsx-development@7.22.5':
+    resolution: {integrity: sha512-bDhuzwWMuInwCYeDeMzyi7TaBgRQei6DqxhbyniL7/VG4RSS7HtSL2QbY4eESy1KJqlWt8g3xeEBGPuo+XqC8A==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/plugin-transform-react-jsx': 7.20.7(@babel/core@7.20.12)
-    dev: false
 
-  /@babel/plugin-transform-react-jsx@7.20.7(@babel/core@7.20.12):
-    resolution: {integrity: sha512-Tfq7qqD+tRj3EoDhY00nn2uP2hsRxgYGi5mLQ5TimKav0a9Lrpd4deE+fcLXU8zFYRjlKPHZhpCvfEA6qnBxqQ==}
+  '@babel/plugin-transform-react-jsx@7.23.4':
+    resolution: {integrity: sha512-5xOpoPguCZCRbo/JeHlloSkTA8Bld1J/E1/kLfD1nsuiW1m8tduTA1ERCgIZokDflX/IBzKcqR3l7VlRgiIfHA==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-annotate-as-pure': 7.18.6
-      '@babel/helper-module-imports': 7.18.6
-      '@babel/helper-plugin-utils': 7.20.2
-      '@babel/plugin-syntax-jsx': 7.18.6(@babel/core@7.20.12)
-      '@babel/types': 7.20.7
-    dev: false
 
-  /@babel/plugin-transform-react-pure-annotations@7.18.6(@babel/core@7.20.12):
-    resolution: {integrity: sha512-I8VfEPg9r2TRDdvnHgPepTKvuRomzA8+u+nhY7qSI1fR2hRNebasZEETLyM5mAUr0Ku56OkXJ0I7NHJnO6cJiQ==}
+  '@babel/plugin-transform-react-pure-annotations@7.23.3':
+    resolution: {integrity: sha512-qMFdSS+TUhB7Q/3HVPnEdYJDQIk57jkntAwSuz9xfSE4n+3I+vHYCli3HoHawN1Z3RfCz/y1zXA/JXjG6cVImQ==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-annotate-as-pure': 7.18.6
-      '@babel/helper-plugin-utils': 7.20.2
-    dev: false
 
-  /@babel/plugin-transform-regenerator@7.20.5(@babel/core@7.20.12):
-    resolution: {integrity: sha512-kW/oO7HPBtntbsahzQ0qSE3tFvkFwnbozz3NWFhLGqH75vLEg+sCGngLlhVkePlCs3Jv0dBBHDzCHxNiFAQKCQ==}
+  '@babel/plugin-transform-regenerator@7.23.3':
+    resolution: {integrity: sha512-KP+75h0KghBMcVpuKisx3XTu9Ncut8Q8TuvGO4IhY+9D5DFEckQefOuIsB/gQ2tG71lCke4NMrtIPS8pOj18BQ==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-plugin-utils': 7.20.2
-      regenerator-transform: 0.15.1
-    dev: false
 
-  /@babel/plugin-transform-reserved-words@7.18.6(@babel/core@7.20.12):
-    resolution: {integrity: sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==}
+  '@babel/plugin-transform-reserved-words@7.23.3':
+    resolution: {integrity: sha512-QnNTazY54YqgGxwIexMZva9gqbPa15t/x9VS+0fsEFWplwVpXYZivtgl43Z1vMpc1bdPP2PP8siFeVcnFvA3Cg==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-plugin-utils': 7.20.2
-    dev: false
 
-  /@babel/plugin-transform-runtime@7.19.6(@babel/core@7.20.12):
-    resolution: {integrity: sha512-PRH37lz4JU156lYFW1p8OxE5i7d6Sl/zV58ooyr+q1J1lnQPyg5tIiXlIwNVhJaY4W3TmOtdc8jqdXQcB1v5Yw==}
+  '@babel/plugin-transform-runtime@7.23.9':
+    resolution: {integrity: sha512-A7clW3a0aSjm3ONU9o2HAILSegJCYlEZmOhmBRReVtIpY/Z/p7yIZ+wR41Z+UipwdGuqwtID/V/dOdZXjwi9gQ==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-module-imports': 7.18.6
-      '@babel/helper-plugin-utils': 7.20.2
-      babel-plugin-polyfill-corejs2: 0.3.3(@babel/core@7.20.12)
-      babel-plugin-polyfill-corejs3: 0.6.0(@babel/core@7.20.12)
-      babel-plugin-polyfill-regenerator: 0.4.1(@babel/core@7.20.12)
-      semver: 6.3.0
-    transitivePeerDependencies:
-      - supports-color
-    dev: false
 
-  /@babel/plugin-transform-shorthand-properties@7.18.6(@babel/core@7.20.12):
-    resolution: {integrity: sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==}
+  '@babel/plugin-transform-shorthand-properties@7.23.3':
+    resolution: {integrity: sha512-ED2fgqZLmexWiN+YNFX26fx4gh5qHDhn1O2gvEhreLW2iI63Sqm4llRLCXALKrCnbN4Jy0VcMQZl/SAzqug/jg==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-plugin-utils': 7.20.2
-    dev: false
 
-  /@babel/plugin-transform-spread@7.20.7(@babel/core@7.20.12):
-    resolution: {integrity: sha512-ewBbHQ+1U/VnH1fxltbJqDeWBU1oNLG8Dj11uIv3xVf7nrQu0bPGe5Rf716r7K5Qz+SqtAOVswoVunoiBtGhxw==}
+  '@babel/plugin-transform-spread@7.23.3':
+    resolution: {integrity: sha512-VvfVYlrlBVu+77xVTOAoxQ6mZbnIq5FM0aGBSFEcIh03qHf+zNqA4DC/3XMUozTg7bZV3e3mZQ0i13VB6v5yUg==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-plugin-utils': 7.20.2
-      '@babel/helper-skip-transparent-expression-wrappers': 7.20.0
-    dev: false
 
-  /@babel/plugin-transform-sticky-regex@7.18.6(@babel/core@7.20.12):
-    resolution: {integrity: sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==}
+  '@babel/plugin-transform-sticky-regex@7.23.3':
+    resolution: {integrity: sha512-HZOyN9g+rtvnOU3Yh7kSxXrKbzgrm5X4GncPY1QOquu7epga5MxKHVpYu2hvQnry/H+JjckSYRb93iNfsioAGg==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-plugin-utils': 7.20.2
-    dev: false
 
-  /@babel/plugin-transform-template-literals@7.18.9(@babel/core@7.20.12):
-    resolution: {integrity: sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==}
+  '@babel/plugin-transform-template-literals@7.23.3':
+    resolution: {integrity: sha512-Flok06AYNp7GV2oJPZZcP9vZdszev6vPBkHLwxwSpaIqx75wn6mUd3UFWsSsA0l8nXAKkyCmL/sR02m8RYGeHg==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-plugin-utils': 7.20.2
-    dev: false
 
-  /@babel/plugin-transform-typeof-symbol@7.18.9(@babel/core@7.20.12):
-    resolution: {integrity: sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==}
+  '@babel/plugin-transform-typeof-symbol@7.23.3':
+    resolution: {integrity: sha512-4t15ViVnaFdrPC74be1gXBSMzXk3B4Us9lP7uLRQHTFpV5Dvt33pn+2MyyNxmN3VTTm3oTrZVMUmuw3oBnQ2oQ==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-plugin-utils': 7.20.2
-    dev: false
 
-  /@babel/plugin-transform-typescript@7.20.7(@babel/core@7.20.12):
-    resolution: {integrity: sha512-m3wVKEvf6SoszD8pu4NZz3PvfKRCMgk6D6d0Qi9hNnlM5M6CFS92EgF4EiHVLKbU0r/r7ty1hg7NPZwE7WRbYw==}
+  '@babel/plugin-transform-typescript@7.23.6':
+    resolution: {integrity: sha512-6cBG5mBvUu4VUD04OHKnYzbuHNP8huDsD3EDqqpIpsswTDoqHCjLoHb6+QgsV1WsT2nipRqCPgxD3LXnEO7XfA==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-create-class-features-plugin': 7.20.12(@babel/core@7.20.12)
-      '@babel/helper-plugin-utils': 7.20.2
-      '@babel/plugin-syntax-typescript': 7.20.0(@babel/core@7.20.12)
-    transitivePeerDependencies:
-      - supports-color
-    dev: false
 
-  /@babel/plugin-transform-unicode-escapes@7.18.10(@babel/core@7.20.12):
-    resolution: {integrity: sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ==}
+  '@babel/plugin-transform-unicode-escapes@7.23.3':
+    resolution: {integrity: sha512-OMCUx/bU6ChE3r4+ZdylEqAjaQgHAgipgW8nsCfu5pGqDcFytVd91AwRvUJSBZDz0exPGgnjoqhgRYLRjFZc9Q==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-plugin-utils': 7.20.2
-    dev: false
 
-  /@babel/plugin-transform-unicode-regex@7.18.6(@babel/core@7.20.12):
-    resolution: {integrity: sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==}
+  '@babel/plugin-transform-unicode-property-regex@7.23.3':
+    resolution: {integrity: sha512-KcLIm+pDZkWZQAFJ9pdfmh89EwVfmNovFBcXko8szpBeF8z68kWIPeKlmSOkT9BXJxs2C0uk+5LxoxIv62MROA==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-create-regexp-features-plugin': 7.20.5(@babel/core@7.20.12)
-      '@babel/helper-plugin-utils': 7.20.2
-    dev: false
 
-  /@babel/preset-env@7.20.2(@babel/core@7.20.12):
-    resolution: {integrity: sha512-1G0efQEWR1EHkKvKHqbG+IN/QdgwfByUpM5V5QroDzGV2t3S/WXNQd693cHiHTlCFMpr9B6FkPFXDA2lQcKoDg==}
+  '@babel/plugin-transform-unicode-regex@7.23.3':
+    resolution: {integrity: sha512-wMHpNA4x2cIA32b/ci3AfwNgheiva2W0WUKWTK7vBHBhDKfPsc5cFGNWm69WBqpwd86u1qwZ9PWevKqm1A3yAw==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/compat-data': 7.20.10
-      '@babel/core': 7.20.12
-      '@babel/helper-compilation-targets': 7.20.7(@babel/core@7.20.12)
-      '@babel/helper-plugin-utils': 7.20.2
-      '@babel/helper-validator-option': 7.18.6
-      '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.18.6(@babel/core@7.20.12)
-      '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.20.7(@babel/core@7.20.12)
-      '@babel/plugin-proposal-async-generator-functions': 7.20.7(@babel/core@7.20.12)
-      '@babel/plugin-proposal-class-properties': 7.18.6(@babel/core@7.20.12)
-      '@babel/plugin-proposal-class-static-block': 7.20.7(@babel/core@7.20.12)
-      '@babel/plugin-proposal-dynamic-import': 7.18.6(@babel/core@7.20.12)
-      '@babel/plugin-proposal-export-namespace-from': 7.18.9(@babel/core@7.20.12)
-      '@babel/plugin-proposal-json-strings': 7.18.6(@babel/core@7.20.12)
-      '@babel/plugin-proposal-logical-assignment-operators': 7.20.7(@babel/core@7.20.12)
-      '@babel/plugin-proposal-nullish-coalescing-operator': 7.18.6(@babel/core@7.20.12)
-      '@babel/plugin-proposal-numeric-separator': 7.18.6(@babel/core@7.20.12)
-      '@babel/plugin-proposal-object-rest-spread': 7.20.7(@babel/core@7.20.12)
-      '@babel/plugin-proposal-optional-catch-binding': 7.18.6(@babel/core@7.20.12)
-      '@babel/plugin-proposal-optional-chaining': 7.20.7(@babel/core@7.20.12)
-      '@babel/plugin-proposal-private-methods': 7.18.6(@babel/core@7.20.12)
-      '@babel/plugin-proposal-private-property-in-object': 7.20.5(@babel/core@7.20.12)
-      '@babel/plugin-proposal-unicode-property-regex': 7.18.6(@babel/core@7.20.12)
-      '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.20.12)
-      '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.20.12)
-      '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.20.12)
-      '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.20.12)
-      '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.20.12)
-      '@babel/plugin-syntax-import-assertions': 7.20.0(@babel/core@7.20.12)
-      '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.20.12)
-      '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.20.12)
-      '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.20.12)
-      '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.20.12)
-      '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.20.12)
-      '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.20.12)
-      '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.20.12)
-      '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.20.12)
-      '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.20.12)
-      '@babel/plugin-transform-arrow-functions': 7.20.7(@babel/core@7.20.12)
-      '@babel/plugin-transform-async-to-generator': 7.20.7(@babel/core@7.20.12)
-      '@babel/plugin-transform-block-scoped-functions': 7.18.6(@babel/core@7.20.12)
-      '@babel/plugin-transform-block-scoping': 7.20.11(@babel/core@7.20.12)
-      '@babel/plugin-transform-classes': 7.20.7(@babel/core@7.20.12)
-      '@babel/plugin-transform-computed-properties': 7.20.7(@babel/core@7.20.12)
-      '@babel/plugin-transform-destructuring': 7.20.7(@babel/core@7.20.12)
-      '@babel/plugin-transform-dotall-regex': 7.18.6(@babel/core@7.20.12)
-      '@babel/plugin-transform-duplicate-keys': 7.18.9(@babel/core@7.20.12)
-      '@babel/plugin-transform-exponentiation-operator': 7.18.6(@babel/core@7.20.12)
-      '@babel/plugin-transform-for-of': 7.18.8(@babel/core@7.20.12)
-      '@babel/plugin-transform-function-name': 7.18.9(@babel/core@7.20.12)
-      '@babel/plugin-transform-literals': 7.18.9(@babel/core@7.20.12)
-      '@babel/plugin-transform-member-expression-literals': 7.18.6(@babel/core@7.20.12)
-      '@babel/plugin-transform-modules-amd': 7.20.11(@babel/core@7.20.12)
-      '@babel/plugin-transform-modules-commonjs': 7.20.11(@babel/core@7.20.12)
-      '@babel/plugin-transform-modules-systemjs': 7.20.11(@babel/core@7.20.12)
-      '@babel/plugin-transform-modules-umd': 7.18.6(@babel/core@7.20.12)
-      '@babel/plugin-transform-named-capturing-groups-regex': 7.20.5(@babel/core@7.20.12)
-      '@babel/plugin-transform-new-target': 7.18.6(@babel/core@7.20.12)
-      '@babel/plugin-transform-object-super': 7.18.6(@babel/core@7.20.12)
-      '@babel/plugin-transform-parameters': 7.20.7(@babel/core@7.20.12)
-      '@babel/plugin-transform-property-literals': 7.18.6(@babel/core@7.20.12)
-      '@babel/plugin-transform-regenerator': 7.20.5(@babel/core@7.20.12)
-      '@babel/plugin-transform-reserved-words': 7.18.6(@babel/core@7.20.12)
-      '@babel/plugin-transform-shorthand-properties': 7.18.6(@babel/core@7.20.12)
-      '@babel/plugin-transform-spread': 7.20.7(@babel/core@7.20.12)
-      '@babel/plugin-transform-sticky-regex': 7.18.6(@babel/core@7.20.12)
-      '@babel/plugin-transform-template-literals': 7.18.9(@babel/core@7.20.12)
-      '@babel/plugin-transform-typeof-symbol': 7.18.9(@babel/core@7.20.12)
-      '@babel/plugin-transform-unicode-escapes': 7.18.10(@babel/core@7.20.12)
-      '@babel/plugin-transform-unicode-regex': 7.18.6(@babel/core@7.20.12)
-      '@babel/preset-modules': 0.1.5(@babel/core@7.20.12)
-      '@babel/types': 7.20.7
-      babel-plugin-polyfill-corejs2: 0.3.3(@babel/core@7.20.12)
-      babel-plugin-polyfill-corejs3: 0.6.0(@babel/core@7.20.12)
-      babel-plugin-polyfill-regenerator: 0.4.1(@babel/core@7.20.12)
-      core-js-compat: 3.27.1
-      semver: 6.3.0
-    transitivePeerDependencies:
-      - supports-color
-    dev: false
 
-  /@babel/preset-modules@0.1.5(@babel/core@7.20.12):
-    resolution: {integrity: sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==}
+  '@babel/plugin-transform-unicode-sets-regex@7.23.3':
+    resolution: {integrity: sha512-W7lliA/v9bNR83Qc3q1ip9CQMZ09CcHDbHfbLRDNuAhn1Mvkr1ZNF7hPmztMQvtTGVLJ9m8IZqWsTkXOml8dbw==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0
+
+  '@babel/preset-env@7.23.9':
+    resolution: {integrity: sha512-3kBGTNBBk9DQiPoXYS0g0BYlwTQYUTifqgKTjxUwEUkduRT2QOa0FPGBJ+NROQhGyYO5BuTJwGvBnqKDykac6A==}
+    engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-plugin-utils': 7.20.2
-      '@babel/plugin-proposal-unicode-property-regex': 7.18.6(@babel/core@7.20.12)
-      '@babel/plugin-transform-dotall-regex': 7.18.6(@babel/core@7.20.12)
-      '@babel/types': 7.20.7
-      esutils: 2.0.3
-    dev: false
 
-  /@babel/preset-react@7.18.6(@babel/core@7.20.12):
-    resolution: {integrity: sha512-zXr6atUmyYdiWRVLOZahakYmOBHtWc2WGCkP8PYTgZi0iJXDY2CN180TdrIW4OGOAdLc7TifzDIvtx6izaRIzg==}
+  '@babel/preset-modules@0.1.6-no-external-plugins':
+    resolution: {integrity: sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0 || ^8.0.0-0 <8.0.0
+
+  '@babel/preset-react@7.23.3':
+    resolution: {integrity: sha512-tbkHOS9axH6Ysf2OUEqoSZ6T3Fa2SrNH6WTWSPBboxKzdxNc9qOICeLXkNG0ZEwbQ1HY8liwOce4aN/Ceyuq6w==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-plugin-utils': 7.20.2
-      '@babel/helper-validator-option': 7.18.6
-      '@babel/plugin-transform-react-display-name': 7.18.6(@babel/core@7.20.12)
-      '@babel/plugin-transform-react-jsx': 7.20.7(@babel/core@7.20.12)
-      '@babel/plugin-transform-react-jsx-development': 7.18.6(@babel/core@7.20.12)
-      '@babel/plugin-transform-react-pure-annotations': 7.18.6(@babel/core@7.20.12)
-    dev: false
-
-  /@babel/preset-typescript@7.18.6(@babel/core@7.20.12):
-    resolution: {integrity: sha512-s9ik86kXBAnD760aybBucdpnLsAt0jK1xqJn2juOn9lkOvSHV60os5hxoVJsPzMQxvnUJFAlkont2DvvaYEBtQ==}
+
+  '@babel/preset-typescript@7.23.3':
+    resolution: {integrity: sha512-17oIGVlqz6CchO9RFYn5U6ZpWRZIngayYCtrPRSgANSwC2V1Jb+iP74nVxzzXJte8b8BYxrL1yY96xfhTBrNNQ==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-plugin-utils': 7.20.2
-      '@babel/helper-validator-option': 7.18.6
-      '@babel/plugin-transform-typescript': 7.20.7(@babel/core@7.20.12)
-    transitivePeerDependencies:
-      - supports-color
-    dev: false
 
-  /@babel/runtime-corejs3@7.20.7:
-    resolution: {integrity: sha512-jr9lCZ4RbRQmCR28Q8U8Fu49zvFqLxTY9AMOUz+iyMohMoAgpEcVxY+wJNay99oXOpOcCTODkk70NDN2aaJEeg==}
+  '@babel/regjsgen@0.8.0':
+    resolution: {integrity: sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==}
+
+  '@babel/runtime-corejs3@7.23.9':
+    resolution: {integrity: sha512-oeOFTrYWdWXCvXGB5orvMTJ6gCZ9I6FBjR+M38iKNXCsPxr4xT0RTdg5uz1H7QP8pp74IzPtwritEr+JscqHXQ==}
     engines: {node: '>=6.9.0'}
-    dependencies:
-      core-js-pure: 3.27.1
-      regenerator-runtime: 0.13.11
-    dev: false
 
-  /@babel/runtime@7.20.7:
-    resolution: {integrity: sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==}
+  '@babel/runtime@7.23.9':
+    resolution: {integrity: sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==}
     engines: {node: '>=6.9.0'}
-    dependencies:
-      regenerator-runtime: 0.13.11
 
-  /@babel/template@7.20.7:
-    resolution: {integrity: sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==}
+  '@babel/template@7.23.9':
+    resolution: {integrity: sha512-+xrD2BWLpvHKNmX2QbpdpsBaWnRxahMwJjO+KZk2JOElj5nSmKezyS1B4u+QbHMTX69t4ukm6hh9lsYQ7GHCKA==}
     engines: {node: '>=6.9.0'}
-    dependencies:
-      '@babel/code-frame': 7.18.6
-      '@babel/parser': 7.20.7
-      '@babel/types': 7.20.7
-    dev: false
 
-  /@babel/traverse@7.20.12:
-    resolution: {integrity: sha512-MsIbFN0u+raeja38qboyF8TIT7K0BFzz/Yd/77ta4MsUsmP2RAnidIlwq7d5HFQrH/OZJecGV6B71C4zAgpoSQ==}
+  '@babel/traverse@7.23.9':
+    resolution: {integrity: sha512-I/4UJ9vs90OkBtY6iiiTORVMyIhJ4kAVmsKo9KFc8UOxMeUfi2hvtIBsET5u9GizXE6/GFSuKCTNfgCswuEjRg==}
     engines: {node: '>=6.9.0'}
-    dependencies:
-      '@babel/code-frame': 7.18.6
-      '@babel/generator': 7.20.7
-      '@babel/helper-environment-visitor': 7.18.9
-      '@babel/helper-function-name': 7.19.0
-      '@babel/helper-hoist-variables': 7.18.6
-      '@babel/helper-split-export-declaration': 7.18.6
-      '@babel/parser': 7.20.7
-      '@babel/types': 7.20.7
-      debug: 4.3.4
-      globals: 11.12.0
-    transitivePeerDependencies:
-      - supports-color
-    dev: false
 
-  /@babel/types@7.20.7:
-    resolution: {integrity: sha512-69OnhBxSSgK0OzTJai4kyPDiKTIe3j+ctaHdIGVbRahTLAT7L3R9oeXHC2aVSuGYt3cVnoAMDmOCgJ2yaiLMvg==}
+  '@babel/types@7.23.9':
+    resolution: {integrity: sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==}
     engines: {node: '>=6.9.0'}
-    dependencies:
-      '@babel/helper-string-parser': 7.19.4
-      '@babel/helper-validator-identifier': 7.19.1
-      to-fast-properties: 2.0.0
-    dev: false
 
-  /@braintree/sanitize-url@6.0.4:
+  '@braintree/sanitize-url@6.0.4':
     resolution: {integrity: sha512-s3jaWicZd0pkP0jf5ysyHUI/RE7MHos6qlToFcGWXVp+ykHOy77OUMrfbgJ9it2C5bow7OIQwYYaHjk9XlBQ2A==}
-    dev: false
 
-  /@colors/colors@1.5.0:
+  '@colors/colors@1.5.0':
     resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==}
     engines: {node: '>=0.1.90'}
-    requiresBuild: true
-    dev: false
-    optional: true
 
-  /@docsearch/css@3.3.1:
-    resolution: {integrity: sha512-nznHXeFHpAYjyaSNFNFpU+IJPjQA7AINM8ONjDx/Zx4O/pGAvqwgmcLNc7zR8qXRutqnzLo06yN63xFn36KFBw==}
-    dev: false
+  '@discoveryjs/json-ext@0.5.7':
+    resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==}
+    engines: {node: '>=10.0.0'}
+
+  '@docsearch/css@3.5.2':
+    resolution: {integrity: sha512-SPiDHaWKQZpwR2siD0KQUwlStvIAnEyK6tAE2h2Wuoq8ue9skzhlyVQ1ddzOxX6khULnAALDiR/isSF3bnuciA==}
 
-  /@docsearch/react@3.3.1(@algolia/client-search@4.14.3)(@types/react@18.0.26)(react-dom@17.0.2)(react@17.0.2):
-    resolution: {integrity: sha512-wdeQBODPkue6yVEEg4ntt+TiGJ6iXMBUNjBQJ0s1WVoc1OdcCnks/lkQ5LEfXETYR/q9QSbCCBnMjvnSoILaag==}
+  '@docsearch/react@3.5.2':
+    resolution: {integrity: sha512-9Ahcrs5z2jq/DcAvYtvlqEBHImbm4YJI8M9y0x6Tqg598P40HTEkX7hsMcIuThI+hTFxRGZ9hll0Wygm2yEjng==}
     peerDependencies:
       '@types/react': '>= 16.8.0 < 19.0.0'
       react: '>= 16.8.0 < 19.0.0'
       react-dom: '>= 16.8.0 < 19.0.0'
+      search-insights: '>= 1 < 3'
     peerDependenciesMeta:
       '@types/react':
         optional: true
@@ -1596,102 +832,5327 @@ packages:
         optional: true
       react-dom:
         optional: true
+      search-insights:
+        optional: true
+
+  '@docusaurus/core@3.4.0':
+    resolution: {integrity: sha512-g+0wwmN2UJsBqy2fQRQ6fhXruoEa62JDeEa5d8IdTJlMoaDaEDfHh7WjwGRn4opuTQWpjAwP/fbcgyHKlE+64w==}
+    engines: {node: '>=18.0'}
+    hasBin: true
+    peerDependencies:
+      react: ^18.0.0
+      react-dom: ^18.0.0
+
+  '@docusaurus/cssnano-preset@3.4.0':
+    resolution: {integrity: sha512-qwLFSz6v/pZHy/UP32IrprmH5ORce86BGtN0eBtG75PpzQJAzp9gefspox+s8IEOr0oZKuQ/nhzZ3xwyc3jYJQ==}
+    engines: {node: '>=18.0'}
+
+  '@docusaurus/logger@3.4.0':
+    resolution: {integrity: sha512-bZwkX+9SJ8lB9kVRkXw+xvHYSMGG4bpYHKGXeXFvyVc79NMeeBSGgzd4TQLHH+DYeOJoCdl8flrFJVxlZ0wo/Q==}
+    engines: {node: '>=18.0'}
+
+  '@docusaurus/mdx-loader@3.4.0':
+    resolution: {integrity: sha512-kSSbrrk4nTjf4d+wtBA9H+FGauf2gCax89kV8SUSJu3qaTdSIKdWERlngsiHaCFgZ7laTJ8a67UFf+xlFPtuTw==}
+    engines: {node: '>=18.0'}
+    peerDependencies:
+      react: ^18.0.0
+      react-dom: ^18.0.0
+
+  '@docusaurus/module-type-aliases@3.4.0':
+    resolution: {integrity: sha512-A1AyS8WF5Bkjnb8s+guTDuYmUiwJzNrtchebBHpc0gz0PyHJNMaybUlSrmJjHVcGrya0LKI4YcR3lBDQfXRYLw==}
+    peerDependencies:
+      react: '*'
+      react-dom: '*'
+
+  '@docusaurus/plugin-content-blog@3.4.0':
+    resolution: {integrity: sha512-vv6ZAj78ibR5Jh7XBUT4ndIjmlAxkijM3Sx5MAAzC1gyv0vupDQNhzuFg1USQmQVj3P5I6bquk12etPV3LJ+Xw==}
+    engines: {node: '>=18.0'}
+    peerDependencies:
+      react: ^18.0.0
+      react-dom: ^18.0.0
+
+  '@docusaurus/plugin-content-docs@3.4.0':
+    resolution: {integrity: sha512-HkUCZffhBo7ocYheD9oZvMcDloRnGhBMOZRyVcAQRFmZPmNqSyISlXA1tQCIxW+r478fty97XXAGjNYzBjpCsg==}
+    engines: {node: '>=18.0'}
+    peerDependencies:
+      react: ^18.0.0
+      react-dom: ^18.0.0
+
+  '@docusaurus/plugin-content-pages@3.4.0':
+    resolution: {integrity: sha512-h2+VN/0JjpR8fIkDEAoadNjfR3oLzB+v1qSXbIAKjQ46JAHx3X22n9nqS+BWSQnTnp1AjkjSvZyJMekmcwxzxg==}
+    engines: {node: '>=18.0'}
+    peerDependencies:
+      react: ^18.0.0
+      react-dom: ^18.0.0
+
+  '@docusaurus/plugin-debug@3.4.0':
+    resolution: {integrity: sha512-uV7FDUNXGyDSD3PwUaf5YijX91T5/H9SX4ErEcshzwgzWwBtK37nUWPU3ZLJfeTavX3fycTOqk9TglpOLaWkCg==}
+    engines: {node: '>=18.0'}
+    peerDependencies:
+      react: ^18.0.0
+      react-dom: ^18.0.0
+
+  '@docusaurus/plugin-google-analytics@3.4.0':
+    resolution: {integrity: sha512-mCArluxEGi3cmYHqsgpGGt3IyLCrFBxPsxNZ56Mpur0xSlInnIHoeLDH7FvVVcPJRPSQ9/MfRqLsainRw+BojA==}
+    engines: {node: '>=18.0'}
+    peerDependencies:
+      react: ^18.0.0
+      react-dom: ^18.0.0
+
+  '@docusaurus/plugin-google-gtag@3.4.0':
+    resolution: {integrity: sha512-Dsgg6PLAqzZw5wZ4QjUYc8Z2KqJqXxHxq3vIoyoBWiLEEfigIs7wHR+oiWUQy3Zk9MIk6JTYj7tMoQU0Jm3nqA==}
+    engines: {node: '>=18.0'}
+    peerDependencies:
+      react: ^18.0.0
+      react-dom: ^18.0.0
+
+  '@docusaurus/plugin-google-tag-manager@3.4.0':
+    resolution: {integrity: sha512-O9tX1BTwxIhgXpOLpFDueYA9DWk69WCbDRrjYoMQtFHSkTyE7RhNgyjSPREUWJb9i+YUg3OrsvrBYRl64FCPCQ==}
+    engines: {node: '>=18.0'}
+    peerDependencies:
+      react: ^18.0.0
+      react-dom: ^18.0.0
+
+  '@docusaurus/plugin-sitemap@3.4.0':
+    resolution: {integrity: sha512-+0VDvx9SmNrFNgwPoeoCha+tRoAjopwT0+pYO1xAbyLcewXSemq+eLxEa46Q1/aoOaJQ0qqHELuQM7iS2gp33Q==}
+    engines: {node: '>=18.0'}
+    peerDependencies:
+      react: ^18.0.0
+      react-dom: ^18.0.0
+
+  '@docusaurus/preset-classic@3.4.0':
+    resolution: {integrity: sha512-Ohj6KB7siKqZaQhNJVMBBUzT3Nnp6eTKqO+FXO3qu/n1hJl3YLwVKTWBg28LF7MWrKu46UuYavwMRxud0VyqHg==}
+    engines: {node: '>=18.0'}
+    peerDependencies:
+      react: ^18.0.0
+      react-dom: ^18.0.0
+
+  '@docusaurus/react-loadable@6.0.0':
+    resolution: {integrity: sha512-YMMxTUQV/QFSnbgrP3tjDzLHRg7vsbMn8e9HAa8o/1iXoiomo48b7sk/kkmWEuWNDPJVlKSJRB6Y2fHqdJk+SQ==}
+    peerDependencies:
+      react: '*'
+
+  '@docusaurus/theme-classic@3.4.0':
+    resolution: {integrity: sha512-0IPtmxsBYv2adr1GnZRdMkEQt1YW6tpzrUPj02YxNpvJ5+ju4E13J5tB4nfdaen/tfR1hmpSPlTFPvTf4kwy8Q==}
+    engines: {node: '>=18.0'}
+    peerDependencies:
+      react: ^18.0.0
+      react-dom: ^18.0.0
+
+  '@docusaurus/theme-common@3.4.0':
+    resolution: {integrity: sha512-0A27alXuv7ZdCg28oPE8nH/Iz73/IUejVaCazqu9elS4ypjiLhK3KfzdSQBnL/g7YfHSlymZKdiOHEo8fJ0qMA==}
+    engines: {node: '>=18.0'}
+    peerDependencies:
+      react: ^18.0.0
+      react-dom: ^18.0.0
+
+  '@docusaurus/theme-mermaid@3.4.0':
+    resolution: {integrity: sha512-3w5QW0HEZ2O6x2w6lU3ZvOe1gNXP2HIoKDMJBil1VmLBc9PmpAG17VmfhI/p3L2etNmOiVs5GgniUqvn8AFEGQ==}
+    engines: {node: '>=18.0'}
+    peerDependencies:
+      react: ^18.0.0
+      react-dom: ^18.0.0
+
+  '@docusaurus/theme-search-algolia@3.4.0':
+    resolution: {integrity: sha512-aiHFx7OCw4Wck1z6IoShVdUWIjntC8FHCw9c5dR8r3q4Ynh+zkS8y2eFFunN/DL6RXPzpnvKCg3vhLQYJDmT9Q==}
+    engines: {node: '>=18.0'}
+    peerDependencies:
+      react: ^18.0.0
+      react-dom: ^18.0.0
+
+  '@docusaurus/theme-translations@3.4.0':
+    resolution: {integrity: sha512-zSxCSpmQCCdQU5Q4CnX/ID8CSUUI3fvmq4hU/GNP/XoAWtXo9SAVnM3TzpU8Gb//H3WCsT8mJcTfyOk3d9ftNg==}
+    engines: {node: '>=18.0'}
+
+  '@docusaurus/types@3.4.0':
+    resolution: {integrity: sha512-4jcDO8kXi5Cf9TcyikB/yKmz14f2RZ2qTRerbHAsS+5InE9ZgSLBNLsewtFTcTOXSVcbU3FoGOzcNWAmU1TR0A==}
+    peerDependencies:
+      react: ^18.0.0
+      react-dom: ^18.0.0
+
+  '@docusaurus/utils-common@3.4.0':
+    resolution: {integrity: sha512-NVx54Wr4rCEKsjOH5QEVvxIqVvm+9kh7q8aYTU5WzUU9/Hctd6aTrcZ3G0Id4zYJ+AeaG5K5qHA4CY5Kcm2iyQ==}
+    engines: {node: '>=18.0'}
+    peerDependencies:
+      '@docusaurus/types': '*'
+    peerDependenciesMeta:
+      '@docusaurus/types':
+        optional: true
+
+  '@docusaurus/utils-validation@3.4.0':
+    resolution: {integrity: sha512-hYQ9fM+AXYVTWxJOT1EuNaRnrR2WGpRdLDQG07O8UOpsvCPWUVOeo26Rbm0JWY2sGLfzAb+tvJ62yF+8F+TV0g==}
+    engines: {node: '>=18.0'}
+
+  '@docusaurus/utils@3.4.0':
+    resolution: {integrity: sha512-fRwnu3L3nnWaXOgs88BVBmG1yGjcQqZNHG+vInhEa2Sz2oQB+ZjbEMO5Rh9ePFpZ0YDiDUhpaVjwmS+AU2F14g==}
+    engines: {node: '>=18.0'}
+    peerDependencies:
+      '@docusaurus/types': '*'
+    peerDependenciesMeta:
+      '@docusaurus/types':
+        optional: true
+
+  '@giscus/react@2.4.0':
+    resolution: {integrity: sha512-y8d8qiZ2sBuaXRcgn/ZWfMlRs9bx26p62BU/HEKQQ+IfHo3B/kglgPjX/IqudwlX+DOlHUl1NvtFo9C8Eqo0eQ==}
+    peerDependencies:
+      react: ^16 || ^17 || ^18
+      react-dom: ^16 || ^17 || ^18
+
+  '@hapi/hoek@9.3.0':
+    resolution: {integrity: sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==}
+
+  '@hapi/topo@5.1.0':
+    resolution: {integrity: sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==}
+
+  '@isaacs/cliui@8.0.2':
+    resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
+    engines: {node: '>=12'}
+
+  '@jest/schemas@29.6.3':
+    resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==}
+    engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+  '@jest/types@29.6.3':
+    resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==}
+    engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+  '@jridgewell/gen-mapping@0.1.1':
+    resolution: {integrity: sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==}
+    engines: {node: '>=6.0.0'}
+
+  '@jridgewell/gen-mapping@0.3.5':
+    resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==}
+    engines: {node: '>=6.0.0'}
+
+  '@jridgewell/resolve-uri@3.1.2':
+    resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
+    engines: {node: '>=6.0.0'}
+
+  '@jridgewell/set-array@1.2.1':
+    resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==}
+    engines: {node: '>=6.0.0'}
+
+  '@jridgewell/source-map@0.3.5':
+    resolution: {integrity: sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==}
+
+  '@jridgewell/sourcemap-codec@1.5.0':
+    resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==}
+
+  '@jridgewell/trace-mapping@0.3.25':
+    resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
+
+  '@leichtgewicht/ip-codec@2.0.4':
+    resolution: {integrity: sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==}
+
+  '@lit-labs/ssr-dom-shim@1.2.0':
+    resolution: {integrity: sha512-yWJKmpGE6lUURKAaIltoPIE/wrbY3TEkqQt+X0m+7fQNnAv0keydnYvbiJFP1PnMhizmIWRWOG5KLhYyc/xl+g==}
+
+  '@lit/reactive-element@2.0.4':
+    resolution: {integrity: sha512-GFn91inaUa2oHLak8awSIigYz0cU0Payr1rcFsrkf5OJ5eSPxElyZfKh0f2p9FsTiZWXQdWGJeXZICEfXXYSXQ==}
+
+  '@mdx-js/mdx@3.0.1':
+    resolution: {integrity: sha512-eIQ4QTrOWyL3LWEe/bu6Taqzq2HQvHcyTMaOrI95P2/LmJE7AsfPfgJGuFLPVqBUE1BC1rik3VIhU+s9u72arA==}
+
+  '@mdx-js/react@3.0.1':
+    resolution: {integrity: sha512-9ZrPIU4MGf6et1m1ov3zKf+q9+deetI51zprKB1D/z3NOb+rUxxtEl3mCjW5wTGh6VhRdwPueh1oRzi6ezkA8A==}
+    peerDependencies:
+      '@types/react': '>=16'
+      react: '>=16'
+
+  '@nodelib/fs.scandir@2.1.5':
+    resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
+    engines: {node: '>= 8'}
+
+  '@nodelib/fs.stat@2.0.5':
+    resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
+    engines: {node: '>= 8'}
+
+  '@nodelib/fs.walk@1.2.8':
+    resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
+    engines: {node: '>= 8'}
+
+  '@pkgjs/parseargs@0.11.0':
+    resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
+    engines: {node: '>=14'}
+
+  '@pnpm/config.env-replace@1.1.0':
+    resolution: {integrity: sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==}
+    engines: {node: '>=12.22.0'}
+
+  '@pnpm/network.ca-file@1.0.2':
+    resolution: {integrity: sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==}
+    engines: {node: '>=12.22.0'}
+
+  '@pnpm/npm-conf@2.2.2':
+    resolution: {integrity: sha512-UA91GwWPhFExt3IizW6bOeY/pQ0BkuNwKjk9iQW9KqxluGCrg4VenZ0/L+2Y0+ZOtme72EVvg6v0zo3AMQRCeA==}
+    engines: {node: '>=12'}
+
+  '@polka/url@1.0.0-next.24':
+    resolution: {integrity: sha512-2LuNTFBIO0m7kKIQvvPHN6UE63VjpmL9rnEEaOOaiSPbZK+zUOYIzBAWcED+3XYzhYsd/0mD57VdxAEqqV52CQ==}
+
+  '@sideway/address@4.1.5':
+    resolution: {integrity: sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==}
+
+  '@sideway/formula@3.0.1':
+    resolution: {integrity: sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==}
+
+  '@sideway/pinpoint@2.0.0':
+    resolution: {integrity: sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==}
+
+  '@sinclair/typebox@0.27.8':
+    resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==}
+
+  '@sindresorhus/is@4.6.0':
+    resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==}
+    engines: {node: '>=10'}
+
+  '@sindresorhus/is@5.6.0':
+    resolution: {integrity: sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==}
+    engines: {node: '>=14.16'}
+
+  '@slorber/remark-comment@1.0.0':
+    resolution: {integrity: sha512-RCE24n7jsOj1M0UPvIQCHTe7fI0sFL4S2nwKVWwHyVr/wI/H8GosgsJGyhnsZoGFnD/P2hLf1mSbrrgSLN93NA==}
+
+  '@svgr/babel-plugin-add-jsx-attribute@8.0.0':
+    resolution: {integrity: sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==}
+    engines: {node: '>=14'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+
+  '@svgr/babel-plugin-remove-jsx-attribute@8.0.0':
+    resolution: {integrity: sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==}
+    engines: {node: '>=14'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+
+  '@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0':
+    resolution: {integrity: sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==}
+    engines: {node: '>=14'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+
+  '@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0':
+    resolution: {integrity: sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==}
+    engines: {node: '>=14'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+
+  '@svgr/babel-plugin-svg-dynamic-title@8.0.0':
+    resolution: {integrity: sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==}
+    engines: {node: '>=14'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+
+  '@svgr/babel-plugin-svg-em-dimensions@8.0.0':
+    resolution: {integrity: sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==}
+    engines: {node: '>=14'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+
+  '@svgr/babel-plugin-transform-react-native-svg@8.1.0':
+    resolution: {integrity: sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==}
+    engines: {node: '>=14'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+
+  '@svgr/babel-plugin-transform-svg-component@8.0.0':
+    resolution: {integrity: sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==}
+    engines: {node: '>=12'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+
+  '@svgr/babel-preset@8.1.0':
+    resolution: {integrity: sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==}
+    engines: {node: '>=14'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+
+  '@svgr/core@8.1.0':
+    resolution: {integrity: sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==}
+    engines: {node: '>=14'}
+
+  '@svgr/hast-util-to-babel-ast@8.0.0':
+    resolution: {integrity: sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==}
+    engines: {node: '>=14'}
+
+  '@svgr/plugin-jsx@8.1.0':
+    resolution: {integrity: sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==}
+    engines: {node: '>=14'}
+    peerDependencies:
+      '@svgr/core': '*'
+
+  '@svgr/plugin-svgo@8.1.0':
+    resolution: {integrity: sha512-Ywtl837OGO9pTLIN/onoWLmDQ4zFUycI1g76vuKGEz6evR/ZTJlJuz3G/fIkb6OVBJ2g0o6CGJzaEjfmEo3AHA==}
+    engines: {node: '>=14'}
+    peerDependencies:
+      '@svgr/core': '*'
+
+  '@svgr/webpack@8.1.0':
+    resolution: {integrity: sha512-LnhVjMWyMQV9ZmeEy26maJk+8HTIbd59cH4F2MJ439k9DqejRisfFNGAPvRYlKETuh9LrImlS8aKsBgKjMA8WA==}
+    engines: {node: '>=14'}
+
+  '@szmarczak/http-timer@5.0.1':
+    resolution: {integrity: sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==}
+    engines: {node: '>=14.16'}
+
+  '@tailwindcss/typography@0.5.9':
+    resolution: {integrity: sha512-t8Sg3DyynFysV9f4JDOVISGsjazNb48AeIYQwcL+Bsq5uf4RYL75C1giZ43KISjeDGBaTN3Kxh7Xj/vRSMJUUg==}
+    peerDependencies:
+      tailwindcss: '>=3.0.0 || insiders'
+
+  '@trysound/sax@0.2.0':
+    resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==}
+    engines: {node: '>=10.13.0'}
+
+  '@tsconfig/docusaurus@2.0.3':
+    resolution: {integrity: sha512-3l1L5PzWVa7l0691TjnsZ0yOIEwG9DziSqu5IPZPlI5Dowi7z42cEym8Y35GHbgHvPcBfNxfrbxm7Cncn4nByQ==}
+
+  '@types/acorn@4.0.6':
+    resolution: {integrity: sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ==}
+
+  '@types/body-parser@1.19.2':
+    resolution: {integrity: sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==}
+
+  '@types/bonjour@3.5.10':
+    resolution: {integrity: sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw==}
+
+  '@types/connect-history-api-fallback@1.3.5':
+    resolution: {integrity: sha512-h8QJa8xSb1WD4fpKBDcATDNGXghFj6/3GRWG6dhmRcu0RX1Ubasur2Uvx5aeEwlf0MwblEC2bMzzMQntxnw/Cw==}
+
+  '@types/connect@3.4.35':
+    resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==}
+
+  '@types/d3-scale-chromatic@3.0.3':
+    resolution: {integrity: sha512-laXM4+1o5ImZv3RpFAsTRn3TEkzqkytiOY0Dz0sq5cnd1dtNlk6sHLon4OvqaiJb28T0S/TdsBI3Sjsy+keJrw==}
+
+  '@types/d3-scale@4.0.8':
+    resolution: {integrity: sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==}
+
+  '@types/d3-time@3.0.3':
+    resolution: {integrity: sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw==}
+
+  '@types/debug@4.1.12':
+    resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
+
+  '@types/eslint-scope@3.7.4':
+    resolution: {integrity: sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==}
+
+  '@types/eslint@8.4.10':
+    resolution: {integrity: sha512-Sl/HOqN8NKPmhWo2VBEPm0nvHnu2LL3v9vKo8MEq0EtbJ4eVzGPl41VNPvn5E1i5poMk4/XD8UriLHpJvEP/Nw==}
+
+  '@types/estree-jsx@1.0.4':
+    resolution: {integrity: sha512-5idy3hvI9lAMqsyilBM+N+boaCf1MgoefbDxN6KEO5aK17TOHwFAYT9sjxzeKAiIWRUBgLxmZ9mPcnzZXtTcRQ==}
+
+  '@types/estree@1.0.5':
+    resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
+
+  '@types/express-serve-static-core@4.17.32':
+    resolution: {integrity: sha512-aI5h/VOkxOF2Z1saPy0Zsxs5avets/iaiAJYznQFm5By/pamU31xWKL//epiF4OfUA2qTOc9PV6tCUjhO8wlZA==}
+
+  '@types/express@4.17.15':
+    resolution: {integrity: sha512-Yv0k4bXGOH+8a+7bELd2PqHQsuiANB+A8a4gnQrkRWzrkKlb6KHaVvyXhqs04sVW/OWlbPyYxRgYlIXLfrufMQ==}
+
+  '@types/gtag.js@0.0.12':
+    resolution: {integrity: sha512-YQV9bUsemkzG81Ea295/nF/5GijnD2Af7QhEofh7xu+kvCN6RdodgNwwGWXB5GMI3NoyvQo0odNctoH/qLMIpg==}
+
+  '@types/hast@3.0.4':
+    resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==}
+
+  '@types/history@4.7.11':
+    resolution: {integrity: sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==}
+
+  '@types/html-minifier-terser@6.1.0':
+    resolution: {integrity: sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==}
+
+  '@types/http-cache-semantics@4.0.4':
+    resolution: {integrity: sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==}
+
+  '@types/http-proxy@1.17.9':
+    resolution: {integrity: sha512-QsbSjA/fSk7xB+UXlCT3wHBy5ai9wOcNDWwZAtud+jXhwOM3l+EYZh8Lng4+/6n8uar0J7xILzqftJdJ/Wdfkw==}
+
+  '@types/istanbul-lib-coverage@2.0.4':
+    resolution: {integrity: sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==}
+
+  '@types/istanbul-lib-report@3.0.0':
+    resolution: {integrity: sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==}
+
+  '@types/istanbul-reports@3.0.1':
+    resolution: {integrity: sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==}
+
+  '@types/json-schema@7.0.11':
+    resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==}
+
+  '@types/mdast@3.0.10':
+    resolution: {integrity: sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA==}
+
+  '@types/mdast@4.0.3':
+    resolution: {integrity: sha512-LsjtqsyF+d2/yFOYaN22dHZI1Cpwkrj+g06G8+qtUKlhovPW89YhqSnfKtMbkgmEtYpH2gydRNULd6y8mciAFg==}
+
+  '@types/mdx@2.0.11':
+    resolution: {integrity: sha512-HM5bwOaIQJIQbAYfax35HCKxx7a3KrK3nBtIqJgSOitivTD1y3oW9P3rxY9RkXYPUk7y/AjAohfHKmFpGE79zw==}
+
+  '@types/mime@3.0.1':
+    resolution: {integrity: sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==}
+
+  '@types/ms@0.7.34':
+    resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==}
+
+  '@types/node@17.0.45':
+    resolution: {integrity: sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==}
+
+  '@types/node@20.11.17':
+    resolution: {integrity: sha512-QmgQZGWu1Yw9TDyAP9ZzpFJKynYNeOvwMJmaxABfieQoVoiVOS6MN1WSpqpRcbeA5+RW82kraAVxCCJg+780Qw==}
+
+  '@types/parse-json@4.0.0':
+    resolution: {integrity: sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==}
+
+  '@types/prismjs@1.26.3':
+    resolution: {integrity: sha512-A0D0aTXvjlqJ5ZILMz3rNfDBOx9hHxLZYv2by47Sm/pqW35zzjusrZTryatjN/Rf8Us2gZrJD+KeHbUSTux1Cw==}
+
+  '@types/prop-types@15.7.5':
+    resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==}
+
+  '@types/qs@6.9.7':
+    resolution: {integrity: sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==}
+
+  '@types/range-parser@1.2.4':
+    resolution: {integrity: sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==}
+
+  '@types/react-router-config@5.0.11':
+    resolution: {integrity: sha512-WmSAg7WgqW7m4x8Mt4N6ZyKz0BubSj/2tVUMsAHp+Yd2AMwcSbeFq9WympT19p5heCFmF97R9eD5uUR/t4HEqw==}
+
+  '@types/react-router-dom@5.3.3':
+    resolution: {integrity: sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==}
+
+  '@types/react-router@5.1.20':
+    resolution: {integrity: sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==}
+
+  '@types/react@18.0.26':
+    resolution: {integrity: sha512-hCR3PJQsAIXyxhTNSiDFY//LhnMZWpNNr5etoCqx/iUfGc5gXWtQR2Phl908jVR6uPXacojQWTg4qRpkxTuGug==}
+
+  '@types/retry@0.12.0':
+    resolution: {integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==}
+
+  '@types/sax@1.2.4':
+    resolution: {integrity: sha512-pSAff4IAxJjfAXUG6tFkO7dsSbTmf8CtUpfhhZ5VhkRpC4628tJhh3+V6H1E+/Gs9piSzYKT5yzHO5M4GG9jkw==}
+
+  '@types/scheduler@0.16.2':
+    resolution: {integrity: sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==}
+
+  '@types/serve-index@1.9.1':
+    resolution: {integrity: sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg==}
+
+  '@types/serve-static@1.15.0':
+    resolution: {integrity: sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg==}
+
+  '@types/sockjs@0.3.33':
+    resolution: {integrity: sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw==}
+
+  '@types/trusted-types@2.0.7':
+    resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
+
+  '@types/unist@2.0.6':
+    resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==}
+
+  '@types/unist@3.0.2':
+    resolution: {integrity: sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==}
+
+  '@types/ws@8.5.10':
+    resolution: {integrity: sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==}
+
+  '@types/yargs-parser@21.0.0':
+    resolution: {integrity: sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==}
+
+  '@types/yargs@17.0.19':
+    resolution: {integrity: sha512-cAx3qamwaYX9R0fzOIZAlFpo4A+1uBVCxqpKz9D26uTF4srRXaGTTsikQmaotCtNdbhzyUH7ft6p9ktz9s6UNQ==}
+
+  '@ungap/structured-clone@1.2.0':
+    resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==}
+
+  '@webassemblyjs/ast@1.11.6':
+    resolution: {integrity: sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==}
+
+  '@webassemblyjs/floating-point-hex-parser@1.11.6':
+    resolution: {integrity: sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==}
+
+  '@webassemblyjs/helper-api-error@1.11.6':
+    resolution: {integrity: sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==}
+
+  '@webassemblyjs/helper-buffer@1.11.6':
+    resolution: {integrity: sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==}
+
+  '@webassemblyjs/helper-numbers@1.11.6':
+    resolution: {integrity: sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==}
+
+  '@webassemblyjs/helper-wasm-bytecode@1.11.6':
+    resolution: {integrity: sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==}
+
+  '@webassemblyjs/helper-wasm-section@1.11.6':
+    resolution: {integrity: sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==}
+
+  '@webassemblyjs/ieee754@1.11.6':
+    resolution: {integrity: sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==}
+
+  '@webassemblyjs/leb128@1.11.6':
+    resolution: {integrity: sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==}
+
+  '@webassemblyjs/utf8@1.11.6':
+    resolution: {integrity: sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==}
+
+  '@webassemblyjs/wasm-edit@1.11.6':
+    resolution: {integrity: sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==}
+
+  '@webassemblyjs/wasm-gen@1.11.6':
+    resolution: {integrity: sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==}
+
+  '@webassemblyjs/wasm-opt@1.11.6':
+    resolution: {integrity: sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==}
+
+  '@webassemblyjs/wasm-parser@1.11.6':
+    resolution: {integrity: sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==}
+
+  '@webassemblyjs/wast-printer@1.11.6':
+    resolution: {integrity: sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==}
+
+  '@xtuc/ieee754@1.2.0':
+    resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==}
+
+  '@xtuc/long@4.2.2':
+    resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==}
+
+  accepts@1.3.8:
+    resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
+    engines: {node: '>= 0.6'}
+
+  acorn-import-assertions@1.9.0:
+    resolution: {integrity: sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==}
+    peerDependencies:
+      acorn: ^8
+
+  acorn-jsx@5.3.2:
+    resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
+    peerDependencies:
+      acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
+
+  acorn-walk@8.2.0:
+    resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==}
+    engines: {node: '>=0.4.0'}
+
+  acorn@8.11.3:
+    resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==}
+    engines: {node: '>=0.4.0'}
+    hasBin: true
+
+  address@1.2.2:
+    resolution: {integrity: sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==}
+    engines: {node: '>= 10.0.0'}
+
+  aggregate-error@3.1.0:
+    resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==}
+    engines: {node: '>=8'}
+
+  ajv-formats@2.1.1:
+    resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==}
+    peerDependencies:
+      ajv: ^8.0.0
+    peerDependenciesMeta:
+      ajv:
+        optional: true
+
+  ajv-keywords@3.5.2:
+    resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==}
+    peerDependencies:
+      ajv: ^6.9.1
+
+  ajv-keywords@5.1.0:
+    resolution: {integrity: sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==}
+    peerDependencies:
+      ajv: ^8.8.2
+
+  ajv@6.12.6:
+    resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
+
+  ajv@8.12.0:
+    resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==}
+
+  algoliasearch-helper@3.16.2:
+    resolution: {integrity: sha512-Yl/Gu5Cq4Z5s/AJ0jR37OPI1H3+z7PHz657ibyaXgMOaWvPlZ3OACN13N+7HCLPUlB0BN+8BtmrG/CqTilowBA==}
+    peerDependencies:
+      algoliasearch: '>= 3.1 < 6'
+
+  algoliasearch@4.22.1:
+    resolution: {integrity: sha512-jwydKFQJKIx9kIZ8Jm44SdpigFwRGPESaxZBaHSV0XWN2yBJAOT4mT7ppvlrpA4UGzz92pqFnVKr/kaZXrcreg==}
+
+  ansi-align@3.0.1:
+    resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==}
+
+  ansi-html-community@0.0.8:
+    resolution: {integrity: sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==}
+    engines: {'0': node >= 0.8.0}
+    hasBin: true
+
+  ansi-regex@5.0.1:
+    resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
+    engines: {node: '>=8'}
+
+  ansi-regex@6.1.0:
+    resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==}
+    engines: {node: '>=12'}
+
+  ansi-styles@3.2.1:
+    resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==}
+    engines: {node: '>=4'}
+
+  ansi-styles@4.3.0:
+    resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
+    engines: {node: '>=8'}
+
+  ansi-styles@6.2.1:
+    resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==}
+    engines: {node: '>=12'}
+
+  any-promise@1.3.0:
+    resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}
+
+  anymatch@3.1.3:
+    resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
+    engines: {node: '>= 8'}
+
+  arg@5.0.2:
+    resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
+
+  argparse@1.0.10:
+    resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==}
+
+  argparse@2.0.1:
+    resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
+
+  array-flatten@1.1.1:
+    resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==}
+
+  array-flatten@2.1.2:
+    resolution: {integrity: sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==}
+
+  array-union@2.1.0:
+    resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==}
+    engines: {node: '>=8'}
+
+  astring@1.8.6:
+    resolution: {integrity: sha512-ISvCdHdlTDlH5IpxQJIex7BWBywFWgjJSVdwst+/iQCoEYnyOaQ95+X1JGshuBjGp6nxKUy1jMgE3zPqN7fQdg==}
+    hasBin: true
+
+  at-least-node@1.0.0:
+    resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==}
+    engines: {node: '>= 4.0.0'}
+
+  autoprefixer@10.4.13:
+    resolution: {integrity: sha512-49vKpMqcZYsJjwotvt4+h/BCjJVnhGwcLpDt5xkcaOG3eLrG/HUYLagrihYsQ+qrIBgIzX1Rw7a6L8I/ZA1Atg==}
+    engines: {node: ^10 || ^12 || >=14}
+    hasBin: true
+    peerDependencies:
+      postcss: ^8.1.0
+
+  autoprefixer@10.4.17:
+    resolution: {integrity: sha512-/cpVNRLSfhOtcGflT13P2794gVSgmPgTR+erw5ifnMLZb0UnSlkK4tquLmkd3BhA+nLo5tX8Cu0upUsGKvKbmg==}
+    engines: {node: ^10 || ^12 || >=14}
+    hasBin: true
+    peerDependencies:
+      postcss: ^8.1.0
+
+  autoprefixer@10.4.19:
+    resolution: {integrity: sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==}
+    engines: {node: ^10 || ^12 || >=14}
+    hasBin: true
+    peerDependencies:
+      postcss: ^8.1.0
+
+  babel-loader@9.1.3:
+    resolution: {integrity: sha512-xG3ST4DglodGf8qSwv0MdeWLhrDsw/32QMdTO5T1ZIp9gQur0HkCyFs7Awskr10JKXFXwpAhiCuYX5oGXnRGbw==}
+    engines: {node: '>= 14.15.0'}
+    peerDependencies:
+      '@babel/core': ^7.12.0
+      webpack: '>=5'
+
+  babel-plugin-dynamic-import-node@2.3.3:
+    resolution: {integrity: sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==}
+
+  babel-plugin-polyfill-corejs2@0.4.8:
+    resolution: {integrity: sha512-OtIuQfafSzpo/LhnJaykc0R/MMnuLSSVjVYy9mHArIZ9qTCSZ6TpWCuEKZYVoN//t8HqBNScHrOtCrIK5IaGLg==}
+    peerDependencies:
+      '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0
+
+  babel-plugin-polyfill-corejs3@0.9.0:
+    resolution: {integrity: sha512-7nZPG1uzK2Ymhy/NbaOWTg3uibM2BmGASS4vHS4szRZAIR8R6GwA/xAujpdrXU5iyklrimWnLWU+BLF9suPTqg==}
+    peerDependencies:
+      '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0
+
+  babel-plugin-polyfill-regenerator@0.5.5:
+    resolution: {integrity: sha512-OJGYZlhLqBh2DDHeqAxWB1XIvr49CxiJ2gIt61/PU55CQK4Z58OzMqjDe1zwQdQk+rBYsRc+1rJmdajM3gimHg==}
+    peerDependencies:
+      '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0
+
+  bail@2.0.2:
+    resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==}
+
+  balanced-match@1.0.2:
+    resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
+
+  batch@0.6.1:
+    resolution: {integrity: sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==}
+
+  big.js@5.2.2:
+    resolution: {integrity: sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==}
+
+  binary-extensions@2.2.0:
+    resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
+    engines: {node: '>=8'}
+
+  body-parser@1.20.1:
+    resolution: {integrity: sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==}
+    engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
+
+  bonjour-service@1.0.14:
+    resolution: {integrity: sha512-HIMbgLnk1Vqvs6B4Wq5ep7mxvj9sGz5d1JJyDNSGNIdA/w2MCz6GTjWTdjqOJV1bEPj+6IkxDvWNFKEBxNt4kQ==}
+
+  boolbase@1.0.0:
+    resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
+
+  boxen@6.2.1:
+    resolution: {integrity: sha512-H4PEsJXfFI/Pt8sjDWbHlQPx4zL/bvSQjcilJmaulGt5mLDorHOHpmdXAJcBcmru7PhYSp/cDMWRko4ZUMFkSw==}
+    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+
+  boxen@7.1.1:
+    resolution: {integrity: sha512-2hCgjEmP8YLWQ130n2FerGv7rYpfBmnmp9Uy2Le1vge6X3gZIfSmEzP5QTDElFxcvVcXlEn8Aq6MU/PZygIOog==}
+    engines: {node: '>=14.16'}
+
+  brace-expansion@1.1.11:
+    resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
+
+  brace-expansion@2.0.1:
+    resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
+
+  braces@3.0.2:
+    resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==}
+    engines: {node: '>=8'}
+
+  braces@3.0.3:
+    resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
+    engines: {node: '>=8'}
+
+  browserslist@4.21.4:
+    resolution: {integrity: sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==}
+    engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
+    hasBin: true
+
+  browserslist@4.22.3:
+    resolution: {integrity: sha512-UAp55yfwNv0klWNapjs/ktHoguxuQNGnOzxYmfnXIS+8AsRDZkSDxg7R1AX3GKzn078SBI5dzwzj/Yx0Or0e3A==}
+    engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
+    hasBin: true
+
+  browserslist@4.23.1:
+    resolution: {integrity: sha512-TUfofFo/KsK/bWZ9TWQ5O26tsWW4Uhmt8IYklbnUa70udB6P2wA7w7o4PY4muaEPBQaAX+CEnmmIA41NVHtPVw==}
+    engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
+    hasBin: true
+
+  buffer-from@1.1.2:
+    resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
+
+  bytes@3.0.0:
+    resolution: {integrity: sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==}
+    engines: {node: '>= 0.8'}
+
+  bytes@3.1.2:
+    resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
+    engines: {node: '>= 0.8'}
+
+  cacheable-lookup@7.0.0:
+    resolution: {integrity: sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==}
+    engines: {node: '>=14.16'}
+
+  cacheable-request@10.2.14:
+    resolution: {integrity: sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==}
+    engines: {node: '>=14.16'}
+
+  call-bind@1.0.2:
+    resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==}
+
+  callsites@3.1.0:
+    resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
+    engines: {node: '>=6'}
+
+  camel-case@4.1.2:
+    resolution: {integrity: sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==}
+
+  camelcase-css@2.0.1:
+    resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==}
+    engines: {node: '>= 6'}
+
+  camelcase@6.3.0:
+    resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==}
+    engines: {node: '>=10'}
+
+  camelcase@7.0.1:
+    resolution: {integrity: sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==}
+    engines: {node: '>=14.16'}
+
+  caniuse-api@3.0.0:
+    resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==}
+
+  caniuse-lite@1.0.30001587:
+    resolution: {integrity: sha512-HMFNotUmLXn71BQxg8cijvqxnIAofforZOwGsxyXJ0qugTdspUF4sPSJ2vhgprHCB996tIDzEq1ubumPDV8ULA==}
+
+  caniuse-lite@1.0.30001632:
+    resolution: {integrity: sha512-udx3o7yHJfUxMLkGohMlVHCvFvWmirKh9JAH/d7WOLPetlH+LTL5cocMZ0t7oZx/mdlOWXti97xLZWc8uURRHg==}
+
+  ccount@2.0.1:
+    resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==}
+
+  chalk@2.4.2:
+    resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==}
+    engines: {node: '>=4'}
+
+  chalk@4.1.2:
+    resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
+    engines: {node: '>=10'}
+
+  chalk@5.3.0:
+    resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==}
+    engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
+
+  char-regex@1.0.2:
+    resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==}
+    engines: {node: '>=10'}
+
+  character-entities-html4@2.1.0:
+    resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==}
+
+  character-entities-legacy@3.0.0:
+    resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==}
+
+  character-entities@2.0.2:
+    resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==}
+
+  character-reference-invalid@2.0.1:
+    resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==}
+
+  cheerio-select@2.1.0:
+    resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==}
+
+  cheerio@1.0.0-rc.12:
+    resolution: {integrity: sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==}
+    engines: {node: '>= 6'}
+
+  chokidar@3.5.3:
+    resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==}
+    engines: {node: '>= 8.10.0'}
+
+  chrome-trace-event@1.0.3:
+    resolution: {integrity: sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==}
+    engines: {node: '>=6.0'}
+
+  ci-info@3.7.1:
+    resolution: {integrity: sha512-4jYS4MOAaCIStSRwiuxc4B8MYhIe676yO1sYGzARnjXkWpmzZMMYxY6zu8WYWDhSuth5zhrQ1rhNSibyyvv4/w==}
+    engines: {node: '>=8'}
+
+  classnames@2.5.1:
+    resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==}
+
+  clean-css@5.3.3:
+    resolution: {integrity: sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==}
+    engines: {node: '>= 10.0'}
+
+  clean-stack@2.2.0:
+    resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==}
+    engines: {node: '>=6'}
+
+  cli-boxes@3.0.0:
+    resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==}
+    engines: {node: '>=10'}
+
+  cli-table3@0.6.3:
+    resolution: {integrity: sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==}
+    engines: {node: 10.* || >= 12.*}
+
+  clone-deep@4.0.1:
+    resolution: {integrity: sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==}
+    engines: {node: '>=6'}
+
+  clsx@1.2.1:
+    resolution: {integrity: sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==}
+    engines: {node: '>=6'}
+
+  clsx@2.1.0:
+    resolution: {integrity: sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==}
+    engines: {node: '>=6'}
+
+  collapse-white-space@2.1.0:
+    resolution: {integrity: sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==}
+
+  color-convert@1.9.3:
+    resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
+
+  color-convert@2.0.1:
+    resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
+    engines: {node: '>=7.0.0'}
+
+  color-name@1.1.3:
+    resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==}
+
+  color-name@1.1.4:
+    resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
+
+  colord@2.9.3:
+    resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==}
+
+  colorette@2.0.19:
+    resolution: {integrity: sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==}
+
+  combine-promises@1.1.0:
+    resolution: {integrity: sha512-ZI9jvcLDxqwaXEixOhArm3r7ReIivsXkpbyEWyeOhzz1QS0iSgBPnWvEqvIQtYyamGCYA88gFhmUrs9hrrQ0pg==}
+    engines: {node: '>=10'}
+
+  comma-separated-tokens@2.0.3:
+    resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==}
+
+  commander@10.0.1:
+    resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==}
+    engines: {node: '>=14'}
+
+  commander@2.20.3:
+    resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
+
+  commander@4.1.1:
+    resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
+    engines: {node: '>= 6'}
+
+  commander@5.1.0:
+    resolution: {integrity: sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==}
+    engines: {node: '>= 6'}
+
+  commander@7.2.0:
+    resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==}
+    engines: {node: '>= 10'}
+
+  commander@8.3.0:
+    resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==}
+    engines: {node: '>= 12'}
+
+  common-path-prefix@3.0.0:
+    resolution: {integrity: sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==}
+
+  compressible@2.0.18:
+    resolution: {integrity: sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==}
+    engines: {node: '>= 0.6'}
+
+  compression@1.7.4:
+    resolution: {integrity: sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==}
+    engines: {node: '>= 0.8.0'}
+
+  concat-map@0.0.1:
+    resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=}
+
+  config-chain@1.1.13:
+    resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==}
+
+  configstore@6.0.0:
+    resolution: {integrity: sha512-cD31W1v3GqUlQvbBCGcXmd2Nj9SvLDOP1oQ0YFuLETufzSPaKp11rYBsSOm7rCsW3OnIRAFM3OxRhceaXNYHkA==}
+    engines: {node: '>=12'}
+
+  connect-history-api-fallback@2.0.0:
+    resolution: {integrity: sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==}
+    engines: {node: '>=0.8'}
+
+  consola@2.15.3:
+    resolution: {integrity: sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==}
+
+  content-disposition@0.5.2:
+    resolution: {integrity: sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==}
+    engines: {node: '>= 0.6'}
+
+  content-disposition@0.5.4:
+    resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==}
+    engines: {node: '>= 0.6'}
+
+  content-type@1.0.4:
+    resolution: {integrity: sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==}
+    engines: {node: '>= 0.6'}
+
+  convert-source-map@2.0.0:
+    resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
+
+  cookie-signature@1.0.6:
+    resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==}
+
+  cookie@0.5.0:
+    resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==}
+    engines: {node: '>= 0.6'}
+
+  copy-text-to-clipboard@3.2.0:
+    resolution: {integrity: sha512-RnJFp1XR/LOBDckxTib5Qjr/PMfkatD0MUCQgdpqS8MdKiNUzBjAQBEN6oUy+jW7LI93BBG3DtMB2KOOKpGs2Q==}
+    engines: {node: '>=12'}
+
+  copy-webpack-plugin@11.0.0:
+    resolution: {integrity: sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==}
+    engines: {node: '>= 14.15.0'}
+    peerDependencies:
+      webpack: ^5.1.0
+
+  core-js-compat@3.35.1:
+    resolution: {integrity: sha512-sftHa5qUJY3rs9Zht1WEnmkvXputCyDBczPnr7QDgL8n3qrF3CMXY4VPSYtOLLiOUJcah2WNXREd48iOl6mQIw==}
+
+  core-js-pure@3.35.1:
+    resolution: {integrity: sha512-zcIdi/CL3MWbBJYo5YCeVAAx+Sy9yJE9I3/u9LkFABwbeaPhTMRWraM8mYFp9jW5Z50hOy7FVzCc8dCrpZqtIQ==}
+
+  core-js@3.35.1:
+    resolution: {integrity: sha512-IgdsbxNyMskrTFxa9lWHyMwAJU5gXOPP+1yO+K59d50VLVAIDAbs7gIv705KzALModfK3ZrSZTPNpC0PQgIZuw==}
+
+  core-util-is@1.0.3:
+    resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
+
+  cose-base@1.0.3:
+    resolution: {integrity: sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==}
+
+  cosmiconfig@6.0.0:
+    resolution: {integrity: sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==}
+    engines: {node: '>=8'}
+
+  cosmiconfig@8.3.6:
+    resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==}
+    engines: {node: '>=14'}
+    peerDependencies:
+      typescript: '>=4.9.5'
+    peerDependenciesMeta:
+      typescript:
+        optional: true
+
+  cross-spawn@7.0.3:
+    resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
+    engines: {node: '>= 8'}
+
+  crypto-random-string@4.0.0:
+    resolution: {integrity: sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==}
+    engines: {node: '>=12'}
+
+  css-declaration-sorter@7.2.0:
+    resolution: {integrity: sha512-h70rUM+3PNFuaBDTLe8wF/cdWu+dOZmb7pJt8Z2sedYbAcQVQV/tEchueg3GWxwqS0cxtbxmaHEdkNACqcvsow==}
+    engines: {node: ^14 || ^16 || >=18}
+    peerDependencies:
+      postcss: ^8.0.9
+
+  css-loader@6.10.0:
+    resolution: {integrity: sha512-LTSA/jWbwdMlk+rhmElbDR2vbtQoTBPr7fkJE+mxrHj+7ru0hUmHafDRzWIjIHTwpitWVaqY2/UWGRca3yUgRw==}
+    engines: {node: '>= 12.13.0'}
+    peerDependencies:
+      '@rspack/core': 0.x || 1.x
+      webpack: ^5.0.0
+    peerDependenciesMeta:
+      '@rspack/core':
+        optional: true
+      webpack:
+        optional: true
+
+  css-minimizer-webpack-plugin@5.0.1:
+    resolution: {integrity: sha512-3caImjKFQkS+ws1TGcFn0V1HyDJFq1Euy589JlD6/3rV2kj+w7r5G9WDMgSHvpvXHNZ2calVypZWuEDQd9wfLg==}
+    engines: {node: '>= 14.15.0'}
+    peerDependencies:
+      '@parcel/css': '*'
+      '@swc/css': '*'
+      clean-css: '*'
+      csso: '*'
+      esbuild: '*'
+      lightningcss: '*'
+      webpack: ^5.0.0
+    peerDependenciesMeta:
+      '@parcel/css':
+        optional: true
+      '@swc/css':
+        optional: true
+      clean-css:
+        optional: true
+      csso:
+        optional: true
+      esbuild:
+        optional: true
+      lightningcss:
+        optional: true
+
+  css-select@4.3.0:
+    resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==}
+
+  css-select@5.1.0:
+    resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==}
+
+  css-tree@2.2.1:
+    resolution: {integrity: sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==}
+    engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'}
+
+  css-tree@2.3.1:
+    resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==}
+    engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0}
+
+  css-what@6.1.0:
+    resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==}
+    engines: {node: '>= 6'}
+
+  cssesc@3.0.0:
+    resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
+    engines: {node: '>=4'}
+    hasBin: true
+
+  cssnano-preset-advanced@6.1.2:
+    resolution: {integrity: sha512-Nhao7eD8ph2DoHolEzQs5CfRpiEP0xa1HBdnFZ82kvqdmbwVBUr2r1QuQ4t1pi+D1ZpqpcO4T+wy/7RxzJ/WPQ==}
+    engines: {node: ^14 || ^16 || >=18.0}
+    peerDependencies:
+      postcss: ^8.4.31
+
+  cssnano-preset-default@6.1.2:
+    resolution: {integrity: sha512-1C0C+eNaeN8OcHQa193aRgYexyJtU8XwbdieEjClw+J9d94E41LwT6ivKH0WT+fYwYWB0Zp3I3IZ7tI/BbUbrg==}
+    engines: {node: ^14 || ^16 || >=18.0}
+    peerDependencies:
+      postcss: ^8.4.31
+
+  cssnano-utils@4.0.2:
+    resolution: {integrity: sha512-ZR1jHg+wZ8o4c3zqf1SIUSTIvm/9mU343FMR6Obe/unskbvpGhZOo1J6d/r8D1pzkRQYuwbcH3hToOuoA2G7oQ==}
+    engines: {node: ^14 || ^16 || >=18.0}
+    peerDependencies:
+      postcss: ^8.4.31
+
+  cssnano@6.1.2:
+    resolution: {integrity: sha512-rYk5UeX7VAM/u0lNqewCdasdtPK81CgX8wJFLEIXHbV2oldWRgJAsZrdhRXkV1NJzA2g850KiFm9mMU2HxNxMA==}
+    engines: {node: ^14 || ^16 || >=18.0}
+    peerDependencies:
+      postcss: ^8.4.31
+
+  csso@5.0.5:
+    resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==}
+    engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'}
+
+  csstype@3.1.1:
+    resolution: {integrity: sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==}
+
+  cytoscape-cose-bilkent@4.1.0:
+    resolution: {integrity: sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==}
+    peerDependencies:
+      cytoscape: ^3.2.0
+
+  cytoscape@3.28.1:
+    resolution: {integrity: sha512-xyItz4O/4zp9/239wCcH8ZcFuuZooEeF8KHRmzjDfGdXsj3OG9MFSMA0pJE0uX3uCN/ygof6hHf4L7lst+JaDg==}
+    engines: {node: '>=0.10'}
+
+  d3-array@2.12.1:
+    resolution: {integrity: sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==}
+
+  d3-array@3.2.4:
+    resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==}
+    engines: {node: '>=12'}
+
+  d3-axis@3.0.0:
+    resolution: {integrity: sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==}
+    engines: {node: '>=12'}
+
+  d3-brush@3.0.0:
+    resolution: {integrity: sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==}
+    engines: {node: '>=12'}
+
+  d3-chord@3.0.1:
+    resolution: {integrity: sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==}
+    engines: {node: '>=12'}
+
+  d3-color@3.1.0:
+    resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==}
+    engines: {node: '>=12'}
+
+  d3-contour@4.0.2:
+    resolution: {integrity: sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==}
+    engines: {node: '>=12'}
+
+  d3-delaunay@6.0.4:
+    resolution: {integrity: sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==}
+    engines: {node: '>=12'}
+
+  d3-dispatch@3.0.1:
+    resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==}
+    engines: {node: '>=12'}
+
+  d3-drag@3.0.0:
+    resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==}
+    engines: {node: '>=12'}
+
+  d3-dsv@3.0.1:
+    resolution: {integrity: sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==}
+    engines: {node: '>=12'}
+    hasBin: true
+
+  d3-ease@3.0.1:
+    resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==}
+    engines: {node: '>=12'}
+
+  d3-fetch@3.0.1:
+    resolution: {integrity: sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==}
+    engines: {node: '>=12'}
+
+  d3-force@3.0.0:
+    resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==}
+    engines: {node: '>=12'}
+
+  d3-format@3.1.0:
+    resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==}
+    engines: {node: '>=12'}
+
+  d3-geo@3.1.0:
+    resolution: {integrity: sha512-JEo5HxXDdDYXCaWdwLRt79y7giK8SbhZJbFWXqbRTolCHFI5jRqteLzCsq51NKbUoX0PjBVSohxrx+NoOUujYA==}
+    engines: {node: '>=12'}
+
+  d3-hierarchy@3.1.2:
+    resolution: {integrity: sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==}
+    engines: {node: '>=12'}
+
+  d3-interpolate@3.0.1:
+    resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==}
+    engines: {node: '>=12'}
+
+  d3-path@1.0.9:
+    resolution: {integrity: sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==}
+
+  d3-path@3.1.0:
+    resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==}
+    engines: {node: '>=12'}
+
+  d3-polygon@3.0.1:
+    resolution: {integrity: sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==}
+    engines: {node: '>=12'}
+
+  d3-quadtree@3.0.1:
+    resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==}
+    engines: {node: '>=12'}
+
+  d3-random@3.0.1:
+    resolution: {integrity: sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==}
+    engines: {node: '>=12'}
+
+  d3-sankey@0.12.3:
+    resolution: {integrity: sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==}
+
+  d3-scale-chromatic@3.0.0:
+    resolution: {integrity: sha512-Lx9thtxAKrO2Pq6OO2Ua474opeziKr279P/TKZsMAhYyNDD3EnCffdbgeSYN5O7m2ByQsxtuP2CSDczNUIZ22g==}
+    engines: {node: '>=12'}
+
+  d3-scale@4.0.2:
+    resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==}
+    engines: {node: '>=12'}
+
+  d3-selection@3.0.0:
+    resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==}
+    engines: {node: '>=12'}
+
+  d3-shape@1.3.7:
+    resolution: {integrity: sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==}
+
+  d3-shape@3.2.0:
+    resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==}
+    engines: {node: '>=12'}
+
+  d3-time-format@4.1.0:
+    resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==}
+    engines: {node: '>=12'}
+
+  d3-time@3.1.0:
+    resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==}
+    engines: {node: '>=12'}
+
+  d3-timer@3.0.1:
+    resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==}
+    engines: {node: '>=12'}
+
+  d3-transition@3.0.1:
+    resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==}
+    engines: {node: '>=12'}
+    peerDependencies:
+      d3-selection: 2 - 3
+
+  d3-zoom@3.0.0:
+    resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==}
+    engines: {node: '>=12'}
+
+  d3@7.8.5:
+    resolution: {integrity: sha512-JgoahDG51ncUfJu6wX/1vWQEqOflgXyl4MaHqlcSruTez7yhaRKR9i8VjjcQGeS2en/jnFivXuaIMnseMMt0XA==}
+    engines: {node: '>=12'}
+
+  dagre-d3-es@7.0.10:
+    resolution: {integrity: sha512-qTCQmEhcynucuaZgY5/+ti3X/rnszKZhEQH/ZdWdtP1tA/y3VoHJzcVrO9pjjJCNpigfscAtoUB5ONcd2wNn0A==}
+
+  dayjs@1.11.10:
+    resolution: {integrity: sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==}
+
+  debounce@1.2.1:
+    resolution: {integrity: sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==}
+
+  debug@2.6.9:
+    resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
+    peerDependencies:
+      supports-color: '*'
+    peerDependenciesMeta:
+      supports-color:
+        optional: true
+
+  debug@4.3.4:
+    resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
+    engines: {node: '>=6.0'}
+    peerDependencies:
+      supports-color: '*'
+    peerDependenciesMeta:
+      supports-color:
+        optional: true
+
+  decode-named-character-reference@1.0.2:
+    resolution: {integrity: sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==}
+
+  decompress-response@6.0.0:
+    resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==}
+    engines: {node: '>=10'}
+
+  deep-extend@0.6.0:
+    resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==}
+    engines: {node: '>=4.0.0'}
+
+  deepmerge@4.2.2:
+    resolution: {integrity: sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==}
+    engines: {node: '>=0.10.0'}
+
+  deepmerge@4.3.1:
+    resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==}
+    engines: {node: '>=0.10.0'}
+
+  default-gateway@6.0.3:
+    resolution: {integrity: sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==}
+    engines: {node: '>= 10'}
+
+  defer-to-connect@2.0.1:
+    resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==}
+    engines: {node: '>=10'}
+
+  define-lazy-prop@2.0.0:
+    resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==}
+    engines: {node: '>=8'}
+
+  define-properties@1.1.4:
+    resolution: {integrity: sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==}
+    engines: {node: '>= 0.4'}
+
+  del@6.1.1:
+    resolution: {integrity: sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg==}
+    engines: {node: '>=10'}
+
+  delaunator@5.0.0:
+    resolution: {integrity: sha512-AyLvtyJdbv/U1GkiS6gUUzclRoAY4Gs75qkMygJJhU75LW4DNuSF2RMzpxs9jw9Oz1BobHjTdkG3zdP55VxAqw==}
+
+  depd@1.1.2:
+    resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==}
+    engines: {node: '>= 0.6'}
+
+  depd@2.0.0:
+    resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
+    engines: {node: '>= 0.8'}
+
+  dequal@2.0.3:
+    resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
+    engines: {node: '>=6'}
+
+  destroy@1.2.0:
+    resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==}
+    engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
+
+  detect-node@2.1.0:
+    resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==}
+
+  detect-port-alt@1.1.6:
+    resolution: {integrity: sha512-5tQykt+LqfJFBEYaDITx7S7cR7mJ/zQmLXZ2qt5w04ainYZw6tBf9dBunMjVeVOdYVRUzUOE4HkY5J7+uttb5Q==}
+    engines: {node: '>= 4.2.1'}
+    hasBin: true
+
+  detect-port@1.5.1:
+    resolution: {integrity: sha512-aBzdj76lueB6uUst5iAs7+0H/oOjqI5D16XUWxlWMIMROhcM0rfsNVk93zTngq1dDNpoXRr++Sus7ETAExppAQ==}
+    hasBin: true
+
+  devlop@1.1.0:
+    resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==}
+
+  didyoumean@1.2.2:
+    resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
+
+  diff@5.2.0:
+    resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==}
+    engines: {node: '>=0.3.1'}
+
+  dir-glob@3.0.1:
+    resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
+    engines: {node: '>=8'}
+
+  dlv@1.1.3:
+    resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==}
+
+  dns-equal@1.0.0:
+    resolution: {integrity: sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==}
+
+  dns-packet@5.4.0:
+    resolution: {integrity: sha512-EgqGeaBB8hLiHLZtp/IbaDQTL8pZ0+IvwzSHA6d7VyMDM+B9hgddEMa9xjK5oYnw0ci0JQ6g2XCD7/f6cafU6g==}
+    engines: {node: '>=6'}
+
+  dom-converter@0.2.0:
+    resolution: {integrity: sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==}
+
+  dom-serializer@1.4.1:
+    resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==}
+
+  dom-serializer@2.0.0:
+    resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==}
+
+  domelementtype@2.3.0:
+    resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==}
+
+  domhandler@4.3.1:
+    resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==}
+    engines: {node: '>= 4'}
+
+  domhandler@5.0.3:
+    resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==}
+    engines: {node: '>= 4'}
+
+  dompurify@3.0.8:
+    resolution: {integrity: sha512-b7uwreMYL2eZhrSCRC4ahLTeZcPZxSmYfmcQGXGkXiZSNW1X85v+SDM5KsWcpivIiUBH47Ji7NtyUdpLeF5JZQ==}
+
+  domutils@2.8.0:
+    resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==}
+
+  domutils@3.0.1:
+    resolution: {integrity: sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==}
+
+  dot-case@3.0.4:
+    resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==}
+
+  dot-prop@6.0.1:
+    resolution: {integrity: sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==}
+    engines: {node: '>=10'}
+
+  duplexer@0.1.2:
+    resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==}
+
+  eastasianwidth@0.2.0:
+    resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
+
+  ee-first@1.1.1:
+    resolution: {integrity: sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=}
+
+  electron-to-chromium@1.4.284:
+    resolution: {integrity: sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==}
+
+  electron-to-chromium@1.4.667:
+    resolution: {integrity: sha512-66L3pLlWhTNVUhnmSA5+qDM3fwnXsM6KAqE36e2w4KN0g6pkEtlT5bs41FQtQwVwKnfhNBXiWRLPs30HSxd7Kw==}
+
+  electron-to-chromium@1.4.796:
+    resolution: {integrity: sha512-NglN/xprcM+SHD2XCli4oC6bWe6kHoytcyLKCWXmRL854F0qhPhaYgUswUsglnPxYaNQIg2uMY4BvaomIf3kLA==}
+
+  elkjs@0.9.1:
+    resolution: {integrity: sha512-JWKDyqAdltuUcyxaECtYG6H4sqysXSLeoXuGUBfRNESMTkj+w+qdb0jya8Z/WI0jVd03WQtCGhS6FOFtlhD5FQ==}
+
+  emoji-regex@8.0.0:
+    resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
+
+  emoji-regex@9.2.2:
+    resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
+
+  emojilib@2.4.0:
+    resolution: {integrity: sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==}
+
+  emojis-list@3.0.0:
+    resolution: {integrity: sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==}
+    engines: {node: '>= 4'}
+
+  emoticon@4.0.1:
+    resolution: {integrity: sha512-dqx7eA9YaqyvYtUhJwT4rC1HIp82j5ybS1/vQ42ur+jBe17dJMwZE4+gvL1XadSFfxaPFFGt3Xsw+Y8akThDlw==}
+
+  encodeurl@1.0.2:
+    resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==}
+    engines: {node: '>= 0.8'}
+
+  enhanced-resolve@5.15.0:
+    resolution: {integrity: sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==}
+    engines: {node: '>=10.13.0'}
+
+  entities@2.2.0:
+    resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==}
+
+  entities@4.4.0:
+    resolution: {integrity: sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==}
+    engines: {node: '>=0.12'}
+
+  error-ex@1.3.2:
+    resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==}
+
+  es-module-lexer@1.4.1:
+    resolution: {integrity: sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==}
+
+  escalade@3.1.1:
+    resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==}
+    engines: {node: '>=6'}
+
+  escalade@3.1.2:
+    resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==}
+    engines: {node: '>=6'}
+
+  escape-goat@4.0.0:
+    resolution: {integrity: sha512-2Sd4ShcWxbx6OY1IHyla/CVNwvg7XwZVoXZHcSu9w9SReNP1EzzD5T8NWKIR38fIqEns9kDWKUQTXXAmlDrdPg==}
+    engines: {node: '>=12'}
+
+  escape-html@1.0.3:
+    resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
+
+  escape-string-regexp@1.0.5:
+    resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==}
+    engines: {node: '>=0.8.0'}
+
+  escape-string-regexp@4.0.0:
+    resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
+    engines: {node: '>=10'}
+
+  escape-string-regexp@5.0.0:
+    resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==}
+    engines: {node: '>=12'}
+
+  eslint-scope@5.1.1:
+    resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==}
+    engines: {node: '>=8.0.0'}
+
+  esprima@4.0.1:
+    resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==}
+    engines: {node: '>=4'}
+    hasBin: true
+
+  esrecurse@4.3.0:
+    resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==}
+    engines: {node: '>=4.0'}
+
+  estraverse@4.3.0:
+    resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==}
+    engines: {node: '>=4.0'}
+
+  estraverse@5.3.0:
+    resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
+    engines: {node: '>=4.0'}
+
+  estree-util-attach-comments@3.0.0:
+    resolution: {integrity: sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==}
+
+  estree-util-build-jsx@3.0.1:
+    resolution: {integrity: sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==}
+
+  estree-util-is-identifier-name@3.0.0:
+    resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==}
+
+  estree-util-to-js@2.0.0:
+    resolution: {integrity: sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==}
+
+  estree-util-value-to-estree@3.0.1:
+    resolution: {integrity: sha512-b2tdzTurEIbwRh+mKrEcaWfu1wgb8J1hVsgREg7FFiecWwK/PhO8X0kyc+0bIcKNtD4sqxIdNoRy6/p/TvECEA==}
+    engines: {node: '>=16.0.0'}
+
+  estree-util-visit@2.0.0:
+    resolution: {integrity: sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==}
+
+  estree-walker@3.0.3:
+    resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
+
+  esutils@2.0.3:
+    resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
+    engines: {node: '>=0.10.0'}
+
+  eta@2.2.0:
+    resolution: {integrity: sha512-UVQ72Rqjy/ZKQalzV5dCCJP80GrmPrMxh6NlNf+erV6ObL0ZFkhCstWRawS85z3smdr3d2wXPsZEY7rDPfGd2g==}
+    engines: {node: '>=6.0.0'}
+
+  etag@1.8.1:
+    resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
+    engines: {node: '>= 0.6'}
+
+  eval@0.1.8:
+    resolution: {integrity: sha512-EzV94NYKoO09GLXGjXj9JIlXijVck4ONSr5wiCWDvhsvj5jxSrzTmRU/9C1DyB6uToszLs8aifA6NQ7lEQdvFw==}
+    engines: {node: '>= 0.8'}
+
+  eventemitter3@4.0.7:
+    resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==}
+
+  events@3.3.0:
+    resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
+    engines: {node: '>=0.8.x'}
+
+  execa@5.1.1:
+    resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==}
+    engines: {node: '>=10'}
+
+  express@4.18.2:
+    resolution: {integrity: sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==}
+    engines: {node: '>= 0.10.0'}
+
+  extend-shallow@2.0.1:
+    resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==}
+    engines: {node: '>=0.10.0'}
+
+  extend@3.0.2:
+    resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==}
+
+  fast-deep-equal@3.1.3:
+    resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
+
+  fast-glob@3.3.2:
+    resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==}
+    engines: {node: '>=8.6.0'}
+
+  fast-json-stable-stringify@2.1.0:
+    resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
+
+  fast-url-parser@1.1.3:
+    resolution: {integrity: sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ==}
+
+  fastq@1.17.1:
+    resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==}
+
+  fault@2.0.1:
+    resolution: {integrity: sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==}
+
+  faye-websocket@0.11.4:
+    resolution: {integrity: sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==}
+    engines: {node: '>=0.8.0'}
+
+  feed@4.2.2:
+    resolution: {integrity: sha512-u5/sxGfiMfZNtJ3OvQpXcvotFpYkL0n9u9mM2vkui2nGo8b4wvDkJ8gAkYqbA8QpGyFCv3RK0Z+Iv+9veCS9bQ==}
+    engines: {node: '>=0.4.0'}
+
+  file-loader@6.2.0:
+    resolution: {integrity: sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==}
+    engines: {node: '>= 10.13.0'}
+    peerDependencies:
+      webpack: ^4.0.0 || ^5.0.0
+
+  filesize@8.0.7:
+    resolution: {integrity: sha512-pjmC+bkIF8XI7fWaH8KxHcZL3DPybs1roSKP4rKDvy20tAWwIObE4+JIseG2byfGKhud5ZnM4YSGKBz7Sh0ndQ==}
+    engines: {node: '>= 0.4.0'}
+
+  fill-range@7.0.1:
+    resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
+    engines: {node: '>=8'}
+
+  fill-range@7.1.1:
+    resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
+    engines: {node: '>=8'}
+
+  finalhandler@1.2.0:
+    resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==}
+    engines: {node: '>= 0.8'}
+
+  find-cache-dir@4.0.0:
+    resolution: {integrity: sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg==}
+    engines: {node: '>=14.16'}
+
+  find-up@3.0.0:
+    resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==}
+    engines: {node: '>=6'}
+
+  find-up@5.0.0:
+    resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
+    engines: {node: '>=10'}
+
+  find-up@6.3.0:
+    resolution: {integrity: sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==}
+    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+
+  flat@5.0.2:
+    resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==}
+    hasBin: true
+
+  follow-redirects@1.15.2:
+    resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==}
+    engines: {node: '>=4.0'}
+    peerDependencies:
+      debug: '*'
+    peerDependenciesMeta:
+      debug:
+        optional: true
+
+  foreground-child@3.3.0:
+    resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==}
+    engines: {node: '>=14'}
+
+  fork-ts-checker-webpack-plugin@6.5.2:
+    resolution: {integrity: sha512-m5cUmF30xkZ7h4tWUgTAcEaKmUW7tfyUyTqNNOz7OxWJ0v1VWKTcOvH8FWHUwSjlW/356Ijc9vi3XfcPstpQKA==}
+    engines: {node: '>=10', yarn: '>=1.0.0'}
+    peerDependencies:
+      eslint: '>= 6'
+      typescript: '>= 2.7'
+      vue-template-compiler: '*'
+      webpack: '>= 4'
+    peerDependenciesMeta:
+      eslint:
+        optional: true
+      vue-template-compiler:
+        optional: true
+
+  form-data-encoder@2.1.4:
+    resolution: {integrity: sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==}
+    engines: {node: '>= 14.17'}
+
+  format@0.2.2:
+    resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==}
+    engines: {node: '>=0.4.x'}
+
+  forwarded@0.2.0:
+    resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
+    engines: {node: '>= 0.6'}
+
+  fraction.js@4.2.0:
+    resolution: {integrity: sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==}
+
+  fraction.js@4.3.7:
+    resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
+
+  fresh@0.5.2:
+    resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==}
+    engines: {node: '>= 0.6'}
+
+  fs-extra@11.2.0:
+    resolution: {integrity: sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==}
+    engines: {node: '>=14.14'}
+
+  fs-extra@9.1.0:
+    resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==}
+    engines: {node: '>=10'}
+
+  fs-monkey@1.0.3:
+    resolution: {integrity: sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q==}
+
+  fs.realpath@1.0.0:
+    resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
+
+  fsevents@2.3.3:
+    resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
+    engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+    os: [darwin]
+
+  function-bind@1.1.2:
+    resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
+
+  gensync@1.0.0-beta.2:
+    resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
+    engines: {node: '>=6.9.0'}
+
+  get-intrinsic@1.1.3:
+    resolution: {integrity: sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==}
+
+  get-own-enumerable-property-symbols@3.0.2:
+    resolution: {integrity: sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==}
+
+  get-stream@6.0.1:
+    resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==}
+    engines: {node: '>=10'}
+
+  giscus@1.5.0:
+    resolution: {integrity: sha512-t3LL0qbSO3JXq3uyQeKpF5CegstGfKX/0gI6eDe1cmnI7D56R7j52yLdzw4pdKrg3VnufwCgCM3FDz7G1Qr6lg==}
+
+  github-slugger@1.5.0:
+    resolution: {integrity: sha512-wIh+gKBI9Nshz2o46B0B3f5k/W+WI9ZAv6y5Dn5WJ5SK1t0TnDimB4WE5rmTD05ZAIn8HALCZVmCsvj0w0v0lw==}
+
+  glob-parent@5.1.2:
+    resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
+    engines: {node: '>= 6'}
+
+  glob-parent@6.0.2:
+    resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
+    engines: {node: '>=10.13.0'}
+
+  glob-to-regexp@0.4.1:
+    resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==}
+
+  glob@10.4.5:
+    resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==}
+    hasBin: true
+
+  glob@7.2.3:
+    resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
+    deprecated: Glob versions prior to v9 are no longer supported
+
+  global-dirs@3.0.1:
+    resolution: {integrity: sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==}
+    engines: {node: '>=10'}
+
+  global-modules@2.0.0:
+    resolution: {integrity: sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==}
+    engines: {node: '>=6'}
+
+  global-prefix@3.0.0:
+    resolution: {integrity: sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==}
+    engines: {node: '>=6'}
+
+  globals@11.12.0:
+    resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==}
+    engines: {node: '>=4'}
+
+  globby@11.1.0:
+    resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==}
+    engines: {node: '>=10'}
+
+  globby@13.1.3:
+    resolution: {integrity: sha512-8krCNHXvlCgHDpegPzleMq07yMYTO2sXKASmZmquEYWEmCx6J5UTRbp5RwMJkTJGtcQ44YpiUYUiN0b9mzy8Bw==}
+    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+
+  got@12.6.1:
+    resolution: {integrity: sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==}
+    engines: {node: '>=14.16'}
+
+  graceful-fs@4.2.10:
+    resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==}
+
+  gray-matter@4.0.3:
+    resolution: {integrity: sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==}
+    engines: {node: '>=6.0'}
+
+  gzip-size@6.0.0:
+    resolution: {integrity: sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==}
+    engines: {node: '>=10'}
+
+  handle-thing@2.0.1:
+    resolution: {integrity: sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==}
+
+  has-flag@3.0.0:
+    resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==}
+    engines: {node: '>=4'}
+
+  has-flag@4.0.0:
+    resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
+    engines: {node: '>=8'}
+
+  has-property-descriptors@1.0.0:
+    resolution: {integrity: sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==}
+
+  has-symbols@1.0.3:
+    resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==}
+    engines: {node: '>= 0.4'}
+
+  has-yarn@3.0.0:
+    resolution: {integrity: sha512-IrsVwUHhEULx3R8f/aA8AHuEzAorplsab/v8HBzEiIukwq5i/EC+xmOW+HfP1OaDP+2JkgT1yILHN2O3UFIbcA==}
+    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+
+  has@1.0.3:
+    resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==}
+    engines: {node: '>= 0.4.0'}
+
+  hasown@2.0.2:
+    resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
+    engines: {node: '>= 0.4'}
+
+  hast-util-from-parse5@8.0.1:
+    resolution: {integrity: sha512-Er/Iixbc7IEa7r/XLtuG52zoqn/b3Xng/w6aZQ0xGVxzhw5xUFxcRqdPzP6yFi/4HBYRaifaI5fQ1RH8n0ZeOQ==}
+
+  hast-util-parse-selector@4.0.0:
+    resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==}
+
+  hast-util-raw@9.0.2:
+    resolution: {integrity: sha512-PldBy71wO9Uq1kyaMch9AHIghtQvIwxBUkv823pKmkTM3oV1JxtsTNYdevMxvUHqcnOAuO65JKU2+0NOxc2ksA==}
+
+  hast-util-to-estree@3.1.0:
+    resolution: {integrity: sha512-lfX5g6hqVh9kjS/B9E2gSkvHH4SZNiQFiqWS0x9fENzEl+8W12RqdRxX6d/Cwxi30tPQs3bIO+aolQJNp1bIyw==}
+
+  hast-util-to-jsx-runtime@2.3.0:
+    resolution: {integrity: sha512-H/y0+IWPdsLLS738P8tDnrQ8Z+dj12zQQ6WC11TIM21C8WFVoIxcqWXf2H3hiTVZjF1AWqoimGwrTWecWrnmRQ==}
+
+  hast-util-to-parse5@8.0.0:
+    resolution: {integrity: sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==}
+
+  hast-util-whitespace@3.0.0:
+    resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==}
+
+  hastscript@8.0.0:
+    resolution: {integrity: sha512-dMOtzCEd3ABUeSIISmrETiKuyydk1w0pa+gE/uormcTpSYuaNJPbX1NU3JLyscSLjwAQM8bWMhhIlnCqnRvDTw==}
+
+  he@1.2.0:
+    resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
+    hasBin: true
+
+  heap@0.2.7:
+    resolution: {integrity: sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==}
+
+  history@4.10.1:
+    resolution: {integrity: sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==}
+
+  hoist-non-react-statics@3.3.2:
+    resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==}
+
+  hpack.js@2.1.6:
+    resolution: {integrity: sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==}
+
+  html-entities@2.3.3:
+    resolution: {integrity: sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==}
+
+  html-escaper@2.0.2:
+    resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==}
+
+  html-minifier-terser@6.1.0:
+    resolution: {integrity: sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==}
+    engines: {node: '>=12'}
+    hasBin: true
+
+  html-minifier-terser@7.2.0:
+    resolution: {integrity: sha512-tXgn3QfqPIpGl9o+K5tpcj3/MN4SfLtsx2GWwBC3SSd0tXQGyF3gsSqad8loJgKZGM3ZxbYDd5yhiBIdWpmvLA==}
+    engines: {node: ^14.13.1 || >=16.0.0}
+    hasBin: true
+
+  html-tags@3.3.1:
+    resolution: {integrity: sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==}
+    engines: {node: '>=8'}
+
+  html-void-elements@3.0.0:
+    resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==}
+
+  html-webpack-plugin@5.6.0:
+    resolution: {integrity: sha512-iwaY4wzbe48AfKLZ/Cc8k0L+FKG6oSNRaZ8x5A/T/IVDGyXcbHncM9TdDa93wn0FsSm82FhTKW7f3vS61thXAw==}
+    engines: {node: '>=10.13.0'}
+    peerDependencies:
+      '@rspack/core': 0.x || 1.x
+      webpack: ^5.20.0
+    peerDependenciesMeta:
+      '@rspack/core':
+        optional: true
+      webpack:
+        optional: true
+
+  htmlparser2@6.1.0:
+    resolution: {integrity: sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==}
+
+  htmlparser2@8.0.1:
+    resolution: {integrity: sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA==}
+
+  http-cache-semantics@4.1.1:
+    resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==}
+
+  http-deceiver@1.2.7:
+    resolution: {integrity: sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==}
+
+  http-errors@1.6.3:
+    resolution: {integrity: sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==}
+    engines: {node: '>= 0.6'}
+
+  http-errors@2.0.0:
+    resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==}
+    engines: {node: '>= 0.8'}
+
+  http-parser-js@0.5.8:
+    resolution: {integrity: sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==}
+
+  http-proxy-middleware@2.0.6:
+    resolution: {integrity: sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==}
+    engines: {node: '>=12.0.0'}
+    peerDependencies:
+      '@types/express': ^4.17.13
+    peerDependenciesMeta:
+      '@types/express':
+        optional: true
+
+  http-proxy@1.18.1:
+    resolution: {integrity: sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==}
+    engines: {node: '>=8.0.0'}
+
+  http2-wrapper@2.2.1:
+    resolution: {integrity: sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==}
+    engines: {node: '>=10.19.0'}
+
+  human-signals@2.1.0:
+    resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}
+    engines: {node: '>=10.17.0'}
+
+  iconv-lite@0.4.24:
+    resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
+    engines: {node: '>=0.10.0'}
+
+  iconv-lite@0.6.3:
+    resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
+    engines: {node: '>=0.10.0'}
+
+  icss-utils@5.1.0:
+    resolution: {integrity: sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==}
+    engines: {node: ^10 || ^12 || >= 14}
+    peerDependencies:
+      postcss: ^8.1.0
+
+  ignore@5.2.4:
+    resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==}
+    engines: {node: '>= 4'}
+
+  image-size@1.0.2:
+    resolution: {integrity: sha512-xfOoWjceHntRb3qFCrh5ZFORYH8XCdYpASltMhZ/Q0KZiOwjdE/Yl2QCiWdwD+lygV5bMCvauzgu5PxBX/Yerg==}
+    engines: {node: '>=14.0.0'}
+    hasBin: true
+
+  immer@9.0.17:
+    resolution: {integrity: sha512-+hBruaLSQvkPfxRiTLK/mi4vLH+/VQS6z2KJahdoxlleFOI8ARqzOF17uy12eFDlqWmPoygwc5evgwcp+dlHhg==}
+
+  import-fresh@3.3.0:
+    resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==}
+    engines: {node: '>=6'}
+
+  import-lazy@4.0.0:
+    resolution: {integrity: sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==}
+    engines: {node: '>=8'}
+
+  imurmurhash@0.1.4:
+    resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
+    engines: {node: '>=0.8.19'}
+
+  indent-string@4.0.0:
+    resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==}
+    engines: {node: '>=8'}
+
+  infima@0.2.0-alpha.43:
+    resolution: {integrity: sha512-2uw57LvUqW0rK/SWYnd/2rRfxNA5DDNOh33jxF7fy46VWoNhGxiUQyVZHbBMjQ33mQem0cjdDVwgWVAmlRfgyQ==}
+    engines: {node: '>=12'}
+
+  inflight@1.0.6:
+    resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
+    deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
+
+  inherits@2.0.3:
+    resolution: {integrity: sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==}
+
+  inherits@2.0.4:
+    resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
+
+  ini@1.3.8:
+    resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==}
+
+  ini@2.0.0:
+    resolution: {integrity: sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==}
+    engines: {node: '>=10'}
+
+  inline-style-parser@0.1.1:
+    resolution: {integrity: sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==}
+
+  inline-style-parser@0.2.2:
+    resolution: {integrity: sha512-EcKzdTHVe8wFVOGEYXiW9WmJXPjqi1T+234YpJr98RiFYKHV3cdy1+3mkTE+KHTHxFFLH51SfaGOoUdW+v7ViQ==}
+
+  internmap@1.0.1:
+    resolution: {integrity: sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==}
+
+  internmap@2.0.3:
+    resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==}
+    engines: {node: '>=12'}
+
+  interpret@1.4.0:
+    resolution: {integrity: sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==}
+    engines: {node: '>= 0.10'}
+
+  invariant@2.2.4:
+    resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==}
+
+  ipaddr.js@1.9.1:
+    resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
+    engines: {node: '>= 0.10'}
+
+  ipaddr.js@2.0.1:
+    resolution: {integrity: sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==}
+    engines: {node: '>= 10'}
+
+  is-alphabetical@2.0.1:
+    resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==}
+
+  is-alphanumerical@2.0.1:
+    resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==}
+
+  is-arrayish@0.2.1:
+    resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
+
+  is-binary-path@2.1.0:
+    resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
+    engines: {node: '>=8'}
+
+  is-ci@3.0.1:
+    resolution: {integrity: sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==}
+    hasBin: true
+
+  is-core-module@2.15.1:
+    resolution: {integrity: sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==}
+    engines: {node: '>= 0.4'}
+
+  is-decimal@2.0.1:
+    resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==}
+
+  is-docker@2.2.1:
+    resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==}
+    engines: {node: '>=8'}
+    hasBin: true
+
+  is-extendable@0.1.1:
+    resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==}
+    engines: {node: '>=0.10.0'}
+
+  is-extglob@2.1.1:
+    resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
+    engines: {node: '>=0.10.0'}
+
+  is-fullwidth-code-point@3.0.0:
+    resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
+    engines: {node: '>=8'}
+
+  is-glob@4.0.3:
+    resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
+    engines: {node: '>=0.10.0'}
+
+  is-hexadecimal@2.0.1:
+    resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==}
+
+  is-installed-globally@0.4.0:
+    resolution: {integrity: sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==}
+    engines: {node: '>=10'}
+
+  is-npm@6.0.0:
+    resolution: {integrity: sha512-JEjxbSmtPSt1c8XTkVrlujcXdKV1/tvuQ7GwKcAlyiVLeYFQ2VHat8xfrDJsIkhCdF/tZ7CiIR3sy141c6+gPQ==}
+    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+
+  is-number@7.0.0:
+    resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
+    engines: {node: '>=0.12.0'}
+
+  is-obj@1.0.1:
+    resolution: {integrity: sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==}
+    engines: {node: '>=0.10.0'}
+
+  is-obj@2.0.0:
+    resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==}
+    engines: {node: '>=8'}
+
+  is-path-cwd@2.2.0:
+    resolution: {integrity: sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==}
+    engines: {node: '>=6'}
+
+  is-path-inside@3.0.3:
+    resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==}
+    engines: {node: '>=8'}
+
+  is-plain-obj@3.0.0:
+    resolution: {integrity: sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==}
+    engines: {node: '>=10'}
+
+  is-plain-obj@4.1.0:
+    resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==}
+    engines: {node: '>=12'}
+
+  is-plain-object@2.0.4:
+    resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==}
+    engines: {node: '>=0.10.0'}
+
+  is-plain-object@5.0.0:
+    resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==}
+    engines: {node: '>=0.10.0'}
+
+  is-reference@3.0.2:
+    resolution: {integrity: sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==}
+
+  is-regexp@1.0.0:
+    resolution: {integrity: sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==}
+    engines: {node: '>=0.10.0'}
+
+  is-root@2.1.0:
+    resolution: {integrity: sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg==}
+    engines: {node: '>=6'}
+
+  is-stream@2.0.1:
+    resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==}
+    engines: {node: '>=8'}
+
+  is-typedarray@1.0.0:
+    resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==}
+
+  is-wsl@2.2.0:
+    resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==}
+    engines: {node: '>=8'}
+
+  is-yarn-global@0.4.1:
+    resolution: {integrity: sha512-/kppl+R+LO5VmhYSEWARUFjodS25D68gvj8W7z0I7OWhUla5xWu8KL6CtB2V0R6yqhnRgbcaREMr4EEM6htLPQ==}
+    engines: {node: '>=12'}
+
+  isarray@0.0.1:
+    resolution: {integrity: sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==}
+
+  isarray@1.0.0:
+    resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==}
+
+  isexe@2.0.0:
+    resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
+
+  isobject@3.0.1:
+    resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==}
+    engines: {node: '>=0.10.0'}
+
+  jackspeak@3.4.3:
+    resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
+
+  jest-util@29.7.0:
+    resolution: {integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==}
+    engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+  jest-worker@27.5.1:
+    resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==}
+    engines: {node: '>= 10.13.0'}
+
+  jest-worker@29.7.0:
+    resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==}
+    engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+  jiti@1.21.6:
+    resolution: {integrity: sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==}
+    hasBin: true
+
+  joi@17.12.1:
+    resolution: {integrity: sha512-vtxmq+Lsc5SlfqotnfVjlViWfOL9nt/avKNbKYizwf6gsCfq9NYY/ceYRMFD8XDdrjJ9abJyScWmhmIiy+XRtQ==}
+
+  js-tokens@4.0.0:
+    resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
+
+  js-yaml@3.14.1:
+    resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==}
+    hasBin: true
+
+  js-yaml@4.1.0:
+    resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
+    hasBin: true
+
+  jsesc@0.5.0:
+    resolution: {integrity: sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==}
+    hasBin: true
+
+  jsesc@2.5.2:
+    resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==}
+    engines: {node: '>=4'}
+    hasBin: true
+
+  json-buffer@3.0.1:
+    resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
+
+  json-parse-even-better-errors@2.3.1:
+    resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==}
+
+  json-schema-traverse@0.4.1:
+    resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
+
+  json-schema-traverse@1.0.0:
+    resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
+
+  json5@2.2.3:
+    resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
+    engines: {node: '>=6'}
+    hasBin: true
+
+  jsonfile@6.1.0:
+    resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==}
+
+  jsonp@0.2.1:
+    resolution: {integrity: sha512-pfog5gdDxPdV4eP7Kg87M8/bHgshlZ5pybl+yKxAnCZ5O7lCIn7Ixydj03wOlnDQesky2BPyA91SQ+5Y/mNwzw==}
+
+  keyv@4.5.4:
+    resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
+
+  khroma@2.0.0:
+    resolution: {integrity: sha512-2J8rDNlQWbtiNYThZRvmMv5yt44ZakX+Tz5ZIp/mN1pt4snn+m030Va5Z4v8xA0cQFDXBwO/8i42xL4QPsVk3g==}
+
+  kind-of@6.0.3:
+    resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==}
+    engines: {node: '>=0.10.0'}
+
+  kleur@3.0.3:
+    resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==}
+    engines: {node: '>=6'}
+
+  kleur@4.1.5:
+    resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==}
+    engines: {node: '>=6'}
+
+  latest-version@7.0.0:
+    resolution: {integrity: sha512-KvNT4XqAMzdcL6ka6Tl3i2lYeFDgXNCuIX+xNx6ZMVR1dFq+idXd9FLKNMOIx0t9mJ9/HudyX4oZWXZQ0UJHeg==}
+    engines: {node: '>=14.16'}
+
+  launch-editor@2.6.1:
+    resolution: {integrity: sha512-eB/uXmFVpY4zezmGp5XtU21kwo7GBbKB+EQ+UZeWtGb9yAM5xt/Evk+lYH3eRNAtId+ej4u7TYPFZ07w4s7rRw==}
+
+  layout-base@1.0.2:
+    resolution: {integrity: sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==}
+
+  leven@3.1.0:
+    resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==}
+    engines: {node: '>=6'}
+
+  lilconfig@2.1.0:
+    resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==}
+    engines: {node: '>=10'}
+
+  lilconfig@3.1.2:
+    resolution: {integrity: sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==}
+    engines: {node: '>=14'}
+
+  lines-and-columns@1.2.4:
+    resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
+
+  lit-element@4.0.4:
+    resolution: {integrity: sha512-98CvgulX6eCPs6TyAIQoJZBCQPo80rgXR+dVBs61cstJXqtI+USQZAbA4gFHh6L/mxBx9MrgPLHLsUgDUHAcCQ==}
+
+  lit-html@3.1.2:
+    resolution: {integrity: sha512-3OBZSUrPnAHoKJ9AMjRL/m01YJxQMf+TMHanNtTHG68ubjnZxK0RFl102DPzsw4mWnHibfZIBJm3LWCZ/LmMvg==}
+
+  lit@3.1.2:
+    resolution: {integrity: sha512-VZx5iAyMtX7CV4K8iTLdCkMaYZ7ipjJZ0JcSdJ0zIdGxxyurjIn7yuuSxNBD7QmjvcNJwr0JS4cAdAtsy7gZ6w==}
+
+  loader-runner@4.3.0:
+    resolution: {integrity: sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==}
+    engines: {node: '>=6.11.5'}
+
+  loader-utils@2.0.4:
+    resolution: {integrity: sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==}
+    engines: {node: '>=8.9.0'}
+
+  loader-utils@3.2.1:
+    resolution: {integrity: sha512-ZvFw1KWS3GVyYBYb7qkmRM/WwL2TQQBxgCK62rlvm4WpVQ23Nb4tYjApUlfjrEGvOs7KHEsmyUn75OHZrJMWPw==}
+    engines: {node: '>= 12.13.0'}
+
+  locate-path@3.0.0:
+    resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==}
+    engines: {node: '>=6'}
+
+  locate-path@6.0.0:
+    resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
+    engines: {node: '>=10'}
+
+  locate-path@7.2.0:
+    resolution: {integrity: sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==}
+    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+
+  lodash-es@4.17.21:
+    resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==}
+
+  lodash.castarray@4.4.0:
+    resolution: {integrity: sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==}
+
+  lodash.debounce@4.0.8:
+    resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==}
+
+  lodash.isplainobject@4.0.6:
+    resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==}
+
+  lodash.memoize@4.1.2:
+    resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==}
+
+  lodash.merge@4.6.2:
+    resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
+
+  lodash.uniq@4.5.0:
+    resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==}
+
+  lodash@4.17.21:
+    resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
+
+  longest-streak@3.1.0:
+    resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==}
+
+  loose-envify@1.4.0:
+    resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
+    hasBin: true
+
+  lower-case@2.0.2:
+    resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==}
+
+  lowercase-keys@3.0.0:
+    resolution: {integrity: sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==}
+    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+
+  lru-cache@10.4.3:
+    resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
+
+  lru-cache@5.1.1:
+    resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
+
+  lru-cache@6.0.0:
+    resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}
+    engines: {node: '>=10'}
+
+  markdown-extensions@2.0.0:
+    resolution: {integrity: sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==}
+    engines: {node: '>=16'}
+
+  markdown-table@3.0.3:
+    resolution: {integrity: sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==}
+
+  mdast-util-directive@3.0.0:
+    resolution: {integrity: sha512-JUpYOqKI4mM3sZcNxmF/ox04XYFFkNwr0CFlrQIkCwbvH0xzMCqkMqAde9wRd80VAhaUrwFwKm2nxretdT1h7Q==}
+
+  mdast-util-find-and-replace@3.0.1:
+    resolution: {integrity: sha512-SG21kZHGC3XRTSUhtofZkBzZTJNM5ecCi0SK2IMKmSXR8vO3peL+kb1O0z7Zl83jKtutG4k5Wv/W7V3/YHvzPA==}
+
+  mdast-util-from-markdown@1.3.1:
+    resolution: {integrity: sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww==}
+
+  mdast-util-from-markdown@2.0.0:
+    resolution: {integrity: sha512-n7MTOr/z+8NAX/wmhhDji8O3bRvPTV/U0oTCaZJkjhPSKTPhS3xufVhKGF8s1pJ7Ox4QgoIU7KHseh09S+9rTA==}
+
+  mdast-util-frontmatter@2.0.1:
+    resolution: {integrity: sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA==}
+
+  mdast-util-gfm-autolink-literal@2.0.0:
+    resolution: {integrity: sha512-FyzMsduZZHSc3i0Px3PQcBT4WJY/X/RCtEJKuybiC6sjPqLv7h1yqAkmILZtuxMSsUyaLUWNp71+vQH2zqp5cg==}
+
+  mdast-util-gfm-footnote@2.0.0:
+    resolution: {integrity: sha512-5jOT2boTSVkMnQ7LTrd6n/18kqwjmuYqo7JUPe+tRCY6O7dAuTFMtTPauYYrMPpox9hlN0uOx/FL8XvEfG9/mQ==}
+
+  mdast-util-gfm-strikethrough@2.0.0:
+    resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==}
+
+  mdast-util-gfm-table@2.0.0:
+    resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==}
+
+  mdast-util-gfm-task-list-item@2.0.0:
+    resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==}
+
+  mdast-util-gfm@3.0.0:
+    resolution: {integrity: sha512-dgQEX5Amaq+DuUqf26jJqSK9qgixgd6rYDHAv4aTBuA92cTknZlKpPfa86Z/s8Dj8xsAQpFfBmPUHWJBWqS4Bw==}
+
+  mdast-util-mdx-expression@2.0.0:
+    resolution: {integrity: sha512-fGCu8eWdKUKNu5mohVGkhBXCXGnOTLuFqOvGMvdikr+J1w7lDJgxThOKpwRWzzbyXAU2hhSwsmssOY4yTokluw==}
+
+  mdast-util-mdx-jsx@3.1.0:
+    resolution: {integrity: sha512-A8AJHlR7/wPQ3+Jre1+1rq040fX9A4Q1jG8JxmSNp/PLPHg80A6475wxTp3KzHpApFH6yWxFotHrJQA3dXP6/w==}
+
+  mdast-util-mdx@3.0.0:
+    resolution: {integrity: sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==}
+
+  mdast-util-mdxjs-esm@2.0.1:
+    resolution: {integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==}
+
+  mdast-util-phrasing@4.1.0:
+    resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==}
+
+  mdast-util-to-hast@13.1.0:
+    resolution: {integrity: sha512-/e2l/6+OdGp/FB+ctrJ9Avz71AN/GRH3oi/3KAx/kMnoUsD6q0woXlDT8lLEeViVKE7oZxE7RXzvO3T8kF2/sA==}
+
+  mdast-util-to-markdown@2.1.0:
+    resolution: {integrity: sha512-SR2VnIEdVNCJbP6y7kVTJgPLifdr8WEU440fQec7qHoHOUz/oJ2jmNRqdDQ3rbiStOXb2mCDGTuwsK5OPUgYlQ==}
+
+  mdast-util-to-string@3.2.0:
+    resolution: {integrity: sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==}
+
+  mdast-util-to-string@4.0.0:
+    resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==}
+
+  mdn-data@2.0.28:
+    resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==}
+
+  mdn-data@2.0.30:
+    resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==}
+
+  media-typer@0.3.0:
+    resolution: {integrity: sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=}
+    engines: {node: '>= 0.6'}
+
+  memfs@3.4.12:
+    resolution: {integrity: sha512-BcjuQn6vfqP+k100e0E9m61Hyqa//Brp+I3f0OBmN0ATHlFA8vx3Lt8z57R3u2bPqe3WGDBC+nF72fTH7isyEw==}
+    engines: {node: '>= 4.0.0'}
+
+  merge-descriptors@1.0.1:
+    resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==}
+
+  merge-stream@2.0.0:
+    resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
+
+  merge2@1.4.1:
+    resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
+    engines: {node: '>= 8'}
+
+  mermaid@10.8.0:
+    resolution: {integrity: sha512-9CzfSreRjdDJxX796+jW4zjEq0DVw5xVF0nWsqff8OTbrt+ml0TZ5PyYUjjUZJa2NYxYJZZXewEquxGiM8qZEA==}
+
+  methods@1.1.2:
+    resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==}
+    engines: {node: '>= 0.6'}
+
+  micromark-core-commonmark@1.1.0:
+    resolution: {integrity: sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==}
+
+  micromark-core-commonmark@2.0.0:
+    resolution: {integrity: sha512-jThOz/pVmAYUtkroV3D5c1osFXAMv9e0ypGDOIZuCeAe91/sD6BoE2Sjzt30yuXtwOYUmySOhMas/PVyh02itA==}
+
+  micromark-extension-directive@3.0.0:
+    resolution: {integrity: sha512-61OI07qpQrERc+0wEysLHMvoiO3s2R56x5u7glHq2Yqq6EHbH4dW25G9GfDdGCDYqA21KE6DWgNSzxSwHc2hSg==}
+
+  micromark-extension-frontmatter@2.0.0:
+    resolution: {integrity: sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg==}
+
+  micromark-extension-gfm-autolink-literal@2.0.0:
+    resolution: {integrity: sha512-rTHfnpt/Q7dEAK1Y5ii0W8bhfJlVJFnJMHIPisfPK3gpVNuOP0VnRl96+YJ3RYWV/P4gFeQoGKNlT3RhuvpqAg==}
+
+  micromark-extension-gfm-footnote@2.0.0:
+    resolution: {integrity: sha512-6Rzu0CYRKDv3BfLAUnZsSlzx3ak6HAoI85KTiijuKIz5UxZxbUI+pD6oHgw+6UtQuiRwnGRhzMmPRv4smcz0fg==}
+
+  micromark-extension-gfm-strikethrough@2.0.0:
+    resolution: {integrity: sha512-c3BR1ClMp5fxxmwP6AoOY2fXO9U8uFMKs4ADD66ahLTNcwzSCyRVU4k7LPV5Nxo/VJiR4TdzxRQY2v3qIUceCw==}
+
+  micromark-extension-gfm-table@2.0.0:
+    resolution: {integrity: sha512-PoHlhypg1ItIucOaHmKE8fbin3vTLpDOUg8KAr8gRCF1MOZI9Nquq2i/44wFvviM4WuxJzc3demT8Y3dkfvYrw==}
+
+  micromark-extension-gfm-tagfilter@2.0.0:
+    resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==}
+
+  micromark-extension-gfm-task-list-item@2.0.1:
+    resolution: {integrity: sha512-cY5PzGcnULaN5O7T+cOzfMoHjBW7j+T9D2sucA5d/KbsBTPcYdebm9zUd9zzdgJGCwahV+/W78Z3nbulBYVbTw==}
+
+  micromark-extension-gfm@3.0.0:
+    resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==}
+
+  micromark-extension-mdx-expression@3.0.0:
+    resolution: {integrity: sha512-sI0nwhUDz97xyzqJAbHQhp5TfaxEvZZZ2JDqUo+7NvyIYG6BZ5CPPqj2ogUoPJlmXHBnyZUzISg9+oUmU6tUjQ==}
+
+  micromark-extension-mdx-jsx@3.0.0:
+    resolution: {integrity: sha512-uvhhss8OGuzR4/N17L1JwvmJIpPhAd8oByMawEKx6NVdBCbesjH4t+vjEp3ZXft9DwvlKSD07fCeI44/N0Vf2w==}
+
+  micromark-extension-mdx-md@2.0.0:
+    resolution: {integrity: sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==}
+
+  micromark-extension-mdxjs-esm@3.0.0:
+    resolution: {integrity: sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==}
+
+  micromark-extension-mdxjs@3.0.0:
+    resolution: {integrity: sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==}
+
+  micromark-factory-destination@1.1.0:
+    resolution: {integrity: sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==}
+
+  micromark-factory-destination@2.0.0:
+    resolution: {integrity: sha512-j9DGrQLm/Uhl2tCzcbLhy5kXsgkHUrjJHg4fFAeoMRwJmJerT9aw4FEhIbZStWN8A3qMwOp1uzHr4UL8AInxtA==}
+
+  micromark-factory-label@1.1.0:
+    resolution: {integrity: sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w==}
+
+  micromark-factory-label@2.0.0:
+    resolution: {integrity: sha512-RR3i96ohZGde//4WSe/dJsxOX6vxIg9TimLAS3i4EhBAFx8Sm5SmqVfR8E87DPSR31nEAjZfbt91OMZWcNgdZw==}
+
+  micromark-factory-mdx-expression@2.0.1:
+    resolution: {integrity: sha512-F0ccWIUHRLRrYp5TC9ZYXmZo+p2AM13ggbsW4T0b5CRKP8KHVRB8t4pwtBgTxtjRmwrK0Irwm7vs2JOZabHZfg==}
+
+  micromark-factory-space@1.1.0:
+    resolution: {integrity: sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==}
+
+  micromark-factory-space@2.0.0:
+    resolution: {integrity: sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==}
+
+  micromark-factory-title@1.1.0:
+    resolution: {integrity: sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ==}
+
+  micromark-factory-title@2.0.0:
+    resolution: {integrity: sha512-jY8CSxmpWLOxS+t8W+FG3Xigc0RDQA9bKMY/EwILvsesiRniiVMejYTE4wumNc2f4UbAa4WsHqe3J1QS1sli+A==}
+
+  micromark-factory-whitespace@1.1.0:
+    resolution: {integrity: sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ==}
+
+  micromark-factory-whitespace@2.0.0:
+    resolution: {integrity: sha512-28kbwaBjc5yAI1XadbdPYHX/eDnqaUFVikLwrO7FDnKG7lpgxnvk/XGRhX/PN0mOZ+dBSZ+LgunHS+6tYQAzhA==}
+
+  micromark-util-character@1.2.0:
+    resolution: {integrity: sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==}
+
+  micromark-util-character@2.1.0:
+    resolution: {integrity: sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==}
+
+  micromark-util-chunked@1.1.0:
+    resolution: {integrity: sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==}
+
+  micromark-util-chunked@2.0.0:
+    resolution: {integrity: sha512-anK8SWmNphkXdaKgz5hJvGa7l00qmcaUQoMYsBwDlSKFKjc6gjGXPDw3FNL3Nbwq5L8gE+RCbGqTw49FK5Qyvg==}
+
+  micromark-util-classify-character@1.1.0:
+    resolution: {integrity: sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw==}
+
+  micromark-util-classify-character@2.0.0:
+    resolution: {integrity: sha512-S0ze2R9GH+fu41FA7pbSqNWObo/kzwf8rN/+IGlW/4tC6oACOs8B++bh+i9bVyNnwCcuksbFwsBme5OCKXCwIw==}
+
+  micromark-util-combine-extensions@1.1.0:
+    resolution: {integrity: sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA==}
+
+  micromark-util-combine-extensions@2.0.0:
+    resolution: {integrity: sha512-vZZio48k7ON0fVS3CUgFatWHoKbbLTK/rT7pzpJ4Bjp5JjkZeasRfrS9wsBdDJK2cJLHMckXZdzPSSr1B8a4oQ==}
+
+  micromark-util-decode-numeric-character-reference@1.1.0:
+    resolution: {integrity: sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw==}
+
+  micromark-util-decode-numeric-character-reference@2.0.1:
+    resolution: {integrity: sha512-bmkNc7z8Wn6kgjZmVHOX3SowGmVdhYS7yBpMnuMnPzDq/6xwVA604DuOXMZTO1lvq01g+Adfa0pE2UKGlxL1XQ==}
+
+  micromark-util-decode-string@1.1.0:
+    resolution: {integrity: sha512-YphLGCK8gM1tG1bd54azwyrQRjCFcmgj2S2GoJDNnh4vYtnL38JS8M4gpxzOPNyHdNEpheyWXCTnnTDY3N+NVQ==}
+
+  micromark-util-decode-string@2.0.0:
+    resolution: {integrity: sha512-r4Sc6leeUTn3P6gk20aFMj2ntPwn6qpDZqWvYmAG6NgvFTIlj4WtrAudLi65qYoaGdXYViXYw2pkmn7QnIFasA==}
+
+  micromark-util-encode@1.1.0:
+    resolution: {integrity: sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==}
+
+  micromark-util-encode@2.0.0:
+    resolution: {integrity: sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA==}
+
+  micromark-util-events-to-acorn@2.0.2:
+    resolution: {integrity: sha512-Fk+xmBrOv9QZnEDguL9OI9/NQQp6Hz4FuQ4YmCb/5V7+9eAh1s6AYSvL20kHkD67YIg7EpE54TiSlcsf3vyZgA==}
+
+  micromark-util-html-tag-name@1.2.0:
+    resolution: {integrity: sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q==}
+
+  micromark-util-html-tag-name@2.0.0:
+    resolution: {integrity: sha512-xNn4Pqkj2puRhKdKTm8t1YHC/BAjx6CEwRFXntTaRf/x16aqka6ouVoutm+QdkISTlT7e2zU7U4ZdlDLJd2Mcw==}
+
+  micromark-util-normalize-identifier@1.1.0:
+    resolution: {integrity: sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q==}
+
+  micromark-util-normalize-identifier@2.0.0:
+    resolution: {integrity: sha512-2xhYT0sfo85FMrUPtHcPo2rrp1lwbDEEzpx7jiH2xXJLqBuy4H0GgXk5ToU8IEwoROtXuL8ND0ttVa4rNqYK3w==}
+
+  micromark-util-resolve-all@1.1.0:
+    resolution: {integrity: sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA==}
+
+  micromark-util-resolve-all@2.0.0:
+    resolution: {integrity: sha512-6KU6qO7DZ7GJkaCgwBNtplXCvGkJToU86ybBAUdavvgsCiG8lSSvYxr9MhwmQ+udpzywHsl4RpGJsYWG1pDOcA==}
+
+  micromark-util-sanitize-uri@1.2.0:
+    resolution: {integrity: sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A==}
+
+  micromark-util-sanitize-uri@2.0.0:
+    resolution: {integrity: sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw==}
+
+  micromark-util-subtokenize@1.1.0:
+    resolution: {integrity: sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A==}
+
+  micromark-util-subtokenize@2.0.0:
+    resolution: {integrity: sha512-vc93L1t+gpR3p8jxeVdaYlbV2jTYteDje19rNSS/H5dlhxUYll5Fy6vJ2cDwP8RnsXi818yGty1ayP55y3W6fg==}
+
+  micromark-util-symbol@1.1.0:
+    resolution: {integrity: sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==}
+
+  micromark-util-symbol@2.0.0:
+    resolution: {integrity: sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==}
+
+  micromark-util-types@1.1.0:
+    resolution: {integrity: sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==}
+
+  micromark-util-types@2.0.0:
+    resolution: {integrity: sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w==}
+
+  micromark@3.2.0:
+    resolution: {integrity: sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==}
+
+  micromark@4.0.0:
+    resolution: {integrity: sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ==}
+
+  micromatch@4.0.5:
+    resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==}
+    engines: {node: '>=8.6'}
+
+  micromatch@4.0.8:
+    resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
+    engines: {node: '>=8.6'}
+
+  mime-db@1.33.0:
+    resolution: {integrity: sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==}
+    engines: {node: '>= 0.6'}
+
+  mime-db@1.52.0:
+    resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
+    engines: {node: '>= 0.6'}
+
+  mime-types@2.1.18:
+    resolution: {integrity: sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==}
+    engines: {node: '>= 0.6'}
+
+  mime-types@2.1.35:
+    resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
+    engines: {node: '>= 0.6'}
+
+  mime@1.6.0:
+    resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==}
+    engines: {node: '>=4'}
+    hasBin: true
+
+  mimic-fn@2.1.0:
+    resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
+    engines: {node: '>=6'}
+
+  mimic-response@3.1.0:
+    resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==}
+    engines: {node: '>=10'}
+
+  mimic-response@4.0.0:
+    resolution: {integrity: sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==}
+    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+
+  mini-css-extract-plugin@2.8.0:
+    resolution: {integrity: sha512-CxmUYPFcTgET1zImteG/LZOy/4T5rTojesQXkSNBiquhydn78tfbCE9sjIjnJ/UcjNjOC1bphTCCW5rrS7cXAg==}
+    engines: {node: '>= 12.13.0'}
+    peerDependencies:
+      webpack: ^5.0.0
+
+  minimalistic-assert@1.0.1:
+    resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==}
+
+  minimatch@3.1.2:
+    resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
+
+  minimatch@9.0.5:
+    resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
+    engines: {node: '>=16 || 14 >=14.17'}
+
+  minimist@1.2.7:
+    resolution: {integrity: sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==}
+
+  minipass@7.1.2:
+    resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
+    engines: {node: '>=16 || 14 >=14.17'}
+
+  mri@1.2.0:
+    resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}
+    engines: {node: '>=4'}
+
+  mrmime@2.0.0:
+    resolution: {integrity: sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==}
+    engines: {node: '>=10'}
+
+  ms@2.0.0:
+    resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
+
+  ms@2.1.2:
+    resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
+
+  ms@2.1.3:
+    resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
+
+  multicast-dns@7.2.5:
+    resolution: {integrity: sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==}
+    hasBin: true
+
+  mz@2.7.0:
+    resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
+
+  nanoid@3.3.4:
+    resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==}
+    engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+    hasBin: true
+
+  nanoid@3.3.7:
+    resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==}
+    engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+    hasBin: true
+
+  negotiator@0.6.3:
+    resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
+    engines: {node: '>= 0.6'}
+
+  neo-async@2.6.2:
+    resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
+
+  no-case@3.0.4:
+    resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==}
+
+  node-emoji@2.1.3:
+    resolution: {integrity: sha512-E2WEOVsgs7O16zsURJ/eH8BqhF029wGpEOnv7Urwdo2wmQanOACwJQh0devF9D9RhoZru0+9JXIS0dBXIAz+lA==}
+    engines: {node: '>=18'}
+
+  node-forge@1.3.1:
+    resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==}
+    engines: {node: '>= 6.13.0'}
+
+  node-releases@2.0.14:
+    resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==}
+
+  node-releases@2.0.8:
+    resolution: {integrity: sha512-dFSmB8fFHEH/s81Xi+Y/15DQY6VHW81nXRj86EMSL3lmuTmK1e+aT4wrFCkTbm+gSwkw4KpX+rT/pMM2c1mF+A==}
+
+  non-layered-tidy-tree-layout@2.0.2:
+    resolution: {integrity: sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw==}
+
+  normalize-path@3.0.0:
+    resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
+    engines: {node: '>=0.10.0'}
+
+  normalize-range@0.1.2:
+    resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==}
+    engines: {node: '>=0.10.0'}
+
+  normalize-url@8.0.0:
+    resolution: {integrity: sha512-uVFpKhj5MheNBJRTiMZ9pE/7hD1QTeEvugSJW/OmLzAp78PB5O6adfMNTvmfKhXBkvCzC+rqifWcVYpGFwTjnw==}
+    engines: {node: '>=14.16'}
+
+  npm-run-path@4.0.1:
+    resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==}
+    engines: {node: '>=8'}
+
+  nprogress@0.2.0:
+    resolution: {integrity: sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==}
+
+  nth-check@2.1.1:
+    resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
+
+  object-assign@4.1.1:
+    resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
+    engines: {node: '>=0.10.0'}
+
+  object-hash@3.0.0:
+    resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==}
+    engines: {node: '>= 6'}
+
+  object-inspect@1.12.2:
+    resolution: {integrity: sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==}
+
+  object-keys@1.1.1:
+    resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==}
+    engines: {node: '>= 0.4'}
+
+  object.assign@4.1.4:
+    resolution: {integrity: sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==}
+    engines: {node: '>= 0.4'}
+
+  obuf@1.1.2:
+    resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==}
+
+  on-finished@2.4.1:
+    resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
+    engines: {node: '>= 0.8'}
+
+  on-headers@1.0.2:
+    resolution: {integrity: sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==}
+    engines: {node: '>= 0.8'}
+
+  once@1.4.0:
+    resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
+
+  onetime@5.1.2:
+    resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==}
+    engines: {node: '>=6'}
+
+  open@8.4.0:
+    resolution: {integrity: sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==}
+    engines: {node: '>=12'}
+
+  opener@1.5.2:
+    resolution: {integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==}
+    hasBin: true
+
+  p-cancelable@3.0.0:
+    resolution: {integrity: sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==}
+    engines: {node: '>=12.20'}
+
+  p-limit@2.3.0:
+    resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==}
+    engines: {node: '>=6'}
+
+  p-limit@3.1.0:
+    resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
+    engines: {node: '>=10'}
+
+  p-limit@4.0.0:
+    resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==}
+    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+
+  p-locate@3.0.0:
+    resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==}
+    engines: {node: '>=6'}
+
+  p-locate@5.0.0:
+    resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
+    engines: {node: '>=10'}
+
+  p-locate@6.0.0:
+    resolution: {integrity: sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==}
+    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+
+  p-map@4.0.0:
+    resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==}
+    engines: {node: '>=10'}
+
+  p-retry@4.6.2:
+    resolution: {integrity: sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==}
+    engines: {node: '>=8'}
+
+  p-try@2.2.0:
+    resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
+    engines: {node: '>=6'}
+
+  package-json-from-dist@1.0.0:
+    resolution: {integrity: sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==}
+
+  package-json@8.1.1:
+    resolution: {integrity: sha512-cbH9IAIJHNj9uXi196JVsRlt7cHKak6u/e6AkL/bkRelZ7rlL3X1YKxsZwa36xipOEKAsdtmaG6aAJoM1fx2zA==}
+    engines: {node: '>=14.16'}
+
+  param-case@3.0.4:
+    resolution: {integrity: sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==}
+
+  parent-module@1.0.1:
+    resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
+    engines: {node: '>=6'}
+
+  parse-entities@4.0.1:
+    resolution: {integrity: sha512-SWzvYcSJh4d/SGLIOQfZ/CoNv6BTlI6YEQ7Nj82oDVnRpwe/Z/F1EMx42x3JAOwGBlCjeCH0BRJQbQ/opHL17w==}
+
+  parse-json@5.2.0:
+    resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
+    engines: {node: '>=8'}
+
+  parse-numeric-range@1.3.0:
+    resolution: {integrity: sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==}
+
+  parse5-htmlparser2-tree-adapter@7.0.0:
+    resolution: {integrity: sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==}
+
+  parse5@7.1.2:
+    resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==}
+
+  parseurl@1.3.3:
+    resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
+    engines: {node: '>= 0.8'}
+
+  pascal-case@3.1.2:
+    resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==}
+
+  path-exists@3.0.0:
+    resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==}
+    engines: {node: '>=4'}
+
+  path-exists@4.0.0:
+    resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
+    engines: {node: '>=8'}
+
+  path-exists@5.0.0:
+    resolution: {integrity: sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==}
+    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+
+  path-is-absolute@1.0.1:
+    resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
+    engines: {node: '>=0.10.0'}
+
+  path-is-inside@1.0.2:
+    resolution: {integrity: sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==}
+
+  path-key@3.1.1:
+    resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
+    engines: {node: '>=8'}
+
+  path-parse@1.0.7:
+    resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
+
+  path-scurry@1.11.1:
+    resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==}
+    engines: {node: '>=16 || 14 >=14.18'}
+
+  path-to-regexp@0.1.7:
+    resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==}
+
+  path-to-regexp@1.8.0:
+    resolution: {integrity: sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==}
+
+  path-to-regexp@2.2.1:
+    resolution: {integrity: sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ==}
+
+  path-type@4.0.0:
+    resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
+    engines: {node: '>=8'}
+
+  periscopic@3.1.0:
+    resolution: {integrity: sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==}
+
+  picocolors@1.0.0:
+    resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
+
+  picocolors@1.1.0:
+    resolution: {integrity: sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==}
+
+  picomatch@2.3.1:
+    resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
+    engines: {node: '>=8.6'}
+
+  pify@2.3.0:
+    resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==}
+    engines: {node: '>=0.10.0'}
+
+  pirates@4.0.6:
+    resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==}
+    engines: {node: '>= 6'}
+
+  pkg-dir@7.0.0:
+    resolution: {integrity: sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==}
+    engines: {node: '>=14.16'}
+
+  pkg-up@3.1.0:
+    resolution: {integrity: sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==}
+    engines: {node: '>=8'}
+
+  postcss-calc@9.0.1:
+    resolution: {integrity: sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ==}
+    engines: {node: ^14 || ^16 || >=18.0}
+    peerDependencies:
+      postcss: ^8.2.2
+
+  postcss-colormin@6.1.0:
+    resolution: {integrity: sha512-x9yX7DOxeMAR+BgGVnNSAxmAj98NX/YxEMNFP+SDCEeNLb2r3i6Hh1ksMsnW8Ub5SLCpbescQqn9YEbE9554Sw==}
+    engines: {node: ^14 || ^16 || >=18.0}
+    peerDependencies:
+      postcss: ^8.4.31
+
+  postcss-convert-values@6.1.0:
+    resolution: {integrity: sha512-zx8IwP/ts9WvUM6NkVSkiU902QZL1bwPhaVaLynPtCsOTqp+ZKbNi+s6XJg3rfqpKGA/oc7Oxk5t8pOQJcwl/w==}
+    engines: {node: ^14 || ^16 || >=18.0}
+    peerDependencies:
+      postcss: ^8.4.31
+
+  postcss-discard-comments@6.0.2:
+    resolution: {integrity: sha512-65w/uIqhSBBfQmYnG92FO1mWZjJ4GL5b8atm5Yw2UgrwD7HiNiSSNwJor1eCFGzUgYnN/iIknhNRVqjrrpuglw==}
+    engines: {node: ^14 || ^16 || >=18.0}
+    peerDependencies:
+      postcss: ^8.4.31
+
+  postcss-discard-duplicates@6.0.3:
+    resolution: {integrity: sha512-+JA0DCvc5XvFAxwx6f/e68gQu/7Z9ud584VLmcgto28eB8FqSFZwtrLwB5Kcp70eIoWP/HXqz4wpo8rD8gpsTw==}
+    engines: {node: ^14 || ^16 || >=18.0}
+    peerDependencies:
+      postcss: ^8.4.31
+
+  postcss-discard-empty@6.0.3:
+    resolution: {integrity: sha512-znyno9cHKQsK6PtxL5D19Fj9uwSzC2mB74cpT66fhgOadEUPyXFkbgwm5tvc3bt3NAy8ltE5MrghxovZRVnOjQ==}
+    engines: {node: ^14 || ^16 || >=18.0}
+    peerDependencies:
+      postcss: ^8.4.31
+
+  postcss-discard-overridden@6.0.2:
+    resolution: {integrity: sha512-j87xzI4LUggC5zND7KdjsI25APtyMuynXZSujByMaav2roV6OZX+8AaCUcZSWqckZpjAjRyFDdpqybgjFO0HJQ==}
+    engines: {node: ^14 || ^16 || >=18.0}
+    peerDependencies:
+      postcss: ^8.4.31
+
+  postcss-discard-unused@6.0.5:
+    resolution: {integrity: sha512-wHalBlRHkaNnNwfC8z+ppX57VhvS+HWgjW508esjdaEYr3Mx7Gnn2xA4R/CKf5+Z9S5qsqC+Uzh4ueENWwCVUA==}
+    engines: {node: ^14 || ^16 || >=18.0}
+    peerDependencies:
+      postcss: ^8.4.31
+
+  postcss-import@15.1.0:
+    resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==}
+    engines: {node: '>=14.0.0'}
+    peerDependencies:
+      postcss: ^8.0.0
+
+  postcss-js@4.0.1:
+    resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==}
+    engines: {node: ^12 || ^14 || >= 16}
+    peerDependencies:
+      postcss: ^8.4.21
+
+  postcss-load-config@4.0.2:
+    resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==}
+    engines: {node: '>= 14'}
+    peerDependencies:
+      postcss: '>=8.0.9'
+      ts-node: '>=9.0.0'
+    peerDependenciesMeta:
+      postcss:
+        optional: true
+      ts-node:
+        optional: true
+
+  postcss-loader@7.3.4:
+    resolution: {integrity: sha512-iW5WTTBSC5BfsBJ9daFMPVrLT36MrNiC6fqOZTTaHjBNX6Pfd5p+hSBqe/fEeNd7pc13QiAyGt7VdGMw4eRC4A==}
+    engines: {node: '>= 14.15.0'}
+    peerDependencies:
+      postcss: ^7.0.0 || ^8.0.1
+      webpack: ^5.0.0
+
+  postcss-merge-idents@6.0.3:
+    resolution: {integrity: sha512-1oIoAsODUs6IHQZkLQGO15uGEbK3EAl5wi9SS8hs45VgsxQfMnxvt+L+zIr7ifZFIH14cfAeVe2uCTa+SPRa3g==}
+    engines: {node: ^14 || ^16 || >=18.0}
+    peerDependencies:
+      postcss: ^8.4.31
+
+  postcss-merge-longhand@6.0.5:
+    resolution: {integrity: sha512-5LOiordeTfi64QhICp07nzzuTDjNSO8g5Ksdibt44d+uvIIAE1oZdRn8y/W5ZtYgRH/lnLDlvi9F8btZcVzu3w==}
+    engines: {node: ^14 || ^16 || >=18.0}
+    peerDependencies:
+      postcss: ^8.4.31
+
+  postcss-merge-rules@6.1.1:
+    resolution: {integrity: sha512-KOdWF0gju31AQPZiD+2Ar9Qjowz1LTChSjFFbS+e2sFgc4uHOp3ZvVX4sNeTlk0w2O31ecFGgrFzhO0RSWbWwQ==}
+    engines: {node: ^14 || ^16 || >=18.0}
+    peerDependencies:
+      postcss: ^8.4.31
+
+  postcss-minify-font-values@6.1.0:
+    resolution: {integrity: sha512-gklfI/n+9rTh8nYaSJXlCo3nOKqMNkxuGpTn/Qm0gstL3ywTr9/WRKznE+oy6fvfolH6dF+QM4nCo8yPLdvGJg==}
+    engines: {node: ^14 || ^16 || >=18.0}
+    peerDependencies:
+      postcss: ^8.4.31
+
+  postcss-minify-gradients@6.0.3:
+    resolution: {integrity: sha512-4KXAHrYlzF0Rr7uc4VrfwDJ2ajrtNEpNEuLxFgwkhFZ56/7gaE4Nr49nLsQDZyUe+ds+kEhf+YAUolJiYXF8+Q==}
+    engines: {node: ^14 || ^16 || >=18.0}
+    peerDependencies:
+      postcss: ^8.4.31
+
+  postcss-minify-params@6.1.0:
+    resolution: {integrity: sha512-bmSKnDtyyE8ujHQK0RQJDIKhQ20Jq1LYiez54WiaOoBtcSuflfK3Nm596LvbtlFcpipMjgClQGyGr7GAs+H1uA==}
+    engines: {node: ^14 || ^16 || >=18.0}
+    peerDependencies:
+      postcss: ^8.4.31
+
+  postcss-minify-selectors@6.0.4:
+    resolution: {integrity: sha512-L8dZSwNLgK7pjTto9PzWRoMbnLq5vsZSTu8+j1P/2GB8qdtGQfn+K1uSvFgYvgh83cbyxT5m43ZZhUMTJDSClQ==}
+    engines: {node: ^14 || ^16 || >=18.0}
+    peerDependencies:
+      postcss: ^8.4.31
+
+  postcss-modules-extract-imports@3.0.0:
+    resolution: {integrity: sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==}
+    engines: {node: ^10 || ^12 || >= 14}
+    peerDependencies:
+      postcss: ^8.1.0
+
+  postcss-modules-local-by-default@4.0.4:
+    resolution: {integrity: sha512-L4QzMnOdVwRm1Qb8m4x8jsZzKAaPAgrUF1r/hjDR2Xj7R+8Zsf97jAlSQzWtKx5YNiNGN8QxmPFIc/sh+RQl+Q==}
+    engines: {node: ^10 || ^12 || >= 14}
+    peerDependencies:
+      postcss: ^8.1.0
+
+  postcss-modules-scope@3.1.1:
+    resolution: {integrity: sha512-uZgqzdTleelWjzJY+Fhti6F3C9iF1JR/dODLs/JDefozYcKTBCdD8BIl6nNPbTbcLnGrk56hzwZC2DaGNvYjzA==}
+    engines: {node: ^10 || ^12 || >= 14}
+    peerDependencies:
+      postcss: ^8.1.0
+
+  postcss-modules-values@4.0.0:
+    resolution: {integrity: sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==}
+    engines: {node: ^10 || ^12 || >= 14}
+    peerDependencies:
+      postcss: ^8.1.0
+
+  postcss-nested@6.2.0:
+    resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==}
+    engines: {node: '>=12.0'}
+    peerDependencies:
+      postcss: ^8.2.14
+
+  postcss-normalize-charset@6.0.2:
+    resolution: {integrity: sha512-a8N9czmdnrjPHa3DeFlwqst5eaL5W8jYu3EBbTTkI5FHkfMhFZh1EGbku6jhHhIzTA6tquI2P42NtZ59M/H/kQ==}
+    engines: {node: ^14 || ^16 || >=18.0}
+    peerDependencies:
+      postcss: ^8.4.31
+
+  postcss-normalize-display-values@6.0.2:
+    resolution: {integrity: sha512-8H04Mxsb82ON/aAkPeq8kcBbAtI5Q2a64X/mnRRfPXBq7XeogoQvReqxEfc0B4WPq1KimjezNC8flUtC3Qz6jg==}
+    engines: {node: ^14 || ^16 || >=18.0}
+    peerDependencies:
+      postcss: ^8.4.31
+
+  postcss-normalize-positions@6.0.2:
+    resolution: {integrity: sha512-/JFzI441OAB9O7VnLA+RtSNZvQ0NCFZDOtp6QPFo1iIyawyXg0YI3CYM9HBy1WvwCRHnPep/BvI1+dGPKoXx/Q==}
+    engines: {node: ^14 || ^16 || >=18.0}
+    peerDependencies:
+      postcss: ^8.4.31
+
+  postcss-normalize-repeat-style@6.0.2:
+    resolution: {integrity: sha512-YdCgsfHkJ2jEXwR4RR3Tm/iOxSfdRt7jplS6XRh9Js9PyCR/aka/FCb6TuHT2U8gQubbm/mPmF6L7FY9d79VwQ==}
+    engines: {node: ^14 || ^16 || >=18.0}
+    peerDependencies:
+      postcss: ^8.4.31
+
+  postcss-normalize-string@6.0.2:
+    resolution: {integrity: sha512-vQZIivlxlfqqMp4L9PZsFE4YUkWniziKjQWUtsxUiVsSSPelQydwS8Wwcuw0+83ZjPWNTl02oxlIvXsmmG+CiQ==}
+    engines: {node: ^14 || ^16 || >=18.0}
+    peerDependencies:
+      postcss: ^8.4.31
+
+  postcss-normalize-timing-functions@6.0.2:
+    resolution: {integrity: sha512-a+YrtMox4TBtId/AEwbA03VcJgtyW4dGBizPl7e88cTFULYsprgHWTbfyjSLyHeBcK/Q9JhXkt2ZXiwaVHoMzA==}
+    engines: {node: ^14 || ^16 || >=18.0}
+    peerDependencies:
+      postcss: ^8.4.31
+
+  postcss-normalize-unicode@6.1.0:
+    resolution: {integrity: sha512-QVC5TQHsVj33otj8/JD869Ndr5Xcc/+fwRh4HAsFsAeygQQXm+0PySrKbr/8tkDKzW+EVT3QkqZMfFrGiossDg==}
+    engines: {node: ^14 || ^16 || >=18.0}
+    peerDependencies:
+      postcss: ^8.4.31
+
+  postcss-normalize-url@6.0.2:
+    resolution: {integrity: sha512-kVNcWhCeKAzZ8B4pv/DnrU1wNh458zBNp8dh4y5hhxih5RZQ12QWMuQrDgPRw3LRl8mN9vOVfHl7uhvHYMoXsQ==}
+    engines: {node: ^14 || ^16 || >=18.0}
+    peerDependencies:
+      postcss: ^8.4.31
+
+  postcss-normalize-whitespace@6.0.2:
+    resolution: {integrity: sha512-sXZ2Nj1icbJOKmdjXVT9pnyHQKiSAyuNQHSgRCUgThn2388Y9cGVDR+E9J9iAYbSbLHI+UUwLVl1Wzco/zgv0Q==}
+    engines: {node: ^14 || ^16 || >=18.0}
+    peerDependencies:
+      postcss: ^8.4.31
+
+  postcss-ordered-values@6.0.2:
+    resolution: {integrity: sha512-VRZSOB+JU32RsEAQrO94QPkClGPKJEL/Z9PCBImXMhIeK5KAYo6slP/hBYlLgrCjFxyqvn5VC81tycFEDBLG1Q==}
+    engines: {node: ^14 || ^16 || >=18.0}
+    peerDependencies:
+      postcss: ^8.4.31
+
+  postcss-reduce-idents@6.0.3:
+    resolution: {integrity: sha512-G3yCqZDpsNPoQgbDUy3T0E6hqOQ5xigUtBQyrmq3tn2GxlyiL0yyl7H+T8ulQR6kOcHJ9t7/9H4/R2tv8tJbMA==}
+    engines: {node: ^14 || ^16 || >=18.0}
+    peerDependencies:
+      postcss: ^8.4.31
+
+  postcss-reduce-initial@6.1.0:
+    resolution: {integrity: sha512-RarLgBK/CrL1qZags04oKbVbrrVK2wcxhvta3GCxrZO4zveibqbRPmm2VI8sSgCXwoUHEliRSbOfpR0b/VIoiw==}
+    engines: {node: ^14 || ^16 || >=18.0}
+    peerDependencies:
+      postcss: ^8.4.31
+
+  postcss-reduce-transforms@6.0.2:
+    resolution: {integrity: sha512-sB+Ya++3Xj1WaT9+5LOOdirAxP7dJZms3GRcYheSPi1PiTMigsxHAdkrbItHxwYHr4kt1zL7mmcHstgMYT+aiA==}
+    engines: {node: ^14 || ^16 || >=18.0}
+    peerDependencies:
+      postcss: ^8.4.31
+
+  postcss-selector-parser@6.0.10:
+    resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==}
+    engines: {node: '>=4'}
+
+  postcss-selector-parser@6.0.11:
+    resolution: {integrity: sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g==}
+    engines: {node: '>=4'}
+
+  postcss-selector-parser@6.1.2:
+    resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==}
+    engines: {node: '>=4'}
+
+  postcss-sort-media-queries@5.2.0:
+    resolution: {integrity: sha512-AZ5fDMLD8SldlAYlvi8NIqo0+Z8xnXU2ia0jxmuhxAU+Lqt9K+AlmLNJ/zWEnE9x+Zx3qL3+1K20ATgNOr3fAA==}
+    engines: {node: '>=14.0.0'}
+    peerDependencies:
+      postcss: ^8.4.23
+
+  postcss-svgo@6.0.3:
+    resolution: {integrity: sha512-dlrahRmxP22bX6iKEjOM+c8/1p+81asjKT+V5lrgOH944ryx/OHpclnIbGsKVd3uWOXFLYJwCVf0eEkJGvO96g==}
+    engines: {node: ^14 || ^16 || >= 18}
+    peerDependencies:
+      postcss: ^8.4.31
+
+  postcss-unique-selectors@6.0.4:
+    resolution: {integrity: sha512-K38OCaIrO8+PzpArzkLKB42dSARtC2tmG6PvD4b1o1Q2E9Os8jzfWFfSy/rixsHwohtsDdFtAWGjFVFUdwYaMg==}
+    engines: {node: ^14 || ^16 || >=18.0}
+    peerDependencies:
+      postcss: ^8.4.31
+
+  postcss-value-parser@4.2.0:
+    resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
+
+  postcss-zindex@6.0.2:
+    resolution: {integrity: sha512-5BxW9l1evPB/4ZIc+2GobEBoKC+h8gPGCMi+jxsYvd2x0mjq7wazk6DrP71pStqxE9Foxh5TVnonbWpFZzXaYg==}
+    engines: {node: ^14 || ^16 || >=18.0}
+    peerDependencies:
+      postcss: ^8.4.31
+
+  postcss@8.4.21:
+    resolution: {integrity: sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==}
+    engines: {node: ^10 || ^12 || >=14}
+
+  postcss@8.4.35:
+    resolution: {integrity: sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==}
+    engines: {node: ^10 || ^12 || >=14}
+
+  postcss@8.4.45:
+    resolution: {integrity: sha512-7KTLTdzdZZYscUc65XmjFiB73vBhBfbPztCYdUNvlaso9PrzjzcmjqBPR0lNGkcVlcO4BjiO5rK/qNz+XAen1Q==}
+    engines: {node: ^10 || ^12 || >=14}
+
+  pretty-error@4.0.0:
+    resolution: {integrity: sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==}
+
+  pretty-time@1.1.0:
+    resolution: {integrity: sha512-28iF6xPQrP8Oa6uxE6a1biz+lWeTOAPKggvjB8HAs6nVMKZwf5bG++632Dx614hIWgUPkgivRfG+a8uAXGTIbA==}
+    engines: {node: '>=4'}
+
+  prism-react-renderer@2.3.1:
+    resolution: {integrity: sha512-Rdf+HzBLR7KYjzpJ1rSoxT9ioO85nZngQEoFIhL07XhtJHlCU3SOz0GJ6+qvMyQe0Se+BV3qpe6Yd/NmQF5Juw==}
+    peerDependencies:
+      react: '>=16.0.0'
+
+  prism-svelte@0.5.0:
+    resolution: {integrity: sha512-db91Bf3pRGKDPz1lAqLFSJXeW13mulUJxhycysFpfXV5MIK7RgWWK2E5aPAa71s8TCzQUXxF5JOV42/iOs6QkA==}
+
+  prismjs@1.29.0:
+    resolution: {integrity: sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==}
+    engines: {node: '>=6'}
+
+  process-nextick-args@2.0.1:
+    resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
+
+  prompts@2.4.2:
+    resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==}
+    engines: {node: '>= 6'}
+
+  prop-types@15.8.1:
+    resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
+
+  property-information@6.4.1:
+    resolution: {integrity: sha512-OHYtXfu5aI2sS2LWFSN5rgJjrQ4pCy8i1jubJLe2QvMF8JJ++HXTUIVWFLfXJoaOfvYYjk2SN8J2wFUWIGXT4w==}
+
+  proto-list@1.2.4:
+    resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==}
+
+  proxy-addr@2.0.7:
+    resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
+    engines: {node: '>= 0.10'}
+
+  punycode@1.4.1:
+    resolution: {integrity: sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==}
+
+  punycode@2.1.1:
+    resolution: {integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==}
+    engines: {node: '>=6'}
+
+  pupa@3.1.0:
+    resolution: {integrity: sha512-FLpr4flz5xZTSJxSeaheeMKN/EDzMdK7b8PTOC6a5PYFKTucWbdqjgqaEyH0shFiSJrVB1+Qqi4Tk19ccU6Aug==}
+    engines: {node: '>=12.20'}
+
+  qs@6.11.0:
+    resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==}
+    engines: {node: '>=0.6'}
+
+  queue-microtask@1.2.3:
+    resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
+
+  queue@6.0.2:
+    resolution: {integrity: sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==}
+
+  quick-lru@5.1.1:
+    resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==}
+    engines: {node: '>=10'}
+
+  randombytes@2.1.0:
+    resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==}
+
+  range-parser@1.2.0:
+    resolution: {integrity: sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==}
+    engines: {node: '>= 0.6'}
+
+  range-parser@1.2.1:
+    resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
+    engines: {node: '>= 0.6'}
+
+  raw-body@2.5.1:
+    resolution: {integrity: sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==}
+    engines: {node: '>= 0.8'}
+
+  rc@1.2.8:
+    resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==}
+    hasBin: true
+
+  react-dev-utils@12.0.1:
+    resolution: {integrity: sha512-84Ivxmr17KjUupyqzFode6xKhjwuEJDROWKJy/BthkL7Wn6NJ8h4WE6k/exAv6ImS+0oZLRRW5j/aINMHyeGeQ==}
+    engines: {node: '>=14'}
+    peerDependencies:
+      typescript: '>=2.7'
+      webpack: '>=4'
+    peerDependenciesMeta:
+      typescript:
+        optional: true
+
+  react-dom@18.2.0:
+    resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==}
+    peerDependencies:
+      react: ^18.2.0
+
+  react-error-overlay@6.0.11:
+    resolution: {integrity: sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==}
+
+  react-fast-compare@3.2.0:
+    resolution: {integrity: sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==}
+
+  react-helmet-async@1.3.0:
+    resolution: {integrity: sha512-9jZ57/dAn9t3q6hneQS0wukqC2ENOBgMNVEhb/ZG9ZSxUetzVIw4iAmEU38IaVg3QGYauQPhSeUTuIUtFglWpg==}
+    peerDependencies:
+      react: ^16.6.0 || ^17.0.0 || ^18.0.0
+      react-dom: ^16.6.0 || ^17.0.0 || ^18.0.0
+
+  react-icons@5.0.1:
+    resolution: {integrity: sha512-WqLZJ4bLzlhmsvme6iFdgO8gfZP17rfjYEJ2m9RsZjZ+cc4k1hTzknEz63YS1MeT50kVzoa1Nz36f4BEx+Wigw==}
+    peerDependencies:
+      react: '*'
+
+  react-is@16.13.1:
+    resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
+
+  react-json-view-lite@1.2.1:
+    resolution: {integrity: sha512-Itc0g86fytOmKZoIoJyGgvNqohWSbh3NXIKNgH6W6FT9PC1ck4xas1tT3Rr/b3UlFXyA9Jjaw9QSXdZy2JwGMQ==}
+    engines: {node: '>=14'}
+    peerDependencies:
+      react: ^16.13.1 || ^17.0.0 || ^18.0.0
+
+  react-loadable-ssr-addon-v5-slorber@1.0.1:
+    resolution: {integrity: sha512-lq3Lyw1lGku8zUEJPDxsNm1AfYHBrO9Y1+olAYwpUJ2IGFBskM0DMKok97A6LWUpHm+o7IvQBOWu9MLenp9Z+A==}
+    engines: {node: '>=10.13.0'}
+    peerDependencies:
+      react-loadable: '*'
+      webpack: '>=4.41.1 || 5.x'
+
+  react-router-config@5.1.1:
+    resolution: {integrity: sha512-DuanZjaD8mQp1ppHjgnnUnyOlqYXZVjnov/JzFhjLEwd3Z4dYjMSnqrEzzGThH47vpCOqPPwJM2FtthLeJ8Pbg==}
+    peerDependencies:
+      react: '>=15'
+      react-router: '>=5'
+
+  react-router-dom@5.3.4:
+    resolution: {integrity: sha512-m4EqFMHv/Ih4kpcBCONHbkT68KoAeHN4p3lAGoNryfHi0dMy0kCzEZakiKRsvg5wHZ/JLrLW8o8KomWiz/qbYQ==}
+    peerDependencies:
+      react: '>=15'
+
+  react-router@5.3.4:
+    resolution: {integrity: sha512-Ys9K+ppnJah3QuaRiLxk+jDWOR1MekYQrlytiXxC1RyfbdsZkS5pvKAzCCr031xHixZwpnsYNT5xysdFHQaYsA==}
+    peerDependencies:
+      react: '>=15'
+
+  react-share@5.1.0:
+    resolution: {integrity: sha512-OvyfMtj/0UzH1wi90OdHhZVJ6WUC/+IeWvBwppeZozwIGyAjQgyR0QXlHOrxVHVECqnGvcpBaFTXVrqouTieaw==}
+    peerDependencies:
+      react: ^17 || ^18
+
+  react@18.2.0:
+    resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==}
+    engines: {node: '>=0.10.0'}
+
+  read-cache@1.0.0:
+    resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==}
+
+  readable-stream@2.3.7:
+    resolution: {integrity: sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==}
+
+  readable-stream@3.6.0:
+    resolution: {integrity: sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==}
+    engines: {node: '>= 6'}
+
+  readdirp@3.6.0:
+    resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
+    engines: {node: '>=8.10.0'}
+
+  reading-time@1.5.0:
+    resolution: {integrity: sha512-onYyVhBNr4CmAxFsKS7bz+uTLRakypIe4R+5A824vBSkQy/hB3fZepoVEf8OVAxzLvK+H/jm9TzpI3ETSm64Kg==}
+
+  rechoir@0.6.2:
+    resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==}
+    engines: {node: '>= 0.10'}
+
+  recursive-readdir@2.2.3:
+    resolution: {integrity: sha512-8HrF5ZsXk5FAH9dgsx3BlUer73nIhuj+9OrQwEbLTPOBzGkL1lsFCR01am+v+0m2Cmbs1nP12hLDl5FA7EszKA==}
+    engines: {node: '>=6.0.0'}
+
+  regenerate-unicode-properties@10.1.0:
+    resolution: {integrity: sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==}
+    engines: {node: '>=4'}
+
+  regenerate@1.4.2:
+    resolution: {integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==}
+
+  regenerator-runtime@0.14.1:
+    resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==}
+
+  regenerator-transform@0.15.2:
+    resolution: {integrity: sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==}
+
+  regexpu-core@5.3.2:
+    resolution: {integrity: sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==}
+    engines: {node: '>=4'}
+
+  registry-auth-token@5.0.2:
+    resolution: {integrity: sha512-o/3ikDxtXaA59BmZuZrJZDJv8NMDGSj+6j6XaeBmHw8eY1i1qd9+6H+LjVvQXx3HN6aRCGa1cUdJ9RaJZUugnQ==}
+    engines: {node: '>=14'}
+
+  registry-url@6.0.1:
+    resolution: {integrity: sha512-+crtS5QjFRqFCoQmvGduwYWEBng99ZvmFvF+cUJkGYF1L1BfU8C6Zp9T7f5vPAwyLkUExpvK+ANVZmGU49qi4Q==}
+    engines: {node: '>=12'}
+
+  regjsparser@0.9.1:
+    resolution: {integrity: sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==}
+    hasBin: true
+
+  rehype-raw@7.0.0:
+    resolution: {integrity: sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==}
+
+  relateurl@0.2.7:
+    resolution: {integrity: sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==}
+    engines: {node: '>= 0.10'}
+
+  remark-directive@3.0.0:
+    resolution: {integrity: sha512-l1UyWJ6Eg1VPU7Hm/9tt0zKtReJQNOA4+iDMAxTyZNWnJnFlbS/7zhiel/rogTLQ2vMYwDzSJa4BiVNqGlqIMA==}
+
+  remark-emoji@4.0.1:
+    resolution: {integrity: sha512-fHdvsTR1dHkWKev9eNyhTo4EFwbUvJ8ka9SgeWkMPYFX4WoI7ViVBms3PjlQYgw5TLvNQso3GUB/b/8t3yo+dg==}
+    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+
+  remark-frontmatter@5.0.0:
+    resolution: {integrity: sha512-XTFYvNASMe5iPN0719nPrdItC9aU0ssC4v14mH1BCi1u0n1gAocqcujWUrByftZTbLhRtiKRyjYTSIOcr69UVQ==}
+
+  remark-gfm@4.0.0:
+    resolution: {integrity: sha512-U92vJgBPkbw4Zfu/IiW2oTZLSL3Zpv+uI7My2eq8JxKgqraFdU8YUGicEJCEgSbeaG+QDFqIcwwfMTOEelPxuA==}
+
+  remark-mdx@3.0.1:
+    resolution: {integrity: sha512-3Pz3yPQ5Rht2pM5R+0J2MrGoBSrzf+tJG94N+t/ilfdh8YLyyKYtidAYwTveB20BoHAcwIopOUqhcmh2F7hGYA==}
+
+  remark-parse@11.0.0:
+    resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==}
+
+  remark-rehype@11.1.0:
+    resolution: {integrity: sha512-z3tJrAs2kIs1AqIIy6pzHmAHlF1hWQ+OdY4/hv+Wxe35EhyLKcajL33iUEn3ScxtFox9nUvRufR/Zre8Q08H/g==}
+
+  remark-stringify@11.0.0:
+    resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==}
+
+  renderkid@3.0.0:
+    resolution: {integrity: sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==}
+
+  require-from-string@2.0.2:
+    resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
+    engines: {node: '>=0.10.0'}
+
+  require-like@0.1.2:
+    resolution: {integrity: sha512-oyrU88skkMtDdauHDuKVrgR+zuItqr6/c//FXzvmxRGMexSDc6hNvJInGW3LL46n+8b50RykrvwSUIIQH2LQ5A==}
+
+  requires-port@1.0.0:
+    resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==}
+
+  resolve-alpn@1.2.1:
+    resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==}
+
+  resolve-from@4.0.0:
+    resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
+    engines: {node: '>=4'}
+
+  resolve-pathname@3.0.0:
+    resolution: {integrity: sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==}
+
+  resolve@1.22.8:
+    resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==}
+    hasBin: true
+
+  responselike@3.0.0:
+    resolution: {integrity: sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==}
+    engines: {node: '>=14.16'}
+
+  retry@0.13.1:
+    resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==}
+    engines: {node: '>= 4'}
+
+  reusify@1.0.4:
+    resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
+    engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
+
+  rimraf@3.0.2:
+    resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
+    deprecated: Rimraf versions prior to v4 are no longer supported
+    hasBin: true
+
+  robust-predicates@3.0.2:
+    resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==}
+
+  rtl-detect@1.0.4:
+    resolution: {integrity: sha512-EBR4I2VDSSYr7PkBmFy04uhycIpDKp+21p/jARYXlCSjQksTBQcJ0HFUPOO79EPPH5JS6VAhiIQbycf0O3JAxQ==}
+
+  rtlcss@4.1.1:
+    resolution: {integrity: sha512-/oVHgBtnPNcggP2aVXQjSy6N1mMAfHg4GSag0QtZBlD5bdDgAHwr4pydqJGd+SUCu9260+Pjqbjwtvu7EMH1KQ==}
+    engines: {node: '>=12.0.0'}
+    hasBin: true
+
+  run-parallel@1.2.0:
+    resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
+
+  rw@1.3.3:
+    resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==}
+
+  sade@1.8.1:
+    resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==}
+    engines: {node: '>=6'}
+
+  safe-buffer@5.1.2:
+    resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}
+
+  safe-buffer@5.2.1:
+    resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
+
+  safer-buffer@2.1.2:
+    resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
+
+  sax@1.2.4:
+    resolution: {integrity: sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==}
+
+  scheduler@0.23.0:
+    resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==}
+
+  schema-utils@2.7.0:
+    resolution: {integrity: sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==}
+    engines: {node: '>= 8.9.0'}
+
+  schema-utils@3.3.0:
+    resolution: {integrity: sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==}
+    engines: {node: '>= 10.13.0'}
+
+  schema-utils@4.0.0:
+    resolution: {integrity: sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==}
+    engines: {node: '>= 12.13.0'}
+
+  schema-utils@4.2.0:
+    resolution: {integrity: sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==}
+    engines: {node: '>= 12.13.0'}
+
+  search-insights@2.13.0:
+    resolution: {integrity: sha512-Orrsjf9trHHxFRuo9/rzm0KIWmgzE8RMlZMzuhZOJ01Rnz3D0YBAe+V6473t6/H6c7irs6Lt48brULAiRWb3Vw==}
+
+  section-matter@1.0.0:
+    resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==}
+    engines: {node: '>=4'}
+
+  select-hose@2.0.0:
+    resolution: {integrity: sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==}
+
+  selfsigned@2.1.1:
+    resolution: {integrity: sha512-GSL3aowiF7wa/WtSFwnUrludWFoNhftq8bUkH9pkzjpN2XSPOAYEgg6e0sS9s0rZwgJzJiQRPU18A6clnoW5wQ==}
+    engines: {node: '>=10'}
+
+  semver-diff@4.0.0:
+    resolution: {integrity: sha512-0Ju4+6A8iOnpL/Thra7dZsSlOHYAHIeMxfhWQRI1/VLcT3WDBZKKtQt/QkBOsiIN9ZpuvHE6cGZ0x4glCMmfiA==}
+    engines: {node: '>=12'}
+
+  semver@6.3.1:
+    resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
+    hasBin: true
+
+  semver@7.6.0:
+    resolution: {integrity: sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==}
+    engines: {node: '>=10'}
+    hasBin: true
+
+  send@0.18.0:
+    resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==}
+    engines: {node: '>= 0.8.0'}
+
+  serialize-javascript@6.0.2:
+    resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==}
+
+  serve-handler@6.1.5:
+    resolution: {integrity: sha512-ijPFle6Hwe8zfmBxJdE+5fta53fdIY0lHISJvuikXB3VYFafRjMRpOffSPvCYsbKyBA7pvy9oYr/BT1O3EArlg==}
+
+  serve-index@1.9.1:
+    resolution: {integrity: sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==}
+    engines: {node: '>= 0.8.0'}
+
+  serve-static@1.15.0:
+    resolution: {integrity: sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==}
+    engines: {node: '>= 0.8.0'}
+
+  setprototypeof@1.1.0:
+    resolution: {integrity: sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==}
+
+  setprototypeof@1.2.0:
+    resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
+
+  shallow-clone@3.0.1:
+    resolution: {integrity: sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==}
+    engines: {node: '>=8'}
+
+  shallowequal@1.1.0:
+    resolution: {integrity: sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==}
+
+  shebang-command@2.0.0:
+    resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
+    engines: {node: '>=8'}
+
+  shebang-regex@3.0.0:
+    resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
+    engines: {node: '>=8'}
+
+  shell-quote@1.8.1:
+    resolution: {integrity: sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==}
+
+  shelljs@0.8.5:
+    resolution: {integrity: sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==}
+    engines: {node: '>=4'}
+    hasBin: true
+
+  side-channel@1.0.4:
+    resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==}
+
+  signal-exit@3.0.7:
+    resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
+
+  signal-exit@4.1.0:
+    resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
+    engines: {node: '>=14'}
+
+  sirv@2.0.4:
+    resolution: {integrity: sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==}
+    engines: {node: '>= 10'}
+
+  sisteransi@1.0.5:
+    resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}
+
+  sitemap@7.1.1:
+    resolution: {integrity: sha512-mK3aFtjz4VdJN0igpIJrinf3EO8U8mxOPsTBzSsy06UtjZQJ3YY3o3Xa7zSc5nMqcMrRwlChHZ18Kxg0caiPBg==}
+    engines: {node: '>=12.0.0', npm: '>=5.6.0'}
+    hasBin: true
+
+  skin-tone@2.0.0:
+    resolution: {integrity: sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==}
+    engines: {node: '>=8'}
+
+  slash@3.0.0:
+    resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
+    engines: {node: '>=8'}
+
+  slash@4.0.0:
+    resolution: {integrity: sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==}
+    engines: {node: '>=12'}
+
+  snake-case@3.0.4:
+    resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==}
+
+  sockjs@0.3.24:
+    resolution: {integrity: sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==}
+
+  sort-css-media-queries@2.2.0:
+    resolution: {integrity: sha512-0xtkGhWCC9MGt/EzgnvbbbKhqWjl1+/rncmhTh5qCpbYguXh6S/qwePfv/JQ8jePXXmqingylxoC49pCkSPIbA==}
+    engines: {node: '>= 6.3.0'}
+
+  source-map-js@1.0.2:
+    resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
+    engines: {node: '>=0.10.0'}
+
+  source-map-js@1.2.1:
+    resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
+    engines: {node: '>=0.10.0'}
+
+  source-map-support@0.5.21:
+    resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==}
+
+  source-map@0.6.1:
+    resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
+    engines: {node: '>=0.10.0'}
+
+  source-map@0.7.4:
+    resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==}
+    engines: {node: '>= 8'}
+
+  space-separated-tokens@2.0.2:
+    resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==}
+
+  spdy-transport@3.0.0:
+    resolution: {integrity: sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==}
+
+  spdy@4.0.2:
+    resolution: {integrity: sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==}
+    engines: {node: '>=6.0.0'}
+
+  sprintf-js@1.0.3:
+    resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==}
+
+  srcset@4.0.0:
+    resolution: {integrity: sha512-wvLeHgcVHKO8Sc/H/5lkGreJQVeYMm9rlmt8PuR1xE31rIuXhuzznUUqAt8MqLhB3MqJdFzlNAfpcWnxiFUcPw==}
+    engines: {node: '>=12'}
+
+  statuses@1.5.0:
+    resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==}
+    engines: {node: '>= 0.6'}
+
+  statuses@2.0.1:
+    resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
+    engines: {node: '>= 0.8'}
+
+  std-env@3.3.1:
+    resolution: {integrity: sha512-3H20QlwQsSm2OvAxWIYhs+j01MzzqwMwGiiO1NQaJYZgJZFPuAbf95/DiKRBSTYIJ2FeGUc+B/6mPGcWP9dO3Q==}
+
+  string-width@4.2.3:
+    resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
+    engines: {node: '>=8'}
+
+  string-width@5.1.2:
+    resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
+    engines: {node: '>=12'}
+
+  string_decoder@1.1.1:
+    resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==}
+
+  string_decoder@1.3.0:
+    resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
+
+  stringify-entities@4.0.3:
+    resolution: {integrity: sha512-BP9nNHMhhfcMbiuQKCqMjhDP5yBCAxsPu4pHFFzJ6Alo9dZgY4VLDPutXqIjpRiMoKdp7Av85Gr73Q5uH9k7+g==}
+
+  stringify-object@3.3.0:
+    resolution: {integrity: sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==}
+    engines: {node: '>=4'}
+
+  strip-ansi@6.0.1:
+    resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
+    engines: {node: '>=8'}
+
+  strip-ansi@7.1.0:
+    resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==}
+    engines: {node: '>=12'}
+
+  strip-bom-string@1.0.0:
+    resolution: {integrity: sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==}
+    engines: {node: '>=0.10.0'}
+
+  strip-final-newline@2.0.0:
+    resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==}
+    engines: {node: '>=6'}
+
+  strip-json-comments@2.0.1:
+    resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==}
+    engines: {node: '>=0.10.0'}
+
+  strip-json-comments@3.1.1:
+    resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
+    engines: {node: '>=8'}
+
+  style-to-object@0.4.4:
+    resolution: {integrity: sha512-HYNoHZa2GorYNyqiCaBgsxvcJIn7OHq6inEga+E6Ke3m5JkoqpQbnFssk4jwe+K7AhGa2fcha4wSOf1Kn01dMg==}
+
+  style-to-object@1.0.5:
+    resolution: {integrity: sha512-rDRwHtoDD3UMMrmZ6BzOW0naTjMsVZLIjsGleSKS/0Oz+cgCfAPRspaqJuE8rDzpKha/nEvnM0IF4seEAZUTKQ==}
+
+  stylehacks@6.1.1:
+    resolution: {integrity: sha512-gSTTEQ670cJNoaeIp9KX6lZmm8LJ3jPB5yJmX8Zq/wQxOsAFXV3qjWzHas3YYk1qesuVIyYWWUpZ0vSE/dTSGg==}
+    engines: {node: ^14 || ^16 || >=18.0}
+    peerDependencies:
+      postcss: ^8.4.31
+
+  stylis@4.3.0:
+    resolution: {integrity: sha512-E87pIogpwUsUwXw7dNyU4QDjdgVMy52m+XEOPEKUn161cCzWjjhPSQhByfd1CcNvrOLnXQ6OnnZDwnJrz/Z4YQ==}
+
+  sucrase@3.35.0:
+    resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==}
+    engines: {node: '>=16 || 14 >=14.17'}
+    hasBin: true
+
+  supports-color@5.5.0:
+    resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==}
+    engines: {node: '>=4'}
+
+  supports-color@7.2.0:
+    resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
+    engines: {node: '>=8'}
+
+  supports-color@8.1.1:
+    resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==}
+    engines: {node: '>=10'}
+
+  supports-preserve-symlinks-flag@1.0.0:
+    resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
+    engines: {node: '>= 0.4'}
+
+  svg-parser@2.0.4:
+    resolution: {integrity: sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==}
+
+  svgo@3.3.2:
+    resolution: {integrity: sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==}
+    engines: {node: '>=14.0.0'}
+    hasBin: true
+
+  tailwindcss@3.4.11:
+    resolution: {integrity: sha512-qhEuBcLemjSJk5ajccN9xJFtM/h0AVCPaA6C92jNP+M2J8kX+eMJHI7R2HFKUvvAsMpcfLILMCFYSeDwpMmlUg==}
+    engines: {node: '>=14.0.0'}
+    hasBin: true
+
+  tapable@1.1.3:
+    resolution: {integrity: sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==}
+    engines: {node: '>=6'}
+
+  tapable@2.2.1:
+    resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==}
+    engines: {node: '>=6'}
+
+  terser-webpack-plugin@5.3.10:
+    resolution: {integrity: sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==}
+    engines: {node: '>= 10.13.0'}
+    peerDependencies:
+      '@swc/core': '*'
+      esbuild: '*'
+      uglify-js: '*'
+      webpack: ^5.1.0
+    peerDependenciesMeta:
+      '@swc/core':
+        optional: true
+      esbuild:
+        optional: true
+      uglify-js:
+        optional: true
+
+  terser@5.27.0:
+    resolution: {integrity: sha512-bi1HRwVRskAjheeYl291n3JC4GgO/Ty4z1nVs5AAsmonJulGxpSektecnNedrwK9C7vpvVtcX3cw00VSLt7U2A==}
+    engines: {node: '>=10'}
+    hasBin: true
+
+  text-table@0.2.0:
+    resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
+
+  thenify-all@1.6.0:
+    resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==}
+    engines: {node: '>=0.8'}
+
+  thenify@3.3.1:
+    resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
+
+  thunky@1.1.0:
+    resolution: {integrity: sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==}
+
+  tiny-invariant@1.3.1:
+    resolution: {integrity: sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==}
+
+  tiny-warning@1.0.3:
+    resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==}
+
+  to-fast-properties@2.0.0:
+    resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==}
+    engines: {node: '>=4'}
+
+  to-regex-range@5.0.1:
+    resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
+    engines: {node: '>=8.0'}
+
+  toidentifier@1.0.1:
+    resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
+    engines: {node: '>=0.6'}
+
+  totalist@3.0.1:
+    resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==}
+    engines: {node: '>=6'}
+
+  trim-lines@3.0.1:
+    resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==}
+
+  trough@2.2.0:
+    resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==}
+
+  ts-dedent@2.2.0:
+    resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==}
+    engines: {node: '>=6.10'}
+
+  ts-interface-checker@0.1.13:
+    resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
+
+  tslib@2.6.2:
+    resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==}
+
+  type-fest@1.4.0:
+    resolution: {integrity: sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==}
+    engines: {node: '>=10'}
+
+  type-fest@2.19.0:
+    resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==}
+    engines: {node: '>=12.20'}
+
+  type-is@1.6.18:
+    resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
+    engines: {node: '>= 0.6'}
+
+  typedarray-to-buffer@3.1.5:
+    resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==}
+
+  typescript@5.3.3:
+    resolution: {integrity: sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==}
+    engines: {node: '>=14.17'}
+    hasBin: true
+
+  undici-types@5.26.5:
+    resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
+
+  unicode-canonical-property-names-ecmascript@2.0.0:
+    resolution: {integrity: sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==}
+    engines: {node: '>=4'}
+
+  unicode-emoji-modifier-base@1.0.0:
+    resolution: {integrity: sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==}
+    engines: {node: '>=4'}
+
+  unicode-match-property-ecmascript@2.0.0:
+    resolution: {integrity: sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==}
+    engines: {node: '>=4'}
+
+  unicode-match-property-value-ecmascript@2.1.0:
+    resolution: {integrity: sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==}
+    engines: {node: '>=4'}
+
+  unicode-property-aliases-ecmascript@2.1.0:
+    resolution: {integrity: sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==}
+    engines: {node: '>=4'}
+
+  unified@11.0.4:
+    resolution: {integrity: sha512-apMPnyLjAX+ty4OrNap7yumyVAMlKx5IWU2wlzzUdYJO9A8f1p9m/gywF/GM2ZDFcjQPrx59Mc90KwmxsoklxQ==}
+
+  unique-string@3.0.0:
+    resolution: {integrity: sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==}
+    engines: {node: '>=12'}
+
+  unist-util-is@6.0.0:
+    resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==}
+
+  unist-util-position-from-estree@2.0.0:
+    resolution: {integrity: sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==}
+
+  unist-util-position@5.0.0:
+    resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==}
+
+  unist-util-remove-position@5.0.0:
+    resolution: {integrity: sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==}
+
+  unist-util-stringify-position@3.0.3:
+    resolution: {integrity: sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==}
+
+  unist-util-stringify-position@4.0.0:
+    resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==}
+
+  unist-util-visit-parents@6.0.1:
+    resolution: {integrity: sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==}
+
+  unist-util-visit@5.0.0:
+    resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==}
+
+  universalify@2.0.0:
+    resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==}
+    engines: {node: '>= 10.0.0'}
+
+  unpipe@1.0.0:
+    resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}
+    engines: {node: '>= 0.8'}
+
+  update-browserslist-db@1.0.10:
+    resolution: {integrity: sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==}
+    hasBin: true
+    peerDependencies:
+      browserslist: '>= 4.21.0'
+
+  update-browserslist-db@1.0.13:
+    resolution: {integrity: sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==}
+    hasBin: true
+    peerDependencies:
+      browserslist: '>= 4.21.0'
+
+  update-browserslist-db@1.0.16:
+    resolution: {integrity: sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==}
+    hasBin: true
+    peerDependencies:
+      browserslist: '>= 4.21.0'
+
+  update-notifier@6.0.2:
+    resolution: {integrity: sha512-EDxhTEVPZZRLWYcJ4ZXjGFN0oP7qYvbXWzEgRm/Yql4dHX5wDbvh89YHP6PK1lzZJYrMtXUuZZz8XGK+U6U1og==}
+    engines: {node: '>=14.16'}
+
+  uri-js@4.4.1:
+    resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
+
+  url-loader@4.1.1:
+    resolution: {integrity: sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA==}
+    engines: {node: '>= 10.13.0'}
+    peerDependencies:
+      file-loader: '*'
+      webpack: ^4.0.0 || ^5.0.0
+    peerDependenciesMeta:
+      file-loader:
+        optional: true
+
+  util-deprecate@1.0.2:
+    resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
+
+  utila@0.4.0:
+    resolution: {integrity: sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==}
+
+  utility-types@3.10.0:
+    resolution: {integrity: sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg==}
+    engines: {node: '>= 4'}
+
+  utils-merge@1.0.1:
+    resolution: {integrity: sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=}
+    engines: {node: '>= 0.4.0'}
+
+  uuid@8.3.2:
+    resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
+    hasBin: true
+
+  uuid@9.0.1:
+    resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==}
+    hasBin: true
+
+  uvu@0.5.6:
+    resolution: {integrity: sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==}
+    engines: {node: '>=8'}
+    hasBin: true
+
+  value-equal@1.0.1:
+    resolution: {integrity: sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==}
+
+  vary@1.1.2:
+    resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
+    engines: {node: '>= 0.8'}
+
+  vfile-location@5.0.2:
+    resolution: {integrity: sha512-NXPYyxyBSH7zB5U6+3uDdd6Nybz6o6/od9rk8bp9H8GR3L+cm/fC0uUTbqBmUTnMCUDslAGBOIKNfvvb+gGlDg==}
+
+  vfile-message@4.0.2:
+    resolution: {integrity: sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==}
+
+  vfile@6.0.1:
+    resolution: {integrity: sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw==}
+
+  watchpack@2.4.0:
+    resolution: {integrity: sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==}
+    engines: {node: '>=10.13.0'}
+
+  wbuf@1.7.3:
+    resolution: {integrity: sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==}
+
+  web-namespaces@2.0.1:
+    resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==}
+
+  web-worker@1.2.0:
+    resolution: {integrity: sha512-PgF341avzqyx60neE9DD+XS26MMNMoUQRz9NOZwW32nPQrF6p77f1htcnjBSEV8BGMKZ16choqUG4hyI0Hx7mA==}
+
+  webpack-bundle-analyzer@4.10.1:
+    resolution: {integrity: sha512-s3P7pgexgT/HTUSYgxJyn28A+99mmLq4HsJepMPzu0R8ImJc52QNqaFYW1Z2z2uIb1/J3eYgaAWVpaC+v/1aAQ==}
+    engines: {node: '>= 10.13.0'}
+    hasBin: true
+
+  webpack-dev-middleware@5.3.3:
+    resolution: {integrity: sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==}
+    engines: {node: '>= 12.13.0'}
+    peerDependencies:
+      webpack: ^4.0.0 || ^5.0.0
+
+  webpack-dev-server@4.15.1:
+    resolution: {integrity: sha512-5hbAst3h3C3L8w6W4P96L5vaV0PxSmJhxZvWKYIdgxOQm8pNZ5dEOmmSLBVpP85ReeyRt6AS1QJNyo/oFFPeVA==}
+    engines: {node: '>= 12.13.0'}
+    hasBin: true
+    peerDependencies:
+      webpack: ^4.37.0 || ^5.0.0
+      webpack-cli: '*'
+    peerDependenciesMeta:
+      webpack:
+        optional: true
+      webpack-cli:
+        optional: true
+
+  webpack-merge@5.10.0:
+    resolution: {integrity: sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==}
+    engines: {node: '>=10.0.0'}
+
+  webpack-sources@3.2.3:
+    resolution: {integrity: sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==}
+    engines: {node: '>=10.13.0'}
+
+  webpack@5.90.1:
+    resolution: {integrity: sha512-SstPdlAC5IvgFnhiRok8hqJo/+ArAbNv7rhU4fnWGHNVfN59HSQFaxZDSAL3IFG2YmqxuRs+IU33milSxbPlog==}
+    engines: {node: '>=10.13.0'}
+    hasBin: true
+    peerDependencies:
+      webpack-cli: '*'
+    peerDependenciesMeta:
+      webpack-cli:
+        optional: true
+
+  webpackbar@5.0.2:
+    resolution: {integrity: sha512-BmFJo7veBDgQzfWXl/wwYXr/VFus0614qZ8i9znqcl9fnEdiVkdbi0TedLQ6xAK92HZHDJ0QmyQ0fmuZPAgCYQ==}
+    engines: {node: '>=12'}
+    peerDependencies:
+      webpack: 3 || 4 || 5
+
+  websocket-driver@0.7.4:
+    resolution: {integrity: sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==}
+    engines: {node: '>=0.8.0'}
+
+  websocket-extensions@0.1.4:
+    resolution: {integrity: sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==}
+    engines: {node: '>=0.8.0'}
+
+  which@1.3.1:
+    resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==}
+    hasBin: true
+
+  which@2.0.2:
+    resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
+    engines: {node: '>= 8'}
+    hasBin: true
+
+  widest-line@4.0.1:
+    resolution: {integrity: sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==}
+    engines: {node: '>=12'}
+
+  wildcard@2.0.0:
+    resolution: {integrity: sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==}
+
+  wrap-ansi@7.0.0:
+    resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
+    engines: {node: '>=10'}
+
+  wrap-ansi@8.1.0:
+    resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
+    engines: {node: '>=12'}
+
+  wrappy@1.0.2:
+    resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
+
+  write-file-atomic@3.0.3:
+    resolution: {integrity: sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==}
+
+  ws@7.5.9:
+    resolution: {integrity: sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==}
+    engines: {node: '>=8.3.0'}
+    peerDependencies:
+      bufferutil: ^4.0.1
+      utf-8-validate: ^5.0.2
+    peerDependenciesMeta:
+      bufferutil:
+        optional: true
+      utf-8-validate:
+        optional: true
+
+  ws@8.16.0:
+    resolution: {integrity: sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==}
+    engines: {node: '>=10.0.0'}
+    peerDependencies:
+      bufferutil: ^4.0.1
+      utf-8-validate: '>=5.0.2'
+    peerDependenciesMeta:
+      bufferutil:
+        optional: true
+      utf-8-validate:
+        optional: true
+
+  xdg-basedir@5.1.0:
+    resolution: {integrity: sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==}
+    engines: {node: '>=12'}
+
+  xml-js@1.6.11:
+    resolution: {integrity: sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==}
+    hasBin: true
+
+  yallist@3.1.1:
+    resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
+
+  yallist@4.0.0:
+    resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
+
+  yaml@1.10.2:
+    resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==}
+    engines: {node: '>= 6'}
+
+  yaml@2.5.1:
+    resolution: {integrity: sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==}
+    engines: {node: '>= 14'}
+    hasBin: true
+
+  yocto-queue@0.1.0:
+    resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
+    engines: {node: '>=10'}
+
+  yocto-queue@1.0.0:
+    resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==}
+    engines: {node: '>=12.20'}
+
+  zwitch@2.0.4:
+    resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
+
+snapshots:
+
+  '@algolia/autocomplete-core@1.9.3(@algolia/client-search@4.22.1)(algoliasearch@4.22.1)(search-insights@2.13.0)':
+    dependencies:
+      '@algolia/autocomplete-plugin-algolia-insights': 1.9.3(@algolia/client-search@4.22.1)(algoliasearch@4.22.1)(search-insights@2.13.0)
+      '@algolia/autocomplete-shared': 1.9.3(@algolia/client-search@4.22.1)(algoliasearch@4.22.1)
+    transitivePeerDependencies:
+      - '@algolia/client-search'
+      - algoliasearch
+      - search-insights
+
+  '@algolia/autocomplete-plugin-algolia-insights@1.9.3(@algolia/client-search@4.22.1)(algoliasearch@4.22.1)(search-insights@2.13.0)':
+    dependencies:
+      '@algolia/autocomplete-shared': 1.9.3(@algolia/client-search@4.22.1)(algoliasearch@4.22.1)
+      search-insights: 2.13.0
+    transitivePeerDependencies:
+      - '@algolia/client-search'
+      - algoliasearch
+
+  '@algolia/autocomplete-preset-algolia@1.9.3(@algolia/client-search@4.22.1)(algoliasearch@4.22.1)':
+    dependencies:
+      '@algolia/autocomplete-shared': 1.9.3(@algolia/client-search@4.22.1)(algoliasearch@4.22.1)
+      '@algolia/client-search': 4.22.1
+      algoliasearch: 4.22.1
+
+  '@algolia/autocomplete-shared@1.9.3(@algolia/client-search@4.22.1)(algoliasearch@4.22.1)':
+    dependencies:
+      '@algolia/client-search': 4.22.1
+      algoliasearch: 4.22.1
+
+  '@algolia/cache-browser-local-storage@4.22.1':
+    dependencies:
+      '@algolia/cache-common': 4.22.1
+
+  '@algolia/cache-common@4.22.1': {}
+
+  '@algolia/cache-in-memory@4.22.1':
+    dependencies:
+      '@algolia/cache-common': 4.22.1
+
+  '@algolia/client-account@4.22.1':
+    dependencies:
+      '@algolia/client-common': 4.22.1
+      '@algolia/client-search': 4.22.1
+      '@algolia/transporter': 4.22.1
+
+  '@algolia/client-analytics@4.22.1':
+    dependencies:
+      '@algolia/client-common': 4.22.1
+      '@algolia/client-search': 4.22.1
+      '@algolia/requester-common': 4.22.1
+      '@algolia/transporter': 4.22.1
+
+  '@algolia/client-common@4.22.1':
+    dependencies:
+      '@algolia/requester-common': 4.22.1
+      '@algolia/transporter': 4.22.1
+
+  '@algolia/client-personalization@4.22.1':
+    dependencies:
+      '@algolia/client-common': 4.22.1
+      '@algolia/requester-common': 4.22.1
+      '@algolia/transporter': 4.22.1
+
+  '@algolia/client-search@4.22.1':
+    dependencies:
+      '@algolia/client-common': 4.22.1
+      '@algolia/requester-common': 4.22.1
+      '@algolia/transporter': 4.22.1
+
+  '@algolia/events@4.0.1': {}
+
+  '@algolia/logger-common@4.22.1': {}
+
+  '@algolia/logger-console@4.22.1':
+    dependencies:
+      '@algolia/logger-common': 4.22.1
+
+  '@algolia/requester-browser-xhr@4.22.1':
+    dependencies:
+      '@algolia/requester-common': 4.22.1
+
+  '@algolia/requester-common@4.22.1': {}
+
+  '@algolia/requester-node-http@4.22.1':
+    dependencies:
+      '@algolia/requester-common': 4.22.1
+
+  '@algolia/transporter@4.22.1':
+    dependencies:
+      '@algolia/cache-common': 4.22.1
+      '@algolia/logger-common': 4.22.1
+      '@algolia/requester-common': 4.22.1
+
+  '@alloc/quick-lru@5.2.0': {}
+
+  '@ampproject/remapping@2.2.0':
+    dependencies:
+      '@jridgewell/gen-mapping': 0.1.1
+      '@jridgewell/trace-mapping': 0.3.25
+
+  '@babel/code-frame@7.23.5':
+    dependencies:
+      '@babel/highlight': 7.23.4
+      chalk: 2.4.2
+
+  '@babel/compat-data@7.23.5': {}
+
+  '@babel/core@7.23.9':
+    dependencies:
+      '@ampproject/remapping': 2.2.0
+      '@babel/code-frame': 7.23.5
+      '@babel/generator': 7.23.6
+      '@babel/helper-compilation-targets': 7.23.6
+      '@babel/helper-module-transforms': 7.23.3(@babel/core@7.23.9)
+      '@babel/helpers': 7.23.9
+      '@babel/parser': 7.23.9
+      '@babel/template': 7.23.9
+      '@babel/traverse': 7.23.9
+      '@babel/types': 7.23.9
+      convert-source-map: 2.0.0
+      debug: 4.3.4
+      gensync: 1.0.0-beta.2
+      json5: 2.2.3
+      semver: 6.3.1
+    transitivePeerDependencies:
+      - supports-color
+
+  '@babel/generator@7.23.6':
+    dependencies:
+      '@babel/types': 7.23.9
+      '@jridgewell/gen-mapping': 0.3.5
+      '@jridgewell/trace-mapping': 0.3.25
+      jsesc: 2.5.2
+
+  '@babel/helper-annotate-as-pure@7.22.5':
+    dependencies:
+      '@babel/types': 7.23.9
+
+  '@babel/helper-builder-binary-assignment-operator-visitor@7.22.15':
+    dependencies:
+      '@babel/types': 7.23.9
+
+  '@babel/helper-compilation-targets@7.23.6':
+    dependencies:
+      '@babel/compat-data': 7.23.5
+      '@babel/helper-validator-option': 7.23.5
+      browserslist: 4.22.3
+      lru-cache: 5.1.1
+      semver: 6.3.1
+
+  '@babel/helper-create-class-features-plugin@7.23.10(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-annotate-as-pure': 7.22.5
+      '@babel/helper-environment-visitor': 7.22.20
+      '@babel/helper-function-name': 7.23.0
+      '@babel/helper-member-expression-to-functions': 7.23.0
+      '@babel/helper-optimise-call-expression': 7.22.5
+      '@babel/helper-replace-supers': 7.22.20(@babel/core@7.23.9)
+      '@babel/helper-skip-transparent-expression-wrappers': 7.22.5
+      '@babel/helper-split-export-declaration': 7.22.6
+      semver: 6.3.1
+
+  '@babel/helper-create-regexp-features-plugin@7.22.15(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-annotate-as-pure': 7.22.5
+      regexpu-core: 5.3.2
+      semver: 6.3.1
+
+  '@babel/helper-define-polyfill-provider@0.5.0(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-compilation-targets': 7.23.6
+      '@babel/helper-plugin-utils': 7.22.5
+      debug: 4.3.4
+      lodash.debounce: 4.0.8
+      resolve: 1.22.8
+    transitivePeerDependencies:
+      - supports-color
+
+  '@babel/helper-environment-visitor@7.22.20': {}
+
+  '@babel/helper-function-name@7.23.0':
+    dependencies:
+      '@babel/template': 7.23.9
+      '@babel/types': 7.23.9
+
+  '@babel/helper-hoist-variables@7.22.5':
+    dependencies:
+      '@babel/types': 7.23.9
+
+  '@babel/helper-member-expression-to-functions@7.23.0':
+    dependencies:
+      '@babel/types': 7.23.9
+
+  '@babel/helper-module-imports@7.22.15':
+    dependencies:
+      '@babel/types': 7.23.9
+
+  '@babel/helper-module-transforms@7.23.3(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-environment-visitor': 7.22.20
+      '@babel/helper-module-imports': 7.22.15
+      '@babel/helper-simple-access': 7.22.5
+      '@babel/helper-split-export-declaration': 7.22.6
+      '@babel/helper-validator-identifier': 7.22.20
+
+  '@babel/helper-optimise-call-expression@7.22.5':
+    dependencies:
+      '@babel/types': 7.23.9
+
+  '@babel/helper-plugin-utils@7.22.5': {}
+
+  '@babel/helper-plugin-utils@7.24.7': {}
+
+  '@babel/helper-remap-async-to-generator@7.22.20(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-annotate-as-pure': 7.22.5
+      '@babel/helper-environment-visitor': 7.22.20
+      '@babel/helper-wrap-function': 7.22.20
+
+  '@babel/helper-replace-supers@7.22.20(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-environment-visitor': 7.22.20
+      '@babel/helper-member-expression-to-functions': 7.23.0
+      '@babel/helper-optimise-call-expression': 7.22.5
+
+  '@babel/helper-simple-access@7.22.5':
+    dependencies:
+      '@babel/types': 7.23.9
+
+  '@babel/helper-skip-transparent-expression-wrappers@7.22.5':
+    dependencies:
+      '@babel/types': 7.23.9
+
+  '@babel/helper-split-export-declaration@7.22.6':
+    dependencies:
+      '@babel/types': 7.23.9
+
+  '@babel/helper-string-parser@7.23.4': {}
+
+  '@babel/helper-validator-identifier@7.22.20': {}
+
+  '@babel/helper-validator-option@7.23.5': {}
+
+  '@babel/helper-wrap-function@7.22.20':
+    dependencies:
+      '@babel/helper-function-name': 7.23.0
+      '@babel/template': 7.23.9
+      '@babel/types': 7.23.9
+
+  '@babel/helpers@7.23.9':
+    dependencies:
+      '@babel/template': 7.23.9
+      '@babel/traverse': 7.23.9
+      '@babel/types': 7.23.9
+    transitivePeerDependencies:
+      - supports-color
+
+  '@babel/highlight@7.23.4':
+    dependencies:
+      '@babel/helper-validator-identifier': 7.22.20
+      chalk: 2.4.2
+      js-tokens: 4.0.0
+
+  '@babel/parser@7.23.9':
+    dependencies:
+      '@babel/types': 7.23.9
+
+  '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.23.3(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-plugin-utils': 7.22.5
+
+  '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.23.3(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-plugin-utils': 7.22.5
+      '@babel/helper-skip-transparent-expression-wrappers': 7.22.5
+      '@babel/plugin-transform-optional-chaining': 7.23.4(@babel/core@7.23.9)
+
+  '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.23.7(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-environment-visitor': 7.22.20
+      '@babel/helper-plugin-utils': 7.22.5
+
+  '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+
+  '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-plugin-utils': 7.22.5
+
+  '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-plugin-utils': 7.22.5
+
+  '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-plugin-utils': 7.22.5
+
+  '@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-plugin-utils': 7.22.5
+
+  '@babel/plugin-syntax-export-namespace-from@7.8.3(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-plugin-utils': 7.22.5
+
+  '@babel/plugin-syntax-import-assertions@7.23.3(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-plugin-utils': 7.22.5
+
+  '@babel/plugin-syntax-import-attributes@7.23.3(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-plugin-utils': 7.22.5
+
+  '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-plugin-utils': 7.22.5
+
+  '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-plugin-utils': 7.22.5
+
+  '@babel/plugin-syntax-jsx@7.23.3(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-plugin-utils': 7.22.5
+
+  '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-plugin-utils': 7.22.5
+
+  '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-plugin-utils': 7.22.5
+
+  '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-plugin-utils': 7.22.5
+
+  '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-plugin-utils': 7.22.5
+
+  '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-plugin-utils': 7.22.5
+
+  '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-plugin-utils': 7.22.5
+
+  '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-plugin-utils': 7.22.5
+
+  '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-plugin-utils': 7.22.5
+
+  '@babel/plugin-syntax-typescript@7.23.3(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-plugin-utils': 7.22.5
+
+  '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.23.9)
+      '@babel/helper-plugin-utils': 7.22.5
+
+  '@babel/plugin-transform-arrow-functions@7.23.3(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-plugin-utils': 7.22.5
+
+  '@babel/plugin-transform-async-generator-functions@7.23.9(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-environment-visitor': 7.22.20
+      '@babel/helper-plugin-utils': 7.22.5
+      '@babel/helper-remap-async-to-generator': 7.22.20(@babel/core@7.23.9)
+      '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.23.9)
+
+  '@babel/plugin-transform-async-to-generator@7.23.3(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-module-imports': 7.22.15
+      '@babel/helper-plugin-utils': 7.22.5
+      '@babel/helper-remap-async-to-generator': 7.22.20(@babel/core@7.23.9)
+
+  '@babel/plugin-transform-block-scoped-functions@7.23.3(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-plugin-utils': 7.22.5
+
+  '@babel/plugin-transform-block-scoping@7.23.4(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-plugin-utils': 7.22.5
+
+  '@babel/plugin-transform-class-properties@7.23.3(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-create-class-features-plugin': 7.23.10(@babel/core@7.23.9)
+      '@babel/helper-plugin-utils': 7.22.5
+
+  '@babel/plugin-transform-class-static-block@7.23.4(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-create-class-features-plugin': 7.23.10(@babel/core@7.23.9)
+      '@babel/helper-plugin-utils': 7.22.5
+      '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.23.9)
+
+  '@babel/plugin-transform-classes@7.23.8(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-annotate-as-pure': 7.22.5
+      '@babel/helper-compilation-targets': 7.23.6
+      '@babel/helper-environment-visitor': 7.22.20
+      '@babel/helper-function-name': 7.23.0
+      '@babel/helper-plugin-utils': 7.22.5
+      '@babel/helper-replace-supers': 7.22.20(@babel/core@7.23.9)
+      '@babel/helper-split-export-declaration': 7.22.6
+      globals: 11.12.0
+
+  '@babel/plugin-transform-computed-properties@7.23.3(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-plugin-utils': 7.22.5
+      '@babel/template': 7.23.9
+
+  '@babel/plugin-transform-destructuring@7.23.3(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-plugin-utils': 7.22.5
+
+  '@babel/plugin-transform-dotall-regex@7.23.3(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.23.9)
+      '@babel/helper-plugin-utils': 7.22.5
+
+  '@babel/plugin-transform-duplicate-keys@7.23.3(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-plugin-utils': 7.22.5
+
+  '@babel/plugin-transform-dynamic-import@7.23.4(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-plugin-utils': 7.22.5
+      '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.23.9)
+
+  '@babel/plugin-transform-exponentiation-operator@7.23.3(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-builder-binary-assignment-operator-visitor': 7.22.15
+      '@babel/helper-plugin-utils': 7.22.5
+
+  '@babel/plugin-transform-export-namespace-from@7.23.4(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-plugin-utils': 7.22.5
+      '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.23.9)
+
+  '@babel/plugin-transform-for-of@7.23.6(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-plugin-utils': 7.22.5
+      '@babel/helper-skip-transparent-expression-wrappers': 7.22.5
+
+  '@babel/plugin-transform-function-name@7.23.3(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-compilation-targets': 7.23.6
+      '@babel/helper-function-name': 7.23.0
+      '@babel/helper-plugin-utils': 7.22.5
+
+  '@babel/plugin-transform-json-strings@7.23.4(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-plugin-utils': 7.22.5
+      '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.23.9)
+
+  '@babel/plugin-transform-literals@7.23.3(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-plugin-utils': 7.22.5
+
+  '@babel/plugin-transform-logical-assignment-operators@7.23.4(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-plugin-utils': 7.22.5
+      '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.23.9)
+
+  '@babel/plugin-transform-member-expression-literals@7.23.3(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-plugin-utils': 7.22.5
+
+  '@babel/plugin-transform-modules-amd@7.23.3(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-module-transforms': 7.23.3(@babel/core@7.23.9)
+      '@babel/helper-plugin-utils': 7.22.5
+
+  '@babel/plugin-transform-modules-commonjs@7.23.3(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-module-transforms': 7.23.3(@babel/core@7.23.9)
+      '@babel/helper-plugin-utils': 7.22.5
+      '@babel/helper-simple-access': 7.22.5
+
+  '@babel/plugin-transform-modules-systemjs@7.23.9(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-hoist-variables': 7.22.5
+      '@babel/helper-module-transforms': 7.23.3(@babel/core@7.23.9)
+      '@babel/helper-plugin-utils': 7.22.5
+      '@babel/helper-validator-identifier': 7.22.20
+
+  '@babel/plugin-transform-modules-umd@7.23.3(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-module-transforms': 7.23.3(@babel/core@7.23.9)
+      '@babel/helper-plugin-utils': 7.22.5
+
+  '@babel/plugin-transform-named-capturing-groups-regex@7.22.5(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.23.9)
+      '@babel/helper-plugin-utils': 7.22.5
+
+  '@babel/plugin-transform-new-target@7.23.3(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-plugin-utils': 7.22.5
+
+  '@babel/plugin-transform-nullish-coalescing-operator@7.23.4(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-plugin-utils': 7.22.5
+      '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.23.9)
+
+  '@babel/plugin-transform-numeric-separator@7.23.4(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-plugin-utils': 7.22.5
+      '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.23.9)
+
+  '@babel/plugin-transform-object-rest-spread@7.23.4(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/compat-data': 7.23.5
+      '@babel/core': 7.23.9
+      '@babel/helper-compilation-targets': 7.23.6
+      '@babel/helper-plugin-utils': 7.22.5
+      '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.23.9)
+      '@babel/plugin-transform-parameters': 7.23.3(@babel/core@7.23.9)
+
+  '@babel/plugin-transform-object-super@7.23.3(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-plugin-utils': 7.22.5
+      '@babel/helper-replace-supers': 7.22.20(@babel/core@7.23.9)
+
+  '@babel/plugin-transform-optional-catch-binding@7.23.4(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-plugin-utils': 7.22.5
+      '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.23.9)
+
+  '@babel/plugin-transform-optional-chaining@7.23.4(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-plugin-utils': 7.22.5
+      '@babel/helper-skip-transparent-expression-wrappers': 7.22.5
+      '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.23.9)
+
+  '@babel/plugin-transform-parameters@7.23.3(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-plugin-utils': 7.22.5
+
+  '@babel/plugin-transform-private-methods@7.23.3(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-create-class-features-plugin': 7.23.10(@babel/core@7.23.9)
+      '@babel/helper-plugin-utils': 7.22.5
+
+  '@babel/plugin-transform-private-property-in-object@7.23.4(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-annotate-as-pure': 7.22.5
+      '@babel/helper-create-class-features-plugin': 7.23.10(@babel/core@7.23.9)
+      '@babel/helper-plugin-utils': 7.22.5
+      '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.23.9)
+
+  '@babel/plugin-transform-property-literals@7.23.3(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-plugin-utils': 7.22.5
+
+  '@babel/plugin-transform-react-constant-elements@7.24.7(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-plugin-utils': 7.24.7
+
+  '@babel/plugin-transform-react-display-name@7.23.3(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-plugin-utils': 7.22.5
+
+  '@babel/plugin-transform-react-jsx-development@7.22.5(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/plugin-transform-react-jsx': 7.23.4(@babel/core@7.23.9)
+
+  '@babel/plugin-transform-react-jsx@7.23.4(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-annotate-as-pure': 7.22.5
+      '@babel/helper-module-imports': 7.22.15
+      '@babel/helper-plugin-utils': 7.22.5
+      '@babel/plugin-syntax-jsx': 7.23.3(@babel/core@7.23.9)
+      '@babel/types': 7.23.9
+
+  '@babel/plugin-transform-react-pure-annotations@7.23.3(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-annotate-as-pure': 7.22.5
+      '@babel/helper-plugin-utils': 7.22.5
+
+  '@babel/plugin-transform-regenerator@7.23.3(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-plugin-utils': 7.22.5
+      regenerator-transform: 0.15.2
+
+  '@babel/plugin-transform-reserved-words@7.23.3(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-plugin-utils': 7.22.5
+
+  '@babel/plugin-transform-runtime@7.23.9(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-module-imports': 7.22.15
+      '@babel/helper-plugin-utils': 7.22.5
+      babel-plugin-polyfill-corejs2: 0.4.8(@babel/core@7.23.9)
+      babel-plugin-polyfill-corejs3: 0.9.0(@babel/core@7.23.9)
+      babel-plugin-polyfill-regenerator: 0.5.5(@babel/core@7.23.9)
+      semver: 6.3.1
+    transitivePeerDependencies:
+      - supports-color
+
+  '@babel/plugin-transform-shorthand-properties@7.23.3(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-plugin-utils': 7.22.5
+
+  '@babel/plugin-transform-spread@7.23.3(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-plugin-utils': 7.22.5
+      '@babel/helper-skip-transparent-expression-wrappers': 7.22.5
+
+  '@babel/plugin-transform-sticky-regex@7.23.3(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-plugin-utils': 7.22.5
+
+  '@babel/plugin-transform-template-literals@7.23.3(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-plugin-utils': 7.22.5
+
+  '@babel/plugin-transform-typeof-symbol@7.23.3(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-plugin-utils': 7.22.5
+
+  '@babel/plugin-transform-typescript@7.23.6(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-annotate-as-pure': 7.22.5
+      '@babel/helper-create-class-features-plugin': 7.23.10(@babel/core@7.23.9)
+      '@babel/helper-plugin-utils': 7.22.5
+      '@babel/plugin-syntax-typescript': 7.23.3(@babel/core@7.23.9)
+
+  '@babel/plugin-transform-unicode-escapes@7.23.3(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-plugin-utils': 7.22.5
+
+  '@babel/plugin-transform-unicode-property-regex@7.23.3(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.23.9)
+      '@babel/helper-plugin-utils': 7.22.5
+
+  '@babel/plugin-transform-unicode-regex@7.23.3(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.23.9)
+      '@babel/helper-plugin-utils': 7.22.5
+
+  '@babel/plugin-transform-unicode-sets-regex@7.23.3(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.23.9)
+      '@babel/helper-plugin-utils': 7.22.5
+
+  '@babel/preset-env@7.23.9(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/compat-data': 7.23.5
+      '@babel/core': 7.23.9
+      '@babel/helper-compilation-targets': 7.23.6
+      '@babel/helper-plugin-utils': 7.22.5
+      '@babel/helper-validator-option': 7.23.5
+      '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.23.3(@babel/core@7.23.9)
+      '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.23.3(@babel/core@7.23.9)
+      '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.23.7(@babel/core@7.23.9)
+      '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.23.9)
+      '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.23.9)
+      '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.23.9)
+      '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.23.9)
+      '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.23.9)
+      '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.23.9)
+      '@babel/plugin-syntax-import-assertions': 7.23.3(@babel/core@7.23.9)
+      '@babel/plugin-syntax-import-attributes': 7.23.3(@babel/core@7.23.9)
+      '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.23.9)
+      '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.23.9)
+      '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.23.9)
+      '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.23.9)
+      '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.23.9)
+      '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.23.9)
+      '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.23.9)
+      '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.23.9)
+      '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.23.9)
+      '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.23.9)
+      '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.23.9)
+      '@babel/plugin-transform-arrow-functions': 7.23.3(@babel/core@7.23.9)
+      '@babel/plugin-transform-async-generator-functions': 7.23.9(@babel/core@7.23.9)
+      '@babel/plugin-transform-async-to-generator': 7.23.3(@babel/core@7.23.9)
+      '@babel/plugin-transform-block-scoped-functions': 7.23.3(@babel/core@7.23.9)
+      '@babel/plugin-transform-block-scoping': 7.23.4(@babel/core@7.23.9)
+      '@babel/plugin-transform-class-properties': 7.23.3(@babel/core@7.23.9)
+      '@babel/plugin-transform-class-static-block': 7.23.4(@babel/core@7.23.9)
+      '@babel/plugin-transform-classes': 7.23.8(@babel/core@7.23.9)
+      '@babel/plugin-transform-computed-properties': 7.23.3(@babel/core@7.23.9)
+      '@babel/plugin-transform-destructuring': 7.23.3(@babel/core@7.23.9)
+      '@babel/plugin-transform-dotall-regex': 7.23.3(@babel/core@7.23.9)
+      '@babel/plugin-transform-duplicate-keys': 7.23.3(@babel/core@7.23.9)
+      '@babel/plugin-transform-dynamic-import': 7.23.4(@babel/core@7.23.9)
+      '@babel/plugin-transform-exponentiation-operator': 7.23.3(@babel/core@7.23.9)
+      '@babel/plugin-transform-export-namespace-from': 7.23.4(@babel/core@7.23.9)
+      '@babel/plugin-transform-for-of': 7.23.6(@babel/core@7.23.9)
+      '@babel/plugin-transform-function-name': 7.23.3(@babel/core@7.23.9)
+      '@babel/plugin-transform-json-strings': 7.23.4(@babel/core@7.23.9)
+      '@babel/plugin-transform-literals': 7.23.3(@babel/core@7.23.9)
+      '@babel/plugin-transform-logical-assignment-operators': 7.23.4(@babel/core@7.23.9)
+      '@babel/plugin-transform-member-expression-literals': 7.23.3(@babel/core@7.23.9)
+      '@babel/plugin-transform-modules-amd': 7.23.3(@babel/core@7.23.9)
+      '@babel/plugin-transform-modules-commonjs': 7.23.3(@babel/core@7.23.9)
+      '@babel/plugin-transform-modules-systemjs': 7.23.9(@babel/core@7.23.9)
+      '@babel/plugin-transform-modules-umd': 7.23.3(@babel/core@7.23.9)
+      '@babel/plugin-transform-named-capturing-groups-regex': 7.22.5(@babel/core@7.23.9)
+      '@babel/plugin-transform-new-target': 7.23.3(@babel/core@7.23.9)
+      '@babel/plugin-transform-nullish-coalescing-operator': 7.23.4(@babel/core@7.23.9)
+      '@babel/plugin-transform-numeric-separator': 7.23.4(@babel/core@7.23.9)
+      '@babel/plugin-transform-object-rest-spread': 7.23.4(@babel/core@7.23.9)
+      '@babel/plugin-transform-object-super': 7.23.3(@babel/core@7.23.9)
+      '@babel/plugin-transform-optional-catch-binding': 7.23.4(@babel/core@7.23.9)
+      '@babel/plugin-transform-optional-chaining': 7.23.4(@babel/core@7.23.9)
+      '@babel/plugin-transform-parameters': 7.23.3(@babel/core@7.23.9)
+      '@babel/plugin-transform-private-methods': 7.23.3(@babel/core@7.23.9)
+      '@babel/plugin-transform-private-property-in-object': 7.23.4(@babel/core@7.23.9)
+      '@babel/plugin-transform-property-literals': 7.23.3(@babel/core@7.23.9)
+      '@babel/plugin-transform-regenerator': 7.23.3(@babel/core@7.23.9)
+      '@babel/plugin-transform-reserved-words': 7.23.3(@babel/core@7.23.9)
+      '@babel/plugin-transform-shorthand-properties': 7.23.3(@babel/core@7.23.9)
+      '@babel/plugin-transform-spread': 7.23.3(@babel/core@7.23.9)
+      '@babel/plugin-transform-sticky-regex': 7.23.3(@babel/core@7.23.9)
+      '@babel/plugin-transform-template-literals': 7.23.3(@babel/core@7.23.9)
+      '@babel/plugin-transform-typeof-symbol': 7.23.3(@babel/core@7.23.9)
+      '@babel/plugin-transform-unicode-escapes': 7.23.3(@babel/core@7.23.9)
+      '@babel/plugin-transform-unicode-property-regex': 7.23.3(@babel/core@7.23.9)
+      '@babel/plugin-transform-unicode-regex': 7.23.3(@babel/core@7.23.9)
+      '@babel/plugin-transform-unicode-sets-regex': 7.23.3(@babel/core@7.23.9)
+      '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.23.9)
+      babel-plugin-polyfill-corejs2: 0.4.8(@babel/core@7.23.9)
+      babel-plugin-polyfill-corejs3: 0.9.0(@babel/core@7.23.9)
+      babel-plugin-polyfill-regenerator: 0.5.5(@babel/core@7.23.9)
+      core-js-compat: 3.35.1
+      semver: 6.3.1
+    transitivePeerDependencies:
+      - supports-color
+
+  '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-plugin-utils': 7.22.5
+      '@babel/types': 7.23.9
+      esutils: 2.0.3
+
+  '@babel/preset-react@7.23.3(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-plugin-utils': 7.22.5
+      '@babel/helper-validator-option': 7.23.5
+      '@babel/plugin-transform-react-display-name': 7.23.3(@babel/core@7.23.9)
+      '@babel/plugin-transform-react-jsx': 7.23.4(@babel/core@7.23.9)
+      '@babel/plugin-transform-react-jsx-development': 7.22.5(@babel/core@7.23.9)
+      '@babel/plugin-transform-react-pure-annotations': 7.23.3(@babel/core@7.23.9)
+
+  '@babel/preset-typescript@7.23.3(@babel/core@7.23.9)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/helper-plugin-utils': 7.22.5
+      '@babel/helper-validator-option': 7.23.5
+      '@babel/plugin-syntax-jsx': 7.23.3(@babel/core@7.23.9)
+      '@babel/plugin-transform-modules-commonjs': 7.23.3(@babel/core@7.23.9)
+      '@babel/plugin-transform-typescript': 7.23.6(@babel/core@7.23.9)
+
+  '@babel/regjsgen@0.8.0': {}
+
+  '@babel/runtime-corejs3@7.23.9':
+    dependencies:
+      core-js-pure: 3.35.1
+      regenerator-runtime: 0.14.1
+
+  '@babel/runtime@7.23.9':
+    dependencies:
+      regenerator-runtime: 0.14.1
+
+  '@babel/template@7.23.9':
+    dependencies:
+      '@babel/code-frame': 7.23.5
+      '@babel/parser': 7.23.9
+      '@babel/types': 7.23.9
+
+  '@babel/traverse@7.23.9':
+    dependencies:
+      '@babel/code-frame': 7.23.5
+      '@babel/generator': 7.23.6
+      '@babel/helper-environment-visitor': 7.22.20
+      '@babel/helper-function-name': 7.23.0
+      '@babel/helper-hoist-variables': 7.22.5
+      '@babel/helper-split-export-declaration': 7.22.6
+      '@babel/parser': 7.23.9
+      '@babel/types': 7.23.9
+      debug: 4.3.4
+      globals: 11.12.0
+    transitivePeerDependencies:
+      - supports-color
+
+  '@babel/types@7.23.9':
     dependencies:
-      '@algolia/autocomplete-core': 1.7.2
-      '@algolia/autocomplete-preset-algolia': 1.7.2(@algolia/client-search@4.14.3)(algoliasearch@4.14.3)
-      '@docsearch/css': 3.3.1
+      '@babel/helper-string-parser': 7.23.4
+      '@babel/helper-validator-identifier': 7.22.20
+      to-fast-properties: 2.0.0
+
+  '@braintree/sanitize-url@6.0.4': {}
+
+  '@colors/colors@1.5.0':
+    optional: true
+
+  '@discoveryjs/json-ext@0.5.7': {}
+
+  '@docsearch/css@3.5.2': {}
+
+  '@docsearch/react@3.5.2(@algolia/client-search@4.22.1)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(search-insights@2.13.0)':
+    dependencies:
+      '@algolia/autocomplete-core': 1.9.3(@algolia/client-search@4.22.1)(algoliasearch@4.22.1)(search-insights@2.13.0)
+      '@algolia/autocomplete-preset-algolia': 1.9.3(@algolia/client-search@4.22.1)(algoliasearch@4.22.1)
+      '@docsearch/css': 3.5.2
+      algoliasearch: 4.22.1
+    optionalDependencies:
       '@types/react': 18.0.26
-      algoliasearch: 4.14.3
-      react: 17.0.2
-      react-dom: 17.0.2(react@17.0.2)
+      react: 18.2.0
+      react-dom: 18.2.0(react@18.2.0)
+      search-insights: 2.13.0
     transitivePeerDependencies:
       - '@algolia/client-search'
-    dev: false
 
-  /@docusaurus/core@2.4.3(@docusaurus/types@2.4.3)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.4):
-    resolution: {integrity: sha512-dWH5P7cgeNSIg9ufReX6gaCl/TmrGKD38Orbwuz05WPhAQtFXHd5B8Qym1TiXfvUNvwoYKkAJOJuGe8ou0Z7PA==}
-    engines: {node: '>=16.14'}
-    hasBin: true
-    peerDependencies:
-      react: ^16.8.4 || ^17.0.0
-      react-dom: ^16.8.4 || ^17.0.0
-    dependencies:
-      '@babel/core': 7.20.12
-      '@babel/generator': 7.20.7
-      '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.20.12)
-      '@babel/plugin-transform-runtime': 7.19.6(@babel/core@7.20.12)
-      '@babel/preset-env': 7.20.2(@babel/core@7.20.12)
-      '@babel/preset-react': 7.18.6(@babel/core@7.20.12)
-      '@babel/preset-typescript': 7.18.6(@babel/core@7.20.12)
-      '@babel/runtime': 7.20.7
-      '@babel/runtime-corejs3': 7.20.7
-      '@babel/traverse': 7.20.12
-      '@docusaurus/cssnano-preset': 2.4.3
-      '@docusaurus/logger': 2.4.3
-      '@docusaurus/mdx-loader': 2.4.3(@docusaurus/types@2.4.3)(react-dom@17.0.2)(react@17.0.2)
-      '@docusaurus/react-loadable': 5.5.2(react@17.0.2)
-      '@docusaurus/utils': 2.4.3(@docusaurus/types@2.4.3)
-      '@docusaurus/utils-common': 2.4.3(@docusaurus/types@2.4.3)
-      '@docusaurus/utils-validation': 2.4.3(@docusaurus/types@2.4.3)
-      '@slorber/static-site-generator-webpack-plugin': 4.0.7
-      '@svgr/webpack': 6.5.1
-      autoprefixer: 10.4.13(postcss@8.4.21)
-      babel-loader: 8.3.0(@babel/core@7.20.12)(webpack@5.75.0)
+  '@docusaurus/core@3.4.0(@docusaurus/types@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.3.3)':
+    dependencies:
+      '@babel/core': 7.23.9
+      '@babel/generator': 7.23.6
+      '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.23.9)
+      '@babel/plugin-transform-runtime': 7.23.9(@babel/core@7.23.9)
+      '@babel/preset-env': 7.23.9(@babel/core@7.23.9)
+      '@babel/preset-react': 7.23.3(@babel/core@7.23.9)
+      '@babel/preset-typescript': 7.23.3(@babel/core@7.23.9)
+      '@babel/runtime': 7.23.9
+      '@babel/runtime-corejs3': 7.23.9
+      '@babel/traverse': 7.23.9
+      '@docusaurus/cssnano-preset': 3.4.0
+      '@docusaurus/logger': 3.4.0
+      '@docusaurus/mdx-loader': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.3.3)
+      '@docusaurus/utils': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(typescript@5.3.3)
+      '@docusaurus/utils-common': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))
+      '@docusaurus/utils-validation': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(typescript@5.3.3)
+      autoprefixer: 10.4.17(postcss@8.4.35)
+      babel-loader: 9.1.3(@babel/core@7.23.9)(webpack@5.90.1)
       babel-plugin-dynamic-import-node: 2.3.3
       boxen: 6.2.1
       chalk: 4.1.2
       chokidar: 3.5.3
-      clean-css: 5.3.1
+      clean-css: 5.3.3
       cli-table3: 0.6.3
       combine-promises: 1.1.0
       commander: 5.1.0
-      copy-webpack-plugin: 11.0.0(webpack@5.75.0)
-      core-js: 3.27.1
-      css-loader: 6.7.3(webpack@5.75.0)
-      css-minimizer-webpack-plugin: 4.2.2(clean-css@5.3.1)(webpack@5.75.0)
-      cssnano: 5.1.14(postcss@8.4.21)
+      copy-webpack-plugin: 11.0.0(webpack@5.90.1)
+      core-js: 3.35.1
+      css-loader: 6.10.0(webpack@5.90.1)
+      css-minimizer-webpack-plugin: 5.0.1(clean-css@5.3.3)(webpack@5.90.1)
+      cssnano: 6.1.2(postcss@8.4.35)
       del: 6.1.1
       detect-port: 1.5.1
       escape-html: 1.0.3
-      eta: 2.0.0
-      file-loader: 6.2.0(webpack@5.75.0)
-      fs-extra: 10.1.0
-      html-minifier-terser: 6.1.0
-      html-tags: 3.2.0
-      html-webpack-plugin: 5.5.0(webpack@5.75.0)
-      import-fresh: 3.3.0
+      eta: 2.2.0
+      eval: 0.1.8
+      file-loader: 6.2.0(webpack@5.90.1)
+      fs-extra: 11.2.0
+      html-minifier-terser: 7.2.0
+      html-tags: 3.3.1
+      html-webpack-plugin: 5.6.0(webpack@5.90.1)
       leven: 3.1.0
       lodash: 4.17.21
-      mini-css-extract-plugin: 2.7.2(webpack@5.75.0)
-      postcss: 8.4.21
-      postcss-loader: 7.0.2(postcss@8.4.21)(webpack@5.75.0)
+      mini-css-extract-plugin: 2.8.0(webpack@5.90.1)
+      p-map: 4.0.0
+      postcss: 8.4.35
+      postcss-loader: 7.3.4(postcss@8.4.35)(typescript@5.3.3)(webpack@5.90.1)
       prompts: 2.4.2
-      react: 17.0.2
-      react-dev-utils: 12.0.1(typescript@4.9.4)(webpack@5.75.0)
-      react-dom: 17.0.2(react@17.0.2)
-      react-helmet-async: 1.3.0(react-dom@17.0.2)(react@17.0.2)
-      react-loadable: /@docusaurus/react-loadable@5.5.2(react@17.0.2)
-      react-loadable-ssr-addon-v5-slorber: 1.0.1(@docusaurus/react-loadable@5.5.2)(webpack@5.75.0)
-      react-router: 5.3.4(react@17.0.2)
-      react-router-config: 5.1.1(react-router@5.3.4)(react@17.0.2)
-      react-router-dom: 5.3.4(react@17.0.2)
+      react: 18.2.0
+      react-dev-utils: 12.0.1(typescript@5.3.3)(webpack@5.90.1)
+      react-dom: 18.2.0(react@18.2.0)
+      react-helmet-async: 1.3.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      react-loadable: '@docusaurus/react-loadable@6.0.0(react@18.2.0)'
+      react-loadable-ssr-addon-v5-slorber: 1.0.1(@docusaurus/react-loadable@6.0.0(react@18.2.0))(webpack@5.90.1)
+      react-router: 5.3.4(react@18.2.0)
+      react-router-config: 5.1.1(react-router@5.3.4(react@18.2.0))(react@18.2.0)
+      react-router-dom: 5.3.4(react@18.2.0)
       rtl-detect: 1.0.4
-      semver: 7.3.8
+      semver: 7.6.0
       serve-handler: 6.1.5
       shelljs: 0.8.5
-      terser-webpack-plugin: 5.3.6(webpack@5.75.0)
-      tslib: 2.4.1
-      update-notifier: 5.1.0
-      url-loader: 4.1.1(file-loader@6.2.0)(webpack@5.75.0)
-      wait-on: 6.0.1
-      webpack: 5.75.0
-      webpack-bundle-analyzer: 4.7.0
-      webpack-dev-server: 4.11.1(webpack@5.75.0)
-      webpack-merge: 5.8.0
-      webpackbar: 5.0.2(webpack@5.75.0)
+      terser-webpack-plugin: 5.3.10(webpack@5.90.1)
+      tslib: 2.6.2
+      update-notifier: 6.0.2
+      url-loader: 4.1.1(file-loader@6.2.0(webpack@5.90.1))(webpack@5.90.1)
+      webpack: 5.90.1
+      webpack-bundle-analyzer: 4.10.1
+      webpack-dev-server: 4.15.1(webpack@5.90.1)
+      webpack-merge: 5.10.0
+      webpackbar: 5.0.2(webpack@5.90.1)
     transitivePeerDependencies:
       - '@docusaurus/types'
       - '@parcel/css'
+      - '@rspack/core'
       - '@swc/core'
       - '@swc/css'
       - bufferutil
@@ -1706,110 +6167,98 @@ packages:
       - utf-8-validate
       - vue-template-compiler
       - webpack-cli
-    dev: false
 
-  /@docusaurus/cssnano-preset@2.4.3:
-    resolution: {integrity: sha512-ZvGSRCi7z9wLnZrXNPG6DmVPHdKGd8dIn9pYbEOFiYihfv4uDR3UtxogmKf+rT8ZlKFf5Lqne8E8nt08zNM8CA==}
-    engines: {node: '>=16.14'}
+  '@docusaurus/cssnano-preset@3.4.0':
     dependencies:
-      cssnano-preset-advanced: 5.3.9(postcss@8.4.21)
-      postcss: 8.4.21
-      postcss-sort-media-queries: 4.3.0(postcss@8.4.21)
-      tslib: 2.4.1
-    dev: false
+      cssnano-preset-advanced: 6.1.2(postcss@8.4.45)
+      postcss: 8.4.45
+      postcss-sort-media-queries: 5.2.0(postcss@8.4.45)
+      tslib: 2.6.2
 
-  /@docusaurus/logger@2.4.3:
-    resolution: {integrity: sha512-Zxws7r3yLufk9xM1zq9ged0YHs65mlRmtsobnFkdZTxWXdTYlWWLWdKyNKAsVC+D7zg+pv2fGbyabdOnyZOM3w==}
-    engines: {node: '>=16.14'}
+  '@docusaurus/logger@3.4.0':
     dependencies:
       chalk: 4.1.2
-      tslib: 2.4.1
-    dev: false
+      tslib: 2.6.2
 
-  /@docusaurus/mdx-loader@2.4.3(@docusaurus/types@2.4.3)(react-dom@17.0.2)(react@17.0.2):
-    resolution: {integrity: sha512-b1+fDnWtl3GiqkL0BRjYtc94FZrcDDBV1j8446+4tptB9BAOlePwG2p/pK6vGvfL53lkOsszXMghr2g67M0vCw==}
-    engines: {node: '>=16.14'}
-    peerDependencies:
-      react: ^16.8.4 || ^17.0.0
-      react-dom: ^16.8.4 || ^17.0.0
+  '@docusaurus/mdx-loader@3.4.0(@docusaurus/types@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.3.3)':
     dependencies:
-      '@babel/parser': 7.20.7
-      '@babel/traverse': 7.20.12
-      '@docusaurus/logger': 2.4.3
-      '@docusaurus/utils': 2.4.3(@docusaurus/types@2.4.3)
-      '@mdx-js/mdx': 1.6.22
+      '@docusaurus/logger': 3.4.0
+      '@docusaurus/utils': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(typescript@5.3.3)
+      '@docusaurus/utils-validation': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(typescript@5.3.3)
+      '@mdx-js/mdx': 3.0.1
+      '@slorber/remark-comment': 1.0.0
       escape-html: 1.0.3
-      file-loader: 6.2.0(webpack@5.75.0)
-      fs-extra: 10.1.0
+      estree-util-value-to-estree: 3.0.1
+      file-loader: 6.2.0(webpack@5.90.1)
+      fs-extra: 11.2.0
       image-size: 1.0.2
-      mdast-util-to-string: 2.0.0
-      react: 17.0.2
-      react-dom: 17.0.2(react@17.0.2)
-      remark-emoji: 2.2.0
+      mdast-util-mdx: 3.0.0
+      mdast-util-to-string: 4.0.0
+      react: 18.2.0
+      react-dom: 18.2.0(react@18.2.0)
+      rehype-raw: 7.0.0
+      remark-directive: 3.0.0
+      remark-emoji: 4.0.1
+      remark-frontmatter: 5.0.0
+      remark-gfm: 4.0.0
       stringify-object: 3.3.0
-      tslib: 2.4.1
-      unified: 9.2.2
-      unist-util-visit: 2.0.3
-      url-loader: 4.1.1(file-loader@6.2.0)(webpack@5.75.0)
-      webpack: 5.75.0
+      tslib: 2.6.2
+      unified: 11.0.4
+      unist-util-visit: 5.0.0
+      url-loader: 4.1.1(file-loader@6.2.0(webpack@5.90.1))(webpack@5.90.1)
+      vfile: 6.0.1
+      webpack: 5.90.1
     transitivePeerDependencies:
       - '@docusaurus/types'
       - '@swc/core'
       - esbuild
       - supports-color
+      - typescript
       - uglify-js
       - webpack-cli
-    dev: false
 
-  /@docusaurus/module-type-aliases@2.4.3(react-dom@17.0.2)(react@17.0.2):
-    resolution: {integrity: sha512-cwkBkt1UCiduuvEAo7XZY01dJfRn7UR/75mBgOdb1hKknhrabJZ8YH+7savd/y9kLExPyrhe0QwdS9GuzsRRIA==}
-    peerDependencies:
-      react: '*'
-      react-dom: '*'
+  '@docusaurus/module-type-aliases@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
     dependencies:
-      '@docusaurus/react-loadable': 5.5.2(react@17.0.2)
-      '@docusaurus/types': 2.4.3(react-dom@17.0.2)(react@17.0.2)
+      '@docusaurus/types': 3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
       '@types/history': 4.7.11
       '@types/react': 18.0.26
-      '@types/react-router-config': 5.0.6
+      '@types/react-router-config': 5.0.11
       '@types/react-router-dom': 5.3.3
-      react: 17.0.2
-      react-dom: 17.0.2(react@17.0.2)
-      react-helmet-async: 1.3.0(react-dom@17.0.2)(react@17.0.2)
-      react-loadable: /@docusaurus/react-loadable@5.5.2(react@17.0.2)
+      react: 18.2.0
+      react-dom: 18.2.0(react@18.2.0)
+      react-helmet-async: 1.3.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      react-loadable: '@docusaurus/react-loadable@6.0.0(react@18.2.0)'
     transitivePeerDependencies:
       - '@swc/core'
       - esbuild
+      - supports-color
       - uglify-js
       - webpack-cli
 
-  /@docusaurus/plugin-content-blog@2.4.3(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.4):
-    resolution: {integrity: sha512-PVhypqaA0t98zVDpOeTqWUTvRqCEjJubtfFUQ7zJNYdbYTbS/E/ytq6zbLVsN/dImvemtO/5JQgjLxsh8XLo8Q==}
-    engines: {node: '>=16.14'}
-    peerDependencies:
-      react: ^16.8.4 || ^17.0.0
-      react-dom: ^16.8.4 || ^17.0.0
+  '@docusaurus/plugin-content-blog@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.3.3)':
     dependencies:
-      '@docusaurus/core': 2.4.3(@docusaurus/types@2.4.3)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.4)
-      '@docusaurus/logger': 2.4.3
-      '@docusaurus/mdx-loader': 2.4.3(@docusaurus/types@2.4.3)(react-dom@17.0.2)(react@17.0.2)
-      '@docusaurus/types': 2.4.3(react-dom@17.0.2)(react@17.0.2)
-      '@docusaurus/utils': 2.4.3(@docusaurus/types@2.4.3)
-      '@docusaurus/utils-common': 2.4.3(@docusaurus/types@2.4.3)
-      '@docusaurus/utils-validation': 2.4.3(@docusaurus/types@2.4.3)
+      '@docusaurus/core': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.3.3)
+      '@docusaurus/logger': 3.4.0
+      '@docusaurus/mdx-loader': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.3.3)
+      '@docusaurus/types': 3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@docusaurus/utils': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(typescript@5.3.3)
+      '@docusaurus/utils-common': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))
+      '@docusaurus/utils-validation': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(typescript@5.3.3)
       cheerio: 1.0.0-rc.12
       feed: 4.2.2
-      fs-extra: 10.1.0
+      fs-extra: 11.2.0
       lodash: 4.17.21
-      react: 17.0.2
-      react-dom: 17.0.2(react@17.0.2)
+      react: 18.2.0
+      react-dom: 18.2.0(react@18.2.0)
       reading-time: 1.5.0
-      tslib: 2.4.1
-      unist-util-visit: 2.0.3
+      srcset: 4.0.0
+      tslib: 2.6.2
+      unist-util-visit: 5.0.0
       utility-types: 3.10.0
-      webpack: 5.75.0
+      webpack: 5.90.1
     transitivePeerDependencies:
       - '@parcel/css'
+      - '@rspack/core'
       - '@swc/core'
       - '@swc/css'
       - bufferutil
@@ -1824,35 +6273,30 @@ packages:
       - utf-8-validate
       - vue-template-compiler
       - webpack-cli
-    dev: false
-
-  /@docusaurus/plugin-content-docs@2.4.3(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.4):
-    resolution: {integrity: sha512-N7Po2LSH6UejQhzTCsvuX5NOzlC+HiXOVvofnEPj0WhMu1etpLEXE6a4aTxrtg95lQ5kf0xUIdjX9sh3d3G76A==}
-    engines: {node: '>=16.14'}
-    peerDependencies:
-      react: ^16.8.4 || ^17.0.0
-      react-dom: ^16.8.4 || ^17.0.0
-    dependencies:
-      '@docusaurus/core': 2.4.3(@docusaurus/types@2.4.3)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.4)
-      '@docusaurus/logger': 2.4.3
-      '@docusaurus/mdx-loader': 2.4.3(@docusaurus/types@2.4.3)(react-dom@17.0.2)(react@17.0.2)
-      '@docusaurus/module-type-aliases': 2.4.3(react-dom@17.0.2)(react@17.0.2)
-      '@docusaurus/types': 2.4.3(react-dom@17.0.2)(react@17.0.2)
-      '@docusaurus/utils': 2.4.3(@docusaurus/types@2.4.3)
-      '@docusaurus/utils-validation': 2.4.3(@docusaurus/types@2.4.3)
-      '@types/react-router-config': 5.0.6
+
+  '@docusaurus/plugin-content-docs@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.3.3)':
+    dependencies:
+      '@docusaurus/core': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.3.3)
+      '@docusaurus/logger': 3.4.0
+      '@docusaurus/mdx-loader': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.3.3)
+      '@docusaurus/module-type-aliases': 3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@docusaurus/types': 3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@docusaurus/utils': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(typescript@5.3.3)
+      '@docusaurus/utils-common': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))
+      '@docusaurus/utils-validation': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(typescript@5.3.3)
+      '@types/react-router-config': 5.0.11
       combine-promises: 1.1.0
-      fs-extra: 10.1.0
-      import-fresh: 3.3.0
+      fs-extra: 11.2.0
       js-yaml: 4.1.0
       lodash: 4.17.21
-      react: 17.0.2
-      react-dom: 17.0.2(react@17.0.2)
-      tslib: 2.4.1
+      react: 18.2.0
+      react-dom: 18.2.0(react@18.2.0)
+      tslib: 2.6.2
       utility-types: 3.10.0
-      webpack: 5.75.0
+      webpack: 5.90.1
     transitivePeerDependencies:
       - '@parcel/css'
+      - '@rspack/core'
       - '@swc/core'
       - '@swc/css'
       - bufferutil
@@ -1867,27 +6311,22 @@ packages:
       - utf-8-validate
       - vue-template-compiler
       - webpack-cli
-    dev: false
-
-  /@docusaurus/plugin-content-pages@2.4.3(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.4):
-    resolution: {integrity: sha512-txtDVz7y3zGk67q0HjG0gRttVPodkHqE0bpJ+7dOaTH40CQFLSh7+aBeGnPOTl+oCPG+hxkim4SndqPqXjQ8Bg==}
-    engines: {node: '>=16.14'}
-    peerDependencies:
-      react: ^16.8.4 || ^17.0.0
-      react-dom: ^16.8.4 || ^17.0.0
-    dependencies:
-      '@docusaurus/core': 2.4.3(@docusaurus/types@2.4.3)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.4)
-      '@docusaurus/mdx-loader': 2.4.3(@docusaurus/types@2.4.3)(react-dom@17.0.2)(react@17.0.2)
-      '@docusaurus/types': 2.4.3(react-dom@17.0.2)(react@17.0.2)
-      '@docusaurus/utils': 2.4.3(@docusaurus/types@2.4.3)
-      '@docusaurus/utils-validation': 2.4.3(@docusaurus/types@2.4.3)
-      fs-extra: 10.1.0
-      react: 17.0.2
-      react-dom: 17.0.2(react@17.0.2)
-      tslib: 2.4.1
-      webpack: 5.75.0
+
+  '@docusaurus/plugin-content-pages@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.3.3)':
+    dependencies:
+      '@docusaurus/core': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.3.3)
+      '@docusaurus/mdx-loader': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.3.3)
+      '@docusaurus/types': 3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@docusaurus/utils': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(typescript@5.3.3)
+      '@docusaurus/utils-validation': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(typescript@5.3.3)
+      fs-extra: 11.2.0
+      react: 18.2.0
+      react-dom: 18.2.0(react@18.2.0)
+      tslib: 2.6.2
+      webpack: 5.90.1
     transitivePeerDependencies:
       - '@parcel/css'
+      - '@rspack/core'
       - '@swc/core'
       - '@swc/css'
       - bufferutil
@@ -1902,32 +6341,25 @@ packages:
       - utf-8-validate
       - vue-template-compiler
       - webpack-cli
-    dev: false
-
-  /@docusaurus/plugin-debug@2.4.3(@types/react@18.0.26)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.4):
-    resolution: {integrity: sha512-LkUbuq3zCmINlFb+gAd4ZvYr+bPAzMC0hwND4F7V9bZ852dCX8YoWyovVUBKq4er1XsOwSQaHmNGtObtn8Av8Q==}
-    engines: {node: '>=16.14'}
-    peerDependencies:
-      react: ^16.8.4 || ^17.0.0
-      react-dom: ^16.8.4 || ^17.0.0
-    dependencies:
-      '@docusaurus/core': 2.4.3(@docusaurus/types@2.4.3)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.4)
-      '@docusaurus/types': 2.4.3(react-dom@17.0.2)(react@17.0.2)
-      '@docusaurus/utils': 2.4.3(@docusaurus/types@2.4.3)
-      fs-extra: 10.1.0
-      react: 17.0.2
-      react-dom: 17.0.2(react@17.0.2)
-      react-json-view: 1.21.3(@types/react@18.0.26)(react-dom@17.0.2)(react@17.0.2)
-      tslib: 2.4.1
+
+  '@docusaurus/plugin-debug@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.3.3)':
+    dependencies:
+      '@docusaurus/core': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.3.3)
+      '@docusaurus/types': 3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@docusaurus/utils': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(typescript@5.3.3)
+      fs-extra: 11.2.0
+      react: 18.2.0
+      react-dom: 18.2.0(react@18.2.0)
+      react-json-view-lite: 1.2.1(react@18.2.0)
+      tslib: 2.6.2
     transitivePeerDependencies:
       - '@parcel/css'
+      - '@rspack/core'
       - '@swc/core'
       - '@swc/css'
-      - '@types/react'
       - bufferutil
       - csso
       - debug
-      - encoding
       - esbuild
       - eslint
       - lightningcss
@@ -1937,23 +6369,18 @@ packages:
       - utf-8-validate
       - vue-template-compiler
       - webpack-cli
-    dev: false
 
-  /@docusaurus/plugin-google-analytics@2.4.3(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.4):
-    resolution: {integrity: sha512-KzBV3k8lDkWOhg/oYGxlK5o9bOwX7KpPc/FTWoB+SfKhlHfhq7qcQdMi1elAaVEIop8tgK6gD1E58Q+XC6otSQ==}
-    engines: {node: '>=16.14'}
-    peerDependencies:
-      react: ^16.8.4 || ^17.0.0
-      react-dom: ^16.8.4 || ^17.0.0
+  '@docusaurus/plugin-google-analytics@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.3.3)':
     dependencies:
-      '@docusaurus/core': 2.4.3(@docusaurus/types@2.4.3)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.4)
-      '@docusaurus/types': 2.4.3(react-dom@17.0.2)(react@17.0.2)
-      '@docusaurus/utils-validation': 2.4.3(@docusaurus/types@2.4.3)
-      react: 17.0.2
-      react-dom: 17.0.2(react@17.0.2)
-      tslib: 2.4.1
+      '@docusaurus/core': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.3.3)
+      '@docusaurus/types': 3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@docusaurus/utils-validation': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(typescript@5.3.3)
+      react: 18.2.0
+      react-dom: 18.2.0(react@18.2.0)
+      tslib: 2.6.2
     transitivePeerDependencies:
       - '@parcel/css'
+      - '@rspack/core'
       - '@swc/core'
       - '@swc/css'
       - bufferutil
@@ -1968,23 +6395,19 @@ packages:
       - utf-8-validate
       - vue-template-compiler
       - webpack-cli
-    dev: false
 
-  /@docusaurus/plugin-google-gtag@2.4.3(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.4):
-    resolution: {integrity: sha512-5FMg0rT7sDy4i9AGsvJC71MQrqQZwgLNdDetLEGDHLfSHLvJhQbTCUGbGXknUgWXQJckcV/AILYeJy+HhxeIFA==}
-    engines: {node: '>=16.14'}
-    peerDependencies:
-      react: ^16.8.4 || ^17.0.0
-      react-dom: ^16.8.4 || ^17.0.0
+  '@docusaurus/plugin-google-gtag@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.3.3)':
     dependencies:
-      '@docusaurus/core': 2.4.3(@docusaurus/types@2.4.3)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.4)
-      '@docusaurus/types': 2.4.3(react-dom@17.0.2)(react@17.0.2)
-      '@docusaurus/utils-validation': 2.4.3(@docusaurus/types@2.4.3)
-      react: 17.0.2
-      react-dom: 17.0.2(react@17.0.2)
-      tslib: 2.4.1
+      '@docusaurus/core': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.3.3)
+      '@docusaurus/types': 3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@docusaurus/utils-validation': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(typescript@5.3.3)
+      '@types/gtag.js': 0.0.12
+      react: 18.2.0
+      react-dom: 18.2.0(react@18.2.0)
+      tslib: 2.6.2
     transitivePeerDependencies:
       - '@parcel/css'
+      - '@rspack/core'
       - '@swc/core'
       - '@swc/css'
       - bufferutil
@@ -1999,23 +6422,18 @@ packages:
       - utf-8-validate
       - vue-template-compiler
       - webpack-cli
-    dev: false
 
-  /@docusaurus/plugin-google-tag-manager@2.4.3(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.4):
-    resolution: {integrity: sha512-1jTzp71yDGuQiX9Bi0pVp3alArV0LSnHXempvQTxwCGAEzUWWaBg4d8pocAlTpbP9aULQQqhgzrs8hgTRPOM0A==}
-    engines: {node: '>=16.14'}
-    peerDependencies:
-      react: ^16.8.4 || ^17.0.0
-      react-dom: ^16.8.4 || ^17.0.0
+  '@docusaurus/plugin-google-tag-manager@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.3.3)':
     dependencies:
-      '@docusaurus/core': 2.4.3(@docusaurus/types@2.4.3)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.4)
-      '@docusaurus/types': 2.4.3(react-dom@17.0.2)(react@17.0.2)
-      '@docusaurus/utils-validation': 2.4.3(@docusaurus/types@2.4.3)
-      react: 17.0.2
-      react-dom: 17.0.2(react@17.0.2)
-      tslib: 2.4.1
+      '@docusaurus/core': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.3.3)
+      '@docusaurus/types': 3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@docusaurus/utils-validation': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(typescript@5.3.3)
+      react: 18.2.0
+      react-dom: 18.2.0(react@18.2.0)
+      tslib: 2.6.2
     transitivePeerDependencies:
       - '@parcel/css'
+      - '@rspack/core'
       - '@swc/core'
       - '@swc/css'
       - bufferutil
@@ -2030,28 +6448,23 @@ packages:
       - utf-8-validate
       - vue-template-compiler
       - webpack-cli
-    dev: false
-
-  /@docusaurus/plugin-sitemap@2.4.3(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.4):
-    resolution: {integrity: sha512-LRQYrK1oH1rNfr4YvWBmRzTL0LN9UAPxBbghgeFRBm5yloF6P+zv1tm2pe2hQTX/QP5bSKdnajCvfnScgKXMZQ==}
-    engines: {node: '>=16.14'}
-    peerDependencies:
-      react: ^16.8.4 || ^17.0.0
-      react-dom: ^16.8.4 || ^17.0.0
-    dependencies:
-      '@docusaurus/core': 2.4.3(@docusaurus/types@2.4.3)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.4)
-      '@docusaurus/logger': 2.4.3
-      '@docusaurus/types': 2.4.3(react-dom@17.0.2)(react@17.0.2)
-      '@docusaurus/utils': 2.4.3(@docusaurus/types@2.4.3)
-      '@docusaurus/utils-common': 2.4.3(@docusaurus/types@2.4.3)
-      '@docusaurus/utils-validation': 2.4.3(@docusaurus/types@2.4.3)
-      fs-extra: 10.1.0
-      react: 17.0.2
-      react-dom: 17.0.2(react@17.0.2)
+
+  '@docusaurus/plugin-sitemap@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.3.3)':
+    dependencies:
+      '@docusaurus/core': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.3.3)
+      '@docusaurus/logger': 3.4.0
+      '@docusaurus/types': 3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@docusaurus/utils': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(typescript@5.3.3)
+      '@docusaurus/utils-common': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))
+      '@docusaurus/utils-validation': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(typescript@5.3.3)
+      fs-extra: 11.2.0
+      react: 18.2.0
+      react-dom: 18.2.0(react@18.2.0)
       sitemap: 7.1.1
-      tslib: 2.4.1
+      tslib: 2.6.2
     transitivePeerDependencies:
       - '@parcel/css'
+      - '@rspack/core'
       - '@swc/core'
       - '@swc/css'
       - bufferutil
@@ -2066,98 +6479,85 @@ packages:
       - utf-8-validate
       - vue-template-compiler
       - webpack-cli
-    dev: false
-
-  /@docusaurus/preset-classic@2.4.3(@algolia/client-search@4.14.3)(@types/react@18.0.26)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.4):
-    resolution: {integrity: sha512-tRyMliepY11Ym6hB1rAFSNGwQDpmszvWYJvlK1E+md4SW8i6ylNHtpZjaYFff9Mdk3i/Pg8ItQq9P0daOJAvQw==}
-    engines: {node: '>=16.14'}
-    peerDependencies:
-      react: ^16.8.4 || ^17.0.0
-      react-dom: ^16.8.4 || ^17.0.0
-    dependencies:
-      '@docusaurus/core': 2.4.3(@docusaurus/types@2.4.3)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.4)
-      '@docusaurus/plugin-content-blog': 2.4.3(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.4)
-      '@docusaurus/plugin-content-docs': 2.4.3(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.4)
-      '@docusaurus/plugin-content-pages': 2.4.3(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.4)
-      '@docusaurus/plugin-debug': 2.4.3(@types/react@18.0.26)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.4)
-      '@docusaurus/plugin-google-analytics': 2.4.3(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.4)
-      '@docusaurus/plugin-google-gtag': 2.4.3(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.4)
-      '@docusaurus/plugin-google-tag-manager': 2.4.3(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.4)
-      '@docusaurus/plugin-sitemap': 2.4.3(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.4)
-      '@docusaurus/theme-classic': 2.4.3(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.4)
-      '@docusaurus/theme-common': 2.4.3(@docusaurus/types@2.4.3)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.4)
-      '@docusaurus/theme-search-algolia': 2.4.3(@algolia/client-search@4.14.3)(@docusaurus/types@2.4.3)(@types/react@18.0.26)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.4)
-      '@docusaurus/types': 2.4.3(react-dom@17.0.2)(react@17.0.2)
-      react: 17.0.2
-      react-dom: 17.0.2(react@17.0.2)
+
+  '@docusaurus/preset-classic@3.4.0(@algolia/client-search@4.22.1)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(search-insights@2.13.0)(typescript@5.3.3)':
+    dependencies:
+      '@docusaurus/core': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.3.3)
+      '@docusaurus/plugin-content-blog': 3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.3.3)
+      '@docusaurus/plugin-content-docs': 3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.3.3)
+      '@docusaurus/plugin-content-pages': 3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.3.3)
+      '@docusaurus/plugin-debug': 3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.3.3)
+      '@docusaurus/plugin-google-analytics': 3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.3.3)
+      '@docusaurus/plugin-google-gtag': 3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.3.3)
+      '@docusaurus/plugin-google-tag-manager': 3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.3.3)
+      '@docusaurus/plugin-sitemap': 3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.3.3)
+      '@docusaurus/theme-classic': 3.4.0(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.3.3)
+      '@docusaurus/theme-common': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.3.3)
+      '@docusaurus/theme-search-algolia': 3.4.0(@algolia/client-search@4.22.1)(@docusaurus/types@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(search-insights@2.13.0)(typescript@5.3.3)
+      '@docusaurus/types': 3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      react: 18.2.0
+      react-dom: 18.2.0(react@18.2.0)
     transitivePeerDependencies:
       - '@algolia/client-search'
       - '@parcel/css'
+      - '@rspack/core'
       - '@swc/core'
       - '@swc/css'
       - '@types/react'
       - bufferutil
       - csso
       - debug
-      - encoding
       - esbuild
       - eslint
       - lightningcss
+      - search-insights
       - supports-color
       - typescript
       - uglify-js
       - utf-8-validate
       - vue-template-compiler
       - webpack-cli
-    dev: false
 
-  /@docusaurus/react-loadable@5.5.2(react@17.0.2):
-    resolution: {integrity: sha512-A3dYjdBGuy0IGT+wyLIGIKLRE+sAk1iNk0f1HjNDysO7u8lhL4N3VEm+FAubmJbAztn94F7MxBTPmnixbiyFdQ==}
-    peerDependencies:
-      react: '*'
+  '@docusaurus/react-loadable@6.0.0(react@18.2.0)':
     dependencies:
       '@types/react': 18.0.26
-      prop-types: 15.8.1
-      react: 17.0.2
-
-  /@docusaurus/theme-classic@2.4.3(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.4):
-    resolution: {integrity: sha512-QKRAJPSGPfDY2yCiPMIVyr+MqwZCIV2lxNzqbyUW0YkrlmdzzP3WuQJPMGLCjWgQp/5c9kpWMvMxjhpZx1R32Q==}
-    engines: {node: '>=16.14'}
-    peerDependencies:
-      react: ^16.8.4 || ^17.0.0
-      react-dom: ^16.8.4 || ^17.0.0
-    dependencies:
-      '@docusaurus/core': 2.4.3(@docusaurus/types@2.4.3)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.4)
-      '@docusaurus/mdx-loader': 2.4.3(@docusaurus/types@2.4.3)(react-dom@17.0.2)(react@17.0.2)
-      '@docusaurus/module-type-aliases': 2.4.3(react-dom@17.0.2)(react@17.0.2)
-      '@docusaurus/plugin-content-blog': 2.4.3(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.4)
-      '@docusaurus/plugin-content-docs': 2.4.3(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.4)
-      '@docusaurus/plugin-content-pages': 2.4.3(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.4)
-      '@docusaurus/theme-common': 2.4.3(@docusaurus/types@2.4.3)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.4)
-      '@docusaurus/theme-translations': 2.4.3
-      '@docusaurus/types': 2.4.3(react-dom@17.0.2)(react@17.0.2)
-      '@docusaurus/utils': 2.4.3(@docusaurus/types@2.4.3)
-      '@docusaurus/utils-common': 2.4.3(@docusaurus/types@2.4.3)
-      '@docusaurus/utils-validation': 2.4.3(@docusaurus/types@2.4.3)
-      '@mdx-js/react': 1.6.22(react@17.0.2)
-      clsx: 1.2.1
-      copy-text-to-clipboard: 3.0.1
+      react: 18.2.0
+
+  '@docusaurus/theme-classic@3.4.0(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.3.3)':
+    dependencies:
+      '@docusaurus/core': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.3.3)
+      '@docusaurus/mdx-loader': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.3.3)
+      '@docusaurus/module-type-aliases': 3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@docusaurus/plugin-content-blog': 3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.3.3)
+      '@docusaurus/plugin-content-docs': 3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.3.3)
+      '@docusaurus/plugin-content-pages': 3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.3.3)
+      '@docusaurus/theme-common': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.3.3)
+      '@docusaurus/theme-translations': 3.4.0
+      '@docusaurus/types': 3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@docusaurus/utils': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(typescript@5.3.3)
+      '@docusaurus/utils-common': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))
+      '@docusaurus/utils-validation': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(typescript@5.3.3)
+      '@mdx-js/react': 3.0.1(@types/react@18.0.26)(react@18.2.0)
+      clsx: 2.1.0
+      copy-text-to-clipboard: 3.2.0
       infima: 0.2.0-alpha.43
       lodash: 4.17.21
       nprogress: 0.2.0
-      postcss: 8.4.21
-      prism-react-renderer: 1.3.5(react@17.0.2)
+      postcss: 8.4.45
+      prism-react-renderer: 2.3.1(react@18.2.0)
       prismjs: 1.29.0
-      react: 17.0.2
-      react-dom: 17.0.2(react@17.0.2)
-      react-router-dom: 5.3.4(react@17.0.2)
-      rtlcss: 3.5.0
-      tslib: 2.4.1
+      react: 18.2.0
+      react-dom: 18.2.0(react@18.2.0)
+      react-router-dom: 5.3.4(react@18.2.0)
+      rtlcss: 4.1.1
+      tslib: 2.6.2
       utility-types: 3.10.0
     transitivePeerDependencies:
       - '@parcel/css'
+      - '@rspack/core'
       - '@swc/core'
       - '@swc/css'
+      - '@types/react'
       - bufferutil
       - csso
       - debug
@@ -2170,36 +6570,30 @@ packages:
       - utf-8-validate
       - vue-template-compiler
       - webpack-cli
-    dev: false
-
-  /@docusaurus/theme-common@2.4.3(@docusaurus/types@2.4.3)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.4):
-    resolution: {integrity: sha512-7KaDJBXKBVGXw5WOVt84FtN8czGWhM0lbyWEZXGp8AFfL6sZQfRTluFp4QriR97qwzSyOfQb+nzcDZZU4tezUw==}
-    engines: {node: '>=16.14'}
-    peerDependencies:
-      react: ^16.8.4 || ^17.0.0
-      react-dom: ^16.8.4 || ^17.0.0
-    dependencies:
-      '@docusaurus/mdx-loader': 2.4.3(@docusaurus/types@2.4.3)(react-dom@17.0.2)(react@17.0.2)
-      '@docusaurus/module-type-aliases': 2.4.3(react-dom@17.0.2)(react@17.0.2)
-      '@docusaurus/plugin-content-blog': 2.4.3(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.4)
-      '@docusaurus/plugin-content-docs': 2.4.3(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.4)
-      '@docusaurus/plugin-content-pages': 2.4.3(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.4)
-      '@docusaurus/utils': 2.4.3(@docusaurus/types@2.4.3)
-      '@docusaurus/utils-common': 2.4.3(@docusaurus/types@2.4.3)
+
+  '@docusaurus/theme-common@3.4.0(@docusaurus/types@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.3.3)':
+    dependencies:
+      '@docusaurus/mdx-loader': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.3.3)
+      '@docusaurus/module-type-aliases': 3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@docusaurus/plugin-content-blog': 3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.3.3)
+      '@docusaurus/plugin-content-docs': 3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.3.3)
+      '@docusaurus/plugin-content-pages': 3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.3.3)
+      '@docusaurus/utils': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(typescript@5.3.3)
+      '@docusaurus/utils-common': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))
       '@types/history': 4.7.11
       '@types/react': 18.0.26
-      '@types/react-router-config': 5.0.6
-      clsx: 1.2.1
+      '@types/react-router-config': 5.0.11
+      clsx: 2.1.0
       parse-numeric-range: 1.3.0
-      prism-react-renderer: 1.3.5(react@17.0.2)
-      react: 17.0.2
-      react-dom: 17.0.2(react@17.0.2)
-      tslib: 2.4.1
-      use-sync-external-store: 1.2.0(react@17.0.2)
+      prism-react-renderer: 2.3.1(react@18.2.0)
+      react: 18.2.0
+      react-dom: 18.2.0(react@18.2.0)
+      tslib: 2.6.2
       utility-types: 3.10.0
     transitivePeerDependencies:
       - '@docusaurus/types'
       - '@parcel/css'
+      - '@rspack/core'
       - '@swc/core'
       - '@swc/css'
       - bufferutil
@@ -2214,27 +6608,21 @@ packages:
       - utf-8-validate
       - vue-template-compiler
       - webpack-cli
-    dev: false
-
-  /@docusaurus/theme-mermaid@2.4.3(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.4):
-    resolution: {integrity: sha512-S1tZ3xpowtFiTrpTKmvVbRHUYGOlEG5CnPzWlO4huJT1sAwLR+pD6f9DYUlPv2+9NezF3EfUrUyW9xLH0UP58w==}
-    engines: {node: '>=16.14'}
-    peerDependencies:
-      react: ^16.8.4 || ^17.0.0
-      react-dom: ^16.8.4 || ^17.0.0
-    dependencies:
-      '@docusaurus/core': 2.4.3(@docusaurus/types@2.4.3)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.4)
-      '@docusaurus/module-type-aliases': 2.4.3(react-dom@17.0.2)(react@17.0.2)
-      '@docusaurus/theme-common': 2.4.3(@docusaurus/types@2.4.3)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.4)
-      '@docusaurus/types': 2.4.3(react-dom@17.0.2)(react@17.0.2)
-      '@docusaurus/utils-validation': 2.4.3(@docusaurus/types@2.4.3)
-      '@mdx-js/react': 1.6.22(react@17.0.2)
-      mermaid: 9.4.3
-      react: 17.0.2
-      react-dom: 17.0.2(react@17.0.2)
-      tslib: 2.4.1
+
+  '@docusaurus/theme-mermaid@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.3.3)':
+    dependencies:
+      '@docusaurus/core': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.3.3)
+      '@docusaurus/module-type-aliases': 3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@docusaurus/theme-common': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.3.3)
+      '@docusaurus/types': 3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@docusaurus/utils-validation': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(typescript@5.3.3)
+      mermaid: 10.8.0
+      react: 18.2.0
+      react-dom: 18.2.0(react@18.2.0)
+      tslib: 2.6.2
     transitivePeerDependencies:
       - '@parcel/css'
+      - '@rspack/core'
       - '@swc/core'
       - '@swc/css'
       - bufferutil
@@ -2249,37 +6637,32 @@ packages:
       - utf-8-validate
       - vue-template-compiler
       - webpack-cli
-    dev: false
-
-  /@docusaurus/theme-search-algolia@2.4.3(@algolia/client-search@4.14.3)(@docusaurus/types@2.4.3)(@types/react@18.0.26)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.4):
-    resolution: {integrity: sha512-jziq4f6YVUB5hZOB85ELATwnxBz/RmSLD3ksGQOLDPKVzat4pmI8tddNWtriPpxR04BNT+ZfpPUMFkNFetSW1Q==}
-    engines: {node: '>=16.14'}
-    peerDependencies:
-      react: ^16.8.4 || ^17.0.0
-      react-dom: ^16.8.4 || ^17.0.0
-    dependencies:
-      '@docsearch/react': 3.3.1(@algolia/client-search@4.14.3)(@types/react@18.0.26)(react-dom@17.0.2)(react@17.0.2)
-      '@docusaurus/core': 2.4.3(@docusaurus/types@2.4.3)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.4)
-      '@docusaurus/logger': 2.4.3
-      '@docusaurus/plugin-content-docs': 2.4.3(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.4)
-      '@docusaurus/theme-common': 2.4.3(@docusaurus/types@2.4.3)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.4)
-      '@docusaurus/theme-translations': 2.4.3
-      '@docusaurus/utils': 2.4.3(@docusaurus/types@2.4.3)
-      '@docusaurus/utils-validation': 2.4.3(@docusaurus/types@2.4.3)
-      algoliasearch: 4.14.3
-      algoliasearch-helper: 3.11.1(algoliasearch@4.14.3)
-      clsx: 1.2.1
-      eta: 2.0.0
-      fs-extra: 10.1.0
+
+  '@docusaurus/theme-search-algolia@3.4.0(@algolia/client-search@4.22.1)(@docusaurus/types@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(search-insights@2.13.0)(typescript@5.3.3)':
+    dependencies:
+      '@docsearch/react': 3.5.2(@algolia/client-search@4.22.1)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(search-insights@2.13.0)
+      '@docusaurus/core': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.3.3)
+      '@docusaurus/logger': 3.4.0
+      '@docusaurus/plugin-content-docs': 3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.3.3)
+      '@docusaurus/theme-common': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.3.3)
+      '@docusaurus/theme-translations': 3.4.0
+      '@docusaurus/utils': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(typescript@5.3.3)
+      '@docusaurus/utils-validation': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(typescript@5.3.3)
+      algoliasearch: 4.22.1
+      algoliasearch-helper: 3.16.2(algoliasearch@4.22.1)
+      clsx: 2.1.0
+      eta: 2.2.0
+      fs-extra: 11.2.0
       lodash: 4.17.21
-      react: 17.0.2
-      react-dom: 17.0.2(react@17.0.2)
-      tslib: 2.4.1
+      react: 18.2.0
+      react-dom: 18.2.0(react@18.2.0)
+      tslib: 2.6.2
       utility-types: 3.10.0
     transitivePeerDependencies:
       - '@algolia/client-search'
       - '@docusaurus/types'
       - '@parcel/css'
+      - '@rspack/core'
       - '@swc/core'
       - '@swc/css'
       - '@types/react'
@@ -2289,1102 +6672,814 @@ packages:
       - esbuild
       - eslint
       - lightningcss
+      - search-insights
       - supports-color
       - typescript
       - uglify-js
       - utf-8-validate
       - vue-template-compiler
       - webpack-cli
-    dev: false
 
-  /@docusaurus/theme-translations@2.4.3:
-    resolution: {integrity: sha512-H4D+lbZbjbKNS/Zw1Lel64PioUAIT3cLYYJLUf3KkuO/oc9e0QCVhIYVtUI2SfBCF2NNdlyhBDQEEMygsCedIg==}
-    engines: {node: '>=16.14'}
+  '@docusaurus/theme-translations@3.4.0':
     dependencies:
-      fs-extra: 10.1.0
-      tslib: 2.4.1
-    dev: false
+      fs-extra: 11.2.0
+      tslib: 2.6.2
 
-  /@docusaurus/types@2.4.3(react-dom@17.0.2)(react@17.0.2):
-    resolution: {integrity: sha512-W6zNLGQqfrp/EoPD0bhb9n7OobP+RHpmvVzpA+Z/IuU3Q63njJM24hmT0GYboovWcDtFmnIJC9wcyx4RVPQscw==}
-    peerDependencies:
-      react: ^16.8.4 || ^17.0.0
-      react-dom: ^16.8.4 || ^17.0.0
+  '@docusaurus/types@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
     dependencies:
+      '@mdx-js/mdx': 3.0.1
       '@types/history': 4.7.11
       '@types/react': 18.0.26
       commander: 5.1.0
-      joi: 17.7.0
-      react: 17.0.2
-      react-dom: 17.0.2(react@17.0.2)
-      react-helmet-async: 1.3.0(react-dom@17.0.2)(react@17.0.2)
+      joi: 17.12.1
+      react: 18.2.0
+      react-dom: 18.2.0(react@18.2.0)
+      react-helmet-async: 1.3.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
       utility-types: 3.10.0
-      webpack: 5.75.0
-      webpack-merge: 5.8.0
+      webpack: 5.90.1
+      webpack-merge: 5.10.0
     transitivePeerDependencies:
       - '@swc/core'
       - esbuild
+      - supports-color
       - uglify-js
       - webpack-cli
 
-  /@docusaurus/utils-common@2.4.3(@docusaurus/types@2.4.3):
-    resolution: {integrity: sha512-/jascp4GbLQCPVmcGkPzEQjNaAk3ADVfMtudk49Ggb+131B1WDD6HqlSmDf8MxGdy7Dja2gc+StHf01kiWoTDQ==}
-    engines: {node: '>=16.14'}
-    peerDependencies:
-      '@docusaurus/types': '*'
-    peerDependenciesMeta:
-      '@docusaurus/types':
-        optional: true
+  '@docusaurus/utils-common@3.4.0(@docusaurus/types@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))':
     dependencies:
-      '@docusaurus/types': 2.4.3(react-dom@17.0.2)(react@17.0.2)
-      tslib: 2.4.1
-    dev: false
+      tslib: 2.6.2
+    optionalDependencies:
+      '@docusaurus/types': 3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
 
-  /@docusaurus/utils-validation@2.4.3(@docusaurus/types@2.4.3):
-    resolution: {integrity: sha512-G2+Vt3WR5E/9drAobP+hhZQMaswRwDlp6qOMi7o7ZypB+VO7N//DZWhZEwhcRGepMDJGQEwtPv7UxtYwPL9PBw==}
-    engines: {node: '>=16.14'}
+  '@docusaurus/utils-validation@3.4.0(@docusaurus/types@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(typescript@5.3.3)':
     dependencies:
-      '@docusaurus/logger': 2.4.3
-      '@docusaurus/utils': 2.4.3(@docusaurus/types@2.4.3)
-      joi: 17.7.0
+      '@docusaurus/logger': 3.4.0
+      '@docusaurus/utils': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(typescript@5.3.3)
+      '@docusaurus/utils-common': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))
+      fs-extra: 11.2.0
+      joi: 17.12.1
       js-yaml: 4.1.0
-      tslib: 2.4.1
+      lodash: 4.17.21
+      tslib: 2.6.2
     transitivePeerDependencies:
       - '@docusaurus/types'
       - '@swc/core'
       - esbuild
       - supports-color
+      - typescript
       - uglify-js
       - webpack-cli
-    dev: false
 
-  /@docusaurus/utils@2.4.3(@docusaurus/types@2.4.3):
-    resolution: {integrity: sha512-fKcXsjrD86Smxv8Pt0TBFqYieZZCPh4cbf9oszUq/AMhZn3ujwpKaVYZACPX8mmjtYx0JOgNx52CREBfiGQB4A==}
-    engines: {node: '>=16.14'}
-    peerDependencies:
-      '@docusaurus/types': '*'
-    peerDependenciesMeta:
-      '@docusaurus/types':
-        optional: true
+  '@docusaurus/utils@3.4.0(@docusaurus/types@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(typescript@5.3.3)':
     dependencies:
-      '@docusaurus/logger': 2.4.3
-      '@docusaurus/types': 2.4.3(react-dom@17.0.2)(react@17.0.2)
-      '@svgr/webpack': 6.5.1
+      '@docusaurus/logger': 3.4.0
+      '@docusaurus/utils-common': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))
+      '@svgr/webpack': 8.1.0(typescript@5.3.3)
       escape-string-regexp: 4.0.0
-      file-loader: 6.2.0(webpack@5.75.0)
-      fs-extra: 10.1.0
+      file-loader: 6.2.0(webpack@5.90.1)
+      fs-extra: 11.2.0
       github-slugger: 1.5.0
       globby: 11.1.0
       gray-matter: 4.0.3
+      jiti: 1.21.6
       js-yaml: 4.1.0
       lodash: 4.17.21
-      micromatch: 4.0.5
+      micromatch: 4.0.8
+      prompts: 2.4.2
       resolve-pathname: 3.0.0
       shelljs: 0.8.5
-      tslib: 2.4.1
-      url-loader: 4.1.1(file-loader@6.2.0)(webpack@5.75.0)
-      webpack: 5.75.0
+      tslib: 2.6.2
+      url-loader: 4.1.1(file-loader@6.2.0(webpack@5.90.1))(webpack@5.90.1)
+      utility-types: 3.10.0
+      webpack: 5.90.1
+    optionalDependencies:
+      '@docusaurus/types': 3.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
     transitivePeerDependencies:
       - '@swc/core'
       - esbuild
       - supports-color
+      - typescript
       - uglify-js
       - webpack-cli
-    dev: false
 
-  /@hapi/hoek@9.3.0:
-    resolution: {integrity: sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==}
+  '@giscus/react@2.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
+    dependencies:
+      giscus: 1.5.0
+      react: 18.2.0
+      react-dom: 18.2.0(react@18.2.0)
 
-  /@hapi/topo@5.1.0:
-    resolution: {integrity: sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==}
+  '@hapi/hoek@9.3.0': {}
+
+  '@hapi/topo@5.1.0':
     dependencies:
       '@hapi/hoek': 9.3.0
 
-  /@jest/schemas@29.0.0:
-    resolution: {integrity: sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==}
-    engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+  '@isaacs/cliui@8.0.2':
     dependencies:
-      '@sinclair/typebox': 0.24.51
-    dev: false
+      string-width: 5.1.2
+      string-width-cjs: string-width@4.2.3
+      strip-ansi: 7.1.0
+      strip-ansi-cjs: strip-ansi@6.0.1
+      wrap-ansi: 8.1.0
+      wrap-ansi-cjs: wrap-ansi@7.0.0
 
-  /@jest/types@29.3.1:
-    resolution: {integrity: sha512-d0S0jmmTpjnhCmNpApgX3jrUZgZ22ivKJRvL2lli5hpCRoNnp1f85r2/wpKfXuYu8E7Jjh1hGfhPyup1NM5AmA==}
-    engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+  '@jest/schemas@29.6.3':
+    dependencies:
+      '@sinclair/typebox': 0.27.8
+
+  '@jest/types@29.6.3':
     dependencies:
-      '@jest/schemas': 29.0.0
+      '@jest/schemas': 29.6.3
       '@types/istanbul-lib-coverage': 2.0.4
       '@types/istanbul-reports': 3.0.1
-      '@types/node': 18.11.18
+      '@types/node': 20.11.17
       '@types/yargs': 17.0.19
       chalk: 4.1.2
-    dev: false
 
-  /@jridgewell/gen-mapping@0.1.1:
-    resolution: {integrity: sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==}
-    engines: {node: '>=6.0.0'}
+  '@jridgewell/gen-mapping@0.1.1':
     dependencies:
-      '@jridgewell/set-array': 1.1.2
-      '@jridgewell/sourcemap-codec': 1.4.14
-    dev: false
+      '@jridgewell/set-array': 1.2.1
+      '@jridgewell/sourcemap-codec': 1.5.0
 
-  /@jridgewell/gen-mapping@0.3.2:
-    resolution: {integrity: sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==}
-    engines: {node: '>=6.0.0'}
+  '@jridgewell/gen-mapping@0.3.5':
     dependencies:
-      '@jridgewell/set-array': 1.1.2
-      '@jridgewell/sourcemap-codec': 1.4.14
-      '@jridgewell/trace-mapping': 0.3.17
+      '@jridgewell/set-array': 1.2.1
+      '@jridgewell/sourcemap-codec': 1.5.0
+      '@jridgewell/trace-mapping': 0.3.25
 
-  /@jridgewell/resolve-uri@3.1.0:
-    resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==}
-    engines: {node: '>=6.0.0'}
+  '@jridgewell/resolve-uri@3.1.2': {}
 
-  /@jridgewell/set-array@1.1.2:
-    resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==}
-    engines: {node: '>=6.0.0'}
+  '@jridgewell/set-array@1.2.1': {}
 
-  /@jridgewell/source-map@0.3.2:
-    resolution: {integrity: sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==}
+  '@jridgewell/source-map@0.3.5':
     dependencies:
-      '@jridgewell/gen-mapping': 0.3.2
-      '@jridgewell/trace-mapping': 0.3.17
+      '@jridgewell/gen-mapping': 0.3.5
+      '@jridgewell/trace-mapping': 0.3.25
 
-  /@jridgewell/sourcemap-codec@1.4.14:
-    resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==}
+  '@jridgewell/sourcemap-codec@1.5.0': {}
 
-  /@jridgewell/trace-mapping@0.3.17:
-    resolution: {integrity: sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==}
+  '@jridgewell/trace-mapping@0.3.25':
     dependencies:
-      '@jridgewell/resolve-uri': 3.1.0
-      '@jridgewell/sourcemap-codec': 1.4.14
+      '@jridgewell/resolve-uri': 3.1.2
+      '@jridgewell/sourcemap-codec': 1.5.0
 
-  /@leichtgewicht/ip-codec@2.0.4:
-    resolution: {integrity: sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==}
-    dev: false
+  '@leichtgewicht/ip-codec@2.0.4': {}
+
+  '@lit-labs/ssr-dom-shim@1.2.0': {}
 
-  /@mdx-js/mdx@1.6.22:
-    resolution: {integrity: sha512-AMxuLxPz2j5/6TpF/XSdKpQP1NlG0z11dFOlq+2IP/lSgl11GY8ji6S/rgsViN/L0BDvHvUMruRb7ub+24LUYA==}
+  '@lit/reactive-element@2.0.4':
     dependencies:
-      '@babel/core': 7.12.9
-      '@babel/plugin-syntax-jsx': 7.12.1(@babel/core@7.12.9)
-      '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.12.9)
-      '@mdx-js/util': 1.6.22
-      babel-plugin-apply-mdx-type-prop: 1.6.22(@babel/core@7.12.9)
-      babel-plugin-extract-import-names: 1.6.22
-      camelcase-css: 2.0.1
-      detab: 2.0.4
-      hast-util-raw: 6.0.1
-      lodash.uniq: 4.5.0
-      mdast-util-to-hast: 10.0.1
-      remark-footnotes: 2.0.0
-      remark-mdx: 1.6.22
-      remark-parse: 8.0.3
-      remark-squeeze-paragraphs: 4.0.0
-      style-to-object: 0.3.0
-      unified: 9.2.0
-      unist-builder: 2.0.3
-      unist-util-visit: 2.0.3
+      '@lit-labs/ssr-dom-shim': 1.2.0
+
+  '@mdx-js/mdx@3.0.1':
+    dependencies:
+      '@types/estree': 1.0.5
+      '@types/estree-jsx': 1.0.4
+      '@types/hast': 3.0.4
+      '@types/mdx': 2.0.11
+      collapse-white-space: 2.1.0
+      devlop: 1.1.0
+      estree-util-build-jsx: 3.0.1
+      estree-util-is-identifier-name: 3.0.0
+      estree-util-to-js: 2.0.0
+      estree-walker: 3.0.3
+      hast-util-to-estree: 3.1.0
+      hast-util-to-jsx-runtime: 2.3.0
+      markdown-extensions: 2.0.0
+      periscopic: 3.1.0
+      remark-mdx: 3.0.1
+      remark-parse: 11.0.0
+      remark-rehype: 11.1.0
+      source-map: 0.7.4
+      unified: 11.0.4
+      unist-util-position-from-estree: 2.0.0
+      unist-util-stringify-position: 4.0.0
+      unist-util-visit: 5.0.0
+      vfile: 6.0.1
     transitivePeerDependencies:
       - supports-color
-    dev: false
 
-  /@mdx-js/react@1.6.22(react@17.0.2):
-    resolution: {integrity: sha512-TDoPum4SHdfPiGSAaRBw7ECyI8VaHpK8GJugbJIJuqyh6kzw9ZLJZW3HGL3NNrJGxcAixUvqROm+YuQOo5eXtg==}
-    peerDependencies:
-      react: ^16.13.1 || ^17.0.0
+  '@mdx-js/react@3.0.1(@types/react@18.0.26)(react@18.2.0)':
     dependencies:
-      react: 17.0.2
-    dev: false
-
-  /@mdx-js/util@1.6.22:
-    resolution: {integrity: sha512-H1rQc1ZOHANWBvPcW+JpGwr+juXSxM8Q8YCkm3GhZd8REu1fHR3z99CErO1p9pkcfcxZnMdIZdIsXkOHY0NilA==}
-    dev: false
+      '@types/mdx': 2.0.11
+      '@types/react': 18.0.26
+      react: 18.2.0
 
-  /@nodelib/fs.scandir@2.1.5:
-    resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
-    engines: {node: '>= 8'}
+  '@nodelib/fs.scandir@2.1.5':
     dependencies:
       '@nodelib/fs.stat': 2.0.5
       run-parallel: 1.2.0
 
-  /@nodelib/fs.stat@2.0.5:
-    resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
-    engines: {node: '>= 8'}
+  '@nodelib/fs.stat@2.0.5': {}
 
-  /@nodelib/fs.walk@1.2.8:
-    resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
-    engines: {node: '>= 8'}
+  '@nodelib/fs.walk@1.2.8':
     dependencies:
       '@nodelib/fs.scandir': 2.1.5
-      fastq: 1.15.0
+      fastq: 1.17.1
+
+  '@pkgjs/parseargs@0.11.0':
+    optional: true
+
+  '@pnpm/config.env-replace@1.1.0': {}
 
-  /@polka/url@1.0.0-next.21:
-    resolution: {integrity: sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==}
-    dev: false
+  '@pnpm/network.ca-file@1.0.2':
+    dependencies:
+      graceful-fs: 4.2.10
+
+  '@pnpm/npm-conf@2.2.2':
+    dependencies:
+      '@pnpm/config.env-replace': 1.1.0
+      '@pnpm/network.ca-file': 1.0.2
+      config-chain: 1.1.13
 
-  /@sideway/address@4.1.4:
-    resolution: {integrity: sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==}
+  '@polka/url@1.0.0-next.24': {}
+
+  '@sideway/address@4.1.5':
     dependencies:
       '@hapi/hoek': 9.3.0
 
-  /@sideway/formula@3.0.1:
-    resolution: {integrity: sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==}
+  '@sideway/formula@3.0.1': {}
 
-  /@sideway/pinpoint@2.0.0:
-    resolution: {integrity: sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==}
+  '@sideway/pinpoint@2.0.0': {}
 
-  /@sinclair/typebox@0.24.51:
-    resolution: {integrity: sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==}
-    dev: false
+  '@sinclair/typebox@0.27.8': {}
 
-  /@sindresorhus/is@0.14.0:
-    resolution: {integrity: sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==}
-    engines: {node: '>=6'}
-    dev: false
+  '@sindresorhus/is@4.6.0': {}
 
-  /@slorber/static-site-generator-webpack-plugin@4.0.7:
-    resolution: {integrity: sha512-Ug7x6z5lwrz0WqdnNFOMYrDQNTPAprvHLSh6+/fmml3qUiz6l5eq+2MzLKWtn/q5K5NpSiFsZTP/fck/3vjSxA==}
-    engines: {node: '>=14'}
+  '@sindresorhus/is@5.6.0': {}
+
+  '@slorber/remark-comment@1.0.0':
     dependencies:
-      eval: 0.1.8
-      p-map: 4.0.0
-      webpack-sources: 3.2.3
-    dev: false
+      micromark-factory-space: 1.1.0
+      micromark-util-character: 1.2.0
+      micromark-util-symbol: 1.1.0
 
-  /@svgr/babel-plugin-add-jsx-attribute@6.5.1(@babel/core@7.20.12):
-    resolution: {integrity: sha512-9PYGcXrAxitycIjRmZB+Q0JaN07GZIWaTBIGQzfaZv+qr1n8X1XUEJ5rZ/vx6OVD9RRYlrNnXWExQXcmZeD/BQ==}
-    engines: {node: '>=10'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
+  '@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.23.9)':
     dependencies:
-      '@babel/core': 7.20.12
-    dev: false
+      '@babel/core': 7.23.9
 
-  /@svgr/babel-plugin-remove-jsx-attribute@6.5.0(@babel/core@7.20.12):
-    resolution: {integrity: sha512-8zYdkym7qNyfXpWvu4yq46k41pyNM9SOstoWhKlm+IfdCE1DdnRKeMUPsWIEO/DEkaWxJ8T9esNdG3QwQ93jBA==}
-    engines: {node: '>=10'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
+  '@svgr/babel-plugin-remove-jsx-attribute@8.0.0(@babel/core@7.23.9)':
     dependencies:
-      '@babel/core': 7.20.12
-    dev: false
+      '@babel/core': 7.23.9
 
-  /@svgr/babel-plugin-remove-jsx-empty-expression@6.5.0(@babel/core@7.20.12):
-    resolution: {integrity: sha512-NFdxMq3xA42Kb1UbzCVxplUc0iqSyM9X8kopImvFnB+uSDdzIHOdbs1op8ofAvVRtbg4oZiyRl3fTYeKcOe9Iw==}
-    engines: {node: '>=10'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
+  '@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0(@babel/core@7.23.9)':
     dependencies:
-      '@babel/core': 7.20.12
-    dev: false
+      '@babel/core': 7.23.9
 
-  /@svgr/babel-plugin-replace-jsx-attribute-value@6.5.1(@babel/core@7.20.12):
-    resolution: {integrity: sha512-8DPaVVE3fd5JKuIC29dqyMB54sA6mfgki2H2+swh+zNJoynC8pMPzOkidqHOSc6Wj032fhl8Z0TVn1GiPpAiJg==}
-    engines: {node: '>=10'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
+  '@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0(@babel/core@7.23.9)':
     dependencies:
-      '@babel/core': 7.20.12
-    dev: false
+      '@babel/core': 7.23.9
 
-  /@svgr/babel-plugin-svg-dynamic-title@6.5.1(@babel/core@7.20.12):
-    resolution: {integrity: sha512-FwOEi0Il72iAzlkaHrlemVurgSQRDFbk0OC8dSvD5fSBPHltNh7JtLsxmZUhjYBZo2PpcU/RJvvi6Q0l7O7ogw==}
-    engines: {node: '>=10'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
+  '@svgr/babel-plugin-svg-dynamic-title@8.0.0(@babel/core@7.23.9)':
     dependencies:
-      '@babel/core': 7.20.12
-    dev: false
+      '@babel/core': 7.23.9
 
-  /@svgr/babel-plugin-svg-em-dimensions@6.5.1(@babel/core@7.20.12):
-    resolution: {integrity: sha512-gWGsiwjb4tw+ITOJ86ndY/DZZ6cuXMNE/SjcDRg+HLuCmwpcjOktwRF9WgAiycTqJD/QXqL2f8IzE2Rzh7aVXA==}
-    engines: {node: '>=10'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
+  '@svgr/babel-plugin-svg-em-dimensions@8.0.0(@babel/core@7.23.9)':
     dependencies:
-      '@babel/core': 7.20.12
-    dev: false
+      '@babel/core': 7.23.9
 
-  /@svgr/babel-plugin-transform-react-native-svg@6.5.1(@babel/core@7.20.12):
-    resolution: {integrity: sha512-2jT3nTayyYP7kI6aGutkyfJ7UMGtuguD72OjeGLwVNyfPRBD8zQthlvL+fAbAKk5n9ZNcvFkp/b1lZ7VsYqVJg==}
-    engines: {node: '>=10'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
+  '@svgr/babel-plugin-transform-react-native-svg@8.1.0(@babel/core@7.23.9)':
     dependencies:
-      '@babel/core': 7.20.12
-    dev: false
+      '@babel/core': 7.23.9
 
-  /@svgr/babel-plugin-transform-svg-component@6.5.1(@babel/core@7.20.12):
-    resolution: {integrity: sha512-a1p6LF5Jt33O3rZoVRBqdxL350oge54iZWHNI6LJB5tQ7EelvD/Mb1mfBiZNAan0dt4i3VArkFRjA4iObuNykQ==}
-    engines: {node: '>=12'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
+  '@svgr/babel-plugin-transform-svg-component@8.0.0(@babel/core@7.23.9)':
     dependencies:
-      '@babel/core': 7.20.12
-    dev: false
+      '@babel/core': 7.23.9
 
-  /@svgr/babel-preset@6.5.1(@babel/core@7.20.12):
-    resolution: {integrity: sha512-6127fvO/FF2oi5EzSQOAjo1LE3OtNVh11R+/8FXa+mHx1ptAaS4cknIjnUA7e6j6fwGGJ17NzaTJFUwOV2zwCw==}
-    engines: {node: '>=10'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
+  '@svgr/babel-preset@8.1.0(@babel/core@7.23.9)':
     dependencies:
-      '@babel/core': 7.20.12
-      '@svgr/babel-plugin-add-jsx-attribute': 6.5.1(@babel/core@7.20.12)
-      '@svgr/babel-plugin-remove-jsx-attribute': 6.5.0(@babel/core@7.20.12)
-      '@svgr/babel-plugin-remove-jsx-empty-expression': 6.5.0(@babel/core@7.20.12)
-      '@svgr/babel-plugin-replace-jsx-attribute-value': 6.5.1(@babel/core@7.20.12)
-      '@svgr/babel-plugin-svg-dynamic-title': 6.5.1(@babel/core@7.20.12)
-      '@svgr/babel-plugin-svg-em-dimensions': 6.5.1(@babel/core@7.20.12)
-      '@svgr/babel-plugin-transform-react-native-svg': 6.5.1(@babel/core@7.20.12)
-      '@svgr/babel-plugin-transform-svg-component': 6.5.1(@babel/core@7.20.12)
-    dev: false
-
-  /@svgr/core@6.5.1:
-    resolution: {integrity: sha512-/xdLSWxK5QkqG524ONSjvg3V/FkNyCv538OIBdQqPNaAta3AsXj/Bd2FbvR87yMbXO2hFSWiAe/Q6IkVPDw+mw==}
-    engines: {node: '>=10'}
+      '@babel/core': 7.23.9
+      '@svgr/babel-plugin-add-jsx-attribute': 8.0.0(@babel/core@7.23.9)
+      '@svgr/babel-plugin-remove-jsx-attribute': 8.0.0(@babel/core@7.23.9)
+      '@svgr/babel-plugin-remove-jsx-empty-expression': 8.0.0(@babel/core@7.23.9)
+      '@svgr/babel-plugin-replace-jsx-attribute-value': 8.0.0(@babel/core@7.23.9)
+      '@svgr/babel-plugin-svg-dynamic-title': 8.0.0(@babel/core@7.23.9)
+      '@svgr/babel-plugin-svg-em-dimensions': 8.0.0(@babel/core@7.23.9)
+      '@svgr/babel-plugin-transform-react-native-svg': 8.1.0(@babel/core@7.23.9)
+      '@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.23.9)
+
+  '@svgr/core@8.1.0(typescript@5.3.3)':
     dependencies:
-      '@babel/core': 7.20.12
-      '@svgr/babel-preset': 6.5.1(@babel/core@7.20.12)
-      '@svgr/plugin-jsx': 6.5.1(@svgr/core@6.5.1)
+      '@babel/core': 7.23.9
+      '@svgr/babel-preset': 8.1.0(@babel/core@7.23.9)
       camelcase: 6.3.0
-      cosmiconfig: 7.1.0
+      cosmiconfig: 8.3.6(typescript@5.3.3)
+      snake-case: 3.0.4
     transitivePeerDependencies:
       - supports-color
-    dev: false
+      - typescript
 
-  /@svgr/hast-util-to-babel-ast@6.5.1:
-    resolution: {integrity: sha512-1hnUxxjd83EAxbL4a0JDJoD3Dao3hmjvyvyEV8PzWmLK3B9m9NPlW7GKjFyoWE8nM7HnXzPcmmSyOW8yOddSXw==}
-    engines: {node: '>=10'}
+  '@svgr/hast-util-to-babel-ast@8.0.0':
     dependencies:
-      '@babel/types': 7.20.7
+      '@babel/types': 7.23.9
       entities: 4.4.0
-    dev: false
 
-  /@svgr/plugin-jsx@6.5.1(@svgr/core@6.5.1):
-    resolution: {integrity: sha512-+UdQxI3jgtSjCykNSlEMuy1jSRQlGC7pqBCPvkG/2dATdWo082zHTTK3uhnAju2/6XpE6B5mZ3z4Z8Ns01S8Gw==}
-    engines: {node: '>=10'}
-    peerDependencies:
-      '@svgr/core': ^6.0.0
+  '@svgr/plugin-jsx@8.1.0(@svgr/core@8.1.0(typescript@5.3.3))':
     dependencies:
-      '@babel/core': 7.20.12
-      '@svgr/babel-preset': 6.5.1(@babel/core@7.20.12)
-      '@svgr/core': 6.5.1
-      '@svgr/hast-util-to-babel-ast': 6.5.1
+      '@babel/core': 7.23.9
+      '@svgr/babel-preset': 8.1.0(@babel/core@7.23.9)
+      '@svgr/core': 8.1.0(typescript@5.3.3)
+      '@svgr/hast-util-to-babel-ast': 8.0.0
       svg-parser: 2.0.4
     transitivePeerDependencies:
       - supports-color
-    dev: false
 
-  /@svgr/plugin-svgo@6.5.1(@svgr/core@6.5.1):
-    resolution: {integrity: sha512-omvZKf8ixP9z6GWgwbtmP9qQMPX4ODXi+wzbVZgomNFsUIlHA1sf4fThdwTWSsZGgvGAG6yE+b/F5gWUkcZ/iQ==}
-    engines: {node: '>=10'}
-    peerDependencies:
-      '@svgr/core': '*'
+  '@svgr/plugin-svgo@8.1.0(@svgr/core@8.1.0(typescript@5.3.3))(typescript@5.3.3)':
     dependencies:
-      '@svgr/core': 6.5.1
-      cosmiconfig: 7.1.0
-      deepmerge: 4.2.2
-      svgo: 2.8.0
-    dev: false
+      '@svgr/core': 8.1.0(typescript@5.3.3)
+      cosmiconfig: 8.3.6(typescript@5.3.3)
+      deepmerge: 4.3.1
+      svgo: 3.3.2
+    transitivePeerDependencies:
+      - typescript
 
-  /@svgr/webpack@6.5.1:
-    resolution: {integrity: sha512-cQ/AsnBkXPkEK8cLbv4Dm7JGXq2XrumKnL1dRpJD9rIO2fTIlJI9a1uCciYG1F2aUsox/hJQyNGbt3soDxSRkA==}
-    engines: {node: '>=10'}
+  '@svgr/webpack@8.1.0(typescript@5.3.3)':
     dependencies:
-      '@babel/core': 7.20.12
-      '@babel/plugin-transform-react-constant-elements': 7.20.2(@babel/core@7.20.12)
-      '@babel/preset-env': 7.20.2(@babel/core@7.20.12)
-      '@babel/preset-react': 7.18.6(@babel/core@7.20.12)
-      '@babel/preset-typescript': 7.18.6(@babel/core@7.20.12)
-      '@svgr/core': 6.5.1
-      '@svgr/plugin-jsx': 6.5.1(@svgr/core@6.5.1)
-      '@svgr/plugin-svgo': 6.5.1(@svgr/core@6.5.1)
+      '@babel/core': 7.23.9
+      '@babel/plugin-transform-react-constant-elements': 7.24.7(@babel/core@7.23.9)
+      '@babel/preset-env': 7.23.9(@babel/core@7.23.9)
+      '@babel/preset-react': 7.23.3(@babel/core@7.23.9)
+      '@babel/preset-typescript': 7.23.3(@babel/core@7.23.9)
+      '@svgr/core': 8.1.0(typescript@5.3.3)
+      '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.3.3))
+      '@svgr/plugin-svgo': 8.1.0(@svgr/core@8.1.0(typescript@5.3.3))(typescript@5.3.3)
     transitivePeerDependencies:
       - supports-color
-    dev: false
+      - typescript
 
-  /@szmarczak/http-timer@1.1.2:
-    resolution: {integrity: sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==}
-    engines: {node: '>=6'}
+  '@szmarczak/http-timer@5.0.1':
     dependencies:
-      defer-to-connect: 1.1.3
-    dev: false
+      defer-to-connect: 2.0.1
 
-  /@tailwindcss/typography@0.5.9(tailwindcss@3.2.4):
-    resolution: {integrity: sha512-t8Sg3DyynFysV9f4JDOVISGsjazNb48AeIYQwcL+Bsq5uf4RYL75C1giZ43KISjeDGBaTN3Kxh7Xj/vRSMJUUg==}
-    peerDependencies:
-      tailwindcss: '>=3.0.0 || insiders'
+  '@tailwindcss/typography@0.5.9(tailwindcss@3.4.11)':
     dependencies:
       lodash.castarray: 4.4.0
       lodash.isplainobject: 4.0.6
       lodash.merge: 4.6.2
       postcss-selector-parser: 6.0.10
-      tailwindcss: 3.2.4(postcss@8.4.21)
-    dev: true
+      tailwindcss: 3.4.11
 
-  /@trysound/sax@0.2.0:
-    resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==}
-    engines: {node: '>=10.13.0'}
-    dev: false
+  '@trysound/sax@0.2.0': {}
 
-  /@tsconfig/docusaurus@1.0.7:
-    resolution: {integrity: sha512-ffTXxGIP/IRMCjuzHd6M4/HdIrw1bMfC7Bv8hMkTadnePkpe0lG0oDSdbRpSDZb2rQMAgpbWiR10BvxvNYwYrg==}
-    dev: true
+  '@tsconfig/docusaurus@2.0.3': {}
 
-  /@types/body-parser@1.19.2:
-    resolution: {integrity: sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==}
+  '@types/acorn@4.0.6':
+    dependencies:
+      '@types/estree': 1.0.5
+
+  '@types/body-parser@1.19.2':
     dependencies:
       '@types/connect': 3.4.35
-      '@types/node': 18.11.18
-    dev: false
+      '@types/node': 20.11.17
 
-  /@types/bonjour@3.5.10:
-    resolution: {integrity: sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw==}
+  '@types/bonjour@3.5.10':
     dependencies:
-      '@types/node': 18.11.18
-    dev: false
+      '@types/node': 20.11.17
 
-  /@types/connect-history-api-fallback@1.3.5:
-    resolution: {integrity: sha512-h8QJa8xSb1WD4fpKBDcATDNGXghFj6/3GRWG6dhmRcu0RX1Ubasur2Uvx5aeEwlf0MwblEC2bMzzMQntxnw/Cw==}
+  '@types/connect-history-api-fallback@1.3.5':
     dependencies:
       '@types/express-serve-static-core': 4.17.32
-      '@types/node': 18.11.18
-    dev: false
+      '@types/node': 20.11.17
 
-  /@types/connect@3.4.35:
-    resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==}
+  '@types/connect@3.4.35':
     dependencies:
-      '@types/node': 18.11.18
-    dev: false
+      '@types/node': 20.11.17
 
-  /@types/eslint-scope@3.7.4:
-    resolution: {integrity: sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==}
+  '@types/d3-scale-chromatic@3.0.3': {}
+
+  '@types/d3-scale@4.0.8':
+    dependencies:
+      '@types/d3-time': 3.0.3
+
+  '@types/d3-time@3.0.3': {}
+
+  '@types/debug@4.1.12':
+    dependencies:
+      '@types/ms': 0.7.34
+
+  '@types/eslint-scope@3.7.4':
     dependencies:
       '@types/eslint': 8.4.10
-      '@types/estree': 0.0.51
+      '@types/estree': 1.0.5
 
-  /@types/eslint@8.4.10:
-    resolution: {integrity: sha512-Sl/HOqN8NKPmhWo2VBEPm0nvHnu2LL3v9vKo8MEq0EtbJ4eVzGPl41VNPvn5E1i5poMk4/XD8UriLHpJvEP/Nw==}
+  '@types/eslint@8.4.10':
     dependencies:
-      '@types/estree': 0.0.51
+      '@types/estree': 1.0.5
       '@types/json-schema': 7.0.11
 
-  /@types/estree@0.0.51:
-    resolution: {integrity: sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==}
+  '@types/estree-jsx@1.0.4':
+    dependencies:
+      '@types/estree': 1.0.5
 
-  /@types/express-serve-static-core@4.17.32:
-    resolution: {integrity: sha512-aI5h/VOkxOF2Z1saPy0Zsxs5avets/iaiAJYznQFm5By/pamU31xWKL//epiF4OfUA2qTOc9PV6tCUjhO8wlZA==}
+  '@types/estree@1.0.5': {}
+
+  '@types/express-serve-static-core@4.17.32':
     dependencies:
-      '@types/node': 18.11.18
+      '@types/node': 20.11.17
       '@types/qs': 6.9.7
       '@types/range-parser': 1.2.4
-    dev: false
 
-  /@types/express@4.17.15:
-    resolution: {integrity: sha512-Yv0k4bXGOH+8a+7bELd2PqHQsuiANB+A8a4gnQrkRWzrkKlb6KHaVvyXhqs04sVW/OWlbPyYxRgYlIXLfrufMQ==}
+  '@types/express@4.17.15':
     dependencies:
       '@types/body-parser': 1.19.2
       '@types/express-serve-static-core': 4.17.32
       '@types/qs': 6.9.7
       '@types/serve-static': 1.15.0
-    dev: false
 
-  /@types/hast@2.3.4:
-    resolution: {integrity: sha512-wLEm0QvaoawEDoTRwzTXp4b4jpwiJDvR5KMnFnVodm3scufTlBOWRD6N1OBf9TZMhjlNsSfcO5V+7AF4+Vy+9g==}
+  '@types/gtag.js@0.0.12': {}
+
+  '@types/hast@3.0.4':
     dependencies:
-      '@types/unist': 2.0.6
-    dev: false
+      '@types/unist': 3.0.2
 
-  /@types/history@4.7.11:
-    resolution: {integrity: sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==}
+  '@types/history@4.7.11': {}
 
-  /@types/html-minifier-terser@6.1.0:
-    resolution: {integrity: sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==}
-    dev: false
+  '@types/html-minifier-terser@6.1.0': {}
 
-  /@types/http-proxy@1.17.9:
-    resolution: {integrity: sha512-QsbSjA/fSk7xB+UXlCT3wHBy5ai9wOcNDWwZAtud+jXhwOM3l+EYZh8Lng4+/6n8uar0J7xILzqftJdJ/Wdfkw==}
+  '@types/http-cache-semantics@4.0.4': {}
+
+  '@types/http-proxy@1.17.9':
     dependencies:
-      '@types/node': 18.11.18
-    dev: false
+      '@types/node': 20.11.17
 
-  /@types/istanbul-lib-coverage@2.0.4:
-    resolution: {integrity: sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==}
-    dev: false
+  '@types/istanbul-lib-coverage@2.0.4': {}
 
-  /@types/istanbul-lib-report@3.0.0:
-    resolution: {integrity: sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==}
+  '@types/istanbul-lib-report@3.0.0':
     dependencies:
       '@types/istanbul-lib-coverage': 2.0.4
-    dev: false
 
-  /@types/istanbul-reports@3.0.1:
-    resolution: {integrity: sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==}
+  '@types/istanbul-reports@3.0.1':
     dependencies:
       '@types/istanbul-lib-report': 3.0.0
-    dev: false
 
-  /@types/json-schema@7.0.11:
-    resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==}
+  '@types/json-schema@7.0.11': {}
 
-  /@types/keyv@3.1.4:
-    resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==}
+  '@types/mdast@3.0.10':
     dependencies:
-      '@types/node': 18.11.18
-    dev: false
+      '@types/unist': 3.0.2
 
-  /@types/mdast@3.0.10:
-    resolution: {integrity: sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA==}
+  '@types/mdast@4.0.3':
     dependencies:
-      '@types/unist': 2.0.6
-    dev: false
+      '@types/unist': 3.0.2
 
-  /@types/mime@3.0.1:
-    resolution: {integrity: sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==}
-    dev: false
+  '@types/mdx@2.0.11': {}
 
-  /@types/node@17.0.45:
-    resolution: {integrity: sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==}
-    dev: false
+  '@types/mime@3.0.1': {}
 
-  /@types/node@18.11.18:
-    resolution: {integrity: sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==}
+  '@types/ms@0.7.34': {}
 
-  /@types/parse-json@4.0.0:
-    resolution: {integrity: sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==}
-    dev: false
+  '@types/node@17.0.45': {}
 
-  /@types/parse5@5.0.3:
-    resolution: {integrity: sha512-kUNnecmtkunAoQ3CnjmMkzNU/gtxG8guhi+Fk2U/kOpIKjIMKnXGp4IJCgQJrXSgMsWYimYG4TGjz/UzbGEBTw==}
-    dev: false
+  '@types/node@20.11.17':
+    dependencies:
+      undici-types: 5.26.5
 
-  /@types/prop-types@15.7.5:
-    resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==}
+  '@types/parse-json@4.0.0': {}
 
-  /@types/qs@6.9.7:
-    resolution: {integrity: sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==}
-    dev: false
+  '@types/prismjs@1.26.3': {}
 
-  /@types/range-parser@1.2.4:
-    resolution: {integrity: sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==}
-    dev: false
+  '@types/prop-types@15.7.5': {}
+
+  '@types/qs@6.9.7': {}
 
-  /@types/react-router-config@5.0.6:
-    resolution: {integrity: sha512-db1mx37a1EJDf1XeX8jJN7R3PZABmJQXR8r28yUjVMFSjkmnQo6X6pOEEmNl+Tp2gYQOGPdYbFIipBtdElZ3Yg==}
+  '@types/range-parser@1.2.4': {}
+
+  '@types/react-router-config@5.0.11':
     dependencies:
       '@types/history': 4.7.11
       '@types/react': 18.0.26
       '@types/react-router': 5.1.20
 
-  /@types/react-router-dom@5.3.3:
-    resolution: {integrity: sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==}
+  '@types/react-router-dom@5.3.3':
     dependencies:
       '@types/history': 4.7.11
       '@types/react': 18.0.26
       '@types/react-router': 5.1.20
 
-  /@types/react-router@5.1.20:
-    resolution: {integrity: sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==}
+  '@types/react-router@5.1.20':
     dependencies:
       '@types/history': 4.7.11
       '@types/react': 18.0.26
 
-  /@types/react@18.0.26:
-    resolution: {integrity: sha512-hCR3PJQsAIXyxhTNSiDFY//LhnMZWpNNr5etoCqx/iUfGc5gXWtQR2Phl908jVR6uPXacojQWTg4qRpkxTuGug==}
+  '@types/react@18.0.26':
     dependencies:
       '@types/prop-types': 15.7.5
       '@types/scheduler': 0.16.2
       csstype: 3.1.1
 
-  /@types/responselike@1.0.0:
-    resolution: {integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==}
-    dependencies:
-      '@types/node': 18.11.18
-    dev: false
-
-  /@types/retry@0.12.0:
-    resolution: {integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==}
-    dev: false
+  '@types/retry@0.12.0': {}
 
-  /@types/sax@1.2.4:
-    resolution: {integrity: sha512-pSAff4IAxJjfAXUG6tFkO7dsSbTmf8CtUpfhhZ5VhkRpC4628tJhh3+V6H1E+/Gs9piSzYKT5yzHO5M4GG9jkw==}
+  '@types/sax@1.2.4':
     dependencies:
-      '@types/node': 18.11.18
-    dev: false
+      '@types/node': 20.11.17
 
-  /@types/scheduler@0.16.2:
-    resolution: {integrity: sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==}
+  '@types/scheduler@0.16.2': {}
 
-  /@types/serve-index@1.9.1:
-    resolution: {integrity: sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg==}
+  '@types/serve-index@1.9.1':
     dependencies:
       '@types/express': 4.17.15
-    dev: false
 
-  /@types/serve-static@1.15.0:
-    resolution: {integrity: sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg==}
+  '@types/serve-static@1.15.0':
     dependencies:
       '@types/mime': 3.0.1
-      '@types/node': 18.11.18
-    dev: false
+      '@types/node': 20.11.17
 
-  /@types/sockjs@0.3.33:
-    resolution: {integrity: sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw==}
+  '@types/sockjs@0.3.33':
     dependencies:
-      '@types/node': 18.11.18
-    dev: false
+      '@types/node': 20.11.17
 
-  /@types/unist@2.0.6:
-    resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==}
-    dev: false
+  '@types/trusted-types@2.0.7': {}
+
+  '@types/unist@2.0.6': {}
 
-  /@types/ws@8.5.4:
-    resolution: {integrity: sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==}
+  '@types/unist@3.0.2': {}
+
+  '@types/ws@8.5.10':
     dependencies:
-      '@types/node': 18.11.18
-    dev: false
+      '@types/node': 20.11.17
 
-  /@types/yargs-parser@21.0.0:
-    resolution: {integrity: sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==}
-    dev: false
+  '@types/yargs-parser@21.0.0': {}
 
-  /@types/yargs@17.0.19:
-    resolution: {integrity: sha512-cAx3qamwaYX9R0fzOIZAlFpo4A+1uBVCxqpKz9D26uTF4srRXaGTTsikQmaotCtNdbhzyUH7ft6p9ktz9s6UNQ==}
+  '@types/yargs@17.0.19':
     dependencies:
       '@types/yargs-parser': 21.0.0
-    dev: false
 
-  /@webassemblyjs/ast@1.11.1:
-    resolution: {integrity: sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==}
+  '@ungap/structured-clone@1.2.0': {}
+
+  '@webassemblyjs/ast@1.11.6':
     dependencies:
-      '@webassemblyjs/helper-numbers': 1.11.1
-      '@webassemblyjs/helper-wasm-bytecode': 1.11.1
+      '@webassemblyjs/helper-numbers': 1.11.6
+      '@webassemblyjs/helper-wasm-bytecode': 1.11.6
 
-  /@webassemblyjs/floating-point-hex-parser@1.11.1:
-    resolution: {integrity: sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==}
+  '@webassemblyjs/floating-point-hex-parser@1.11.6': {}
 
-  /@webassemblyjs/helper-api-error@1.11.1:
-    resolution: {integrity: sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==}
+  '@webassemblyjs/helper-api-error@1.11.6': {}
 
-  /@webassemblyjs/helper-buffer@1.11.1:
-    resolution: {integrity: sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==}
+  '@webassemblyjs/helper-buffer@1.11.6': {}
 
-  /@webassemblyjs/helper-numbers@1.11.1:
-    resolution: {integrity: sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==}
+  '@webassemblyjs/helper-numbers@1.11.6':
     dependencies:
-      '@webassemblyjs/floating-point-hex-parser': 1.11.1
-      '@webassemblyjs/helper-api-error': 1.11.1
+      '@webassemblyjs/floating-point-hex-parser': 1.11.6
+      '@webassemblyjs/helper-api-error': 1.11.6
       '@xtuc/long': 4.2.2
 
-  /@webassemblyjs/helper-wasm-bytecode@1.11.1:
-    resolution: {integrity: sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==}
+  '@webassemblyjs/helper-wasm-bytecode@1.11.6': {}
 
-  /@webassemblyjs/helper-wasm-section@1.11.1:
-    resolution: {integrity: sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==}
+  '@webassemblyjs/helper-wasm-section@1.11.6':
     dependencies:
-      '@webassemblyjs/ast': 1.11.1
-      '@webassemblyjs/helper-buffer': 1.11.1
-      '@webassemblyjs/helper-wasm-bytecode': 1.11.1
-      '@webassemblyjs/wasm-gen': 1.11.1
+      '@webassemblyjs/ast': 1.11.6
+      '@webassemblyjs/helper-buffer': 1.11.6
+      '@webassemblyjs/helper-wasm-bytecode': 1.11.6
+      '@webassemblyjs/wasm-gen': 1.11.6
 
-  /@webassemblyjs/ieee754@1.11.1:
-    resolution: {integrity: sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==}
+  '@webassemblyjs/ieee754@1.11.6':
     dependencies:
       '@xtuc/ieee754': 1.2.0
 
-  /@webassemblyjs/leb128@1.11.1:
-    resolution: {integrity: sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==}
+  '@webassemblyjs/leb128@1.11.6':
     dependencies:
       '@xtuc/long': 4.2.2
 
-  /@webassemblyjs/utf8@1.11.1:
-    resolution: {integrity: sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==}
+  '@webassemblyjs/utf8@1.11.6': {}
 
-  /@webassemblyjs/wasm-edit@1.11.1:
-    resolution: {integrity: sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==}
+  '@webassemblyjs/wasm-edit@1.11.6':
     dependencies:
-      '@webassemblyjs/ast': 1.11.1
-      '@webassemblyjs/helper-buffer': 1.11.1
-      '@webassemblyjs/helper-wasm-bytecode': 1.11.1
-      '@webassemblyjs/helper-wasm-section': 1.11.1
-      '@webassemblyjs/wasm-gen': 1.11.1
-      '@webassemblyjs/wasm-opt': 1.11.1
-      '@webassemblyjs/wasm-parser': 1.11.1
-      '@webassemblyjs/wast-printer': 1.11.1
+      '@webassemblyjs/ast': 1.11.6
+      '@webassemblyjs/helper-buffer': 1.11.6
+      '@webassemblyjs/helper-wasm-bytecode': 1.11.6
+      '@webassemblyjs/helper-wasm-section': 1.11.6
+      '@webassemblyjs/wasm-gen': 1.11.6
+      '@webassemblyjs/wasm-opt': 1.11.6
+      '@webassemblyjs/wasm-parser': 1.11.6
+      '@webassemblyjs/wast-printer': 1.11.6
 
-  /@webassemblyjs/wasm-gen@1.11.1:
-    resolution: {integrity: sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==}
+  '@webassemblyjs/wasm-gen@1.11.6':
     dependencies:
-      '@webassemblyjs/ast': 1.11.1
-      '@webassemblyjs/helper-wasm-bytecode': 1.11.1
-      '@webassemblyjs/ieee754': 1.11.1
-      '@webassemblyjs/leb128': 1.11.1
-      '@webassemblyjs/utf8': 1.11.1
+      '@webassemblyjs/ast': 1.11.6
+      '@webassemblyjs/helper-wasm-bytecode': 1.11.6
+      '@webassemblyjs/ieee754': 1.11.6
+      '@webassemblyjs/leb128': 1.11.6
+      '@webassemblyjs/utf8': 1.11.6
 
-  /@webassemblyjs/wasm-opt@1.11.1:
-    resolution: {integrity: sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==}
+  '@webassemblyjs/wasm-opt@1.11.6':
     dependencies:
-      '@webassemblyjs/ast': 1.11.1
-      '@webassemblyjs/helper-buffer': 1.11.1
-      '@webassemblyjs/wasm-gen': 1.11.1
-      '@webassemblyjs/wasm-parser': 1.11.1
+      '@webassemblyjs/ast': 1.11.6
+      '@webassemblyjs/helper-buffer': 1.11.6
+      '@webassemblyjs/wasm-gen': 1.11.6
+      '@webassemblyjs/wasm-parser': 1.11.6
 
-  /@webassemblyjs/wasm-parser@1.11.1:
-    resolution: {integrity: sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==}
+  '@webassemblyjs/wasm-parser@1.11.6':
     dependencies:
-      '@webassemblyjs/ast': 1.11.1
-      '@webassemblyjs/helper-api-error': 1.11.1
-      '@webassemblyjs/helper-wasm-bytecode': 1.11.1
-      '@webassemblyjs/ieee754': 1.11.1
-      '@webassemblyjs/leb128': 1.11.1
-      '@webassemblyjs/utf8': 1.11.1
+      '@webassemblyjs/ast': 1.11.6
+      '@webassemblyjs/helper-api-error': 1.11.6
+      '@webassemblyjs/helper-wasm-bytecode': 1.11.6
+      '@webassemblyjs/ieee754': 1.11.6
+      '@webassemblyjs/leb128': 1.11.6
+      '@webassemblyjs/utf8': 1.11.6
 
-  /@webassemblyjs/wast-printer@1.11.1:
-    resolution: {integrity: sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==}
+  '@webassemblyjs/wast-printer@1.11.6':
     dependencies:
-      '@webassemblyjs/ast': 1.11.1
+      '@webassemblyjs/ast': 1.11.6
       '@xtuc/long': 4.2.2
 
-  /@xtuc/ieee754@1.2.0:
-    resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==}
+  '@xtuc/ieee754@1.2.0': {}
 
-  /@xtuc/long@4.2.2:
-    resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==}
+  '@xtuc/long@4.2.2': {}
 
-  /accepts@1.3.8:
-    resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
-    engines: {node: '>= 0.6'}
+  accepts@1.3.8:
     dependencies:
       mime-types: 2.1.35
       negotiator: 0.6.3
-    dev: false
 
-  /acorn-import-assertions@1.8.0(acorn@8.8.1):
-    resolution: {integrity: sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==}
-    peerDependencies:
-      acorn: ^8
+  acorn-import-assertions@1.9.0(acorn@8.11.3):
     dependencies:
-      acorn: 8.8.1
+      acorn: 8.11.3
 
-  /acorn-node@1.8.2:
-    resolution: {integrity: sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==}
+  acorn-jsx@5.3.2(acorn@8.11.3):
     dependencies:
-      acorn: 7.4.1
-      acorn-walk: 7.2.0
-      xtend: 4.0.2
-
-  /acorn-walk@7.2.0:
-    resolution: {integrity: sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==}
-    engines: {node: '>=0.4.0'}
+      acorn: 8.11.3
 
-  /acorn-walk@8.2.0:
-    resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==}
-    engines: {node: '>=0.4.0'}
-    dev: false
-
-  /acorn@7.4.1:
-    resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==}
-    engines: {node: '>=0.4.0'}
-    hasBin: true
+  acorn-walk@8.2.0: {}
 
-  /acorn@8.8.1:
-    resolution: {integrity: sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==}
-    engines: {node: '>=0.4.0'}
-    hasBin: true
+  acorn@8.11.3: {}
 
-  /address@1.2.2:
-    resolution: {integrity: sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==}
-    engines: {node: '>= 10.0.0'}
-    dev: false
+  address@1.2.2: {}
 
-  /aggregate-error@3.1.0:
-    resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==}
-    engines: {node: '>=8'}
+  aggregate-error@3.1.0:
     dependencies:
       clean-stack: 2.2.0
       indent-string: 4.0.0
-    dev: false
 
-  /ajv-formats@2.1.1(ajv@8.12.0):
-    resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==}
-    peerDependencies:
-      ajv: ^8.0.0
-    peerDependenciesMeta:
-      ajv:
-        optional: true
-    dependencies:
+  ajv-formats@2.1.1(ajv@8.12.0):
+    optionalDependencies:
       ajv: 8.12.0
-    dev: false
 
-  /ajv-keywords@3.5.2(ajv@6.12.6):
-    resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==}
-    peerDependencies:
-      ajv: ^6.9.1
+  ajv-keywords@3.5.2(ajv@6.12.6):
     dependencies:
       ajv: 6.12.6
 
-  /ajv-keywords@5.1.0(ajv@8.12.0):
-    resolution: {integrity: sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==}
-    peerDependencies:
-      ajv: ^8.8.2
+  ajv-keywords@5.1.0(ajv@8.12.0):
     dependencies:
       ajv: 8.12.0
       fast-deep-equal: 3.1.3
-    dev: false
 
-  /ajv@6.12.6:
-    resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
+  ajv@6.12.6:
     dependencies:
       fast-deep-equal: 3.1.3
       fast-json-stable-stringify: 2.1.0
       json-schema-traverse: 0.4.1
       uri-js: 4.4.1
 
-  /ajv@8.12.0:
-    resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==}
+  ajv@8.12.0:
     dependencies:
       fast-deep-equal: 3.1.3
       json-schema-traverse: 1.0.0
       require-from-string: 2.0.2
       uri-js: 4.4.1
-    dev: false
 
-  /algoliasearch-helper@3.11.1(algoliasearch@4.14.3):
-    resolution: {integrity: sha512-mvsPN3eK4E0bZG0/WlWJjeqe/bUD2KOEVOl0GyL/TGXn6wcpZU8NOuztGHCUKXkyg5gq6YzUakVTmnmSSO5Yiw==}
-    peerDependencies:
-      algoliasearch: '>= 3.1 < 6'
+  algoliasearch-helper@3.16.2(algoliasearch@4.22.1):
     dependencies:
       '@algolia/events': 4.0.1
-      algoliasearch: 4.14.3
-    dev: false
-
-  /algoliasearch@4.14.3:
-    resolution: {integrity: sha512-GZTEuxzfWbP/vr7ZJfGzIl8fOsoxN916Z6FY2Egc9q2TmZ6hvq5KfAxY89pPW01oW/2HDEKA8d30f9iAH9eXYg==}
-    dependencies:
-      '@algolia/cache-browser-local-storage': 4.14.3
-      '@algolia/cache-common': 4.14.3
-      '@algolia/cache-in-memory': 4.14.3
-      '@algolia/client-account': 4.14.3
-      '@algolia/client-analytics': 4.14.3
-      '@algolia/client-common': 4.14.3
-      '@algolia/client-personalization': 4.14.3
-      '@algolia/client-search': 4.14.3
-      '@algolia/logger-common': 4.14.3
-      '@algolia/logger-console': 4.14.3
-      '@algolia/requester-browser-xhr': 4.14.3
-      '@algolia/requester-common': 4.14.3
-      '@algolia/requester-node-http': 4.14.3
-      '@algolia/transporter': 4.14.3
-    dev: false
-
-  /ansi-align@3.0.1:
-    resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==}
+      algoliasearch: 4.22.1
+
+  algoliasearch@4.22.1:
+    dependencies:
+      '@algolia/cache-browser-local-storage': 4.22.1
+      '@algolia/cache-common': 4.22.1
+      '@algolia/cache-in-memory': 4.22.1
+      '@algolia/client-account': 4.22.1
+      '@algolia/client-analytics': 4.22.1
+      '@algolia/client-common': 4.22.1
+      '@algolia/client-personalization': 4.22.1
+      '@algolia/client-search': 4.22.1
+      '@algolia/logger-common': 4.22.1
+      '@algolia/logger-console': 4.22.1
+      '@algolia/requester-browser-xhr': 4.22.1
+      '@algolia/requester-common': 4.22.1
+      '@algolia/requester-node-http': 4.22.1
+      '@algolia/transporter': 4.22.1
+
+  ansi-align@3.0.1:
     dependencies:
       string-width: 4.2.3
-    dev: false
 
-  /ansi-html-community@0.0.8:
-    resolution: {integrity: sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==}
-    engines: {'0': node >= 0.8.0}
-    hasBin: true
-    dev: false
+  ansi-html-community@0.0.8: {}
 
-  /ansi-regex@5.0.1:
-    resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
-    engines: {node: '>=8'}
-    dev: false
+  ansi-regex@5.0.1: {}
 
-  /ansi-regex@6.0.1:
-    resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==}
-    engines: {node: '>=12'}
-    dev: false
+  ansi-regex@6.1.0: {}
 
-  /ansi-styles@3.2.1:
-    resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==}
-    engines: {node: '>=4'}
+  ansi-styles@3.2.1:
     dependencies:
       color-convert: 1.9.3
-    dev: false
 
-  /ansi-styles@4.3.0:
-    resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
-    engines: {node: '>=8'}
+  ansi-styles@4.3.0:
     dependencies:
       color-convert: 2.0.1
-    dev: false
 
-  /ansi-styles@6.2.1:
-    resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==}
-    engines: {node: '>=12'}
-    dev: false
+  ansi-styles@6.2.1: {}
 
-  /anymatch@3.1.3:
-    resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
-    engines: {node: '>= 8'}
+  any-promise@1.3.0: {}
+
+  anymatch@3.1.3:
     dependencies:
       normalize-path: 3.0.0
       picomatch: 2.3.1
 
-  /arg@5.0.2:
-    resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
+  arg@5.0.2: {}
 
-  /argparse@1.0.10:
-    resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==}
+  argparse@1.0.10:
     dependencies:
       sprintf-js: 1.0.3
-    dev: false
 
-  /argparse@2.0.1:
-    resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
-    dev: false
+  argparse@2.0.1: {}
 
-  /array-flatten@1.1.1:
-    resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==}
-    dev: false
+  array-flatten@1.1.1: {}
 
-  /array-flatten@2.1.2:
-    resolution: {integrity: sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==}
-    dev: false
+  array-flatten@2.1.2: {}
 
-  /array-union@2.1.0:
-    resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==}
-    engines: {node: '>=8'}
-    dev: false
+  array-union@2.1.0: {}
 
-  /asap@2.0.6:
-    resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==}
-    dev: false
+  astring@1.8.6: {}
 
-  /at-least-node@1.0.0:
-    resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==}
-    engines: {node: '>= 4.0.0'}
-    dev: false
+  at-least-node@1.0.0: {}
 
-  /autoprefixer@10.4.13(postcss@8.4.21):
-    resolution: {integrity: sha512-49vKpMqcZYsJjwotvt4+h/BCjJVnhGwcLpDt5xkcaOG3eLrG/HUYLagrihYsQ+qrIBgIzX1Rw7a6L8I/ZA1Atg==}
-    engines: {node: ^10 || ^12 || >=14}
-    hasBin: true
-    peerDependencies:
-      postcss: ^8.1.0
+  autoprefixer@10.4.13(postcss@8.4.21):
     dependencies:
       browserslist: 4.21.4
-      caniuse-lite: 1.0.30001442
+      caniuse-lite: 1.0.30001587
       fraction.js: 4.2.0
       normalize-range: 0.1.2
       picocolors: 1.0.0
       postcss: 8.4.21
       postcss-value-parser: 4.2.0
-    dev: false
 
-  /axios@0.25.0:
-    resolution: {integrity: sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==}
+  autoprefixer@10.4.17(postcss@8.4.35):
     dependencies:
-      follow-redirects: 1.15.2
-    transitivePeerDependencies:
-      - debug
-    dev: false
+      browserslist: 4.22.3
+      caniuse-lite: 1.0.30001587
+      fraction.js: 4.3.7
+      normalize-range: 0.1.2
+      picocolors: 1.1.0
+      postcss: 8.4.35
+      postcss-value-parser: 4.2.0
 
-  /babel-loader@8.3.0(@babel/core@7.20.12)(webpack@5.75.0):
-    resolution: {integrity: sha512-H8SvsMF+m9t15HNLMipppzkC+Y2Yq+v3SonZyU70RBL/h1gxPkH08Ot8pEE9Z4Kd+czyWJClmFS8qzIP9OZ04Q==}
-    engines: {node: '>= 8.9'}
-    peerDependencies:
-      '@babel/core': ^7.0.0
-      webpack: '>=2'
+  autoprefixer@10.4.19(postcss@8.4.45):
     dependencies:
-      '@babel/core': 7.20.12
-      find-cache-dir: 3.3.2
-      loader-utils: 2.0.4
-      make-dir: 3.1.0
-      schema-utils: 2.7.1
-      webpack: 5.75.0
-    dev: false
+      browserslist: 4.23.1
+      caniuse-lite: 1.0.30001632
+      fraction.js: 4.3.7
+      normalize-range: 0.1.2
+      picocolors: 1.1.0
+      postcss: 8.4.45
+      postcss-value-parser: 4.2.0
 
-  /babel-plugin-apply-mdx-type-prop@1.6.22(@babel/core@7.12.9):
-    resolution: {integrity: sha512-VefL+8o+F/DfK24lPZMtJctrCVOfgbqLAGZSkxwhazQv4VxPg3Za/i40fu22KR2m8eEda+IfSOlPLUSIiLcnCQ==}
-    peerDependencies:
-      '@babel/core': ^7.11.6
+  babel-loader@9.1.3(@babel/core@7.23.9)(webpack@5.90.1):
     dependencies:
-      '@babel/core': 7.12.9
-      '@babel/helper-plugin-utils': 7.10.4
-      '@mdx-js/util': 1.6.22
-    dev: false
+      '@babel/core': 7.23.9
+      find-cache-dir: 4.0.0
+      schema-utils: 4.0.0
+      webpack: 5.90.1
 
-  /babel-plugin-dynamic-import-node@2.3.3:
-    resolution: {integrity: sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==}
+  babel-plugin-dynamic-import-node@2.3.3:
     dependencies:
       object.assign: 4.1.4
-    dev: false
 
-  /babel-plugin-extract-import-names@1.6.22:
-    resolution: {integrity: sha512-yJ9BsJaISua7d8zNT7oRG1ZLBJCIdZ4PZqmH8qa9N5AK01ifk3fnkc98AXhtzE7UkfCsEumvoQWgoYLhOnJ7jQ==}
-    dependencies:
-      '@babel/helper-plugin-utils': 7.10.4
-    dev: false
-
-  /babel-plugin-polyfill-corejs2@0.3.3(@babel/core@7.20.12):
-    resolution: {integrity: sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
+  babel-plugin-polyfill-corejs2@0.4.8(@babel/core@7.23.9):
     dependencies:
-      '@babel/compat-data': 7.20.10
-      '@babel/core': 7.20.12
-      '@babel/helper-define-polyfill-provider': 0.3.3(@babel/core@7.20.12)
-      semver: 6.3.0
+      '@babel/compat-data': 7.23.5
+      '@babel/core': 7.23.9
+      '@babel/helper-define-polyfill-provider': 0.5.0(@babel/core@7.23.9)
+      semver: 6.3.1
     transitivePeerDependencies:
       - supports-color
-    dev: false
 
-  /babel-plugin-polyfill-corejs3@0.6.0(@babel/core@7.20.12):
-    resolution: {integrity: sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA==}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
+  babel-plugin-polyfill-corejs3@0.9.0(@babel/core@7.23.9):
     dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-define-polyfill-provider': 0.3.3(@babel/core@7.20.12)
-      core-js-compat: 3.27.1
+      '@babel/core': 7.23.9
+      '@babel/helper-define-polyfill-provider': 0.5.0(@babel/core@7.23.9)
+      core-js-compat: 3.35.1
     transitivePeerDependencies:
       - supports-color
-    dev: false
 
-  /babel-plugin-polyfill-regenerator@0.4.1(@babel/core@7.20.12):
-    resolution: {integrity: sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw==}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
+  babel-plugin-polyfill-regenerator@0.5.5(@babel/core@7.23.9):
     dependencies:
-      '@babel/core': 7.20.12
-      '@babel/helper-define-polyfill-provider': 0.3.3(@babel/core@7.20.12)
+      '@babel/core': 7.23.9
+      '@babel/helper-define-polyfill-provider': 0.5.0(@babel/core@7.23.9)
     transitivePeerDependencies:
       - supports-color
-    dev: false
 
-  /bail@1.0.5:
-    resolution: {integrity: sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ==}
-    dev: false
+  bail@2.0.2: {}
 
-  /balanced-match@1.0.2:
-    resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
-    dev: false
-
-  /base16@1.0.0:
-    resolution: {integrity: sha512-pNdYkNPiJUnEhnfXV56+sQy8+AaPcG3POZAUnwr4EeqCUZFz4u2PePbo3e5Gj4ziYPCWGUZT9RHisvJKnwFuBQ==}
-    dev: false
+  balanced-match@1.0.2: {}
 
-  /batch@0.6.1:
-    resolution: {integrity: sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==}
-    dev: false
+  batch@0.6.1: {}
 
-  /big.js@5.2.2:
-    resolution: {integrity: sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==}
-    dev: false
+  big.js@5.2.2: {}
 
-  /binary-extensions@2.2.0:
-    resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
-    engines: {node: '>=8'}
+  binary-extensions@2.2.0: {}
 
-  /body-parser@1.20.1:
-    resolution: {integrity: sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==}
-    engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
+  body-parser@1.20.1:
     dependencies:
       bytes: 3.1.2
       content-type: 1.0.4
@@ -3400,173 +7495,149 @@ packages:
       unpipe: 1.0.0
     transitivePeerDependencies:
       - supports-color
-    dev: false
 
-  /bonjour-service@1.0.14:
-    resolution: {integrity: sha512-HIMbgLnk1Vqvs6B4Wq5ep7mxvj9sGz5d1JJyDNSGNIdA/w2MCz6GTjWTdjqOJV1bEPj+6IkxDvWNFKEBxNt4kQ==}
+  bonjour-service@1.0.14:
     dependencies:
       array-flatten: 2.1.2
       dns-equal: 1.0.0
       fast-deep-equal: 3.1.3
       multicast-dns: 7.2.5
-    dev: false
 
-  /boolbase@1.0.0:
-    resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
-    dev: false
+  boolbase@1.0.0: {}
 
-  /boxen@5.1.2:
-    resolution: {integrity: sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==}
-    engines: {node: '>=10'}
+  boxen@6.2.1:
     dependencies:
       ansi-align: 3.0.1
       camelcase: 6.3.0
       chalk: 4.1.2
-      cli-boxes: 2.2.1
-      string-width: 4.2.3
-      type-fest: 0.20.2
-      widest-line: 3.1.0
-      wrap-ansi: 7.0.0
-    dev: false
+      cli-boxes: 3.0.0
+      string-width: 5.1.2
+      type-fest: 2.19.0
+      widest-line: 4.0.1
+      wrap-ansi: 8.1.0
 
-  /boxen@6.2.1:
-    resolution: {integrity: sha512-H4PEsJXfFI/Pt8sjDWbHlQPx4zL/bvSQjcilJmaulGt5mLDorHOHpmdXAJcBcmru7PhYSp/cDMWRko4ZUMFkSw==}
-    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+  boxen@7.1.1:
     dependencies:
       ansi-align: 3.0.1
-      camelcase: 6.3.0
-      chalk: 4.1.2
+      camelcase: 7.0.1
+      chalk: 5.3.0
       cli-boxes: 3.0.0
       string-width: 5.1.2
       type-fest: 2.19.0
       widest-line: 4.0.1
-      wrap-ansi: 8.0.1
-    dev: false
+      wrap-ansi: 8.1.0
 
-  /brace-expansion@1.1.11:
-    resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
+  brace-expansion@1.1.11:
     dependencies:
       balanced-match: 1.0.2
       concat-map: 0.0.1
-    dev: false
 
-  /braces@3.0.2:
-    resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==}
-    engines: {node: '>=8'}
+  brace-expansion@2.0.1:
+    dependencies:
+      balanced-match: 1.0.2
+
+  braces@3.0.2:
     dependencies:
       fill-range: 7.0.1
 
-  /browserslist@4.21.4:
-    resolution: {integrity: sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==}
-    engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
-    hasBin: true
+  braces@3.0.3:
+    dependencies:
+      fill-range: 7.1.1
+
+  browserslist@4.21.4:
     dependencies:
-      caniuse-lite: 1.0.30001442
+      caniuse-lite: 1.0.30001587
       electron-to-chromium: 1.4.284
       node-releases: 2.0.8
       update-browserslist-db: 1.0.10(browserslist@4.21.4)
 
-  /buffer-from@1.1.2:
-    resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
+  browserslist@4.22.3:
+    dependencies:
+      caniuse-lite: 1.0.30001587
+      electron-to-chromium: 1.4.667
+      node-releases: 2.0.14
+      update-browserslist-db: 1.0.13(browserslist@4.22.3)
 
-  /bytes@3.0.0:
-    resolution: {integrity: sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==}
-    engines: {node: '>= 0.8'}
-    dev: false
+  browserslist@4.23.1:
+    dependencies:
+      caniuse-lite: 1.0.30001632
+      electron-to-chromium: 1.4.796
+      node-releases: 2.0.14
+      update-browserslist-db: 1.0.16(browserslist@4.23.1)
 
-  /bytes@3.1.2:
-    resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
-    engines: {node: '>= 0.8'}
-    dev: false
+  buffer-from@1.1.2: {}
 
-  /cacheable-request@6.1.0:
-    resolution: {integrity: sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==}
-    engines: {node: '>=8'}
+  bytes@3.0.0: {}
+
+  bytes@3.1.2: {}
+
+  cacheable-lookup@7.0.0: {}
+
+  cacheable-request@10.2.14:
     dependencies:
-      clone-response: 1.0.3
-      get-stream: 5.2.0
-      http-cache-semantics: 4.1.0
-      keyv: 3.1.0
-      lowercase-keys: 2.0.0
-      normalize-url: 4.5.1
-      responselike: 1.0.2
-    dev: false
+      '@types/http-cache-semantics': 4.0.4
+      get-stream: 6.0.1
+      http-cache-semantics: 4.1.1
+      keyv: 4.5.4
+      mimic-response: 4.0.0
+      normalize-url: 8.0.0
+      responselike: 3.0.0
 
-  /call-bind@1.0.2:
-    resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==}
+  call-bind@1.0.2:
     dependencies:
-      function-bind: 1.1.1
+      function-bind: 1.1.2
       get-intrinsic: 1.1.3
-    dev: false
 
-  /callsites@3.1.0:
-    resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
-    engines: {node: '>=6'}
-    dev: false
+  callsites@3.1.0: {}
 
-  /camel-case@4.1.2:
-    resolution: {integrity: sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==}
+  camel-case@4.1.2:
     dependencies:
       pascal-case: 3.1.2
-      tslib: 2.4.1
-    dev: false
+      tslib: 2.6.2
 
-  /camelcase-css@2.0.1:
-    resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==}
-    engines: {node: '>= 6'}
+  camelcase-css@2.0.1: {}
 
-  /camelcase@6.3.0:
-    resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==}
-    engines: {node: '>=10'}
-    dev: false
+  camelcase@6.3.0: {}
 
-  /caniuse-api@3.0.0:
-    resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==}
+  camelcase@7.0.1: {}
+
+  caniuse-api@3.0.0:
     dependencies:
-      browserslist: 4.21.4
-      caniuse-lite: 1.0.30001442
+      browserslist: 4.23.1
+      caniuse-lite: 1.0.30001587
       lodash.memoize: 4.1.2
       lodash.uniq: 4.5.0
-    dev: false
 
-  /caniuse-lite@1.0.30001442:
-    resolution: {integrity: sha512-239m03Pqy0hwxYPYR5JwOIxRJfLTWtle9FV8zosfV5pHg+/51uD4nxcUlM8+mWWGfwKtt8lJNHnD3cWw9VZ6ow==}
+  caniuse-lite@1.0.30001587: {}
 
-  /ccount@1.1.0:
-    resolution: {integrity: sha512-vlNK021QdI7PNeiUh/lKkC/mNHHfV0m/Ad5JoI0TYtlBnJAslM/JIkm/tGC88bkLIwO6OQ5uV6ztS6kVAtCDlg==}
-    dev: false
+  caniuse-lite@1.0.30001632: {}
 
-  /chalk@2.4.2:
-    resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==}
-    engines: {node: '>=4'}
+  ccount@2.0.1: {}
+
+  chalk@2.4.2:
     dependencies:
       ansi-styles: 3.2.1
       escape-string-regexp: 1.0.5
       supports-color: 5.5.0
-    dev: false
 
-  /chalk@4.1.2:
-    resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
-    engines: {node: '>=10'}
+  chalk@4.1.2:
     dependencies:
       ansi-styles: 4.3.0
       supports-color: 7.2.0
-    dev: false
 
-  /character-entities-legacy@1.1.4:
-    resolution: {integrity: sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==}
-    dev: false
+  chalk@5.3.0: {}
 
-  /character-entities@1.2.4:
-    resolution: {integrity: sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==}
-    dev: false
+  char-regex@1.0.2: {}
 
-  /character-reference-invalid@1.1.4:
-    resolution: {integrity: sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==}
-    dev: false
+  character-entities-html4@2.1.0: {}
 
-  /cheerio-select@2.1.0:
-    resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==}
+  character-entities-legacy@3.0.0: {}
+
+  character-entities@2.0.2: {}
+
+  character-reference-invalid@2.0.1: {}
+
+  cheerio-select@2.1.0:
     dependencies:
       boolbase: 1.0.0
       css-select: 5.1.0
@@ -3574,11 +7645,8 @@ packages:
       domelementtype: 2.3.0
       domhandler: 5.0.3
       domutils: 3.0.1
-    dev: false
 
-  /cheerio@1.0.0-rc.12:
-    resolution: {integrity: sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==}
-    engines: {node: '>= 6'}
+  cheerio@1.0.0-rc.12:
     dependencies:
       cheerio-select: 2.1.0
       dom-serializer: 2.0.0
@@ -3587,11 +7655,8 @@ packages:
       htmlparser2: 8.0.1
       parse5: 7.1.2
       parse5-htmlparser2-tree-adapter: 7.0.0
-    dev: false
 
-  /chokidar@3.5.3:
-    resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==}
-    engines: {node: '>= 8.10.0'}
+  chokidar@3.5.3:
     dependencies:
       anymatch: 3.1.3
       braces: 3.0.2
@@ -3601,143 +7666,79 @@ packages:
       normalize-path: 3.0.0
       readdirp: 3.6.0
     optionalDependencies:
-      fsevents: 2.3.2
+      fsevents: 2.3.3
 
-  /chrome-trace-event@1.0.3:
-    resolution: {integrity: sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==}
-    engines: {node: '>=6.0'}
+  chrome-trace-event@1.0.3: {}
 
-  /ci-info@2.0.0:
-    resolution: {integrity: sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==}
-    dev: false
+  ci-info@3.7.1: {}
 
-  /ci-info@3.7.1:
-    resolution: {integrity: sha512-4jYS4MOAaCIStSRwiuxc4B8MYhIe676yO1sYGzARnjXkWpmzZMMYxY6zu8WYWDhSuth5zhrQ1rhNSibyyvv4/w==}
-    engines: {node: '>=8'}
-    dev: false
+  classnames@2.5.1: {}
 
-  /clean-css@5.3.1:
-    resolution: {integrity: sha512-lCr8OHhiWCTw4v8POJovCoh4T7I9U11yVsPjMWWnnMmp9ZowCxyad1Pathle/9HjaDp+fdQKjO9fQydE6RHTZg==}
-    engines: {node: '>= 10.0'}
+  clean-css@5.3.3:
     dependencies:
       source-map: 0.6.1
-    dev: false
 
-  /clean-stack@2.2.0:
-    resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==}
-    engines: {node: '>=6'}
-    dev: false
-
-  /cli-boxes@2.2.1:
-    resolution: {integrity: sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==}
-    engines: {node: '>=6'}
-    dev: false
+  clean-stack@2.2.0: {}
 
-  /cli-boxes@3.0.0:
-    resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==}
-    engines: {node: '>=10'}
-    dev: false
+  cli-boxes@3.0.0: {}
 
-  /cli-table3@0.6.3:
-    resolution: {integrity: sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==}
-    engines: {node: 10.* || >= 12.*}
+  cli-table3@0.6.3:
     dependencies:
       string-width: 4.2.3
     optionalDependencies:
       '@colors/colors': 1.5.0
-    dev: false
 
-  /clone-deep@4.0.1:
-    resolution: {integrity: sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==}
-    engines: {node: '>=6'}
+  clone-deep@4.0.1:
     dependencies:
       is-plain-object: 2.0.4
       kind-of: 6.0.3
       shallow-clone: 3.0.1
 
-  /clone-response@1.0.3:
-    resolution: {integrity: sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==}
-    dependencies:
-      mimic-response: 1.0.1
-    dev: false
+  clsx@1.2.1: {}
 
-  /clsx@1.2.1:
-    resolution: {integrity: sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==}
-    engines: {node: '>=6'}
-    dev: false
+  clsx@2.1.0: {}
 
-  /collapse-white-space@1.0.6:
-    resolution: {integrity: sha512-jEovNnrhMuqyCcjfEJA56v0Xq8SkIoPKDyaHahwo3POf4qcSXqMYuwNcOTzp74vTsR9Tn08z4MxWqAhcekogkQ==}
-    dev: false
+  collapse-white-space@2.1.0: {}
 
-  /color-convert@1.9.3:
-    resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
+  color-convert@1.9.3:
     dependencies:
       color-name: 1.1.3
-    dev: false
 
-  /color-convert@2.0.1:
-    resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
-    engines: {node: '>=7.0.0'}
+  color-convert@2.0.1:
     dependencies:
       color-name: 1.1.4
-    dev: false
 
-  /color-name@1.1.3:
-    resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==}
-    dev: false
+  color-name@1.1.3: {}
 
-  /color-name@1.1.4:
-    resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
+  color-name@1.1.4: {}
 
-  /colord@2.9.3:
-    resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==}
-    dev: false
+  colord@2.9.3: {}
 
-  /colorette@2.0.19:
-    resolution: {integrity: sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==}
-    dev: false
+  colorette@2.0.19: {}
 
-  /combine-promises@1.1.0:
-    resolution: {integrity: sha512-ZI9jvcLDxqwaXEixOhArm3r7ReIivsXkpbyEWyeOhzz1QS0iSgBPnWvEqvIQtYyamGCYA88gFhmUrs9hrrQ0pg==}
-    engines: {node: '>=10'}
-    dev: false
+  combine-promises@1.1.0: {}
 
-  /comma-separated-tokens@1.0.8:
-    resolution: {integrity: sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==}
-    dev: false
+  comma-separated-tokens@2.0.3: {}
 
-  /commander@2.20.3:
-    resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
+  commander@10.0.1: {}
 
-  /commander@5.1.0:
-    resolution: {integrity: sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==}
-    engines: {node: '>= 6'}
+  commander@2.20.3: {}
 
-  /commander@7.2.0:
-    resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==}
-    engines: {node: '>= 10'}
-    dev: false
+  commander@4.1.1: {}
 
-  /commander@8.3.0:
-    resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==}
-    engines: {node: '>= 12'}
-    dev: false
+  commander@5.1.0: {}
 
-  /commondir@1.0.1:
-    resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==}
-    dev: false
+  commander@7.2.0: {}
 
-  /compressible@2.0.18:
-    resolution: {integrity: sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==}
-    engines: {node: '>= 0.6'}
+  commander@8.3.0: {}
+
+  common-path-prefix@3.0.0: {}
+
+  compressible@2.0.18:
     dependencies:
       mime-db: 1.52.0
-    dev: false
 
-  /compression@1.7.4:
-    resolution: {integrity: sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==}
-    engines: {node: '>= 0.8.0'}
+  compression@1.7.4:
     dependencies:
       accepts: 1.3.8
       bytes: 3.0.0
@@ -3748,560 +7749,383 @@ packages:
       vary: 1.1.2
     transitivePeerDependencies:
       - supports-color
-    dev: false
 
-  /concat-map@0.0.1:
-    resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=}
-    dev: false
+  concat-map@0.0.1: {}
 
-  /configstore@5.0.1:
-    resolution: {integrity: sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==}
-    engines: {node: '>=8'}
+  config-chain@1.1.13:
+    dependencies:
+      ini: 1.3.8
+      proto-list: 1.2.4
+
+  configstore@6.0.0:
     dependencies:
-      dot-prop: 5.3.0
+      dot-prop: 6.0.1
       graceful-fs: 4.2.10
-      make-dir: 3.1.0
-      unique-string: 2.0.0
+      unique-string: 3.0.0
       write-file-atomic: 3.0.3
-      xdg-basedir: 4.0.0
-    dev: false
+      xdg-basedir: 5.1.0
 
-  /connect-history-api-fallback@2.0.0:
-    resolution: {integrity: sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==}
-    engines: {node: '>=0.8'}
-    dev: false
+  connect-history-api-fallback@2.0.0: {}
 
-  /consola@2.15.3:
-    resolution: {integrity: sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==}
-    dev: false
+  consola@2.15.3: {}
 
-  /content-disposition@0.5.2:
-    resolution: {integrity: sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==}
-    engines: {node: '>= 0.6'}
-    dev: false
+  content-disposition@0.5.2: {}
 
-  /content-disposition@0.5.4:
-    resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==}
-    engines: {node: '>= 0.6'}
+  content-disposition@0.5.4:
     dependencies:
       safe-buffer: 5.2.1
-    dev: false
 
-  /content-type@1.0.4:
-    resolution: {integrity: sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==}
-    engines: {node: '>= 0.6'}
-    dev: false
+  content-type@1.0.4: {}
 
-  /convert-source-map@1.9.0:
-    resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==}
-    dev: false
+  convert-source-map@2.0.0: {}
 
-  /cookie-signature@1.0.6:
-    resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==}
-    dev: false
+  cookie-signature@1.0.6: {}
 
-  /cookie@0.5.0:
-    resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==}
-    engines: {node: '>= 0.6'}
-    dev: false
+  cookie@0.5.0: {}
 
-  /copy-text-to-clipboard@3.0.1:
-    resolution: {integrity: sha512-rvVsHrpFcL4F2P8ihsoLdFHmd404+CMg71S756oRSeQgqk51U3kicGdnvfkrxva0xXH92SjGS62B0XIJsbh+9Q==}
-    engines: {node: '>=12'}
-    dev: false
+  copy-text-to-clipboard@3.2.0: {}
 
-  /copy-webpack-plugin@11.0.0(webpack@5.75.0):
-    resolution: {integrity: sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==}
-    engines: {node: '>= 14.15.0'}
-    peerDependencies:
-      webpack: ^5.1.0
+  copy-webpack-plugin@11.0.0(webpack@5.90.1):
     dependencies:
-      fast-glob: 3.2.12
+      fast-glob: 3.3.2
       glob-parent: 6.0.2
       globby: 13.1.3
       normalize-path: 3.0.0
       schema-utils: 4.0.0
-      serialize-javascript: 6.0.0
-      webpack: 5.75.0
-    dev: false
-
-  /core-js-compat@3.27.1:
-    resolution: {integrity: sha512-Dg91JFeCDA17FKnneN7oCMz4BkQ4TcffkgHP4OWwp9yx3pi7ubqMDXXSacfNak1PQqjc95skyt+YBLHQJnkJwA==}
-    dependencies:
-      browserslist: 4.21.4
-    dev: false
-
-  /core-js-pure@3.27.1:
-    resolution: {integrity: sha512-BS2NHgwwUppfeoqOXqi08mUqS5FiZpuRuJJpKsaME7kJz0xxuk0xkhDdfMIlP/zLa80krBqss1LtD7f889heAw==}
-    requiresBuild: true
-    dev: false
-
-  /core-js@3.27.1:
-    resolution: {integrity: sha512-GutwJLBChfGCpwwhbYoqfv03LAfmiz7e7D/BNxzeMxwQf10GRSzqiOjx7AmtEk+heiD/JWmBuyBPgFtx0Sg1ww==}
-    requiresBuild: true
-    dev: false
-
-  /core-util-is@1.0.3:
-    resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
-    dev: false
-
-  /cose-base@1.0.3:
-    resolution: {integrity: sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==}
-    dependencies:
-      layout-base: 1.0.2
-    dev: false
-
-  /cose-base@2.2.0:
-    resolution: {integrity: sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==}
-    dependencies:
-      layout-base: 2.0.1
-    dev: false
-
-  /cosmiconfig@6.0.0:
-    resolution: {integrity: sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==}
-    engines: {node: '>=8'}
-    dependencies:
-      '@types/parse-json': 4.0.0
-      import-fresh: 3.3.0
-      parse-json: 5.2.0
-      path-type: 4.0.0
-      yaml: 1.10.2
-    dev: false
-
-  /cosmiconfig@7.1.0:
-    resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==}
-    engines: {node: '>=10'}
-    dependencies:
-      '@types/parse-json': 4.0.0
-      import-fresh: 3.3.0
-      parse-json: 5.2.0
-      path-type: 4.0.0
-      yaml: 1.10.2
-    dev: false
-
-  /cross-fetch@3.1.5:
-    resolution: {integrity: sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==}
-    dependencies:
-      node-fetch: 2.6.7
-    transitivePeerDependencies:
-      - encoding
-    dev: false
-
-  /cross-spawn@7.0.3:
-    resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
-    engines: {node: '>= 8'}
-    dependencies:
-      path-key: 3.1.1
-      shebang-command: 2.0.0
-      which: 2.0.2
-    dev: false
-
-  /crypto-random-string@2.0.0:
-    resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==}
-    engines: {node: '>=8'}
-    dev: false
-
-  /css-declaration-sorter@6.3.1(postcss@8.4.21):
-    resolution: {integrity: sha512-fBffmak0bPAnyqc/HO8C3n2sHrp9wcqQz6ES9koRF2/mLOVAx9zIQ3Y7R29sYCteTPqMCwns4WYQoCX91Xl3+w==}
-    engines: {node: ^10 || ^12 || >=14}
-    peerDependencies:
-      postcss: ^8.0.9
-    dependencies:
-      postcss: 8.4.21
-    dev: false
-
-  /css-loader@6.7.3(webpack@5.75.0):
-    resolution: {integrity: sha512-qhOH1KlBMnZP8FzRO6YCH9UHXQhVMcEGLyNdb7Hv2cpcmJbW0YrddO+tG1ab5nT41KpHIYGsbeHqxB9xPu1pKQ==}
-    engines: {node: '>= 12.13.0'}
-    peerDependencies:
-      webpack: ^5.0.0
-    dependencies:
-      icss-utils: 5.1.0(postcss@8.4.21)
-      postcss: 8.4.21
-      postcss-modules-extract-imports: 3.0.0(postcss@8.4.21)
-      postcss-modules-local-by-default: 4.0.0(postcss@8.4.21)
-      postcss-modules-scope: 3.0.0(postcss@8.4.21)
-      postcss-modules-values: 4.0.0(postcss@8.4.21)
-      postcss-value-parser: 4.2.0
-      semver: 7.3.8
-      webpack: 5.75.0
-    dev: false
-
-  /css-minimizer-webpack-plugin@4.2.2(clean-css@5.3.1)(webpack@5.75.0):
-    resolution: {integrity: sha512-s3Of/4jKfw1Hj9CxEO1E5oXhQAxlayuHO2y/ML+C6I9sQ7FdzfEV6QgMLN3vI+qFsjJGIAFLKtQK7t8BOXAIyA==}
-    engines: {node: '>= 14.15.0'}
-    peerDependencies:
-      '@parcel/css': '*'
-      '@swc/css': '*'
-      clean-css: '*'
-      csso: '*'
-      esbuild: '*'
-      lightningcss: '*'
-      webpack: ^5.0.0
-    peerDependenciesMeta:
-      '@parcel/css':
-        optional: true
-      '@swc/css':
-        optional: true
-      clean-css:
-        optional: true
-      csso:
-        optional: true
-      esbuild:
-        optional: true
-      lightningcss:
-        optional: true
-    dependencies:
-      clean-css: 5.3.1
-      cssnano: 5.1.14(postcss@8.4.21)
-      jest-worker: 29.3.1
-      postcss: 8.4.21
-      schema-utils: 4.0.0
-      serialize-javascript: 6.0.0
-      source-map: 0.6.1
-      webpack: 5.75.0
-    dev: false
+      serialize-javascript: 6.0.2
+      webpack: 5.90.1
 
-  /css-select@4.3.0:
-    resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==}
+  core-js-compat@3.35.1:
     dependencies:
-      boolbase: 1.0.0
-      css-what: 6.1.0
-      domhandler: 4.3.1
-      domutils: 2.8.0
-      nth-check: 2.1.1
-    dev: false
+      browserslist: 4.22.3
 
-  /css-select@5.1.0:
-    resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==}
+  core-js-pure@3.35.1: {}
+
+  core-js@3.35.1: {}
+
+  core-util-is@1.0.3: {}
+
+  cose-base@1.0.3:
     dependencies:
-      boolbase: 1.0.0
-      css-what: 6.1.0
-      domhandler: 5.0.3
-      domutils: 3.0.1
-      nth-check: 2.1.1
-    dev: false
+      layout-base: 1.0.2
 
-  /css-tree@1.1.3:
-    resolution: {integrity: sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==}
-    engines: {node: '>=8.0.0'}
+  cosmiconfig@6.0.0:
     dependencies:
-      mdn-data: 2.0.14
-      source-map: 0.6.1
-    dev: false
+      '@types/parse-json': 4.0.0
+      import-fresh: 3.3.0
+      parse-json: 5.2.0
+      path-type: 4.0.0
+      yaml: 1.10.2
 
-  /css-what@6.1.0:
-    resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==}
-    engines: {node: '>= 6'}
-    dev: false
+  cosmiconfig@8.3.6(typescript@5.3.3):
+    dependencies:
+      import-fresh: 3.3.0
+      js-yaml: 4.1.0
+      parse-json: 5.2.0
+      path-type: 4.0.0
+    optionalDependencies:
+      typescript: 5.3.3
 
-  /cssesc@3.0.0:
-    resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
-    engines: {node: '>=4'}
-    hasBin: true
+  cross-spawn@7.0.3:
+    dependencies:
+      path-key: 3.1.1
+      shebang-command: 2.0.0
+      which: 2.0.2
 
-  /cssnano-preset-advanced@5.3.9(postcss@8.4.21):
-    resolution: {integrity: sha512-njnh4pp1xCsibJcEHnWZb4EEzni0ePMqPuPNyuWT4Z+YeXmsgqNuTPIljXFEXhxGsWs9183JkXgHxc1TcsahIg==}
-    engines: {node: ^10 || ^12 || >=14.0}
-    peerDependencies:
-      postcss: ^8.2.15
+  crypto-random-string@4.0.0:
     dependencies:
-      autoprefixer: 10.4.13(postcss@8.4.21)
-      cssnano-preset-default: 5.2.13(postcss@8.4.21)
-      postcss: 8.4.21
-      postcss-discard-unused: 5.1.0(postcss@8.4.21)
-      postcss-merge-idents: 5.1.1(postcss@8.4.21)
-      postcss-reduce-idents: 5.2.0(postcss@8.4.21)
-      postcss-zindex: 5.1.0(postcss@8.4.21)
-    dev: false
+      type-fest: 1.4.0
 
-  /cssnano-preset-default@5.2.13(postcss@8.4.21):
-    resolution: {integrity: sha512-PX7sQ4Pb+UtOWuz8A1d+Rbi+WimBIxJTRyBdgGp1J75VU0r/HFQeLnMYgHiCAp6AR4rqrc7Y4R+1Rjk3KJz6DQ==}
-    engines: {node: ^10 || ^12 || >=14.0}
-    peerDependencies:
-      postcss: ^8.2.15
+  css-declaration-sorter@7.2.0(postcss@8.4.35):
     dependencies:
-      css-declaration-sorter: 6.3.1(postcss@8.4.21)
-      cssnano-utils: 3.1.0(postcss@8.4.21)
-      postcss: 8.4.21
-      postcss-calc: 8.2.4(postcss@8.4.21)
-      postcss-colormin: 5.3.0(postcss@8.4.21)
-      postcss-convert-values: 5.1.3(postcss@8.4.21)
-      postcss-discard-comments: 5.1.2(postcss@8.4.21)
-      postcss-discard-duplicates: 5.1.0(postcss@8.4.21)
-      postcss-discard-empty: 5.1.1(postcss@8.4.21)
-      postcss-discard-overridden: 5.1.0(postcss@8.4.21)
-      postcss-merge-longhand: 5.1.7(postcss@8.4.21)
-      postcss-merge-rules: 5.1.3(postcss@8.4.21)
-      postcss-minify-font-values: 5.1.0(postcss@8.4.21)
-      postcss-minify-gradients: 5.1.1(postcss@8.4.21)
-      postcss-minify-params: 5.1.4(postcss@8.4.21)
-      postcss-minify-selectors: 5.2.1(postcss@8.4.21)
-      postcss-normalize-charset: 5.1.0(postcss@8.4.21)
-      postcss-normalize-display-values: 5.1.0(postcss@8.4.21)
-      postcss-normalize-positions: 5.1.1(postcss@8.4.21)
-      postcss-normalize-repeat-style: 5.1.1(postcss@8.4.21)
-      postcss-normalize-string: 5.1.0(postcss@8.4.21)
-      postcss-normalize-timing-functions: 5.1.0(postcss@8.4.21)
-      postcss-normalize-unicode: 5.1.1(postcss@8.4.21)
-      postcss-normalize-url: 5.1.0(postcss@8.4.21)
-      postcss-normalize-whitespace: 5.1.1(postcss@8.4.21)
-      postcss-ordered-values: 5.1.3(postcss@8.4.21)
-      postcss-reduce-initial: 5.1.1(postcss@8.4.21)
-      postcss-reduce-transforms: 5.1.0(postcss@8.4.21)
-      postcss-svgo: 5.1.0(postcss@8.4.21)
-      postcss-unique-selectors: 5.1.1(postcss@8.4.21)
-    dev: false
-
-  /cssnano-utils@3.1.0(postcss@8.4.21):
-    resolution: {integrity: sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==}
-    engines: {node: ^10 || ^12 || >=14.0}
-    peerDependencies:
-      postcss: ^8.2.15
+      postcss: 8.4.35
+
+  css-declaration-sorter@7.2.0(postcss@8.4.45):
     dependencies:
-      postcss: 8.4.21
-    dev: false
+      postcss: 8.4.45
 
-  /cssnano@5.1.14(postcss@8.4.21):
-    resolution: {integrity: sha512-Oou7ihiTocbKqi0J1bB+TRJIQX5RMR3JghA8hcWSw9mjBLQ5Y3RWqEDoYG3sRNlAbCIXpqMoZGbq5KDR3vdzgw==}
-    engines: {node: ^10 || ^12 || >=14.0}
-    peerDependencies:
-      postcss: ^8.2.15
+  css-loader@6.10.0(webpack@5.90.1):
     dependencies:
-      cssnano-preset-default: 5.2.13(postcss@8.4.21)
-      lilconfig: 2.0.6
-      postcss: 8.4.21
-      yaml: 1.10.2
-    dev: false
+      icss-utils: 5.1.0(postcss@8.4.35)
+      postcss: 8.4.35
+      postcss-modules-extract-imports: 3.0.0(postcss@8.4.35)
+      postcss-modules-local-by-default: 4.0.4(postcss@8.4.35)
+      postcss-modules-scope: 3.1.1(postcss@8.4.35)
+      postcss-modules-values: 4.0.0(postcss@8.4.35)
+      postcss-value-parser: 4.2.0
+      semver: 7.6.0
+    optionalDependencies:
+      webpack: 5.90.1
 
-  /csso@4.2.0:
-    resolution: {integrity: sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==}
-    engines: {node: '>=8.0.0'}
+  css-minimizer-webpack-plugin@5.0.1(clean-css@5.3.3)(webpack@5.90.1):
     dependencies:
-      css-tree: 1.1.3
-    dev: false
+      '@jridgewell/trace-mapping': 0.3.25
+      cssnano: 6.1.2(postcss@8.4.35)
+      jest-worker: 29.7.0
+      postcss: 8.4.35
+      schema-utils: 4.2.0
+      serialize-javascript: 6.0.2
+      webpack: 5.90.1
+    optionalDependencies:
+      clean-css: 5.3.3
 
-  /csstype@3.1.1:
-    resolution: {integrity: sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==}
+  css-select@4.3.0:
+    dependencies:
+      boolbase: 1.0.0
+      css-what: 6.1.0
+      domhandler: 4.3.1
+      domutils: 2.8.0
+      nth-check: 2.1.1
 
-  /cytoscape-cose-bilkent@4.1.0(cytoscape@3.26.0):
-    resolution: {integrity: sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==}
-    peerDependencies:
-      cytoscape: ^3.2.0
+  css-select@5.1.0:
     dependencies:
-      cose-base: 1.0.3
-      cytoscape: 3.26.0
-    dev: false
+      boolbase: 1.0.0
+      css-what: 6.1.0
+      domhandler: 5.0.3
+      domutils: 3.0.1
+      nth-check: 2.1.1
 
-  /cytoscape-fcose@2.2.0(cytoscape@3.26.0):
-    resolution: {integrity: sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==}
-    peerDependencies:
-      cytoscape: ^3.2.0
+  css-tree@2.2.1:
+    dependencies:
+      mdn-data: 2.0.28
+      source-map-js: 1.2.1
+
+  css-tree@2.3.1:
+    dependencies:
+      mdn-data: 2.0.30
+      source-map-js: 1.2.1
+
+  css-what@6.1.0: {}
+
+  cssesc@3.0.0: {}
+
+  cssnano-preset-advanced@6.1.2(postcss@8.4.45):
+    dependencies:
+      autoprefixer: 10.4.19(postcss@8.4.45)
+      browserslist: 4.23.1
+      cssnano-preset-default: 6.1.2(postcss@8.4.45)
+      postcss: 8.4.45
+      postcss-discard-unused: 6.0.5(postcss@8.4.45)
+      postcss-merge-idents: 6.0.3(postcss@8.4.45)
+      postcss-reduce-idents: 6.0.3(postcss@8.4.45)
+      postcss-zindex: 6.0.2(postcss@8.4.45)
+
+  cssnano-preset-default@6.1.2(postcss@8.4.35):
+    dependencies:
+      browserslist: 4.23.1
+      css-declaration-sorter: 7.2.0(postcss@8.4.35)
+      cssnano-utils: 4.0.2(postcss@8.4.35)
+      postcss: 8.4.35
+      postcss-calc: 9.0.1(postcss@8.4.35)
+      postcss-colormin: 6.1.0(postcss@8.4.35)
+      postcss-convert-values: 6.1.0(postcss@8.4.35)
+      postcss-discard-comments: 6.0.2(postcss@8.4.35)
+      postcss-discard-duplicates: 6.0.3(postcss@8.4.35)
+      postcss-discard-empty: 6.0.3(postcss@8.4.35)
+      postcss-discard-overridden: 6.0.2(postcss@8.4.35)
+      postcss-merge-longhand: 6.0.5(postcss@8.4.35)
+      postcss-merge-rules: 6.1.1(postcss@8.4.35)
+      postcss-minify-font-values: 6.1.0(postcss@8.4.35)
+      postcss-minify-gradients: 6.0.3(postcss@8.4.35)
+      postcss-minify-params: 6.1.0(postcss@8.4.35)
+      postcss-minify-selectors: 6.0.4(postcss@8.4.35)
+      postcss-normalize-charset: 6.0.2(postcss@8.4.35)
+      postcss-normalize-display-values: 6.0.2(postcss@8.4.35)
+      postcss-normalize-positions: 6.0.2(postcss@8.4.35)
+      postcss-normalize-repeat-style: 6.0.2(postcss@8.4.35)
+      postcss-normalize-string: 6.0.2(postcss@8.4.35)
+      postcss-normalize-timing-functions: 6.0.2(postcss@8.4.35)
+      postcss-normalize-unicode: 6.1.0(postcss@8.4.35)
+      postcss-normalize-url: 6.0.2(postcss@8.4.35)
+      postcss-normalize-whitespace: 6.0.2(postcss@8.4.35)
+      postcss-ordered-values: 6.0.2(postcss@8.4.35)
+      postcss-reduce-initial: 6.1.0(postcss@8.4.35)
+      postcss-reduce-transforms: 6.0.2(postcss@8.4.35)
+      postcss-svgo: 6.0.3(postcss@8.4.35)
+      postcss-unique-selectors: 6.0.4(postcss@8.4.35)
+
+  cssnano-preset-default@6.1.2(postcss@8.4.45):
+    dependencies:
+      browserslist: 4.23.1
+      css-declaration-sorter: 7.2.0(postcss@8.4.45)
+      cssnano-utils: 4.0.2(postcss@8.4.45)
+      postcss: 8.4.45
+      postcss-calc: 9.0.1(postcss@8.4.45)
+      postcss-colormin: 6.1.0(postcss@8.4.45)
+      postcss-convert-values: 6.1.0(postcss@8.4.45)
+      postcss-discard-comments: 6.0.2(postcss@8.4.45)
+      postcss-discard-duplicates: 6.0.3(postcss@8.4.45)
+      postcss-discard-empty: 6.0.3(postcss@8.4.45)
+      postcss-discard-overridden: 6.0.2(postcss@8.4.45)
+      postcss-merge-longhand: 6.0.5(postcss@8.4.45)
+      postcss-merge-rules: 6.1.1(postcss@8.4.45)
+      postcss-minify-font-values: 6.1.0(postcss@8.4.45)
+      postcss-minify-gradients: 6.0.3(postcss@8.4.45)
+      postcss-minify-params: 6.1.0(postcss@8.4.45)
+      postcss-minify-selectors: 6.0.4(postcss@8.4.45)
+      postcss-normalize-charset: 6.0.2(postcss@8.4.45)
+      postcss-normalize-display-values: 6.0.2(postcss@8.4.45)
+      postcss-normalize-positions: 6.0.2(postcss@8.4.45)
+      postcss-normalize-repeat-style: 6.0.2(postcss@8.4.45)
+      postcss-normalize-string: 6.0.2(postcss@8.4.45)
+      postcss-normalize-timing-functions: 6.0.2(postcss@8.4.45)
+      postcss-normalize-unicode: 6.1.0(postcss@8.4.45)
+      postcss-normalize-url: 6.0.2(postcss@8.4.45)
+      postcss-normalize-whitespace: 6.0.2(postcss@8.4.45)
+      postcss-ordered-values: 6.0.2(postcss@8.4.45)
+      postcss-reduce-initial: 6.1.0(postcss@8.4.45)
+      postcss-reduce-transforms: 6.0.2(postcss@8.4.45)
+      postcss-svgo: 6.0.3(postcss@8.4.45)
+      postcss-unique-selectors: 6.0.4(postcss@8.4.45)
+
+  cssnano-utils@4.0.2(postcss@8.4.35):
+    dependencies:
+      postcss: 8.4.35
+
+  cssnano-utils@4.0.2(postcss@8.4.45):
+    dependencies:
+      postcss: 8.4.45
+
+  cssnano@6.1.2(postcss@8.4.35):
+    dependencies:
+      cssnano-preset-default: 6.1.2(postcss@8.4.35)
+      lilconfig: 3.1.2
+      postcss: 8.4.35
+
+  csso@5.0.5:
+    dependencies:
+      css-tree: 2.2.1
+
+  csstype@3.1.1: {}
+
+  cytoscape-cose-bilkent@4.1.0(cytoscape@3.28.1):
     dependencies:
-      cose-base: 2.2.0
-      cytoscape: 3.26.0
-    dev: false
+      cose-base: 1.0.3
+      cytoscape: 3.28.1
 
-  /cytoscape@3.26.0:
-    resolution: {integrity: sha512-IV+crL+KBcrCnVVUCZW+zRRRFUZQcrtdOPXki+o4CFUWLdAEYvuZLcBSJC9EBK++suamERKzeY7roq2hdovV3w==}
-    engines: {node: '>=0.10'}
+  cytoscape@3.28.1:
     dependencies:
       heap: 0.2.7
       lodash: 4.17.21
-    dev: false
 
-  /d3-array@3.2.4:
-    resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==}
-    engines: {node: '>=12'}
+  d3-array@2.12.1:
+    dependencies:
+      internmap: 1.0.1
+
+  d3-array@3.2.4:
     dependencies:
       internmap: 2.0.3
-    dev: false
 
-  /d3-axis@3.0.0:
-    resolution: {integrity: sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==}
-    engines: {node: '>=12'}
-    dev: false
+  d3-axis@3.0.0: {}
 
-  /d3-brush@3.0.0:
-    resolution: {integrity: sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==}
-    engines: {node: '>=12'}
+  d3-brush@3.0.0:
     dependencies:
       d3-dispatch: 3.0.1
       d3-drag: 3.0.0
       d3-interpolate: 3.0.1
       d3-selection: 3.0.0
       d3-transition: 3.0.1(d3-selection@3.0.0)
-    dev: false
 
-  /d3-chord@3.0.1:
-    resolution: {integrity: sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==}
-    engines: {node: '>=12'}
+  d3-chord@3.0.1:
     dependencies:
       d3-path: 3.1.0
-    dev: false
 
-  /d3-color@3.1.0:
-    resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==}
-    engines: {node: '>=12'}
-    dev: false
+  d3-color@3.1.0: {}
 
-  /d3-contour@4.0.2:
-    resolution: {integrity: sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==}
-    engines: {node: '>=12'}
+  d3-contour@4.0.2:
     dependencies:
       d3-array: 3.2.4
-    dev: false
 
-  /d3-delaunay@6.0.4:
-    resolution: {integrity: sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==}
-    engines: {node: '>=12'}
+  d3-delaunay@6.0.4:
     dependencies:
       delaunator: 5.0.0
-    dev: false
 
-  /d3-dispatch@3.0.1:
-    resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==}
-    engines: {node: '>=12'}
-    dev: false
+  d3-dispatch@3.0.1: {}
 
-  /d3-drag@3.0.0:
-    resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==}
-    engines: {node: '>=12'}
+  d3-drag@3.0.0:
     dependencies:
       d3-dispatch: 3.0.1
       d3-selection: 3.0.0
-    dev: false
 
-  /d3-dsv@3.0.1:
-    resolution: {integrity: sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==}
-    engines: {node: '>=12'}
-    hasBin: true
+  d3-dsv@3.0.1:
     dependencies:
       commander: 7.2.0
       iconv-lite: 0.6.3
       rw: 1.3.3
-    dev: false
 
-  /d3-ease@3.0.1:
-    resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==}
-    engines: {node: '>=12'}
-    dev: false
+  d3-ease@3.0.1: {}
 
-  /d3-fetch@3.0.1:
-    resolution: {integrity: sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==}
-    engines: {node: '>=12'}
+  d3-fetch@3.0.1:
     dependencies:
       d3-dsv: 3.0.1
-    dev: false
 
-  /d3-force@3.0.0:
-    resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==}
-    engines: {node: '>=12'}
+  d3-force@3.0.0:
     dependencies:
       d3-dispatch: 3.0.1
       d3-quadtree: 3.0.1
       d3-timer: 3.0.1
-    dev: false
 
-  /d3-format@3.1.0:
-    resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==}
-    engines: {node: '>=12'}
-    dev: false
+  d3-format@3.1.0: {}
 
-  /d3-geo@3.1.0:
-    resolution: {integrity: sha512-JEo5HxXDdDYXCaWdwLRt79y7giK8SbhZJbFWXqbRTolCHFI5jRqteLzCsq51NKbUoX0PjBVSohxrx+NoOUujYA==}
-    engines: {node: '>=12'}
+  d3-geo@3.1.0:
     dependencies:
       d3-array: 3.2.4
-    dev: false
 
-  /d3-hierarchy@3.1.2:
-    resolution: {integrity: sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==}
-    engines: {node: '>=12'}
-    dev: false
+  d3-hierarchy@3.1.2: {}
 
-  /d3-interpolate@3.0.1:
-    resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==}
-    engines: {node: '>=12'}
+  d3-interpolate@3.0.1:
     dependencies:
       d3-color: 3.1.0
-    dev: false
 
-  /d3-path@3.1.0:
-    resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==}
-    engines: {node: '>=12'}
-    dev: false
+  d3-path@1.0.9: {}
 
-  /d3-polygon@3.0.1:
-    resolution: {integrity: sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==}
-    engines: {node: '>=12'}
-    dev: false
+  d3-path@3.1.0: {}
 
-  /d3-quadtree@3.0.1:
-    resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==}
-    engines: {node: '>=12'}
-    dev: false
+  d3-polygon@3.0.1: {}
 
-  /d3-random@3.0.1:
-    resolution: {integrity: sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==}
-    engines: {node: '>=12'}
-    dev: false
+  d3-quadtree@3.0.1: {}
 
-  /d3-scale-chromatic@3.0.0:
-    resolution: {integrity: sha512-Lx9thtxAKrO2Pq6OO2Ua474opeziKr279P/TKZsMAhYyNDD3EnCffdbgeSYN5O7m2ByQsxtuP2CSDczNUIZ22g==}
-    engines: {node: '>=12'}
+  d3-random@3.0.1: {}
+
+  d3-sankey@0.12.3:
+    dependencies:
+      d3-array: 2.12.1
+      d3-shape: 1.3.7
+
+  d3-scale-chromatic@3.0.0:
     dependencies:
       d3-color: 3.1.0
       d3-interpolate: 3.0.1
-    dev: false
 
-  /d3-scale@4.0.2:
-    resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==}
-    engines: {node: '>=12'}
+  d3-scale@4.0.2:
     dependencies:
       d3-array: 3.2.4
       d3-format: 3.1.0
       d3-interpolate: 3.0.1
       d3-time: 3.1.0
       d3-time-format: 4.1.0
-    dev: false
 
-  /d3-selection@3.0.0:
-    resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==}
-    engines: {node: '>=12'}
-    dev: false
+  d3-selection@3.0.0: {}
 
-  /d3-shape@3.2.0:
-    resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==}
-    engines: {node: '>=12'}
+  d3-shape@1.3.7:
+    dependencies:
+      d3-path: 1.0.9
+
+  d3-shape@3.2.0:
     dependencies:
       d3-path: 3.1.0
-    dev: false
 
-  /d3-time-format@4.1.0:
-    resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==}
-    engines: {node: '>=12'}
+  d3-time-format@4.1.0:
     dependencies:
       d3-time: 3.1.0
-    dev: false
 
-  /d3-time@3.1.0:
-    resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==}
-    engines: {node: '>=12'}
+  d3-time@3.1.0:
     dependencies:
       d3-array: 3.2.4
-    dev: false
 
-  /d3-timer@3.0.1:
-    resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==}
-    engines: {node: '>=12'}
-    dev: false
+  d3-timer@3.0.1: {}
 
-  /d3-transition@3.0.1(d3-selection@3.0.0):
-    resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==}
-    engines: {node: '>=12'}
-    peerDependencies:
-      d3-selection: 2 - 3
+  d3-transition@3.0.1(d3-selection@3.0.0):
     dependencies:
       d3-color: 3.1.0
       d3-dispatch: 3.0.1
@@ -4309,22 +8133,16 @@ packages:
       d3-interpolate: 3.0.1
       d3-selection: 3.0.0
       d3-timer: 3.0.1
-    dev: false
 
-  /d3-zoom@3.0.0:
-    resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==}
-    engines: {node: '>=12'}
+  d3-zoom@3.0.0:
     dependencies:
       d3-dispatch: 3.0.1
       d3-drag: 3.0.0
       d3-interpolate: 3.0.1
       d3-selection: 3.0.0
       d3-transition: 3.0.1(d3-selection@3.0.0)
-    dev: false
 
-  /d3@7.8.5:
-    resolution: {integrity: sha512-JgoahDG51ncUfJu6wX/1vWQEqOflgXyl4MaHqlcSruTez7yhaRKR9i8VjjcQGeS2en/jnFivXuaIMnseMMt0XA==}
-    engines: {node: '>=12'}
+  d3@7.8.5:
     dependencies:
       d3-array: 3.2.4
       d3-axis: 3.0.0
@@ -4356,89 +8174,52 @@ packages:
       d3-timer: 3.0.1
       d3-transition: 3.0.1(d3-selection@3.0.0)
       d3-zoom: 3.0.0
-    dev: false
 
-  /dagre-d3-es@7.0.9:
-    resolution: {integrity: sha512-rYR4QfVmy+sR44IBDvVtcAmOReGBvRCWDpO2QjYwqgh9yijw6eSHBqaPG/LIOEy7aBsniLvtMW6pg19qJhq60w==}
+  dagre-d3-es@7.0.10:
     dependencies:
       d3: 7.8.5
       lodash-es: 4.17.21
-    dev: false
 
-  /dayjs@1.11.10:
-    resolution: {integrity: sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==}
-    dev: false
+  dayjs@1.11.10: {}
 
-  /debug@2.6.9:
-    resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
-    peerDependencies:
-      supports-color: '*'
-    peerDependenciesMeta:
-      supports-color:
-        optional: true
+  debounce@1.2.1: {}
+
+  debug@2.6.9:
     dependencies:
       ms: 2.0.0
-    dev: false
 
-  /debug@4.3.4:
-    resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
-    engines: {node: '>=6.0'}
-    peerDependencies:
-      supports-color: '*'
-    peerDependenciesMeta:
-      supports-color:
-        optional: true
+  debug@4.3.4:
     dependencies:
       ms: 2.1.2
-    dev: false
 
-  /decompress-response@3.3.0:
-    resolution: {integrity: sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA==}
-    engines: {node: '>=4'}
+  decode-named-character-reference@1.0.2:
     dependencies:
-      mimic-response: 1.0.1
-    dev: false
+      character-entities: 2.0.2
 
-  /deep-extend@0.6.0:
-    resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==}
-    engines: {node: '>=4.0.0'}
-    dev: false
+  decompress-response@6.0.0:
+    dependencies:
+      mimic-response: 3.1.0
 
-  /deepmerge@4.2.2:
-    resolution: {integrity: sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==}
-    engines: {node: '>=0.10.0'}
-    dev: false
+  deep-extend@0.6.0: {}
 
-  /default-gateway@6.0.3:
-    resolution: {integrity: sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==}
-    engines: {node: '>= 10'}
+  deepmerge@4.2.2: {}
+
+  deepmerge@4.3.1: {}
+
+  default-gateway@6.0.3:
     dependencies:
       execa: 5.1.1
-    dev: false
 
-  /defer-to-connect@1.1.3:
-    resolution: {integrity: sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==}
-    dev: false
+  defer-to-connect@2.0.1: {}
 
-  /define-lazy-prop@2.0.0:
-    resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==}
-    engines: {node: '>=8'}
-    dev: false
+  define-lazy-prop@2.0.0: {}
 
-  /define-properties@1.1.4:
-    resolution: {integrity: sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==}
-    engines: {node: '>= 0.4'}
+  define-properties@1.1.4:
     dependencies:
       has-property-descriptors: 1.0.0
       object-keys: 1.1.1
-    dev: false
-
-  /defined@1.0.1:
-    resolution: {integrity: sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==}
 
-  /del@6.1.1:
-    resolution: {integrity: sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg==}
-    engines: {node: '>=10'}
+  del@6.1.1:
     dependencies:
       globby: 11.1.0
       graceful-fs: 4.2.10
@@ -4448,333 +8229,223 @@ packages:
       p-map: 4.0.0
       rimraf: 3.0.2
       slash: 3.0.0
-    dev: false
 
-  /delaunator@5.0.0:
-    resolution: {integrity: sha512-AyLvtyJdbv/U1GkiS6gUUzclRoAY4Gs75qkMygJJhU75LW4DNuSF2RMzpxs9jw9Oz1BobHjTdkG3zdP55VxAqw==}
+  delaunator@5.0.0:
     dependencies:
       robust-predicates: 3.0.2
-    dev: false
 
-  /depd@1.1.2:
-    resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==}
-    engines: {node: '>= 0.6'}
-    dev: false
+  depd@1.1.2: {}
 
-  /depd@2.0.0:
-    resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
-    engines: {node: '>= 0.8'}
-    dev: false
+  depd@2.0.0: {}
 
-  /destroy@1.2.0:
-    resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==}
-    engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
-    dev: false
+  dequal@2.0.3: {}
 
-  /detab@2.0.4:
-    resolution: {integrity: sha512-8zdsQA5bIkoRECvCrNKPla84lyoR7DSAyf7p0YgXzBO9PDJx8KntPUay7NS6yp+KdxdVtiE5SpHKtbp2ZQyA9g==}
-    dependencies:
-      repeat-string: 1.6.1
-    dev: false
+  destroy@1.2.0: {}
 
-  /detect-node@2.1.0:
-    resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==}
-    dev: false
+  detect-node@2.1.0: {}
 
-  /detect-port-alt@1.1.6:
-    resolution: {integrity: sha512-5tQykt+LqfJFBEYaDITx7S7cR7mJ/zQmLXZ2qt5w04ainYZw6tBf9dBunMjVeVOdYVRUzUOE4HkY5J7+uttb5Q==}
-    engines: {node: '>= 4.2.1'}
-    hasBin: true
+  detect-port-alt@1.1.6:
     dependencies:
       address: 1.2.2
       debug: 2.6.9
     transitivePeerDependencies:
       - supports-color
-    dev: false
 
-  /detect-port@1.5.1:
-    resolution: {integrity: sha512-aBzdj76lueB6uUst5iAs7+0H/oOjqI5D16XUWxlWMIMROhcM0rfsNVk93zTngq1dDNpoXRr++Sus7ETAExppAQ==}
-    hasBin: true
+  detect-port@1.5.1:
     dependencies:
       address: 1.2.2
       debug: 4.3.4
     transitivePeerDependencies:
       - supports-color
-    dev: false
 
-  /detective@5.2.1:
-    resolution: {integrity: sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw==}
-    engines: {node: '>=0.8.0'}
-    hasBin: true
+  devlop@1.1.0:
     dependencies:
-      acorn-node: 1.8.2
-      defined: 1.0.1
-      minimist: 1.2.7
+      dequal: 2.0.3
 
-  /didyoumean@1.2.2:
-    resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
+  didyoumean@1.2.2: {}
 
-  /dir-glob@3.0.1:
-    resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
-    engines: {node: '>=8'}
+  diff@5.2.0: {}
+
+  dir-glob@3.0.1:
     dependencies:
       path-type: 4.0.0
-    dev: false
 
-  /dlv@1.1.3:
-    resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==}
+  dlv@1.1.3: {}
 
-  /dns-equal@1.0.0:
-    resolution: {integrity: sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==}
-    dev: false
+  dns-equal@1.0.0: {}
 
-  /dns-packet@5.4.0:
-    resolution: {integrity: sha512-EgqGeaBB8hLiHLZtp/IbaDQTL8pZ0+IvwzSHA6d7VyMDM+B9hgddEMa9xjK5oYnw0ci0JQ6g2XCD7/f6cafU6g==}
-    engines: {node: '>=6'}
+  dns-packet@5.4.0:
     dependencies:
       '@leichtgewicht/ip-codec': 2.0.4
-    dev: false
-
-  /docusaurus-plugin-image-zoom@0.1.1:
-    resolution: {integrity: sha512-cJXo5TKh9OR1gE4B5iS5ovLWYYDFwatqRm00iXFPOaShZG99l5tgkDKgbQPAwSL9wg4I+wz3aMwkOtDhMIpKDQ==}
-    dependencies:
-      medium-zoom: 1.0.8
-    dev: false
 
-  /dom-converter@0.2.0:
-    resolution: {integrity: sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==}
+  dom-converter@0.2.0:
     dependencies:
       utila: 0.4.0
-    dev: false
 
-  /dom-serializer@1.4.1:
-    resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==}
+  dom-serializer@1.4.1:
     dependencies:
       domelementtype: 2.3.0
       domhandler: 4.3.1
       entities: 2.2.0
-    dev: false
 
-  /dom-serializer@2.0.0:
-    resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==}
+  dom-serializer@2.0.0:
     dependencies:
       domelementtype: 2.3.0
       domhandler: 5.0.3
       entities: 4.4.0
-    dev: false
 
-  /domelementtype@2.3.0:
-    resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==}
-    dev: false
+  domelementtype@2.3.0: {}
 
-  /domhandler@4.3.1:
-    resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==}
-    engines: {node: '>= 4'}
+  domhandler@4.3.1:
     dependencies:
       domelementtype: 2.3.0
-    dev: false
 
-  /domhandler@5.0.3:
-    resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==}
-    engines: {node: '>= 4'}
+  domhandler@5.0.3:
     dependencies:
       domelementtype: 2.3.0
-    dev: false
 
-  /dompurify@2.4.3:
-    resolution: {integrity: sha512-q6QaLcakcRjebxjg8/+NP+h0rPfatOgOzc46Fst9VAA3jF2ApfKBNKMzdP4DYTqtUMXSCd5pRS/8Po/OmoCHZQ==}
-    dev: false
+  dompurify@3.0.8: {}
 
-  /domutils@2.8.0:
-    resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==}
+  domutils@2.8.0:
     dependencies:
       dom-serializer: 1.4.1
       domelementtype: 2.3.0
       domhandler: 4.3.1
-    dev: false
 
-  /domutils@3.0.1:
-    resolution: {integrity: sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==}
+  domutils@3.0.1:
     dependencies:
       dom-serializer: 2.0.0
       domelementtype: 2.3.0
       domhandler: 5.0.3
-    dev: false
 
-  /dot-case@3.0.4:
-    resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==}
+  dot-case@3.0.4:
     dependencies:
       no-case: 3.0.4
-      tslib: 2.4.1
-    dev: false
+      tslib: 2.6.2
 
-  /dot-prop@5.3.0:
-    resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==}
-    engines: {node: '>=8'}
+  dot-prop@6.0.1:
     dependencies:
       is-obj: 2.0.0
-    dev: false
 
-  /duplexer3@0.1.5:
-    resolution: {integrity: sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==}
-    dev: false
+  duplexer@0.1.2: {}
 
-  /duplexer@0.1.2:
-    resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==}
-    dev: false
+  eastasianwidth@0.2.0: {}
 
-  /eastasianwidth@0.2.0:
-    resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
-    dev: false
+  ee-first@1.1.1: {}
 
-  /ee-first@1.1.1:
-    resolution: {integrity: sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=}
-    dev: false
+  electron-to-chromium@1.4.284: {}
 
-  /electron-to-chromium@1.4.284:
-    resolution: {integrity: sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==}
+  electron-to-chromium@1.4.667: {}
 
-  /elkjs@0.8.2:
-    resolution: {integrity: sha512-L6uRgvZTH+4OF5NE/MBbzQx/WYpru1xCBE9respNj6qznEewGUIfhzmm7horWWxbNO2M0WckQypGctR8lH79xQ==}
-    dev: false
+  electron-to-chromium@1.4.796: {}
 
-  /emoji-regex@8.0.0:
-    resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
-    dev: false
+  elkjs@0.9.1: {}
 
-  /emoji-regex@9.2.2:
-    resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
-    dev: false
+  emoji-regex@8.0.0: {}
 
-  /emojis-list@3.0.0:
-    resolution: {integrity: sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==}
-    engines: {node: '>= 4'}
-    dev: false
+  emoji-regex@9.2.2: {}
 
-  /emoticon@3.2.0:
-    resolution: {integrity: sha512-SNujglcLTTg+lDAcApPNgEdudaqQFiAbJCqzjNxJkvN9vAwCGi0uu8IUVvx+f16h+V44KCY6Y2yboroc9pilHg==}
-    dev: false
+  emojilib@2.4.0: {}
 
-  /encodeurl@1.0.2:
-    resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==}
-    engines: {node: '>= 0.8'}
-    dev: false
+  emojis-list@3.0.0: {}
 
-  /end-of-stream@1.4.4:
-    resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==}
-    dependencies:
-      once: 1.4.0
-    dev: false
+  emoticon@4.0.1: {}
 
-  /enhanced-resolve@5.12.0:
-    resolution: {integrity: sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ==}
-    engines: {node: '>=10.13.0'}
+  encodeurl@1.0.2: {}
+
+  enhanced-resolve@5.15.0:
     dependencies:
       graceful-fs: 4.2.10
       tapable: 2.2.1
 
-  /entities@2.2.0:
-    resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==}
-    dev: false
+  entities@2.2.0: {}
 
-  /entities@4.4.0:
-    resolution: {integrity: sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==}
-    engines: {node: '>=0.12'}
-    dev: false
+  entities@4.4.0: {}
 
-  /error-ex@1.3.2:
-    resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==}
+  error-ex@1.3.2:
     dependencies:
       is-arrayish: 0.2.1
-    dev: false
 
-  /es-module-lexer@0.9.3:
-    resolution: {integrity: sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==}
+  es-module-lexer@1.4.1: {}
 
-  /escalade@3.1.1:
-    resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==}
-    engines: {node: '>=6'}
+  escalade@3.1.1: {}
 
-  /escape-goat@2.1.1:
-    resolution: {integrity: sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==}
-    engines: {node: '>=8'}
-    dev: false
+  escalade@3.1.2: {}
 
-  /escape-html@1.0.3:
-    resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
-    dev: false
+  escape-goat@4.0.0: {}
 
-  /escape-string-regexp@1.0.5:
-    resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==}
-    engines: {node: '>=0.8.0'}
-    dev: false
+  escape-html@1.0.3: {}
 
-  /escape-string-regexp@4.0.0:
-    resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
-    engines: {node: '>=10'}
-    dev: false
+  escape-string-regexp@1.0.5: {}
 
-  /eslint-scope@5.1.1:
-    resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==}
-    engines: {node: '>=8.0.0'}
+  escape-string-regexp@4.0.0: {}
+
+  escape-string-regexp@5.0.0: {}
+
+  eslint-scope@5.1.1:
     dependencies:
       esrecurse: 4.3.0
       estraverse: 4.3.0
 
-  /esprima@4.0.1:
-    resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==}
-    engines: {node: '>=4'}
-    hasBin: true
-    dev: false
+  esprima@4.0.1: {}
+
+  esrecurse@4.3.0:
+    dependencies:
+      estraverse: 5.3.0
+
+  estraverse@4.3.0: {}
+
+  estraverse@5.3.0: {}
+
+  estree-util-attach-comments@3.0.0:
+    dependencies:
+      '@types/estree': 1.0.5
+
+  estree-util-build-jsx@3.0.1:
+    dependencies:
+      '@types/estree-jsx': 1.0.4
+      devlop: 1.1.0
+      estree-util-is-identifier-name: 3.0.0
+      estree-walker: 3.0.3
+
+  estree-util-is-identifier-name@3.0.0: {}
 
-  /esrecurse@4.3.0:
-    resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==}
-    engines: {node: '>=4.0'}
+  estree-util-to-js@2.0.0:
     dependencies:
-      estraverse: 5.3.0
+      '@types/estree-jsx': 1.0.4
+      astring: 1.8.6
+      source-map: 0.7.4
 
-  /estraverse@4.3.0:
-    resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==}
-    engines: {node: '>=4.0'}
+  estree-util-value-to-estree@3.0.1:
+    dependencies:
+      '@types/estree': 1.0.5
+      is-plain-obj: 4.1.0
 
-  /estraverse@5.3.0:
-    resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
-    engines: {node: '>=4.0'}
+  estree-util-visit@2.0.0:
+    dependencies:
+      '@types/estree-jsx': 1.0.4
+      '@types/unist': 3.0.2
 
-  /esutils@2.0.3:
-    resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
-    engines: {node: '>=0.10.0'}
-    dev: false
+  estree-walker@3.0.3:
+    dependencies:
+      '@types/estree': 1.0.5
 
-  /eta@2.0.0:
-    resolution: {integrity: sha512-NqE7S2VmVwgMS8yBxsH4VgNQjNjLq1gfGU0u9I6Cjh468nPRMoDfGdK9n1p/3Dvsw3ebklDkZsFAnKJ9sefjBA==}
-    engines: {node: '>=6.0.0'}
-    dev: false
+  esutils@2.0.3: {}
 
-  /etag@1.8.1:
-    resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
-    engines: {node: '>= 0.6'}
-    dev: false
+  eta@2.2.0: {}
 
-  /eval@0.1.8:
-    resolution: {integrity: sha512-EzV94NYKoO09GLXGjXj9JIlXijVck4ONSr5wiCWDvhsvj5jxSrzTmRU/9C1DyB6uToszLs8aifA6NQ7lEQdvFw==}
-    engines: {node: '>= 0.8'}
+  etag@1.8.1: {}
+
+  eval@0.1.8:
     dependencies:
-      '@types/node': 18.11.18
+      '@types/node': 20.11.17
       require-like: 0.1.2
-    dev: false
 
-  /eventemitter3@4.0.7:
-    resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==}
-    dev: false
+  eventemitter3@4.0.7: {}
 
-  /events@3.3.0:
-    resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
-    engines: {node: '>=0.8.x'}
+  events@3.3.0: {}
 
-  /execa@5.1.1:
-    resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==}
-    engines: {node: '>=10'}
+  execa@5.1.1:
     dependencies:
       cross-spawn: 7.0.3
       get-stream: 6.0.1
@@ -4785,11 +8456,8 @@ packages:
       onetime: 5.1.2
       signal-exit: 3.0.7
       strip-final-newline: 2.0.0
-    dev: false
 
-  /express@4.18.2:
-    resolution: {integrity: sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==}
-    engines: {node: '>= 0.10.0'}
+  express@4.18.2:
     dependencies:
       accepts: 1.3.8
       array-flatten: 1.1.1
@@ -4824,25 +8492,16 @@ packages:
       vary: 1.1.2
     transitivePeerDependencies:
       - supports-color
-    dev: false
 
-  /extend-shallow@2.0.1:
-    resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==}
-    engines: {node: '>=0.10.0'}
+  extend-shallow@2.0.1:
     dependencies:
       is-extendable: 0.1.1
-    dev: false
 
-  /extend@3.0.2:
-    resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==}
-    dev: false
+  extend@3.0.2: {}
 
-  /fast-deep-equal@3.1.3:
-    resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
+  fast-deep-equal@3.1.3: {}
 
-  /fast-glob@3.2.12:
-    resolution: {integrity: sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==}
-    engines: {node: '>=8.6.0'}
+  fast-glob@3.3.2:
     dependencies:
       '@nodelib/fs.stat': 2.0.5
       '@nodelib/fs.walk': 1.2.8
@@ -4850,85 +8509,45 @@ packages:
       merge2: 1.4.1
       micromatch: 4.0.5
 
-  /fast-json-stable-stringify@2.1.0:
-    resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
+  fast-json-stable-stringify@2.1.0: {}
 
-  /fast-url-parser@1.1.3:
-    resolution: {integrity: sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ==}
+  fast-url-parser@1.1.3:
     dependencies:
       punycode: 1.4.1
-    dev: false
 
-  /fastq@1.15.0:
-    resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==}
+  fastq@1.17.1:
     dependencies:
       reusify: 1.0.4
 
-  /faye-websocket@0.11.4:
-    resolution: {integrity: sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==}
-    engines: {node: '>=0.8.0'}
-    dependencies:
-      websocket-driver: 0.7.4
-    dev: false
-
-  /fbemitter@3.0.0:
-    resolution: {integrity: sha512-KWKaceCwKQU0+HPoop6gn4eOHk50bBv/VxjJtGMfwmJt3D29JpN4H4eisCtIPA+a8GVBam+ldMMpMjJUvpDyHw==}
+  fault@2.0.1:
     dependencies:
-      fbjs: 3.0.4
-    transitivePeerDependencies:
-      - encoding
-    dev: false
+      format: 0.2.2
 
-  /fbjs-css-vars@1.0.2:
-    resolution: {integrity: sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ==}
-    dev: false
-
-  /fbjs@3.0.4:
-    resolution: {integrity: sha512-ucV0tDODnGV3JCnnkmoszb5lf4bNpzjv80K41wd4k798Etq+UYD0y0TIfalLjZoKgjive6/adkRnszwapiDgBQ==}
+  faye-websocket@0.11.4:
     dependencies:
-      cross-fetch: 3.1.5
-      fbjs-css-vars: 1.0.2
-      loose-envify: 1.4.0
-      object-assign: 4.1.1
-      promise: 7.3.1
-      setimmediate: 1.0.5
-      ua-parser-js: 0.7.32
-    transitivePeerDependencies:
-      - encoding
-    dev: false
+      websocket-driver: 0.7.4
 
-  /feed@4.2.2:
-    resolution: {integrity: sha512-u5/sxGfiMfZNtJ3OvQpXcvotFpYkL0n9u9mM2vkui2nGo8b4wvDkJ8gAkYqbA8QpGyFCv3RK0Z+Iv+9veCS9bQ==}
-    engines: {node: '>=0.4.0'}
+  feed@4.2.2:
     dependencies:
       xml-js: 1.6.11
-    dev: false
 
-  /file-loader@6.2.0(webpack@5.75.0):
-    resolution: {integrity: sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==}
-    engines: {node: '>= 10.13.0'}
-    peerDependencies:
-      webpack: ^4.0.0 || ^5.0.0
+  file-loader@6.2.0(webpack@5.90.1):
     dependencies:
       loader-utils: 2.0.4
-      schema-utils: 3.1.1
-      webpack: 5.75.0
-    dev: false
+      schema-utils: 3.3.0
+      webpack: 5.90.1
 
-  /filesize@8.0.7:
-    resolution: {integrity: sha512-pjmC+bkIF8XI7fWaH8KxHcZL3DPybs1roSKP4rKDvy20tAWwIObE4+JIseG2byfGKhud5ZnM4YSGKBz7Sh0ndQ==}
-    engines: {node: '>= 0.4.0'}
-    dev: false
+  filesize@8.0.7: {}
 
-  /fill-range@7.0.1:
-    resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
-    engines: {node: '>=8'}
+  fill-range@7.0.1:
     dependencies:
       to-regex-range: 5.0.1
 
-  /finalhandler@1.2.0:
-    resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==}
-    engines: {node: '>= 0.8'}
+  fill-range@7.1.1:
+    dependencies:
+      to-regex-range: 5.0.1
+
+  finalhandler@1.2.0:
     dependencies:
       debug: 2.6.9
       encodeurl: 1.0.2
@@ -4939,77 +8558,38 @@ packages:
       unpipe: 1.0.0
     transitivePeerDependencies:
       - supports-color
-    dev: false
 
-  /find-cache-dir@3.3.2:
-    resolution: {integrity: sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==}
-    engines: {node: '>=8'}
+  find-cache-dir@4.0.0:
     dependencies:
-      commondir: 1.0.1
-      make-dir: 3.1.0
-      pkg-dir: 4.2.0
-    dev: false
+      common-path-prefix: 3.0.0
+      pkg-dir: 7.0.0
 
-  /find-up@3.0.0:
-    resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==}
-    engines: {node: '>=6'}
+  find-up@3.0.0:
     dependencies:
       locate-path: 3.0.0
-    dev: false
-
-  /find-up@4.1.0:
-    resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
-    engines: {node: '>=8'}
-    dependencies:
-      locate-path: 5.0.0
-      path-exists: 4.0.0
-    dev: false
 
-  /find-up@5.0.0:
-    resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
-    engines: {node: '>=10'}
+  find-up@5.0.0:
     dependencies:
       locate-path: 6.0.0
       path-exists: 4.0.0
-    dev: false
 
-  /flux@4.0.3(react@17.0.2):
-    resolution: {integrity: sha512-yKAbrp7JhZhj6uiT1FTuVMlIAT1J4jqEyBpFApi1kxpGZCvacMVc/t1pMQyotqHhAgvoE3bNvAykhCo2CLjnYw==}
-    peerDependencies:
-      react: ^15.0.2 || ^16.0.0 || ^17.0.0
+  find-up@6.3.0:
     dependencies:
-      fbemitter: 3.0.0
-      fbjs: 3.0.4
-      react: 17.0.2
-    transitivePeerDependencies:
-      - encoding
-    dev: false
+      locate-path: 7.2.0
+      path-exists: 5.0.0
 
-  /follow-redirects@1.15.2:
-    resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==}
-    engines: {node: '>=4.0'}
-    peerDependencies:
-      debug: '*'
-    peerDependenciesMeta:
-      debug:
-        optional: true
-    dev: false
+  flat@5.0.2: {}
 
-  /fork-ts-checker-webpack-plugin@6.5.2(typescript@4.9.4)(webpack@5.75.0):
-    resolution: {integrity: sha512-m5cUmF30xkZ7h4tWUgTAcEaKmUW7tfyUyTqNNOz7OxWJ0v1VWKTcOvH8FWHUwSjlW/356Ijc9vi3XfcPstpQKA==}
-    engines: {node: '>=10', yarn: '>=1.0.0'}
-    peerDependencies:
-      eslint: '>= 6'
-      typescript: '>= 2.7'
-      vue-template-compiler: '*'
-      webpack: '>= 4'
-    peerDependenciesMeta:
-      eslint:
-        optional: true
-      vue-template-compiler:
-        optional: true
+  follow-redirects@1.15.2: {}
+
+  foreground-child@3.3.0:
+    dependencies:
+      cross-spawn: 7.0.3
+      signal-exit: 4.1.0
+
+  fork-ts-checker-webpack-plugin@6.5.2(typescript@5.3.3)(webpack@5.90.1):
     dependencies:
-      '@babel/code-frame': 7.18.6
+      '@babel/code-frame': 7.23.5
       '@types/json-schema': 7.0.11
       chalk: 4.1.2
       chokidar: 3.5.3
@@ -5020,120 +8600,83 @@ packages:
       memfs: 3.4.12
       minimatch: 3.1.2
       schema-utils: 2.7.0
-      semver: 7.3.8
+      semver: 7.6.0
       tapable: 1.1.3
-      typescript: 4.9.4
-      webpack: 5.75.0
-    dev: false
+      typescript: 5.3.3
+      webpack: 5.90.1
 
-  /forwarded@0.2.0:
-    resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
-    engines: {node: '>= 0.6'}
-    dev: false
+  form-data-encoder@2.1.4: {}
 
-  /fraction.js@4.2.0:
-    resolution: {integrity: sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==}
-    dev: false
+  format@0.2.2: {}
 
-  /fresh@0.5.2:
-    resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==}
-    engines: {node: '>= 0.6'}
-    dev: false
+  forwarded@0.2.0: {}
 
-  /fs-extra@10.1.0:
-    resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==}
-    engines: {node: '>=12'}
+  fraction.js@4.2.0: {}
+
+  fraction.js@4.3.7: {}
+
+  fresh@0.5.2: {}
+
+  fs-extra@11.2.0:
     dependencies:
       graceful-fs: 4.2.10
       jsonfile: 6.1.0
       universalify: 2.0.0
-    dev: false
 
-  /fs-extra@9.1.0:
-    resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==}
-    engines: {node: '>=10'}
+  fs-extra@9.1.0:
     dependencies:
       at-least-node: 1.0.0
       graceful-fs: 4.2.10
       jsonfile: 6.1.0
       universalify: 2.0.0
-    dev: false
 
-  /fs-monkey@1.0.3:
-    resolution: {integrity: sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q==}
-    dev: false
+  fs-monkey@1.0.3: {}
 
-  /fs.realpath@1.0.0:
-    resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
-    dev: false
+  fs.realpath@1.0.0: {}
 
-  /fsevents@2.3.2:
-    resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
-    engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
-    os: [darwin]
-    requiresBuild: true
+  fsevents@2.3.3:
     optional: true
 
-  /function-bind@1.1.1:
-    resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==}
+  function-bind@1.1.2: {}
 
-  /gensync@1.0.0-beta.2:
-    resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
-    engines: {node: '>=6.9.0'}
-    dev: false
+  gensync@1.0.0-beta.2: {}
 
-  /get-intrinsic@1.1.3:
-    resolution: {integrity: sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==}
+  get-intrinsic@1.1.3:
     dependencies:
-      function-bind: 1.1.1
+      function-bind: 1.1.2
       has: 1.0.3
       has-symbols: 1.0.3
-    dev: false
 
-  /get-own-enumerable-property-symbols@3.0.2:
-    resolution: {integrity: sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==}
-    dev: false
+  get-own-enumerable-property-symbols@3.0.2: {}
 
-  /get-stream@4.1.0:
-    resolution: {integrity: sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==}
-    engines: {node: '>=6'}
-    dependencies:
-      pump: 3.0.0
-    dev: false
+  get-stream@6.0.1: {}
 
-  /get-stream@5.2.0:
-    resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==}
-    engines: {node: '>=8'}
+  giscus@1.5.0:
     dependencies:
-      pump: 3.0.0
-    dev: false
-
-  /get-stream@6.0.1:
-    resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==}
-    engines: {node: '>=10'}
-    dev: false
+      lit: 3.1.2
 
-  /github-slugger@1.5.0:
-    resolution: {integrity: sha512-wIh+gKBI9Nshz2o46B0B3f5k/W+WI9ZAv6y5Dn5WJ5SK1t0TnDimB4WE5rmTD05ZAIn8HALCZVmCsvj0w0v0lw==}
-    dev: false
+  github-slugger@1.5.0: {}
 
-  /glob-parent@5.1.2:
-    resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
-    engines: {node: '>= 6'}
+  glob-parent@5.1.2:
     dependencies:
       is-glob: 4.0.3
 
-  /glob-parent@6.0.2:
-    resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
-    engines: {node: '>=10.13.0'}
+  glob-parent@6.0.2:
     dependencies:
       is-glob: 4.0.3
 
-  /glob-to-regexp@0.4.1:
-    resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==}
+  glob-to-regexp@0.4.1: {}
 
-  /glob@7.2.3:
-    resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
+  glob@10.4.5:
+    dependencies:
+      foreground-child: 3.3.0
+      jackspeak: 3.4.3
+      minimatch: 9.0.5
+      minipass: 7.1.2
+      package-json-from-dist: 1.0.0
+      path-scurry: 1.11.1
+
+  glob@7.2.3:
     dependencies:
       fs.realpath: 1.0.0
       inflight: 1.0.6
@@ -5141,2120 +8684,2112 @@ packages:
       minimatch: 3.1.2
       once: 1.4.0
       path-is-absolute: 1.0.1
-    dev: false
 
-  /global-dirs@3.0.1:
-    resolution: {integrity: sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==}
-    engines: {node: '>=10'}
+  global-dirs@3.0.1:
     dependencies:
       ini: 2.0.0
-    dev: false
 
-  /global-modules@2.0.0:
-    resolution: {integrity: sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==}
-    engines: {node: '>=6'}
+  global-modules@2.0.0:
     dependencies:
       global-prefix: 3.0.0
-    dev: false
 
-  /global-prefix@3.0.0:
-    resolution: {integrity: sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==}
-    engines: {node: '>=6'}
+  global-prefix@3.0.0:
     dependencies:
       ini: 1.3.8
       kind-of: 6.0.3
       which: 1.3.1
-    dev: false
 
-  /globals@11.12.0:
-    resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==}
-    engines: {node: '>=4'}
-    dev: false
+  globals@11.12.0: {}
 
-  /globby@11.1.0:
-    resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==}
-    engines: {node: '>=10'}
+  globby@11.1.0:
     dependencies:
       array-union: 2.1.0
       dir-glob: 3.0.1
-      fast-glob: 3.2.12
+      fast-glob: 3.3.2
       ignore: 5.2.4
       merge2: 1.4.1
       slash: 3.0.0
-    dev: false
 
-  /globby@13.1.3:
-    resolution: {integrity: sha512-8krCNHXvlCgHDpegPzleMq07yMYTO2sXKASmZmquEYWEmCx6J5UTRbp5RwMJkTJGtcQ44YpiUYUiN0b9mzy8Bw==}
-    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+  globby@13.1.3:
     dependencies:
       dir-glob: 3.0.1
-      fast-glob: 3.2.12
+      fast-glob: 3.3.2
       ignore: 5.2.4
       merge2: 1.4.1
       slash: 4.0.0
-    dev: false
 
-  /got@9.6.0:
-    resolution: {integrity: sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==}
-    engines: {node: '>=8.6'}
+  got@12.6.1:
     dependencies:
-      '@sindresorhus/is': 0.14.0
-      '@szmarczak/http-timer': 1.1.2
-      '@types/keyv': 3.1.4
-      '@types/responselike': 1.0.0
-      cacheable-request: 6.1.0
-      decompress-response: 3.3.0
-      duplexer3: 0.1.5
-      get-stream: 4.1.0
-      lowercase-keys: 1.0.1
-      mimic-response: 1.0.1
-      p-cancelable: 1.1.0
-      to-readable-stream: 1.0.0
-      url-parse-lax: 3.0.0
-    dev: false
-
-  /graceful-fs@4.2.10:
-    resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==}
+      '@sindresorhus/is': 5.6.0
+      '@szmarczak/http-timer': 5.0.1
+      cacheable-lookup: 7.0.0
+      cacheable-request: 10.2.14
+      decompress-response: 6.0.0
+      form-data-encoder: 2.1.4
+      get-stream: 6.0.1
+      http2-wrapper: 2.2.1
+      lowercase-keys: 3.0.0
+      p-cancelable: 3.0.0
+      responselike: 3.0.0
 
-  /gray-matter@4.0.3:
-    resolution: {integrity: sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==}
-    engines: {node: '>=6.0'}
+  graceful-fs@4.2.10: {}
+
+  gray-matter@4.0.3:
     dependencies:
       js-yaml: 3.14.1
       kind-of: 6.0.3
       section-matter: 1.0.0
       strip-bom-string: 1.0.0
-    dev: false
 
-  /gzip-size@6.0.0:
-    resolution: {integrity: sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==}
-    engines: {node: '>=10'}
+  gzip-size@6.0.0:
     dependencies:
       duplexer: 0.1.2
-    dev: false
 
-  /handle-thing@2.0.1:
-    resolution: {integrity: sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==}
-    dev: false
+  handle-thing@2.0.1: {}
 
-  /has-flag@3.0.0:
-    resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==}
-    engines: {node: '>=4'}
-    dev: false
+  has-flag@3.0.0: {}
 
-  /has-flag@4.0.0:
-    resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
-    engines: {node: '>=8'}
+  has-flag@4.0.0: {}
 
-  /has-property-descriptors@1.0.0:
-    resolution: {integrity: sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==}
+  has-property-descriptors@1.0.0:
     dependencies:
       get-intrinsic: 1.1.3
-    dev: false
 
-  /has-symbols@1.0.3:
-    resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==}
-    engines: {node: '>= 0.4'}
-    dev: false
+  has-symbols@1.0.3: {}
 
-  /has-yarn@2.1.0:
-    resolution: {integrity: sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==}
-    engines: {node: '>=8'}
-    dev: false
+  has-yarn@3.0.0: {}
 
-  /has@1.0.3:
-    resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==}
-    engines: {node: '>= 0.4.0'}
+  has@1.0.3:
     dependencies:
-      function-bind: 1.1.1
+      function-bind: 1.1.2
 
-  /hast-to-hyperscript@9.0.1:
-    resolution: {integrity: sha512-zQgLKqF+O2F72S1aa4y2ivxzSlko3MAvxkwG8ehGmNiqd98BIN3JM1rAJPmplEyLmGLO2QZYJtIneOSZ2YbJuA==}
+  hasown@2.0.2:
     dependencies:
-      '@types/unist': 2.0.6
-      comma-separated-tokens: 1.0.8
-      property-information: 5.6.0
-      space-separated-tokens: 1.1.5
-      style-to-object: 0.3.0
-      unist-util-is: 4.1.0
-      web-namespaces: 1.1.4
-    dev: false
-
-  /hast-util-from-parse5@6.0.1:
-    resolution: {integrity: sha512-jeJUWiN5pSxW12Rh01smtVkZgZr33wBokLzKLwinYOUfSzm1Nl/c3GUGebDyOKjdsRgMvoVbV0VpAcpjF4NrJA==}
-    dependencies:
-      '@types/parse5': 5.0.3
-      hastscript: 6.0.0
-      property-information: 5.6.0
-      vfile: 4.2.1
-      vfile-location: 3.2.0
-      web-namespaces: 1.1.4
-    dev: false
-
-  /hast-util-parse-selector@2.2.5:
-    resolution: {integrity: sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==}
-    dev: false
-
-  /hast-util-raw@6.0.1:
-    resolution: {integrity: sha512-ZMuiYA+UF7BXBtsTBNcLBF5HzXzkyE6MLzJnL605LKE8GJylNjGc4jjxazAHUtcwT5/CEt6afRKViYB4X66dig==}
-    dependencies:
-      '@types/hast': 2.3.4
-      hast-util-from-parse5: 6.0.1
-      hast-util-to-parse5: 6.0.0
-      html-void-elements: 1.0.5
-      parse5: 6.0.1
-      unist-util-position: 3.1.0
-      vfile: 4.2.1
-      web-namespaces: 1.1.4
-      xtend: 4.0.2
-      zwitch: 1.0.5
-    dev: false
-
-  /hast-util-to-parse5@6.0.0:
-    resolution: {integrity: sha512-Lu5m6Lgm/fWuz8eWnrKezHtVY83JeRGaNQ2kn9aJgqaxvVkFCZQBEhgodZUDUvoodgyROHDb3r5IxAEdl6suJQ==}
-    dependencies:
-      hast-to-hyperscript: 9.0.1
-      property-information: 5.6.0
-      web-namespaces: 1.1.4
-      xtend: 4.0.2
-      zwitch: 1.0.5
-    dev: false
-
-  /hastscript@6.0.0:
-    resolution: {integrity: sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==}
-    dependencies:
-      '@types/hast': 2.3.4
-      comma-separated-tokens: 1.0.8
-      hast-util-parse-selector: 2.2.5
-      property-information: 5.6.0
-      space-separated-tokens: 1.1.5
-    dev: false
-
-  /he@1.2.0:
-    resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
-    hasBin: true
-    dev: false
+      function-bind: 1.1.2
 
-  /heap@0.2.7:
-    resolution: {integrity: sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==}
-    dev: false
+  hast-util-from-parse5@8.0.1:
+    dependencies:
+      '@types/hast': 3.0.4
+      '@types/unist': 3.0.2
+      devlop: 1.1.0
+      hastscript: 8.0.0
+      property-information: 6.4.1
+      vfile: 6.0.1
+      vfile-location: 5.0.2
+      web-namespaces: 2.0.1
 
-  /history@4.10.1:
-    resolution: {integrity: sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==}
+  hast-util-parse-selector@4.0.0:
+    dependencies:
+      '@types/hast': 3.0.4
+
+  hast-util-raw@9.0.2:
+    dependencies:
+      '@types/hast': 3.0.4
+      '@types/unist': 3.0.2
+      '@ungap/structured-clone': 1.2.0
+      hast-util-from-parse5: 8.0.1
+      hast-util-to-parse5: 8.0.0
+      html-void-elements: 3.0.0
+      mdast-util-to-hast: 13.1.0
+      parse5: 7.1.2
+      unist-util-position: 5.0.0
+      unist-util-visit: 5.0.0
+      vfile: 6.0.1
+      web-namespaces: 2.0.1
+      zwitch: 2.0.4
+
+  hast-util-to-estree@3.1.0:
+    dependencies:
+      '@types/estree': 1.0.5
+      '@types/estree-jsx': 1.0.4
+      '@types/hast': 3.0.4
+      comma-separated-tokens: 2.0.3
+      devlop: 1.1.0
+      estree-util-attach-comments: 3.0.0
+      estree-util-is-identifier-name: 3.0.0
+      hast-util-whitespace: 3.0.0
+      mdast-util-mdx-expression: 2.0.0
+      mdast-util-mdx-jsx: 3.1.0
+      mdast-util-mdxjs-esm: 2.0.1
+      property-information: 6.4.1
+      space-separated-tokens: 2.0.2
+      style-to-object: 0.4.4
+      unist-util-position: 5.0.0
+      zwitch: 2.0.4
+    transitivePeerDependencies:
+      - supports-color
+
+  hast-util-to-jsx-runtime@2.3.0:
+    dependencies:
+      '@types/estree': 1.0.5
+      '@types/hast': 3.0.4
+      '@types/unist': 3.0.2
+      comma-separated-tokens: 2.0.3
+      devlop: 1.1.0
+      estree-util-is-identifier-name: 3.0.0
+      hast-util-whitespace: 3.0.0
+      mdast-util-mdx-expression: 2.0.0
+      mdast-util-mdx-jsx: 3.1.0
+      mdast-util-mdxjs-esm: 2.0.1
+      property-information: 6.4.1
+      space-separated-tokens: 2.0.2
+      style-to-object: 1.0.5
+      unist-util-position: 5.0.0
+      vfile-message: 4.0.2
+    transitivePeerDependencies:
+      - supports-color
+
+  hast-util-to-parse5@8.0.0:
+    dependencies:
+      '@types/hast': 3.0.4
+      comma-separated-tokens: 2.0.3
+      devlop: 1.1.0
+      property-information: 6.4.1
+      space-separated-tokens: 2.0.2
+      web-namespaces: 2.0.1
+      zwitch: 2.0.4
+
+  hast-util-whitespace@3.0.0:
+    dependencies:
+      '@types/hast': 3.0.4
+
+  hastscript@8.0.0:
+    dependencies:
+      '@types/hast': 3.0.4
+      comma-separated-tokens: 2.0.3
+      hast-util-parse-selector: 4.0.0
+      property-information: 6.4.1
+      space-separated-tokens: 2.0.2
+
+  he@1.2.0: {}
+
+  heap@0.2.7: {}
+
+  history@4.10.1:
     dependencies:
-      '@babel/runtime': 7.20.7
+      '@babel/runtime': 7.23.9
       loose-envify: 1.4.0
       resolve-pathname: 3.0.0
       tiny-invariant: 1.3.1
       tiny-warning: 1.0.3
       value-equal: 1.0.1
-    dev: false
 
-  /hoist-non-react-statics@3.3.2:
-    resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==}
+  hoist-non-react-statics@3.3.2:
     dependencies:
       react-is: 16.13.1
-    dev: false
 
-  /hpack.js@2.1.6:
-    resolution: {integrity: sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==}
+  hpack.js@2.1.6:
     dependencies:
       inherits: 2.0.4
       obuf: 1.1.2
       readable-stream: 2.3.7
       wbuf: 1.7.3
-    dev: false
 
-  /html-entities@2.3.3:
-    resolution: {integrity: sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==}
-    dev: false
+  html-entities@2.3.3: {}
 
-  /html-minifier-terser@6.1.0:
-    resolution: {integrity: sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==}
-    engines: {node: '>=12'}
-    hasBin: true
+  html-escaper@2.0.2: {}
+
+  html-minifier-terser@6.1.0:
     dependencies:
       camel-case: 4.1.2
-      clean-css: 5.3.1
+      clean-css: 5.3.3
       commander: 8.3.0
       he: 1.2.0
       param-case: 3.0.4
       relateurl: 0.2.7
-      terser: 5.16.1
-    dev: false
+      terser: 5.27.0
 
-  /html-tags@3.2.0:
-    resolution: {integrity: sha512-vy7ClnArOZwCnqZgvv+ddgHgJiAFXe3Ge9ML5/mBctVJoUoYPCdxVucOywjDARn6CVoh3dRSFdPHy2sX80L0Wg==}
-    engines: {node: '>=8'}
-    dev: false
+  html-minifier-terser@7.2.0:
+    dependencies:
+      camel-case: 4.1.2
+      clean-css: 5.3.3
+      commander: 10.0.1
+      entities: 4.4.0
+      param-case: 3.0.4
+      relateurl: 0.2.7
+      terser: 5.27.0
 
-  /html-void-elements@1.0.5:
-    resolution: {integrity: sha512-uE/TxKuyNIcx44cIWnjr/rfIATDH7ZaOMmstu0CwhFG1Dunhlp4OC6/NMbhiwoq5BpW0ubi303qnEk/PZj614w==}
-    dev: false
+  html-tags@3.3.1: {}
 
-  /html-webpack-plugin@5.5.0(webpack@5.75.0):
-    resolution: {integrity: sha512-sy88PC2cRTVxvETRgUHFrL4No3UxvcH8G1NepGhqaTT+GXN2kTamqasot0inS5hXeg1cMbFDt27zzo9p35lZVw==}
-    engines: {node: '>=10.13.0'}
-    peerDependencies:
-      webpack: ^5.20.0
+  html-void-elements@3.0.0: {}
+
+  html-webpack-plugin@5.6.0(webpack@5.90.1):
     dependencies:
       '@types/html-minifier-terser': 6.1.0
       html-minifier-terser: 6.1.0
       lodash: 4.17.21
       pretty-error: 4.0.0
       tapable: 2.2.1
-      webpack: 5.75.0
-    dev: false
+    optionalDependencies:
+      webpack: 5.90.1
 
-  /htmlparser2@6.1.0:
-    resolution: {integrity: sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==}
+  htmlparser2@6.1.0:
     dependencies:
       domelementtype: 2.3.0
       domhandler: 4.3.1
       domutils: 2.8.0
       entities: 2.2.0
-    dev: false
 
-  /htmlparser2@8.0.1:
-    resolution: {integrity: sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA==}
+  htmlparser2@8.0.1:
     dependencies:
       domelementtype: 2.3.0
       domhandler: 5.0.3
       domutils: 3.0.1
       entities: 4.4.0
-    dev: false
 
-  /http-cache-semantics@4.1.0:
-    resolution: {integrity: sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==}
-    dev: false
+  http-cache-semantics@4.1.1: {}
 
-  /http-deceiver@1.2.7:
-    resolution: {integrity: sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==}
-    dev: false
+  http-deceiver@1.2.7: {}
 
-  /http-errors@1.6.3:
-    resolution: {integrity: sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==}
-    engines: {node: '>= 0.6'}
+  http-errors@1.6.3:
     dependencies:
       depd: 1.1.2
       inherits: 2.0.3
       setprototypeof: 1.1.0
       statuses: 1.5.0
-    dev: false
 
-  /http-errors@2.0.0:
-    resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==}
-    engines: {node: '>= 0.8'}
+  http-errors@2.0.0:
     dependencies:
       depd: 2.0.0
       inherits: 2.0.4
       setprototypeof: 1.2.0
       statuses: 2.0.1
       toidentifier: 1.0.1
-    dev: false
 
-  /http-parser-js@0.5.8:
-    resolution: {integrity: sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==}
-    dev: false
+  http-parser-js@0.5.8: {}
 
-  /http-proxy-middleware@2.0.6(@types/express@4.17.15):
-    resolution: {integrity: sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==}
-    engines: {node: '>=12.0.0'}
-    peerDependencies:
-      '@types/express': ^4.17.13
-    peerDependenciesMeta:
-      '@types/express':
-        optional: true
+  http-proxy-middleware@2.0.6(@types/express@4.17.15):
     dependencies:
-      '@types/express': 4.17.15
       '@types/http-proxy': 1.17.9
       http-proxy: 1.18.1
       is-glob: 4.0.3
       is-plain-obj: 3.0.0
-      micromatch: 4.0.5
+      micromatch: 4.0.8
+    optionalDependencies:
+      '@types/express': 4.17.15
     transitivePeerDependencies:
       - debug
-    dev: false
 
-  /http-proxy@1.18.1:
-    resolution: {integrity: sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==}
-    engines: {node: '>=8.0.0'}
+  http-proxy@1.18.1:
     dependencies:
       eventemitter3: 4.0.7
       follow-redirects: 1.15.2
       requires-port: 1.0.0
     transitivePeerDependencies:
       - debug
-    dev: false
 
-  /human-signals@2.1.0:
-    resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}
-    engines: {node: '>=10.17.0'}
-    dev: false
+  http2-wrapper@2.2.1:
+    dependencies:
+      quick-lru: 5.1.1
+      resolve-alpn: 1.2.1
 
-  /iconv-lite@0.4.24:
-    resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
-    engines: {node: '>=0.10.0'}
+  human-signals@2.1.0: {}
+
+  iconv-lite@0.4.24:
     dependencies:
       safer-buffer: 2.1.2
-    dev: false
 
-  /iconv-lite@0.6.3:
-    resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
-    engines: {node: '>=0.10.0'}
+  iconv-lite@0.6.3:
     dependencies:
       safer-buffer: 2.1.2
-    dev: false
 
-  /icss-utils@5.1.0(postcss@8.4.21):
-    resolution: {integrity: sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==}
-    engines: {node: ^10 || ^12 || >= 14}
-    peerDependencies:
-      postcss: ^8.1.0
+  icss-utils@5.1.0(postcss@8.4.35):
     dependencies:
-      postcss: 8.4.21
-    dev: false
+      postcss: 8.4.35
 
-  /ignore@5.2.4:
-    resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==}
-    engines: {node: '>= 4'}
-    dev: false
+  ignore@5.2.4: {}
 
-  /image-size@1.0.2:
-    resolution: {integrity: sha512-xfOoWjceHntRb3qFCrh5ZFORYH8XCdYpASltMhZ/Q0KZiOwjdE/Yl2QCiWdwD+lygV5bMCvauzgu5PxBX/Yerg==}
-    engines: {node: '>=14.0.0'}
-    hasBin: true
+  image-size@1.0.2:
     dependencies:
       queue: 6.0.2
-    dev: false
 
-  /immer@9.0.17:
-    resolution: {integrity: sha512-+hBruaLSQvkPfxRiTLK/mi4vLH+/VQS6z2KJahdoxlleFOI8ARqzOF17uy12eFDlqWmPoygwc5evgwcp+dlHhg==}
-    dev: false
+  immer@9.0.17: {}
 
-  /import-fresh@3.3.0:
-    resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==}
-    engines: {node: '>=6'}
+  import-fresh@3.3.0:
     dependencies:
       parent-module: 1.0.1
       resolve-from: 4.0.0
-    dev: false
 
-  /import-lazy@2.1.0:
-    resolution: {integrity: sha512-m7ZEHgtw69qOGw+jwxXkHlrlIPdTGkyh66zXZ1ajZbxkDBNjSY/LGbmjc7h0s2ELsUDTAhFr55TrPSSqJGPG0A==}
-    engines: {node: '>=4'}
-    dev: false
+  import-lazy@4.0.0: {}
 
-  /imurmurhash@0.1.4:
-    resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
-    engines: {node: '>=0.8.19'}
-    dev: false
+  imurmurhash@0.1.4: {}
 
-  /indent-string@4.0.0:
-    resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==}
-    engines: {node: '>=8'}
-    dev: false
+  indent-string@4.0.0: {}
 
-  /infima@0.2.0-alpha.43:
-    resolution: {integrity: sha512-2uw57LvUqW0rK/SWYnd/2rRfxNA5DDNOh33jxF7fy46VWoNhGxiUQyVZHbBMjQ33mQem0cjdDVwgWVAmlRfgyQ==}
-    engines: {node: '>=12'}
-    dev: false
+  infima@0.2.0-alpha.43: {}
 
-  /inflight@1.0.6:
-    resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
+  inflight@1.0.6:
     dependencies:
       once: 1.4.0
       wrappy: 1.0.2
-    dev: false
 
-  /inherits@2.0.3:
-    resolution: {integrity: sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==}
-    dev: false
+  inherits@2.0.3: {}
 
-  /inherits@2.0.4:
-    resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
-    dev: false
+  inherits@2.0.4: {}
 
-  /ini@1.3.8:
-    resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==}
-    dev: false
+  ini@1.3.8: {}
 
-  /ini@2.0.0:
-    resolution: {integrity: sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==}
-    engines: {node: '>=10'}
-    dev: false
+  ini@2.0.0: {}
+
+  inline-style-parser@0.1.1: {}
 
-  /inline-style-parser@0.1.1:
-    resolution: {integrity: sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==}
-    dev: false
+  inline-style-parser@0.2.2: {}
 
-  /internmap@2.0.3:
-    resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==}
-    engines: {node: '>=12'}
-    dev: false
+  internmap@1.0.1: {}
 
-  /interpret@1.4.0:
-    resolution: {integrity: sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==}
-    engines: {node: '>= 0.10'}
-    dev: false
+  internmap@2.0.3: {}
 
-  /invariant@2.2.4:
-    resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==}
+  interpret@1.4.0: {}
+
+  invariant@2.2.4:
     dependencies:
       loose-envify: 1.4.0
 
-  /ipaddr.js@1.9.1:
-    resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
-    engines: {node: '>= 0.10'}
-    dev: false
+  ipaddr.js@1.9.1: {}
 
-  /ipaddr.js@2.0.1:
-    resolution: {integrity: sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==}
-    engines: {node: '>= 10'}
-    dev: false
+  ipaddr.js@2.0.1: {}
 
-  /is-alphabetical@1.0.4:
-    resolution: {integrity: sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==}
-    dev: false
+  is-alphabetical@2.0.1: {}
 
-  /is-alphanumerical@1.0.4:
-    resolution: {integrity: sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==}
+  is-alphanumerical@2.0.1:
     dependencies:
-      is-alphabetical: 1.0.4
-      is-decimal: 1.0.4
-    dev: false
+      is-alphabetical: 2.0.1
+      is-decimal: 2.0.1
 
-  /is-arrayish@0.2.1:
-    resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
-    dev: false
+  is-arrayish@0.2.1: {}
 
-  /is-binary-path@2.1.0:
-    resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
-    engines: {node: '>=8'}
+  is-binary-path@2.1.0:
     dependencies:
       binary-extensions: 2.2.0
 
-  /is-buffer@2.0.5:
-    resolution: {integrity: sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==}
-    engines: {node: '>=4'}
-    dev: false
-
-  /is-ci@2.0.0:
-    resolution: {integrity: sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==}
-    hasBin: true
+  is-ci@3.0.1:
     dependencies:
-      ci-info: 2.0.0
-    dev: false
+      ci-info: 3.7.1
 
-  /is-core-module@2.11.0:
-    resolution: {integrity: sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==}
+  is-core-module@2.15.1:
     dependencies:
-      has: 1.0.3
+      hasown: 2.0.2
 
-  /is-decimal@1.0.4:
-    resolution: {integrity: sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==}
-    dev: false
+  is-decimal@2.0.1: {}
 
-  /is-docker@2.2.1:
-    resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==}
-    engines: {node: '>=8'}
-    hasBin: true
-    dev: false
+  is-docker@2.2.1: {}
 
-  /is-extendable@0.1.1:
-    resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==}
-    engines: {node: '>=0.10.0'}
-    dev: false
+  is-extendable@0.1.1: {}
 
-  /is-extglob@2.1.1:
-    resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
-    engines: {node: '>=0.10.0'}
+  is-extglob@2.1.1: {}
 
-  /is-fullwidth-code-point@3.0.0:
-    resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
-    engines: {node: '>=8'}
-    dev: false
+  is-fullwidth-code-point@3.0.0: {}
 
-  /is-glob@4.0.3:
-    resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
-    engines: {node: '>=0.10.0'}
+  is-glob@4.0.3:
     dependencies:
       is-extglob: 2.1.1
 
-  /is-hexadecimal@1.0.4:
-    resolution: {integrity: sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==}
-    dev: false
+  is-hexadecimal@2.0.1: {}
 
-  /is-installed-globally@0.4.0:
-    resolution: {integrity: sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==}
-    engines: {node: '>=10'}
+  is-installed-globally@0.4.0:
     dependencies:
       global-dirs: 3.0.1
       is-path-inside: 3.0.3
-    dev: false
 
-  /is-npm@5.0.0:
-    resolution: {integrity: sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA==}
-    engines: {node: '>=10'}
-    dev: false
+  is-npm@6.0.0: {}
 
-  /is-number@7.0.0:
-    resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
-    engines: {node: '>=0.12.0'}
+  is-number@7.0.0: {}
 
-  /is-obj@1.0.1:
-    resolution: {integrity: sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==}
-    engines: {node: '>=0.10.0'}
-    dev: false
+  is-obj@1.0.1: {}
 
-  /is-obj@2.0.0:
-    resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==}
-    engines: {node: '>=8'}
-    dev: false
+  is-obj@2.0.0: {}
 
-  /is-path-cwd@2.2.0:
-    resolution: {integrity: sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==}
-    engines: {node: '>=6'}
-    dev: false
+  is-path-cwd@2.2.0: {}
 
-  /is-path-inside@3.0.3:
-    resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==}
-    engines: {node: '>=8'}
-    dev: false
+  is-path-inside@3.0.3: {}
 
-  /is-plain-obj@2.1.0:
-    resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==}
-    engines: {node: '>=8'}
-    dev: false
+  is-plain-obj@3.0.0: {}
 
-  /is-plain-obj@3.0.0:
-    resolution: {integrity: sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==}
-    engines: {node: '>=10'}
-    dev: false
+  is-plain-obj@4.1.0: {}
 
-  /is-plain-object@2.0.4:
-    resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==}
-    engines: {node: '>=0.10.0'}
+  is-plain-object@2.0.4:
     dependencies:
       isobject: 3.0.1
 
-  /is-regexp@1.0.0:
-    resolution: {integrity: sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==}
-    engines: {node: '>=0.10.0'}
-    dev: false
+  is-plain-object@5.0.0: {}
 
-  /is-root@2.1.0:
-    resolution: {integrity: sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg==}
-    engines: {node: '>=6'}
-    dev: false
+  is-reference@3.0.2:
+    dependencies:
+      '@types/estree': 1.0.5
 
-  /is-stream@2.0.1:
-    resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==}
-    engines: {node: '>=8'}
-    dev: false
+  is-regexp@1.0.0: {}
 
-  /is-typedarray@1.0.0:
-    resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==}
-    dev: false
+  is-root@2.1.0: {}
 
-  /is-whitespace-character@1.0.4:
-    resolution: {integrity: sha512-SDweEzfIZM0SJV0EUga669UTKlmL0Pq8Lno0QDQsPnvECB3IM2aP0gdx5TrU0A01MAPfViaZiI2V1QMZLaKK5w==}
-    dev: false
+  is-stream@2.0.1: {}
 
-  /is-word-character@1.0.4:
-    resolution: {integrity: sha512-5SMO8RVennx3nZrqtKwCGyyetPE9VDba5ugvKLaD4KopPG5kR4mQ7tNt/r7feL5yt5h3lpuBbIUmCOG2eSzXHA==}
-    dev: false
+  is-typedarray@1.0.0: {}
 
-  /is-wsl@2.2.0:
-    resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==}
-    engines: {node: '>=8'}
+  is-wsl@2.2.0:
     dependencies:
       is-docker: 2.2.1
-    dev: false
 
-  /is-yarn-global@0.3.0:
-    resolution: {integrity: sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==}
-    dev: false
+  is-yarn-global@0.4.1: {}
 
-  /isarray@0.0.1:
-    resolution: {integrity: sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==}
-    dev: false
+  isarray@0.0.1: {}
 
-  /isarray@1.0.0:
-    resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==}
-    dev: false
+  isarray@1.0.0: {}
 
-  /isexe@2.0.0:
-    resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
-    dev: false
+  isexe@2.0.0: {}
 
-  /isobject@3.0.1:
-    resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==}
-    engines: {node: '>=0.10.0'}
+  isobject@3.0.1: {}
 
-  /jest-util@29.3.1:
-    resolution: {integrity: sha512-7YOVZaiX7RJLv76ZfHt4nbNEzzTRiMW/IiOG7ZOKmTXmoGBxUDefgMAxQubu6WPVqP5zSzAdZG0FfLcC7HOIFQ==}
-    engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+  jackspeak@3.4.3:
     dependencies:
-      '@jest/types': 29.3.1
-      '@types/node': 18.11.18
+      '@isaacs/cliui': 8.0.2
+    optionalDependencies:
+      '@pkgjs/parseargs': 0.11.0
+
+  jest-util@29.7.0:
+    dependencies:
+      '@jest/types': 29.6.3
+      '@types/node': 20.11.17
       chalk: 4.1.2
       ci-info: 3.7.1
       graceful-fs: 4.2.10
       picomatch: 2.3.1
-    dev: false
 
-  /jest-worker@27.5.1:
-    resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==}
-    engines: {node: '>= 10.13.0'}
+  jest-worker@27.5.1:
     dependencies:
-      '@types/node': 18.11.18
+      '@types/node': 20.11.17
       merge-stream: 2.0.0
       supports-color: 8.1.1
 
-  /jest-worker@29.3.1:
-    resolution: {integrity: sha512-lY4AnnmsEWeiXirAIA0c9SDPbuCBq8IYuDVL8PMm0MZ2PEs2yPvRA/J64QBXuZp7CYKrDM/rmNrc9/i3KJQncw==}
-    engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+  jest-worker@29.7.0:
     dependencies:
-      '@types/node': 18.11.18
-      jest-util: 29.3.1
+      '@types/node': 20.11.17
+      jest-util: 29.7.0
       merge-stream: 2.0.0
       supports-color: 8.1.1
-    dev: false
 
-  /joi@17.7.0:
-    resolution: {integrity: sha512-1/ugc8djfn93rTE3WRKdCzGGt/EtiYKxITMO4Wiv6q5JL1gl9ePt4kBsl1S499nbosspfctIQTpYIhSmHA3WAg==}
+  jiti@1.21.6: {}
+
+  joi@17.12.1:
     dependencies:
       '@hapi/hoek': 9.3.0
       '@hapi/topo': 5.1.0
-      '@sideway/address': 4.1.4
+      '@sideway/address': 4.1.5
       '@sideway/formula': 3.0.1
       '@sideway/pinpoint': 2.0.0
 
-  /js-tokens@4.0.0:
-    resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
+  js-tokens@4.0.0: {}
 
-  /js-yaml@3.14.1:
-    resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==}
-    hasBin: true
+  js-yaml@3.14.1:
     dependencies:
       argparse: 1.0.10
       esprima: 4.0.1
-    dev: false
 
-  /js-yaml@4.1.0:
-    resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
-    hasBin: true
+  js-yaml@4.1.0:
     dependencies:
       argparse: 2.0.1
-    dev: false
 
-  /jsesc@0.5.0:
-    resolution: {integrity: sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==}
-    hasBin: true
-    dev: false
+  jsesc@0.5.0: {}
 
-  /jsesc@2.5.2:
-    resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==}
-    engines: {node: '>=4'}
-    hasBin: true
-    dev: false
+  jsesc@2.5.2: {}
 
-  /json-buffer@3.0.0:
-    resolution: {integrity: sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ==}
-    dev: false
+  json-buffer@3.0.1: {}
 
-  /json-parse-even-better-errors@2.3.1:
-    resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==}
+  json-parse-even-better-errors@2.3.1: {}
 
-  /json-schema-traverse@0.4.1:
-    resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
+  json-schema-traverse@0.4.1: {}
 
-  /json-schema-traverse@1.0.0:
-    resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
-    dev: false
+  json-schema-traverse@1.0.0: {}
 
-  /json5@2.2.3:
-    resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
-    engines: {node: '>=6'}
-    hasBin: true
-    dev: false
+  json5@2.2.3: {}
 
-  /jsonfile@6.1.0:
-    resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==}
+  jsonfile@6.1.0:
     dependencies:
       universalify: 2.0.0
     optionalDependencies:
       graceful-fs: 4.2.10
-    dev: false
 
-  /keyv@3.1.0:
-    resolution: {integrity: sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==}
+  jsonp@0.2.1:
     dependencies:
-      json-buffer: 3.0.0
-    dev: false
+      debug: 2.6.9
+    transitivePeerDependencies:
+      - supports-color
 
-  /khroma@2.0.0:
-    resolution: {integrity: sha512-2J8rDNlQWbtiNYThZRvmMv5yt44ZakX+Tz5ZIp/mN1pt4snn+m030Va5Z4v8xA0cQFDXBwO/8i42xL4QPsVk3g==}
-    dev: false
+  keyv@4.5.4:
+    dependencies:
+      json-buffer: 3.0.1
 
-  /kind-of@6.0.3:
-    resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==}
-    engines: {node: '>=0.10.0'}
+  khroma@2.0.0: {}
 
-  /kleur@3.0.3:
-    resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==}
-    engines: {node: '>=6'}
-    dev: false
+  kind-of@6.0.3: {}
 
-  /klona@2.0.5:
-    resolution: {integrity: sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ==}
-    engines: {node: '>= 8'}
-    dev: false
+  kleur@3.0.3: {}
 
-  /latest-version@5.1.0:
-    resolution: {integrity: sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==}
-    engines: {node: '>=8'}
+  kleur@4.1.5: {}
+
+  latest-version@7.0.0:
     dependencies:
-      package-json: 6.5.0
-    dev: false
+      package-json: 8.1.1
 
-  /layout-base@1.0.2:
-    resolution: {integrity: sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==}
-    dev: false
+  launch-editor@2.6.1:
+    dependencies:
+      picocolors: 1.1.0
+      shell-quote: 1.8.1
 
-  /layout-base@2.0.1:
-    resolution: {integrity: sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==}
-    dev: false
+  layout-base@1.0.2: {}
 
-  /leven@3.1.0:
-    resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==}
-    engines: {node: '>=6'}
-    dev: false
+  leven@3.1.0: {}
+
+  lilconfig@2.1.0: {}
+
+  lilconfig@3.1.2: {}
+
+  lines-and-columns@1.2.4: {}
+
+  lit-element@4.0.4:
+    dependencies:
+      '@lit-labs/ssr-dom-shim': 1.2.0
+      '@lit/reactive-element': 2.0.4
+      lit-html: 3.1.2
+
+  lit-html@3.1.2:
+    dependencies:
+      '@types/trusted-types': 2.0.7
+
+  lit@3.1.2:
+    dependencies:
+      '@lit/reactive-element': 2.0.4
+      lit-element: 4.0.4
+      lit-html: 3.1.2
+
+  loader-runner@4.3.0: {}
+
+  loader-utils@2.0.4:
+    dependencies:
+      big.js: 5.2.2
+      emojis-list: 3.0.0
+      json5: 2.2.3
+
+  loader-utils@3.2.1: {}
+
+  locate-path@3.0.0:
+    dependencies:
+      p-locate: 3.0.0
+      path-exists: 3.0.0
+
+  locate-path@6.0.0:
+    dependencies:
+      p-locate: 5.0.0
+
+  locate-path@7.2.0:
+    dependencies:
+      p-locate: 6.0.0
+
+  lodash-es@4.17.21: {}
+
+  lodash.castarray@4.4.0: {}
+
+  lodash.debounce@4.0.8: {}
+
+  lodash.isplainobject@4.0.6: {}
+
+  lodash.memoize@4.1.2: {}
+
+  lodash.merge@4.6.2: {}
+
+  lodash.uniq@4.5.0: {}
+
+  lodash@4.17.21: {}
+
+  longest-streak@3.1.0: {}
+
+  loose-envify@1.4.0:
+    dependencies:
+      js-tokens: 4.0.0
+
+  lower-case@2.0.2:
+    dependencies:
+      tslib: 2.6.2
+
+  lowercase-keys@3.0.0: {}
+
+  lru-cache@10.4.3: {}
+
+  lru-cache@5.1.1:
+    dependencies:
+      yallist: 3.1.1
+
+  lru-cache@6.0.0:
+    dependencies:
+      yallist: 4.0.0
+
+  markdown-extensions@2.0.0: {}
+
+  markdown-table@3.0.3: {}
+
+  mdast-util-directive@3.0.0:
+    dependencies:
+      '@types/mdast': 4.0.3
+      '@types/unist': 3.0.2
+      devlop: 1.1.0
+      mdast-util-from-markdown: 2.0.0
+      mdast-util-to-markdown: 2.1.0
+      parse-entities: 4.0.1
+      stringify-entities: 4.0.3
+      unist-util-visit-parents: 6.0.1
+    transitivePeerDependencies:
+      - supports-color
+
+  mdast-util-find-and-replace@3.0.1:
+    dependencies:
+      '@types/mdast': 4.0.3
+      escape-string-regexp: 5.0.0
+      unist-util-is: 6.0.0
+      unist-util-visit-parents: 6.0.1
+
+  mdast-util-from-markdown@1.3.1:
+    dependencies:
+      '@types/mdast': 3.0.10
+      '@types/unist': 2.0.6
+      decode-named-character-reference: 1.0.2
+      mdast-util-to-string: 3.2.0
+      micromark: 3.2.0
+      micromark-util-decode-numeric-character-reference: 1.1.0
+      micromark-util-decode-string: 1.1.0
+      micromark-util-normalize-identifier: 1.1.0
+      micromark-util-symbol: 1.1.0
+      micromark-util-types: 1.1.0
+      unist-util-stringify-position: 3.0.3
+      uvu: 0.5.6
+    transitivePeerDependencies:
+      - supports-color
+
+  mdast-util-from-markdown@2.0.0:
+    dependencies:
+      '@types/mdast': 4.0.3
+      '@types/unist': 3.0.2
+      decode-named-character-reference: 1.0.2
+      devlop: 1.1.0
+      mdast-util-to-string: 4.0.0
+      micromark: 4.0.0
+      micromark-util-decode-numeric-character-reference: 2.0.1
+      micromark-util-decode-string: 2.0.0
+      micromark-util-normalize-identifier: 2.0.0
+      micromark-util-symbol: 2.0.0
+      micromark-util-types: 2.0.0
+      unist-util-stringify-position: 4.0.0
+    transitivePeerDependencies:
+      - supports-color
+
+  mdast-util-frontmatter@2.0.1:
+    dependencies:
+      '@types/mdast': 4.0.3
+      devlop: 1.1.0
+      escape-string-regexp: 5.0.0
+      mdast-util-from-markdown: 2.0.0
+      mdast-util-to-markdown: 2.1.0
+      micromark-extension-frontmatter: 2.0.0
+    transitivePeerDependencies:
+      - supports-color
+
+  mdast-util-gfm-autolink-literal@2.0.0:
+    dependencies:
+      '@types/mdast': 4.0.3
+      ccount: 2.0.1
+      devlop: 1.1.0
+      mdast-util-find-and-replace: 3.0.1
+      micromark-util-character: 2.1.0
+
+  mdast-util-gfm-footnote@2.0.0:
+    dependencies:
+      '@types/mdast': 4.0.3
+      devlop: 1.1.0
+      mdast-util-from-markdown: 2.0.0
+      mdast-util-to-markdown: 2.1.0
+      micromark-util-normalize-identifier: 2.0.0
+    transitivePeerDependencies:
+      - supports-color
+
+  mdast-util-gfm-strikethrough@2.0.0:
+    dependencies:
+      '@types/mdast': 4.0.3
+      mdast-util-from-markdown: 2.0.0
+      mdast-util-to-markdown: 2.1.0
+    transitivePeerDependencies:
+      - supports-color
+
+  mdast-util-gfm-table@2.0.0:
+    dependencies:
+      '@types/mdast': 4.0.3
+      devlop: 1.1.0
+      markdown-table: 3.0.3
+      mdast-util-from-markdown: 2.0.0
+      mdast-util-to-markdown: 2.1.0
+    transitivePeerDependencies:
+      - supports-color
+
+  mdast-util-gfm-task-list-item@2.0.0:
+    dependencies:
+      '@types/mdast': 4.0.3
+      devlop: 1.1.0
+      mdast-util-from-markdown: 2.0.0
+      mdast-util-to-markdown: 2.1.0
+    transitivePeerDependencies:
+      - supports-color
+
+  mdast-util-gfm@3.0.0:
+    dependencies:
+      mdast-util-from-markdown: 2.0.0
+      mdast-util-gfm-autolink-literal: 2.0.0
+      mdast-util-gfm-footnote: 2.0.0
+      mdast-util-gfm-strikethrough: 2.0.0
+      mdast-util-gfm-table: 2.0.0
+      mdast-util-gfm-task-list-item: 2.0.0
+      mdast-util-to-markdown: 2.1.0
+    transitivePeerDependencies:
+      - supports-color
+
+  mdast-util-mdx-expression@2.0.0:
+    dependencies:
+      '@types/estree-jsx': 1.0.4
+      '@types/hast': 3.0.4
+      '@types/mdast': 4.0.3
+      devlop: 1.1.0
+      mdast-util-from-markdown: 2.0.0
+      mdast-util-to-markdown: 2.1.0
+    transitivePeerDependencies:
+      - supports-color
+
+  mdast-util-mdx-jsx@3.1.0:
+    dependencies:
+      '@types/estree-jsx': 1.0.4
+      '@types/hast': 3.0.4
+      '@types/mdast': 4.0.3
+      '@types/unist': 3.0.2
+      ccount: 2.0.1
+      devlop: 1.1.0
+      mdast-util-from-markdown: 2.0.0
+      mdast-util-to-markdown: 2.1.0
+      parse-entities: 4.0.1
+      stringify-entities: 4.0.3
+      unist-util-remove-position: 5.0.0
+      unist-util-stringify-position: 4.0.0
+      vfile-message: 4.0.2
+    transitivePeerDependencies:
+      - supports-color
+
+  mdast-util-mdx@3.0.0:
+    dependencies:
+      mdast-util-from-markdown: 2.0.0
+      mdast-util-mdx-expression: 2.0.0
+      mdast-util-mdx-jsx: 3.1.0
+      mdast-util-mdxjs-esm: 2.0.1
+      mdast-util-to-markdown: 2.1.0
+    transitivePeerDependencies:
+      - supports-color
+
+  mdast-util-mdxjs-esm@2.0.1:
+    dependencies:
+      '@types/estree-jsx': 1.0.4
+      '@types/hast': 3.0.4
+      '@types/mdast': 4.0.3
+      devlop: 1.1.0
+      mdast-util-from-markdown: 2.0.0
+      mdast-util-to-markdown: 2.1.0
+    transitivePeerDependencies:
+      - supports-color
+
+  mdast-util-phrasing@4.1.0:
+    dependencies:
+      '@types/mdast': 4.0.3
+      unist-util-is: 6.0.0
+
+  mdast-util-to-hast@13.1.0:
+    dependencies:
+      '@types/hast': 3.0.4
+      '@types/mdast': 4.0.3
+      '@ungap/structured-clone': 1.2.0
+      devlop: 1.1.0
+      micromark-util-sanitize-uri: 2.0.0
+      trim-lines: 3.0.1
+      unist-util-position: 5.0.0
+      unist-util-visit: 5.0.0
+      vfile: 6.0.1
+
+  mdast-util-to-markdown@2.1.0:
+    dependencies:
+      '@types/mdast': 4.0.3
+      '@types/unist': 3.0.2
+      longest-streak: 3.1.0
+      mdast-util-phrasing: 4.1.0
+      mdast-util-to-string: 4.0.0
+      micromark-util-decode-string: 2.0.0
+      unist-util-visit: 5.0.0
+      zwitch: 2.0.4
+
+  mdast-util-to-string@3.2.0:
+    dependencies:
+      '@types/mdast': 3.0.10
+
+  mdast-util-to-string@4.0.0:
+    dependencies:
+      '@types/mdast': 4.0.3
+
+  mdn-data@2.0.28: {}
+
+  mdn-data@2.0.30: {}
+
+  media-typer@0.3.0: {}
+
+  memfs@3.4.12:
+    dependencies:
+      fs-monkey: 1.0.3
+
+  merge-descriptors@1.0.1: {}
+
+  merge-stream@2.0.0: {}
+
+  merge2@1.4.1: {}
+
+  mermaid@10.8.0:
+    dependencies:
+      '@braintree/sanitize-url': 6.0.4
+      '@types/d3-scale': 4.0.8
+      '@types/d3-scale-chromatic': 3.0.3
+      cytoscape: 3.28.1
+      cytoscape-cose-bilkent: 4.1.0(cytoscape@3.28.1)
+      d3: 7.8.5
+      d3-sankey: 0.12.3
+      dagre-d3-es: 7.0.10
+      dayjs: 1.11.10
+      dompurify: 3.0.8
+      elkjs: 0.9.1
+      khroma: 2.0.0
+      lodash-es: 4.17.21
+      mdast-util-from-markdown: 1.3.1
+      non-layered-tidy-tree-layout: 2.0.2
+      stylis: 4.3.0
+      ts-dedent: 2.2.0
+      uuid: 9.0.1
+      web-worker: 1.2.0
+    transitivePeerDependencies:
+      - supports-color
+
+  methods@1.1.2: {}
+
+  micromark-core-commonmark@1.1.0:
+    dependencies:
+      decode-named-character-reference: 1.0.2
+      micromark-factory-destination: 1.1.0
+      micromark-factory-label: 1.1.0
+      micromark-factory-space: 1.1.0
+      micromark-factory-title: 1.1.0
+      micromark-factory-whitespace: 1.1.0
+      micromark-util-character: 1.2.0
+      micromark-util-chunked: 1.1.0
+      micromark-util-classify-character: 1.1.0
+      micromark-util-html-tag-name: 1.2.0
+      micromark-util-normalize-identifier: 1.1.0
+      micromark-util-resolve-all: 1.1.0
+      micromark-util-subtokenize: 1.1.0
+      micromark-util-symbol: 1.1.0
+      micromark-util-types: 1.1.0
+      uvu: 0.5.6
+
+  micromark-core-commonmark@2.0.0:
+    dependencies:
+      decode-named-character-reference: 1.0.2
+      devlop: 1.1.0
+      micromark-factory-destination: 2.0.0
+      micromark-factory-label: 2.0.0
+      micromark-factory-space: 2.0.0
+      micromark-factory-title: 2.0.0
+      micromark-factory-whitespace: 2.0.0
+      micromark-util-character: 2.1.0
+      micromark-util-chunked: 2.0.0
+      micromark-util-classify-character: 2.0.0
+      micromark-util-html-tag-name: 2.0.0
+      micromark-util-normalize-identifier: 2.0.0
+      micromark-util-resolve-all: 2.0.0
+      micromark-util-subtokenize: 2.0.0
+      micromark-util-symbol: 2.0.0
+      micromark-util-types: 2.0.0
+
+  micromark-extension-directive@3.0.0:
+    dependencies:
+      devlop: 1.1.0
+      micromark-factory-space: 2.0.0
+      micromark-factory-whitespace: 2.0.0
+      micromark-util-character: 2.1.0
+      micromark-util-symbol: 2.0.0
+      micromark-util-types: 2.0.0
+      parse-entities: 4.0.1
+
+  micromark-extension-frontmatter@2.0.0:
+    dependencies:
+      fault: 2.0.1
+      micromark-util-character: 2.1.0
+      micromark-util-symbol: 2.0.0
+      micromark-util-types: 2.0.0
+
+  micromark-extension-gfm-autolink-literal@2.0.0:
+    dependencies:
+      micromark-util-character: 2.1.0
+      micromark-util-sanitize-uri: 2.0.0
+      micromark-util-symbol: 2.0.0
+      micromark-util-types: 2.0.0
+
+  micromark-extension-gfm-footnote@2.0.0:
+    dependencies:
+      devlop: 1.1.0
+      micromark-core-commonmark: 2.0.0
+      micromark-factory-space: 2.0.0
+      micromark-util-character: 2.1.0
+      micromark-util-normalize-identifier: 2.0.0
+      micromark-util-sanitize-uri: 2.0.0
+      micromark-util-symbol: 2.0.0
+      micromark-util-types: 2.0.0
+
+  micromark-extension-gfm-strikethrough@2.0.0:
+    dependencies:
+      devlop: 1.1.0
+      micromark-util-chunked: 2.0.0
+      micromark-util-classify-character: 2.0.0
+      micromark-util-resolve-all: 2.0.0
+      micromark-util-symbol: 2.0.0
+      micromark-util-types: 2.0.0
+
+  micromark-extension-gfm-table@2.0.0:
+    dependencies:
+      devlop: 1.1.0
+      micromark-factory-space: 2.0.0
+      micromark-util-character: 2.1.0
+      micromark-util-symbol: 2.0.0
+      micromark-util-types: 2.0.0
+
+  micromark-extension-gfm-tagfilter@2.0.0:
+    dependencies:
+      micromark-util-types: 2.0.0
+
+  micromark-extension-gfm-task-list-item@2.0.1:
+    dependencies:
+      devlop: 1.1.0
+      micromark-factory-space: 2.0.0
+      micromark-util-character: 2.1.0
+      micromark-util-symbol: 2.0.0
+      micromark-util-types: 2.0.0
+
+  micromark-extension-gfm@3.0.0:
+    dependencies:
+      micromark-extension-gfm-autolink-literal: 2.0.0
+      micromark-extension-gfm-footnote: 2.0.0
+      micromark-extension-gfm-strikethrough: 2.0.0
+      micromark-extension-gfm-table: 2.0.0
+      micromark-extension-gfm-tagfilter: 2.0.0
+      micromark-extension-gfm-task-list-item: 2.0.1
+      micromark-util-combine-extensions: 2.0.0
+      micromark-util-types: 2.0.0
+
+  micromark-extension-mdx-expression@3.0.0:
+    dependencies:
+      '@types/estree': 1.0.5
+      devlop: 1.1.0
+      micromark-factory-mdx-expression: 2.0.1
+      micromark-factory-space: 2.0.0
+      micromark-util-character: 2.1.0
+      micromark-util-events-to-acorn: 2.0.2
+      micromark-util-symbol: 2.0.0
+      micromark-util-types: 2.0.0
+
+  micromark-extension-mdx-jsx@3.0.0:
+    dependencies:
+      '@types/acorn': 4.0.6
+      '@types/estree': 1.0.5
+      devlop: 1.1.0
+      estree-util-is-identifier-name: 3.0.0
+      micromark-factory-mdx-expression: 2.0.1
+      micromark-factory-space: 2.0.0
+      micromark-util-character: 2.1.0
+      micromark-util-symbol: 2.0.0
+      micromark-util-types: 2.0.0
+      vfile-message: 4.0.2
+
+  micromark-extension-mdx-md@2.0.0:
+    dependencies:
+      micromark-util-types: 2.0.0
+
+  micromark-extension-mdxjs-esm@3.0.0:
+    dependencies:
+      '@types/estree': 1.0.5
+      devlop: 1.1.0
+      micromark-core-commonmark: 2.0.0
+      micromark-util-character: 2.1.0
+      micromark-util-events-to-acorn: 2.0.2
+      micromark-util-symbol: 2.0.0
+      micromark-util-types: 2.0.0
+      unist-util-position-from-estree: 2.0.0
+      vfile-message: 4.0.2
+
+  micromark-extension-mdxjs@3.0.0:
+    dependencies:
+      acorn: 8.11.3
+      acorn-jsx: 5.3.2(acorn@8.11.3)
+      micromark-extension-mdx-expression: 3.0.0
+      micromark-extension-mdx-jsx: 3.0.0
+      micromark-extension-mdx-md: 2.0.0
+      micromark-extension-mdxjs-esm: 3.0.0
+      micromark-util-combine-extensions: 2.0.0
+      micromark-util-types: 2.0.0
+
+  micromark-factory-destination@1.1.0:
+    dependencies:
+      micromark-util-character: 1.2.0
+      micromark-util-symbol: 1.1.0
+      micromark-util-types: 1.1.0
+
+  micromark-factory-destination@2.0.0:
+    dependencies:
+      micromark-util-character: 2.1.0
+      micromark-util-symbol: 2.0.0
+      micromark-util-types: 2.0.0
+
+  micromark-factory-label@1.1.0:
+    dependencies:
+      micromark-util-character: 1.2.0
+      micromark-util-symbol: 1.1.0
+      micromark-util-types: 1.1.0
+      uvu: 0.5.6
 
-  /lilconfig@2.0.6:
-    resolution: {integrity: sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==}
-    engines: {node: '>=10'}
+  micromark-factory-label@2.0.0:
+    dependencies:
+      devlop: 1.1.0
+      micromark-util-character: 2.1.0
+      micromark-util-symbol: 2.0.0
+      micromark-util-types: 2.0.0
 
-  /lines-and-columns@1.2.4:
-    resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
-    dev: false
+  micromark-factory-mdx-expression@2.0.1:
+    dependencies:
+      '@types/estree': 1.0.5
+      devlop: 1.1.0
+      micromark-util-character: 2.1.0
+      micromark-util-events-to-acorn: 2.0.2
+      micromark-util-symbol: 2.0.0
+      micromark-util-types: 2.0.0
+      unist-util-position-from-estree: 2.0.0
+      vfile-message: 4.0.2
 
-  /loader-runner@4.3.0:
-    resolution: {integrity: sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==}
-    engines: {node: '>=6.11.5'}
+  micromark-factory-space@1.1.0:
+    dependencies:
+      micromark-util-character: 1.2.0
+      micromark-util-types: 1.1.0
 
-  /loader-utils@2.0.4:
-    resolution: {integrity: sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==}
-    engines: {node: '>=8.9.0'}
+  micromark-factory-space@2.0.0:
     dependencies:
-      big.js: 5.2.2
-      emojis-list: 3.0.0
-      json5: 2.2.3
-    dev: false
+      micromark-util-character: 2.1.0
+      micromark-util-types: 2.0.0
 
-  /loader-utils@3.2.1:
-    resolution: {integrity: sha512-ZvFw1KWS3GVyYBYb7qkmRM/WwL2TQQBxgCK62rlvm4WpVQ23Nb4tYjApUlfjrEGvOs7KHEsmyUn75OHZrJMWPw==}
-    engines: {node: '>= 12.13.0'}
-    dev: false
+  micromark-factory-title@1.1.0:
+    dependencies:
+      micromark-factory-space: 1.1.0
+      micromark-util-character: 1.2.0
+      micromark-util-symbol: 1.1.0
+      micromark-util-types: 1.1.0
 
-  /locate-path@3.0.0:
-    resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==}
-    engines: {node: '>=6'}
+  micromark-factory-title@2.0.0:
     dependencies:
-      p-locate: 3.0.0
-      path-exists: 3.0.0
-    dev: false
+      micromark-factory-space: 2.0.0
+      micromark-util-character: 2.1.0
+      micromark-util-symbol: 2.0.0
+      micromark-util-types: 2.0.0
 
-  /locate-path@5.0.0:
-    resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==}
-    engines: {node: '>=8'}
+  micromark-factory-whitespace@1.1.0:
     dependencies:
-      p-locate: 4.1.0
-    dev: false
+      micromark-factory-space: 1.1.0
+      micromark-util-character: 1.2.0
+      micromark-util-symbol: 1.1.0
+      micromark-util-types: 1.1.0
 
-  /locate-path@6.0.0:
-    resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
-    engines: {node: '>=10'}
+  micromark-factory-whitespace@2.0.0:
     dependencies:
-      p-locate: 5.0.0
-    dev: false
+      micromark-factory-space: 2.0.0
+      micromark-util-character: 2.1.0
+      micromark-util-symbol: 2.0.0
+      micromark-util-types: 2.0.0
 
-  /lodash-es@4.17.21:
-    resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==}
-    dev: false
+  micromark-util-character@1.2.0:
+    dependencies:
+      micromark-util-symbol: 1.1.0
+      micromark-util-types: 1.1.0
 
-  /lodash.castarray@4.4.0:
-    resolution: {integrity: sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==}
-    dev: true
+  micromark-util-character@2.1.0:
+    dependencies:
+      micromark-util-symbol: 2.0.0
+      micromark-util-types: 2.0.0
 
-  /lodash.curry@4.1.1:
-    resolution: {integrity: sha512-/u14pXGviLaweY5JI0IUzgzF2J6Ne8INyzAZjImcryjgkZ+ebruBxy2/JaOOkTqScddcYtakjhSaeemV8lR0tA==}
-    dev: false
+  micromark-util-chunked@1.1.0:
+    dependencies:
+      micromark-util-symbol: 1.1.0
 
-  /lodash.debounce@4.0.8:
-    resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==}
-    dev: false
+  micromark-util-chunked@2.0.0:
+    dependencies:
+      micromark-util-symbol: 2.0.0
 
-  /lodash.flow@3.5.0:
-    resolution: {integrity: sha512-ff3BX/tSioo+XojX4MOsOMhJw0nZoUEF011LX8g8d3gvjVbxd89cCio4BCXronjxcTUIJUoqKEUA+n4CqvvRPw==}
-    dev: false
+  micromark-util-classify-character@1.1.0:
+    dependencies:
+      micromark-util-character: 1.2.0
+      micromark-util-symbol: 1.1.0
+      micromark-util-types: 1.1.0
 
-  /lodash.isplainobject@4.0.6:
-    resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==}
-    dev: true
+  micromark-util-classify-character@2.0.0:
+    dependencies:
+      micromark-util-character: 2.1.0
+      micromark-util-symbol: 2.0.0
+      micromark-util-types: 2.0.0
 
-  /lodash.memoize@4.1.2:
-    resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==}
-    dev: false
+  micromark-util-combine-extensions@1.1.0:
+    dependencies:
+      micromark-util-chunked: 1.1.0
+      micromark-util-types: 1.1.0
 
-  /lodash.merge@4.6.2:
-    resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
-    dev: true
+  micromark-util-combine-extensions@2.0.0:
+    dependencies:
+      micromark-util-chunked: 2.0.0
+      micromark-util-types: 2.0.0
 
-  /lodash.uniq@4.5.0:
-    resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==}
-    dev: false
+  micromark-util-decode-numeric-character-reference@1.1.0:
+    dependencies:
+      micromark-util-symbol: 1.1.0
 
-  /lodash@4.17.21:
-    resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
-    dev: false
+  micromark-util-decode-numeric-character-reference@2.0.1:
+    dependencies:
+      micromark-util-symbol: 2.0.0
 
-  /loose-envify@1.4.0:
-    resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
-    hasBin: true
+  micromark-util-decode-string@1.1.0:
     dependencies:
-      js-tokens: 4.0.0
+      decode-named-character-reference: 1.0.2
+      micromark-util-character: 1.2.0
+      micromark-util-decode-numeric-character-reference: 1.1.0
+      micromark-util-symbol: 1.1.0
 
-  /lower-case@2.0.2:
-    resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==}
+  micromark-util-decode-string@2.0.0:
     dependencies:
-      tslib: 2.4.1
-    dev: false
+      decode-named-character-reference: 1.0.2
+      micromark-util-character: 2.1.0
+      micromark-util-decode-numeric-character-reference: 2.0.1
+      micromark-util-symbol: 2.0.0
 
-  /lowercase-keys@1.0.1:
-    resolution: {integrity: sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==}
-    engines: {node: '>=0.10.0'}
-    dev: false
+  micromark-util-encode@1.1.0: {}
 
-  /lowercase-keys@2.0.0:
-    resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==}
-    engines: {node: '>=8'}
-    dev: false
+  micromark-util-encode@2.0.0: {}
 
-  /lru-cache@5.1.1:
-    resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
+  micromark-util-events-to-acorn@2.0.2:
     dependencies:
-      yallist: 3.1.1
-    dev: false
+      '@types/acorn': 4.0.6
+      '@types/estree': 1.0.5
+      '@types/unist': 3.0.2
+      devlop: 1.1.0
+      estree-util-visit: 2.0.0
+      micromark-util-symbol: 2.0.0
+      micromark-util-types: 2.0.0
+      vfile-message: 4.0.2
 
-  /lru-cache@6.0.0:
-    resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}
-    engines: {node: '>=10'}
+  micromark-util-html-tag-name@1.2.0: {}
+
+  micromark-util-html-tag-name@2.0.0: {}
+
+  micromark-util-normalize-identifier@1.1.0:
     dependencies:
-      yallist: 4.0.0
-    dev: false
+      micromark-util-symbol: 1.1.0
 
-  /make-dir@3.1.0:
-    resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==}
-    engines: {node: '>=8'}
+  micromark-util-normalize-identifier@2.0.0:
     dependencies:
-      semver: 6.3.0
-    dev: false
+      micromark-util-symbol: 2.0.0
 
-  /markdown-escapes@1.0.4:
-    resolution: {integrity: sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg==}
-    dev: false
+  micromark-util-resolve-all@1.1.0:
+    dependencies:
+      micromark-util-types: 1.1.0
 
-  /mdast-squeeze-paragraphs@4.0.0:
-    resolution: {integrity: sha512-zxdPn69hkQ1rm4J+2Cs2j6wDEv7O17TfXTJ33tl/+JPIoEmtV9t2ZzBM5LPHE8QlHsmVD8t3vPKCyY3oH+H8MQ==}
+  micromark-util-resolve-all@2.0.0:
     dependencies:
-      unist-util-remove: 2.1.0
-    dev: false
+      micromark-util-types: 2.0.0
 
-  /mdast-util-definitions@4.0.0:
-    resolution: {integrity: sha512-k8AJ6aNnUkB7IE+5azR9h81O5EQ/cTDXtWdMq9Kk5KcEW/8ritU5CeLg/9HhOC++nALHBlaogJ5jz0Ybk3kPMQ==}
+  micromark-util-sanitize-uri@1.2.0:
     dependencies:
-      unist-util-visit: 2.0.3
-    dev: false
+      micromark-util-character: 1.2.0
+      micromark-util-encode: 1.1.0
+      micromark-util-symbol: 1.1.0
 
-  /mdast-util-to-hast@10.0.1:
-    resolution: {integrity: sha512-BW3LM9SEMnjf4HXXVApZMt8gLQWVNXc3jryK0nJu/rOXPOnlkUjmdkDlmxMirpbU9ILncGFIwLH/ubnWBbcdgA==}
+  micromark-util-sanitize-uri@2.0.0:
     dependencies:
-      '@types/mdast': 3.0.10
-      '@types/unist': 2.0.6
-      mdast-util-definitions: 4.0.0
-      mdurl: 1.0.1
-      unist-builder: 2.0.3
-      unist-util-generated: 1.1.6
-      unist-util-position: 3.1.0
-      unist-util-visit: 2.0.3
-    dev: false
-
-  /mdast-util-to-string@2.0.0:
-    resolution: {integrity: sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w==}
-    dev: false
-
-  /mdn-data@2.0.14:
-    resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==}
-    dev: false
-
-  /mdurl@1.0.1:
-    resolution: {integrity: sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==}
-    dev: false
-
-  /media-typer@0.3.0:
-    resolution: {integrity: sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=}
-    engines: {node: '>= 0.6'}
-    dev: false
+      micromark-util-character: 2.1.0
+      micromark-util-encode: 2.0.0
+      micromark-util-symbol: 2.0.0
 
-  /medium-zoom@1.0.8:
-    resolution: {integrity: sha512-CjFVuFq/IfrdqesAXfg+hzlDKu6A2n80ZIq0Kl9kWjoHh9j1N9Uvk5X0/MmN0hOfm5F9YBswlClhcwnmtwz7gA==}
-    dev: false
+  micromark-util-subtokenize@1.1.0:
+    dependencies:
+      micromark-util-chunked: 1.1.0
+      micromark-util-symbol: 1.1.0
+      micromark-util-types: 1.1.0
+      uvu: 0.5.6
 
-  /memfs@3.4.12:
-    resolution: {integrity: sha512-BcjuQn6vfqP+k100e0E9m61Hyqa//Brp+I3f0OBmN0ATHlFA8vx3Lt8z57R3u2bPqe3WGDBC+nF72fTH7isyEw==}
-    engines: {node: '>= 4.0.0'}
+  micromark-util-subtokenize@2.0.0:
     dependencies:
-      fs-monkey: 1.0.3
-    dev: false
+      devlop: 1.1.0
+      micromark-util-chunked: 2.0.0
+      micromark-util-symbol: 2.0.0
+      micromark-util-types: 2.0.0
 
-  /merge-descriptors@1.0.1:
-    resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==}
-    dev: false
+  micromark-util-symbol@1.1.0: {}
 
-  /merge-stream@2.0.0:
-    resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
+  micromark-util-symbol@2.0.0: {}
 
-  /merge2@1.4.1:
-    resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
-    engines: {node: '>= 8'}
+  micromark-util-types@1.1.0: {}
 
-  /mermaid@9.4.3:
-    resolution: {integrity: sha512-TLkQEtqhRSuEHSE34lh5bCa94KATCyluAXmFnNI2PRZwOpXFeqiJWwZl+d2CcemE1RS6QbbueSSq9QIg8Uxcyw==}
+  micromark-util-types@2.0.0: {}
+
+  micromark@3.2.0:
     dependencies:
-      '@braintree/sanitize-url': 6.0.4
-      cytoscape: 3.26.0
-      cytoscape-cose-bilkent: 4.1.0(cytoscape@3.26.0)
-      cytoscape-fcose: 2.2.0(cytoscape@3.26.0)
-      d3: 7.8.5
-      dagre-d3-es: 7.0.9
-      dayjs: 1.11.10
-      dompurify: 2.4.3
-      elkjs: 0.8.2
-      khroma: 2.0.0
-      lodash-es: 4.17.21
-      non-layered-tidy-tree-layout: 2.0.2
-      stylis: 4.3.0
-      ts-dedent: 2.2.0
-      uuid: 9.0.1
-      web-worker: 1.2.0
-    dev: false
+      '@types/debug': 4.1.12
+      debug: 4.3.4
+      decode-named-character-reference: 1.0.2
+      micromark-core-commonmark: 1.1.0
+      micromark-factory-space: 1.1.0
+      micromark-util-character: 1.2.0
+      micromark-util-chunked: 1.1.0
+      micromark-util-combine-extensions: 1.1.0
+      micromark-util-decode-numeric-character-reference: 1.1.0
+      micromark-util-encode: 1.1.0
+      micromark-util-normalize-identifier: 1.1.0
+      micromark-util-resolve-all: 1.1.0
+      micromark-util-sanitize-uri: 1.2.0
+      micromark-util-subtokenize: 1.1.0
+      micromark-util-symbol: 1.1.0
+      micromark-util-types: 1.1.0
+      uvu: 0.5.6
+    transitivePeerDependencies:
+      - supports-color
 
-  /methods@1.1.2:
-    resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==}
-    engines: {node: '>= 0.6'}
-    dev: false
+  micromark@4.0.0:
+    dependencies:
+      '@types/debug': 4.1.12
+      debug: 4.3.4
+      decode-named-character-reference: 1.0.2
+      devlop: 1.1.0
+      micromark-core-commonmark: 2.0.0
+      micromark-factory-space: 2.0.0
+      micromark-util-character: 2.1.0
+      micromark-util-chunked: 2.0.0
+      micromark-util-combine-extensions: 2.0.0
+      micromark-util-decode-numeric-character-reference: 2.0.1
+      micromark-util-encode: 2.0.0
+      micromark-util-normalize-identifier: 2.0.0
+      micromark-util-resolve-all: 2.0.0
+      micromark-util-sanitize-uri: 2.0.0
+      micromark-util-subtokenize: 2.0.0
+      micromark-util-symbol: 2.0.0
+      micromark-util-types: 2.0.0
+    transitivePeerDependencies:
+      - supports-color
 
-  /micromatch@4.0.5:
-    resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==}
-    engines: {node: '>=8.6'}
+  micromatch@4.0.5:
     dependencies:
       braces: 3.0.2
       picomatch: 2.3.1
 
-  /mime-db@1.33.0:
-    resolution: {integrity: sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==}
-    engines: {node: '>= 0.6'}
-    dev: false
+  micromatch@4.0.8:
+    dependencies:
+      braces: 3.0.3
+      picomatch: 2.3.1
 
-  /mime-db@1.52.0:
-    resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
-    engines: {node: '>= 0.6'}
+  mime-db@1.33.0: {}
 
-  /mime-types@2.1.18:
-    resolution: {integrity: sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==}
-    engines: {node: '>= 0.6'}
+  mime-db@1.52.0: {}
+
+  mime-types@2.1.18:
     dependencies:
       mime-db: 1.33.0
-    dev: false
 
-  /mime-types@2.1.35:
-    resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
-    engines: {node: '>= 0.6'}
+  mime-types@2.1.35:
     dependencies:
       mime-db: 1.52.0
 
-  /mime@1.6.0:
-    resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==}
-    engines: {node: '>=4'}
-    hasBin: true
-    dev: false
+  mime@1.6.0: {}
 
-  /mimic-fn@2.1.0:
-    resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
-    engines: {node: '>=6'}
-    dev: false
+  mimic-fn@2.1.0: {}
 
-  /mimic-response@1.0.1:
-    resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==}
-    engines: {node: '>=4'}
-    dev: false
+  mimic-response@3.1.0: {}
 
-  /mini-css-extract-plugin@2.7.2(webpack@5.75.0):
-    resolution: {integrity: sha512-EdlUizq13o0Pd+uCp+WO/JpkLvHRVGt97RqfeGhXqAcorYo1ypJSpkV+WDT0vY/kmh/p7wRdJNJtuyK540PXDw==}
-    engines: {node: '>= 12.13.0'}
-    peerDependencies:
-      webpack: ^5.0.0
+  mimic-response@4.0.0: {}
+
+  mini-css-extract-plugin@2.8.0(webpack@5.90.1):
     dependencies:
       schema-utils: 4.0.0
-      webpack: 5.75.0
-    dev: false
+      tapable: 2.2.1
+      webpack: 5.90.1
 
-  /minimalistic-assert@1.0.1:
-    resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==}
-    dev: false
+  minimalistic-assert@1.0.1: {}
 
-  /minimatch@3.1.2:
-    resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
+  minimatch@3.1.2:
     dependencies:
       brace-expansion: 1.1.11
-    dev: false
 
-  /minimist@1.2.7:
-    resolution: {integrity: sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==}
+  minimatch@9.0.5:
+    dependencies:
+      brace-expansion: 2.0.1
 
-  /mrmime@1.0.1:
-    resolution: {integrity: sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==}
-    engines: {node: '>=10'}
-    dev: false
+  minimist@1.2.7: {}
 
-  /ms@2.0.0:
-    resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
-    dev: false
+  minipass@7.1.2: {}
 
-  /ms@2.1.2:
-    resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
-    dev: false
+  mri@1.2.0: {}
 
-  /ms@2.1.3:
-    resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
-    dev: false
+  mrmime@2.0.0: {}
 
-  /multicast-dns@7.2.5:
-    resolution: {integrity: sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==}
-    hasBin: true
+  ms@2.0.0: {}
+
+  ms@2.1.2: {}
+
+  ms@2.1.3: {}
+
+  multicast-dns@7.2.5:
     dependencies:
       dns-packet: 5.4.0
       thunky: 1.1.0
-    dev: false
 
-  /nanoid@3.3.4:
-    resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==}
-    engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
-    hasBin: true
+  mz@2.7.0:
+    dependencies:
+      any-promise: 1.3.0
+      object-assign: 4.1.1
+      thenify-all: 1.6.0
 
-  /negotiator@0.6.3:
-    resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
-    engines: {node: '>= 0.6'}
-    dev: false
+  nanoid@3.3.4: {}
 
-  /neo-async@2.6.2:
-    resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
+  nanoid@3.3.7: {}
 
-  /no-case@3.0.4:
-    resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==}
-    dependencies:
-      lower-case: 2.0.2
-      tslib: 2.4.1
-    dev: false
+  negotiator@0.6.3: {}
 
-  /node-emoji@1.11.0:
-    resolution: {integrity: sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==}
+  neo-async@2.6.2: {}
+
+  no-case@3.0.4:
     dependencies:
-      lodash: 4.17.21
-    dev: false
+      lower-case: 2.0.2
+      tslib: 2.6.2
 
-  /node-fetch@2.6.7:
-    resolution: {integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==}
-    engines: {node: 4.x || >=6.0.0}
-    peerDependencies:
-      encoding: ^0.1.0
-    peerDependenciesMeta:
-      encoding:
-        optional: true
+  node-emoji@2.1.3:
     dependencies:
-      whatwg-url: 5.0.0
-    dev: false
+      '@sindresorhus/is': 4.6.0
+      char-regex: 1.0.2
+      emojilib: 2.4.0
+      skin-tone: 2.0.0
 
-  /node-forge@1.3.1:
-    resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==}
-    engines: {node: '>= 6.13.0'}
-    dev: false
+  node-forge@1.3.1: {}
 
-  /node-releases@2.0.8:
-    resolution: {integrity: sha512-dFSmB8fFHEH/s81Xi+Y/15DQY6VHW81nXRj86EMSL3lmuTmK1e+aT4wrFCkTbm+gSwkw4KpX+rT/pMM2c1mF+A==}
+  node-releases@2.0.14: {}
 
-  /non-layered-tidy-tree-layout@2.0.2:
-    resolution: {integrity: sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw==}
-    dev: false
+  node-releases@2.0.8: {}
 
-  /normalize-path@3.0.0:
-    resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
-    engines: {node: '>=0.10.0'}
+  non-layered-tidy-tree-layout@2.0.2: {}
 
-  /normalize-range@0.1.2:
-    resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==}
-    engines: {node: '>=0.10.0'}
-    dev: false
+  normalize-path@3.0.0: {}
 
-  /normalize-url@4.5.1:
-    resolution: {integrity: sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==}
-    engines: {node: '>=8'}
-    dev: false
+  normalize-range@0.1.2: {}
 
-  /normalize-url@6.1.0:
-    resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==}
-    engines: {node: '>=10'}
-    dev: false
+  normalize-url@8.0.0: {}
 
-  /npm-run-path@4.0.1:
-    resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==}
-    engines: {node: '>=8'}
+  npm-run-path@4.0.1:
     dependencies:
       path-key: 3.1.1
-    dev: false
 
-  /nprogress@0.2.0:
-    resolution: {integrity: sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==}
-    dev: false
+  nprogress@0.2.0: {}
 
-  /nth-check@2.1.1:
-    resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
+  nth-check@2.1.1:
     dependencies:
       boolbase: 1.0.0
-    dev: false
 
-  /object-assign@4.1.1:
-    resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
-    engines: {node: '>=0.10.0'}
+  object-assign@4.1.1: {}
 
-  /object-hash@3.0.0:
-    resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==}
-    engines: {node: '>= 6'}
+  object-hash@3.0.0: {}
 
-  /object-inspect@1.12.2:
-    resolution: {integrity: sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==}
-    dev: false
+  object-inspect@1.12.2: {}
 
-  /object-keys@1.1.1:
-    resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==}
-    engines: {node: '>= 0.4'}
-    dev: false
+  object-keys@1.1.1: {}
 
-  /object.assign@4.1.4:
-    resolution: {integrity: sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==}
-    engines: {node: '>= 0.4'}
+  object.assign@4.1.4:
     dependencies:
       call-bind: 1.0.2
       define-properties: 1.1.4
       has-symbols: 1.0.3
       object-keys: 1.1.1
-    dev: false
 
-  /obuf@1.1.2:
-    resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==}
-    dev: false
+  obuf@1.1.2: {}
 
-  /on-finished@2.4.1:
-    resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
-    engines: {node: '>= 0.8'}
+  on-finished@2.4.1:
     dependencies:
       ee-first: 1.1.1
-    dev: false
 
-  /on-headers@1.0.2:
-    resolution: {integrity: sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==}
-    engines: {node: '>= 0.8'}
-    dev: false
+  on-headers@1.0.2: {}
 
-  /once@1.4.0:
-    resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
+  once@1.4.0:
     dependencies:
       wrappy: 1.0.2
-    dev: false
 
-  /onetime@5.1.2:
-    resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==}
-    engines: {node: '>=6'}
+  onetime@5.1.2:
     dependencies:
       mimic-fn: 2.1.0
-    dev: false
 
-  /open@8.4.0:
-    resolution: {integrity: sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==}
-    engines: {node: '>=12'}
+  open@8.4.0:
     dependencies:
       define-lazy-prop: 2.0.0
       is-docker: 2.2.1
       is-wsl: 2.2.0
-    dev: false
 
-  /opener@1.5.2:
-    resolution: {integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==}
-    hasBin: true
-    dev: false
+  opener@1.5.2: {}
 
-  /p-cancelable@1.1.0:
-    resolution: {integrity: sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==}
-    engines: {node: '>=6'}
-    dev: false
+  p-cancelable@3.0.0: {}
 
-  /p-limit@2.3.0:
-    resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==}
-    engines: {node: '>=6'}
+  p-limit@2.3.0:
     dependencies:
       p-try: 2.2.0
-    dev: false
 
-  /p-limit@3.1.0:
-    resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
-    engines: {node: '>=10'}
+  p-limit@3.1.0:
     dependencies:
       yocto-queue: 0.1.0
-    dev: false
 
-  /p-locate@3.0.0:
-    resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==}
-    engines: {node: '>=6'}
+  p-limit@4.0.0:
     dependencies:
-      p-limit: 2.3.0
-    dev: false
+      yocto-queue: 1.0.0
 
-  /p-locate@4.1.0:
-    resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==}
-    engines: {node: '>=8'}
+  p-locate@3.0.0:
     dependencies:
       p-limit: 2.3.0
-    dev: false
 
-  /p-locate@5.0.0:
-    resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
-    engines: {node: '>=10'}
+  p-locate@5.0.0:
     dependencies:
       p-limit: 3.1.0
-    dev: false
 
-  /p-map@4.0.0:
-    resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==}
-    engines: {node: '>=10'}
+  p-locate@6.0.0:
+    dependencies:
+      p-limit: 4.0.0
+
+  p-map@4.0.0:
     dependencies:
       aggregate-error: 3.1.0
-    dev: false
 
-  /p-retry@4.6.2:
-    resolution: {integrity: sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==}
-    engines: {node: '>=8'}
+  p-retry@4.6.2:
     dependencies:
       '@types/retry': 0.12.0
       retry: 0.13.1
-    dev: false
 
-  /p-try@2.2.0:
-    resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
-    engines: {node: '>=6'}
-    dev: false
+  p-try@2.2.0: {}
 
-  /package-json@6.5.0:
-    resolution: {integrity: sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==}
-    engines: {node: '>=8'}
+  package-json-from-dist@1.0.0: {}
+
+  package-json@8.1.1:
     dependencies:
-      got: 9.6.0
-      registry-auth-token: 4.2.2
-      registry-url: 5.1.0
-      semver: 6.3.0
-    dev: false
+      got: 12.6.1
+      registry-auth-token: 5.0.2
+      registry-url: 6.0.1
+      semver: 7.6.0
 
-  /param-case@3.0.4:
-    resolution: {integrity: sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==}
+  param-case@3.0.4:
     dependencies:
       dot-case: 3.0.4
-      tslib: 2.4.1
-    dev: false
+      tslib: 2.6.2
 
-  /parent-module@1.0.1:
-    resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
-    engines: {node: '>=6'}
+  parent-module@1.0.1:
     dependencies:
       callsites: 3.1.0
-    dev: false
 
-  /parse-entities@2.0.0:
-    resolution: {integrity: sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==}
+  parse-entities@4.0.1:
     dependencies:
-      character-entities: 1.2.4
-      character-entities-legacy: 1.1.4
-      character-reference-invalid: 1.1.4
-      is-alphanumerical: 1.0.4
-      is-decimal: 1.0.4
-      is-hexadecimal: 1.0.4
-    dev: false
+      '@types/unist': 2.0.6
+      character-entities: 2.0.2
+      character-entities-legacy: 3.0.0
+      character-reference-invalid: 2.0.1
+      decode-named-character-reference: 1.0.2
+      is-alphanumerical: 2.0.1
+      is-decimal: 2.0.1
+      is-hexadecimal: 2.0.1
 
-  /parse-json@5.2.0:
-    resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
-    engines: {node: '>=8'}
+  parse-json@5.2.0:
     dependencies:
-      '@babel/code-frame': 7.18.6
+      '@babel/code-frame': 7.23.5
       error-ex: 1.3.2
       json-parse-even-better-errors: 2.3.1
       lines-and-columns: 1.2.4
-    dev: false
 
-  /parse-numeric-range@1.3.0:
-    resolution: {integrity: sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==}
-    dev: false
+  parse-numeric-range@1.3.0: {}
+
+  parse5-htmlparser2-tree-adapter@7.0.0:
+    dependencies:
+      domhandler: 5.0.3
+      parse5: 7.1.2
+
+  parse5@7.1.2:
+    dependencies:
+      entities: 4.4.0
+
+  parseurl@1.3.3: {}
+
+  pascal-case@3.1.2:
+    dependencies:
+      no-case: 3.0.4
+      tslib: 2.6.2
+
+  path-exists@3.0.0: {}
+
+  path-exists@4.0.0: {}
+
+  path-exists@5.0.0: {}
+
+  path-is-absolute@1.0.1: {}
+
+  path-is-inside@1.0.2: {}
+
+  path-key@3.1.1: {}
+
+  path-parse@1.0.7: {}
+
+  path-scurry@1.11.1:
+    dependencies:
+      lru-cache: 10.4.3
+      minipass: 7.1.2
+
+  path-to-regexp@0.1.7: {}
+
+  path-to-regexp@1.8.0:
+    dependencies:
+      isarray: 0.0.1
+
+  path-to-regexp@2.2.1: {}
+
+  path-type@4.0.0: {}
+
+  periscopic@3.1.0:
+    dependencies:
+      '@types/estree': 1.0.5
+      estree-walker: 3.0.3
+      is-reference: 3.0.2
+
+  picocolors@1.0.0: {}
+
+  picocolors@1.1.0: {}
+
+  picomatch@2.3.1: {}
+
+  pify@2.3.0: {}
+
+  pirates@4.0.6: {}
+
+  pkg-dir@7.0.0:
+    dependencies:
+      find-up: 6.3.0
+
+  pkg-up@3.1.0:
+    dependencies:
+      find-up: 3.0.0
+
+  postcss-calc@9.0.1(postcss@8.4.35):
+    dependencies:
+      postcss: 8.4.35
+      postcss-selector-parser: 6.1.2
+      postcss-value-parser: 4.2.0
+
+  postcss-calc@9.0.1(postcss@8.4.45):
+    dependencies:
+      postcss: 8.4.45
+      postcss-selector-parser: 6.1.2
+      postcss-value-parser: 4.2.0
+
+  postcss-colormin@6.1.0(postcss@8.4.35):
+    dependencies:
+      browserslist: 4.23.1
+      caniuse-api: 3.0.0
+      colord: 2.9.3
+      postcss: 8.4.35
+      postcss-value-parser: 4.2.0
+
+  postcss-colormin@6.1.0(postcss@8.4.45):
+    dependencies:
+      browserslist: 4.23.1
+      caniuse-api: 3.0.0
+      colord: 2.9.3
+      postcss: 8.4.45
+      postcss-value-parser: 4.2.0
+
+  postcss-convert-values@6.1.0(postcss@8.4.35):
+    dependencies:
+      browserslist: 4.23.1
+      postcss: 8.4.35
+      postcss-value-parser: 4.2.0
 
-  /parse5-htmlparser2-tree-adapter@7.0.0:
-    resolution: {integrity: sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==}
+  postcss-convert-values@6.1.0(postcss@8.4.45):
     dependencies:
-      domhandler: 5.0.3
-      parse5: 7.1.2
-    dev: false
+      browserslist: 4.23.1
+      postcss: 8.4.45
+      postcss-value-parser: 4.2.0
 
-  /parse5@6.0.1:
-    resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==}
-    dev: false
+  postcss-discard-comments@6.0.2(postcss@8.4.35):
+    dependencies:
+      postcss: 8.4.35
 
-  /parse5@7.1.2:
-    resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==}
+  postcss-discard-comments@6.0.2(postcss@8.4.45):
     dependencies:
-      entities: 4.4.0
-    dev: false
+      postcss: 8.4.45
 
-  /parseurl@1.3.3:
-    resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
-    engines: {node: '>= 0.8'}
-    dev: false
+  postcss-discard-duplicates@6.0.3(postcss@8.4.35):
+    dependencies:
+      postcss: 8.4.35
 
-  /pascal-case@3.1.2:
-    resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==}
+  postcss-discard-duplicates@6.0.3(postcss@8.4.45):
     dependencies:
-      no-case: 3.0.4
-      tslib: 2.4.1
-    dev: false
+      postcss: 8.4.45
 
-  /path-exists@3.0.0:
-    resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==}
-    engines: {node: '>=4'}
-    dev: false
+  postcss-discard-empty@6.0.3(postcss@8.4.35):
+    dependencies:
+      postcss: 8.4.35
 
-  /path-exists@4.0.0:
-    resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
-    engines: {node: '>=8'}
-    dev: false
+  postcss-discard-empty@6.0.3(postcss@8.4.45):
+    dependencies:
+      postcss: 8.4.45
 
-  /path-is-absolute@1.0.1:
-    resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
-    engines: {node: '>=0.10.0'}
-    dev: false
+  postcss-discard-overridden@6.0.2(postcss@8.4.35):
+    dependencies:
+      postcss: 8.4.35
 
-  /path-is-inside@1.0.2:
-    resolution: {integrity: sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==}
-    dev: false
+  postcss-discard-overridden@6.0.2(postcss@8.4.45):
+    dependencies:
+      postcss: 8.4.45
 
-  /path-key@3.1.1:
-    resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
-    engines: {node: '>=8'}
-    dev: false
+  postcss-discard-unused@6.0.5(postcss@8.4.45):
+    dependencies:
+      postcss: 8.4.45
+      postcss-selector-parser: 6.1.2
 
-  /path-parse@1.0.7:
-    resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
+  postcss-import@15.1.0(postcss@8.4.45):
+    dependencies:
+      postcss: 8.4.45
+      postcss-value-parser: 4.2.0
+      read-cache: 1.0.0
+      resolve: 1.22.8
 
-  /path-to-regexp@0.1.7:
-    resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==}
-    dev: false
+  postcss-js@4.0.1(postcss@8.4.45):
+    dependencies:
+      camelcase-css: 2.0.1
+      postcss: 8.4.45
 
-  /path-to-regexp@1.8.0:
-    resolution: {integrity: sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==}
+  postcss-load-config@4.0.2(postcss@8.4.45):
     dependencies:
-      isarray: 0.0.1
-    dev: false
+      lilconfig: 3.1.2
+      yaml: 2.5.1
+    optionalDependencies:
+      postcss: 8.4.45
 
-  /path-to-regexp@2.2.1:
-    resolution: {integrity: sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ==}
-    dev: false
+  postcss-loader@7.3.4(postcss@8.4.35)(typescript@5.3.3)(webpack@5.90.1):
+    dependencies:
+      cosmiconfig: 8.3.6(typescript@5.3.3)
+      jiti: 1.21.6
+      postcss: 8.4.35
+      semver: 7.6.0
+      webpack: 5.90.1
+    transitivePeerDependencies:
+      - typescript
 
-  /path-type@4.0.0:
-    resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
-    engines: {node: '>=8'}
-    dev: false
+  postcss-merge-idents@6.0.3(postcss@8.4.45):
+    dependencies:
+      cssnano-utils: 4.0.2(postcss@8.4.45)
+      postcss: 8.4.45
+      postcss-value-parser: 4.2.0
 
-  /picocolors@1.0.0:
-    resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
+  postcss-merge-longhand@6.0.5(postcss@8.4.35):
+    dependencies:
+      postcss: 8.4.35
+      postcss-value-parser: 4.2.0
+      stylehacks: 6.1.1(postcss@8.4.35)
 
-  /picomatch@2.3.1:
-    resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
-    engines: {node: '>=8.6'}
+  postcss-merge-longhand@6.0.5(postcss@8.4.45):
+    dependencies:
+      postcss: 8.4.45
+      postcss-value-parser: 4.2.0
+      stylehacks: 6.1.1(postcss@8.4.45)
 
-  /pify@2.3.0:
-    resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==}
-    engines: {node: '>=0.10.0'}
+  postcss-merge-rules@6.1.1(postcss@8.4.35):
+    dependencies:
+      browserslist: 4.23.1
+      caniuse-api: 3.0.0
+      cssnano-utils: 4.0.2(postcss@8.4.35)
+      postcss: 8.4.35
+      postcss-selector-parser: 6.1.2
 
-  /pkg-dir@4.2.0:
-    resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==}
-    engines: {node: '>=8'}
+  postcss-merge-rules@6.1.1(postcss@8.4.45):
     dependencies:
-      find-up: 4.1.0
-    dev: false
+      browserslist: 4.23.1
+      caniuse-api: 3.0.0
+      cssnano-utils: 4.0.2(postcss@8.4.45)
+      postcss: 8.4.45
+      postcss-selector-parser: 6.1.2
 
-  /pkg-up@3.1.0:
-    resolution: {integrity: sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==}
-    engines: {node: '>=8'}
+  postcss-minify-font-values@6.1.0(postcss@8.4.35):
     dependencies:
-      find-up: 3.0.0
-    dev: false
+      postcss: 8.4.35
+      postcss-value-parser: 4.2.0
 
-  /postcss-calc@8.2.4(postcss@8.4.21):
-    resolution: {integrity: sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==}
-    peerDependencies:
-      postcss: ^8.2.2
+  postcss-minify-font-values@6.1.0(postcss@8.4.45):
     dependencies:
-      postcss: 8.4.21
-      postcss-selector-parser: 6.0.11
+      postcss: 8.4.45
       postcss-value-parser: 4.2.0
-    dev: false
 
-  /postcss-colormin@5.3.0(postcss@8.4.21):
-    resolution: {integrity: sha512-WdDO4gOFG2Z8n4P8TWBpshnL3JpmNmJwdnfP2gbk2qBA8PWwOYcmjmI/t3CmMeL72a7Hkd+x/Mg9O2/0rD54Pg==}
-    engines: {node: ^10 || ^12 || >=14.0}
-    peerDependencies:
-      postcss: ^8.2.15
+  postcss-minify-gradients@6.0.3(postcss@8.4.35):
     dependencies:
-      browserslist: 4.21.4
-      caniuse-api: 3.0.0
       colord: 2.9.3
-      postcss: 8.4.21
+      cssnano-utils: 4.0.2(postcss@8.4.35)
+      postcss: 8.4.35
       postcss-value-parser: 4.2.0
-    dev: false
 
-  /postcss-convert-values@5.1.3(postcss@8.4.21):
-    resolution: {integrity: sha512-82pC1xkJZtcJEfiLw6UXnXVXScgtBrjlO5CBmuDQc+dlb88ZYheFsjTn40+zBVi3DkfF7iezO0nJUPLcJK3pvA==}
-    engines: {node: ^10 || ^12 || >=14.0}
-    peerDependencies:
-      postcss: ^8.2.15
+  postcss-minify-gradients@6.0.3(postcss@8.4.45):
     dependencies:
-      browserslist: 4.21.4
-      postcss: 8.4.21
+      colord: 2.9.3
+      cssnano-utils: 4.0.2(postcss@8.4.45)
+      postcss: 8.4.45
       postcss-value-parser: 4.2.0
-    dev: false
 
-  /postcss-discard-comments@5.1.2(postcss@8.4.21):
-    resolution: {integrity: sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==}
-    engines: {node: ^10 || ^12 || >=14.0}
-    peerDependencies:
-      postcss: ^8.2.15
+  postcss-minify-params@6.1.0(postcss@8.4.35):
     dependencies:
-      postcss: 8.4.21
-    dev: false
+      browserslist: 4.23.1
+      cssnano-utils: 4.0.2(postcss@8.4.35)
+      postcss: 8.4.35
+      postcss-value-parser: 4.2.0
 
-  /postcss-discard-duplicates@5.1.0(postcss@8.4.21):
-    resolution: {integrity: sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==}
-    engines: {node: ^10 || ^12 || >=14.0}
-    peerDependencies:
-      postcss: ^8.2.15
+  postcss-minify-params@6.1.0(postcss@8.4.45):
     dependencies:
-      postcss: 8.4.21
-    dev: false
+      browserslist: 4.23.1
+      cssnano-utils: 4.0.2(postcss@8.4.45)
+      postcss: 8.4.45
+      postcss-value-parser: 4.2.0
 
-  /postcss-discard-empty@5.1.1(postcss@8.4.21):
-    resolution: {integrity: sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==}
-    engines: {node: ^10 || ^12 || >=14.0}
-    peerDependencies:
-      postcss: ^8.2.15
+  postcss-minify-selectors@6.0.4(postcss@8.4.35):
     dependencies:
-      postcss: 8.4.21
-    dev: false
+      postcss: 8.4.35
+      postcss-selector-parser: 6.1.2
 
-  /postcss-discard-overridden@5.1.0(postcss@8.4.21):
-    resolution: {integrity: sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==}
-    engines: {node: ^10 || ^12 || >=14.0}
-    peerDependencies:
-      postcss: ^8.2.15
+  postcss-minify-selectors@6.0.4(postcss@8.4.45):
     dependencies:
-      postcss: 8.4.21
-    dev: false
+      postcss: 8.4.45
+      postcss-selector-parser: 6.1.2
 
-  /postcss-discard-unused@5.1.0(postcss@8.4.21):
-    resolution: {integrity: sha512-KwLWymI9hbwXmJa0dkrzpRbSJEh0vVUd7r8t0yOGPcfKzyJJxFM8kLyC5Ev9avji6nY95pOp1W6HqIrfT+0VGw==}
-    engines: {node: ^10 || ^12 || >=14.0}
-    peerDependencies:
-      postcss: ^8.2.15
+  postcss-modules-extract-imports@3.0.0(postcss@8.4.35):
     dependencies:
-      postcss: 8.4.21
-      postcss-selector-parser: 6.0.11
-    dev: false
+      postcss: 8.4.35
 
-  /postcss-import@14.1.0(postcss@8.4.21):
-    resolution: {integrity: sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==}
-    engines: {node: '>=10.0.0'}
-    peerDependencies:
-      postcss: ^8.0.0
+  postcss-modules-local-by-default@4.0.4(postcss@8.4.35):
     dependencies:
-      postcss: 8.4.21
+      icss-utils: 5.1.0(postcss@8.4.35)
+      postcss: 8.4.35
+      postcss-selector-parser: 6.1.2
       postcss-value-parser: 4.2.0
-      read-cache: 1.0.0
-      resolve: 1.22.1
-
-  /postcss-js@4.0.0(postcss@8.4.21):
-    resolution: {integrity: sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ==}
-    engines: {node: ^12 || ^14 || >= 16}
-    peerDependencies:
-      postcss: ^8.3.3
-    dependencies:
-      camelcase-css: 2.0.1
-      postcss: 8.4.21
 
-  /postcss-load-config@3.1.4(postcss@8.4.21):
-    resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==}
-    engines: {node: '>= 10'}
-    peerDependencies:
-      postcss: '>=8.0.9'
-      ts-node: '>=9.0.0'
-    peerDependenciesMeta:
-      postcss:
-        optional: true
-      ts-node:
-        optional: true
+  postcss-modules-scope@3.1.1(postcss@8.4.35):
     dependencies:
-      lilconfig: 2.0.6
-      postcss: 8.4.21
-      yaml: 1.10.2
+      postcss: 8.4.35
+      postcss-selector-parser: 6.1.2
 
-  /postcss-loader@7.0.2(postcss@8.4.21)(webpack@5.75.0):
-    resolution: {integrity: sha512-fUJzV/QH7NXUAqV8dWJ9Lg4aTkDCezpTS5HgJ2DvqznexTbSTxgi/dTECvTZ15BwKTtk8G/bqI/QTu2HPd3ZCg==}
-    engines: {node: '>= 14.15.0'}
-    peerDependencies:
-      postcss: ^7.0.0 || ^8.0.1
-      webpack: ^5.0.0
+  postcss-modules-values@4.0.0(postcss@8.4.35):
     dependencies:
-      cosmiconfig: 7.1.0
-      klona: 2.0.5
-      postcss: 8.4.21
-      semver: 7.3.8
-      webpack: 5.75.0
-    dev: false
+      icss-utils: 5.1.0(postcss@8.4.35)
+      postcss: 8.4.35
 
-  /postcss-merge-idents@5.1.1(postcss@8.4.21):
-    resolution: {integrity: sha512-pCijL1TREiCoog5nQp7wUe+TUonA2tC2sQ54UGeMmryK3UFGIYKqDyjnqd6RcuI4znFn9hWSLNN8xKE/vWcUQw==}
-    engines: {node: ^10 || ^12 || >=14.0}
-    peerDependencies:
-      postcss: ^8.2.15
+  postcss-nested@6.2.0(postcss@8.4.45):
     dependencies:
-      cssnano-utils: 3.1.0(postcss@8.4.21)
-      postcss: 8.4.21
-      postcss-value-parser: 4.2.0
-    dev: false
+      postcss: 8.4.45
+      postcss-selector-parser: 6.1.2
 
-  /postcss-merge-longhand@5.1.7(postcss@8.4.21):
-    resolution: {integrity: sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ==}
-    engines: {node: ^10 || ^12 || >=14.0}
-    peerDependencies:
-      postcss: ^8.2.15
+  postcss-normalize-charset@6.0.2(postcss@8.4.35):
     dependencies:
-      postcss: 8.4.21
-      postcss-value-parser: 4.2.0
-      stylehacks: 5.1.1(postcss@8.4.21)
-    dev: false
+      postcss: 8.4.35
 
-  /postcss-merge-rules@5.1.3(postcss@8.4.21):
-    resolution: {integrity: sha512-LbLd7uFC00vpOuMvyZop8+vvhnfRGpp2S+IMQKeuOZZapPRY4SMq5ErjQeHbHsjCUgJkRNrlU+LmxsKIqPKQlA==}
-    engines: {node: ^10 || ^12 || >=14.0}
-    peerDependencies:
-      postcss: ^8.2.15
+  postcss-normalize-charset@6.0.2(postcss@8.4.45):
     dependencies:
-      browserslist: 4.21.4
-      caniuse-api: 3.0.0
-      cssnano-utils: 3.1.0(postcss@8.4.21)
-      postcss: 8.4.21
-      postcss-selector-parser: 6.0.11
-    dev: false
+      postcss: 8.4.45
 
-  /postcss-minify-font-values@5.1.0(postcss@8.4.21):
-    resolution: {integrity: sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA==}
-    engines: {node: ^10 || ^12 || >=14.0}
-    peerDependencies:
-      postcss: ^8.2.15
+  postcss-normalize-display-values@6.0.2(postcss@8.4.35):
     dependencies:
-      postcss: 8.4.21
+      postcss: 8.4.35
       postcss-value-parser: 4.2.0
-    dev: false
 
-  /postcss-minify-gradients@5.1.1(postcss@8.4.21):
-    resolution: {integrity: sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw==}
-    engines: {node: ^10 || ^12 || >=14.0}
-    peerDependencies:
-      postcss: ^8.2.15
+  postcss-normalize-display-values@6.0.2(postcss@8.4.45):
     dependencies:
-      colord: 2.9.3
-      cssnano-utils: 3.1.0(postcss@8.4.21)
-      postcss: 8.4.21
+      postcss: 8.4.45
       postcss-value-parser: 4.2.0
-    dev: false
 
-  /postcss-minify-params@5.1.4(postcss@8.4.21):
-    resolution: {integrity: sha512-+mePA3MgdmVmv6g+30rn57USjOGSAyuxUmkfiWpzalZ8aiBkdPYjXWtHuwJGm1v5Ojy0Z0LaSYhHaLJQB0P8Jw==}
-    engines: {node: ^10 || ^12 || >=14.0}
-    peerDependencies:
-      postcss: ^8.2.15
+  postcss-normalize-positions@6.0.2(postcss@8.4.35):
     dependencies:
-      browserslist: 4.21.4
-      cssnano-utils: 3.1.0(postcss@8.4.21)
-      postcss: 8.4.21
+      postcss: 8.4.35
       postcss-value-parser: 4.2.0
-    dev: false
 
-  /postcss-minify-selectors@5.2.1(postcss@8.4.21):
-    resolution: {integrity: sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg==}
-    engines: {node: ^10 || ^12 || >=14.0}
-    peerDependencies:
-      postcss: ^8.2.15
+  postcss-normalize-positions@6.0.2(postcss@8.4.45):
     dependencies:
-      postcss: 8.4.21
-      postcss-selector-parser: 6.0.11
-    dev: false
+      postcss: 8.4.45
+      postcss-value-parser: 4.2.0
 
-  /postcss-modules-extract-imports@3.0.0(postcss@8.4.21):
-    resolution: {integrity: sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==}
-    engines: {node: ^10 || ^12 || >= 14}
-    peerDependencies:
-      postcss: ^8.1.0
+  postcss-normalize-repeat-style@6.0.2(postcss@8.4.35):
     dependencies:
-      postcss: 8.4.21
-    dev: false
+      postcss: 8.4.35
+      postcss-value-parser: 4.2.0
 
-  /postcss-modules-local-by-default@4.0.0(postcss@8.4.21):
-    resolution: {integrity: sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==}
-    engines: {node: ^10 || ^12 || >= 14}
-    peerDependencies:
-      postcss: ^8.1.0
+  postcss-normalize-repeat-style@6.0.2(postcss@8.4.45):
     dependencies:
-      icss-utils: 5.1.0(postcss@8.4.21)
-      postcss: 8.4.21
-      postcss-selector-parser: 6.0.11
+      postcss: 8.4.45
       postcss-value-parser: 4.2.0
-    dev: false
 
-  /postcss-modules-scope@3.0.0(postcss@8.4.21):
-    resolution: {integrity: sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==}
-    engines: {node: ^10 || ^12 || >= 14}
-    peerDependencies:
-      postcss: ^8.1.0
+  postcss-normalize-string@6.0.2(postcss@8.4.35):
     dependencies:
-      postcss: 8.4.21
-      postcss-selector-parser: 6.0.11
-    dev: false
+      postcss: 8.4.35
+      postcss-value-parser: 4.2.0
 
-  /postcss-modules-values@4.0.0(postcss@8.4.21):
-    resolution: {integrity: sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==}
-    engines: {node: ^10 || ^12 || >= 14}
-    peerDependencies:
-      postcss: ^8.1.0
+  postcss-normalize-string@6.0.2(postcss@8.4.45):
     dependencies:
-      icss-utils: 5.1.0(postcss@8.4.21)
-      postcss: 8.4.21
-    dev: false
+      postcss: 8.4.45
+      postcss-value-parser: 4.2.0
 
-  /postcss-nested@6.0.0(postcss@8.4.21):
-    resolution: {integrity: sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==}
-    engines: {node: '>=12.0'}
-    peerDependencies:
-      postcss: ^8.2.14
+  postcss-normalize-timing-functions@6.0.2(postcss@8.4.35):
     dependencies:
-      postcss: 8.4.21
-      postcss-selector-parser: 6.0.11
+      postcss: 8.4.35
+      postcss-value-parser: 4.2.0
 
-  /postcss-normalize-charset@5.1.0(postcss@8.4.21):
-    resolution: {integrity: sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==}
-    engines: {node: ^10 || ^12 || >=14.0}
-    peerDependencies:
-      postcss: ^8.2.15
+  postcss-normalize-timing-functions@6.0.2(postcss@8.4.45):
     dependencies:
-      postcss: 8.4.21
-    dev: false
+      postcss: 8.4.45
+      postcss-value-parser: 4.2.0
 
-  /postcss-normalize-display-values@5.1.0(postcss@8.4.21):
-    resolution: {integrity: sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA==}
-    engines: {node: ^10 || ^12 || >=14.0}
-    peerDependencies:
-      postcss: ^8.2.15
+  postcss-normalize-unicode@6.1.0(postcss@8.4.35):
     dependencies:
-      postcss: 8.4.21
+      browserslist: 4.23.1
+      postcss: 8.4.35
       postcss-value-parser: 4.2.0
-    dev: false
 
-  /postcss-normalize-positions@5.1.1(postcss@8.4.21):
-    resolution: {integrity: sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg==}
-    engines: {node: ^10 || ^12 || >=14.0}
-    peerDependencies:
-      postcss: ^8.2.15
+  postcss-normalize-unicode@6.1.0(postcss@8.4.45):
     dependencies:
-      postcss: 8.4.21
+      browserslist: 4.23.1
+      postcss: 8.4.45
       postcss-value-parser: 4.2.0
-    dev: false
 
-  /postcss-normalize-repeat-style@5.1.1(postcss@8.4.21):
-    resolution: {integrity: sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g==}
-    engines: {node: ^10 || ^12 || >=14.0}
-    peerDependencies:
-      postcss: ^8.2.15
+  postcss-normalize-url@6.0.2(postcss@8.4.35):
     dependencies:
-      postcss: 8.4.21
+      postcss: 8.4.35
       postcss-value-parser: 4.2.0
-    dev: false
 
-  /postcss-normalize-string@5.1.0(postcss@8.4.21):
-    resolution: {integrity: sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w==}
-    engines: {node: ^10 || ^12 || >=14.0}
-    peerDependencies:
-      postcss: ^8.2.15
+  postcss-normalize-url@6.0.2(postcss@8.4.45):
     dependencies:
-      postcss: 8.4.21
+      postcss: 8.4.45
       postcss-value-parser: 4.2.0
-    dev: false
 
-  /postcss-normalize-timing-functions@5.1.0(postcss@8.4.21):
-    resolution: {integrity: sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg==}
-    engines: {node: ^10 || ^12 || >=14.0}
-    peerDependencies:
-      postcss: ^8.2.15
+  postcss-normalize-whitespace@6.0.2(postcss@8.4.35):
     dependencies:
-      postcss: 8.4.21
+      postcss: 8.4.35
       postcss-value-parser: 4.2.0
-    dev: false
 
-  /postcss-normalize-unicode@5.1.1(postcss@8.4.21):
-    resolution: {integrity: sha512-qnCL5jzkNUmKVhZoENp1mJiGNPcsJCs1aaRmURmeJGES23Z/ajaln+EPTD+rBeNkSryI+2WTdW+lwcVdOikrpA==}
-    engines: {node: ^10 || ^12 || >=14.0}
-    peerDependencies:
-      postcss: ^8.2.15
+  postcss-normalize-whitespace@6.0.2(postcss@8.4.45):
     dependencies:
-      browserslist: 4.21.4
-      postcss: 8.4.21
+      postcss: 8.4.45
       postcss-value-parser: 4.2.0
-    dev: false
 
-  /postcss-normalize-url@5.1.0(postcss@8.4.21):
-    resolution: {integrity: sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew==}
-    engines: {node: ^10 || ^12 || >=14.0}
-    peerDependencies:
-      postcss: ^8.2.15
+  postcss-ordered-values@6.0.2(postcss@8.4.35):
     dependencies:
-      normalize-url: 6.1.0
-      postcss: 8.4.21
+      cssnano-utils: 4.0.2(postcss@8.4.35)
+      postcss: 8.4.35
       postcss-value-parser: 4.2.0
-    dev: false
 
-  /postcss-normalize-whitespace@5.1.1(postcss@8.4.21):
-    resolution: {integrity: sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA==}
-    engines: {node: ^10 || ^12 || >=14.0}
-    peerDependencies:
-      postcss: ^8.2.15
+  postcss-ordered-values@6.0.2(postcss@8.4.45):
     dependencies:
-      postcss: 8.4.21
+      cssnano-utils: 4.0.2(postcss@8.4.45)
+      postcss: 8.4.45
       postcss-value-parser: 4.2.0
-    dev: false
 
-  /postcss-ordered-values@5.1.3(postcss@8.4.21):
-    resolution: {integrity: sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ==}
-    engines: {node: ^10 || ^12 || >=14.0}
-    peerDependencies:
-      postcss: ^8.2.15
+  postcss-reduce-idents@6.0.3(postcss@8.4.45):
     dependencies:
-      cssnano-utils: 3.1.0(postcss@8.4.21)
-      postcss: 8.4.21
+      postcss: 8.4.45
       postcss-value-parser: 4.2.0
-    dev: false
 
-  /postcss-reduce-idents@5.2.0(postcss@8.4.21):
-    resolution: {integrity: sha512-BTrLjICoSB6gxbc58D5mdBK8OhXRDqud/zodYfdSi52qvDHdMwk+9kB9xsM8yJThH/sZU5A6QVSmMmaN001gIg==}
-    engines: {node: ^10 || ^12 || >=14.0}
-    peerDependencies:
-      postcss: ^8.2.15
+  postcss-reduce-initial@6.1.0(postcss@8.4.35):
     dependencies:
-      postcss: 8.4.21
-      postcss-value-parser: 4.2.0
-    dev: false
+      browserslist: 4.23.1
+      caniuse-api: 3.0.0
+      postcss: 8.4.35
 
-  /postcss-reduce-initial@5.1.1(postcss@8.4.21):
-    resolution: {integrity: sha512-//jeDqWcHPuXGZLoolFrUXBDyuEGbr9S2rMo19bkTIjBQ4PqkaO+oI8wua5BOUxpfi97i3PCoInsiFIEBfkm9w==}
-    engines: {node: ^10 || ^12 || >=14.0}
-    peerDependencies:
-      postcss: ^8.2.15
+  postcss-reduce-initial@6.1.0(postcss@8.4.45):
     dependencies:
-      browserslist: 4.21.4
+      browserslist: 4.23.1
       caniuse-api: 3.0.0
-      postcss: 8.4.21
-    dev: false
+      postcss: 8.4.45
 
-  /postcss-reduce-transforms@5.1.0(postcss@8.4.21):
-    resolution: {integrity: sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ==}
-    engines: {node: ^10 || ^12 || >=14.0}
-    peerDependencies:
-      postcss: ^8.2.15
+  postcss-reduce-transforms@6.0.2(postcss@8.4.35):
     dependencies:
-      postcss: 8.4.21
+      postcss: 8.4.35
       postcss-value-parser: 4.2.0
-    dev: false
 
-  /postcss-selector-parser@6.0.10:
-    resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==}
-    engines: {node: '>=4'}
+  postcss-reduce-transforms@6.0.2(postcss@8.4.45):
+    dependencies:
+      postcss: 8.4.45
+      postcss-value-parser: 4.2.0
+
+  postcss-selector-parser@6.0.10:
     dependencies:
       cssesc: 3.0.0
       util-deprecate: 1.0.2
-    dev: true
 
-  /postcss-selector-parser@6.0.11:
-    resolution: {integrity: sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g==}
-    engines: {node: '>=4'}
+  postcss-selector-parser@6.0.11:
     dependencies:
       cssesc: 3.0.0
       util-deprecate: 1.0.2
 
-  /postcss-sort-media-queries@4.3.0(postcss@8.4.21):
-    resolution: {integrity: sha512-jAl8gJM2DvuIJiI9sL1CuiHtKM4s5aEIomkU8G3LFvbP+p8i7Sz8VV63uieTgoewGqKbi+hxBTiOKJlB35upCg==}
-    engines: {node: '>=10.0.0'}
-    peerDependencies:
-      postcss: ^8.4.16
+  postcss-selector-parser@6.1.2:
     dependencies:
-      postcss: 8.4.21
-      sort-css-media-queries: 2.1.0
-    dev: false
+      cssesc: 3.0.0
+      util-deprecate: 1.0.2
 
-  /postcss-svgo@5.1.0(postcss@8.4.21):
-    resolution: {integrity: sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA==}
-    engines: {node: ^10 || ^12 || >=14.0}
-    peerDependencies:
-      postcss: ^8.2.15
+  postcss-sort-media-queries@5.2.0(postcss@8.4.45):
     dependencies:
-      postcss: 8.4.21
+      postcss: 8.4.45
+      sort-css-media-queries: 2.2.0
+
+  postcss-svgo@6.0.3(postcss@8.4.35):
+    dependencies:
+      postcss: 8.4.35
       postcss-value-parser: 4.2.0
-      svgo: 2.8.0
-    dev: false
+      svgo: 3.3.2
 
-  /postcss-unique-selectors@5.1.1(postcss@8.4.21):
-    resolution: {integrity: sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA==}
-    engines: {node: ^10 || ^12 || >=14.0}
-    peerDependencies:
-      postcss: ^8.2.15
+  postcss-svgo@6.0.3(postcss@8.4.45):
     dependencies:
-      postcss: 8.4.21
-      postcss-selector-parser: 6.0.11
-    dev: false
+      postcss: 8.4.45
+      postcss-value-parser: 4.2.0
+      svgo: 3.3.2
 
-  /postcss-value-parser@4.2.0:
-    resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
+  postcss-unique-selectors@6.0.4(postcss@8.4.35):
+    dependencies:
+      postcss: 8.4.35
+      postcss-selector-parser: 6.1.2
 
-  /postcss-zindex@5.1.0(postcss@8.4.21):
-    resolution: {integrity: sha512-fgFMf0OtVSBR1va1JNHYgMxYk73yhn/qb4uQDq1DLGYolz8gHCyr/sesEuGUaYs58E3ZJRcpoGuPVoB7Meiq9A==}
-    engines: {node: ^10 || ^12 || >=14.0}
-    peerDependencies:
-      postcss: ^8.2.15
+  postcss-unique-selectors@6.0.4(postcss@8.4.45):
     dependencies:
-      postcss: 8.4.21
-    dev: false
+      postcss: 8.4.45
+      postcss-selector-parser: 6.1.2
 
-  /postcss@8.4.21:
-    resolution: {integrity: sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==}
-    engines: {node: ^10 || ^12 || >=14}
+  postcss-value-parser@4.2.0: {}
+
+  postcss-zindex@6.0.2(postcss@8.4.45):
+    dependencies:
+      postcss: 8.4.45
+
+  postcss@8.4.21:
     dependencies:
       nanoid: 3.3.4
       picocolors: 1.0.0
       source-map-js: 1.0.2
 
-  /prepend-http@2.0.0:
-    resolution: {integrity: sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA==}
-    engines: {node: '>=4'}
-    dev: false
+  postcss@8.4.35:
+    dependencies:
+      nanoid: 3.3.7
+      picocolors: 1.1.0
+      source-map-js: 1.2.1
 
-  /pretty-error@4.0.0:
-    resolution: {integrity: sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==}
+  postcss@8.4.45:
+    dependencies:
+      nanoid: 3.3.7
+      picocolors: 1.1.0
+      source-map-js: 1.2.1
+
+  pretty-error@4.0.0:
     dependencies:
       lodash: 4.17.21
       renderkid: 3.0.0
-    dev: false
 
-  /pretty-time@1.1.0:
-    resolution: {integrity: sha512-28iF6xPQrP8Oa6uxE6a1biz+lWeTOAPKggvjB8HAs6nVMKZwf5bG++632Dx614hIWgUPkgivRfG+a8uAXGTIbA==}
-    engines: {node: '>=4'}
-    dev: false
+  pretty-time@1.1.0: {}
 
-  /prism-react-renderer@1.3.5(react@17.0.2):
-    resolution: {integrity: sha512-IJ+MSwBWKG+SM3b2SUfdrhC+gu01QkV2KmRQgREThBfSQRoufqRfxfHUxpG1WcaFjP+kojcFyO9Qqtpgt3qLCg==}
-    peerDependencies:
-      react: '>=0.14.9'
+  prism-react-renderer@2.3.1(react@18.2.0):
     dependencies:
-      react: 17.0.2
-    dev: false
-
-  /prism-svelte@0.5.0:
-    resolution: {integrity: sha512-db91Bf3pRGKDPz1lAqLFSJXeW13mulUJxhycysFpfXV5MIK7RgWWK2E5aPAa71s8TCzQUXxF5JOV42/iOs6QkA==}
-    dev: false
+      '@types/prismjs': 1.26.3
+      clsx: 2.1.0
+      react: 18.2.0
 
-  /prismjs@1.29.0:
-    resolution: {integrity: sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==}
-    engines: {node: '>=6'}
-    dev: false
+  prism-svelte@0.5.0: {}
 
-  /process-nextick-args@2.0.1:
-    resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
-    dev: false
+  prismjs@1.29.0: {}
 
-  /promise@7.3.1:
-    resolution: {integrity: sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==}
-    dependencies:
-      asap: 2.0.6
-    dev: false
+  process-nextick-args@2.0.1: {}
 
-  /prompts@2.4.2:
-    resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==}
-    engines: {node: '>= 6'}
+  prompts@2.4.2:
     dependencies:
       kleur: 3.0.3
       sisteransi: 1.0.5
-    dev: false
 
-  /prop-types@15.8.1:
-    resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
+  prop-types@15.8.1:
     dependencies:
       loose-envify: 1.4.0
       object-assign: 4.1.1
       react-is: 16.13.1
 
-  /property-information@5.6.0:
-    resolution: {integrity: sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==}
-    dependencies:
-      xtend: 4.0.2
-    dev: false
+  property-information@6.4.1: {}
 
-  /proxy-addr@2.0.7:
-    resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
-    engines: {node: '>= 0.10'}
-    dependencies:
-      forwarded: 0.2.0
-      ipaddr.js: 1.9.1
-    dev: false
+  proto-list@1.2.4: {}
 
-  /pump@3.0.0:
-    resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==}
+  proxy-addr@2.0.7:
     dependencies:
-      end-of-stream: 1.4.4
-      once: 1.4.0
-    dev: false
+      forwarded: 0.2.0
+      ipaddr.js: 1.9.1
 
-  /punycode@1.4.1:
-    resolution: {integrity: sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==}
-    dev: false
+  punycode@1.4.1: {}
 
-  /punycode@2.1.1:
-    resolution: {integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==}
-    engines: {node: '>=6'}
+  punycode@2.1.1: {}
 
-  /pupa@2.1.1:
-    resolution: {integrity: sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==}
-    engines: {node: '>=8'}
+  pupa@3.1.0:
     dependencies:
-      escape-goat: 2.1.1
-    dev: false
-
-  /pure-color@1.3.0:
-    resolution: {integrity: sha512-QFADYnsVoBMw1srW7OVKEYjG+MbIa49s54w1MA1EDY6r2r/sTcKKYqRX1f4GYvnXP7eN/Pe9HFcX+hwzmrXRHA==}
-    dev: false
+      escape-goat: 4.0.0
 
-  /qs@6.11.0:
-    resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==}
-    engines: {node: '>=0.6'}
+  qs@6.11.0:
     dependencies:
       side-channel: 1.0.4
-    dev: false
 
-  /queue-microtask@1.2.3:
-    resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
+  queue-microtask@1.2.3: {}
 
-  /queue@6.0.2:
-    resolution: {integrity: sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==}
+  queue@6.0.2:
     dependencies:
       inherits: 2.0.4
-    dev: false
 
-  /quick-lru@5.1.1:
-    resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==}
-    engines: {node: '>=10'}
+  quick-lru@5.1.1: {}
 
-  /randombytes@2.1.0:
-    resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==}
+  randombytes@2.1.0:
     dependencies:
       safe-buffer: 5.2.1
 
-  /range-parser@1.2.0:
-    resolution: {integrity: sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==}
-    engines: {node: '>= 0.6'}
-    dev: false
+  range-parser@1.2.0: {}
 
-  /range-parser@1.2.1:
-    resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
-    engines: {node: '>= 0.6'}
-    dev: false
+  range-parser@1.2.1: {}
 
-  /raw-body@2.5.1:
-    resolution: {integrity: sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==}
-    engines: {node: '>= 0.8'}
+  raw-body@2.5.1:
     dependencies:
       bytes: 3.1.2
       http-errors: 2.0.0
       iconv-lite: 0.4.24
       unpipe: 1.0.0
-    dev: false
 
-  /rc@1.2.8:
-    resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==}
-    hasBin: true
+  rc@1.2.8:
     dependencies:
       deep-extend: 0.6.0
       ini: 1.3.8
       minimist: 1.2.7
       strip-json-comments: 2.0.1
-    dev: false
-
-  /react-base16-styling@0.6.0:
-    resolution: {integrity: sha512-yvh/7CArceR/jNATXOKDlvTnPKPmGZz7zsenQ3jUwLzHkNUR0CvY3yGYJbWJ/nnxsL8Sgmt5cO3/SILVuPO6TQ==}
-    dependencies:
-      base16: 1.0.0
-      lodash.curry: 4.1.1
-      lodash.flow: 3.5.0
-      pure-color: 1.3.0
-    dev: false
 
-  /react-dev-utils@12.0.1(typescript@4.9.4)(webpack@5.75.0):
-    resolution: {integrity: sha512-84Ivxmr17KjUupyqzFode6xKhjwuEJDROWKJy/BthkL7Wn6NJ8h4WE6k/exAv6ImS+0oZLRRW5j/aINMHyeGeQ==}
-    engines: {node: '>=14'}
-    peerDependencies:
-      typescript: '>=2.7'
-      webpack: '>=4'
-    peerDependenciesMeta:
-      typescript:
-        optional: true
+  react-dev-utils@12.0.1(typescript@5.3.3)(webpack@5.90.1):
     dependencies:
-      '@babel/code-frame': 7.18.6
+      '@babel/code-frame': 7.23.5
       address: 1.2.2
-      browserslist: 4.21.4
+      browserslist: 4.22.3
       chalk: 4.1.2
       cross-spawn: 7.0.3
       detect-port-alt: 1.1.6
       escape-string-regexp: 4.0.0
       filesize: 8.0.7
       find-up: 5.0.0
-      fork-ts-checker-webpack-plugin: 6.5.2(typescript@4.9.4)(webpack@5.75.0)
+      fork-ts-checker-webpack-plugin: 6.5.2(typescript@5.3.3)(webpack@5.90.1)
       global-modules: 2.0.0
       globby: 11.1.0
       gzip-size: 6.0.0
@@ -7266,174 +10801,100 @@ packages:
       prompts: 2.4.2
       react-error-overlay: 6.0.11
       recursive-readdir: 2.2.3
-      shell-quote: 1.7.4
+      shell-quote: 1.8.1
       strip-ansi: 6.0.1
       text-table: 0.2.0
-      typescript: 4.9.4
-      webpack: 5.75.0
+      webpack: 5.90.1
+    optionalDependencies:
+      typescript: 5.3.3
     transitivePeerDependencies:
       - eslint
       - supports-color
       - vue-template-compiler
-    dev: false
 
-  /react-dom@17.0.2(react@17.0.2):
-    resolution: {integrity: sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==}
-    peerDependencies:
-      react: 17.0.2
+  react-dom@18.2.0(react@18.2.0):
     dependencies:
       loose-envify: 1.4.0
-      object-assign: 4.1.1
-      react: 17.0.2
-      scheduler: 0.20.2
+      react: 18.2.0
+      scheduler: 0.23.0
 
-  /react-error-overlay@6.0.11:
-    resolution: {integrity: sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==}
-    dev: false
+  react-error-overlay@6.0.11: {}
 
-  /react-fast-compare@3.2.0:
-    resolution: {integrity: sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==}
+  react-fast-compare@3.2.0: {}
 
-  /react-helmet-async@1.3.0(react-dom@17.0.2)(react@17.0.2):
-    resolution: {integrity: sha512-9jZ57/dAn9t3q6hneQS0wukqC2ENOBgMNVEhb/ZG9ZSxUetzVIw4iAmEU38IaVg3QGYauQPhSeUTuIUtFglWpg==}
-    peerDependencies:
-      react: ^16.6.0 || ^17.0.0 || ^18.0.0
-      react-dom: ^16.6.0 || ^17.0.0 || ^18.0.0
+  react-helmet-async@1.3.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
     dependencies:
-      '@babel/runtime': 7.20.7
+      '@babel/runtime': 7.23.9
       invariant: 2.2.4
       prop-types: 15.8.1
-      react: 17.0.2
-      react-dom: 17.0.2(react@17.0.2)
+      react: 18.2.0
+      react-dom: 18.2.0(react@18.2.0)
       react-fast-compare: 3.2.0
       shallowequal: 1.1.0
 
-  /react-icons@4.7.1(react@17.0.2):
-    resolution: {integrity: sha512-yHd3oKGMgm7zxo3EA7H2n7vxSoiGmHk5t6Ou4bXsfcgWyhfDKMpyKfhHR6Bjnn63c+YXBLBPUql9H4wPJM6sXw==}
-    peerDependencies:
-      react: '*'
+  react-icons@5.0.1(react@18.2.0):
     dependencies:
-      react: 17.0.2
-    dev: false
+      react: 18.2.0
 
-  /react-is@16.13.1:
-    resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
+  react-is@16.13.1: {}
 
-  /react-json-view@1.21.3(@types/react@18.0.26)(react-dom@17.0.2)(react@17.0.2):
-    resolution: {integrity: sha512-13p8IREj9/x/Ye4WI/JpjhoIwuzEgUAtgJZNBJckfzJt1qyh24BdTm6UQNGnyTq9dapQdrqvquZTo3dz1X6Cjw==}
-    peerDependencies:
-      react: ^17.0.0 || ^16.3.0 || ^15.5.4
-      react-dom: ^17.0.0 || ^16.3.0 || ^15.5.4
+  react-json-view-lite@1.2.1(react@18.2.0):
     dependencies:
-      flux: 4.0.3(react@17.0.2)
-      react: 17.0.2
-      react-base16-styling: 0.6.0
-      react-dom: 17.0.2(react@17.0.2)
-      react-lifecycles-compat: 3.0.4
-      react-textarea-autosize: 8.4.0(@types/react@18.0.26)(react@17.0.2)
-    transitivePeerDependencies:
-      - '@types/react'
-      - encoding
-    dev: false
-
-  /react-lifecycles-compat@3.0.4:
-    resolution: {integrity: sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==}
-    dev: false
+      react: 18.2.0
 
-  /react-loadable-ssr-addon-v5-slorber@1.0.1(@docusaurus/react-loadable@5.5.2)(webpack@5.75.0):
-    resolution: {integrity: sha512-lq3Lyw1lGku8zUEJPDxsNm1AfYHBrO9Y1+olAYwpUJ2IGFBskM0DMKok97A6LWUpHm+o7IvQBOWu9MLenp9Z+A==}
-    engines: {node: '>=10.13.0'}
-    peerDependencies:
-      react-loadable: '*'
-      webpack: '>=4.41.1 || 5.x'
+  react-loadable-ssr-addon-v5-slorber@1.0.1(@docusaurus/react-loadable@6.0.0(react@18.2.0))(webpack@5.90.1):
     dependencies:
-      '@babel/runtime': 7.20.7
-      react-loadable: /@docusaurus/react-loadable@5.5.2(react@17.0.2)
-      webpack: 5.75.0
-    dev: false
+      '@babel/runtime': 7.23.9
+      react-loadable: '@docusaurus/react-loadable@6.0.0(react@18.2.0)'
+      webpack: 5.90.1
 
-  /react-router-config@5.1.1(react-router@5.3.4)(react@17.0.2):
-    resolution: {integrity: sha512-DuanZjaD8mQp1ppHjgnnUnyOlqYXZVjnov/JzFhjLEwd3Z4dYjMSnqrEzzGThH47vpCOqPPwJM2FtthLeJ8Pbg==}
-    peerDependencies:
-      react: '>=15'
-      react-router: '>=5'
+  react-router-config@5.1.1(react-router@5.3.4(react@18.2.0))(react@18.2.0):
     dependencies:
-      '@babel/runtime': 7.20.7
-      react: 17.0.2
-      react-router: 5.3.4(react@17.0.2)
-    dev: false
+      '@babel/runtime': 7.23.9
+      react: 18.2.0
+      react-router: 5.3.4(react@18.2.0)
 
-  /react-router-dom@5.3.4(react@17.0.2):
-    resolution: {integrity: sha512-m4EqFMHv/Ih4kpcBCONHbkT68KoAeHN4p3lAGoNryfHi0dMy0kCzEZakiKRsvg5wHZ/JLrLW8o8KomWiz/qbYQ==}
-    peerDependencies:
-      react: '>=15'
+  react-router-dom@5.3.4(react@18.2.0):
     dependencies:
-      '@babel/runtime': 7.20.7
+      '@babel/runtime': 7.23.9
       history: 4.10.1
       loose-envify: 1.4.0
       prop-types: 15.8.1
-      react: 17.0.2
-      react-router: 5.3.4(react@17.0.2)
+      react: 18.2.0
+      react-router: 5.3.4(react@18.2.0)
       tiny-invariant: 1.3.1
       tiny-warning: 1.0.3
-    dev: false
 
-  /react-router@5.3.4(react@17.0.2):
-    resolution: {integrity: sha512-Ys9K+ppnJah3QuaRiLxk+jDWOR1MekYQrlytiXxC1RyfbdsZkS5pvKAzCCr031xHixZwpnsYNT5xysdFHQaYsA==}
-    peerDependencies:
-      react: '>=15'
+  react-router@5.3.4(react@18.2.0):
     dependencies:
-      '@babel/runtime': 7.20.7
+      '@babel/runtime': 7.23.9
       history: 4.10.1
       hoist-non-react-statics: 3.3.2
       loose-envify: 1.4.0
       path-to-regexp: 1.8.0
       prop-types: 15.8.1
-      react: 17.0.2
+      react: 18.2.0
       react-is: 16.13.1
       tiny-invariant: 1.3.1
       tiny-warning: 1.0.3
-    dev: false
-
-  /react-simple-typewriter@5.0.1(react-dom@17.0.2)(react@17.0.2):
-    resolution: {integrity: sha512-vA5HkABwJKL/DJ4RshSlY/igdr+FiVY4MLsSQYJX6FZG/f1/VwN4y1i3mPXRyfaswrvI8xii1kOVe1dYtO2Row==}
-    engines: {node: '>=14'}
-    peerDependencies:
-      react: '>=18.0.0'
-      react-dom: '>=18.0.0'
-    dependencies:
-      react: 17.0.2
-      react-dom: 17.0.2(react@17.0.2)
-    dev: false
 
-  /react-textarea-autosize@8.4.0(@types/react@18.0.26)(react@17.0.2):
-    resolution: {integrity: sha512-YrTFaEHLgJsi8sJVYHBzYn+mkP3prGkmP2DKb/tm0t7CLJY5t1Rxix8070LAKb0wby7bl/lf2EeHkuMihMZMwQ==}
-    engines: {node: '>=10'}
-    peerDependencies:
-      react: ^16.8.0 || ^17.0.0 || ^18.0.0
+  react-share@5.1.0(react@18.2.0):
     dependencies:
-      '@babel/runtime': 7.20.7
-      react: 17.0.2
-      use-composed-ref: 1.3.0(react@17.0.2)
-      use-latest: 1.2.1(@types/react@18.0.26)(react@17.0.2)
+      classnames: 2.5.1
+      jsonp: 0.2.1
+      react: 18.2.0
     transitivePeerDependencies:
-      - '@types/react'
-    dev: false
+      - supports-color
 
-  /react@17.0.2:
-    resolution: {integrity: sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==}
-    engines: {node: '>=0.10.0'}
+  react@18.2.0:
     dependencies:
       loose-envify: 1.4.0
-      object-assign: 4.1.1
 
-  /read-cache@1.0.0:
-    resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==}
+  read-cache@1.0.0:
     dependencies:
       pify: 2.3.0
 
-  /readable-stream@2.3.7:
-    resolution: {integrity: sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==}
+  readable-stream@2.3.7:
     dependencies:
       core-util-is: 1.0.3
       inherits: 2.0.4
@@ -7442,361 +10903,256 @@ packages:
       safe-buffer: 5.1.2
       string_decoder: 1.1.1
       util-deprecate: 1.0.2
-    dev: false
 
-  /readable-stream@3.6.0:
-    resolution: {integrity: sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==}
-    engines: {node: '>= 6'}
+  readable-stream@3.6.0:
     dependencies:
       inherits: 2.0.4
       string_decoder: 1.3.0
       util-deprecate: 1.0.2
-    dev: false
 
-  /readdirp@3.6.0:
-    resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
-    engines: {node: '>=8.10.0'}
+  readdirp@3.6.0:
     dependencies:
       picomatch: 2.3.1
 
-  /reading-time@1.5.0:
-    resolution: {integrity: sha512-onYyVhBNr4CmAxFsKS7bz+uTLRakypIe4R+5A824vBSkQy/hB3fZepoVEf8OVAxzLvK+H/jm9TzpI3ETSm64Kg==}
-    dev: false
+  reading-time@1.5.0: {}
 
-  /rechoir@0.6.2:
-    resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==}
-    engines: {node: '>= 0.10'}
+  rechoir@0.6.2:
     dependencies:
-      resolve: 1.22.1
-    dev: false
+      resolve: 1.22.8
 
-  /recursive-readdir@2.2.3:
-    resolution: {integrity: sha512-8HrF5ZsXk5FAH9dgsx3BlUer73nIhuj+9OrQwEbLTPOBzGkL1lsFCR01am+v+0m2Cmbs1nP12hLDl5FA7EszKA==}
-    engines: {node: '>=6.0.0'}
+  recursive-readdir@2.2.3:
     dependencies:
       minimatch: 3.1.2
-    dev: false
 
-  /regenerate-unicode-properties@10.1.0:
-    resolution: {integrity: sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==}
-    engines: {node: '>=4'}
+  regenerate-unicode-properties@10.1.0:
     dependencies:
       regenerate: 1.4.2
-    dev: false
 
-  /regenerate@1.4.2:
-    resolution: {integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==}
-    dev: false
+  regenerate@1.4.2: {}
 
-  /regenerator-runtime@0.13.11:
-    resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==}
+  regenerator-runtime@0.14.1: {}
 
-  /regenerator-transform@0.15.1:
-    resolution: {integrity: sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg==}
+  regenerator-transform@0.15.2:
     dependencies:
-      '@babel/runtime': 7.20.7
-    dev: false
+      '@babel/runtime': 7.23.9
 
-  /regexpu-core@5.2.2:
-    resolution: {integrity: sha512-T0+1Zp2wjF/juXMrMxHxidqGYn8U4R+zleSJhX9tQ1PUsS8a9UtYfbsF9LdiVgNX3kiX8RNaKM42nfSgvFJjmw==}
-    engines: {node: '>=4'}
+  regexpu-core@5.3.2:
     dependencies:
+      '@babel/regjsgen': 0.8.0
       regenerate: 1.4.2
       regenerate-unicode-properties: 10.1.0
-      regjsgen: 0.7.1
       regjsparser: 0.9.1
       unicode-match-property-ecmascript: 2.0.0
       unicode-match-property-value-ecmascript: 2.1.0
-    dev: false
 
-  /registry-auth-token@4.2.2:
-    resolution: {integrity: sha512-PC5ZysNb42zpFME6D/XlIgtNGdTl8bBOCw90xQLVMpzuuubJKYDWFAEuUNc+Cn8Z8724tg2SDhDRrkVEsqfDMg==}
-    engines: {node: '>=6.0.0'}
+  registry-auth-token@5.0.2:
     dependencies:
-      rc: 1.2.8
-    dev: false
+      '@pnpm/npm-conf': 2.2.2
 
-  /registry-url@5.1.0:
-    resolution: {integrity: sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==}
-    engines: {node: '>=8'}
+  registry-url@6.0.1:
     dependencies:
       rc: 1.2.8
-    dev: false
-
-  /regjsgen@0.7.1:
-    resolution: {integrity: sha512-RAt+8H2ZEzHeYWxZ3H2z6tF18zyyOnlcdaafLrm21Bguj7uZy6ULibiAFdXEtKQY4Sy7wDTwDiOazasMLc4KPA==}
-    dev: false
 
-  /regjsparser@0.9.1:
-    resolution: {integrity: sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==}
-    hasBin: true
+  regjsparser@0.9.1:
     dependencies:
       jsesc: 0.5.0
-    dev: false
 
-  /relateurl@0.2.7:
-    resolution: {integrity: sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==}
-    engines: {node: '>= 0.10'}
-    dev: false
-
-  /remark-emoji@2.2.0:
-    resolution: {integrity: sha512-P3cj9s5ggsUvWw5fS2uzCHJMGuXYRb0NnZqYlNecewXt8QBU9n5vW3DUUKOhepS8F9CwdMx9B8a3i7pqFWAI5w==}
-    dependencies:
-      emoticon: 3.2.0
-      node-emoji: 1.11.0
-      unist-util-visit: 2.0.3
-    dev: false
-
-  /remark-footnotes@2.0.0:
-    resolution: {integrity: sha512-3Clt8ZMH75Ayjp9q4CorNeyjwIxHFcTkaektplKGl2A1jNGEUey8cKL0ZC5vJwfcD5GFGsNLImLG/NGzWIzoMQ==}
-    dev: false
-
-  /remark-mdx@1.6.22:
-    resolution: {integrity: sha512-phMHBJgeV76uyFkH4rvzCftLfKCr2RZuF+/gmVcaKrpsihyzmhXjA0BEMDaPTXG5y8qZOKPVo83NAOX01LPnOQ==}
-    dependencies:
-      '@babel/core': 7.12.9
-      '@babel/helper-plugin-utils': 7.10.4
-      '@babel/plugin-proposal-object-rest-spread': 7.12.1(@babel/core@7.12.9)
-      '@babel/plugin-syntax-jsx': 7.12.1(@babel/core@7.12.9)
-      '@mdx-js/util': 1.6.22
-      is-alphabetical: 1.0.4
-      remark-parse: 8.0.3
-      unified: 9.2.0
+  rehype-raw@7.0.0:
+    dependencies:
+      '@types/hast': 3.0.4
+      hast-util-raw: 9.0.2
+      vfile: 6.0.1
+
+  relateurl@0.2.7: {}
+
+  remark-directive@3.0.0:
+    dependencies:
+      '@types/mdast': 4.0.3
+      mdast-util-directive: 3.0.0
+      micromark-extension-directive: 3.0.0
+      unified: 11.0.4
     transitivePeerDependencies:
       - supports-color
-    dev: false
-
-  /remark-parse@8.0.3:
-    resolution: {integrity: sha512-E1K9+QLGgggHxCQtLt++uXltxEprmWzNfg+MxpfHsZlrddKzZ/hZyWHDbK3/Ap8HJQqYJRXP+jHczdL6q6i85Q==}
-    dependencies:
-      ccount: 1.1.0
-      collapse-white-space: 1.0.6
-      is-alphabetical: 1.0.4
-      is-decimal: 1.0.4
-      is-whitespace-character: 1.0.4
-      is-word-character: 1.0.4
-      markdown-escapes: 1.0.4
-      parse-entities: 2.0.0
-      repeat-string: 1.6.1
-      state-toggle: 1.0.3
-      trim: 0.0.1
-      trim-trailing-lines: 1.1.4
-      unherit: 1.1.3
-      unist-util-remove-position: 2.0.1
-      vfile-location: 3.2.0
-      xtend: 4.0.2
-    dev: false
-
-  /remark-squeeze-paragraphs@4.0.0:
-    resolution: {integrity: sha512-8qRqmL9F4nuLPIgl92XUuxI3pFxize+F1H0e/W3llTk0UsjJaj01+RrirkMw7P21RKe4X6goQhYRSvNWX+70Rw==}
-    dependencies:
-      mdast-squeeze-paragraphs: 4.0.0
-    dev: false
-
-  /renderkid@3.0.0:
-    resolution: {integrity: sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==}
+
+  remark-emoji@4.0.1:
+    dependencies:
+      '@types/mdast': 4.0.3
+      emoticon: 4.0.1
+      mdast-util-find-and-replace: 3.0.1
+      node-emoji: 2.1.3
+      unified: 11.0.4
+
+  remark-frontmatter@5.0.0:
+    dependencies:
+      '@types/mdast': 4.0.3
+      mdast-util-frontmatter: 2.0.1
+      micromark-extension-frontmatter: 2.0.0
+      unified: 11.0.4
+    transitivePeerDependencies:
+      - supports-color
+
+  remark-gfm@4.0.0:
+    dependencies:
+      '@types/mdast': 4.0.3
+      mdast-util-gfm: 3.0.0
+      micromark-extension-gfm: 3.0.0
+      remark-parse: 11.0.0
+      remark-stringify: 11.0.0
+      unified: 11.0.4
+    transitivePeerDependencies:
+      - supports-color
+
+  remark-mdx@3.0.1:
+    dependencies:
+      mdast-util-mdx: 3.0.0
+      micromark-extension-mdxjs: 3.0.0
+    transitivePeerDependencies:
+      - supports-color
+
+  remark-parse@11.0.0:
+    dependencies:
+      '@types/mdast': 4.0.3
+      mdast-util-from-markdown: 2.0.0
+      micromark-util-types: 2.0.0
+      unified: 11.0.4
+    transitivePeerDependencies:
+      - supports-color
+
+  remark-rehype@11.1.0:
+    dependencies:
+      '@types/hast': 3.0.4
+      '@types/mdast': 4.0.3
+      mdast-util-to-hast: 13.1.0
+      unified: 11.0.4
+      vfile: 6.0.1
+
+  remark-stringify@11.0.0:
+    dependencies:
+      '@types/mdast': 4.0.3
+      mdast-util-to-markdown: 2.1.0
+      unified: 11.0.4
+
+  renderkid@3.0.0:
     dependencies:
       css-select: 4.3.0
       dom-converter: 0.2.0
       htmlparser2: 6.1.0
       lodash: 4.17.21
       strip-ansi: 6.0.1
-    dev: false
 
-  /repeat-string@1.6.1:
-    resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==}
-    engines: {node: '>=0.10'}
-    dev: false
+  require-from-string@2.0.2: {}
 
-  /require-from-string@2.0.2:
-    resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
-    engines: {node: '>=0.10.0'}
-    dev: false
+  require-like@0.1.2: {}
 
-  /require-like@0.1.2:
-    resolution: {integrity: sha512-oyrU88skkMtDdauHDuKVrgR+zuItqr6/c//FXzvmxRGMexSDc6hNvJInGW3LL46n+8b50RykrvwSUIIQH2LQ5A==}
-    dev: false
+  requires-port@1.0.0: {}
 
-  /requires-port@1.0.0:
-    resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==}
-    dev: false
+  resolve-alpn@1.2.1: {}
 
-  /resolve-from@4.0.0:
-    resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
-    engines: {node: '>=4'}
-    dev: false
+  resolve-from@4.0.0: {}
 
-  /resolve-pathname@3.0.0:
-    resolution: {integrity: sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==}
-    dev: false
+  resolve-pathname@3.0.0: {}
 
-  /resolve@1.22.1:
-    resolution: {integrity: sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==}
-    hasBin: true
+  resolve@1.22.8:
     dependencies:
-      is-core-module: 2.11.0
+      is-core-module: 2.15.1
       path-parse: 1.0.7
       supports-preserve-symlinks-flag: 1.0.0
 
-  /responselike@1.0.2:
-    resolution: {integrity: sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ==}
+  responselike@3.0.0:
     dependencies:
-      lowercase-keys: 1.0.1
-    dev: false
+      lowercase-keys: 3.0.0
 
-  /retry@0.13.1:
-    resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==}
-    engines: {node: '>= 4'}
-    dev: false
+  retry@0.13.1: {}
 
-  /reusify@1.0.4:
-    resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
-    engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
+  reusify@1.0.4: {}
 
-  /rimraf@3.0.2:
-    resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
-    hasBin: true
+  rimraf@3.0.2:
     dependencies:
       glob: 7.2.3
-    dev: false
 
-  /robust-predicates@3.0.2:
-    resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==}
-    dev: false
+  robust-predicates@3.0.2: {}
 
-  /rtl-detect@1.0.4:
-    resolution: {integrity: sha512-EBR4I2VDSSYr7PkBmFy04uhycIpDKp+21p/jARYXlCSjQksTBQcJ0HFUPOO79EPPH5JS6VAhiIQbycf0O3JAxQ==}
-    dev: false
+  rtl-detect@1.0.4: {}
 
-  /rtlcss@3.5.0:
-    resolution: {integrity: sha512-wzgMaMFHQTnyi9YOwsx9LjOxYXJPzS8sYnFaKm6R5ysvTkwzHiB0vxnbHwchHQT65PTdBjDG21/kQBWI7q9O7A==}
-    hasBin: true
+  rtlcss@4.1.1:
     dependencies:
-      find-up: 5.0.0
-      picocolors: 1.0.0
+      escalade: 3.1.1
+      picocolors: 1.1.0
       postcss: 8.4.21
       strip-json-comments: 3.1.1
-    dev: false
 
-  /run-parallel@1.2.0:
-    resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
+  run-parallel@1.2.0:
     dependencies:
       queue-microtask: 1.2.3
 
-  /rw@1.3.3:
-    resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==}
-    dev: false
+  rw@1.3.3: {}
 
-  /rxjs@7.8.0:
-    resolution: {integrity: sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==}
+  sade@1.8.1:
     dependencies:
-      tslib: 2.4.1
-    dev: false
+      mri: 1.2.0
 
-  /safe-buffer@5.1.2:
-    resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}
-    dev: false
+  safe-buffer@5.1.2: {}
 
-  /safe-buffer@5.2.1:
-    resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
+  safe-buffer@5.2.1: {}
 
-  /safer-buffer@2.1.2:
-    resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
-    dev: false
+  safer-buffer@2.1.2: {}
 
-  /sax@1.2.4:
-    resolution: {integrity: sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==}
-    dev: false
+  sax@1.2.4: {}
 
-  /scheduler@0.20.2:
-    resolution: {integrity: sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==}
+  scheduler@0.23.0:
     dependencies:
       loose-envify: 1.4.0
-      object-assign: 4.1.1
 
-  /schema-utils@2.7.0:
-    resolution: {integrity: sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==}
-    engines: {node: '>= 8.9.0'}
+  schema-utils@2.7.0:
     dependencies:
       '@types/json-schema': 7.0.11
       ajv: 6.12.6
       ajv-keywords: 3.5.2(ajv@6.12.6)
-    dev: false
 
-  /schema-utils@2.7.1:
-    resolution: {integrity: sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==}
-    engines: {node: '>= 8.9.0'}
+  schema-utils@3.3.0:
     dependencies:
       '@types/json-schema': 7.0.11
       ajv: 6.12.6
       ajv-keywords: 3.5.2(ajv@6.12.6)
-    dev: false
 
-  /schema-utils@3.1.1:
-    resolution: {integrity: sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==}
-    engines: {node: '>= 10.13.0'}
+  schema-utils@4.0.0:
     dependencies:
       '@types/json-schema': 7.0.11
-      ajv: 6.12.6
-      ajv-keywords: 3.5.2(ajv@6.12.6)
+      ajv: 8.12.0
+      ajv-formats: 2.1.1(ajv@8.12.0)
+      ajv-keywords: 5.1.0(ajv@8.12.0)
 
-  /schema-utils@4.0.0:
-    resolution: {integrity: sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==}
-    engines: {node: '>= 12.13.0'}
+  schema-utils@4.2.0:
     dependencies:
       '@types/json-schema': 7.0.11
       ajv: 8.12.0
       ajv-formats: 2.1.1(ajv@8.12.0)
       ajv-keywords: 5.1.0(ajv@8.12.0)
-    dev: false
 
-  /section-matter@1.0.0:
-    resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==}
-    engines: {node: '>=4'}
+  search-insights@2.13.0: {}
+
+  section-matter@1.0.0:
     dependencies:
       extend-shallow: 2.0.1
       kind-of: 6.0.3
-    dev: false
 
-  /select-hose@2.0.0:
-    resolution: {integrity: sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==}
-    dev: false
+  select-hose@2.0.0: {}
 
-  /selfsigned@2.1.1:
-    resolution: {integrity: sha512-GSL3aowiF7wa/WtSFwnUrludWFoNhftq8bUkH9pkzjpN2XSPOAYEgg6e0sS9s0rZwgJzJiQRPU18A6clnoW5wQ==}
-    engines: {node: '>=10'}
+  selfsigned@2.1.1:
     dependencies:
       node-forge: 1.3.1
-    dev: false
 
-  /semver-diff@3.1.1:
-    resolution: {integrity: sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==}
-    engines: {node: '>=8'}
+  semver-diff@4.0.0:
     dependencies:
-      semver: 6.3.0
-    dev: false
-
-  /semver@5.7.1:
-    resolution: {integrity: sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==}
-    hasBin: true
-    dev: false
+      semver: 7.6.0
 
-  /semver@6.3.0:
-    resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==}
-    hasBin: true
-    dev: false
+  semver@6.3.1: {}
 
-  /semver@7.3.8:
-    resolution: {integrity: sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==}
-    engines: {node: '>=10'}
-    hasBin: true
+  semver@7.6.0:
     dependencies:
       lru-cache: 6.0.0
-    dev: false
 
-  /send@0.18.0:
-    resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==}
-    engines: {node: '>= 0.8.0'}
+  send@0.18.0:
     dependencies:
       debug: 2.6.9
       depd: 2.0.0
@@ -7813,15 +11169,12 @@ packages:
       statuses: 2.0.1
     transitivePeerDependencies:
       - supports-color
-    dev: false
 
-  /serialize-javascript@6.0.0:
-    resolution: {integrity: sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==}
+  serialize-javascript@6.0.2:
     dependencies:
       randombytes: 2.1.0
 
-  /serve-handler@6.1.5:
-    resolution: {integrity: sha512-ijPFle6Hwe8zfmBxJdE+5fta53fdIY0lHISJvuikXB3VYFafRjMRpOffSPvCYsbKyBA7pvy9oYr/BT1O3EArlg==}
+  serve-handler@6.1.5:
     dependencies:
       bytes: 3.0.0
       content-disposition: 0.5.2
@@ -7831,11 +11184,8 @@ packages:
       path-is-inside: 1.0.2
       path-to-regexp: 2.2.1
       range-parser: 1.2.0
-    dev: false
 
-  /serve-index@1.9.1:
-    resolution: {integrity: sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==}
-    engines: {node: '>= 0.8.0'}
+  serve-index@1.9.1:
     dependencies:
       accepts: 1.3.8
       batch: 0.6.1
@@ -7846,11 +11196,8 @@ packages:
       parseurl: 1.3.3
     transitivePeerDependencies:
       - supports-color
-    dev: false
 
-  /serve-static@1.15.0:
-    resolution: {integrity: sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==}
-    engines: {node: '>= 0.8.0'}
+  serve-static@1.15.0:
     dependencies:
       encodeurl: 1.0.2
       escape-html: 1.0.3
@@ -7858,139 +11205,93 @@ packages:
       send: 0.18.0
     transitivePeerDependencies:
       - supports-color
-    dev: false
 
-  /setimmediate@1.0.5:
-    resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==}
-    dev: false
-
-  /setprototypeof@1.1.0:
-    resolution: {integrity: sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==}
-    dev: false
+  setprototypeof@1.1.0: {}
 
-  /setprototypeof@1.2.0:
-    resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
-    dev: false
+  setprototypeof@1.2.0: {}
 
-  /shallow-clone@3.0.1:
-    resolution: {integrity: sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==}
-    engines: {node: '>=8'}
+  shallow-clone@3.0.1:
     dependencies:
       kind-of: 6.0.3
 
-  /shallowequal@1.1.0:
-    resolution: {integrity: sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==}
+  shallowequal@1.1.0: {}
 
-  /shebang-command@2.0.0:
-    resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
-    engines: {node: '>=8'}
+  shebang-command@2.0.0:
     dependencies:
       shebang-regex: 3.0.0
-    dev: false
 
-  /shebang-regex@3.0.0:
-    resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
-    engines: {node: '>=8'}
-    dev: false
+  shebang-regex@3.0.0: {}
 
-  /shell-quote@1.7.4:
-    resolution: {integrity: sha512-8o/QEhSSRb1a5i7TFR0iM4G16Z0vYB2OQVs4G3aAFXjn3T6yEx8AZxy1PgDF7I00LZHYA3WxaSYIf5e5sAX8Rw==}
-    dev: false
+  shell-quote@1.8.1: {}
 
-  /shelljs@0.8.5:
-    resolution: {integrity: sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==}
-    engines: {node: '>=4'}
-    hasBin: true
+  shelljs@0.8.5:
     dependencies:
       glob: 7.2.3
       interpret: 1.4.0
       rechoir: 0.6.2
-    dev: false
 
-  /side-channel@1.0.4:
-    resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==}
+  side-channel@1.0.4:
     dependencies:
       call-bind: 1.0.2
       get-intrinsic: 1.1.3
       object-inspect: 1.12.2
-    dev: false
 
-  /signal-exit@3.0.7:
-    resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
-    dev: false
+  signal-exit@3.0.7: {}
 
-  /sirv@1.0.19:
-    resolution: {integrity: sha512-JuLThK3TnZG1TAKDwNIqNq6QA2afLOCcm+iE8D1Kj3GA40pSPsxQjjJl0J8X3tsR7T+CP1GavpzLwYkgVLWrZQ==}
-    engines: {node: '>= 10'}
+  signal-exit@4.1.0: {}
+
+  sirv@2.0.4:
     dependencies:
-      '@polka/url': 1.0.0-next.21
-      mrmime: 1.0.1
-      totalist: 1.1.0
-    dev: false
+      '@polka/url': 1.0.0-next.24
+      mrmime: 2.0.0
+      totalist: 3.0.1
 
-  /sisteransi@1.0.5:
-    resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}
-    dev: false
+  sisteransi@1.0.5: {}
 
-  /sitemap@7.1.1:
-    resolution: {integrity: sha512-mK3aFtjz4VdJN0igpIJrinf3EO8U8mxOPsTBzSsy06UtjZQJ3YY3o3Xa7zSc5nMqcMrRwlChHZ18Kxg0caiPBg==}
-    engines: {node: '>=12.0.0', npm: '>=5.6.0'}
-    hasBin: true
+  sitemap@7.1.1:
     dependencies:
       '@types/node': 17.0.45
       '@types/sax': 1.2.4
       arg: 5.0.2
       sax: 1.2.4
-    dev: false
 
-  /slash@3.0.0:
-    resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
-    engines: {node: '>=8'}
-    dev: false
+  skin-tone@2.0.0:
+    dependencies:
+      unicode-emoji-modifier-base: 1.0.0
 
-  /slash@4.0.0:
-    resolution: {integrity: sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==}
-    engines: {node: '>=12'}
-    dev: false
+  slash@3.0.0: {}
 
-  /sockjs@0.3.24:
-    resolution: {integrity: sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==}
+  slash@4.0.0: {}
+
+  snake-case@3.0.4:
+    dependencies:
+      dot-case: 3.0.4
+      tslib: 2.6.2
+
+  sockjs@0.3.24:
     dependencies:
       faye-websocket: 0.11.4
       uuid: 8.3.2
       websocket-driver: 0.7.4
-    dev: false
 
-  /sort-css-media-queries@2.1.0:
-    resolution: {integrity: sha512-IeWvo8NkNiY2vVYdPa27MCQiR0MN0M80johAYFVxWWXQ44KU84WNxjslwBHmc/7ZL2ccwkM7/e6S5aiKZXm7jA==}
-    engines: {node: '>= 6.3.0'}
-    dev: false
+  sort-css-media-queries@2.2.0: {}
 
-  /source-map-js@1.0.2:
-    resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
-    engines: {node: '>=0.10.0'}
+  source-map-js@1.0.2: {}
 
-  /source-map-support@0.5.21:
-    resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==}
+  source-map-js@1.2.1: {}
+
+  source-map-support@0.5.21:
     dependencies:
       buffer-from: 1.1.2
       source-map: 0.6.1
 
-  /source-map@0.5.7:
-    resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==}
-    engines: {node: '>=0.10.0'}
-    dev: false
+  source-map@0.6.1: {}
 
-  /source-map@0.6.1:
-    resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
-    engines: {node: '>=0.10.0'}
+  source-map@0.7.4: {}
 
-  /space-separated-tokens@1.1.5:
-    resolution: {integrity: sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==}
-    dev: false
+  space-separated-tokens@2.0.2: {}
 
-  /spdy-transport@3.0.0:
-    resolution: {integrity: sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==}
+  spdy-transport@3.0.0:
     dependencies:
       debug: 4.3.4
       detect-node: 2.1.0
@@ -8000,11 +11301,8 @@ packages:
       wbuf: 1.7.3
     transitivePeerDependencies:
       - supports-color
-    dev: false
 
-  /spdy@4.0.2:
-    resolution: {integrity: sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==}
-    engines: {node: '>=6.0.0'}
+  spdy@4.0.2:
     dependencies:
       debug: 4.3.4
       handle-thing: 2.0.1
@@ -8013,705 +11311,418 @@ packages:
       spdy-transport: 3.0.0
     transitivePeerDependencies:
       - supports-color
-    dev: false
 
-  /sprintf-js@1.0.3:
-    resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==}
-    dev: false
-
-  /stable@0.1.8:
-    resolution: {integrity: sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==}
-    deprecated: 'Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility'
-    dev: false
+  sprintf-js@1.0.3: {}
 
-  /state-toggle@1.0.3:
-    resolution: {integrity: sha512-d/5Z4/2iiCnHw6Xzghyhb+GcmF89bxwgXG60wjIiZaxnymbyOmI8Hk4VqHXiVVp6u2ysaskFfXg3ekCj4WNftQ==}
-    dev: false
+  srcset@4.0.0: {}
 
-  /statuses@1.5.0:
-    resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==}
-    engines: {node: '>= 0.6'}
-    dev: false
+  statuses@1.5.0: {}
 
-  /statuses@2.0.1:
-    resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
-    engines: {node: '>= 0.8'}
-    dev: false
+  statuses@2.0.1: {}
 
-  /std-env@3.3.1:
-    resolution: {integrity: sha512-3H20QlwQsSm2OvAxWIYhs+j01MzzqwMwGiiO1NQaJYZgJZFPuAbf95/DiKRBSTYIJ2FeGUc+B/6mPGcWP9dO3Q==}
-    dev: false
+  std-env@3.3.1: {}
 
-  /string-width@4.2.3:
-    resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
-    engines: {node: '>=8'}
+  string-width@4.2.3:
     dependencies:
       emoji-regex: 8.0.0
       is-fullwidth-code-point: 3.0.0
       strip-ansi: 6.0.1
-    dev: false
 
-  /string-width@5.1.2:
-    resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
-    engines: {node: '>=12'}
+  string-width@5.1.2:
     dependencies:
       eastasianwidth: 0.2.0
       emoji-regex: 9.2.2
-      strip-ansi: 7.0.1
-    dev: false
+      strip-ansi: 7.1.0
 
-  /string_decoder@1.1.1:
-    resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==}
+  string_decoder@1.1.1:
     dependencies:
       safe-buffer: 5.1.2
-    dev: false
 
-  /string_decoder@1.3.0:
-    resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
+  string_decoder@1.3.0:
     dependencies:
       safe-buffer: 5.2.1
-    dev: false
 
-  /stringify-object@3.3.0:
-    resolution: {integrity: sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==}
-    engines: {node: '>=4'}
+  stringify-entities@4.0.3:
+    dependencies:
+      character-entities-html4: 2.1.0
+      character-entities-legacy: 3.0.0
+
+  stringify-object@3.3.0:
     dependencies:
       get-own-enumerable-property-symbols: 3.0.2
       is-obj: 1.0.1
       is-regexp: 1.0.0
-    dev: false
 
-  /strip-ansi@6.0.1:
-    resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
-    engines: {node: '>=8'}
+  strip-ansi@6.0.1:
     dependencies:
       ansi-regex: 5.0.1
-    dev: false
 
-  /strip-ansi@7.0.1:
-    resolution: {integrity: sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==}
-    engines: {node: '>=12'}
+  strip-ansi@7.1.0:
     dependencies:
-      ansi-regex: 6.0.1
-    dev: false
+      ansi-regex: 6.1.0
 
-  /strip-bom-string@1.0.0:
-    resolution: {integrity: sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==}
-    engines: {node: '>=0.10.0'}
-    dev: false
+  strip-bom-string@1.0.0: {}
 
-  /strip-final-newline@2.0.0:
-    resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==}
-    engines: {node: '>=6'}
-    dev: false
+  strip-final-newline@2.0.0: {}
 
-  /strip-json-comments@2.0.1:
-    resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==}
-    engines: {node: '>=0.10.0'}
-    dev: false
+  strip-json-comments@2.0.1: {}
 
-  /strip-json-comments@3.1.1:
-    resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
-    engines: {node: '>=8'}
-    dev: false
+  strip-json-comments@3.1.1: {}
 
-  /style-to-object@0.3.0:
-    resolution: {integrity: sha512-CzFnRRXhzWIdItT3OmF8SQfWyahHhjq3HwcMNCNLn+N7klOOqPjMeG/4JSu77D7ypZdGvSzvkrbyeTMizz2VrA==}
+  style-to-object@0.4.4:
     dependencies:
       inline-style-parser: 0.1.1
-    dev: false
 
-  /stylehacks@5.1.1(postcss@8.4.21):
-    resolution: {integrity: sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw==}
-    engines: {node: ^10 || ^12 || >=14.0}
-    peerDependencies:
-      postcss: ^8.2.15
+  style-to-object@1.0.5:
+    dependencies:
+      inline-style-parser: 0.2.2
+
+  stylehacks@6.1.1(postcss@8.4.35):
+    dependencies:
+      browserslist: 4.23.1
+      postcss: 8.4.35
+      postcss-selector-parser: 6.1.2
+
+  stylehacks@6.1.1(postcss@8.4.45):
     dependencies:
-      browserslist: 4.21.4
-      postcss: 8.4.21
-      postcss-selector-parser: 6.0.11
-    dev: false
+      browserslist: 4.23.1
+      postcss: 8.4.45
+      postcss-selector-parser: 6.1.2
 
-  /stylis@4.3.0:
-    resolution: {integrity: sha512-E87pIogpwUsUwXw7dNyU4QDjdgVMy52m+XEOPEKUn161cCzWjjhPSQhByfd1CcNvrOLnXQ6OnnZDwnJrz/Z4YQ==}
-    dev: false
+  stylis@4.3.0: {}
 
-  /supports-color@5.5.0:
-    resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==}
-    engines: {node: '>=4'}
+  sucrase@3.35.0:
+    dependencies:
+      '@jridgewell/gen-mapping': 0.3.5
+      commander: 4.1.1
+      glob: 10.4.5
+      lines-and-columns: 1.2.4
+      mz: 2.7.0
+      pirates: 4.0.6
+      ts-interface-checker: 0.1.13
+
+  supports-color@5.5.0:
     dependencies:
       has-flag: 3.0.0
-    dev: false
 
-  /supports-color@7.2.0:
-    resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
-    engines: {node: '>=8'}
+  supports-color@7.2.0:
     dependencies:
       has-flag: 4.0.0
-    dev: false
 
-  /supports-color@8.1.1:
-    resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==}
-    engines: {node: '>=10'}
+  supports-color@8.1.1:
     dependencies:
       has-flag: 4.0.0
 
-  /supports-preserve-symlinks-flag@1.0.0:
-    resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
-    engines: {node: '>= 0.4'}
+  supports-preserve-symlinks-flag@1.0.0: {}
 
-  /svg-parser@2.0.4:
-    resolution: {integrity: sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==}
-    dev: false
+  svg-parser@2.0.4: {}
 
-  /svgo@2.8.0:
-    resolution: {integrity: sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==}
-    engines: {node: '>=10.13.0'}
-    hasBin: true
+  svgo@3.3.2:
     dependencies:
       '@trysound/sax': 0.2.0
       commander: 7.2.0
-      css-select: 4.3.0
-      css-tree: 1.1.3
-      csso: 4.2.0
-      picocolors: 1.0.0
-      stable: 0.1.8
-    dev: false
+      css-select: 5.1.0
+      css-tree: 2.3.1
+      css-what: 6.1.0
+      csso: 5.0.5
+      picocolors: 1.1.0
 
-  /tailwindcss@3.2.4(postcss@8.4.21):
-    resolution: {integrity: sha512-AhwtHCKMtR71JgeYDaswmZXhPcW9iuI9Sp2LvZPo9upDZ7231ZJ7eA9RaURbhpXGVlrjX4cFNlB4ieTetEb7hQ==}
-    engines: {node: '>=12.13.0'}
-    hasBin: true
-    peerDependencies:
-      postcss: ^8.0.9
+  tailwindcss@3.4.11:
     dependencies:
+      '@alloc/quick-lru': 5.2.0
       arg: 5.0.2
       chokidar: 3.5.3
-      color-name: 1.1.4
-      detective: 5.2.1
       didyoumean: 1.2.2
       dlv: 1.1.3
-      fast-glob: 3.2.12
+      fast-glob: 3.3.2
       glob-parent: 6.0.2
       is-glob: 4.0.3
-      lilconfig: 2.0.6
+      jiti: 1.21.6
+      lilconfig: 2.1.0
       micromatch: 4.0.5
       normalize-path: 3.0.0
       object-hash: 3.0.0
       picocolors: 1.0.0
-      postcss: 8.4.21
-      postcss-import: 14.1.0(postcss@8.4.21)
-      postcss-js: 4.0.0(postcss@8.4.21)
-      postcss-load-config: 3.1.4(postcss@8.4.21)
-      postcss-nested: 6.0.0(postcss@8.4.21)
+      postcss: 8.4.45
+      postcss-import: 15.1.0(postcss@8.4.45)
+      postcss-js: 4.0.1(postcss@8.4.45)
+      postcss-load-config: 4.0.2(postcss@8.4.45)
+      postcss-nested: 6.2.0(postcss@8.4.45)
       postcss-selector-parser: 6.0.11
-      postcss-value-parser: 4.2.0
-      quick-lru: 5.1.1
-      resolve: 1.22.1
+      resolve: 1.22.8
+      sucrase: 3.35.0
     transitivePeerDependencies:
       - ts-node
 
-  /tapable@1.1.3:
-    resolution: {integrity: sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==}
-    engines: {node: '>=6'}
-    dev: false
+  tapable@1.1.3: {}
 
-  /tapable@2.2.1:
-    resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==}
-    engines: {node: '>=6'}
+  tapable@2.2.1: {}
 
-  /terser-webpack-plugin@5.3.6(webpack@5.75.0):
-    resolution: {integrity: sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ==}
-    engines: {node: '>= 10.13.0'}
-    peerDependencies:
-      '@swc/core': '*'
-      esbuild: '*'
-      uglify-js: '*'
-      webpack: ^5.1.0
-    peerDependenciesMeta:
-      '@swc/core':
-        optional: true
-      esbuild:
-        optional: true
-      uglify-js:
-        optional: true
+  terser-webpack-plugin@5.3.10(webpack@5.90.1):
     dependencies:
-      '@jridgewell/trace-mapping': 0.3.17
+      '@jridgewell/trace-mapping': 0.3.25
       jest-worker: 27.5.1
-      schema-utils: 3.1.1
-      serialize-javascript: 6.0.0
-      terser: 5.16.1
-      webpack: 5.75.0
+      schema-utils: 3.3.0
+      serialize-javascript: 6.0.2
+      terser: 5.27.0
+      webpack: 5.90.1
 
-  /terser@5.16.1:
-    resolution: {integrity: sha512-xvQfyfA1ayT0qdK47zskQgRZeWLoOQ8JQ6mIgRGVNwZKdQMU+5FkCBjmv4QjcrTzyZquRw2FVtlJSRUmMKQslw==}
-    engines: {node: '>=10'}
-    hasBin: true
+  terser@5.27.0:
     dependencies:
-      '@jridgewell/source-map': 0.3.2
-      acorn: 8.8.1
+      '@jridgewell/source-map': 0.3.5
+      acorn: 8.11.3
       commander: 2.20.3
       source-map-support: 0.5.21
 
-  /text-table@0.2.0:
-    resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
-    dev: false
+  text-table@0.2.0: {}
 
-  /thunky@1.1.0:
-    resolution: {integrity: sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==}
-    dev: false
+  thenify-all@1.6.0:
+    dependencies:
+      thenify: 3.3.1
 
-  /tiny-invariant@1.3.1:
-    resolution: {integrity: sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==}
-    dev: false
+  thenify@3.3.1:
+    dependencies:
+      any-promise: 1.3.0
 
-  /tiny-warning@1.0.3:
-    resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==}
-    dev: false
+  thunky@1.1.0: {}
 
-  /to-fast-properties@2.0.0:
-    resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==}
-    engines: {node: '>=4'}
-    dev: false
+  tiny-invariant@1.3.1: {}
 
-  /to-readable-stream@1.0.0:
-    resolution: {integrity: sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==}
-    engines: {node: '>=6'}
-    dev: false
+  tiny-warning@1.0.3: {}
 
-  /to-regex-range@5.0.1:
-    resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
-    engines: {node: '>=8.0'}
+  to-fast-properties@2.0.0: {}
+
+  to-regex-range@5.0.1:
     dependencies:
       is-number: 7.0.0
 
-  /toidentifier@1.0.1:
-    resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
-    engines: {node: '>=0.6'}
-    dev: false
-
-  /totalist@1.1.0:
-    resolution: {integrity: sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g==}
-    engines: {node: '>=6'}
-    dev: false
+  toidentifier@1.0.1: {}
 
-  /tr46@0.0.3:
-    resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
-    dev: false
+  totalist@3.0.1: {}
 
-  /trim-trailing-lines@1.1.4:
-    resolution: {integrity: sha512-rjUWSqnfTNrjbB9NQWfPMH/xRK1deHeGsHoVfpxJ++XeYXE0d6B1En37AHfw3jtfTU7dzMzZL2jjpe8Qb5gLIQ==}
-    dev: false
+  trim-lines@3.0.1: {}
 
-  /trim@0.0.1:
-    resolution: {integrity: sha1-WFhUf2spB1fulczMZm+1AITEYN0=}
-    dev: false
+  trough@2.2.0: {}
 
-  /trough@1.0.5:
-    resolution: {integrity: sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==}
-    dev: false
+  ts-dedent@2.2.0: {}
 
-  /ts-dedent@2.2.0:
-    resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==}
-    engines: {node: '>=6.10'}
-    dev: false
+  ts-interface-checker@0.1.13: {}
 
-  /tslib@2.4.1:
-    resolution: {integrity: sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==}
-    dev: false
+  tslib@2.6.2: {}
 
-  /type-fest@0.20.2:
-    resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==}
-    engines: {node: '>=10'}
-    dev: false
+  type-fest@1.4.0: {}
 
-  /type-fest@2.19.0:
-    resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==}
-    engines: {node: '>=12.20'}
-    dev: false
+  type-fest@2.19.0: {}
 
-  /type-is@1.6.18:
-    resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
-    engines: {node: '>= 0.6'}
+  type-is@1.6.18:
     dependencies:
       media-typer: 0.3.0
       mime-types: 2.1.35
-    dev: false
 
-  /typedarray-to-buffer@3.1.5:
-    resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==}
+  typedarray-to-buffer@3.1.5:
     dependencies:
       is-typedarray: 1.0.0
-    dev: false
 
-  /typescript@4.9.4:
-    resolution: {integrity: sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==}
-    engines: {node: '>=4.2.0'}
-    hasBin: true
+  typescript@5.3.3: {}
 
-  /ua-parser-js@0.7.32:
-    resolution: {integrity: sha512-f9BESNVhzlhEFf2CHMSj40NWOjYPl1YKYbrvIr/hFTDEmLq7SRbWvm7FcdcpCYT95zrOhC7gZSxjdnnTpBcwVw==}
-    dev: false
+  undici-types@5.26.5: {}
 
-  /unherit@1.1.3:
-    resolution: {integrity: sha512-Ft16BJcnapDKp0+J/rqFC3Rrk6Y/Ng4nzsC028k2jdDII/rdZ7Wd3pPT/6+vIIxRagwRc9K0IUX0Ra4fKvw+WQ==}
-    dependencies:
-      inherits: 2.0.4
-      xtend: 4.0.2
-    dev: false
+  unicode-canonical-property-names-ecmascript@2.0.0: {}
 
-  /unicode-canonical-property-names-ecmascript@2.0.0:
-    resolution: {integrity: sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==}
-    engines: {node: '>=4'}
-    dev: false
+  unicode-emoji-modifier-base@1.0.0: {}
 
-  /unicode-match-property-ecmascript@2.0.0:
-    resolution: {integrity: sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==}
-    engines: {node: '>=4'}
+  unicode-match-property-ecmascript@2.0.0:
     dependencies:
       unicode-canonical-property-names-ecmascript: 2.0.0
       unicode-property-aliases-ecmascript: 2.1.0
-    dev: false
 
-  /unicode-match-property-value-ecmascript@2.1.0:
-    resolution: {integrity: sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==}
-    engines: {node: '>=4'}
-    dev: false
+  unicode-match-property-value-ecmascript@2.1.0: {}
 
-  /unicode-property-aliases-ecmascript@2.1.0:
-    resolution: {integrity: sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==}
-    engines: {node: '>=4'}
-    dev: false
+  unicode-property-aliases-ecmascript@2.1.0: {}
 
-  /unified@9.2.0:
-    resolution: {integrity: sha512-vx2Z0vY+a3YoTj8+pttM3tiJHCwY5UFbYdiWrwBEbHmK8pvsPj2rtAX2BFfgXen8T39CJWblWRDT4L5WGXtDdg==}
+  unified@11.0.4:
     dependencies:
-      '@types/unist': 2.0.6
-      bail: 1.0.5
+      '@types/unist': 3.0.2
+      bail: 2.0.2
+      devlop: 1.1.0
       extend: 3.0.2
-      is-buffer: 2.0.5
-      is-plain-obj: 2.1.0
-      trough: 1.0.5
-      vfile: 4.2.1
-    dev: false
+      is-plain-obj: 4.1.0
+      trough: 2.2.0
+      vfile: 6.0.1
 
-  /unified@9.2.2:
-    resolution: {integrity: sha512-Sg7j110mtefBD+qunSLO1lqOEKdrwBFBrR6Qd8f4uwkhWNlbkaqwHse6e7QvD3AP/MNoJdEDLaf8OxYyoWgorQ==}
-    dependencies:
-      '@types/unist': 2.0.6
-      bail: 1.0.5
-      extend: 3.0.2
-      is-buffer: 2.0.5
-      is-plain-obj: 2.1.0
-      trough: 1.0.5
-      vfile: 4.2.1
-    dev: false
-
-  /unique-string@2.0.0:
-    resolution: {integrity: sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==}
-    engines: {node: '>=8'}
+  unique-string@3.0.0:
     dependencies:
-      crypto-random-string: 2.0.0
-    dev: false
-
-  /unist-builder@2.0.3:
-    resolution: {integrity: sha512-f98yt5pnlMWlzP539tPc4grGMsFaQQlP/vM396b00jngsiINumNmsY8rkXjfoi1c6QaM8nQ3vaGDuoKWbe/1Uw==}
-    dev: false
+      crypto-random-string: 4.0.0
 
-  /unist-util-generated@1.1.6:
-    resolution: {integrity: sha512-cln2Mm1/CZzN5ttGK7vkoGw+RZ8VcUH6BtGbq98DDtRGquAAOXig1mrBQYelOwMXYS8rK+vZDyyojSjp7JX+Lg==}
-    dev: false
-
-  /unist-util-is@4.1.0:
-    resolution: {integrity: sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==}
-    dev: false
+  unist-util-is@6.0.0:
+    dependencies:
+      '@types/unist': 3.0.2
 
-  /unist-util-position@3.1.0:
-    resolution: {integrity: sha512-w+PkwCbYSFw8vpgWD0v7zRCl1FpY3fjDSQ3/N/wNd9Ffa4gPi8+4keqt99N3XW6F99t/mUzp2xAhNmfKWp95QA==}
-    dev: false
+  unist-util-position-from-estree@2.0.0:
+    dependencies:
+      '@types/unist': 3.0.2
 
-  /unist-util-remove-position@2.0.1:
-    resolution: {integrity: sha512-fDZsLYIe2uT+oGFnuZmy73K6ZxOPG/Qcm+w7jbEjaFcJgbQ6cqjs/eSPzXhsmGpAsWPkqZM9pYjww5QTn3LHMA==}
+  unist-util-position@5.0.0:
     dependencies:
-      unist-util-visit: 2.0.3
-    dev: false
+      '@types/unist': 3.0.2
 
-  /unist-util-remove@2.1.0:
-    resolution: {integrity: sha512-J8NYPyBm4baYLdCbjmf1bhPu45Cr1MWTm77qd9istEkzWpnN6O9tMsEbB2JhNnBCqGENRqEWomQ+He6au0B27Q==}
+  unist-util-remove-position@5.0.0:
     dependencies:
-      unist-util-is: 4.1.0
-    dev: false
+      '@types/unist': 3.0.2
+      unist-util-visit: 5.0.0
 
-  /unist-util-stringify-position@2.0.3:
-    resolution: {integrity: sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==}
+  unist-util-stringify-position@3.0.3:
     dependencies:
       '@types/unist': 2.0.6
-    dev: false
 
-  /unist-util-visit-parents@3.1.1:
-    resolution: {integrity: sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg==}
+  unist-util-stringify-position@4.0.0:
     dependencies:
-      '@types/unist': 2.0.6
-      unist-util-is: 4.1.0
-    dev: false
+      '@types/unist': 3.0.2
 
-  /unist-util-visit@2.0.3:
-    resolution: {integrity: sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==}
+  unist-util-visit-parents@6.0.1:
     dependencies:
-      '@types/unist': 2.0.6
-      unist-util-is: 4.1.0
-      unist-util-visit-parents: 3.1.1
-    dev: false
+      '@types/unist': 3.0.2
+      unist-util-is: 6.0.0
 
-  /universalify@2.0.0:
-    resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==}
-    engines: {node: '>= 10.0.0'}
-    dev: false
+  unist-util-visit@5.0.0:
+    dependencies:
+      '@types/unist': 3.0.2
+      unist-util-is: 6.0.0
+      unist-util-visit-parents: 6.0.1
 
-  /unpipe@1.0.0:
-    resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}
-    engines: {node: '>= 0.8'}
-    dev: false
+  universalify@2.0.0: {}
 
-  /update-browserslist-db@1.0.10(browserslist@4.21.4):
-    resolution: {integrity: sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==}
-    hasBin: true
-    peerDependencies:
-      browserslist: '>= 4.21.0'
+  unpipe@1.0.0: {}
+
+  update-browserslist-db@1.0.10(browserslist@4.21.4):
     dependencies:
       browserslist: 4.21.4
       escalade: 3.1.1
       picocolors: 1.0.0
 
-  /update-notifier@5.1.0:
-    resolution: {integrity: sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw==}
-    engines: {node: '>=10'}
-    dependencies:
-      boxen: 5.1.2
-      chalk: 4.1.2
-      configstore: 5.0.1
-      has-yarn: 2.1.0
-      import-lazy: 2.1.0
-      is-ci: 2.0.0
-      is-installed-globally: 0.4.0
-      is-npm: 5.0.0
-      is-yarn-global: 0.3.0
-      latest-version: 5.1.0
-      pupa: 2.1.1
-      semver: 7.3.8
-      semver-diff: 3.1.1
-      xdg-basedir: 4.0.0
-    dev: false
-
-  /uri-js@4.4.1:
-    resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
-    dependencies:
-      punycode: 2.1.1
-
-  /url-loader@4.1.1(file-loader@6.2.0)(webpack@5.75.0):
-    resolution: {integrity: sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA==}
-    engines: {node: '>= 10.13.0'}
-    peerDependencies:
-      file-loader: '*'
-      webpack: ^4.0.0 || ^5.0.0
-    peerDependenciesMeta:
-      file-loader:
-        optional: true
-    dependencies:
-      file-loader: 6.2.0(webpack@5.75.0)
-      loader-utils: 2.0.4
-      mime-types: 2.1.35
-      schema-utils: 3.1.1
-      webpack: 5.75.0
-    dev: false
-
-  /url-parse-lax@3.0.0:
-    resolution: {integrity: sha512-NjFKA0DidqPa5ciFcSrXnAltTtzz84ogy+NebPvfEgAck0+TNg4UJ4IN+fB7zRZfbgUf0syOo9MDxFkDSMuFaQ==}
-    engines: {node: '>=4'}
+  update-browserslist-db@1.0.13(browserslist@4.22.3):
     dependencies:
-      prepend-http: 2.0.0
-    dev: false
+      browserslist: 4.22.3
+      escalade: 3.1.1
+      picocolors: 1.1.0
 
-  /use-composed-ref@1.3.0(react@17.0.2):
-    resolution: {integrity: sha512-GLMG0Jc/jiKov/3Ulid1wbv3r54K9HlMW29IWcDFPEqFkSO2nS0MuefWgMJpeHQ9YJeXDL3ZUF+P3jdXlZX/cQ==}
-    peerDependencies:
-      react: ^16.8.0 || ^17.0.0 || ^18.0.0
+  update-browserslist-db@1.0.16(browserslist@4.23.1):
     dependencies:
-      react: 17.0.2
-    dev: false
+      browserslist: 4.23.1
+      escalade: 3.1.2
+      picocolors: 1.1.0
 
-  /use-isomorphic-layout-effect@1.1.2(@types/react@18.0.26)(react@17.0.2):
-    resolution: {integrity: sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==}
-    peerDependencies:
-      '@types/react': '*'
-      react: ^16.8.0 || ^17.0.0 || ^18.0.0
-    peerDependenciesMeta:
-      '@types/react':
-        optional: true
+  update-notifier@6.0.2:
     dependencies:
-      '@types/react': 18.0.26
-      react: 17.0.2
-    dev: false
+      boxen: 7.1.1
+      chalk: 5.3.0
+      configstore: 6.0.0
+      has-yarn: 3.0.0
+      import-lazy: 4.0.0
+      is-ci: 3.0.1
+      is-installed-globally: 0.4.0
+      is-npm: 6.0.0
+      is-yarn-global: 0.4.1
+      latest-version: 7.0.0
+      pupa: 3.1.0
+      semver: 7.6.0
+      semver-diff: 4.0.0
+      xdg-basedir: 5.1.0
 
-  /use-latest@1.2.1(@types/react@18.0.26)(react@17.0.2):
-    resolution: {integrity: sha512-xA+AVm/Wlg3e2P/JiItTziwS7FK92LWrDB0p+hgXloIMuVCeJJ8v6f0eeHyPZaJrM+usM1FkFfbNCrJGs8A/zw==}
-    peerDependencies:
-      '@types/react': '*'
-      react: ^16.8.0 || ^17.0.0 || ^18.0.0
-    peerDependenciesMeta:
-      '@types/react':
-        optional: true
+  uri-js@4.4.1:
     dependencies:
-      '@types/react': 18.0.26
-      react: 17.0.2
-      use-isomorphic-layout-effect: 1.1.2(@types/react@18.0.26)(react@17.0.2)
-    dev: false
+      punycode: 2.1.1
 
-  /use-sync-external-store@1.2.0(react@17.0.2):
-    resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==}
-    peerDependencies:
-      react: ^16.8.0 || ^17.0.0 || ^18.0.0
+  url-loader@4.1.1(file-loader@6.2.0(webpack@5.90.1))(webpack@5.90.1):
     dependencies:
-      react: 17.0.2
-    dev: false
+      loader-utils: 2.0.4
+      mime-types: 2.1.35
+      schema-utils: 3.3.0
+      webpack: 5.90.1
+    optionalDependencies:
+      file-loader: 6.2.0(webpack@5.90.1)
 
-  /util-deprecate@1.0.2:
-    resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
+  util-deprecate@1.0.2: {}
 
-  /utila@0.4.0:
-    resolution: {integrity: sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==}
-    dev: false
+  utila@0.4.0: {}
 
-  /utility-types@3.10.0:
-    resolution: {integrity: sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg==}
-    engines: {node: '>= 4'}
+  utility-types@3.10.0: {}
 
-  /utils-merge@1.0.1:
-    resolution: {integrity: sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=}
-    engines: {node: '>= 0.4.0'}
-    dev: false
+  utils-merge@1.0.1: {}
 
-  /uuid@8.3.2:
-    resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
-    hasBin: true
-    dev: false
+  uuid@8.3.2: {}
 
-  /uuid@9.0.1:
-    resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==}
-    hasBin: true
-    dev: false
+  uuid@9.0.1: {}
 
-  /value-equal@1.0.1:
-    resolution: {integrity: sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==}
-    dev: false
+  uvu@0.5.6:
+    dependencies:
+      dequal: 2.0.3
+      diff: 5.2.0
+      kleur: 4.1.5
+      sade: 1.8.1
 
-  /vary@1.1.2:
-    resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
-    engines: {node: '>= 0.8'}
-    dev: false
+  value-equal@1.0.1: {}
 
-  /vfile-location@3.2.0:
-    resolution: {integrity: sha512-aLEIZKv/oxuCDZ8lkJGhuhztf/BW4M+iHdCwglA/eWc+vtuRFJj8EtgceYFX4LRjOhCAAiNHsKGssC6onJ+jbA==}
-    dev: false
+  vary@1.1.2: {}
 
-  /vfile-message@2.0.4:
-    resolution: {integrity: sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ==}
+  vfile-location@5.0.2:
     dependencies:
-      '@types/unist': 2.0.6
-      unist-util-stringify-position: 2.0.3
-    dev: false
+      '@types/unist': 3.0.2
+      vfile: 6.0.1
 
-  /vfile@4.2.1:
-    resolution: {integrity: sha512-O6AE4OskCG5S1emQ/4gl8zK586RqA3srz3nfK/Viy0UPToBc5Trp9BVFb1u0CjsKrAWwnpr4ifM/KBXPWwJbCA==}
+  vfile-message@4.0.2:
     dependencies:
-      '@types/unist': 2.0.6
-      is-buffer: 2.0.5
-      unist-util-stringify-position: 2.0.3
-      vfile-message: 2.0.4
-    dev: false
+      '@types/unist': 3.0.2
+      unist-util-stringify-position: 4.0.0
 
-  /wait-on@6.0.1:
-    resolution: {integrity: sha512-zht+KASY3usTY5u2LgaNqn/Cd8MukxLGjdcZxT2ns5QzDmTFc4XoWBgC+C/na+sMRZTuVygQoMYwdcVjHnYIVw==}
-    engines: {node: '>=10.0.0'}
-    hasBin: true
+  vfile@6.0.1:
     dependencies:
-      axios: 0.25.0
-      joi: 17.7.0
-      lodash: 4.17.21
-      minimist: 1.2.7
-      rxjs: 7.8.0
-    transitivePeerDependencies:
-      - debug
-    dev: false
+      '@types/unist': 3.0.2
+      unist-util-stringify-position: 4.0.0
+      vfile-message: 4.0.2
 
-  /watchpack@2.4.0:
-    resolution: {integrity: sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==}
-    engines: {node: '>=10.13.0'}
+  watchpack@2.4.0:
     dependencies:
       glob-to-regexp: 0.4.1
       graceful-fs: 4.2.10
 
-  /wbuf@1.7.3:
-    resolution: {integrity: sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==}
+  wbuf@1.7.3:
     dependencies:
       minimalistic-assert: 1.0.1
-    dev: false
-
-  /web-namespaces@1.1.4:
-    resolution: {integrity: sha512-wYxSGajtmoP4WxfejAPIr4l0fVh+jeMXZb08wNc0tMg6xsfZXj3cECqIK0G7ZAqUq0PP8WlMDtaOGVBTAWztNw==}
-    dev: false
 
-  /web-worker@1.2.0:
-    resolution: {integrity: sha512-PgF341avzqyx60neE9DD+XS26MMNMoUQRz9NOZwW32nPQrF6p77f1htcnjBSEV8BGMKZ16choqUG4hyI0Hx7mA==}
-    dev: false
+  web-namespaces@2.0.1: {}
 
-  /webidl-conversions@3.0.1:
-    resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
-    dev: false
+  web-worker@1.2.0: {}
 
-  /webpack-bundle-analyzer@4.7.0:
-    resolution: {integrity: sha512-j9b8ynpJS4K+zfO5GGwsAcQX4ZHpWV+yRiHDiL+bE0XHJ8NiPYLTNVQdlFYWxtpg9lfAQNlwJg16J9AJtFSXRg==}
-    engines: {node: '>= 10.13.0'}
-    hasBin: true
+  webpack-bundle-analyzer@4.10.1:
     dependencies:
-      acorn: 8.8.1
+      '@discoveryjs/json-ext': 0.5.7
+      acorn: 8.11.3
       acorn-walk: 8.2.0
-      chalk: 4.1.2
       commander: 7.2.0
+      debounce: 1.2.1
+      escape-string-regexp: 4.0.0
       gzip-size: 6.0.0
-      lodash: 4.17.21
+      html-escaper: 2.0.2
+      is-plain-object: 5.0.0
       opener: 1.5.2
-      sirv: 1.0.19
+      picocolors: 1.1.0
+      sirv: 2.0.4
       ws: 7.5.9
     transitivePeerDependencies:
       - bufferutil
       - utf-8-validate
-    dev: false
 
-  /webpack-dev-middleware@5.3.3(webpack@5.75.0):
-    resolution: {integrity: sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==}
-    engines: {node: '>= 12.13.0'}
-    peerDependencies:
-      webpack: ^4.0.0 || ^5.0.0
+  webpack-dev-middleware@5.3.3(webpack@5.90.1):
     dependencies:
       colorette: 2.0.19
       memfs: 3.4.12
       mime-types: 2.1.35
       range-parser: 1.2.1
       schema-utils: 4.0.0
-      webpack: 5.75.0
-    dev: false
+      webpack: 5.90.1
 
-  /webpack-dev-server@4.11.1(webpack@5.75.0):
-    resolution: {integrity: sha512-lILVz9tAUy1zGFwieuaQtYiadImb5M3d+H+L1zDYalYoDl0cksAB1UNyuE5MMWJrG6zR1tXkCP2fitl7yoUJiw==}
-    engines: {node: '>= 12.13.0'}
-    hasBin: true
-    peerDependencies:
-      webpack: ^4.37.0 || ^5.0.0
-      webpack-cli: '*'
-    peerDependenciesMeta:
-      webpack-cli:
-        optional: true
+  webpack-dev-server@4.15.1(webpack@5.90.1):
     dependencies:
       '@types/bonjour': 3.5.10
       '@types/connect-history-api-fallback': 1.3.5
@@ -8719,7 +11730,7 @@ packages:
       '@types/serve-index': 1.9.1
       '@types/serve-static': 1.15.0
       '@types/sockjs': 0.3.33
-      '@types/ws': 8.5.4
+      '@types/ws': 8.5.10
       ansi-html-community: 0.0.8
       bonjour-service: 1.0.14
       chokidar: 3.5.3
@@ -8732,6 +11743,7 @@ packages:
       html-entities: 2.3.3
       http-proxy-middleware: 2.0.6(@types/express@4.17.15)
       ipaddr.js: 2.0.1
+      launch-editor: 2.6.1
       open: 8.4.0
       p-retry: 4.6.2
       rimraf: 3.0.2
@@ -8740,48 +11752,37 @@ packages:
       serve-index: 1.9.1
       sockjs: 0.3.24
       spdy: 4.0.2
-      webpack: 5.75.0
-      webpack-dev-middleware: 5.3.3(webpack@5.75.0)
-      ws: 8.11.0
+      webpack-dev-middleware: 5.3.3(webpack@5.90.1)
+      ws: 8.16.0
+    optionalDependencies:
+      webpack: 5.90.1
     transitivePeerDependencies:
       - bufferutil
       - debug
       - supports-color
       - utf-8-validate
-    dev: false
 
-  /webpack-merge@5.8.0:
-    resolution: {integrity: sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==}
-    engines: {node: '>=10.0.0'}
+  webpack-merge@5.10.0:
     dependencies:
       clone-deep: 4.0.1
+      flat: 5.0.2
       wildcard: 2.0.0
 
-  /webpack-sources@3.2.3:
-    resolution: {integrity: sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==}
-    engines: {node: '>=10.13.0'}
+  webpack-sources@3.2.3: {}
 
-  /webpack@5.75.0:
-    resolution: {integrity: sha512-piaIaoVJlqMsPtX/+3KTTO6jfvrSYgauFVdt8cr9LTHKmcq/AMd4mhzsiP7ZF/PGRNPGA8336jldh9l2Kt2ogQ==}
-    engines: {node: '>=10.13.0'}
-    hasBin: true
-    peerDependencies:
-      webpack-cli: '*'
-    peerDependenciesMeta:
-      webpack-cli:
-        optional: true
+  webpack@5.90.1:
     dependencies:
       '@types/eslint-scope': 3.7.4
-      '@types/estree': 0.0.51
-      '@webassemblyjs/ast': 1.11.1
-      '@webassemblyjs/wasm-edit': 1.11.1
-      '@webassemblyjs/wasm-parser': 1.11.1
-      acorn: 8.8.1
-      acorn-import-assertions: 1.8.0(acorn@8.8.1)
-      browserslist: 4.21.4
+      '@types/estree': 1.0.5
+      '@webassemblyjs/ast': 1.11.6
+      '@webassemblyjs/wasm-edit': 1.11.6
+      '@webassemblyjs/wasm-parser': 1.11.6
+      acorn: 8.11.3
+      acorn-import-assertions: 1.9.0(acorn@8.11.3)
+      browserslist: 4.22.3
       chrome-trace-event: 1.0.3
-      enhanced-resolve: 5.12.0
-      es-module-lexer: 0.9.3
+      enhanced-resolve: 5.15.0
+      es-module-lexer: 1.4.1
       eslint-scope: 5.1.1
       events: 3.3.0
       glob-to-regexp: 0.4.1
@@ -8790,9 +11791,9 @@ packages:
       loader-runner: 4.3.0
       mime-types: 2.1.35
       neo-async: 2.6.2
-      schema-utils: 3.1.1
+      schema-utils: 3.3.0
       tapable: 2.2.1
-      terser-webpack-plugin: 5.3.6(webpack@5.75.0)
+      terser-webpack-plugin: 5.3.10(webpack@5.90.1)
       watchpack: 2.4.0
       webpack-sources: 3.2.3
     transitivePeerDependencies:
@@ -8800,162 +11801,77 @@ packages:
       - esbuild
       - uglify-js
 
-  /webpackbar@5.0.2(webpack@5.75.0):
-    resolution: {integrity: sha512-BmFJo7veBDgQzfWXl/wwYXr/VFus0614qZ8i9znqcl9fnEdiVkdbi0TedLQ6xAK92HZHDJ0QmyQ0fmuZPAgCYQ==}
-    engines: {node: '>=12'}
-    peerDependencies:
-      webpack: 3 || 4 || 5
+  webpackbar@5.0.2(webpack@5.90.1):
     dependencies:
       chalk: 4.1.2
       consola: 2.15.3
       pretty-time: 1.1.0
       std-env: 3.3.1
-      webpack: 5.75.0
-    dev: false
+      webpack: 5.90.1
 
-  /websocket-driver@0.7.4:
-    resolution: {integrity: sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==}
-    engines: {node: '>=0.8.0'}
+  websocket-driver@0.7.4:
     dependencies:
       http-parser-js: 0.5.8
       safe-buffer: 5.2.1
       websocket-extensions: 0.1.4
-    dev: false
-
-  /websocket-extensions@0.1.4:
-    resolution: {integrity: sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==}
-    engines: {node: '>=0.8.0'}
-    dev: false
 
-  /whatwg-url@5.0.0:
-    resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
-    dependencies:
-      tr46: 0.0.3
-      webidl-conversions: 3.0.1
-    dev: false
+  websocket-extensions@0.1.4: {}
 
-  /which@1.3.1:
-    resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==}
-    hasBin: true
+  which@1.3.1:
     dependencies:
       isexe: 2.0.0
-    dev: false
 
-  /which@2.0.2:
-    resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
-    engines: {node: '>= 8'}
-    hasBin: true
+  which@2.0.2:
     dependencies:
       isexe: 2.0.0
-    dev: false
-
-  /widest-line@3.1.0:
-    resolution: {integrity: sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==}
-    engines: {node: '>=8'}
-    dependencies:
-      string-width: 4.2.3
-    dev: false
 
-  /widest-line@4.0.1:
-    resolution: {integrity: sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==}
-    engines: {node: '>=12'}
+  widest-line@4.0.1:
     dependencies:
       string-width: 5.1.2
-    dev: false
 
-  /wildcard@2.0.0:
-    resolution: {integrity: sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==}
+  wildcard@2.0.0: {}
 
-  /wrap-ansi@7.0.0:
-    resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
-    engines: {node: '>=10'}
+  wrap-ansi@7.0.0:
     dependencies:
       ansi-styles: 4.3.0
       string-width: 4.2.3
       strip-ansi: 6.0.1
-    dev: false
 
-  /wrap-ansi@8.0.1:
-    resolution: {integrity: sha512-QFF+ufAqhoYHvoHdajT/Po7KoXVBPXS2bgjIam5isfWJPfIOnQZ50JtUiVvCv/sjgacf3yRrt2ZKUZ/V4itN4g==}
-    engines: {node: '>=12'}
+  wrap-ansi@8.1.0:
     dependencies:
       ansi-styles: 6.2.1
       string-width: 5.1.2
-      strip-ansi: 7.0.1
-    dev: false
+      strip-ansi: 7.1.0
 
-  /wrappy@1.0.2:
-    resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
-    dev: false
+  wrappy@1.0.2: {}
 
-  /write-file-atomic@3.0.3:
-    resolution: {integrity: sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==}
+  write-file-atomic@3.0.3:
     dependencies:
       imurmurhash: 0.1.4
       is-typedarray: 1.0.0
       signal-exit: 3.0.7
       typedarray-to-buffer: 3.1.5
-    dev: false
 
-  /ws@7.5.9:
-    resolution: {integrity: sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==}
-    engines: {node: '>=8.3.0'}
-    peerDependencies:
-      bufferutil: ^4.0.1
-      utf-8-validate: ^5.0.2
-    peerDependenciesMeta:
-      bufferutil:
-        optional: true
-      utf-8-validate:
-        optional: true
-    dev: false
+  ws@7.5.9: {}
 
-  /ws@8.11.0:
-    resolution: {integrity: sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==}
-    engines: {node: '>=10.0.0'}
-    peerDependencies:
-      bufferutil: ^4.0.1
-      utf-8-validate: ^5.0.2
-    peerDependenciesMeta:
-      bufferutil:
-        optional: true
-      utf-8-validate:
-        optional: true
-    dev: false
+  ws@8.16.0: {}
 
-  /xdg-basedir@4.0.0:
-    resolution: {integrity: sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==}
-    engines: {node: '>=8'}
-    dev: false
+  xdg-basedir@5.1.0: {}
 
-  /xml-js@1.6.11:
-    resolution: {integrity: sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==}
-    hasBin: true
+  xml-js@1.6.11:
     dependencies:
       sax: 1.2.4
-    dev: false
 
-  /xtend@4.0.2:
-    resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
-    engines: {node: '>=0.4'}
+  yallist@3.1.1: {}
 
-  /yallist@3.1.1:
-    resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
-    dev: false
+  yallist@4.0.0: {}
 
-  /yallist@4.0.0:
-    resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
-    dev: false
+  yaml@1.10.2: {}
 
-  /yaml@1.10.2:
-    resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==}
-    engines: {node: '>= 6'}
+  yaml@2.5.1: {}
 
-  /yocto-queue@0.1.0:
-    resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
-    engines: {node: '>=10'}
-    dev: false
+  yocto-queue@0.1.0: {}
+
+  yocto-queue@1.0.0: {}
 
-  /zwitch@1.0.5:
-    resolution: {integrity: sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==}
-    dev: false
+  zwitch@2.0.4: {}
diff --git a/src/components/FrameworkIntegration.tsx b/src/components/FrameworkIntegration.tsx
index 32c8d21a..683725bc 100644
--- a/src/components/FrameworkIntegration.tsx
+++ b/src/components/FrameworkIntegration.tsx
@@ -63,6 +63,7 @@ export default function FrameworkIntegration(): JSX.Element {
                                 darkModeBrightness={0.85}
                             />
                             <Logo src="/img/logo/nestjs.png" darkModeBrightness={0.85} />
+                            <Logo src="/img/logo/redwood.png" darkSrc="/img/logo/redwood-dark.png" />
                         </div>
                     </div>
                     <div>
diff --git a/src/components/GiscusComponent.tsx b/src/components/GiscusComponent.tsx
new file mode 100644
index 00000000..84d44d77
--- /dev/null
+++ b/src/components/GiscusComponent.tsx
@@ -0,0 +1,48 @@
+import React from 'react';
+import Giscus from '@giscus/react';
+import { useColorMode } from '@docusaurus/theme-common';
+
+export default function GiscusComponent() {
+    const { colorMode } = useColorMode();
+
+    return (
+        <div style={{ paddingTop: 32 }}>
+            <h6>Comments</h6>
+            <span>
+                Feel free to ask questions, give feedback, or report issues.{' '}
+                <p
+                    className={'text-sm text-gray-500 italic'}
+                    style={{
+                        display: 'inline',
+                        fontStyle: 'italic',
+                    }}
+                >
+                    Don't Spam
+                </p>
+            </span>
+            <hr />
+            <div className={'text-sm text-gray-600'}>
+                You can edit/delete your comments by going directly to the discussion, clicking on the 'comments' link
+                below
+            </div>
+            <div style={{ padding: 2 }}>
+                <Giscus
+                    key={colorMode} // This is important to force re-mounting on theme change(bug in giscus)
+                    repo="zenstackhq/zenstack-docs"
+                    repoId="R_kgDOIvA9Bg"
+                    category="Comments"
+                    categoryId="DIC_kwDOIvA9Bs4Cego9" // id of "Comments"
+                    mapping="pathname" // Important! To map comments to URL
+                    term="Welcome zenstack site comments!"
+                    strict="0"
+                    reactionsEnabled="1"
+                    emitMetadata="0"
+                    inputPosition="top"
+                    theme={colorMode}
+                    lang="en"
+                    loading="lazy"
+                />
+            </div>
+        </div>
+    );
+}
diff --git a/src/components/Sponsorship.tsx b/src/components/Sponsorship.tsx
new file mode 100644
index 00000000..f7e1b4d5
--- /dev/null
+++ b/src/components/Sponsorship.tsx
@@ -0,0 +1,23 @@
+import React from 'react';
+export default function Sponsorship(): JSX.Element {
+    return (
+        <div className="flex justify-center w-full">
+            <div className="flex flex-col text-center xl:w-3/4">
+                <h2 className="text-2xl md:text-3xl lg:text-4xl pb-20">Our Generous Sponsors</h2>
+                <div className="flex flex-auto justify-center">
+                    <Sponsor src="/img/logo/marblism-logo.png" name="Marblism" website="https://www.marblism.com/?utm_source=zen" />
+                </div>
+            </div>
+        </div>
+    );
+}
+
+function Sponsor({ src, name, website }: { src: string; name: string; website: string }): JSX.Element {
+    const alt = src.split('/').pop()?.split('.')[0] ?? 'logo';
+    return (
+        <a href={website} target="_blank" className="no-underline">
+            <img src={src} className="h-40 object-contain" alt={alt} />
+            <h2>{name}</h2>
+        </a>
+    );
+}
diff --git a/src/components/UserLogos.tsx b/src/components/UserLogos.tsx
new file mode 100644
index 00000000..d9068967
--- /dev/null
+++ b/src/components/UserLogos.tsx
@@ -0,0 +1,79 @@
+import React from 'react';
+
+interface UserLogoProps {
+    src: string;
+    name: string;
+    website: string;
+    className?: string;
+    style?: React.CSSProperties;
+    imageStyle?: React.CSSProperties;
+    darkSrc?: string;
+}
+
+function UserLogo({ src, name, website, className, style, imageStyle, darkSrc }: UserLogoProps): JSX.Element {
+    return (
+        <div className="flex flex-col items-center gap-4 w-[calc(50%-1rem)] sm:w-[calc(25%-1.5rem)]  2xl:w-auto">
+            <img src={src} className="object-contain w-28 dark:hidden" alt={name} style={imageStyle} />
+            <img src={darkSrc ?? src} className="object-contain hidden dark:block w-28" alt={name} style={imageStyle} />
+            <a className={className} href={website} style={style}>
+                {name}
+            </a>
+        </div>
+    );
+}
+
+export default function UserLogs(): JSX.Element {
+    return (
+        <div className="flex justify-center w-full">
+            <div className="flex flex-col text-center xl:w-3/4">
+                <h2 className="text-2xl md:text-3xl lg:text-4xl pb-20">Used and Loved by</h2>
+                <div className="flex flex-wrap justify-evenly gap-8 items-end w-full font-bold">
+                    <UserLogo
+                        src="/img/logo/marblism-logo.png"
+                        name="Marblism"
+                        website="https://www.marblism.com/?utm_source=zen"
+                        className="text-gray-500 hover:text-gray-500"
+                    />
+                    <UserLogo
+                        src="/img/logo/coderabbit.png"
+                        name="CodeRabbit"
+                        website="https://coderabbit.ai/"
+                        style={{ color: 'rgb(236 86 41)' }}
+                    />
+                    <UserLogo
+                        src="/img/logo/veeva.svg"
+                        name="Veeva"
+                        website="https://quickvault.veeva.com/"
+                        style={{ color: 'rgb(236 159 69)' }}
+                    />
+                    <UserLogo
+                        src="/img/logo/mermaidicon.svg"
+                        name="Mermaid Chart"
+                        website="https://www.mermaidchart.com/"
+                        style={{ color: 'rgb(236 75 114)' }}
+                    />
+                    <UserLogo
+                        src="/img/logo/techlockdown.png"
+                        name="TECH LOCKDOWN"
+                        website="https://www.techlockdown.com/"
+                        className="text-gray-500 hover:text-gray-500"
+                    />
+                    <UserLogo
+                        src="/img/logo/carrot.jpeg"
+                        name="Carrot"
+                        website="https://carrot.tech/"
+                        style={{ color: 'rgb(231, 235, 93)' }}
+                    />
+                    <UserLogo
+                        src="/img/logo/brainbase.svg"
+                        darkSrc="/img/logo/brainbase_dark.svg"
+                        name="Brainbase"
+                        website="https://usebrainbase.com/"
+                        style={{ color: 'rgb(59, 59, 59)' }}
+                        imageStyle={{ padding: '0.6em' }}
+                    />
+                </div>
+            </div>
+        </div>
+    );
+}
diff --git a/src/components/VOCFlat.tsx b/src/components/VOCFlat.tsx
index d8aca682..c70f64a0 100644
--- a/src/components/VOCFlat.tsx
+++ b/src/components/VOCFlat.tsx
@@ -49,9 +49,9 @@ function Quote({
                                 />
                             </svg>
                         </div>
-                        <p className="text-lg md:text-xl italic px-4 pb-6 font-medium md:leading-relaxed text-gray-600 dark:text-gray-300">
+                        <div className="text-lg md:text-xl italic px-4 pb-6 font-medium md:leading-relaxed text-gray-600 dark:text-gray-300">
                             {children}
-                        </p>
+                        </div>
                     </div>
                 </div>
                 <figcaption className="flex items-center justify-center space-x-3">
@@ -97,7 +97,7 @@ export default function VOC(): JSX.Element {
                 <Quote
                     author="Søren Bramer Schmidt"
                     company="Prisma"
-                    avatar="https://pbs.twimg.com/profile_images/710410251401629696/UB1dgaWi_400x400.jpg"
+                    avatar="https://pbs.twimg.com/profile_images/1796990572965613569/aJIPcp4r_400x400.jpg"
                     link="https://twitter.com/sorenbs/status/1709924686635892917"
                 >
                     ZenStack is an excellent example in what we have in mind when we talk about Data DX 🎉
@@ -105,7 +105,7 @@ export default function VOC(): JSX.Element {
                 <Quote
                     author="Nikolas Burk"
                     company="Prisma"
-                    avatar="https://unavatar.io/twitter/nikolasburk"
+                    avatar="https://pbs.twimg.com/profile_images/1539606936966012929/vRvuh5cx_400x400.jpg"
                     link="https://twitter.com/nikolasburk/status/1625066262555504641"
                 >
                     <div className="text-left text-lg">
diff --git a/src/components/ValueProposition.tsx b/src/components/ValueProposition.tsx
index 77d84db0..c58a8aef 100644
--- a/src/components/ValueProposition.tsx
+++ b/src/components/ValueProposition.tsx
@@ -29,12 +29,12 @@ const FeatureList: FeatureItem[] = [
         ),
     },
     {
-        title: 'E2E Type Safety',
-        img: '/img/type-safety.png',
+        title: 'AI Friendly',
+        img: '/img/ai-friendly.png',
         description: (
             <>
-                No more duplicating type definitions and syncing changes. Use one single toolkit to generate types for
-                your entire stack and enjoy flawless auto-completion.
+                Schema-first reduces code complexity, helping AI understand better with fewer hallucinations. 
+                Schema serves as a single source of truth for AI integration.
             </>
         ),
     },
diff --git a/src/components/ZenStackInStack.tsx b/src/components/ZenStackInStack.tsx
index 281a8b93..9a5f1312 100644
--- a/src/components/ZenStackInStack.tsx
+++ b/src/components/ZenStackInStack.tsx
@@ -52,7 +52,7 @@ export default function ZenStackInStack(): JSX.Element {
                             src="/img/home/supercharged-orm-dark.png"
                             alt="supercharged orm"
                         />
-                        <LearnMore url="/docs/intro/zmodel" />
+                        <LearnMore url="/docs/the-complete-guide/part1/" />
                     </div>
                     <div>
                         <Badge>API</Badge>
@@ -77,7 +77,7 @@ export default function ZenStackInStack(): JSX.Element {
 import { prisma } from 'server/db';
 import { enhance } from '@zenstackhq/runtime';
 import { NextRequestHandler } from '@zenstackhq/server/next';
-import RestApiHandler from '@zenstackhq/server/api/rest';
+import { RestApiHandler } from '@zenstackhq/server/api';
 
 export default NextRequestHandler({
     getPrisma: (req, res) => enhance(prisma, { user: getSessionUser(req, res) }),
@@ -97,7 +97,7 @@ export default NextRequestHandler({
 import { prisma } from '~/server/db';
 import { enhance } from '@zenstackhq/runtime';
 import { createEventHandler } from '@zenstackhq/server/nuxt';
-import RestApiHandler from '@zenstackhq/server/api/rest';
+import { RestApiHandler } from '@zenstackhq/server/api';
 
 export default createEventHandler({
     getPrisma: (event) => enhance(prisma, { user: getSessionUser(event) }),
@@ -117,7 +117,7 @@ export default createEventHandler({
 import { prisma } from '$/lib/db';
 import { enhance } from '@zenstackhq/runtime';
 import { SvelteKitHandler } from '@zenstackhq/server/sveltekit';
-import RestApiHandler from '@zenstackhq/server/api/rpc';
+import { RestApiHandler } from '@zenstackhq/server/api/rpc';
 
 export const handle = SvelteKitHandler({
     prefix: '/api/model',
@@ -138,7 +138,7 @@ export const handle = SvelteKitHandler({
 import { prisma } from './db';
 import { enhance } from '@zenstackhq/runtime';
 import { ZenStackMiddleware } from '@zenstackhq/server/express';
-import RestApiHandler from '@zenstackhq/server/api/rpc';
+import { RestApiHandler } from '@zenstackhq/server/api';
 
 app.use(
     '/api/model',
@@ -161,7 +161,7 @@ app.use(
 import { prisma } from './db';
 import { enhance } from '@zenstackhq/runtime';
 import { ZenStackFastifyPlugin } from '@zenstackhq/server/fastify';
-import RestApiHandler from '@zenstackhq/server/api/rpc';
+import { RestApiHandler } from '@zenstackhq/server/api';
 
 server.register(ZenStackFastifyPlugin, {
     prefix: '/api/model',
@@ -174,7 +174,7 @@ server.register(ZenStackFastifyPlugin, {
                             </Tabs>
                         </div>
                         <img className="xl:max-w-3/4 block" src="/img/home/swagger-ui.png" alt="swagger ui" />
-                        <LearnMore url="/docs/intro/api" />
+                        <LearnMore url="/docs/the-complete-guide/part3/" />
                     </div>
                     <div>
                         <Badge>Full-stack</Badge>
@@ -186,7 +186,7 @@ server.register(ZenStackFastifyPlugin, {
                             automatically generated APIs, which are secured by the access policies.
                         </Content>
                         <img className="xl:max-w-3/4 block mb-4" src="/img/home/client-hooks.png" alt="client hooks" />
-                        <LearnMore url="/docs/intro/frontend" />
+                        <LearnMore url="/docs/the-complete-guide/part4/" />
                     </div>
                 </div>
             </div>
diff --git a/src/components/blog/post-paginator.tsx b/src/components/blog/post-paginator.tsx
new file mode 100644
index 00000000..31aca17c
--- /dev/null
+++ b/src/components/blog/post-paginator.tsx
@@ -0,0 +1,76 @@
+import React from 'react';
+import Link from '@docusaurus/Link';
+
+import clsx from 'clsx';
+
+export const PostPaginator = ({ posts, title }) => {
+    if (posts.length < 1) {
+        return null;
+    }
+
+    return (
+        <div
+            className={clsx(
+                'mr-auto w-full',
+                'py-10',
+                'blog-sm:py-12',
+                'blog-md:py-16',
+                'max-w-[894px]',
+                'blog-sm:max-w-screen-blog-sm',
+                'blog-lg:max-w-screen-content-2xl'
+            )}
+        >
+            <div className="blog-sm:px-6 w-full">
+                <h2 className="mb-4 p-0 text-2xl font-semibold">{title}</h2>
+                <div className="flex flex-col not-prose ">
+                    {posts.map((post) => (
+                        <Link
+                            to={post.permalink}
+                            rel="dofollow"
+                            key={post.permalink ?? post.id}
+                            style={{ color: 'var(--ifm-font-color-base)' }}
+                            className={clsx(
+                                'flex',
+                                'flex-col',
+                                'gap-2',
+                                'p-5',
+                                'mb-5',
+                                'rounded-lg',
+                                'border',
+                                'hover:bg-gray-100',
+                                'dark:hover:bg-gray-800',
+                                'not-prose',
+                                'no-underline',
+                                'border-solid',
+                                'border',
+                                'hover:no-underline',
+                                'group'
+                            )}
+                        >
+                            <div className={clsx('font-bold', 'group-hover:underline')}>{post.title}</div>
+
+                            <p
+                                className={clsx('font-sm')}
+                                style={{
+                                    display: '-webkit-box',
+                                    '-webkit-line-clamp': '3',
+                                    '-webkit-box-orient': 'vertical',
+                                    overflow: 'hidden',
+                                    'text-overflow': 'ellipsis',
+                                }}
+                            >
+                                {post.description}
+                            </p>
+                        </Link>
+                    ))}
+                    <p className="mt-8 mb-6 p-0 text-xl font-bold">
+                    🚀 Ready to build high-quality, scalable Prisma app with built-in AuthZ and instant CRUD APIs?
+                    </p>
+                    <Link className="mb-4 p-0 text-xl font-bold underline" to="/docs/welcome">
+                        Get started with ZenStack's ultimate guide to build faster and smarter
+                    </Link>
+                </div>
+            </div>
+        </div>
+    );
+};
diff --git a/src/css/custom.css b/src/css/custom.css
index b2bde74f..ae6d471d 100644
--- a/src/css/custom.css
+++ b/src/css/custom.css
@@ -46,3 +46,25 @@
         display: none;
     }
 }
+
+#github-button::before {
+    content: '';
+    position: absolute;
+    top: 0;
+    right: -100%;
+    bottom: 0;
+    width: 100%;
+    opacity: 0.1;
+    background: var(--ifm-navbar-background-color);
+    transition: right 0.5s;
+    pointer-events: none;
+}
+
+[data-theme='dark'] #github-button::before {
+    opacity: 0.3;
+}
+
+#github-button:hover::before {
+    right: 0;
+    pointer-events: mouse;
+}
diff --git a/src/lib/content.ts b/src/lib/content.ts
index aba69cbe..307e3d76 100644
--- a/src/lib/content.ts
+++ b/src/lib/content.ts
@@ -1,2 +1,2 @@
 export const description =
-    'A TypeScript toolkit that supercharges Prisma ORM with a powerful access control layer and unleashes its full power for full-stack development.';
+    'A TypeScript toolkit that enhances Prisma ORM with flexible Authorization and auto-generated, type-safe APIs/hooks, simplifying full-stack development.';
diff --git a/src/lib/prism-zmodel.js b/src/lib/prism-zmodel.js
index 63253e82..a5198df6 100644
--- a/src/lib/prism-zmodel.js
+++ b/src/lib/prism-zmodel.js
@@ -1,5 +1,5 @@
 Prism.languages.zmodel = Prism.languages.extend('clike', {
-    keyword: /\b(?:datasource|enum|generator|model|type|abstract|import|extends|attribute|view|plugin)\b/,
+    keyword: /\b(?:datasource|enum|generator|model|type|abstract|import|extends|attribute|view|plugin|proc)\b/,
     'type-class-name': /(\b()\s+)[\w.\\]+/,
 });
 
diff --git a/src/pages/index.tsx b/src/pages/index.tsx
index 3d02e381..79c7c0e8 100644
--- a/src/pages/index.tsx
+++ b/src/pages/index.tsx
@@ -4,31 +4,33 @@ import ValueProposition from '@site/src/components/ValueProposition';
 import Layout from '@theme/Layout';
 import clsx from 'clsx';
 import React from 'react';
-import { Typewriter } from 'react-simple-typewriter';
 import ZenStackInStack from '../components/ZenStackInStack';
 import { description } from '../lib/content';
 import styles from './index.module.css';
 import FrameworkIntegration from '../components/FrameworkIntegration';
 import VOC from '../components/VOCFlat';
+import Sponsorship from '../components/Sponsorship';
+import UserLogs from '../components/UserLogos';
 
 function Header() {
     return (
         <header className={clsx('hero hero--primary', styles.heroBanner)}>
             <div className="w-full flex justify-center">
-                <div className="w-full xl:container grid grid-cols-1 xl:grid-cols-2 gap-8 xl:gap-16 text-white dark:text-gray-800">
-                    <div className="w-full text-left flex flex-col items-start">
-                        <h1 className="hero__title font-bold text-3xl md:text-5xl xl:text-6xl mb-8 lg:mb-8 lg:leading-[70px] whitespace-nowrap">
-                            <span className="inline-block 2xl:hidden leading-relaxed">Database to </span>
-                            <span className="hidden 2xl:inline-block leading-relaxed">From Database to </span>{' '}
-                            <span>
-                                <Typewriter words={['API', 'Service', 'UI']} loop={true} />
+                <div className="w-full xl:container flex justify-center text-white dark:text-gray-800">
+                    <div className="w-full max-w-4xl text-center flex flex-col items-center px-4">
+                        <h1 className="hero__title font-bold text-2xl sm:text-3xl md:text-4xl lg:text-5xl xl:text-6xl 2xl:text-7xl mb-6 sm:mb-8 lg:mb-12 leading-tight sm:leading-tight md:leading-tight lg:leading-tight xl:leading-tight 2xl:leading-tight">
+                            <span className="inline-block text-white dark:text-gray-800">
+                                Schema-First Full‑Stack Toolkit
+                            </span>
+                            <br className="hidden sm:block" />
+                            <span className="sm:hidden"> </span>
+                            <span className="inline-block mt-2 sm:mt-0 text-gray-800 dark:text-white font-extrabold">
+                                with Clean & Scalable Authorization
                             </span>
-                            {','}
-                            <br /> In Minutes
                         </h1>
-                        <p className="hero__subtitle font-semi-bold text-base lg:text-2xl mx-auto mb-8 lg:mb-8 lg:leading-relaxed text-gray-100 dark:text-gray-700">
-                            A TypeScript toolkit that supercharges Prisma ORM with a powerful access control layer and
-                            unleashes its full power for full-stack development.
+                        <p className="hero__subtitle font-semi-bold text-sm sm:text-base md:text-lg lg:text-xl xl:text-2xl mb-6 sm:mb-8 lg:mb-12 leading-relaxed text-gray-100 dark:text-gray-700 max-w-none sm:max-w-2xl text-center">
+                            A TypeScript toolkit that enhances Prisma ORM with flexible Authorization and
+                            auto-generated, type-safe APIs/hooks, simplifying full-stack development
                         </p>
                         <div className={styles.buttons}>
                             <Link
@@ -39,28 +41,6 @@ function Header() {
                             </Link>
                         </div>
                     </div>
-                    <div className="w-full h-full mx-auto flex justify-end">
-                        <div className="block: xl:hidden w-full relative pt-[56%]">
-                            <iframe
-                                width="100%"
-                                height="100%"
-                                src="https://www.youtube.com/embed/dI8LeuFTr9c?autoplay=1mute=1"
-                                title="ZenStack Introduction"
-                                allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; autoplay"
-                                allowFullScreen
-                                className="absolute inset-0 rounded-xl shadow-xl"
-                            ></iframe>
-                        </div>
-                        <iframe
-                            width="100%"
-                            height="100%"
-                            src="https://www.youtube.com/embed/dI8LeuFTr9c?autoplay=1&mute=1"
-                            title="ZenStack Introduction"
-                            allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
-                            allowFullScreen
-                            className="hidden xl:block shadow-xl rounded-xl"
-                        ></iframe>
-                    </div>
                 </div>
             </div>
         </header>
@@ -70,7 +50,7 @@ function Header() {
 function Section({ children, className }: { children: React.ReactNode; className?: string }) {
     return (
         <section
-            className={`flex justify-center px-8 py-16 lg:px-16 lg:py-32 bg-white text-gray-800 dark:bg-gray-900 dark:text-gray-200  ${
+            className={`flex justify-center px-8 py-16 lg:px-16 lg:py-32  text-gray-800 dark:bg-gray-900 dark:text-gray-200  ${
                 className ?? ''
             }`}
         >
@@ -83,7 +63,7 @@ export default function Home(): JSX.Element {
     const { siteConfig } = useDocusaurusContext();
     return (
         <Layout
-            title={`${siteConfig.title} - Lighting-Fast Development for Database-Centric Apps`}
+            title={`${siteConfig.title}:Full-Stack Toolkit with Authorization on Prisma ORM`}
             description={description}
         >
             <Header />
@@ -92,19 +72,22 @@ export default function Home(): JSX.Element {
                     <ValueProposition />
                 </Section>
 
-                <Section className="bg-slate-50">
-                    <ZenStackInStack />
+                <Section className="bg-slate-50 dark:bg-slate-800">
+                    <UserLogs />
                 </Section>
 
                 <Section>
-                    <FrameworkIntegration />
+                    <ZenStackInStack />
                 </Section>
 
-                {/* <Section className="bg-slate-50">
-                    <UseCases />
-                </Section> */}
+                <Section className="bg-slate-50 dark:bg-slate-800">
+                    <FrameworkIntegration />
+                </Section>
 
-                <Section className="bg-slate-50">
+                <Section>
+                    <Sponsorship />
+                </Section>
+                <Section className="bg-slate-50 dark:bg-slate-800">
                     <VOC />
 
                     <div className="flex justify-center w-full mt-32">
diff --git a/src/plugins/blog-plugin.js b/src/plugins/blog-plugin.js
new file mode 100644
index 00000000..0d002964
--- /dev/null
+++ b/src/plugins/blog-plugin.js
@@ -0,0 +1,78 @@
+const blogPluginExports = require('@docusaurus/plugin-content-blog');
+const utils = require('@docusaurus/utils');
+const path = require('path');
+
+const defaultBlogPlugin = blogPluginExports.default;
+const MIN_RELATED_POSTS = 10;
+
+function getMultipleRandomElement(arr, num) {
+    const shuffled = [...arr].sort(() => 0.5 - Math.random());
+
+    return shuffled.slice(0, num);
+}
+
+function getRelatedPosts(allBlogPosts, metadata) {
+    const currentTags = new Set(metadata.frontMatter.tags?.filter((tag) => tag?.toLowerCase() != 'zenstack'));
+
+    let relatedPosts = allBlogPosts.filter(
+        (post) =>
+            post.metadata.frontMatter.tags.some((tag) => currentTags.has(tag)) && post.metadata.title !== metadata.title
+    );
+
+    if (relatedPosts.length < MIN_RELATED_POSTS) {
+        remainingCount = MIN_RELATED_POSTS - relatedPosts.length;
+        const remainingPosts = getMultipleRandomElement(
+            allBlogPosts.filter((post) => !relatedPosts.includes(post) && post.metadata.title !== metadata.title),
+            remainingCount
+        );
+        relatedPosts = relatedPosts.concat(remainingPosts);
+    }
+
+    const filteredPostInfos = relatedPosts.map((post) => {
+        return {
+            title: post.metadata.title,
+            description: post.metadata.description,
+            permalink: post.metadata.permalink,
+            formattedDate: post.metadata.formattedDate,
+            authors: post.metadata.authors,
+            readingTime: post.metadata.readingTime,
+            date: post.metadata.date,
+            relatedWeight: post.metadata.frontMatter.tags.filter((tag) => currentTags.has(tag)).length * 4 + 1,
+        };
+    });
+
+    return filteredPostInfos;
+}
+
+async function blogPluginExtended(...pluginArgs) {
+    const blogPluginInstance = await defaultBlogPlugin(...pluginArgs);
+
+    return {
+        // Add all properties of the default blog plugin so existing functionality is preserved
+        ...blogPluginInstance,
+        contentLoaded: async function (data) {
+            await blogPluginInstance.contentLoaded(data);
+            const { content: blogContents, actions } = data;
+            const { blogPosts: allBlogPosts } = blogContents;
+            const { createData } = actions;
+            // Create routes for blog entries.
+            await Promise.all(
+                allBlogPosts.map(async (blogPost) => {
+                    const { metadata } = blogPost;
+                    const relatedPosts = getRelatedPosts(allBlogPosts, metadata);
+                    await createData(
+                        // Note that this created data path must be in sync with
+                        // metadataPath provided to mdx-loader.
+                        `${utils.docuHash(metadata.source)}.json`,
+                        JSON.stringify({ ...metadata, relatedPosts }, null, 2)
+                    );
+                })
+            );
+        },
+    };
+}
+
+module.exports = {
+    ...blogPluginExports,
+    default: blogPluginExtended,
+};
diff --git a/src/theme/BlogPostPage/Metadata/index.d.ts b/src/theme/BlogPostPage/Metadata/index.d.ts
new file mode 100644
index 00000000..5865d883
--- /dev/null
+++ b/src/theme/BlogPostPage/Metadata/index.d.ts
@@ -0,0 +1,2 @@
+/// <reference types="react" />
+export default function BlogPostPageMetadata(): JSX.Element;
diff --git a/src/theme/BlogPostPage/Metadata/index.js b/src/theme/BlogPostPage/Metadata/index.js
new file mode 100644
index 00000000..35c6d607
--- /dev/null
+++ b/src/theme/BlogPostPage/Metadata/index.js
@@ -0,0 +1,35 @@
+import React from 'react';
+import {PageMetadata} from '@docusaurus/theme-common';
+import {useBlogPost} from '@docusaurus/theme-common/internal';
+export default function BlogPostPageMetadata() {
+  const {assets, metadata} = useBlogPost();
+  const {title, description, date, tags, authors, frontMatter} = metadata;
+  const {keywords} = frontMatter;
+  const image = assets.image ?? frontMatter.image;
+  return (
+    <PageMetadata
+      title={title}
+      description={description}
+      keywords={keywords}
+      image={image}>
+      <meta property="og:type" content="article" />
+      <meta property="article:published_time" content={date} />
+      {/* TODO double check those article meta array syntaxes, see https://ogp.me/#array */}
+      {authors.some((author) => author.url) && (
+        <meta
+          property="article:author"
+          content={authors
+            .map((author) => author.url)
+            .filter(Boolean)
+            .join(',')}
+        />
+      )}
+      {tags.length > 0 && (
+        <meta
+          property="article:tag"
+          content={tags.map((tag) => tag.label).join(',')}
+        />
+      )}
+    </PageMetadata>
+  );
+}
diff --git a/src/theme/BlogPostPage/icons.tsx b/src/theme/BlogPostPage/icons.tsx
new file mode 100644
index 00000000..3130ef29
--- /dev/null
+++ b/src/theme/BlogPostPage/icons.tsx
@@ -0,0 +1,29 @@
+import clsx from 'clsx';
+import React from 'react';
+
+export const Twitter = (props: React.SVGProps<SVGSVGElement>): JSX.Element => (
+    <svg
+        xmlns="http://www.w3.org/2000/svg"
+        width={36}
+        height={36}
+        fill="none"
+        {...props}
+        viewBox="0 0 24 24"
+        className={clsx('text-black dark:text-white', props.className)}
+    >
+        <title>Twitter</title>
+        <rect
+            width={24}
+            height={24}
+            fill="currentColor"
+            rx={12}
+            className={clsx('text-gray-200 dark:text-black', props.className)}
+        />
+        <path
+            fill="currentColor"
+            fillRule="evenodd"
+            d="m6 6 4.62 6.75L6.098 18H7.1l3.963-4.604L14.214 18H18l-4.827-7.052L17.433 6h-1l-3.703 4.3L9.786 6H6Zm1.196.632h2.258l7.35 10.736h-2.258L7.196 6.632Z"
+            clipRule="evenodd"
+        />
+    </svg>
+);
diff --git a/src/theme/BlogPostPage/index.d.ts b/src/theme/BlogPostPage/index.d.ts
new file mode 100644
index 00000000..5cdad0bc
--- /dev/null
+++ b/src/theme/BlogPostPage/index.d.ts
@@ -0,0 +1,4 @@
+/// <reference types="@docusaurus/plugin-content-blog" />
+/// <reference types="react" />
+import type { Props } from '@theme/BlogPostPage';
+export default function BlogPostPage(props: Props): JSX.Element;
diff --git a/src/theme/BlogPostPage/index.js b/src/theme/BlogPostPage/index.js
new file mode 100644
index 00000000..f0b976c7
--- /dev/null
+++ b/src/theme/BlogPostPage/index.js
@@ -0,0 +1,135 @@
+import React from 'react';
+import clsx from 'clsx';
+import { HtmlClassNameProvider, ThemeClassNames } from '@docusaurus/theme-common';
+import { BlogPostProvider, useBlogPost } from '@docusaurus/theme-common/internal';
+import BlogLayout from '@theme/BlogLayout';
+import BlogPostItem from '@theme/BlogPostItem';
+import BlogPostPaginator from '@theme/BlogPostPaginator';
+import BlogPostPageMetadata from '@theme/BlogPostPage/Metadata';
+import TOC from '@theme/TOC';
+import Unlisted from '@theme/Unlisted';
+import GiscusComponent from '@site/src/components/GiscusComponent';
+import { PostPaginator } from '@site/src/components/blog/post-paginator';
+import { LinkedinShareButton, RedditShareButton, TwitterShareButton, RedditIcon, LinkedinIcon } from 'react-share';
+import { Twitter } from './icons';
+import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
+
+function getMultipleRandomPosts(relatedPosts, number) {
+    // Create a copy of the original array to avoid modifying it
+    const weightedItems = [...relatedPosts];
+    const result = [];
+
+    weightedItems.forEach((item) => {
+        //square the weight to increase the chance of the post being selected
+        //add a new field to avoid modify the original object
+        item.sortWeight = weightedItems.length > 15 ? Math.pow(item.relatedWeight, 2) : item.relatedWeight;
+    });
+
+    // Calculate the total weight
+    let totalWeight = weightedItems.reduce((sum, item) => sum + item.sortWeight, 0);
+
+    while (weightedItems.length > 0) {
+        // Generate a random value between 0 and the total weight
+        const randomValue = Math.random() * totalWeight;
+        let weightSum = 0;
+        let selectedIndex = -1;
+
+        // Find the item that corresponds to the random value
+        for (let i = 0; i < weightedItems.length; i++) {
+            weightSum += weightedItems[i].sortWeight;
+            if (randomValue <= weightSum) {
+                selectedIndex = i;
+                break;
+            }
+        }
+
+        // If an item was selected, add it to the result and remove it from the original array
+        if (selectedIndex !== -1) {
+            const [selectedItem] = weightedItems.splice(selectedIndex, 1);
+            result.push(selectedItem);
+            totalWeight -= selectedItem.sortWeight;
+        }
+    }
+
+    return result.slice(0, number);
+}
+
+function BlogPostPageContent({ children }) {
+    const { metadata, toc } = useBlogPost();
+
+    const { relatedPosts, nextItem, prevItem, frontMatter, unlisted, permalink, title, description } = metadata;
+    const {
+        hide_table_of_contents: hideTableOfContents,
+        toc_min_heading_level: tocMinHeadingLevel,
+        toc_max_heading_level: tocMaxHeadingLevel,
+    } = frontMatter;
+
+    const randomThreeRelatedPosts = getMultipleRandomPosts(relatedPosts, 3);
+
+    console.log('relatedPosts', relatedPosts);
+    const {
+        siteConfig: { url },
+    } = useDocusaurusContext();
+
+    return (
+        <BlogLayout
+            toc={
+                !hideTableOfContents && toc.length > 0 ? (
+                    <TOC toc={toc} minHeadingLevel={tocMinHeadingLevel} maxHeadingLevel={tocMaxHeadingLevel} />
+                ) : undefined
+            }
+        >
+            {unlisted && <Unlisted />}
+
+            <BlogPostItem>{children}</BlogPostItem>
+            <div className="flex items-center gap-3 pt-10 not-prose">
+                <span className="text-base">Share on</span>
+                <TwitterShareButton
+                    windowWidth={750}
+                    windowHeight={800}
+                    url={url + permalink}
+                    className="flex"
+                    title={title}
+                >
+                    <Twitter width={36} height={36} />
+                </TwitterShareButton>
+                <RedditShareButton
+                    className="flex"
+                    windowWidth={750}
+                    windowHeight={600}
+                    url={url + permalink}
+                    title={title}
+                >
+                    <RedditIcon size={36} round />
+                </RedditShareButton>
+                <LinkedinShareButton
+                    url={url + permalink}
+                    title={title}
+                    source={url}
+                    summary={description}
+                    className="flex"
+                >
+                    <LinkedinIcon size={36} round />
+                </LinkedinShareButton>
+            </div>
+            <PostPaginator title="Related Articles" posts={randomThreeRelatedPosts}></PostPaginator>
+            <GiscusComponent />
+            {(nextItem || prevItem) && <BlogPostPaginator nextItem={nextItem} prevItem={prevItem} />}
+        </BlogLayout>
+    );
+}
+export default function BlogPostPage(props) {
+    const BlogPostContent = props.content;
+    return (
+        <BlogPostProvider content={props.content} isBlogPostPage>
+            <HtmlClassNameProvider
+                className={clsx(ThemeClassNames.wrapper.blogPages, ThemeClassNames.page.blogPostPage)}
+            >
+                <BlogPostPageMetadata />
+                <BlogPostPageContent sidebar={props.sidebar}>
+                    <BlogPostContent />
+                </BlogPostPageContent>
+            </HtmlClassNameProvider>
+        </BlogPostProvider>
+    );
+}
diff --git a/src/theme/DocItem/Layout/index.js b/src/theme/DocItem/Layout/index.js
new file mode 100644
index 00000000..c4a780f7
--- /dev/null
+++ b/src/theme/DocItem/Layout/index.js
@@ -0,0 +1,60 @@
+import React from "react";
+import clsx from "clsx";
+import { useWindowSize } from "@docusaurus/theme-common";
+import { useDoc } from "@docusaurus/theme-common/internal";
+import DocItemPaginator from "@theme/DocItem/Paginator";
+import DocVersionBanner from "@theme/DocVersionBanner";
+import DocVersionBadge from "@theme/DocVersionBadge";
+import DocItemFooter from "@theme/DocItem/Footer";
+import DocItemTOCMobile from "@theme/DocItem/TOC/Mobile";
+import DocItemTOCDesktop from "@theme/DocItem/TOC/Desktop";
+import DocItemContent from "@theme/DocItem/Content";
+import DocBreadcrumbs from "@theme/DocBreadcrumbs";
+import Unlisted from "@theme/Unlisted";
+import styles from "./styles.module.css";
+import GiscusComponent from "@site/src/components/GiscusComponent";
+/**
+ * Decide if the toc should be rendered, on mobile or desktop viewports
+ */
+function useDocTOC() {
+  const { frontMatter, toc } = useDoc();
+  const windowSize = useWindowSize();
+  const hidden = frontMatter.hide_table_of_contents;
+  const canRender = !hidden && toc.length > 0;
+  const mobile = canRender ? <DocItemTOCMobile /> : undefined;
+  const desktop =
+    canRender && (windowSize === "desktop" || windowSize === "ssr") ? (
+      <DocItemTOCDesktop />
+    ) : undefined;
+  return {
+    hidden,
+    mobile,
+    desktop,
+  };
+}
+export default function DocItemLayout({ children }) {
+  const docTOC = useDocTOC();
+  const {
+    metadata: { unlisted },
+  } = useDoc();
+  return (
+    <div className="row">
+      <div className={clsx("col", !docTOC.hidden && styles.docItemCol)}>
+        {unlisted && <Unlisted />}
+        <DocVersionBanner />
+        <div className={styles.docItemContainer}>
+          <article>
+            <DocBreadcrumbs />
+            <DocVersionBadge />
+            {docTOC.mobile}
+            <DocItemContent>{children}</DocItemContent>
+            <DocItemFooter />
+          </article>
+          <DocItemPaginator />
+        </div>
+        <GiscusComponent />
+      </div>
+      {docTOC.desktop && <div className="col col--3">{docTOC.desktop}</div>}
+    </div>
+  );
+}
diff --git a/src/theme/DocItem/Layout/styles.module.css b/src/theme/DocItem/Layout/styles.module.css
new file mode 100644
index 00000000..7b61eebc
--- /dev/null
+++ b/src/theme/DocItem/Layout/styles.module.css
@@ -0,0 +1,12 @@
+.docItemContainer header + *,
+.docItemContainer article > *:first-child {
+  margin-top: 0;
+}
+
+@media (min-width: 997px) {
+  .docItemCol {
+      --readableMaxWidth: calc(calc(1080px*0.25)*3);
+      max-width: var(--readableMaxWidth) !important;
+      margin: 0 auto;
+  }
+}
diff --git a/static/config/cli.json b/static/config/cli.json
new file mode 100644
index 00000000..55a0cf28
--- /dev/null
+++ b/static/config/cli.json
@@ -0,0 +1,10 @@
+{
+    "notifications": [
+        {
+            "id": 1,
+            "title": "💡 How can the AI copilot help you with the schema? ",
+            "url": "https://dub.sh/zenstack",
+            "active": false
+        }
+    ]
+}
diff --git a/static/img/ai-friendly.png b/static/img/ai-friendly.png
new file mode 100644
index 00000000..12d92f90
Binary files /dev/null and b/static/img/ai-friendly.png differ
diff --git a/static/img/jetbrains-screenshot.png b/static/img/jetbrains-screenshot.png
new file mode 100644
index 00000000..2b22530b
Binary files /dev/null and b/static/img/jetbrains-screenshot.png differ
diff --git a/static/img/logo/brainbase.svg b/static/img/logo/brainbase.svg
new file mode 100644
index 00000000..c07f820f
--- /dev/null
+++ b/static/img/logo/brainbase.svg
@@ -0,0 +1,9 @@
+<svg viewBox="0 0 800 800" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M400 412C400 405.373 405.373 400 412 400H788C794.627 400 800 405.373 800 412V740C800 773.137 773.137 800 740 800H412C405.373 800 400 794.627 400 788V412Z" fill="currentColor"/>
+<circle cx="400" cy="400" r="400" fill="currentColor"/>
+<path d="M0 60C0 26.8629 26.8629 0 60 0H397.614C398.932 0 400 1.06811 400 2.38569V2.38569C400 221.982 221.982 400 2.38569 400V400C1.06811 400 0 398.932 0 397.614V60Z" fill="currentColor"/>
+<path d="M400 412C400 405.373 405.373 400 412 400H738C744.627 400 750 405.373 750 412V725C750 738.807 738.807 750 725 750H412C405.373 750 400 744.627 400 738V412Z" fill="currentColor"/>
+<circle cx="400" cy="400" r="350" fill="currentColor"/>
+<path d="M50 75C50 61.1929 61.1929 50 75 50H388C394.627 50 400 55.3726 400 62V388C400 394.627 394.627 400 388 400H62C55.3726 400 50 394.627 50 388V75Z" fill="currentColor"/>
+<rect x="399.919" y="209" width="270" height="270" rx="12" transform="rotate(45 399.919 209)" fill="white"/>
+</svg>
diff --git a/static/img/logo/brainbase_dark.svg b/static/img/logo/brainbase_dark.svg
new file mode 100644
index 00000000..afe86452
--- /dev/null
+++ b/static/img/logo/brainbase_dark.svg
@@ -0,0 +1,9 @@
+<svg viewBox="0 0 800 800" fill="none" xmlns="http://www.w3.org/2000/svg">
+  <path d="M400 412C400 405.373 405.373 400 412 400H788C794.627 400 800 405.373 800 412V740C800 773.137 773.137 800 740 800H412C405.373 800 400 794.627 400 788V412Z" fill="white"/>
+  <circle cx="400" cy="400" r="400" fill="white"/>
+  <path d="M0 60C0 26.8629 26.8629 0 60 0H397.614C398.932 0 400 1.06811 400 2.38569V2.38569C400 221.982 221.982 400 2.38569 400V400C1.06811 400 0 398.932 0 397.614V60Z" fill="white"/>
+  <path d="M400 412C400 405.373 405.373 400 412 400H738C744.627 400 750 405.373 750 412V725C750 738.807 738.807 750 725 750H412C405.373 750 400 744.627 400 738V412Z" fill="white"/>
+  <circle cx="400" cy="400" r="350" fill="white"/>
+  <path d="M50 75C50 61.1929 61.1929 50 75 50H388C394.627 50 400 55.3726 400 62V388C400 394.627 394.627 400 388 400H62C55.3726 400 50 394.627 50 388V75Z" fill="white"/>
+  <rect x="399.919" y="209" width="270" height="270" rx="12" transform="rotate(45 399.919 209)" fill="black"/>
+</svg>
diff --git a/static/img/logo/carrot.jpeg b/static/img/logo/carrot.jpeg
new file mode 100644
index 00000000..6f9f8b02
Binary files /dev/null and b/static/img/logo/carrot.jpeg differ
diff --git a/static/img/logo/coderabbit.png b/static/img/logo/coderabbit.png
new file mode 100644
index 00000000..0c3afc15
Binary files /dev/null and b/static/img/logo/coderabbit.png differ
diff --git a/static/img/logo/marblism-logo.png b/static/img/logo/marblism-logo.png
new file mode 100644
index 00000000..53c63cbd
Binary files /dev/null and b/static/img/logo/marblism-logo.png differ
diff --git a/static/img/logo/mermaidicon.svg b/static/img/logo/mermaidicon.svg
new file mode 100644
index 00000000..0deae3d8
--- /dev/null
+++ b/static/img/logo/mermaidicon.svg
@@ -0,0 +1 @@
+<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 490.16 490.16"><defs><style>.cls-1{fill:#ff3670;}.cls-2{fill:#fff;}</style></defs><rect class="cls-1" width="490.16" height="490.16" rx="84.61"/><path class="cls-2" d="M407.48,111.18A165.2,165.2,0,0,0,245.08,220,165.2,165.2,0,0,0,82.68,111.18a165.5,165.5,0,0,0,72.06,143.64,88.81,88.81,0,0,1,38.53,73.45v50.86H296.9V328.27a88.8,88.8,0,0,1,38.52-73.45,165.41,165.41,0,0,0,72.06-143.64Z"/><path class="cls-2" d="M160.63,328.27a56.09,56.09,0,0,0-24.27-46.49,198.74,198.74,0,0,1-28.54-23.66A196.87,196.87,0,0,1,82.53,227V379.13h78.1Z"/><path class="cls-2" d="M329.53,328.27a56.09,56.09,0,0,1,24.27-46.49,198.74,198.74,0,0,0,28.54-23.66A196.87,196.87,0,0,0,407.63,227V379.13h-78.1Z"/></svg>
\ No newline at end of file
diff --git a/static/img/logo/redwood-dark.png b/static/img/logo/redwood-dark.png
new file mode 100644
index 00000000..a7a81271
Binary files /dev/null and b/static/img/logo/redwood-dark.png differ
diff --git a/static/img/logo/redwood.png b/static/img/logo/redwood.png
new file mode 100644
index 00000000..1f37519d
Binary files /dev/null and b/static/img/logo/redwood.png differ
diff --git a/static/img/logo/techlockdown.png b/static/img/logo/techlockdown.png
new file mode 100644
index 00000000..0da8c7b2
Binary files /dev/null and b/static/img/logo/techlockdown.png differ
diff --git a/static/img/logo/veeva.svg b/static/img/logo/veeva.svg
new file mode 100644
index 00000000..d369b871
--- /dev/null
+++ b/static/img/logo/veeva.svg
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 19.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="layer" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 652 652" style="enable-background:new 0 0 652 652;" xml:space="preserve">
+<style type="text/css">
+	.st0{fill:#F8991D;}
+	.st1{fill:#8B8C8E;}
+</style>
+<g>
+	<path class="st0" d="M508.2,270.3h-22.5l-38.2,76.2l-38.1-76.2h-22.1c23.9,47.8,60.5,121,60.5,121L508.2,270.3z"/>
+	<path class="st0" d="M333.6,288.3c18.7,0,34.4,12.9,38.7,30.3h-77.4C299.2,301.3,314.9,288.3,333.6,288.3 M333.6,368
+		c-18.7,0-34.4-12.9-38.7-30.2h97.7v-9.6c0-32.5-26.5-59-59-59c-32.5,0-59,26.5-59,59c0,32.5,26.5,59,59,59
+		c22.4,0,42.3-12.4,52.3-31.5h-23.4C355.1,363.4,344.8,368,333.6,368"/>
+	<path class="st0" d="M561.4,368.5c-22,0-39.9-17.9-39.9-39.8c0-22,17.9-39.8,39.9-39.8c21.8,0,39.5,17.5,39.8,39.2v1.3
+		C600.9,350.9,583.2,368.5,561.4,368.5 M620.5,328.6c0-0.2,0-0.4,0-0.7v-57.3h-19.2v14.5c-10.5-9.6-24.5-15.6-39.8-15.6
+		c-32.5,0-59,26.5-59,59c0,32.5,26.5,59,59,59c15.4,0,29.3-5.9,39.8-15.6v14.7h19.2v-57.5C620.4,329.1,620.5,328.8,620.5,328.6"/>
+	<path class="st0" d="M202.5,288.3c18.7,0,34.4,12.9,38.7,30.3h-77.4C168.2,301.3,183.9,288.3,202.5,288.3 M202.5,368
+		c-18.7,0-34.4-12.9-38.7-30.2h97.7v-9.6c0-32.5-26.5-59-59-59c-32.6,0-59,26.5-59,59c0,32.5,26.5,59,59,59
+		c22.4,0,42.2-12.4,52.2-31.5h-23.4C224.1,363.4,213.8,368,202.5,368"/>
+</g>
+<polygon class="st0" points="20.5,252.4 44.2,252.4 89.3,343.1 134.1,252.4 157.4,252.4 89.1,391.1 "/>
+<polygon class="st0" points="20.5,252.4 44.2,252.4 89.3,343.1 134.1,252.4 157.4,252.4 89.1,391.1 "/>
+<g>
+	<path class="st1" d="M58.1,252.6L89,314.2l30.8-61.6C119.8,252.6,58.1,252.5,58.1,252.6"/>
+</g>
+</svg>
diff --git a/static/img/zmodel-format-dark.png b/static/img/zmodel-format-dark.png
new file mode 100644
index 00000000..4c62e41e
Binary files /dev/null and b/static/img/zmodel-format-dark.png differ
diff --git a/static/img/zmodel-format-light.png b/static/img/zmodel-format-light.png
new file mode 100644
index 00000000..4493eed3
Binary files /dev/null and b/static/img/zmodel-format-light.png differ
diff --git a/vercel.json b/vercel.json
index 8ad2f157..441c9349 100644
--- a/vercel.json
+++ b/vercel.json
@@ -3,7 +3,29 @@
     "redirects": [
         { "source": "/docs", "destination": "/docs/welcome" },
         { "source": "/docs/category/get-started", "destination": "/docs/category/quick-start", "permanent": true },
-        { "source": "/docs/get-started/:path*", "destination": "/docs/category/quick-start", "permanent": true },
-        { "source": "/docs/category/guides", "destination": "/docs/category/recipes", "permanent": true }
+        { "source": "/docs/get-started/:path*", "destination": "/docs/quick-start/:path*", "permanent": true },
+        { "source": "/docs/category/guides", "destination": "/docs/category/recipes", "permanent": true },
+        { "source": "/docs/intro", "destination": "/docs", "permanent": true },
+        {
+            "source": "/docs/intro/prisma-crash-course",
+            "destination": "/docs/the-complete-guide/part1/prisma",
+            "permanent": true
+        },
+        {
+            "source": "/docs/intro/zmodel",
+            "destination": "/docs/the-complete-guide/part1/zmodel",
+            "permanent": true
+        },
+        {
+            "source": "/docs/intro/api",
+            "destination": "/docs/the-complete-guide/part3/",
+            "permanent": true
+        },
+        {
+            "source": "/docs/intro/frontend",
+            "destination": "/docs/the-complete-guide/part4/",
+            "permanent": true
+        },
+        { "source": "/docs/guides/authentication/:path*", "destination": "/docs/quick-start/authentication/:path*", "permanent": true }
     ]
 }
diff --git a/versioned_docs/version-1.x/_components/_todo-requirements.md b/versioned_docs/version-1.x/_components/_todo-requirements.md
new file mode 100644
index 00000000..3a9ad023
--- /dev/null
+++ b/versioned_docs/version-1.x/_components/_todo-requirements.md
@@ -0,0 +1,5 @@
+- Email/password based signin/signup.
+- `Space` for multi-tenancy management. Users can join multiple spaces, and a space can have many users.
+- Users can be of "USER" or "ADMIN" role in a space. Space admins can manage space members.
+- Users can manage Todo lists and items in spaces they belong to.
+- Public Todo lists are accessible to all space members. Private lists are only accessible to the owner and space admins.
diff --git a/versioned_docs/version-1.x/_components/_zenstack-init-tips.md b/versioned_docs/version-1.x/_components/_zenstack-init-tips.md
new file mode 100644
index 00000000..63905c2b
--- /dev/null
+++ b/versioned_docs/version-1.x/_components/_zenstack-init-tips.md
@@ -0,0 +1,7 @@
+import ZenStackInit from './_zenstack-init.md'
+
+:::tip
+
+<ZenStackInit />
+
+:::
diff --git a/versioned_docs/version-1.x/_components/_zenstack-init.md b/versioned_docs/version-1.x/_components/_zenstack-init.md
new file mode 100644
index 00000000..a70e19ae
--- /dev/null
+++ b/versioned_docs/version-1.x/_components/_zenstack-init.md
@@ -0,0 +1,10 @@
+The "init" command does the following things for you:
+
+1. Install Prisma if it's not already installed.
+2. Install the `zenstack` CLI package as a dev dependency.
+3. Install the `@zenstackhq/runtime` package - used for enhancing `PrismaClient` at the runtime.
+4. Copy the `prisma/schema.prisma` file to `schema.zmodel` if it exists; otherwise, create a new template `schema.zmodel` file.
+
+You can always manually complete the steps above if you have a special project setup that the "init" command doesn't work with.
+
+After the initialization, please remember that you should edit the `schema.zmodel` moving forward. The `prisma/schema.prisma` file will be automatically regenerated when you run `zenstack generate`.
diff --git a/versioned_docs/version-1.x/assets/todo-app-erd.svg b/versioned_docs/version-1.x/assets/todo-app-erd.svg
new file mode 100644
index 00000000..c18421b1
--- /dev/null
+++ b/versioned_docs/version-1.x/assets/todo-app-erd.svg
@@ -0,0 +1 @@
+<svg aria-roledescription="er" role="graphics-document document" viewBox="0 0 503.96209716796875 465" style="max-width: 100%;" xmlns="http://www.w3.org/2000/svg" width="100%" id="graph-div" height="100%" xmlns:xlink="http://www.w3.org/1999/xlink"><style>@import url("https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.0/css/all.min.css");'</style><style>#graph-div{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;fill:#333;}#graph-div .error-icon{fill:hsl(60, 90%, 100%);}#graph-div .error-text{fill:rgb(0, 0, 0);stroke:rgb(0, 0, 0);}#graph-div .edge-thickness-normal{stroke-width:2px;}#graph-div .edge-thickness-thick{stroke-width:3.5px;}#graph-div .edge-pattern-solid{stroke-dasharray:0;}#graph-div .edge-pattern-dashed{stroke-dasharray:3;}#graph-div .edge-pattern-dotted{stroke-dasharray:2;}#graph-div .marker{fill:#0b0b0b;stroke:#0b0b0b;}#graph-div .marker.cross{stroke:#0b0b0b;}#graph-div svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;}#graph-div .entityBox{fill:#ECECFE;stroke:#BCBCFB;}#graph-div .attributeBoxOdd{fill:#ffffff;stroke:#BCBCFB;}#graph-div .attributeBoxEven{fill:#f2f2f2;stroke:#BCBCFB;}#graph-div .relationshipLabelBox{fill:hsl(60, 90%, 100%);opacity:0.7;background-color:hsl(60, 90%, 100%);}#graph-div .relationshipLabelBox rect{opacity:0.5;}#graph-div .relationshipLine{stroke:#0b0b0b;}#graph-div .entityTitleText{text-anchor:middle;font-size:18px;fill:#333;}#graph-div #MD_PARENT_START{fill:#f5f5f5!important;stroke:#0b0b0b!important;stroke-width:1;}#graph-div #MD_PARENT_END{fill:#f5f5f5!important;stroke:#0b0b0b!important;stroke-width:1;}#graph-div :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}</style><g></g><defs><marker orient="auto" markerHeight="240" markerWidth="190" refY="7" refX="0" id="MD_PARENT_START"><path d="M 18,7 L9,13 L1,7 L9,1 Z"></path></marker></defs><defs><marker orient="auto" markerHeight="28" markerWidth="20" refY="7" refX="19" id="MD_PARENT_END"><path d="M 18,7 L9,13 L1,7 L9,1 Z"></path></marker></defs><defs><marker orient="auto" markerHeight="18" markerWidth="18" refY="9" refX="0" id="ONLY_ONE_START"><path d="M9,0 L9,18 M15,0 L15,18" fill="none" stroke="gray"></path></marker></defs><defs><marker orient="auto" markerHeight="18" markerWidth="18" refY="9" refX="18" id="ONLY_ONE_END"><path d="M3,0 L3,18 M9,0 L9,18" fill="none" stroke="gray"></path></marker></defs><defs><marker orient="auto" markerHeight="18" markerWidth="30" refY="9" refX="0" id="ZERO_OR_ONE_START"><circle r="6" cy="9" cx="21" fill="white" stroke="gray"></circle><path d="M9,0 L9,18" fill="none" stroke="gray"></path></marker></defs><defs><marker orient="auto" markerHeight="18" markerWidth="30" refY="9" refX="30" id="ZERO_OR_ONE_END"><circle r="6" cy="9" cx="9" fill="white" stroke="gray"></circle><path d="M21,0 L21,18" fill="none" stroke="gray"></path></marker></defs><defs><marker orient="auto" markerHeight="36" markerWidth="45" refY="18" refX="18" id="ONE_OR_MORE_START"><path d="M0,18 Q 18,0 36,18 Q 18,36 0,18 M42,9 L42,27" fill="none" stroke="gray"></path></marker></defs><defs><marker orient="auto" markerHeight="36" markerWidth="45" refY="18" refX="27" id="ONE_OR_MORE_END"><path d="M3,9 L3,27 M9,18 Q27,0 45,18 Q27,36 9,18" fill="none" stroke="gray"></path></marker></defs><defs><marker orient="auto" markerHeight="36" markerWidth="57" refY="18" refX="18" id="ZERO_OR_MORE_START"><circle r="6" cy="18" cx="48" fill="white" stroke="gray"></circle><path d="M0,18 Q18,0 36,18 Q18,36 0,18" fill="none" stroke="gray"></path></marker></defs><defs><marker orient="auto" markerHeight="36" markerWidth="57" refY="18" refX="39" id="ZERO_OR_MORE_END"><circle r="6" cy="18" cx="9" fill="white" stroke="gray"></circle><path d="M21,18 Q39,0 57,18 Q39,36 21,18" fill="none" stroke="gray"></path></marker></defs><path style="stroke: gray; fill: none;" marker-start="url(#ONLY_ONE_START)" marker-end="url(#ZERO_OR_MORE_END)" d="M73.57142857142857,95L68.80952380952381,103.33333333333333C64.04761904761905,111.66666666666667,54.52380952380952,128.33333333333334,52.14285714285714,145C49.76190476190476,161.66666666666666,54.52380952380952,178.33333333333334,56.9047619047619,186.66666666666666L59.285714285714285,195" class="er relationshipLine"></path><path style="stroke: gray; fill: none;" marker-start="url(#ONLY_ONE_START)" marker-end="url(#ZERO_OR_MORE_END)" d="M295,79.375L270,90.3125C245,101.25,195,123.125,162.85714285714286,142.39583333333334C130.7142857142857,161.66666666666666,116.42857142857143,178.33333333333334,109.28571428571428,186.66666666666666L102.14285714285714,195" class="er relationshipLine"></path><path style="stroke: gray; fill: none;" marker-start="url(#ONLY_ONE_START)" marker-end="url(#ZERO_OR_MORE_END)" d="M145,86.66666666666667L161.66666666666666,96.3888888888889C178.33333333333334,106.11111111111113,211.66666666666666,125.55555555555556,235.47619047619048,143.61111111111111C259.2857142857143,161.66666666666666,273.57142857142856,178.33333333333334,280.7142857142857,186.66666666666666L287.85714285714283,195" class="er relationshipLine"></path><path style="stroke: gray; fill: none;" marker-start="url(#ONLY_ONE_START)" marker-end="url(#ZERO_OR_MORE_END)" d="M345,95L345,103.33333333333333C345,111.66666666666667,345,128.33333333333334,342.61904761904765,145C340.23809523809524,161.66666666666666,335.4761904761905,178.33333333333334,333.09523809523813,186.66666666666666L330.7142857142857,195" class="er relationshipLine"></path><path style="stroke: gray; fill: none;" marker-start="url(#ONLY_ONE_START)" marker-end="url(#ZERO_OR_MORE_END)" d="M320,270L320,278.3333333333333C320,286.6666666666667,320,303.3333333333333,329.5238095238095,320C339.04761904761904,336.6666666666667,358.0952380952381,353.3333333333333,367.61904761904765,361.6666666666667L377.14285714285717,370" class="er relationshipLine"></path><path style="stroke: gray; fill: none;" marker-start="url(#ONLY_ONE_START)" marker-end="url(#ZERO_OR_MORE_END)" d="M395,92.5L407.5,101.25C420,110,445,127.5,457.5,150.83333333333334C470,174.16666666666666,470,203.33333333333334,470,232.5C470,261.6666666666667,470,290.8333333333333,465.23809523809524,313.75C460.47619047619054,336.6666666666667,450.95238095238096,353.3333333333333,446.1904761904762,361.6666666666667L441.42857142857144,370" class="er relationshipLine"></path><g transform="translate(45,20 )" id="entity-Space-bb25dc3b-107f-5e01-b2bf-0e2d912f0777"><rect height="75" width="100" y="0" x="0" class="er entityBox"></rect><text style="dominant-baseline: middle; text-anchor: middle; font-size: 12px;" transform="translate(50,37.5)" y="0" x="0" id="text-entity-Space-bb25dc3b-107f-5e01-b2bf-0e2d912f0777" class="er entityLabel">Space</text></g><g transform="translate(20,195 )" id="entity-SpaceUser-395512d5-88f1-5ee6-a202-e2e41536dd14"><rect height="75" width="100" y="0" x="0" class="er entityBox"></rect><text style="dominant-baseline: middle; text-anchor: middle; font-size: 12px;" transform="translate(50,37.5)" y="0" x="0" id="text-entity-SpaceUser-395512d5-88f1-5ee6-a202-e2e41536dd14" class="er entityLabel">SpaceUser</text></g><g transform="translate(295,20 )" id="entity-User-818c511a-4a32-5484-ba70-875d765a9175"><rect height="75" width="100" y="0" x="0" class="er entityBox"></rect><text style="dominant-baseline: middle; text-anchor: middle; font-size: 12px;" transform="translate(50,37.5)" y="0" x="0" id="text-entity-User-818c511a-4a32-5484-ba70-875d765a9175" class="er entityLabel">User</text></g><g transform="translate(270,195 )" id="entity-List-70d39bf1-f498-5cf9-b274-59cf820c68a1"><rect height="75" width="100" y="0" x="0" class="er entityBox"></rect><text style="dominant-baseline: middle; text-anchor: middle; font-size: 12px;" transform="translate(50,37.5)" y="0" x="0" id="text-entity-List-70d39bf1-f498-5cf9-b274-59cf820c68a1" class="er entityLabel">List</text></g><g transform="translate(370,370 )" id="entity-Todo-0df3d4bf-3f4b-5a61-afb2-a536a206feb4"><rect height="75" width="100" y="0" x="0" class="er entityBox"></rect><text style="dominant-baseline: middle; text-anchor: middle; font-size: 12px;" transform="translate(50,37.5)" y="0" x="0" id="text-entity-Todo-0df3d4bf-3f4b-5a61-afb2-a536a206feb4" class="er entityLabel">Todo</text></g><rect height="13.5" width="19.3515625" y="136.36656188964844" x="42.766056060791016" class="er relationshipLabelBox"></rect><text style="text-anchor: middle; dominant-baseline: middle; font-size: 12px;" y="143.11656188964844" x="52.441837310791016" id="rel1056" class="er relationshipLabel">has</text><rect height="13.500007629394531" width="58.4296875" y="119.88307571411133" x="162.4468536376953" class="er relationshipLabelBox"></rect><text style="text-anchor: middle; dominant-baseline: middle; font-size: 12px;" y="126.6330795288086" x="191.6616973876953" id="rel1057" class="er relationshipLabel">member-of</text><rect height="13.5" width="19.3515625" y="127.09541320800781" x="212.249267578125" class="er relationshipLabelBox"></rect><text style="text-anchor: middle; dominant-baseline: middle; font-size: 12px;" y="133.8454132080078" x="221.925048828125" id="rel1058" class="er relationshipLabel">has</text><rect height="13.5" width="28.015625" y="138.91192626953125" x="328.51519775390625" class="er relationshipLabelBox"></rect><text style="text-anchor: middle; dominant-baseline: middle; font-size: 12px;" y="145.66192626953125" x="342.52301025390625" id="rel1059" class="er relationshipLabel">owns</text><rect height="13.5" width="19.3515625" y="320.6854553222656" x="324.6860046386719" class="er relationshipLabelBox"></rect><text style="text-anchor: middle; dominant-baseline: middle; font-size: 12px;" y="327.4354553222656" x="334.3617858886719" id="rel1060" class="er relationshipLabel">has</text><rect height="13.5" width="28.015625" y="212.3065185546875" x="455.94647216796875" class="er relationshipLabelBox"></rect><text style="text-anchor: middle; dominant-baseline: middle; font-size: 12px;" y="219.0565185546875" x="469.95428466796875" id="rel1061" class="er relationshipLabel">owns</text></svg>
\ No newline at end of file
diff --git a/versioned_docs/version-1.x/assets/todo-app-openapi.png b/versioned_docs/version-1.x/assets/todo-app-openapi.png
new file mode 100644
index 00000000..f42805f7
Binary files /dev/null and b/versioned_docs/version-1.x/assets/todo-app-openapi.png differ
diff --git a/versioned_docs/version-1.x/assets/todo-app-splash.png b/versioned_docs/version-1.x/assets/todo-app-splash.png
new file mode 100644
index 00000000..ba74ce84
Binary files /dev/null and b/versioned_docs/version-1.x/assets/todo-app-splash.png differ
diff --git a/versioned_docs/version-1.x/faq.md b/versioned_docs/version-1.x/faq.md
new file mode 100644
index 00000000..23f03248
--- /dev/null
+++ b/versioned_docs/version-1.x/faq.md
@@ -0,0 +1,74 @@
+---
+description: ZenStack FAQ.
+
+slug: /faq
+sidebar_label: FAQ
+sidebar_position: 8
+---
+
+# 🙋🏻 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. Collocating access control rules with the data model.
+
+### Does using ZenStack introduce more connections to my databases?
+
+No. It's common to create enhanced PrismaClient wrappers per request (for binding to different user identity). Under the hood all such wrappers share the same PrismaClient instance and thus the same database connection.
+
+### 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](./reference/server-adapters/next) and [Fastify](./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](./reference/zmodel-language#custom-attributes-and-functions)
+-   [Plugins](./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.
+
+### Does ZenStack require a specific Prisma version?
+
+No. ZenStack references Prisma as a peer dependency and should work with Prisma 4.8.0 and above.
+
+### Does ZenStack work with Prisma migration?
+
+Yes. When you run `zenstack generate` it generates a standard Prisma schema file that can be used with Prisma migration.
+
+### Are there any other IDE integrations besides VSCode?
+
+Yes, ZenStack has integrations with VSCode and JetBrains IDEs. Please see [here](./guides/ide) for ore details.
+
+### Why am I getting "Critical dependency: the request of a dependency is an expression" error with Next.js 13 and above?
+
+Next.js 13's new bundler has compatibility issues with `@zenstackhq/runtime` package. We've submitted a [PR](https://github.com/vercel/next.js/pull/54829) to Next.js but it hasn't been accepted yet. For the time being, please add the following settings to your `next.config.js`:
+
+```js
+{
+  ...
+  experimental: {
+    serverComponentsExternalPackages: [
+      "@zenstackhq/runtime"
+    ],
+  }
+}
+```
+
+### Does ZenStack work with Prisma Client Extensions?
+
+The short answer is it works in most cases. Please refer to [this guide](./guides/client-extensions) for more details.
diff --git a/versioned_docs/version-1.x/guides/_category_.yml b/versioned_docs/version-1.x/guides/_category_.yml
new file mode 100644
index 00000000..acb7bc3f
--- /dev/null
+++ b/versioned_docs/version-1.x/guides/_category_.yml
@@ -0,0 +1,7 @@
+position: 5
+label: Recipes
+collapsible: true
+collapsed: true
+link:
+    type: generated-index
+    title: Recipes
diff --git a/docs/guides/authentication/_category_.yml b/versioned_docs/version-1.x/guides/authentication/_category_.yml
similarity index 100%
rename from docs/guides/authentication/_category_.yml
rename to versioned_docs/version-1.x/guides/authentication/_category_.yml
diff --git a/docs/guides/authentication/auth0.md b/versioned_docs/version-1.x/guides/authentication/auth0.md
similarity index 100%
rename from docs/guides/authentication/auth0.md
rename to versioned_docs/version-1.x/guides/authentication/auth0.md
diff --git a/docs/guides/authentication/clerk.md b/versioned_docs/version-1.x/guides/authentication/clerk.md
similarity index 96%
rename from docs/guides/authentication/clerk.md
rename to versioned_docs/version-1.x/guides/authentication/clerk.md
index a676ea53..2d657130 100644
--- a/docs/guides/authentication/clerk.md
+++ b/versioned_docs/version-1.x/guides/authentication/clerk.md
@@ -46,7 +46,7 @@ If you choose to [synchronize user data to your database](https://clerk.com/docs
 
 ### Create an enhanced Prisma client
 
-You can create an enhanced Prisma client that automatically validates access policies, field validation rules, etc., during CRUD operations. For more details, please refer to [ZModel Language](/docs/reference/zmodel-language) reference.
+You can create an enhanced Prisma client that automatically validates access policies, field validation rules, etc., during CRUD operations. For more details, please refer to [ZModel Language](../../reference/zmodel-language) reference.
 
 To create such a client with a standard setup, call the `enhance` API with a regular Prisma client and the current user (fetched from Clerk). Here's an example:
 
@@ -66,4 +66,4 @@ async function getPrisma(req: NextApiRequest) {
 
 ---
 
-You can find a working sample project [here](https://github.com/zenstackhq/docs-tutorial-clerk).
+You can find a working sample project [here](https://github.com/zenstackhq/docs-tutorial-clerk/tree/v1).
diff --git a/docs/guides/authentication/lucia.md b/versioned_docs/version-1.x/guides/authentication/lucia.md
similarity index 100%
rename from docs/guides/authentication/lucia.md
rename to versioned_docs/version-1.x/guides/authentication/lucia.md
diff --git a/docs/guides/authentication/next-auth.md b/versioned_docs/version-1.x/guides/authentication/next-auth.md
similarity index 97%
rename from docs/guides/authentication/next-auth.md
rename to versioned_docs/version-1.x/guides/authentication/next-auth.md
index 53442986..73cb4b8b 100644
--- a/docs/guides/authentication/next-auth.md
+++ b/versioned_docs/version-1.x/guides/authentication/next-auth.md
@@ -8,7 +8,7 @@ sidebar_label: Auth.js (NextAuth)
 
 [NextAuth](https://next-auth.js.org/) is a comprehensive framework for implementing authentication in Next.js projects. It offers a pluggable mechanism for configuring how user data is persisted.
 
-To get access policies to work, ZenStack needs to be connected to the authentication system to get the user's identity. This guide introduces tasks required for integrating ZenStack with NextAuth. You can find a complete example [here](https://github.com/zenstackhq/sample-todo-nextjs ':target=blank').
+To get access policies to work, ZenStack needs to be connected to the authentication system to get the user's identity. This guide introduces tasks required for integrating ZenStack with NextAuth. You can find a complete example [here](https://github.com/zenstackhq/sample-todo-nextjs/tree/v1 ':target=blank').
 
 ### Data model requirement
 
@@ -106,7 +106,7 @@ function authorize(prisma: PrismaClient) {
 
 ### Create an enhanced Prisma client
 
-You can create an enhanced Prisma client which automatically validates access policies, field validation rules etc., during CRUD operations. For more details, please refer to [ZModel Language](/docs/reference/zmodel-language) reference.
+You can create an enhanced Prisma client which automatically validates access policies, field validation rules etc., during CRUD operations. For more details, please refer to [ZModel Language](../../reference/zmodel-language) reference.
 
 To create such a client with a standard setup, call the `enhance` API with a regular Prisma client and the current user (fetched with NextAuth API). Here's an example:
 
diff --git a/docs/guides/authentication/supabase.md b/versioned_docs/version-1.x/guides/authentication/supabase.md
similarity index 96%
rename from docs/guides/authentication/supabase.md
rename to versioned_docs/version-1.x/guides/authentication/supabase.md
index a3e4816c..73d5cc77 100644
--- a/docs/guides/authentication/supabase.md
+++ b/versioned_docs/version-1.x/guides/authentication/supabase.md
@@ -92,3 +92,5 @@ async function getPrisma() {
 ```
 
 You can then use this enhanced Prisma client for CRUD operations that you desire to be governed by the access policies you defined in your data models.
+
+Next, make sure to read [this guide](../supabase-security) to ensure your database is securely protected from HTTP requests to the supabase API.
\ No newline at end of file
diff --git a/versioned_docs/version-1.x/guides/client-extensions.md b/versioned_docs/version-1.x/guides/client-extensions.md
new file mode 100644
index 00000000..e77b5eec
--- /dev/null
+++ b/versioned_docs/version-1.x/guides/client-extensions.md
@@ -0,0 +1,138 @@
+---
+description: Using ZenStack with Prisma Client Extensions.
+sidebar_position: 11
+---
+
+# Using With Prisma Client Extensions
+
+[Prisma Client Extension](https://www.prisma.io/docs/orm/prisma-client/client-extensions) is a mechanism for extending the interface and functionality of Prisma Client. This guide introduces two ways of using ZenStack with client extensions and their implications.
+
+We'll use the following ZModel as a reference throughout this guide:
+
+```zmodel
+model Post {
+  id Int @id @default(autoincrement())
+  title String
+  content String?
+  published Boolean @default(false)
+
+  @@allow('read', published)
+}
+```
+
+## Enhancing a Prisma Client with extensions installed
+
+This should be the most common way of using ZenStack with client extensions. You install client extensions to the global `PrismaClient` instance when the application starts and then call `enhance` with the extended client on a per-request basis. It will generally work as you would intuitively expect.
+
+Let's look at a few examples.
+
+### 1. Model-level methods
+
+```ts
+const extendedPrisma = prisma.$extends({
+  model: {
+    post: {
+      async getFeeds() {
+          const context = Prisma.getExtensionContext(this);
+          return context.findMany();
+      },
+    },
+  },
+});
+
+const db = enhance(extendedPrisma);
+const feeds = await db.post.getFeeds();
+```
+
+The `getFeeds()` call will return only published posts. The `context` variable fetched with `Prisma.getExtensionContext(this)` is enhanced by ZenStack.
+
+### 2. Query-level overrides
+
+```ts
+const extendedPrisma = prisma.$extends({
+  query: {
+    post: {
+      findMany({ query, args }) {
+        console.log('Query args:', inspect(args, { depth: null }));
+        return query(args);
+      },
+    },
+  },
+});
+
+const enhanced = enhance(extendedPrisma);
+const posts = await enhanced.post.findMany();
+```
+
+The `findMany` call returns only published posts. The printed log will demonstrate that the `args` is injected by ZenStack to include access policy filters.
+
+You can also override mutation methods and it generally works. However, it must be noted that ZenStack enforces mutation policies before or after the mutation is executed (in the case of "after", a transaction is used to roll back if the policy fails), depending on the access policies and the mutation input. This means that if you alter the mutation args in a client extension to make it conform to the access policy, it may not work because ZenStack intercepts the mutation call first, and may decide to reject it based on the original args.
+
+### 3. Computed fields
+
+```ts
+const extendedPrisma = prisma.$extends({
+  result: {
+    post: {
+      myTitle: {
+        needs: { title: true },
+        compute(post) {
+          return 'MyTitle: ' + post.title;
+        },
+      },
+    },
+  },
+});
+
+const enhanced = enhance(extendedPrisma);
+const post = await enhanced.post.findFirst();
+```
+
+Computed fields will work as expected. ZenStack enhancement works transparently with it.
+
+:::danger
+
+Currently there's a limitation that computed fields are not governed by field-level access policies. This means that if you have a computed field that depends on a field that the current user cannot read, the computed field will still be calculated and returned.
+
+:::
+
+## Installing extensions to an enhanced Prisma Client
+
+Such a setup DOES NOT WORK as you would expect in most cases, so it should generally be avoided. For example,
+
+```ts
+const enhanced = enhance(prisma);
+const db = enhanced.$extends({
+  model: {
+    post: {
+      async getFeeds() {
+          const context = Prisma.getExtensionContext(this);
+          return context.findMany();
+      },
+    },
+  },
+});
+
+const feeds = await db.post.getFeeds();
+```
+
+The `getFeeds()` call will return all posts (both published and unpublished ones). This is due to how Prisma internally implements the client extensions. Although you're calling `$extends` on an enhanced client, inside the extension, the context (returned by `Prisma.getExtensionContext(this)`) bypasses ZenStack and directly calls into the original Prisma client.
+
+You can refactor the code to make it work by explicitly referencing the enhanced client in the extension:
+
+```ts
+const enhanced = enhance(prisma);
+const db = enhanced.$extends({
+  model: {
+    post: {
+      async getFeeds() {
+          return enhanced.post.findMany();
+      },
+    },
+  },
+});
+
+const feeds = await db.post.getFeeds();
+```
+
+It may or may not be practical, depending on the structure of your code base.
diff --git a/versioned_docs/version-1.x/guides/debugging.md b/versioned_docs/version-1.x/guides/debugging.md
new file mode 100644
index 00000000..4f543c9b
--- /dev/null
+++ b/versioned_docs/version-1.x/guides/debugging.md
@@ -0,0 +1,66 @@
+---
+sidebar_position: 9
+---
+# Debugging and Troubleshooting
+
+ZenStack's access policies are powerful, but when the rules get complex, it can be challenging to reason why an operation is being allowed or denied. This guide lists some techniques for debugging and troubleshooting.
+
+### Enable Debug Logging
+
+ZenStack enforces access policies by injecting into Prisma queries, so a straightforward way to see what's going on is to enable logging of such queries. You can achieve it with two steps:
+
+1. When calling `enhance()` to create an enhanced PrismaClient, pass in a `logPrismaQueries` option set to `true`:
+
+    ```ts
+    const db = enhance(prisma, { user }, { logPrismaQuery: true })
+    ```
+
+2. Enable "info" level logging on PrismaClient
+
+    The queries will be logged into PrismaClient's logger with the "info" level, so you need to ensure that level is turned on when creating the PrismaClient instance.
+
+    ```ts
+    const prisma = new PrismaClient({ log: ['info'] });
+    ```
+
+When these are set up, you should be able to see all Prisma queries ZenStack makes in the console like the following:
+
+```js
+prisma:info [policy] `findMany` list:
+{
+  where: {
+    AND: [
+      { NOT: { OR: [] } },
+      {
+        OR: [
+          { owner: { is: { id: 1 } } },
+          {
+            AND: [
+              {
+                space: {
+                  members: {
+                    some: { user: { is: { id: 1 } } }
+                  }
+                }
+              },
+              { NOT: { private: true } }
+            ]
+          }
+        ]
+      }
+    ]
+  }
+}
+```
+
+### Try Things Out In REPL
+
+The `zenstack` CLI provides a REPL environment for you to execute queries with the regular or enhanced PrismaClient interactively. You can quickly switch between different user contexts and see how the access policies affect the result.
+
+<iframe width="800" height="450" src="https://www.youtube.com/embed/yJr8zZVj-JA?si=kACpOQH57XINBCR7" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
+
+Please find more details about the REPL in the [CLI reference](../reference/cli#repl).
+
+---
+
+In the future, we may devise more advanced debugging tools that help you break down complex rules and pinpoint the one that causes an operation to be allowed or denied. Please stay tuned!
diff --git a/versioned_docs/version-1.x/guides/deploy.md b/versioned_docs/version-1.x/guides/deploy.md
new file mode 100644
index 00000000..d3373b4a
--- /dev/null
+++ b/versioned_docs/version-1.x/guides/deploy.md
@@ -0,0 +1,36 @@
+---
+sidebar_position: 10
+---
+# Deploying to Production
+
+After the hard work of building your application, nothing is more exciting than deploying it to production. This guide explains how ZenStack participates in the deployment process.
+
+### Running ZenStack CLI During Deployment
+
+If you use a framework that requires a build step, for example, "next build" for Next.js, you can run `zenstack generate` as the very first step in the build process. It ensures the `schema.prisma` file is up-to-date, and the supporting modules are generated into the `node_modules/.zenstack` directory.
+
+Here's what the build script of our sample Todo app looks like for deploying to Vercel:
+
+```json title='package.json'
+{
+  "scripts": {
+    "vercel-build": "zenstack generate && next build && prisma migrate deploy"
+  }
+}
+```
+
+### Not Using ZenStack CLI During Deployment
+
+There might be cases where you don't want to run the `zenstack` CLI during the deployment process. For example, you may want to avoid installing dev dependencies during the build to keep a clean "node_modules" folder. In such cases, you can run `zenstack generate` with a `--output` option to generate the supporting modules into your source tree and then commit them to your repository.
+
+```bash
+npx zenstack generate --output ./zenstack
+```
+
+At the runtime, when calling `enhance()`, you need to pass in an extra `loadPath` option to point to the directory where the supporting modules are located:
+
+```ts
+const db = enhance(prisma, { user }, { loadPath: './zenstack' })
+```
+
+In this way, all information ZenStack needs at runtime is already in the source tree, and you don't need to run `zenstack generate` during deployment.
diff --git a/versioned_docs/version-1.x/guides/dev-workflow.mdx b/versioned_docs/version-1.x/guides/dev-workflow.mdx
new file mode 100644
index 00000000..a5ad0285
--- /dev/null
+++ b/versioned_docs/version-1.x/guides/dev-workflow.mdx
@@ -0,0 +1,26 @@
+---
+description: Development Workflow
+sidebar_position: 1
+---
+
+# Development Workflow
+
+The development workflow using ZenStack is very close to Prisma. The main difference is,
+instead of modifying `schema.prisma` file, you edit `schema.zmodel` to make changes to
+data models and policies and then use `zenstack generate` command to generate Prisma
+schema. After that, everything is just standard Prisma workflow, including generating
+and deploying migrations.
+
+The following diagram illustrates a typical workflow for prototyping data model changes
+during feature development:
+
+import useBaseUrl from '@docusaurus/useBaseUrl';
+import ThemedImage from '@theme/ThemedImage';
+
+<ThemedImage
+    alt="Feature Prototyping Workflow"
+    sources={{
+        light: useBaseUrl('/img/dev-workflow-light.png'),
+        dark: useBaseUrl('/img/dev-workflow-dark.png'),
+    }}
+/>
diff --git a/versioned_docs/version-1.x/guides/existing-prisma.md b/versioned_docs/version-1.x/guides/existing-prisma.md
new file mode 100644
index 00000000..e201f760
--- /dev/null
+++ b/versioned_docs/version-1.x/guides/existing-prisma.md
@@ -0,0 +1,66 @@
+---
+description: Steps for migrating from existing Prisma projects.
+sidebar_position: 4
+---
+
+# Migrating Existing Prisma Projects
+
+ZenStack CLI provides an `init` command for easily converting an existing Prisma project.
+
+```bash
+npx zenstack@1 init
+```
+
+The command does the following things:
+
+1. It copies over `prisma/schema.prisma` to `/schema.zmodel`.
+
+    If your Prisma schema is in a non-standard location, you can pass it in with the `--prisma` option.
+
+    ```bash
+    npx zenstack@1 init --prisma prisma/my.schema
+    ```
+
+1. It installs NPM packages.
+
+    `zenstack` package as a dev dependency and `@zenstackhq/runtime` as a regular dependency. The CLI tries to guess the package manager you use, but you can also explicitly specify one with the `--package-manager` option (supported values are `npm | pnpm | yarn`).
+
+    ```bash
+    npx zenstack@1 init --package-manager pnpm
+    ```
+
+If the `init` command doesn't suit your needs, manually doing these steps is just fine. See [ZenStack CLI](../reference/cli) for more details about using the CLI.
+
+## Prisma generators' triple slash hack
+
+One major limitation of Prisma schema is the lack of support for custom attributes and functions. Generators can't directly attach specific metadata to models. The community has been using the triple-slash comment hack as a workaround.
+
+Here's an example from [TypeGraphQL Prisma](https://prisma.typegraphql.com/). The comment on the "password" field marks it to be omitted from both the GraphQL input and output types. The generator parses its text and acts accordingly.
+
+```zmodel title='schema.prisma'
+model User {
+  id Int @default(autoincrement()) @id
+  email String  @unique
+  /// @TypeGraphQL.omit(output: true, input: true)
+  password String
+  posts Post[]
+}
+```
+
+To make this scenario continue working, ZenStack preserves all triple-slash comments when generating the Prisma schema.
+
+The hack works but is error-prone because comments have no protection from the compiler. Fortunately, with ZModel, you can implement it in a much nicer way, thanks to its custom attributes support:
+
+```zmodel title='schema.zmodel'
+
+attribute @TypeGraphQL.omit(output: Boolean?, input: Boolean?)
+
+model User {
+  id Int @default(autoincrement()) @id
+  email String  @unique
+  password String @TypeGraphQL.omit(output: true, input: true)
+  posts Post[]
+}
+```
+
+Now, if you make a typo or pass in wrongly-typed expressions to the attribute, the compiler catches it for you. When ZenStack generates the Prisma schema, it translates all custom attributes back to triple-slash comments, so the original Prisma generators can continue working as before.
diff --git a/versioned_docs/version-1.x/guides/ide.md b/versioned_docs/version-1.x/guides/ide.md
new file mode 100644
index 00000000..4d12002a
--- /dev/null
+++ b/versioned_docs/version-1.x/guides/ide.md
@@ -0,0 +1,47 @@
+---
+description: IDE extensions
+sidebar_position: 5
+---
+
+# Installing IDE Extensions
+
+## VSCode
+
+ZenStack VSCode extension provides syntax highlighting and error checking to improve the efficiency of your modeling work. It offers functionalities similar to the Prisma VSCode extension, plus support for added features like access policies.
+
+### Features
+
+- Syntax highlighting
+- Error highlighting
+- Go to definition
+- Auto-completion
+- Hover documentation
+- Formatting
+
+### Installation
+
+You can install it by searching "ZenStack Language Tools" inside VSCode or directly from [here](https://marketplace.visualstudio.com/items?itemName=zenstack.zenstack).
+
+[![VSCode Screenshot](/img/vscode-screenshot.png)](https://marketplace.visualstudio.com/items?itemName=zenstack.zenstack)
+
+## JetBrains IDEs
+
+ZenStack provides a plugin for JetBrains IDEs. The extension is based on JetBrain platform's new [Language Server Protocol (LSP) support](https://blog.jetbrains.com/platform/2023/07/lsp-for-plugin-developers/). The extension only offers preliminary features today, but please expect improvements in the future as the LSP support matures.
+
+:::info
+JetBrain's LSP support is not available to the community edition of the IDEs, so is the ZenStack plugin.
+:::
+
+### Features
+
+- Syntax highlighting
+- Error highlighting
+- Go to definition
+- Auto-completion (partial)
+- Formatting
+
+### Installation
+
+You can install it by searching "ZenStack Language Tools" inside JetBrain IDE's plugins marketplace or directly from [here](https://plugins.jetbrains.com/plugin/23397-zenstack-language-tools).
+
+[![JetBrains Screenshot](/img/jetbrains-screenshot.png)](https://plugins.jetbrains.com/plugin/17210-zenstack-language-tools)
diff --git a/versioned_docs/version-1.x/guides/multiple-schema.md b/versioned_docs/version-1.x/guides/multiple-schema.md
new file mode 100644
index 00000000..60a4a5bb
--- /dev/null
+++ b/versioned_docs/version-1.x/guides/multiple-schema.md
@@ -0,0 +1,116 @@
+---
+description: ZenStack offers increased flexibility in managing your schema file through multiple schema files and model inheritance.
+sidebar_position: 6
+---
+# Multiple Schema Files and Model Inheritance
+
+As your business expands, your schema file grows with it. ZenStack offers increased flexibility in managing your schema file through multiple schema files and model inheritance. An example project is provided below for your reference:
+
+[https://github.com/zenstackhq/sample-multiple-schema](https://github.com/zenstackhq/sample-multiple-schema) 
+
+## Model Inheritance
+
+You can use abstract model to include common fields that would be shared by multiple models.  By extending abstract model, you can keep your schema DRY(Don’t repeat yourself).  The abstract model can contain both fields and attributes like a normal model, but it is erased after generation and does not appear in the generated Prisma schema file.
+
+You can have your model defined as:
+
+```tsx
+abstract model Base {
+    id Int @id @default(autoincrement())
+    createdAt DateTime @default(now())
+    updatedAt DateTime @updatedAt()
+
+    //Logged-in users can view
+    @@allow('read', auth() != null)
+
+}
+
+model Post extends Base
+{
+    title String
+    content String?
+    viewCount Int @default(0)
+}
+
+model ToDo extends Base
+{
+    title String
+    completed Boolean @default(false)
+}
+```
+
+The generated `schema.prisma` file would be:
+
+```zmodel
+/// @@allow('all', auth() != null && published)
+model User {
+    name String
+    id Int @id() @default(autoincrement())
+    createdAt DateTime @default(now())
+    updatedAt DateTime @updatedAt()
+    published Boolean @default(true)
+}
+
+/// @@allow('all', auth() != null && published)
+model Post {
+    title String
+    content String?
+    viewCount Int @default(0)
+    comment Comment[]
+    id Int @id() @default(autoincrement())
+    createdAt DateTime @default(now())
+    updatedAt DateTime @updatedAt()
+    published Boolean @default(true)
+}
+
+/// @@allow('all', auth() != null && published)
+model Comment {
+    content String
+    post Post @relation(fields: [postId], references: [id])
+    postId Int
+    id Int @id() @default(autoincrement())
+    createdAt DateTime @default(now())
+    updatedAt DateTime @updatedAt()
+    published Boolean @default(true)
+}
+```
+
+## Multiple Schema Files
+
+You can split your schema file into multiple files and use `import` statements to include the file you needed.  When running `zenstack generate`, it recursively traverses all the imported models and combine them into a single model.  For instance, you can split the above schema file into two files below:
+
+- base.zmodel
+    
+    ```tsx
+    abstract model Base {
+        id Int @id @default(autoincrement())
+        createdAt DateTime @default(now())
+        updatedAt DateTime @updatedAt()
+        published Boolean @default(true)
+    
+        // author has full access
+        @@allow('all', auth() != null && published)
+    }
+    ```
+    
+- schema.zmodel
+    
+    ```tsx
+    import "base"
+    model User extends Base {
+        name String
+    }
+    
+    model Post extends Base {
+        title String
+        content String?
+        viewCount Int @default(0)
+        comment Comment[]
+    }
+    
+    model Comment extends Base {
+        content String
+        post Post @relation(fields: [postId], references: [id])
+        postId Int
+    }
+    ```
diff --git a/versioned_docs/version-1.x/guides/redwood.md b/versioned_docs/version-1.x/guides/redwood.md
new file mode 100644
index 00000000..0635ab04
--- /dev/null
+++ b/versioned_docs/version-1.x/guides/redwood.md
@@ -0,0 +1,189 @@
+---
+description: Guide for using ZenStack with RedwoodJS.
+sidebar_position: 8
+---
+
+# Using With RedwoodJS
+
+[RedwoodJS](https://redwoodjs.com/) is an opinionated full-stack framework that combines a set of tools and libraries and helps you build GraphQL-based applications quickly without struggling with tooling.
+
+ZenStack provides a plugin package to help you easily integrate with RedwoodJS projects. After setting it up, you can define access policies in ZModel and use the enhanced `PrismaClient` to enforce the policies in your services automatically.
+
+## Details
+
+### Setting up
+
+You can prepare your RedwoodJS project for ZenStack by running the following command at the root of your project:
+
+```bash
+yarn rw setup package @zenstackhq/redwood
+```
+
+The setup command will:
+
+1. Update "redwood.toml" to allow the ZenStack CLI plugin
+   
+    ```yaml
+    [[experimental.cli.plugins]]
+    package = "@zenstackhq/redwood"
+    enabled = true
+    ```
+
+2. Install ZenStack dependencies to the "api" package
+   
+    - `zenstack`: the main CLI
+    - `@zenstackhq/runtime`: the runtime library for creating enhanced `PrismaClient`
+    - `@zenstackhq/redwood`: custom CLI and runtime for RedwoodJS
+
+3. Prepare ZModel schema
+
+    Your Prisma schema file "api/db/schema.prisma" will be copied to "api/db/schema.zmodel". Moving forward, you should edit "schema.zmodel" to update the database schema and access policies. The "schema.prisma" file will be regenerated when you run `yarn rw @zenstackhq generate`.
+
+4. Register the location of "schema.zmodel" and "schema.prisma" in "api/package.json"
+
+    ```json title="api/package.json"
+    {
+      ...
+      "zenstack": {
+        "schema": "db/schema.zmodel",
+        "prisma": "db/schema.prisma"
+      }
+    }
+    ```
+   
+5. Install a GraphQLYoga plugin to the GraphQL handler
+   
+    ```ts title="api/src/functions/graphql.[ts|js]"
+    import { useZenStack } from '@zenstackhq/redwood'
+    import { db } from 'src/lib/db'
+    import { createGraphQLHandler } from '@redwoodjs/graphql-server'
+    ...
+
+    export const handler = createGraphQLHandler({
+      ...
+      extraPlugins: [useZenStack(db)],
+    })
+    ```
+
+    The `useZenStack` plugin creates an enhanced `PrismaClient` for the current requesting user and stores it as `db` field in the global GraphQL context. You can use it in your service code via `context.db`. For example, the service for listing blog posts can be implemented as follows: only posts readable to the current user will be returned.
+
+    ```ts title="api/src/services/posts/posts.js"
+    export const posts = (...args) => {
+      return context.db.post.findMany()
+    }
+    ```
+
+    The plugin, by default, uses `context.currentUser` to get the current requesting user. You can customize it by passing in a function as the second argument to calculate a custom user object based on `context.currentUser`. E.g.:
+
+    ```ts
+    useZenStack(db, async (currentUser) => {
+      const typedUser = currentUser as { id: string };
+      const dbUser = await db.user.findUnique({ 
+        where: { id: typedUser.id },
+        // select more fields
+        select: { id: true, role: true }
+      });
+      return dbUser;
+    });
+    ```
+
+    See [here](../the-complete-guide/part1/access-policy/current-user) for more details about accessing the current user.
+   
+6. Eject service templates
+
+    The setup runs `yarn rw setup generator service` to eject template files used by `yarn rw g service` command. It also modifies the templates to use `context.db` instead of `db` to access the database with automatic access policy enforcement.
+
+### Modeling data and access policies
+
+ZenStack's ZModel language is a superset of Prisma schema language. You should use it to define both the data schema and access policies. [The Complete Guide](../the-complete-guide/part1/) of ZenStack is the best way to learn how to author ZModel schemas. Here's a quick example to show how you can define access policies for the blog post sample used throughout the official RedwoodJS tutorial:
+
+```zmodel title="api/db/schema.zmodel"
+model Post {
+  id        Int      @id @default(autoincrement())
+  title     String
+  body      String
+  comments  Comment[]
+  user      User     @relation(fields: [userId], references: [id])
+  userId    Int
+  createdAt DateTime @default(now())
+  published Boolean @default(true)
+
+  // 🔐 Admin user can do everything to his own posts
+  @@allow('all', auth().roles == 'admin' && auth() == user)
+
+  // 🔐 Posts are visible to everyone if published
+  @@allow('read', published)
+}
+```
+
+You should run the following command after updating "schema.zmodel":
+
+```bash
+yarn rw @zenstackhq generate
+```
+
+The command does the following things:
+
+1. Regenerate "schema.prisma"
+2. Run `prisma generate` to regenerate `PrismaClient`
+3. Generate supporting JS modules for enforcing access policies at runtime
+
+
+<!-- You can also use the
+
+```bash
+yarn rw @zenstackhq sample
+```
+
+command to browse a list of sample schemas and create from them. -->
+
+### Development workflow
+
+The workflow of using ZenStack is very similar to using Prisma in RedwoodJS projects. The two main differences are:
+
+1. Code generation
+
+    You should run `yarn rw @zenstackhq generate` instead of `yarn rw prisma generate`. The ZenStack's "generate" command internally regenerates the Prisma schema from the ZModel schema, runs `prisma generate` automatically, and outputs supporting modules for access policy enforcement.
+
+2. Database access in services
+
+    In your service code, you should use `context.db` instead of `db` for accessing the database. The setup procedure prepared a customized service code template. When you run `yarn rw g service`, the generated code will already use `context.db`.
+
+Other Prisma-related workflows like generation migration or pushing schema to the database stay unchanged.
+
+### Deployment
+
+You should run the "generate" command in your deployment script before `yarn rw deploy`. For example, to deploy to Vercel, the command can be:
+
+```bash
+yarn rw @zenstackhq generate && yarn rw deploy vercel
+```
+
+:::info Notes about Netlify
+
+ZenStack generates supporting JS modules into the `node_modules/.zenstack` folder, and the folder needs to be accessible at the runtime. Netlify detects the dependencies it needs to bundle by inspecting an application's root package.json file. This will result in the `.zenstack` folder being ignored. 
+
+To fix this problem, add the following section to your `netlify.toml` file:
+
+```toml
+[functions]
+  included_files = ["node_modules/.zenstack/*"]
+```
+
+:::
+
+### Using the `@zenstackhq` CLI plugin
+
+The `@zenstackhq/redwood` package registers a set of custom commands to the RedwoodJS CLI under the `@zenstackhq` namespace. You can run it with:
+
+```bash
+yarn rw @zenstackhq <cmd> [options] 
+```
+
+The plugin is a simple wrapper of the standard `zenstack` CLI, similar to how RedwoodJS wraps the standard `prisma` CLI. It's equivalent to running `npx zenstack ...` inside the "api" directory.
+
+See the [CLI references](../reference/cli) for the complete list of commands.
+
+## Sample application
+
+You can find a completed multi-tenant Todo application built with RedwoodJS and ZenStack at: [https://github.com/zenstackhq/sample-todo-redwood](https://github.com/zenstackhq/sample-todo-redwood).
diff --git a/versioned_docs/version-1.x/guides/supabase-security.md b/versioned_docs/version-1.x/guides/supabase-security.md
new file mode 100644
index 00000000..d95a7b8a
--- /dev/null
+++ b/versioned_docs/version-1.x/guides/supabase-security.md
@@ -0,0 +1,100 @@
+---
+description: Guide to securely integrating Supabase with ZenStack - how to prevent unauthorized access.
+sidebar_position: 3
+sidebar_label: Secure Supabase with ZenStack
+---
+
+
+# Ensuring Security When Integrating Supabase with ZenStack
+
+Integrating Supabase with ZenStack can be a great choice as Supabase provides authentication as well as database hosting and file uploads.
+
+However, it is essential to take some precautions when using ZenStack with Supabase to ensure that the ZenStack authorization layer cannot be bypassed via requests to the Supabase API url.
+
+This article will help you to understand the risk, how to test if your database is exposed, and how to prevent it.
+
+## 1.  Understanding the Risk
+
+Supabase allows anonymous requests to the Supabase rest API access the database by default, assuming Row Level Security (RLS) is in place. 
+
+ZenStack's authorization can effectively serve as an in-application security layer, but **direct HTTP requests to your Supabase API could still leave your database exposed.**
+
+This poses a potential risk because Supabase API URL and anon key are both public and can easily be found eg by inspecting the url/headers of a Supabase authentication network request.
+
+
+## 2. How to test if your database is exposed to direct Supabase API requests
+
+Use Bash or an API client (such as postman or VS Code thunder client) to send a HTTP request with these details:
+
+```bash
+curl '{SUPABASE_PROJECT_URL}/rest/v1/{DATABASE_TABLE}?select=*' \
+-H "apikey: SUPABASE_ANON_KEY" \
+-H "Authorization: Bearer SUPABASE_ANON_KEY"
+```
+
+For example:
+
+```bash
+curl 'https://jypbzsozorjnogibmfhu.supabase.co/rest/v1/User?select=*' \
+-H "apikey: eyOiJIUzJhbGciI1..." \
+-H "Authorization: Bearer eyOiJIUzJhbGciI1..."
+```
+
+Note:
+- Your Supabase project URL should look like this: `https://projectid.supabase.co`
+- You can find both of these values inside Supabase dashboard > settings > api
+
+As previously mentioned, all the detailed needed to make this request are public and could be accessed eg by inspecting the url/headers of a Supabase authentication network request.
+
+If your database is secure, you should receive a permission denied error, such as:
+
+```json
+{
+   "code": "42501",
+   "details": null,
+   "hint": null,
+   "message": "permission denied for table User"
+}
+```
+
+If you have RLS enabled on the table, you may get a response with an empty array instead of a permission denied error. This means RLS filtering is effective, and it's preventing you from reading rows that are not granted access. However, it's still recommended to fully revoke anonymous access permissions for better security
+
+## 3. How to prevent direct Supabase API requests
+
+### Revoke default & current privileges
+
+Start by revoking all privileges from the `anon` role to prevent unauthenticated access, and changing the defauts to ensure future objects are also protected.
+
+In the Supabase dashboard inside the SQL Editor tab, execute the following:
+
+```sql
+-- Revoke current privileges
+REVOKE SELECT ON ALL TABLES IN SCHEMA public FROM anon;
+REVOKE USAGE ON ALL SEQUENCES IN SCHEMA public FROM anon;
+REVOKE EXECUTE ON ALL FUNCTIONS IN SCHEMA public FROM anon;
+
+-- Revoke default privileges for future objects
+ALTER DEFAULT PRIVILEGES IN SCHEMA public REVOKE ALL ON TABLES FROM anon;
+ALTER DEFAULT PRIVILEGES IN SCHEMA public REVOKE ALL ON FUNCTIONS FROM anon;
+ALTER DEFAULT PRIVILEGES IN SCHEMA public REVOKE ALL ON SEQUENCES FROM anon;
+```
+
+Note: If you are using Supabase cli for local development, then create a migration file with `pnpm supabase db migration new disable-anon-access` to prevent accidental rollbacks. (this creates a new local sql migration file, 'disable-anon-access' can be replaced)
+
+### Verify Role Configurations
+
+In the Supabase dashboard under the Database > Roles section, confirm the following:
+
+**Anon Role:**  All permissions should be disabled.
+**Authenticated Role:** All permissions should be also disabled here.
+**Postgres Role:** Ensure it retains the ability to bypass RLS for necessary backend tasks.
+
+
+## Conclusion
+
+By disabling permissions for `anon` and `authenticated` roles, you prevent direct public access to your database, relying instead on your application logic and ZenStack's authorization to control access.
+
+Remember to regularly test your application security to ensure effective and appropriate access control for your application.
+
+
+
diff --git a/versioned_docs/version-1.x/guides/telemetry.md b/versioned_docs/version-1.x/guides/telemetry.md
new file mode 100644
index 00000000..c208731b
--- /dev/null
+++ b/versioned_docs/version-1.x/guides/telemetry.md
@@ -0,0 +1,26 @@
+---
+description: Telemetry
+sidebar_position: 100
+---
+
+# Telemetry
+
+ZenStack CLI and VSCode extension sends anonymous telemetry for analyzing usage stats and finding bugs.
+
+The information collected includes:
+
+-   OS
+-   Node.js version
+-   CLI/extension version
+-   CLI command and arguments
+-   CLI errors
+-   Duration of command run
+-   Region (based on IP)
+
+We don't collect any telemetry at the runtime of apps built with ZenStack.
+
+We appreciate that you keep the telemetry ON so we can keep improving the toolkit. We follow the [Console Do Not Track](https://consoledonottrack.com/ ':target=blank') convention, and you can turn off the telemetry by setting the environment variable `DO_NOT_TRACK` to `1`:
+
+```bash
+DO_NOT_TRACK=1 npx zenstack ...
+```
diff --git a/versioned_docs/version-1.x/guides/trpc.mdx b/versioned_docs/version-1.x/guides/trpc.mdx
new file mode 100644
index 00000000..58477fd7
--- /dev/null
+++ b/versioned_docs/version-1.x/guides/trpc.mdx
@@ -0,0 +1,90 @@
+---
+description: Guide for using ZenStack with tRPC.
+sidebar_position: 7
+---
+
+import InitTips from '../_components/_zenstack-init-tips.md';
+
+# Using With tRPC
+
+[tRPC](https://trpc.io) is a fantastic library that magically turns server-side procedures into client-callable functions without requiring you to provide any formal contract. The popular [T3 stack](https://create.t3.gg/) promotes the combo of Prisma + tRPC for achieving type safety from your frontend all the way down to the database.
+
+ZenStack makes things even easier by automatically generating tRPC routers from the ZModel schema. You can use the generated routers together with an enhanced Prisma client; since the Prisma client has the ability to enforce access policies, there is no need to implement authorization code anymore.
+
+## Details
+
+### 1. Initializing the project
+
+If you haven't initialized your tRPC project with ZenStack, run the following command to do it:
+
+```bash
+npx zenstack@1 init
+```
+
+<InitTips />
+
+### 2. Installing the tRPC plugin
+
+You can enable tRPC router generation with the `@zenstackhq/trpc` plugin.
+
+First install the trpc package:
+
+```bash
+npm install -D @zenstackhq/trpc@1
+```
+
+```zmodel title='/schema.zmodel'
+
+plugin trpc {
+    provider = '@zenstackhq/trpc'
+    output = 'src/server/routers/generated'
+}
+
+```
+
+### 3. Setting up the tRPC context
+
+Usually in your tRPC project, you have a function to create a tRPC context. You need to make sure the context contains a `prisma` field that is an instance of Prisma client. The generated tRPC routers use that to talk to the database.
+
+For most of the cases you should use a Prisma client that's "enhanced" by ZenStack so that the CRUD operations are guarded by the access policies defined in the ZModel schema. Here's a quick example with Next.js:
+
+```ts title='/src/server/context.ts'
+
+import { enhance } from '@zenstackhq/runtime';
+import { prisma } from './db';
+import { getSessionUser } from './auth';
+
+export const createContext = async ({ req, res }: CreateNextContextOptions) => {
+    return {
+        ...,
+        // use access-control-enabled Prisma client
+        prisma: await enhance(prisma, { user: getSessionUser(req, res) }),
+    };
+};
+```
+
+### 4. Using the generated routers
+
+Now run the `zenstack` CLI to generate artifacts:
+
+```bash
+npx zenstack generate
+```
+
+You should find a bunch of tRPC routers generated in the output folder, one per each data model. A `createRouter` helper function is also generated, which returns a router instance for all models. You can use it as your top-level tRPC router, or merge it with other routers to form a more complex setup.
+
+```ts title='/src/server/routers/_app.ts'
+import { createRouter } from './generated/routers';
+
+const t = initTRPC.context<Context>().create();
+
+export const appRouter = createRouter(t.router, t.procedure);
+
+export type AppRouter = typeof appRouter;
+```
+
+_NOTE_: The ZenStack trpc plugin is based on the awesome work by [Omar Dulaimi](https://github.com/omar-dulaimi/prisma-trpc-generator).
+
+---
+
+Please refer to the [@zenstackhq/trpc](../reference/plugins/trpc) plugin documentation for more details.
diff --git a/versioned_docs/version-1.x/install.md b/versioned_docs/version-1.x/install.md
new file mode 100644
index 00000000..2ecdc7f2
--- /dev/null
+++ b/versioned_docs/version-1.x/install.md
@@ -0,0 +1,41 @@
+---
+description: Installation
+slug: /install
+sidebar_label: Installation
+sidebar_position: 2
+---
+
+import ZenStackInit from './_components/_zenstack-init.md';
+
+# Installing ZenStack
+
+You can install ZenStack to your project using one of the following methods.
+
+### Using `zenstack init`
+
+The easiest way to install ZenStack is to use the `zenstack init` command. In an existing TypeScript project folder, run the following command:
+
+```bash
+npx zenstack@1 init
+```
+
+<ZenStackInit />
+
+### Manually
+
+The following steps show how to install using "npm" as package manager. Replace it with the one you use for your project (like pnpm or yarn).
+
+1. Make sure Prisma is installed
+2. Install the `zenstack` CLI package as a dev dependency
+   
+   ```bash
+   npm install --save-dev zenstack@1
+   ```
+3. Install the `@zenstackhq/runtime` package as a regular dependency
+   
+   ```bash
+   npm install @zenstackhq/runtime@1
+   ```
+4. Bootstrap ZModel from Prisma schema
+    
+    If you have a Prisma schema file, copy it to `schema.zmodel` in the project root folder. Remember, you should edit the `schema.zmodel` moving forward. The `prisma/schema.prisma` file will automatically regenerate when you run `zenstack generate`.
diff --git a/versioned_docs/version-1.x/quick-start/_access-policy.md b/versioned_docs/version-1.x/quick-start/_access-policy.md
new file mode 100644
index 00000000..79e03a1d
--- /dev/null
+++ b/versioned_docs/version-1.x/quick-start/_access-policy.md
@@ -0,0 +1,16 @@
+:::tip
+
+By default, all operations are denied for a model. You can use the `@@allow` attribute to open up some permissions.
+
+`@@allow` takes two parameters, the first is operation: create/read/update/delete. You can use a comma separated string to pass multiple operations, or use 'all' to abbreviate all operations. The second parameter is a boolean expression that verdicts if the rule should be activated.
+
+Similarly, `@@deny` can be used to explicitly turn off some operations. It has the same syntax as `@@allow` but the opposite effect.
+
+Whether an operation is permitted is determined as follows:
+
+1. If any `@@deny` rule evaluates to true, it's denied.
+2. If any `@@allow` rule evaluates to true, it's allowed.
+3. Otherwise, it's denied.
+
+Check out [Understanding Access Policies](../the-complete-guide/part1/access-policy) for more details.
+:::
diff --git a/versioned_docs/version-1.x/quick-start/_category_.yml b/versioned_docs/version-1.x/quick-start/_category_.yml
new file mode 100644
index 00000000..e8cf2a98
--- /dev/null
+++ b/versioned_docs/version-1.x/quick-start/_category_.yml
@@ -0,0 +1,7 @@
+position: 3
+label: Quick Start
+collapsible: true
+collapsed: false
+link:
+    type: generated-index
+    title: Quick Start
diff --git a/versioned_docs/version-1.x/quick-start/_password-omit.md b/versioned_docs/version-1.x/quick-start/_password-omit.md
new file mode 100644
index 00000000..70ba4322
--- /dev/null
+++ b/versioned_docs/version-1.x/quick-start/_password-omit.md
@@ -0,0 +1,6 @@
+:::tip
+
+1. `@password` is a ZenStack attribute that marks a field to be hashed (using [bcryptjs](https://www.npmjs.com/package/bcryptjs)) before saving.
+1. `@omit` indicates the field should be dropped when returned from a query.
+
+:::
diff --git a/versioned_docs/version-1.x/quick-start/_prerequisite.md b/versioned_docs/version-1.x/quick-start/_prerequisite.md
new file mode 100644
index 00000000..86e37d0d
--- /dev/null
+++ b/versioned_docs/version-1.x/quick-start/_prerequisite.md
@@ -0,0 +1,2 @@
+1. Make sure you have Node.js 18 or above installed.
+1. Install the [VSCode extension](https://marketplace.visualstudio.com/items?itemName=zenstack.zenstack) for editing data models.
diff --git a/versioned_docs/version-1.x/quick-start/_requirements.md b/versioned_docs/version-1.x/quick-start/_requirements.md
new file mode 100644
index 00000000..30c14a44
--- /dev/null
+++ b/versioned_docs/version-1.x/quick-start/_requirements.md
@@ -0,0 +1,9 @@
+Our target app should meet the following requirements:
+
+1. Email/password-based signin/signup.
+1. Users can create posts for themselves.
+1. Post owners can update/publish/unpublish/delete their own posts.
+1. Users cannot make changes to posts that do not belong to them.
+1. Published posts can be viewed by all logged-in users.
+
+Let's get started 🚀.
diff --git a/versioned_docs/version-1.x/quick-start/_zenstack-init-tips.md b/versioned_docs/version-1.x/quick-start/_zenstack-init-tips.md
new file mode 100644
index 00000000..472efd3f
--- /dev/null
+++ b/versioned_docs/version-1.x/quick-start/_zenstack-init-tips.md
@@ -0,0 +1,7 @@
+:::info
+
+The command installs a few NPM dependencies. If the project already has a Prisma schema at `prisma/schema.prisma`, it's copied over to `schema.zmodel`. Otherwise, a sample `schema.zmodel` file is created.
+
+Moving forward, you will keep updating `schema.zmodel` file, and `prisma/schema.prisma` will be automatically generated from it.
+
+:::
diff --git a/versioned_docs/version-1.x/quick-start/backend.mdx b/versioned_docs/version-1.x/quick-start/backend.mdx
new file mode 100644
index 00000000..3355a9b6
--- /dev/null
+++ b/versioned_docs/version-1.x/quick-start/backend.mdx
@@ -0,0 +1,316 @@
+---
+description: Steps for using ZenStack only in backend development.
+sidebar_position: 7
+---
+
+import InitTips from './_zenstack-init-tips.md';
+import AccessPolicy from './_access-policy.md';
+
+# Backend-only Usage
+
+ZenStack gives you a pleasant type-safe approach for calling your database "directly"
+from the frontend without coding a backend. Moreover, even if you're only building the
+backend side of an app, it can still help by defining access policies right inside your
+data model. This results in a security model that's both easier to maintain and more
+reliable than a manually implemented one.
+
+Let's have some fun by creating a simple blogging service with Express.js. You can find the final build result [here](https://github.com/zenstackhq/docs-tutorial-express).
+
+## Requirements
+
+Our target app should meet the following requirements:
+
+1. Users can create posts for themselves.
+1. Post owner can update/publish/unpublish/delete their own posts.
+1. Users cannot make changes to posts that do not belong to them.
+1. Published posts can be viewed by all users.
+
+Let's get started 🚀.
+
+## Prerequisite
+
+1. Make sure you have Node.js 18 or above installed.
+1. Install the [VSCode extension](https://marketplace.visualstudio.com/items?itemName=zenstack.zenstack) for editing data models.
+
+## Building the app
+
+### 1. Create a new Prisma project
+
+Prisma has a nice sample project for Express.js. Let's use it as a starting point:
+
+```bash
+npx degit prisma/prisma-examples/typescript/rest-express my-blog-app
+cd my-blog-app
+npm install
+```
+
+Next, let's initialize the database with seed data and start the dev server:
+
+```bash
+npx prisma db push
+npx prisma db seed
+npm run dev
+```
+
+Your server should be listening on port 3000 for RESTful requests.
+
+Let's add a simple API to list all posts:
+
+```ts title='/src/index.ts'
+app.get(`/post`, async (req, res) => {
+    const post = await prisma.post.findMany({
+        include: { author: true },
+    });
+    res.json(post);
+});
+```
+
+Restart the server and verify it works by:
+
+```bash
+curl localhost:3000/post
+```
+
+You should see a list of posts like this:
+
+```json
+[
+    {
+        "id": 1,
+        "createdAt": "2023-01-14T06:46:52.628Z",
+        "updatedAt": "2023-01-14T06:46:52.628Z",
+        "title": "Join the Prisma Slack",
+        "content": "https://slack.prisma.io",
+        "published": true,
+        "viewCount": 0,
+        "authorId": 1,
+        "author": { "id": 1, "email": "alice@prisma.io", "name": "Alice" }
+    },
+    ...
+]
+```
+
+### 2. Initialize the project for ZenStack
+
+Let's run the `zenstack` CLI to prepare your project for using ZenStack.
+
+```bash
+npx zenstack@1 init
+```
+
+<InitTips />
+
+### 3. Add access policies
+
+Our bootstrapped `schema.zmodel` file already has a `User` and a `Post` model defined.
+However, they don't carry any access policies. So if you use a plain Prisma client in the
+backend code, anyone can do anything to any data unless you write some custom
+authorization logic.
+
+You need two things to secure it up:
+
+1. Implement an authentication mechanism to identify the current user.
+1. Implement authorization to control who can take which actions to what data.
+
+#1 can usually be achieved by requiring a session cookie or a JWT token. For simplicity,
+we'll skip this part in the tutorial and just pass user id in a plain `X-USER-ID` header.
+We'll focus on #2 and utilize ZenStack to implement authorization without manually coding it.
+
+Let's add the following access policies to the `User` and `Post` model to implement the
+requirements we laid out previously:
+
+```zmodel title='/schema.zmodel'
+model User {
+    id Int @id() @default(autoincrement())
+    email String @unique()
+    name String?
+    posts Post[]
+
+    // highlight-start
+    // make user profile public
+    @@allow('read', true)
+    // highlight-end
+}
+
+model Post {
+    id Int @id() @default(autoincrement())
+    createdAt DateTime @default(now())
+    updatedAt DateTime @updatedAt()
+    title String
+    content String?
+    published Boolean @default(false)
+    viewCount Int @default(0)
+    author User? @relation(fields: [authorId], references: [id])
+    authorId Int?
+
+    // highlight-start
+    // author has full access
+    @@allow('all', auth() == author)
+
+    // logged-in users can view published posts
+    @@allow('read', auth() != null && published)
+    // highlight-end
+}
+```
+
+<AccessPolicy />
+
+Now flush the changes to the Prisma schema and database schema:
+
+```bash
+npx zenstack generate && npx prisma db push
+```
+
+### 4. Secure the APIs
+
+Now let's secure up our APIs, leveraging the access policies.
+
+First, add a simple middleware to require `X-USER-ID` header (to simulate authentication).
+Make sure the following code is inserted above all express routes so it can be applied to
+all endpoints.
+
+```ts title='/src/index.ts'
+app.use((req, res, next) => {
+    const userId = req.header('X-USER-ID');
+    if (!userId || Number.isNaN(parseInt(userId))) {
+        res.status(403).json({ error: 'unauthorized' });
+    } else {
+        next();
+    }
+});
+```
+
+Next, add two helper methods to get a Prisma client that's bound to the current user identity:
+
+```ts title='/src/index.ts'
+import { Request } from 'express';
+import { enhance } from '@zenstackhq/runtime';
+
+function getUserId(req: Request) {
+    return parseInt(req.header('X-USER-ID')!);
+}
+
+// Gets a Prisma client bound to the current user identity
+function getPrisma(req: Request) {
+    const userId = getUserId(req);
+    const user = Number.isNaN(userId) ? undefined : { id: userId };
+    return enhance(prisma, { user });
+}
+```
+
+:::tip
+
+`enhance` is a ZenStack runtime API that wraps a Prisma client and enhances it with access policy checks.
+
+:::
+
+Then, change our `POST /post` API to use extracted user id instead of passing in user email in the body
+when creating a new post:
+
+```ts title='/src/index.ts'
+app.post(`/post`, async (req, res) => {
+    const { title, content } = req.body;
+    const result = await prisma.post.create({
+        data: {
+            title,
+            content,
+            // highlight-next-line
+            author: { connect: { id: getUserId(req) } },
+        },
+    });
+    res.json(result);
+});
+```
+
+Finally, change all code that uses `prisma` to `getPrisma(req)`. Here I'm showing only one occurrence:
+
+```ts title='/src/index.ts'
+app.get(`/post`, async (req, res) => {
+    // highlight-next-line
+    const post = await getPrisma(req).post.findMany({
+        include: { author: true },
+    });
+    res.json(post);
+});
+```
+
+Note that you don't need to change your Prisma query because the wrapped Prisma client has exactly the same API as the original Prisma client.
+
+### 5. Test the secured backend
+
+Now, restart the server and hit the `/post` endpoint again directly. You should get a 403 error because the middleware rejected it:
+
+```bash
+curl -I localhost:3000/post
+```
+
+```plain
+  HTTP/1.1 403 Forbidden
+  X-Powered-By: Express
+  Content-Type: application/json; charset=utf-8
+  Content-Length: 24
+  ETag: W/"18-gH7/fIZxPCVRh6TuPVNAgHt/40I"
+  Date: Sat, 14 Jan 2023 08:08:10 GMT
+  Connection: keep-alive
+  Keep-Alive: timeout=5
+```
+
+We can make it work by specifying `X-USER-ID` header:
+
+```bash
+curl -H "X-USER-ID: 1" localhost:3000/post
+```
+
+Let's create a new unpublished post for user#1:
+
+```bash
+curl --request POST \
+    -H "X-USER-ID: 1" -H "Content-Type: application/json" \
+    -d '{"title": "My new post", "content": "Some awesome content"}' \
+    localhost:3000/post
+```
+
+, and hit `/post` API again with user#1:
+
+```bash
+curl -H "X-USER-ID: 1" localhost:3000/post
+```
+
+You should see in the result the newly created post:
+
+```json
+[
+    ...
+    {
+        "id": 4,
+        "createdAt": "2023-01-14T08:42:56.833Z",
+        "updatedAt": "2023-01-14T08:42:56.833Z",
+        "title": "My new post",
+        "content": "Some awesome content",
+        "published": false,
+        "viewCount": 0,
+        "authorId": 1,
+        "author": { "id": 1, "email": "alice@prisma.io", "name": "Alice" }
+    }
+]
+```
+
+However, if you hit `/post` API with user#2:
+
+```bash
+curl -H "X-USER-ID: 2" localhost:3000/post
+```
+
+, you won't see the new post because it's not published yet. Our access policies are at work.
+
+Feel free to play more with updating and deleting posts with different user ids and see if the policies work as expected.
+
+## Wrap up
+
+🎉 Congratulations! You've made a simple but secure blogging service without writing any
+authorization code. Pretty cool, isn't it?
+
+If you have trouble following the building process, you can find the final result
+[here](https://github.com/zenstackhq/docs-tutorial-express/tree/v1). For more details about ZenStack, please refer to the [Reference](../category/reference) and [Guides](../category/recipes) parts of the documentation.
+
+Have fun building cool stuff 🚀!
diff --git a/versioned_docs/version-1.x/quick-start/next-app-router.mdx b/versioned_docs/version-1.x/quick-start/next-app-router.mdx
new file mode 100644
index 00000000..a11cadc3
--- /dev/null
+++ b/versioned_docs/version-1.x/quick-start/next-app-router.mdx
@@ -0,0 +1,753 @@
+---
+title: Next.js (app router)
+description: Step-by-step guide for building a blogging app with Next.js (app router).
+sidebar_position: 2
+---
+
+import Requirements from './_requirements.md';
+import Prerequisite from './_prerequisite.md';
+import InitTips from './_zenstack-init-tips.md';
+import PasswordOmit from './_password-omit.md';
+import AccessPolicy from './_access-policy.md';
+
+# Get Started With Next.js (app router)
+
+Let's have some fun by creating a simple blogging app. You'll see how effortless it is to have a secure backend service without actually coding it.
+
+You can find the final build result [here](https://github.com/zenstackhq/docs-tutorial-nextjs-app-dir).
+
+## Requirements
+
+<Requirements />
+
+## Prerequisite
+
+<Prerequisite />
+
+## Building the app
+
+### 1. Create a new project
+
+The easiest way to create a Next.js project with boilerplates is with `create-t3-app`. Run the following command to create a new project with Prisma, NextAuth and TailwindCSS.
+
+```bash
+npx create-t3-app@latest --prisma --nextAuth --tailwind --appRouter --CI my-blog-app
+cd my-blog-app
+```
+
+Remove `DISCORD_CLIENT_ID` and `DISCORD_CLIENT_SECRET` related code from `src/env.js`, since we're not going to use Discord for authentication. After that, start the dev server:
+
+```bash
+npm run dev
+```
+
+If everything works, you should have a running Next.js app at [http://localhost:3000](http://localhost:3000).
+
+<div align="center">
+    <img src="/img/t3app.png" width="800" />
+</div>
+
+### 2. Initialize the project for ZenStack
+
+Let's run the `zenstack` CLI to prepare your project for using ZenStack.
+
+```bash
+npx zenstack@1 init
+```
+
+<InitTips />
+
+Next, add the following configuration to the `next.config.js` file:
+
+```js title='/next.config.js'
+const config = {
+  // highlight-start
+  experimental: {
+    serverComponentsExternalPackages: ["@zenstackhq/runtime"],
+  }
+  // highlight-end
+};
+```
+
+:::info
+
+The extra configuration is needed because the package `@zenstackhq/runtime` is not yet fully compatible with NextJS's server component bundler. We'll resolve this issue in a future release.
+
+:::
+
+### 3. Preparing the User model for authentication
+
+First, in `schema.zmodel`, make a few changes to the `User` model:
+
+```zmodel title='/schema.zmodel'
+model User {
+  id            String    @id @default(cuid())
+  name          String?
+  email         String?   @unique
+  emailVerified DateTime?
+  // highlight-next-line
+  password      String @password @omit
+  image         String?
+  accounts      Account[]
+  sessions      Session[]
+  posts         Post[]
+
+  // highlight-start
+  // everyone can signup, and user profile is also publicly readable
+  @@allow('create,read', true)
+
+  // only the user can update or delete their own profile
+  @@allow('update,delete', auth() == this)
+  // highlight-end
+}
+```
+
+For simplicity, we'll use username/password-based authentication in this project. In the code above,
+we added a `password` field to support it, together with two access policy rules to control the
+permissions of this model.
+
+<PasswordOmit />
+
+<AccessPolicy />
+
+Now run `zenstack generate` and `prisma db push` to flush the changes to the Prisma schema and database:
+
+```bash
+npx zenstack generate && npx prisma db push
+```
+
+### 4. Configure NextAuth to use credential-based auth
+
+Now let's update `/src/server/auth.ts` to the content below to
+use credentials auth and JWT-based session:
+
+```ts title='/src/server/auth.ts'
+import { PrismaAdapter } from "@auth/prisma-adapter";
+import type { PrismaClient } from "@prisma/client";
+import { compare } from "bcryptjs";
+import {
+  getServerSession,
+  type DefaultSession,
+  type NextAuthOptions,
+} from "next-auth";
+import { type Adapter } from "next-auth/adapters";
+import CredentialsProvider from "next-auth/providers/credentials";
+
+import { db } from "~/server/db";
+
+/**
+ * Module augmentation for `next-auth` types. Allows us to add custom properties to the `session`
+ * object and keep type safety.
+ *
+ * @see https://next-auth.js.org/getting-started/typescript#module-augmentation
+ */
+declare module "next-auth" {
+  interface Session extends DefaultSession {
+    user: {
+      id: string;
+    } & DefaultSession["user"];
+  }
+}
+
+/**
+ * Options for NextAuth.js used to configure adapters, providers, callbacks, etc.
+ *
+ * @see https://next-auth.js.org/configuration/options
+ */
+export const authOptions: NextAuthOptions = {
+  session: {
+    strategy: "jwt",
+  },
+  callbacks: {
+    session({ session, token }) {
+      if (session.user) {
+        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+        session.user.id = token.sub!;
+      }
+      return session;
+    },
+  },
+  adapter: PrismaAdapter(db) as Adapter,
+  providers: [
+    CredentialsProvider({
+      credentials: {
+        email: { type: "email" },
+        password: { type: "password" },
+      },
+      authorize: authorize(db),
+    }),
+    /**
+     * ...add more providers here.
+     *
+     * Most other providers require a bit more work than the Discord provider. For example, the
+     * GitHub provider requires you to add the `refresh_token_expires_in` field to the Account
+     * model. Refer to the NextAuth.js docs for the provider you want to use. Example:
+     *
+     * @see https://next-auth.js.org/providers/github
+     */
+  ],
+};
+
+function authorize(prisma: PrismaClient) {
+  return async (
+    credentials: Record<"email" | "password", string> | undefined,
+  ) => {
+    if (!credentials) throw new Error("Missing credentials");
+    if (!credentials.email)
+      throw new Error('"email" is required in credentials');
+    if (!credentials.password)
+      throw new Error('"password" is required in credentials');
+    const maybeUser = await prisma.user.findFirst({
+      where: { email: credentials.email },
+      select: { id: true, email: true, password: true },
+    });
+    if (!maybeUser?.password) return null;
+    // verify the input password with stored hash
+    const isValid = await compare(credentials.password, maybeUser.password);
+    if (!isValid) return null;
+    return { id: maybeUser.id, email: maybeUser.email };
+  };
+}
+
+/**
+ * Wrapper for `getServerSession` so that you don't need to import the `authOptions` in every file.
+ *
+ * @see https://next-auth.js.org/configuration/nextjs
+ */
+export const getServerAuthSession = () => getServerSession(authOptions);
+```
+
+Finally, add a `NEXTAUTH_SECRET` environment variable in .env file and set it to an arbitrary
+value (use a complex secret in production and don't check it into git):
+
+```plain title='/.env'
+  NEXTAUTH_SECRET=abc123
+```
+
+### 5. Mount CRUD service & generate hooks
+
+ZenStack has built-in support for Next.js and can provide database CRUD services
+automagically, so you don't need to write it yourself.
+
+First install the `@zenstackhq/server`, `@tanstack/react-query`, and `@zenstackhq/tanstack-query` packages:
+
+```bash
+npm install @zenstackhq/server @tanstack/react-query
+npm install -D @zenstackhq/tanstack-query
+```
+
+Let's mount it to the `/api/model/[...path]` endpoint. Create a `/src/app/api/model/[...path]/route.ts`
+file and fill in the content below:
+
+```ts title='/src/app/api/model/[...path]/route.ts'
+import { enhance } from "@zenstackhq/runtime";
+import { NextRequestHandler } from "@zenstackhq/server/next";
+import { getServerAuthSession } from "~/server/auth";
+import { db } from "~/server/db";
+
+// create an enhanced Prisma client with user context
+async function getPrisma() {
+  const session = await getServerAuthSession();
+  return enhance(db, { user: session?.user });
+}
+
+const handler = NextRequestHandler({ getPrisma, useAppDir: true });
+
+export {
+  handler as DELETE,
+  handler as GET,
+  handler as PATCH,
+  handler as POST,
+  handler as PUT,
+};
+```
+
+The `/api/model` route is now ready to access database query and mutation requests.
+However, manually calling the service will be tedious. Fortunately, ZenStack can
+automatically generate React data query hooks for you.
+
+Let's enable it by adding the following snippet at the top level to `schema.zmodel`:
+
+```zmodel title='/schema.zmodel'
+plugin hooks {
+  provider = '@zenstackhq/tanstack-query'
+  target = 'react'
+  version = 'v5'
+  output = "./src/lib/hooks"
+}
+```
+
+Now run `zenstack generate` again; you'll find the hooks generated under `/src/lib/hooks` folder:
+
+```bash
+npx zenstack generate
+```
+
+Now we're ready to implement the signup/signin flow.
+
+### 6. Set up context providers
+
+NextAuth and React Query require context providers to be set up. Create a `/src/app/providers.tsx` file with the following content:
+
+```tsx title='/src/app/providers.tsx'
+"use client";
+
+import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
+import { SessionProvider } from "next-auth/react";
+import type { ReactNode } from "react";
+
+const queryClient = new QueryClient();
+
+export default function Providers({ children }: { children: ReactNode }) {
+  return (
+    <QueryClientProvider client={queryClient}>
+      <SessionProvider>{children}</SessionProvider>
+    </QueryClientProvider>
+  );
+}
+```
+
+Then, update `/src/app/layout.tsx` to install the query client provider:
+
+```ts title='/src/app/layout.tsx'
+"use client";
+
+import "~/styles/globals.css";
+
+import { Inter } from "next/font/google";
+import Providers from "./providers";
+
+const inter = Inter({
+  subsets: ["latin"],
+  variable: "--font-sans",
+});
+
+export default function RootLayout({
+  children,
+}: {
+  children: React.ReactNode;
+}) {
+  return (
+    <html lang="en">
+      <body className={`font-sans ${inter.variable}`}>
+        <Providers>{children}</Providers>
+      </body>
+    </html>
+  );
+}
+```
+
+:::info
+We've only done the minimum setup to get React Query to work with NextJS's app router. For more more in-depth background and setup, please refer to React Query's official documentation about [Advanced Server Rendering](https://tanstack.com/query/latest/docs/framework/react/guides/advanced-ssr).
+:::
+
+### 7. Implement Signup/Signin
+
+Now let's implement the signup/signin pages. First, create a new page `/src/app/signup/page.tsx`:
+
+```tsx title='/src/app/signup/page.tsx'
+"use client";
+
+import type { NextPage } from "next";
+import { signIn } from "next-auth/react";
+import { useRouter } from "next/navigation";
+import { useState, type FormEvent } from "react";
+import { useCreateUser } from "~/lib/hooks";
+
+const Signup: NextPage = () => {
+  const [email, setEmail] = useState("");
+  const [password, setPassword] = useState("");
+  const { mutateAsync: signup } = useCreateUser();
+  const router = useRouter();
+
+  async function onSignup(e: FormEvent) {
+    e.preventDefault();
+    try {
+      await signup({ data: { email, password } });
+      // eslint-disable-next-line @typescript-eslint/no-explicit-any
+    } catch (err: any) {
+      console.error(err);
+      // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
+      if (err.info?.prisma && err.info?.code === "P2002") {
+        // P2002 is Prisma's error code for unique constraint violations
+        alert("User already exists");
+      } else {
+        alert("An unknown error occurred");
+      }
+      return;
+    }
+
+    // signin to create a session
+    await signIn("credentials", { redirect: false, email, password });
+    router.push("/");
+  }
+
+  return (
+    <div className="flex min-h-screen flex-col items-center justify-center bg-gradient-to-b from-[#2e026d] to-[#15162c]">
+      <h1 className="text-5xl font-extrabold text-white">Sign up</h1>
+      <form
+        className="mt-16 flex flex-col gap-8 text-2xl"
+        onSubmit={(e) => void onSignup(e)}
+      >
+        <div>
+          <label htmlFor="email" className="inline-block w-32 text-white">
+            Email
+          </label>
+          <input
+            id="email"
+            type="email"
+            value={email}
+            onChange={(e) => setEmail(e.currentTarget.value)}
+            className="ml-4 w-72 rounded border p-2"
+          />
+        </div>
+        <div>
+          <label htmlFor="password" className="inline-block w-32 text-white ">
+            Password
+          </label>
+          <input
+            id="password"
+            type="password"
+            value={password}
+            onChange={(e) => setPassword(e.currentTarget.value)}
+            className="ml-4 w-72 rounded border p-2"
+          />
+        </div>
+        <input
+          type="submit"
+          value="Create account"
+          className="cursor-pointer rounded border border-gray-500 py-4 text-white"
+        />
+      </form>
+    </div>
+  );
+};
+
+export default Signup;
+```
+
+In the code above, we used the auto-generated `useCreateUser` hooks to create new `User` entities.
+
+:::tip
+
+1. The services backing the hooks are governed by the access policies we defined. Here the `create`
+   call can succeed because we explicitly allowed it in the `User` model. By default, all operations
+   are forbidden.
+1. The `password` field is automatically hashed. You can confirm it using a sqlite inspection tool
+   to browse the `prisma/db.sqlite` database file.
+
+:::
+
+Try visiting [http://localhost:3000/signup](http://localhost:3000/signup) and creating a new user.
+It should look like this:
+
+<div align="center">
+    <img src="/img/tutorial-signup-form.png" width="800" />
+</div>
+
+Similarly, create the signin page `/src/app/signin/page.tsx`:
+
+```tsx title='/src/app/signin/page.tsx'
+"use client";
+
+import type { NextPage } from "next";
+import { signIn } from "next-auth/react";
+import { useRouter } from "next/navigation";
+import { useState, type FormEvent } from "react";
+
+const Signin: NextPage = () => {
+  const [email, setEmail] = useState("");
+  const [password, setPassword] = useState("");
+  const router = useRouter();
+
+  async function onSignin(e: FormEvent) {
+    e.preventDefault();
+
+    const result = await signIn("credentials", {
+      redirect: false,
+      email,
+      password,
+    });
+
+    if (result?.ok) {
+      router.push("/");
+    } else {
+      alert("Signin failed");
+    }
+  }
+
+  return (
+    <div className="flex min-h-screen flex-col items-center justify-center bg-gradient-to-b from-[#2e026d] to-[#15162c]">
+      <h1 className="text-5xl font-extrabold text-white">Login</h1>
+      <form
+        className="mt-16 flex flex-col gap-8 text-2xl"
+        onSubmit={(e) => void onSignin(e)}
+      >
+        <div>
+          <label htmlFor="email" className="inline-block w-32  text-white">
+            Email
+          </label>
+          <input
+            id="email"
+            type="email"
+            value={email}
+            onChange={(e) => setEmail(e.currentTarget.value)}
+            className="ml-4 w-72 rounded border p-2"
+          />
+        </div>
+        <div>
+          <label htmlFor="password" className="inline-block w-32  text-white">
+            Password
+          </label>
+          <input
+            id="password"
+            type="password"
+            value={password}
+            onChange={(e) => setPassword(e.currentTarget.value)}
+            className="ml-4 w-72 rounded border p-2"
+          />
+        </div>
+        <input
+          type="submit"
+          value="Sign me in"
+          className="cursor-pointer rounded border border-gray-500 py-4 text-white"
+        />
+      </form>
+    </div>
+  );
+};
+
+export default Signin;
+```
+
+### 8. Prepare the Post model
+
+Now let's update the `Post` model to add access policies.
+
+```zmodel title='/schema.zmodel'
+model Post {
+  id Int @id @default(autoincrement())
+  name String
+  createdAt DateTime @default(now())
+  updatedAt DateTime @updatedAt
+  // highlight-next-line
+  published Boolean @default(false)
+
+  createdBy User @relation(fields: [createdById], references: [id])
+  createdById String
+
+  @@index([name])
+
+  // highlight-start
+  // author has full access
+  @@allow('all', auth() == createdBy)
+
+  // logged-in users can view published posts
+  @@allow('read', auth() != null && published)
+  // highlight-end
+}
+```
+
+Don't forget to regenerate and push schema changes to the database:
+
+```bash
+npx zenstack generate && npx prisma db push
+```
+
+### 9. Build up the home page
+
+Now let's replace `/src/app/page.tsx` with the content below and use it for viewing and managing posts.
+
+```tsx title='/src/app/page.tsx'
+"use client";
+
+import type { Post } from "@prisma/client";
+import { type NextPage } from "next";
+import { signOut, useSession } from "next-auth/react";
+import Link from "next/link";
+import { useRouter } from "next/navigation";
+import {
+  useFindManyPost,
+  useCreatePost,
+  useUpdatePost,
+  useDeletePost,
+} from "../lib/hooks";
+
+type AuthUser = { id: string; email?: string | null };
+
+const Welcome = ({ user }: { user: AuthUser }) => {
+  const router = useRouter();
+  async function onSignout() {
+    await signOut({ redirect: false });
+    router.push("/signin");
+  }
+  return (
+    <div className="flex gap-4">
+      <h3 className="text-lg">Welcome back, {user?.email}</h3>
+      <button
+        className="text-gray-300 underline"
+        onClick={() => void onSignout()}
+      >
+        Signout
+      </button>
+    </div>
+  );
+};
+
+const SigninSignup = () => {
+  return (
+    <div className="flex gap-4 text-2xl">
+      <Link href="/signin" className="rounded-lg border px-4 py-2">
+        Signin
+      </Link>
+      <Link href="/signup" className="rounded-lg border px-4 py-2">
+        Signup
+      </Link>
+    </div>
+  );
+};
+
+const Posts = ({ user }: { user: AuthUser }) => {
+  // Post crud hooks
+  const { mutateAsync: createPost } = useCreatePost();
+  const { mutateAsync: updatePost } = useUpdatePost();
+  const { mutateAsync: deletePost } = useDeletePost();
+
+  // list all posts that're visible to the current user, together with their authors
+  const { data: posts } = useFindManyPost({
+    include: { createdBy: true },
+    orderBy: { createdAt: "desc" },
+  });
+
+  async function onCreatePost() {
+    const name = prompt("Enter post name");
+    if (name) {
+      await createPost({
+        data: { name, createdBy: { connect: { id: user.id } } },
+      });
+    }
+  }
+
+  async function onTogglePublished(post: Post) {
+    await updatePost({
+      where: { id: post.id },
+      data: { published: !post.published },
+    });
+  }
+
+  async function onDelete(post: Post) {
+    await deletePost({ where: { id: post.id } });
+  }
+
+  return (
+    <div className="container flex flex-col text-white">
+      <button
+        className="rounded border border-white p-2 text-lg"
+        onClick={() => void onCreatePost()}
+      >
+        + Create Post
+      </button>
+
+      <ul className="container mt-8 flex flex-col gap-2">
+        {posts?.map((post) => (
+          <li key={post.id} className="flex items-end justify-between gap-4">
+            <p className={`text-2xl ${!post.published ? "text-gray-400" : ""}`}>
+              {post.name}
+              <span className="text-lg"> by {post.createdBy.email}</span>
+            </p>
+            <div className="flex w-32 justify-end gap-1 text-left">
+              <button
+                className="underline"
+                onClick={() => void onTogglePublished(post)}
+              >
+                {post.published ? "Unpublish" : "Publish"}
+              </button>
+              <button className="underline" onClick={() => void onDelete(post)}>
+                Delete
+              </button>
+            </div>
+          </li>
+        ))}
+      </ul>
+    </div>
+  );
+};
+
+const Home: NextPage = () => {
+  const { data: session, status } = useSession();
+
+  if (status === "loading") return <p>Loading ...</p>;
+
+  return (
+    <main className="flex min-h-screen flex-col items-center justify-center bg-gradient-to-b from-[#2e026d] to-[#15162c]">
+      <div className="container flex flex-col items-center justify-center gap-12 px-4 py-16 text-white">
+        <h1 className="text-5xl font-extrabold">My Awesome Blog</h1>
+
+        {session?.user ? (
+          // welcome & blog posts
+          <div className="flex flex-col">
+            <Welcome user={session.user} />
+            <section className="mt-10">
+              <Posts user={session.user} />
+            </section>
+          </div>
+        ) : (
+          // if not logged in
+          <SigninSignup />
+        )}
+      </div>
+    </main>
+  );
+};
+
+export default Home;
+```
+
+## Verifying the result
+
+Restart the dev server and try creating a few posts. You should see something like the following:
+
+<div align="center">
+    <img src="/img/tutorial-blog-posts.png" width="800" />
+</div>
+
+The code looks a bit long because we tucked all UI components directly into the page.
+As you can see, querying and mutating `Post` entities are fairly straightforward,
+the generated hooks. When mutation happens, e.g. a new post is created, data refetching
+is also triggered automatically.
+
+Try opening an incognito browser window and signing up for a separate account. You should
+find that the published posts are visible, as we specified with access policies.
+
+<div align="center">
+    <img src="/img/tutorial-blog-posts-readable.png" width="800" />
+</div>
+
+Since we haven't hidden "Unpublish" and "Delete" buttons for posts not owned by the current
+user, you can still click them even for posts not owned to you, but it will end up with a
+403 error:
+
+![network error](/img/tutorial-post-update-denied.png)
+
+```json
+{
+    "prisma": true,
+    "rejectedByPolicy": true,
+    "code": "P2004",
+    "message": "denied by policy: post entities failed 'update' check, 1 entities failed policy check"
+}
+```
+
+You can catch the error and render a nice message to the user.
+
+## Wrap up
+
+🎉 Congratulations! You've made a simple blogging app without writing a single line of
+backend code. Pretty cool, isn't it?
+
+If you have trouble following the building process, you can find the final result
+[here](https://github.com/zenstackhq/docs-tutorial-nextjs-app-dir/tree/v1). For more details about ZenStack,
+please refer to the [Reference](../category/reference) and [Guides](../category/recipes)
+parts of the documentation.
+
+Have fun building cool stuff 🚀!
diff --git a/versioned_docs/version-1.x/quick-start/nextjs.mdx b/versioned_docs/version-1.x/quick-start/nextjs.mdx
new file mode 100644
index 00000000..d1a479c8
--- /dev/null
+++ b/versioned_docs/version-1.x/quick-start/nextjs.mdx
@@ -0,0 +1,649 @@
+---
+title: Next.js (pages router)
+description: Step-by-step guide for building a blogging app with Next.js (pages router).
+sidebar_position: 1
+---
+
+import Requirements from './_requirements.md';
+import Prerequisite from './_prerequisite.md';
+import InitTips from './_zenstack-init-tips.md';
+import PasswordOmit from './_password-omit.md';
+import AccessPolicy from './_access-policy.md';
+
+# Get Started With Next.js (pages router)
+
+Let's have some fun by creating a simple blogging app. You'll see how effortless it is to have a secure backend service without actually coding it.
+
+You can find the final build result [here](https://github.com/zenstackhq/docs-tutorial-nextjs).
+
+## Requirements
+
+<Requirements />
+
+## Prerequisite
+
+<Prerequisite />
+
+## Building the app
+
+### 1. Create a new project
+
+The easiest way to create a Next.js project with boilerplates is with `create-t3-app`. Run the following command to create a new project with Prisma, NextAuth and TailwindCSS.
+
+```bash
+npx create-t3-app@latest --prisma --nextAuth --tailwind --CI my-blog-app
+cd my-blog-app
+```
+
+Remove `DISCORD_CLIENT_ID` and `DISCORD_CLIENT_SECRET` related code from `src/env.js`, since we're not going to use Discord for authentication. After that, start the dev server:
+
+```bash
+npm run dev
+```
+
+If everything works, you should have a running Next.js app at [http://localhost:3000](http://localhost:3000).
+
+<div align="center">
+    <img src="/img/t3app.png" width="800" />
+</div>
+
+### 2. Initialize the project for ZenStack
+
+Let's run the `zenstack` CLI to prepare your project for using ZenStack.
+
+```bash
+npx zenstack@1 init
+```
+
+<InitTips />
+
+### 3. Preparing the User model for authentication
+
+First, in `schema.zmodel`, make a few changes to the `User` model:
+
+```zmodel title='/schema.zmodel'
+model User {
+  id            String    @id @default(cuid())
+  name          String?
+  email         String?   @unique
+  emailVerified DateTime?
+  // highlight-next-line
+  password      String @password @omit
+  image         String?
+  accounts      Account[]
+  sessions      Session[]
+  posts         Post[]
+
+  // highlight-start
+  // everyone can signup, and user profile is also publicly readable
+  @@allow('create,read', true)
+
+  // only the user can update or delete their own profile
+  @@allow('update,delete', auth() == this)
+  // highlight-end
+}
+```
+
+For simplicity, we'll use username/password-based authentication in this project. In the code above,
+we added a `password` field to support it, together with two access policy rules to control the
+permissions of this model.
+
+<PasswordOmit />
+
+<AccessPolicy />
+
+Now run `zenstack generate` and `prisma db push` to flush the changes to the Prisma schema and database:
+
+```bash
+npx zenstack generate && npx prisma db push
+```
+
+### 4. Configure NextAuth to use credential-based auth
+
+Now let's update `/src/server/auth.ts` to the content below to
+use credentials auth and JWT-based session:
+
+```ts title='/src/server/auth.ts'
+import { PrismaAdapter } from '@next-auth/prisma-adapter';
+import type { PrismaClient } from '@prisma/client';
+import { compare } from 'bcryptjs';
+import type { GetServerSidePropsContext } from 'next';
+import NextAuth, { getServerSession, type DefaultSession, type NextAuthOptions } from 'next-auth';
+import CredentialsProvider from 'next-auth/providers/credentials';
+import { db } from './db';
+/**
+ * Module augmentation for `next-auth` types.
+ * Allows us to add custom properties to the `session` object and keep type
+ * safety.
+ *
+ * @see https://next-auth.js.org/getting-started/typescript#module-augmentation
+ **/
+declare module 'next-auth' {
+    interface Session extends DefaultSession {
+        user: {
+            id: string;
+        } & DefaultSession['user'];
+    }
+}
+
+/**
+ * Options for NextAuth.js used to configure adapters, providers, callbacks,
+ * etc.
+ *
+ * @see https://next-auth.js.org/configuration/options
+ **/
+export const authOptions: NextAuthOptions = {
+    session: {
+        strategy: 'jwt',
+    },
+    // Include user.id on session
+    callbacks: {
+        session({ session, token }) {
+            if (session.user) {
+                session.user.id = token.sub!;
+            }
+            return session;
+        },
+    },
+    // Configure one or more authentication providers
+    adapter: PrismaAdapter(db),
+    providers: [
+        CredentialsProvider({
+            credentials: {
+                email: { type: 'email' },
+                password: { type: 'password' },
+            },
+            authorize: authorize(db),
+        }),
+    ],
+};
+
+function authorize(prisma: PrismaClient) {
+    return async (credentials: Record<'email' | 'password', string> | undefined) => {
+        if (!credentials) throw new Error('Missing credentials');
+        if (!credentials.email) throw new Error('"email" is required in credentials');
+        if (!credentials.password) throw new Error('"password" is required in credentials');
+        const maybeUser = await prisma.user.findFirst({
+            where: { email: credentials.email },
+            select: { id: true, email: true, password: true },
+        });
+        if (!maybeUser || !maybeUser.password) return null;
+        // verify the input password with stored hash
+        const isValid = await compare(credentials.password, maybeUser.password);
+        if (!isValid) return null;
+        return { id: maybeUser.id, email: maybeUser.email };
+    };
+}
+
+/**
+ * Wrapper for `getServerSession` so that you don't need to import the
+ * `authOptions` in every file.
+ *
+ * @see https://next-auth.js.org/configuration/nextjs
+ **/
+export const getServerAuthSession = (ctx: {
+    req: GetServerSidePropsContext['req'];
+    res: GetServerSidePropsContext['res'];
+}) => {
+    return getServerSession(ctx.req, ctx.res, authOptions);
+};
+
+export default NextAuth(authOptions);
+```
+
+Finally, add a `NEXTAUTH_SECRET` environment variable in .env file and set it to an arbitrary
+value (use a complex secret in production and don't check it into git):
+
+```plain title='/.env'
+  NEXTAUTH_SECRET=abc123
+```
+
+### 5. Mount CRUD service & generate hooks
+
+ZenStack has built-in support for Next.js and can provide database CRUD services
+automagically, so you don't need to write it yourself.
+
+First install the `@zenstackhq/server` and `@zenstackhq/swr` packages:
+
+```bash
+npm install @zenstackhq/server swr
+npm install -D  @zenstackhq/swr
+```
+
+Let's mount it to the `/api/model/[...path]` endpoint. Create a `/src/pages/api/model/[...path].ts`
+file and fill in the content below:
+
+```ts title='/src/pages/api/model/[...path].ts'
+import { NextRequestHandler } from '@zenstackhq/server/next';
+import { enhance } from '@zenstackhq/runtime';
+import type { NextApiRequest, NextApiResponse } from 'next';
+import { getServerAuthSession } from '../../../server/auth';
+import { prisma } from '../../../server/db';
+
+async function getPrisma(req: NextApiRequest, res: NextApiResponse) {
+    const session = await getServerAuthSession({ req, res });
+    // create a wrapper of Prisma client that enforces access policy,
+    // data validation, and @password, @omit behaviors
+    return enhance(prisma, { user: session?.user });
+}
+
+export default NextRequestHandler({ getPrisma });
+```
+
+The `/api/model` route is now ready to access database query and mutation requests.
+However, manually calling the service will be tedious. Fortunately, ZenStack can
+automatically generate React hooks for you.
+
+Let's enable it by adding the following snippet at the top level to `schema.zmodel`:
+
+```zmodel
+plugin hooks {
+  provider = '@zenstackhq/swr'
+  output = "./src/lib/hooks"
+}
+```
+
+Now run `zenstack generate` again; you'll find the hooks generated under `/src/lib/hooks` folder:
+
+```bash
+npx zenstack generate
+```
+
+Now we're ready to implement the signup/signin flow.
+
+### 6. Implement Signup/Signin
+
+Now let's implement the signup/signin pages. First, create a new page `/src/pages/signup.tsx`:
+
+```tsx title='/src/pages/signup.tsx'
+/* eslint-disable @typescript-eslint/no-explicit-any */
+/* eslint-disable @typescript-eslint/no-unsafe-member-access */
+import type { NextPage } from 'next';
+import { signIn } from 'next-auth/react';
+import Router from 'next/router';
+import { useState, type FormEvent } from 'react';
+import { useMutateUser } from '../lib/hooks';
+
+const Signup: NextPage = () => {
+    const [email, setEmail] = useState('');
+    const [password, setPassword] = useState('');
+    const { createUser: signup } = useMutateUser();
+
+    async function onSignup(e: FormEvent) {
+        e.preventDefault();
+        try {
+            await signup({ data: { email, password } });
+        } catch (err: any) {
+            console.error(err);
+            if (err.info?.prisma && err.info?.code === 'P2002') {
+                // P2002 is Prisma's error code for unique constraint violations
+                alert('User alread exists');
+            } else {
+                alert('An unknown error occurred');
+            }
+            return;
+        }
+
+        // signin to create a session
+        await signIn('credentials', { redirect: false, email, password });
+        await Router.push('/');
+    }
+
+    return (
+        <div className="flex min-h-screen flex-col items-center justify-center bg-gradient-to-b from-[#2e026d] to-[#15162c]">
+            <h1 className="text-5xl font-extrabold text-white">Sign up</h1>
+            <form className="mt-16 flex flex-col gap-8 text-2xl" onSubmit={(e) => void onSignup(e)}>
+                <div>
+                    <label htmlFor="email" className="inline-block w-32 text-white">
+                        Email
+                    </label>
+                    <input
+                        id="email"
+                        type="email"
+                        value={email}
+                        onChange={(e) => setEmail(e.currentTarget.value)}
+                        className="ml-4 w-72 rounded border p-2"
+                    />
+                </div>
+                <div>
+                    <label htmlFor="password" className="inline-block w-32 text-white ">
+                        Password
+                    </label>
+                    <input
+                        id="password"
+                        type="password"
+                        value={password}
+                        onChange={(e) => setPassword(e.currentTarget.value)}
+                        className="ml-4 w-72 rounded border p-2"
+                    />
+                </div>
+                <input
+                    type="submit"
+                    value="Create account"
+                    className="cursor-pointer rounded border border-gray-500 py-4 text-white"
+                />
+            </form>
+        </div>
+    );
+};
+
+export default Signup;
+```
+
+In the code above, we used the auto-generated `useMutateUser` hooks to create new `User` entities.
+
+:::tip
+
+1. The services backing the hooks are governed by the access policies we defined. Here the `create`
+   call can succeed because we explicitly allowed it in the `User` model. By default, all operations
+   are forbidden.
+1. The `password` field is automatically hashed. You can confirm it using a sqlite inspection tool
+   to browse the `prisma/db.sqlite` database file.
+
+:::
+
+Try visiting [http://localhost:3000/signup](http://localhost:3000/signup) and creating a new user.
+It should look like this:
+
+<div align="center">
+    <img src="/img/tutorial-signup-form.png" width="800" />
+</div>
+
+Similarly, create the signin page `/src/pages/signin.tsx`:
+
+```tsx title='/src/pages/signin.tsx'
+import type { NextPage } from 'next';
+import { signIn } from 'next-auth/react';
+import Router from 'next/router';
+import { useState, type FormEvent } from 'react';
+
+const Signin: NextPage = () => {
+    const [email, setEmail] = useState('');
+    const [password, setPassword] = useState('');
+
+    async function onSignin(e: FormEvent) {
+        e.preventDefault();
+
+        const result = await signIn('credentials', {
+            redirect: false,
+            email,
+            password,
+        });
+
+        if (result?.ok) {
+            await Router.push('/');
+        } else {
+            alert('Signin failed');
+        }
+    }
+
+    return (
+        <div className="flex min-h-screen flex-col items-center justify-center bg-gradient-to-b from-[#2e026d] to-[#15162c]">
+            <h1 className="text-5xl font-extrabold text-white">Login</h1>
+            <form className="mt-16 flex flex-col gap-8 text-2xl" onSubmit={(e) => void onSignin(e)}>
+                <div>
+                    <label htmlFor="email" className="inline-block w-32  text-white">
+                        Email
+                    </label>
+                    <input
+                        id="email"
+                        type="email"
+                        value={email}
+                        onChange={(e) => setEmail(e.currentTarget.value)}
+                        className="ml-4 w-72 rounded border p-2"
+                    />
+                </div>
+                <div>
+                    <label htmlFor="password" className="inline-block w-32  text-white">
+                        Password
+                    </label>
+                    <input
+                        id="password"
+                        type="password"
+                        value={password}
+                        onChange={(e) => setPassword(e.currentTarget.value)}
+                        className="ml-4 w-72 rounded border p-2"
+                    />
+                </div>
+                <input
+                    type="submit"
+                    value="Sign me in"
+                    className="cursor-pointer rounded border border-gray-500 py-4 text-white"
+                />
+            </form>
+        </div>
+    );
+};
+
+export default Signin;
+```
+
+### 7. Prepare the Blog model
+
+Now let's create a `Blog` model. We'll use it to store blog posts.
+
+```zmodel title='/schema.zmodel'
+model Post {
+  id        String @id @default(cuid())
+  createdAt DateTime @default(now())
+  updatedAt DateTime @updatedAt
+  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)
+}
+```
+
+`User` and `Post` model has a one-to-many relation. We can establish it by adding
+a `posts` relation field to the `User` model.
+
+```zmodel title='/schema.zmodel'
+model User {
+    id            String    @id @default(cuid())
+    name          String?
+    email         String?   @unique
+    emailVerified DateTime?
+    password      String @password @omit
+    // highlight-next-line
+    posts         Post[]
+    image         String?
+    accounts      Account[]
+    sessions      Session[]
+
+    // everyone can signup, and user profile is also publicly readable
+    @@allow('create,read', true)
+
+    // only the user can update or delete their own profile
+    @@allow('update,delete', auth() == this)
+}
+```
+
+Don't forget to regenerate and push schema changes to the database:
+
+```bash
+npx zenstack generate && npx prisma db push
+```
+
+### 8. Build up the home page
+
+Now let's replace `/src/pages/index.tsx` with the content below and use it for viewing and managing posts.
+
+```tsx title='/src/pages/index.tsx'
+import type { Post } from '@prisma/client';
+import { type NextPage } from 'next';
+import { signOut, useSession } from 'next-auth/react';
+import Link from 'next/link';
+import Router from 'next/router';
+import { useFindManyPost, useMutatePost } from '../lib/hooks';
+
+type AuthUser = { id: string; email?: string | null };
+
+const Welcome = ({ user }: { user: AuthUser }) => {
+    async function onSignout() {
+        await signOut({ redirect: false });
+        await Router.push('/signin');
+    }
+    return (
+        <div className="flex gap-4">
+            <h3 className="text-lg">Welcome back, {user?.email}</h3>
+            <button className="text-gray-300 underline" onClick={() => void onSignout()}>
+                Signout
+            </button>
+        </div>
+    );
+};
+
+const SigninSignup = () => {
+    return (
+        <div className="flex gap-4 text-2xl">
+            <Link href="/signin" className="rounded-lg border px-4 py-2">
+                Signin
+            </Link>
+            <Link href="/signup" className="rounded-lg border px-4 py-2">
+                Signup
+            </Link>
+        </div>
+    );
+};
+
+const Posts = ({ user }: { user: AuthUser }) => {
+    // Post crud hooks
+    const { createPost, updatePost, deletePost } = useMutatePost();
+
+    // list all posts that're visible to the current user, together with their authors
+    const { data: posts } = useFindManyPost({
+        include: { author: true },
+        orderBy: { createdAt: 'desc' },
+    });
+
+    async function onCreatePost() {
+        const title = prompt('Enter post title');
+        if (title) {
+            await createPost({ data: { title, authorId: user.id } });
+        }
+    }
+
+    async function onTogglePublished(post: Post) {
+        await updatePost({
+            where: { id: post.id },
+            data: { published: !post.published },
+        });
+    }
+
+    async function onDelete(post: Post) {
+        await deletePost({ where: { id: post.id } });
+    }
+
+    return (
+        <div className="container flex flex-col text-white">
+            <button className="rounded border border-white p-2 text-lg" onClick={() => void onCreatePost()}>
+                + Create Post
+            </button>
+
+            <ul className="container mt-8 flex flex-col gap-2">
+                {posts?.map((post) => (
+                    <li key={post.id} className="flex items-end justify-between gap-4">
+                        <p className={`text-2xl ${!post.published ? 'text-gray-400' : ''}`}>
+                            {post.title}
+                            <span className="text-lg"> by {post.author.email}</span>
+                        </p>
+                        <div className="flex w-32 justify-end gap-1 text-left">
+                            <button className="underline" onClick={() => void onTogglePublished(post)}>
+                                {post.published ? 'Unpublish' : 'Publish'}
+                            </button>
+                            <button className="underline" onClick={() => void onDelete(post)}>
+                                Delete
+                            </button>
+                        </div>
+                    </li>
+                ))}
+            </ul>
+        </div>
+    );
+};
+
+const Home: NextPage = () => {
+    const { data: session, status } = useSession();
+
+    if (status === 'loading') return <p>Loading ...</p>;
+
+    return (
+        <main className="flex min-h-screen flex-col items-center justify-center bg-gradient-to-b from-[#2e026d] to-[#15162c]">
+            <div className="container flex flex-col items-center justify-center gap-12 px-4 py-16 text-white">
+                <h1 className="text-5xl font-extrabold">My Awesome Blog</h1>
+
+                {session?.user ? (
+                    // welcome & blog posts
+                    <div className="flex flex-col">
+                        <Welcome user={session.user} />
+                        <section className="mt-10">
+                            <Posts user={session.user} />
+                        </section>
+                    </div>
+                ) : (
+                    // if not logged in
+                    <SigninSignup />
+                )}
+            </div>
+        </main>
+    );
+};
+
+export default Home;
+```
+
+## Verifying the result
+
+Restart the dev server and try creating a few posts. You should see something like the following:
+
+<div align="center">
+    <img src="/img/tutorial-blog-posts.png" width="800" />
+</div>
+
+The code looks a bit long because we tucked all UI components directly into the page.
+As you can see, querying and mutating `Post` entities are fairly straightforward,
+the generated hooks. When mutation happens, e.g. a new post is created, data refetching
+is also triggered automatically.
+
+Try opening an incognito browser window and signing up for a separate account. You should
+find that the published posts are visible, as we specified with access policies.
+
+<div align="center">
+    <img src="/img/tutorial-blog-posts-readable.png" width="800" />
+</div>
+
+Since we haven't hidden "Unpublish" and "Delete" buttons for posts not owned by the current
+user, you can still click them even for posts not owned to you, but it will end up with a
+403 error:
+
+![network error](/img/tutorial-post-update-denied.png)
+
+```json
+{
+    "prisma": true,
+    "rejectedByPolicy": true,
+    "code": "P2004",
+    "message": "denied by policy: post entities failed 'update' check, 1 entities failed policy check"
+}
+```
+
+You can catch the error and render a nice message to the user.
+
+## Wrap up
+
+🎉 Congratulations! You've made a simple blogging app without writing a single line of
+backend code. Pretty cool, isn't it?
+
+If you have trouble following the building process, you can find the final result
+[here](https://github.com/zenstackhq/docs-tutorial-nextjs/tree/v1). For more details about ZenStack,
+please refer to the [Reference](../category/reference) and [Guides](../category/recipes)
+parts of the documentation.
+
+Have fun building cool stuff 🚀!
diff --git a/versioned_docs/version-1.x/quick-start/nuxt.mdx b/versioned_docs/version-1.x/quick-start/nuxt.mdx
new file mode 100644
index 00000000..2cccfcee
--- /dev/null
+++ b/versioned_docs/version-1.x/quick-start/nuxt.mdx
@@ -0,0 +1,367 @@
+---
+title: Nuxt
+description: Step-by-step guide for building a blogging app with Nuxt V3.
+sidebar_position: 3
+---
+
+import Requirements from './_requirements.md';
+import Prerequisite from './_prerequisite.md';
+import InitTips from './_zenstack-init-tips.md';
+import AccessPolicy from './_access-policy.md';
+
+# Get Started With Nuxt (V3)
+
+Nuxt is an excellent full-stack framework built around Vue.js. It provides the structure and facilities that help you build complete web apps with ease. In this tutorial, we'll demonstrate how to build a simple blogging app. You'll see how effortless it is to build a fully secure application with Nuxt and ZenStack combined.
+
+You can find the final build result [here](https://github.com/zenstackhq/docs-tutorial-nuxt).
+
+## Requirements
+
+<Requirements />
+
+## Prerequisite
+
+<Prerequisite />
+
+## Building the app
+
+### 1. Creating a new project
+
+Although authentication is not the focus of the project, we need a basic signup/signin system to get going. The [Nuxt with Local Auth](https://github.com/nuxt/examples/tree/main/auth/local) sample can serve as a good starter.
+
+First, make a copy of the project using [degit](https://github.com/Rich-Harris/degit), install dependencies, and start the dev server:
+
+```bash
+npx degit nuxt/examples/examples/auth/local my-nuxt-blog
+cd my-nuxt-blog
+npm install
+npm run dev
+```
+
+If everything works, you should have a running Nuxt app at [http://localhost:3000](http://localhost:3000).
+
+<div align="center">
+    <img src="/img/get-started/nuxt/initial.png" width="800" />
+</div>
+
+### 2. Initialize the project for ZenStack
+
+Let's run the `zenstack` CLI to prepare your project for using ZenStack. The CLI will also install Prisma dependencies too.
+
+```bash
+npx zenstack@1 init
+```
+
+<InitTips />
+
+The CLI generates a template `schema.zmodel` file containing data schema and access policies, which almost already satisfies our requirements. We only need to make some small tweaks to it:
+
+1. Add a `name` field to the `User` model
+1. Make the `User` model readable by all users
+
+```diff title='/schema.zmodel'
+model User {
+    id String @id @default(cuid())
+    email String @unique @email
++   name String?
+    password String @password @omit @length(8, 16)
+    posts Post[]
+
+    // everybody can signup
+    @@allow('create', true)
+
++   // user profiles are public
++   @@allow('read', true)
+
+    // full access by self
+    @@allow('all', auth() == this)
+}
+```
+
+Last, run `zenstack` CLI to regenerate Prisma schema, and sync it to the database:
+
+```bash
+npx zenstack generate
+npx prisma db push
+```
+
+### 3. Implementing authentication
+
+The sample project already contains the basic structure for authentication APIs and UI, however it's not connected to the database yet. Let's do that now.
+
+First, create a `/server/prisma.ts` file to create and export a PrismaClient instance:
+
+```ts title='/server/prisma.ts'
+import { PrismaClient } from '@prisma/client';
+export const prisma = new PrismaClient();
+```
+
+Then, replace the content of `/auth/server/utils/db.ts` with the following:
+
+```ts title='/auth/server/utils/db.ts'
+import type { User } from '@prisma/client';
+import { prisma } from '~/server/prisma';
+
+export async function findUserByEmail(email: string) {
+    return prisma.user.findUnique({ where: { email } });
+}
+
+export async function createUser(user: Omit<User, 'id'>) {
+    return prisma.user.create({
+        data: user,
+    });
+}
+```
+
+Now when user signs up or logs in, Prisma is used to create and retrive user data from the database.
+
+Finally, restart the dev server, and you should be able to create user account and login now.
+
+<div align="center">
+    <img src="/img/get-started/nuxt/signup.png" width="800" />
+</div>
+
+### 4. Mount CRUD service & generate hooks
+
+Let's take a review of the `Post` model in `schema.zmodel`:
+
+```zmodel title='/schema.zmodel'
+model Post {
+    id String @id @default(cuid())
+    createdAt DateTime @default(now())
+    updatedAt DateTime @updatedAt
+    title String @length(1, 256)
+    content String
+    published Boolean @default(false)
+    author User @relation(fields: [authorId], references: [id])
+    authorId String
+
+    // allow read for all signin users
+    @@allow('read', auth() != null && published)
+
+    // full access by author
+    @@allow('all', author == auth())
+}
+```
+
+Notice how the access policies are defined in the schema to control who has what access to the data? To enforce these policies, we'll need to create an "enhanced" PrismaClient, like:
+
+```ts
+import { enhance } from '@zenstackhq/runtime';
+const db = enhance(prisma, { user: ... });
+```
+
+All CRUD calls made via the enhanced client will be subject to the access policies. Since the ORM is already secured, ZenStack can automatically mount a set of CRUD APIs onto your Nuxt app. To achieve it, first install an extra server adapter package:
+
+```bash
+npm install @zenstackhq/server
+```
+
+Then, create a `/server/api/model/[...].ts` file with the following content:
+
+```ts title='/server/api/model/[...].ts'
+import { enhance } from '@zenstackhq/runtime';
+import { createEventHandler } from '@zenstackhq/server/nuxt';
+import { prisma } from '~/server/prisma';
+
+export default createEventHandler({
+    getPrisma: async (event) => {
+        const session = await useAuthSession(event);
+        return enhance(prisma, {
+            user: session.data.id ? { id: session.data.id } : undefined,
+        });
+    },
+});
+```
+
+:::info
+The "[...].ts" file name convention creates a "catch all" event handler that handles all requests sent to the `/api/model` endpoint and its descendants.
+:::
+
+The CRUD APIs for all your models are now mounted to the `/api/model` endpoint.
+
+# 5. Generating client hooks
+
+We can write frontend code to consume the CRUD APIs, but that would be quite boring. Instead, let's use a plugin to generate them for us. First, install the `@zenstackhq/tanstack-query` plugin package:
+
+```bash
+npm install @tanstack/vue-query --force
+npm install -D @zenstackhq/tanstack-query
+```
+
+:::info
+The `--force` flag is required due to a misconfigured dependency in `@tanstack/vue-query`.
+:::
+
+Then, add the following snippet to `schema.zmodel`:
+
+```zmodel title='/schema.zmodel'
+plugin hooks {
+    provider = '@zenstackhq/tanstack-query'
+    target = 'vue'
+    output = 'lib/hooks'
+}
+```
+
+Finally, rerun `zenstack` CLI to trigger the generation:
+
+```bash
+npx zenstack generate
+```
+
+The TanStack Query hooks will be generated under `/lib/hooks` and ready to be used in our frontend code.
+
+# 6. Building the home page
+
+With all the preparation work done, we can build the page for creating and listing posts now!
+
+First, enable "@tanstack/vue-query" in the app by creating a plugin under `/plugins/vue-query.ts`:
+
+```ts title='/plugins/vue-query.ts'
+import { VueQueryPlugin, QueryClient } from '@tanstack/vue-query';
+
+export default defineNuxtPlugin((nuxt) => {
+    const queryClient = new QueryClient();
+    nuxt.vueApp.use(VueQueryPlugin, { queryClient });
+});
+```
+
+Also, add the following `<script setup>` code to `/app.vue` to configure the API endpoint used by the query hooks:
+```html title='/app.vue'
+<script setup lang="ts">
+import { provideHooksContext } from './lib/hooks';
+
+// Provide tanstack-query context
+// Use an absolute endpoint so server-side fetch works too
+provideHooksContext({
+    endpoint: 'http://localhost:3000/api/model',
+});
+</script>
+```
+
+Then, create a `Post` component under `/components/Post.vue` for rendering a single post:
+
+```html title='/components/Post.vue'
+<script setup lang="ts">
+import { useDeletePost, useUpdatePost } from '~/lib/hooks';
+
+const props = defineProps({
+    id: String,
+    title: String,
+    author: {
+        type: Object,
+        default: () => ({ email: '' }),
+    },
+    published: Boolean,
+});
+
+const updatePost = useUpdatePost();
+const deletePost = useDeletePost();
+
+const onTogglePublish = async () => {
+    try {
+        await updatePost.mutateAsync({
+            where: { id: props.id },
+            data: { published: !props.published },
+        });
+    } catch (err: any) {
+        alert(err.info?.message ?? err);
+    }
+};
+
+const onDelete = async () => {
+    try {
+        await deletePost.mutateAsync({ where: { id: props.id } });
+    } catch (err: any) {
+        alert(err.info?.message ?? err);
+    }
+};
+</script>
+
+<template>
+    <div class="flex justify-center">
+        <div class="min-w-80">
+            <span class="mr-4 text-lg font-semibold">{{ title }}</span
+            ><span>by {{ author.email }}</span>
+        </div>
+        <div class="ml-8 space-x-2">
+            <NButton @click="onTogglePublish">{{
+                published ? 'Unpublish' : 'Publish'
+            }}</NButton>
+            <NButton @click="onDelete">Delete</NButton>
+        </div>
+    </div>
+</template>
+```
+
+Finally, update the content of `/pages/index.vue` to the following:
+
+```html title='/pages/index.vue'
+<script setup lang="ts">
+import { useFindManyPost, useCreatePost } from '~/lib/hooks';
+
+const title = ref('');
+const createPost = useCreatePost();
+const { session } = useAuth();
+
+const onCreatePost = async () => {
+    try {
+        await createPost.mutateAsync({
+            data: {
+                author: { connect: { id: session.value!.id } },
+                title: title.value,
+                content: 'My awesome content',
+            },
+        });
+        title.value = '';
+    } catch (err: any) {
+        alert(err.info?.message ?? err);
+    }
+};
+const { data: posts } = useFindManyPost({ include: { author: true } });
+</script>
+
+<template>
+    <h1 class="text-2xl font-bold my-4">My Awesome Blog</h1>
+    <div v-if="session?.id">
+        <div class="flex justify-center">
+            <NTextInput
+                v-model="title"
+                placeholder="Title"
+                class="w-1/2 mr-4"
+                @keyup.enter="onCreatePost"
+            />
+            <NButton @click="onCreatePost">Create Post</NButton>
+        </div>
+        <ul v-if="posts" class="py-8 space-y-2">
+            <li v-for="post in posts" :key="post.id">
+                <Post v-bind="post" />
+            </li>
+        </ul>
+    </div>
+</template>
+```
+
+### 5. Testing the app
+
+Now you can create and manage posts in the home page.
+
+<div align="center">
+    <img src="/img/get-started/nuxt/posts.png" width="800" />
+</div>
+
+Try signing up another account in an incognito browser tab and publish a post. That post should be visible to the previous account too. Our policy rules are at work.
+
+You may have noticed that a user can publish, unpublish, or delete other user's posts from the UI. That's because we haven't implemented any conditional rendering logic yet. However, if you try to do that, the server side will reject the request and keep the data safe. In a real application, you'll want to capture the error and show a proper message to the user.
+
+<div align="center">
+    <img src="/img/get-started/nuxt/rejected.png" width="400" />
+</div>
+
+## Wrap up
+
+🎉 Congratulations! You've made a simple blogging app without writing a single backend API. Pretty cool, isn't it?
+
+If you have trouble following the building process, you can find the final result [here](https://github.com/zenstackhq/docs-tutorial-nuxt/tree/v1). For more details about ZenStack, please refer to the [Reference](../category/reference) and [Guides](../category/recipes) parts of the documentation.
+
+Have fun building cool stuff 🚀!
diff --git a/versioned_docs/version-1.x/quick-start/remix-run.mdx b/versioned_docs/version-1.x/quick-start/remix-run.mdx
new file mode 100644
index 00000000..5f21cbb8
--- /dev/null
+++ b/versioned_docs/version-1.x/quick-start/remix-run.mdx
@@ -0,0 +1,529 @@
+---
+title: Remix.run
+description: Step-by-step guide for building a blogging app with Remix.run.
+sidebar_position: 5
+---
+
+import Requirements from './_requirements.md';
+import Prerequisite from './_prerequisite.md';
+import InitTips from './_zenstack-init-tips.md';
+import AccessPolicy from './_access-policy.md';
+
+# Get Started With Remix.run
+
+Remix is an excellent framework that allows you to easily collocate data loading and mutation server-side logic with your client-side code. Let's have some fun by creating a simple blogging app. You'll see how effortless it is to build a fully secure application with Remix and ZenStack combined.
+
+You can find the final build result [here](https://github.com/zenstackhq/docs-tutorial-remix).
+
+## Requirements
+
+<Requirements />
+
+## Prerequisite
+
+<Prerequisite />
+
+## Building the app
+
+### 1. Create a new project
+
+The easiest way to create a Remix project with boilerplates is with `create-remix`. Run the following command to create a new project. When prompted, choose the following:
+
+-   Typescript or Javascript? **Typescript**
+-   Do you want me to run `npm install`? **Yes**
+
+```bash
+npx create-remix@latest --template remix-run/indie-stack my-blog-app
+cd my-blog-app
+npm run dev
+```
+
+If everything works, you should have a running Remix app at [http://localhost:3000](http://localhost:3000).
+
+<div align="center">
+    <img src="/img/remix-start.png" width="800" />
+</div>
+
+Now sign up for a new account to continue.
+
+### 2. Initialize the project for ZenStack
+
+Let's run the `zenstack` CLI to prepare your project for using ZenStack.
+
+```bash
+npx zenstack@1 init
+```
+
+<InitTips />
+
+### 3. Prepare the Blog model
+
+The template project already contains a `Note` model. Let's repurpose it to be a `Post` model. Rename it from `Note` to `Post`, and add a `published` field to store if the post is published or not. Also add access control rules with `@@allow` attribute to authorize requests.
+
+```zmodel title='/schema.zmodel'
+// highlight-next-line
+model Post {
+  id String @id @default(cuid())
+  title String
+  body String
+  // highlight-next-line
+  published Boolean @default(false)
+  createdAt DateTime @default(now())
+  updatedAt DateTime @updatedAt
+  user User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade)
+  userId String
+
+  // highlight-start
+  // author has full access
+  @@allow('all', auth() == user)
+
+  // logged-in users can view published posts
+  @@allow('read', auth() != null && published)
+  // highlight-end
+}
+```
+
+<AccessPolicy />
+
+Also we need to rename the relation in the `User` model from `notes` to `posts`.
+
+```zmodel title='/schema.zmodel'
+model User {
+  id String @id @default(cuid())
+  email String @unique
+  createdAt DateTime @default(now())
+  updatedAt DateTime @updatedAt
+  password Password?
+  // highlight-next-line
+  posts Post[]
+}
+```
+
+Now let's regenerate `schema.prisma` and push schema changes to the database:
+
+```bash
+npx zenstack generate && npx prisma db push
+```
+
+### 4. Clean up unused files
+
+Remove a few files and folders that are not needed anymore:
+
+-   /app/models/note.server.ts
+-   /app/routes/note.tsx
+-   /app/routes/notes
+
+### 5. Build up the blog homepage
+
+First add a helper method to `/app/db.server.ts` to get an access-policy-enabled prisma client. We'll use this wrapper to manipulate the `Post` model.
+
+```ts title='/app/db.server.ts'
+import { enhance } from '@zenstackhq/runtime';
+
+export function getEnhancedPrisma(userId: string) {
+    return enhance(prisma, { user: { id: userId } });
+}
+```
+
+Create `/app/models/post.server.ts` with the following content:
+
+```ts
+import type { User } from '@prisma/client';
+import { getEnhancedPrisma } from '~/db.server';
+
+export function getPosts({ userId }: { userId: User['id'] }) {
+    return getEnhancedPrisma(userId).post.findMany({
+        orderBy: { updatedAt: 'desc' },
+    });
+}
+```
+
+Notice that we don't need to filter the posts by user id? Still at runtime, only posts belonging to the current user will be returned. This is because of the access policy we previously defined in the `Post` model.
+
+Let's create a new page at `/app/routes/posts.tsx` for listing and managing our blog posts.
+
+```tsx title='/app/routes/posts.tsx'
+import type { LoaderArgs } from '@remix-run/node';
+import { json } from '@remix-run/node';
+import { Form, Link, NavLink, Outlet, useLoaderData } from '@remix-run/react';
+import { getPosts } from '~/models/post.server';
+import { requireUserId } from '~/session.server';
+import { useUser } from '~/utils';
+
+export async function loader({ request }: LoaderArgs) {
+    const userId = await requireUserId(request);
+    const posts = await getPosts({ userId });
+    return json({ posts });
+}
+
+export default function PostsPage() {
+    const data = useLoaderData<typeof loader>();
+    const user = useUser();
+
+    return (
+        <div className="flex h-full min-h-screen flex-col">
+            <header className="flex items-center justify-between bg-slate-800 p-4 text-white">
+                <h1 className="text-3xl font-bold">
+                    <Link to=".">Posts</Link>
+                </h1>
+                <p>{user.email}</p>
+                <Form action="/logout" method="post">
+                    <button
+                        type="submit"
+                        className="rounded bg-slate-600 py-2 px-4 text-blue-100 hover:bg-blue-500 active:bg-blue-600"
+                    >
+                        Logout
+                    </button>
+                </Form>
+            </header>
+
+            <main className="flex h-full bg-white">
+                <div className="h-full w-80 border-r bg-gray-50">
+                    <Link to="new" className="block p-4 text-xl text-blue-500">
+                        + New Post
+                    </Link>
+
+                    <hr />
+
+                    {data.posts.length === 0 ? (
+                        <p className="p-4">No posts yet</p>
+                    ) : (
+                        <ol>
+                            {data.posts.map((post) => (
+                                <li key={post.id}>
+                                    <NavLink
+                                        className={({ isActive }) =>
+                                            `block border-b p-4 text-xl ${isActive ? 'bg-white' : ''}`
+                                        }
+                                        to={post.id}
+                                    >
+                                        📝 {post.title}
+                                    </NavLink>
+                                </li>
+                            ))}
+                        </ol>
+                    )}
+                </div>
+
+                <div className="flex-1 p-6">
+                    <Outlet />
+                </div>
+            </main>
+        </div>
+    );
+}
+```
+
+Restart your dev server and hit [http://localhost:3000/posts](http://localhost:3000/posts), you should see something like this:
+
+<div align="center">
+    <img src="/img/remix-posts.png" width="800" />
+</div>
+
+### 6. Build up the create post page
+
+First add a function to `/app/models/post.server.ts` for creating a new post.
+
+```ts title='/app/models/post.server.ts'
+import type { Post } from '@prisma/client';
+
+export function createPost({ body, title, userId }: Post & { userId: User['id'] }) {
+    return getEnhancedPrisma(userId).post.create({
+        data: {
+            title,
+            body,
+            user: {
+                connect: {
+                    id: userId,
+                },
+            },
+        },
+    });
+}
+```
+
+Add `/app/routes/posts/new.tsx` with the following content:
+
+```tsx title='/app/routes/posts/new.tsx'
+import type { ActionArgs } from '@remix-run/node';
+import { json, redirect } from '@remix-run/node';
+import { Form, useActionData } from '@remix-run/react';
+import * as React from 'react';
+import { createPost } from '~/models/post.server';
+
+import { requireUserId } from '~/session.server';
+
+export async function action({ request }: ActionArgs) {
+    const userId = await requireUserId(request);
+
+    const formData = await request.formData();
+    const title = formData.get('title');
+    const body = formData.get('body');
+
+    if (typeof title !== 'string' || title.length === 0) {
+        return json({ errors: { title: 'Title is required', body: null } }, { status: 400 });
+    }
+
+    if (typeof body !== 'string' || body.length === 0) {
+        return json({ errors: { title: null, body: 'Body is required' } }, { status: 400 });
+    }
+
+    const post = await createPost({ title, body, userId });
+
+    return redirect(`/posts/${post.id}`);
+}
+
+export default function NewPostPage() {
+    const actionData = useActionData<typeof action>();
+    const titleRef = React.useRef<HTMLInputElement>(null);
+    const bodyRef = React.useRef<HTMLTextAreaElement>(null);
+
+    React.useEffect(() => {
+        if (actionData?.errors?.title) {
+            titleRef.current?.focus();
+        } else if (actionData?.errors?.body) {
+            bodyRef.current?.focus();
+        }
+    }, [actionData]);
+
+    return (
+        <Form
+            method="post"
+            style={{
+                display: 'flex',
+                flexDirection: 'column',
+                gap: 8,
+                width: '100%',
+            }}
+        >
+            <div>
+                <label className="flex w-full flex-col gap-1">
+                    <span>Title: </span>
+                    <input
+                        ref={titleRef}
+                        name="title"
+                        className="flex-1 rounded-md border-2 border-blue-500 px-3 text-lg leading-loose"
+                        aria-invalid={actionData?.errors?.title ? true : undefined}
+                        aria-errormessage={actionData?.errors?.title ? 'title-error' : undefined}
+                    />
+                </label>
+                {actionData?.errors?.title && (
+                    <div className="pt-1 text-red-700" id="title-error">
+                        {actionData.errors.title}
+                    </div>
+                )}
+            </div>
+
+            <div>
+                <label className="flex w-full flex-col gap-1">
+                    <span>Body: </span>
+                    <textarea
+                        ref={bodyRef}
+                        name="body"
+                        rows={8}
+                        className="w-full flex-1 rounded-md border-2 border-blue-500 py-2 px-3 text-lg leading-6"
+                        aria-invalid={actionData?.errors?.body ? true : undefined}
+                        aria-errormessage={actionData?.errors?.body ? 'body-error' : undefined}
+                    />
+                </label>
+                {actionData?.errors?.body && (
+                    <div className="pt-1 text-red-700" id="body-error">
+                        {actionData.errors.body}
+                    </div>
+                )}
+            </div>
+
+            <div className="text-right">
+                <button
+                    type="submit"
+                    className="rounded bg-blue-500 py-2 px-4 text-white hover:bg-blue-600 focus:bg-blue-400"
+                >
+                    Save
+                </button>
+            </div>
+        </Form>
+    );
+}
+```
+
+### 7. Build up the post details page
+
+First add a few functions to `/app/models/post.server.ts` for manipulating a single post.
+
+```ts title='/app/models/post.server.ts'
+export function getPost({
+    id,
+    userId,
+}: Pick<Post, 'id'> & {
+    userId: User['id'];
+}) {
+    return getEnhancedPrisma(userId).post.findUnique({
+        where: { id },
+    });
+}
+
+export function deletePost({ id, userId }: Pick<Post, 'id'> & { userId: User['id'] }) {
+    return getEnhancedPrisma(userId).post.delete({
+        where: { id },
+    });
+}
+
+export function publishPost({ id, userId }: Pick<Post, 'id'> & { userId: User['id'] }) {
+    return getEnhancedPrisma(userId).post.update({
+        where: { id },
+        data: { published: true },
+    });
+}
+
+export function unpublishPost({ id, userId }: Pick<Post, 'id'> & { userId: User['id'] }) {
+    return getEnhancedPrisma(userId).post.update({
+        where: { id },
+        data: { published: false },
+    });
+}
+```
+
+Create `/app/routes/posts/$postId.tsx` with the following content:
+
+```tsx
+import type { ActionArgs, LoaderArgs } from '@remix-run/node';
+import { json, redirect } from '@remix-run/node';
+import { Form, useCatch, useLoaderData } from '@remix-run/react';
+import invariant from 'tiny-invariant';
+import { deletePost, getPost, publishPost, unpublishPost } from '~/models/post.server';
+
+import { requireUserId } from '~/session.server';
+
+export async function loader({ request, params }: LoaderArgs) {
+    const userId = await requireUserId(request);
+    invariant(params.postId, 'await not found');
+
+    const post = await getPost({ userId, id: params.postId });
+    if (!post) {
+        throw new Response('Not Found', { status: 404 });
+    }
+    return json({ post });
+}
+
+export async function action({ request, params }: ActionArgs) {
+    const userId = await requireUserId(request);
+    invariant(params.postId, 'postId not found');
+
+    const intent = (await request.formData()).get('intent');
+    switch (intent) {
+        case 'delete':
+            await deletePost({ userId, id: params.postId });
+            return redirect('/posts');
+        case 'publish':
+            await publishPost({ userId, id: params.postId });
+            return null;
+        case 'unpublish':
+            await unpublishPost({ userId, id: params.postId });
+            return null;
+    }
+}
+
+export default function PostDetailsPage() {
+    const data = useLoaderData<typeof loader>();
+
+    return (
+        <div>
+            <h3 className="text-2xl font-bold">
+                {data.post.title} {!data.post.published && <span className="text-base font-normal italic">Draft</span>}
+            </h3>
+            <p className="py-6">{data.post.body}</p>
+            <hr className="my-4" />
+            <div className="flex gap-2">
+                <Form method="post">
+                    <input type="hidden" name="intent" value="delete" />
+                    <button
+                        type="submit"
+                        className="rounded bg-blue-500  py-2 px-4 text-white hover:bg-blue-600 focus:bg-blue-400"
+                    >
+                        Delete
+                    </button>
+                </Form>
+                <Form method="post">
+                    <input type="hidden" name="intent" value={data.post.published ? 'unpublish' : 'publish'} />
+                    <button
+                        type="submit"
+                        className="rounded bg-blue-500  py-2 px-4 text-white hover:bg-blue-600 focus:bg-blue-400"
+                    >
+                        {data.post.published ? 'Unpublish' : 'Publish'}
+                    </button>
+                </Form>
+            </div>
+        </div>
+    );
+}
+
+export function ErrorBoundary({ error }: { error: Error }) {
+    console.error(error);
+
+    return <div>An unexpected error occurred: {error.message}</div>;
+}
+
+export function CatchBoundary() {
+    const caught = useCatch();
+
+    if (caught.status === 404) {
+        return <div>Post not found</div>;
+    }
+
+    throw new Error(`Unexpected caught response with status: ${caught.status}`);
+}
+```
+
+Then, add `/app/routes/posts/index.tsx` to show default content when no post is selected:
+
+```tsx title='/app/routes/posts/index.tsx'
+import { Link } from '@remix-run/react';
+
+export default function PostIndexPage() {
+    return (
+        <p>
+            No post selected. Select a post on the left, or{' '}
+            <Link to="new" className="text-blue-500 underline">
+                create a new post.
+            </Link>
+        </p>
+    );
+}
+```
+
+Finally, update `/app/routes/index.tsx` to change the main link to our posts page:
+
+```tsx title='/app/routes/index.tsx'
+<Link
+    to="/posts"
+    className="flex items-center justify-center rounded-md border border-transparent bg-white px-4 py-3 text-base font-medium text-yellow-700 shadow-sm hover:bg-yellow-50 sm:px-8"
+>
+    View Posts for {user.email}
+</Link>
+```
+
+### 8. Test it out!
+
+🎉 Congratulations! You've made a simple blogging app without manually implementing any access control logic. Now test it out and see if you can create posts and manage them.
+
+<div align="center">
+    <img src="/img/remix-test-it.png" width="800" />
+</div>
+
+Since we haven't hidden "Unpublish" and "Delete" buttons for posts not owned by the current
+user, you can still click them even for posts not owned to you, but it will end up with an error. Even if you forget to block certain actions from the frontend, our access control logic will still prevent them from happening.
+
+<div align="center">
+    <img src="/img/remix-forbidden.png" width="800" />
+</div>
+
+You can catch the error and render a nice message to the user.
+
+## Wrap up
+
+If you have trouble following the building process, you can find the final result
+[here](https://github.com/zenstackhq/docs-tutorial-remix/tree/v1). For more details about ZenStack,
+please refer to the [Reference](../category/reference) and [Guides](../category/recipes)
+parts of the documentation.
+
+Have fun building cool stuff 🚀!
diff --git a/versioned_docs/version-1.x/quick-start/sveltekit.mdx b/versioned_docs/version-1.x/quick-start/sveltekit.mdx
new file mode 100644
index 00000000..ca717828
--- /dev/null
+++ b/versioned_docs/version-1.x/quick-start/sveltekit.mdx
@@ -0,0 +1,624 @@
+---
+title: SvelteKit
+description: Step-by-step guide for building a blogging app with SvelteKit.
+sidebar_position: 4
+---
+
+import Requirements from './_requirements.md';
+import Prerequisite from './_prerequisite.md';
+import InitTips from './_zenstack-init-tips.md';
+import PasswordOmit from './_password-omit.md';
+import AccessPolicy from './_access-policy.md';
+
+# Get Started With SvelteKit
+
+Let's have some fun by creating a simple blogging app. You'll see how effortless it is to have a secure backend service without actually coding it.
+
+You can find the final build result [here](https://github.com/zenstackhq/docs-tutorial-sveltekit).
+
+## Requirements
+
+<Requirements />
+
+## Prerequisite
+
+<Prerequisite />
+
+## Building the app
+
+### 1. Create a new project
+
+The easiest way to create a SvelteKit project with boilerplates is with `npm create svelte`. Run the following command to create a new project:
+
+```bash
+npm create svelte@latest my-blog-app
+```
+
+Choose the following options when prompted:
+
+- Which Svelte app template?
+
+    Skeleton project
+
+- Add type checking with TypeScript?
+
+    Yes, using TypeScript syntax
+
+- Select additional options (use arrow keys/space bar)
+
+    Add ESLint for code linting, Add Prettier for code formatting
+
+### 2. Configure TailwindCSS
+
+Follow the guide [here](https://tailwindcss.com/docs/guides/sveltekit) to add TailwindCSS to your project.
+
+### 3. Initialize the project for ZenStack
+
+Let's run the `zenstack` CLI to prepare your project for using ZenStack. Inside your project directory, run the following command:
+
+```bash
+npx zenstack@1 init
+```
+
+<InitTips />
+
+Replace the `User` and `Post` models in the `/schema.zmodel` file with the following content:
+
+```zmodel title='/schema.zmodel'
+model User {
+    id String @id @default(cuid())
+    email String @unique @email
+    password String @password @omit @length(8, 16)
+    posts Post[]
+
+    // everybody can signup
+    @@allow('create,read', true)
+
+    // full access by self
+    @@allow('all', auth() == this)
+}
+
+model Post {
+    id String @id @default(cuid())
+    createdAt DateTime @default(now())
+    updatedAt DateTime @updatedAt
+    title String @length(1, 256)
+    published Boolean @default(false)
+    author User? @relation(fields: [authorId], references: [id])
+    authorId String?
+
+    // allow read for all signin users
+    @@allow('read', auth() != null && published)
+
+    // full access by author
+    @@allow('all', author == auth())
+}
+```
+
+<PasswordOmit />
+
+<AccessPolicy />
+
+Now run `zenstack generate` and `prisma db push` to flush the changes to the Prisma schema and database:
+
+```bash
+npx zenstack generate && npx prisma db push
+```
+
+### 4. Install a few extra dependencies
+
+```bash
+npm add jsonwebtoken bcryptjs
+npm add -D @types/jsonwebtoken @types/bcryptjs
+```
+
+### 5. Set up environment variables
+
+Create a ".env" file under the root directory of your project and add the following content:
+
+```bash
+JWT_SECRET=abc123
+```
+
+:::info
+In a real-world application, you should use a strong secret.
+:::
+
+### 6. Create database and auth utilities
+
+Create a `/src/lib/db.ts` file with the following content:
+
+```ts title='/src/lib/db.ts'
+import { PrismaClient } from '@prisma/client';
+export const prisma = new PrismaClient();
+```
+
+Create a `/src/lib/auth.ts` file with the following content:
+
+```ts title='/src/lib/auth.ts'
+import { env } from '$env/dynamic/private';
+import type { User } from '@prisma/client';
+import jwt from 'jsonwebtoken';
+
+export const JWT_TOKEN_COOKIE_NAME = 'ZenStack-Blog-Token';
+
+export function createToken(user: User) {
+	return jwt.sign(
+		{
+			sub: user.id,
+			email: user.email
+		},
+		env.JWT_SECRET,
+		{ expiresIn: '7d' }
+	);
+}
+```
+
+### 7. Create a signup page
+
+:::info
+For real-world applications you should usually use a framework or service instead of building your own authentication solution. But since authentication is not the focus of this guide, we'll just implement a minimal email/password-based auth.
+:::
+
+Create a `/src/routes/signup/+page.svelte` file with the following content:
+
+```svelte title='/src/routes/signup/+page.svelte'
+<script lang="ts">
+    import { enhance } from '$app/forms';
+    import type { ActionData } from './$types';
+    export let form: ActionData;
+</script>
+
+<div class="flex min-h-screen flex-col items-center justify-center bg-gradient-to-b from-[#2e026d] to-[#15162c]"
+>
+    <h1 class="text-5xl font-extrabold text-white">Sign up</h1>
+    <form class="mt-16 flex flex-col gap-8 text-2xl" method="post" use:enhance>
+        {#if form?.dup}
+            <p class="text-red-600 my-2">Email aready registered!</p>
+        {/if}
+        <div>
+            <label for="email" class="inline-block w-32 text-white"> Email </label>
+            <input
+                id="email"
+                name="email"
+                type="email"
+                value={form?.email ?? ''}
+                class="ml-4 w-72 rounded border p-2"
+            />
+        </div>
+        <div>
+            <label for="password" class="inline-block w-32 text-white"> Password </label>
+            <input id="password" name="password" type="password" class="ml-4 w-72 rounded border p-2" />
+        </div>
+        <input
+            type="submit"
+            value="Create account"
+            class="cursor-pointer rounded border border-gray-500 py-4 text-white"
+        />
+        <div class="mt-2 text-base font-medium text-gray-300">
+            Already have an account?{' '}
+            <a href="/signin" class="text-primary-700 underline"> Login here </a>
+        </div>
+    </form>
+</div>
+```
+
+Then create its server-side form handler `/src/routes/signup/+page.server.ts` with the following content:
+
+```ts title='/src/routes/signup/+page.server.ts'
+import { createToken, JWT_TOKEN_COOKIE_NAME } from '$lib/auth';
+import { fail, redirect, type Actions } from '@sveltejs/kit';
+import { isPrismaClientKnownRequestError, enhance } from '@zenstackhq/runtime';
+import { prisma } from '../../lib/db';
+
+export const actions = {
+    default: async ({ request, cookies }) => {
+        const data = await request.formData();
+        const email = data.get('email');
+        const password = data.get('password');
+
+        if (typeof email !== 'string' || typeof password !== 'string') {
+            return fail(400, { email, password, missing: true });
+        }
+
+        const db = enhance(prisma);
+
+        try {
+            // create the user together with a default space
+            const user = await db.user.create({
+                data: {
+                    email,
+                    password
+                }
+            });
+
+            // sign a JWT token and set it as a cookie
+            const token = createToken(user);
+            cookies.set(JWT_TOKEN_COOKIE_NAME, token, {
+                httpOnly: true,
+                expires: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
+                path: '/'
+            });
+        } catch (err) {
+            if (isPrismaClientKnownRequestError(err) && err.code === 'P2002') {
+                // duplicated email
+                return fail(400, { email, password, dup: true });
+            } else {
+                throw err;
+            }
+        }
+
+        throw redirect(303, `/`);
+    }
+} satisfies Actions;
+```
+
+Now start the dev server by running:
+```bash
+npm run dev
+```
+
+Hit [http://localhost:5173/signup](http://localhost:5173/signup) and you should see the signup page like:
+
+<div align="center">
+    <img src="/img/get-started/sveltekit/signup.png" width="800" />
+</div>
+
+Create an account to continue.
+
+### 8. Create a signin page
+
+Create a `/src/routes/signin/+page.svelte` file with the following content:
+
+```svelte title='/src/routes/signin/+page.svelte'
+<script lang="ts">
+	import { enhance } from '$app/forms';
+	import type { ActionData } from '../signin/$types';
+
+	export let form: ActionData;
+</script>
+
+<div class="flex min-h-screen flex-col items-center justify-center bg-gradient-to-b from-[#2e026d] to-[#15162c]"
+>
+    <h1 class="text-5xl font-extrabold text-white">Login</h1>
+    <form class="mt-16 flex flex-col gap-8 text-2xl" method="post" use:enhance>
+        {#if form?.invalid}
+            <p class="text-red-600 my-2">Invalid credentials</p>
+        {/if}
+        <div>
+            <label for="email" class="inline-block w-32 text-white"> Email </label>
+            <input
+                id="email"
+                name="email"
+                type="email"
+                value={form?.email ?? ''}
+                class="ml-4 w-72 rounded border p-2"
+            />
+        </div>
+        <div>
+            <label for="password" class="inline-block w-32 text-white"> Password </label>
+            <input id="password" name="password" type="password" class="ml-4 w-72 rounded border p-2" />
+        </div>
+        <input
+            type="submit"
+            value="Sign in to my account"
+            class="cursor-pointer rounded border border-gray-500 py-4 text-white"
+        />
+        <div class="mt-2 text-base font-medium text-gray-300">
+            Don't have an account yet?{' '}
+            <a href="/signup" class="text-primary-700 underline"> Signup here </a>
+        </div>
+    </form>
+</div>
+```
+
+Then create its server-side form handler `/src/routes/signin/+page.server.ts` with the following content:
+
+```ts title='/src/routes/signin/+page.server.ts'
+import { JWT_TOKEN_COOKIE_NAME, createToken } from '$lib/auth';
+import { prisma } from '$lib/db';
+import { fail, redirect, type Actions } from '@sveltejs/kit';
+import bcrypt from 'bcryptjs';
+
+export const actions = {
+    default: async ({ request, cookies }) => {
+        const data = await request.formData();
+
+        const email = data.get('email');
+        const password = data.get('password');
+
+        if (typeof email !== 'string' || typeof password !== 'string') {
+            return fail(400, { email, password, missing: true });
+        }
+
+        const user = await prisma.user.findFirst({
+            where: { email }
+        });
+        if (!user || !bcrypt.compareSync(password, user.password)) {
+            return fail(401, { email, password, invalid: true });
+        }
+
+        const token = createToken(user);
+        cookies.set(JWT_TOKEN_COOKIE_NAME, token, {
+            httpOnly: true,
+            expires: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
+            path: '/'
+        });
+        throw redirect(303, `/`);
+    }
+} satisfies Actions;
+```
+
+Hit [http://localhost:5173/signin](http://localhost:5173/signin) and you should see the signin page like:
+
+<div align="center">
+    <img src="/img/get-started/sveltekit/signin.png" width="800" />
+</div>
+
+Try signing in with the account you just created.
+
+### 9. Create a server hook to add user and his corresponding db to context
+
+Use a [server hook](https://kit.svelte.dev/docs/hooks#server-hooks) to intercept requests, extract JWT token and decode it to user identity, create an access-policy-aware Prisma wrapper with the identity, and add both the user and db to the request context. When building the rest of pages in our app, we can fetch them directly from the `locals` context.
+
+First, update `/src/app.d.ts` to include `user` and `db` fields in the request context's typing:
+
+```ts title='/src/app.d.ts'
+import type { PrismaClient, User } from '@prisma/client';
+
+declare global {
+    namespace App {
+        interface Locals {
+            user?: User;
+            db: PrismaClient;
+        }
+    }
+}
+
+export {};
+```
+
+Then create server hooks `/src/hooks.server.ts`:
+
+```ts title='/src/hooks.server.ts'
+import { env } from '$env/dynamic/private';
+import { JWT_TOKEN_COOKIE_NAME } from '$lib/auth';
+import { prisma } from '$lib/db';
+import type { Handle } from '@sveltejs/kit';
+import { enhance } from '@zenstackhq/runtime';
+import jwt from 'jsonwebtoken';
+
+const auth = (async ({ event, resolve }) => {
+    const token = event.cookies.get(JWT_TOKEN_COOKIE_NAME);
+    if (token) {
+        try {
+            const decoded = jwt.verify(token, env.JWT_SECRET);
+            const user = await prisma.user.findUnique({
+                where: { id: decoded.sub as string }
+            });
+            if (user) {
+                event.locals.user = user;
+            } else {
+                console.warn('User not found:', decoded.sub);
+                event.cookies.delete(JWT_TOKEN_COOKIE_NAME, { path: '/' });
+            }
+        } catch {
+            event.cookies.delete(JWT_TOKEN_COOKIE_NAME, { path: '/' });
+        }
+    }
+
+    // create an enhanced PrismaClient that recognizes access policies
+    event.locals.db = enhance(prisma, {
+        user: event.locals.user ? { id: event.locals.user.id } : undefined
+    });
+
+    return resolve(event);
+}) satisfies Handle;
+
+export const handle = auth;
+```
+
+### 10. Build the post list page
+
+Finally we've got to build the main page of our app. For the sake of simplicity, we'll just put all UI directly in the page without further extracting components.
+
+Replace `/src/routes/+page.svelte` with the following content:
+
+```svelte title='/src/routes/+page.svelte'
+<script lang="ts">
+    import { enhance } from '$app/forms';
+    import type { ActionData, PageData } from './$types';
+
+    export let data: PageData;
+    export let form: ActionData;
+</script>
+
+<main class="flex flex-col min-h-screen items-center justify-center gap-12 px-4 py-16 text-white bg-gradient-to-b from-[#2e026d] to-[#15162c]"
+>
+    <h1 class="text-5xl font-extrabold">My Awesome Blog</h1>
+
+    {#if data.user}
+        <div class="flex flex-col">
+            <div class="flex gap-4">
+                <h3 class="text-lg">Welcome back, {data.user.email}</h3>
+                <form method="post" action="?/signOut">
+                    <button type="submit" class="text-gray-300 underline">Sign out</button>
+                </form>
+            </div>
+
+            <section class="mt-10 container flex flex-col text-white">
+                <!-- create post -->
+                <form method="post" action="?/create" use:enhance>
+                    <input
+                        name="title"
+                        type="text"
+                        placeholder="Post title"
+                        required
+                        class="text-lg text-black px-4 py-2 rounded mr-2"
+                    />
+                    <button class="rounded border border-white px-4 py-2 text-lg" type="submit"
+                        >+ Create</button
+                    >
+                </form>
+
+                <!-- post list -->
+                <ul class="container mt-8 flex flex-col gap-2">
+                    {#each data.posts as post (post.id)}
+                        <li class="flex items-end justify-between gap-4">
+                            <!-- post info -->
+                            <p class={`text-2xl ${!post.published ? 'text-gray-400' : ''}`}>
+                                {post.title}
+                                <span class="text-lg"> by {post.author.email}</span>
+                            </p>
+
+                            <!-- post management -->
+                            <div class="flex w-32 justify-end gap-1 text-left">
+                                <form method="post" action="?/togglePublish" use:enhance>
+                                    <input type="hidden" name="id" value={post.id} />
+                                    <button class="underline" type="submit">
+                                        {post.published ? 'Unpublish' : 'Publish'}
+                                    </button>
+                                </form>
+                                <form method="post" action="?/delete" use:enhance>
+                                    <input type="hidden" name="id" value={post.id} />
+                                    <button class="underline" type="submit">Delete</button>
+                                </form>
+                            </div>
+                        </li>
+                    {/each}
+                </ul>
+
+                {#if form?.error}
+                    <p class="text-red-500 mt-4">{form.error}</p>
+                {/if}
+            </section>
+        </div>
+    {:else}
+        <div class="flex gap-4 text-2xl">
+            <a href="/signin" class="rounded-lg border px-4 py-2">Signin</a>
+            <a href="/signup" class="rounded-lg border px-4 py-2">Signup</a>
+        </div>
+    {/if}
+</main>
+```
+
+Then create a `/src/routes/+page.server.ts` file to handle page data loading and form actions:
+
+```ts title='/src/routes/+page.server.ts'
+import type { PageServerLoad } from './$types';
+import { fail, type Actions, redirect } from '@sveltejs/kit';
+import { JWT_TOKEN_COOKIE_NAME } from '$lib/auth';
+
+export const load: PageServerLoad = async ({ locals }) => {
+    const posts = await locals.db.post.findMany({ include: { author: true } });
+    return {
+        user: locals.user,
+        posts
+    };
+};
+
+export const actions: Actions = {
+    create: async ({ request, locals }) => {
+        const data = await request.formData();
+        const title = data.get('title');
+        if (typeof title !== 'string') {
+            return fail(400, { missing: true });
+        }
+
+        await locals.db.post.create({
+            data: { title, author: { connect: { id: locals.user?.id } } }
+        });
+
+        return { success: true };
+    },
+
+    togglePublish: async ({ request, locals }) => {
+        const data = await request.formData();
+        const postId = data.get('id');
+        if (typeof postId !== 'string') {
+            return fail(400, { missing: true });
+        }
+
+        const post = await locals.db.post.findUnique({ where: { id: postId } });
+        if (!post) {
+            return fail(404, { notFound: true });
+        }
+
+        try {
+            await locals.db.post.update({ where: { id: postId }, data: { published: !post.published } });
+            return { success: true };
+        } catch (err) {
+            // error can be thrown for reasons like access policy violation
+            return { error: `${err}` };
+        }
+    },
+
+    delete: async ({ request, locals }) => {
+        const data = await request.formData();
+        const postId = data.get('id');
+        if (typeof postId !== 'string') {
+            return fail(400, { missing: true });
+        }
+
+        try {
+            await locals.db.post.delete({ where: { id: postId } });
+            return { success: true };
+        } catch (err) {
+            // error can be thrown for reasons like access policy violation
+            return { error: `${err}` };
+        }
+    },
+
+    signOut: async ({ cookies }) => {
+        cookies.delete(JWT_TOKEN_COOKIE_NAME);
+        throw redirect(303, '/signin');
+    }
+};
+```
+
+:::tip
+⚡️ Here's the important thing:
+
+As you can see, our server-side code doesn't contain any authorization logic. Yet our backend is still secure thanks to the access policies we defined in `schema.zmodel`. The enhanced Prisma client (fetched from `locals.db`) enforces that necessary filters and checks are injected when calling into the underlying Prisma client. Your ZModel is the **single source of truth** for access control.
+:::
+
+## Verifying the result
+
+Now our app is complete and let's verify the result. Create two posts with your current account, and publish one of them:
+
+<div align="center">
+    <img src="/img/get-started/sveltekit/yiming-posts.png" width="800" />
+</div>
+
+Now, open a new incognito window and sign up a new account _emily&#64;zenstack.dev_. "Post1" should appear in her list, since our access policy allows all users to read published posts.
+
+<div align="center">
+    <img src="/img/get-started/sveltekit/emily-posts.png" width="800" />
+</div>
+
+We didn't do any UI blocking to prevent Emily from unpublishing or deleting other people's posts; however, if she tries to do so, the operation will be rejected by the server-side code:
+
+<div align="center">
+    <img src="/img/get-started/sveltekit/update-error.png" width="800" />
+</div>
+
+In a real application you can catch the error and render a proper message to the user.
+
+## Wrap up
+
+🎉 Congratulations! You've made a simple but secure blogging app without writing any authorization logic. Pretty cool, isn't it?
+
+In this tutorial, we used ZenStack only on the SSR side (loader and actions) of SvelteKit, but its power doesn't stop there. There're two more things worth your attention that enable more advanced scenarios:
+
+- The [`@zenstackhq/server/sveltekit`](../reference/server-adapters/sveltekit) server adapter provides a SvelteKit hooks handler that installs an automatic CRUD API for database operations.
+
+- The [`@zenstackhq/tanstack-query`](../reference/plugins/tanstack-query) plugin provides automatic generation of client-side query and mutation hooks for consuming the afore-mentioned CRUD API.
+
+These two features combined allow you to build a typical SPA (CSR + API). You can also freely mix and match SSR and CSR as you wish.
+
+If you have trouble following the building process, you can find the final result
+[here](https://github.com/zenstackhq/docs-tutorial-sveltekit/tree/v1). For more details about ZenStack,
+please refer to the [Reference](../category/reference) and [Recipes](../category/recipes)
+parts of the documentation.
+
+Have fun building cool stuff 🚀!
diff --git a/versioned_docs/version-1.x/reference/_category_.yml b/versioned_docs/version-1.x/reference/_category_.yml
new file mode 100644
index 00000000..3e14b45e
--- /dev/null
+++ b/versioned_docs/version-1.x/reference/_category_.yml
@@ -0,0 +1,7 @@
+position: 6
+label: Reference
+collapsible: true
+collapsed: true
+link:
+    type: generated-index
+    title: Reference
diff --git a/versioned_docs/version-1.x/reference/cli.md b/versioned_docs/version-1.x/reference/cli.md
new file mode 100644
index 00000000..71eff80d
--- /dev/null
+++ b/versioned_docs/version-1.x/reference/cli.md
@@ -0,0 +1,242 @@
+---
+description: CLI references
+sidebar_position: 2
+sidebar_label: CLI
+---
+
+# ZenStack CLI Reference
+
+## Usage
+
+```
+zenstack [options] [command]
+
+ζ ZenStack is a Prisma power pack for building full-stack apps.
+
+Documentation: https://zenstack.dev.
+
+Options:
+  -v --version           display CLI version
+  -h, --help             display help for command
+
+Commands:
+  info [path]            Get information of installed ZenStack and related packages.
+  init [options] [path]  Initialize an existing project for ZenStack.
+  generate [options]     Generates RESTful API and Typescript client for your data model.
+  repl [options]         Start a REPL session.
+  format [options]       Format a ZenStack schema file.
+  help [command]         Display help for a command.
+```
+
+## Sub Commands
+
+### init
+
+Initializes an existing project to use ZenStack.
+
+```bash
+zenstack init [options] [path]
+```
+
+#### Arguments
+
+| Name | Description  | Default        |
+| ---- | ------------ | -------------- |
+| path | Project path | current folder |
+
+#### Options
+
+| Name                  | Description                                      | Default                                   |
+| --------------------- | ------------------------------------------------ | ----------------------------------------- |
+| --prisma              | location of Prisma schema file to bootstrap from | &lt;project path&gt;/prisma/schema.prisma |
+| -p, --package-manager | package manager to use: "npm", "yarn", or "pnpm" | auto detect                               |
+| --no-version-check    | do not check for new versions of ZenStack        | false                  |
+
+#### Examples
+
+Initialize current folder with default settings.
+
+```bash
+npx zenstack init
+```
+
+Initialize "myapp" folder with custom package manager and schema location.
+
+```bash
+npx zenstack init -p pnpm --prisma prisma/my.schema myapp
+```
+
+### generate
+
+Generates Prisma schema and other artifacts as specified by "plugin"s in ZModel.
+
+```bash
+zenstack generate [options]
+```
+
+#### Arguments
+
+| Name | Description  | Default        |
+| ---- | ------------ | -------------- |
+| path | Project path | current folder |
+
+#### Options
+
+| Name                  | Description                                      | Default                |
+| --------------------- | ------------------------------------------------ | ---------------------- |
+| --schema              | schema file (with extension .zmodel)             | ./schema.zmodel        |
+| -o, --output &lt;path&gt;   | default output directory for TS/JS files generated by built-in plugins   | node_modules/.zenstack |
+| --no-default-plugins  | do not automatically run built-in plugins        | false                  |
+| --no-compile          | do not compile the output of built-in plugins    | false                  |
+| --no-version-check    | do not check for new versions of ZenStack        | false                  |
+
+You can also specify the ZModel schema location in the "package.json" file of your project like the following:
+
+```json title="package.json"
+{
+  "zenstack": {
+    "schema": "./db/schema.zmodel"
+  }
+}
+```
+
+#### Examples
+
+Generate with default settings.
+
+```bash
+npx zenstack generate
+```
+
+Generate with custom schema location.
+
+```bash
+npx zenstack generate --schema src/my.zmodel
+```
+
+### repl
+
+Starts a REPL session. You should run the command inside the package where you ran `zenstack generate`.
+
+```bash
+npx zenstack repl
+```
+
+You can call PrismaClient methods interactively in the REPL session. The following variables are available in the REPL session.
+
+- `prisma`
+
+    The original PrismaClient instance (without ZenStack enhancement).
+
+- `db`
+
+    The ZenStack enhanced PrismaClient instance.
+
+You don't need to `await` the Prisma method call result. The REPL session will automatically await and print the result.
+
+#### Options
+
+| Name  | Description         | Default |
+| ----- | ------------------- | ------- |
+| --debug | Enable debug output. Can be toggled on the fly in the repl session with the ".debug" command. | false |
+| --table | Enable table format. Can be toggled on the fly in the repl session with the ".table" command. | false |
+| --prisma-client | Path to load PrismaClient module. | "./node_modules/.prisma/client" |
+
+#### Repl Commands
+
+You can use the following commands in the REPL session.
+
+- `.debug [on|off]`
+  
+    Toggle debug output.
+
+- `.table [on|off]`
+
+    Toggle table format.
+
+- `.auth [user object]`
+
+    Set current user. E.g.: `.auth { id: 1 }`. Run the command without argument to reset to anonymous user.
+
+#### Examples
+
+Start the session:
+```bash
+npx zenstack repl
+```
+
+Inside the session:
+```ts
+> prisma.user.findMany()
+[
+  {
+    id: '7aa301d2-7a29-4e1e-a041-822913a3ea78',
+    createdAt: 2023-09-05T04:04:43.793Z,
+    updatedAt: 2023-09-05T04:04:43.793Z,
+    email: 'yiming@whimslab.io',
+    ...
+  }
+]
+
+> .auth { id: '7aa301d2-7a29-4e1e-a041-822913a3ea78' }
+Auth user: { id: '7aa301d2-7a29-4e1e-a041-822913a3ea78' }. Use ".auth" to switch to anonymous.
+
+> .table
+Table output: true
+
+> db.list.findMany({select: { title: true, private: true}})
+┌─────────┬────────────────────────────────────────────────────┬─────────┐
+│ (index) │                       title                        │ private │
+├─────────┼────────────────────────────────────────────────────┼─────────┤
+│    0    │                        'L1'                        │  false  │
+│    1    │               'Wonderful new world'                │  false  │
+│    2    │ 'Model for a space in which users can collaborate' │  false  │
+└─────────┴────────────────────────────────────────────────────┴─────────┘
+```
+
+### format
+
+Format a ZenStack schema file.
+
+```bash
+zenstack format [options]
+```
+
+#### Options
+
+| Name                  | Description                                      | Default                |
+| --------------------- | ------------------------------------------------ | ---------------------- |
+| --schema              | schema file (with extension .zmodel)             | ./schema.zmodel        |
+
+You can also specify the ZModel schema location in the "package.json" file of your project like the following:
+
+```json title="package.json"
+{
+  "zenstack": {
+    "schema": "./db/schema.zmodel"
+  }
+}
+```
+
+### info
+
+Get information of installed ZenStack and related packages.
+
+```bash
+zenstack info [options] [path]
+```
+
+#### Arguments
+
+| Name | Description  | Default        |
+| ---- | ------------ | -------------- |
+| path | Project path | current folder |
+
+<!-- ### Config file
+
+ZenStack CLI loads configuration from `zenstack.config.json` file if it finds one. The config file path can be overridden with `--config` option.
+
+The config file is a JSON object with the following properties:
+
+| Name                 | Description                                                         | Default              |
+| -------------------- | ------------------------------------------------------------------- | -------------------- | -->
diff --git a/versioned_docs/version-1.x/reference/error-handling.md b/versioned_docs/version-1.x/reference/error-handling.md
new file mode 100644
index 00000000..b3de378a
--- /dev/null
+++ b/versioned_docs/version-1.x/reference/error-handling.md
@@ -0,0 +1,43 @@
+---
+description: Error handling
+sidebar_position: 6
+---
+
+# Error Handling
+
+ZenStack's enhancements to Prisma clients are transparent proxies, so normal errors thrown from a Prisma client simply pass through, and you can handle them the same way as you do in a regular Prisma project. It's good to read these references from Prisma:
+
+-   [Handling exceptions and errors](https://www.prisma.io/docs/concepts/components/prisma-client/handling-exceptions-and-errors)
+-   [Error message reference](https://www.prisma.io/docs/reference/api-reference/error-reference)
+
+The enhanced Prisma client can throw extra errors when an operation is rejected by [access policies](./zmodel-language#access-policy) or its data fails [validation rules](./zmodel-language#data-validation). To keep a consistent programming experience, a `PrismaClientKnownRequestError` is thrown with code [`P2004`](https://www.prisma.io/docs/reference/api-reference/error-reference#p2004) is used in such cases:
+
+```ts
+throw new PrismaClientKnownRequestError(message, {
+    clientVersion: getVersion(),
+    code: 'P2004',
+    meta: ...
+});
+```
+
+The error contains a `meta` field providing more information about the error. It contains the following fields:
+
+- `reason`
+    
+    A string field indicating the detailed reason of the rejection. Its value can be one of the following:
+
+    - *ACCESS_POLICY_VIOLATION*
+  
+        CRUD failed because of access policy violation.
+
+    - *RESULT_NOT_READABLE*
+  
+        CRUD succeeded but the result was not readable (violating "read" access policies).
+
+    - *DATA_VALIDATION_VIOLATION*
+    
+        CRUD failed because of a [data validation](./zmodel-language#data-validation) error.
+
+- `zodErrors`
+  
+    An object field containing the raw Zod validation errors. Only present if the `reason` is `DATA_VALIDATION_VIOLATION`. See [Zod documentation](https://zod.dev/?id=error-handling) for more details.
diff --git a/versioned_docs/version-1.x/reference/limitations.md b/versioned_docs/version-1.x/reference/limitations.md
new file mode 100644
index 00000000..59e3bc83
--- /dev/null
+++ b/versioned_docs/version-1.x/reference/limitations.md
@@ -0,0 +1,26 @@
+---
+description: Current limitations
+sidebar_position: 100
+---
+
+# Limitations
+
+This section lists the current limitations of ZenStack.
+
+### Sequential operations transaction
+
+[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.
+
+### Edge runtime is not supported
+
+Prisma by default doesn't support running in edge runtime environments (e.g. [Vercel Edge Runtime](https://edge-runtime.vercel.app/)). Its [Accelerate](https://www.prisma.io/data-platform/accelerate) commercial service does support edge, but ZenStack is not compatible with it yet. We plan to support it in the near future.
+
+### Cloudflare D1 database is not supported
+
+Prisma doesn't support interactive transactions with D1 databases. ZenStack relies on the feature to enforce access policies in certain cases.
diff --git a/versioned_docs/version-1.x/reference/plugins/_category_.yml b/versioned_docs/version-1.x/reference/plugins/_category_.yml
new file mode 100644
index 00000000..a2d8b377
--- /dev/null
+++ b/versioned_docs/version-1.x/reference/plugins/_category_.yml
@@ -0,0 +1,7 @@
+position: 4
+label: Plugins
+collapsible: true
+collapsed: true
+link:
+    type: generated-index
+    title: Plugins
diff --git a/versioned_docs/version-1.x/reference/plugins/_invalidation.md b/versioned_docs/version-1.x/reference/plugins/_invalidation.md
new file mode 100644
index 00000000..813e81b5
--- /dev/null
+++ b/versioned_docs/version-1.x/reference/plugins/_invalidation.md
@@ -0,0 +1,42 @@
+The mutation hooks generated by ZenStack automatically invalidates the queries that are potentially affected by the changes. For example, if you create a `Post`, the `useFindManyPost` query will be automatically invalidated when the mutation succeeds. Invalidation causes cache to be purged and fresh data to be refetched.
+
+The automatic invalidation takes care of nested read, write, and delete cascading.
+
+**1. Nested Read**
+
+Nested reads are also subject to automatic invalidation. For example, if you create a `Post`, the query made by
+
+```ts
+useFindUniqueUser({ where: { id: userId }, include: { posts: true } });
+```
+
+will be invalidated.
+
+**2. Nested Write**
+
+Similarly, nested writes also trigger automatic invalidation. For example, if you create a `Post` in a nested update to `User` like:
+
+```ts
+updateUser({ where: { id: userId }, posts: { create: { title: 'post1' } } });
+```
+
+The mutation will cause queries like ```useFindManyPost()``` to be invalidated.
+
+**3. Delete Cascade**
+
+In ZModel, relations can be configured to cascade delete, e.g.:
+
+```zmodel
+model User {
+    ...
+    posts Post[]
+}
+
+model Post {
+    ...
+    user User @relation(fields: [userId], references: [id], onDelete: Cascade)
+    userId Int
+}
+```
+
+When a `User` is deleted, the `Post` entities it owns will be deleted automatically. The generated hooks takes cascade delete into account. For example, if you delete a `User`, `Post` model will be considered as affected and queries like ```useFindManyPost()``` will be invalidated.
diff --git a/versioned_docs/version-1.x/reference/plugins/_optimistic-behavior.md b/versioned_docs/version-1.x/reference/plugins/_optimistic-behavior.md
new file mode 100644
index 00000000..32463b03
--- /dev/null
+++ b/versioned_docs/version-1.x/reference/plugins/_optimistic-behavior.md
@@ -0,0 +1,3 @@
+- `create` mutation inserts item to the head of the query results of the corresponding `useFindMany` queries.
+- `update` mutation updates the item in the query results of `useFindXXX` queries and their nested reads by matching the item's ID.
+- `delete` mutation removes the item from the query results of the corresponding `useFindMany` queries and sets `null` to `useFindUnique` and `useFindFirst` query results, by matching the item's ID.
diff --git a/versioned_docs/version-1.x/reference/plugins/_optimistic-limitation.md b/versioned_docs/version-1.x/reference/plugins/_optimistic-limitation.md
new file mode 100644
index 00000000..792ae1ba
--- /dev/null
+++ b/versioned_docs/version-1.x/reference/plugins/_optimistic-limitation.md
@@ -0,0 +1,4 @@
+- The automatic optimistic update relies on ID matching. It only works for queries that select the ID field(s).
+- Non-entity-fetching queries like `count`, `aggregate`, and `groupBy` are not affected.
+- Infinite queries are not affected.
+- It doesn't respect filter conditions or access policies that potentially affect the queries under update. For example, for query `useFindManyPost({ where: { published: true }})`, when a non-published `Post` is created, it'll still be inserted into the query result.
\ No newline at end of file
diff --git a/docs/reference/plugins/access-policy.md b/versioned_docs/version-1.x/reference/plugins/access-policy.md
similarity index 77%
rename from docs/reference/plugins/access-policy.md
rename to versioned_docs/version-1.x/reference/plugins/access-policy.md
index 567da10a..97f55b19 100644
--- a/docs/reference/plugins/access-policy.md
+++ b/versioned_docs/version-1.x/reference/plugins/access-policy.md
@@ -5,7 +5,7 @@ sidebar_position: 3
 
 # @core/access-policy
 
-The `@core/access-policy` plugin generates access policy guard objects from [policy rules](/docs/the-complete-guide/part1/access-policy/) (defined using `@@allow` and `@@deny` attributes).
+The `@core/access-policy` plugin generates access policy guard objects from [policy rules](../../the-complete-guide/part1/access-policy/) (defined using `@@allow` and `@@deny` attributes).
 
 :::info
 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.
@@ -26,14 +26,14 @@ This plugin is built-in to ZenStack and does not need to be installed separately
 ### Example
 
 ```zmodel title='/schema.zmodel'
-plugin zod {
+plugin policy {
   provider = '@core/access-policy'
   output = 'src/lib/zenstack'
   compile = false
 }
 ```
 
-Runtime APIs like [`enhance`](/docs/reference/runtime-api#enhance) and [`withPolicy`](/docs/reference/runtime-api#withpolicy) depend on the output of this plugin and by default load it from the default output location. If you customize the output location, you need to load and pass it manually:
+Runtime APIs like [`enhance`](../runtime-api#enhance) and [`withPolicy`](../runtime-api#withpolicy) depend on the output of this plugin and by default load it from the default output location. If you customize the output location, you need to load and pass it manually:
 
 ```ts
 const policy = require('./lib/zenstack/policy').default;
diff --git a/docs/reference/plugins/model-meta.md b/versioned_docs/version-1.x/reference/plugins/model-meta.md
similarity index 80%
rename from docs/reference/plugins/model-meta.md
rename to versioned_docs/version-1.x/reference/plugins/model-meta.md
index 92617ce6..cd814d1c 100644
--- a/docs/reference/plugins/model-meta.md
+++ b/versioned_docs/version-1.x/reference/plugins/model-meta.md
@@ -26,14 +26,14 @@ This plugin is built-in to ZenStack and does not need to be installed separately
 ### Example
 
 ```zmodel title='/schema.zmodel'
-plugin zod {
+plugin meta {
   provider = '@core/model-meta'
   output = 'src/lib/zenstack'
   compile = false
 }
 ```
 
-Runtime APIs like [`enhance`](/docs/reference/runtime-api#enhance) and [`withPolicy`](/docs/reference/runtime-api#withpolicy) depend on the output of this plugin and by default load it from the default output location. If you customize the output location, you need to load and pass it manually:
+Runtime APIs like [`enhance`](../runtime-api#enhance) and [`withPolicy`](../runtime-api#withpolicy) depend on the output of this plugin and by default load it from the default output location. If you customize the output location, you need to load and pass it manually:
 
 ```ts
 const meta = require('./lib/zenstack/model-meta').default;
diff --git a/versioned_docs/version-1.x/reference/plugins/openapi.mdx b/versioned_docs/version-1.x/reference/plugins/openapi.mdx
new file mode 100644
index 00000000..71bbe56f
--- /dev/null
+++ b/versioned_docs/version-1.x/reference/plugins/openapi.mdx
@@ -0,0 +1,181 @@
+---
+description: Plugin for generating OpenAPI 3.0 specifications
+sidebar_position: 8
+---
+
+# @zenstackhq/openapi
+
+The `@zenstackhq/openapi` generates an [OpenAPI V3](https://spec.openapis.org/oas/v3.1.0) 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.
+
+### Installation
+
+```bash
+npm install --save-dev @zenstackhq/openapi
+```
+
+### Options
+
+| Name            | Type   | Description                                                                   | Required | Default                |
+| --------------- | ------ | ----------------------------------------------------------------------------- | -------- | ---------------------- |
+| output          | String | Output file path (with suffix .yaml or .json, relative to the path of ZModel)                                 | Yes      |                        |
+| flavor          | String | API flavor: "rpc" or "rest". See [here](../server-adapters/api-handlers/) for more details.                                       | No       | rpc                   |
+| specVersion     | String | OpenAPI specification version                                                 | No       | 3.1.0                  |
+| title           | String | API title                                                                     | No       | ZenStack Generated API |
+| version         | String | API version                                                                   | No       | 1.0.0                  |
+| prefix          | String | API path prefix, e.g., '/api'                                                 | No       |                        |
+| description     | String | API description                                                               | No       |                        |
+| summary         | String | API summary                                                                   | No       |                        |
+| securitySchemes | Object | Security schemes for the API. See [here](#security-schemes) for more details. | No       |                        |
+
+#### API flavor
+
+The `flavor` option controls the style of API endpoints and data format of the generated specification. Currently we support two flavors: `rpc` and `rest`. The default flavor is `rpc`.
+
+- rpc
+
+    Fully mirrors [PrismaClient's query API](https://www.prisma.io/docs/reference/api-reference/prisma-client-reference#model-queries).
+
+- rest
+
+    RESTful resource-centric endpoints using [JSON:API](https://jsonapi.org/) as transportation format
+
+
+The two flavors correspond to the two style of APIs supported by ZenStack server adapters. See [here](../server-adapters/api-handlers/) for more details.
+
+#### Security schemes
+
+The `securitySchemes` option is an object containing security schemes for the API, following the specification [here](https://swagger.io/docs/specification/authentication/). The object will be generated as the `securitySchemes` field under the `components` section in the OpenAPI specification.
+
+Example usage:
+
+```zmodel
+plugin openapi {
+    provider = '@zenstackhq/openapi'
+    securitySchemes = {
+        basic: { type: 'http', scheme: 'basic' },
+        bearer: { type: 'http', scheme: 'bearer', bearerFormat: 'JWT' },
+        apiKey: { type: 'apiKey', in: 'header', name: 'X-API-KEY' }
+    }
+}
+```
+
+The names of the configured security schemes will be added to the root `security` field and inherited by all operations. The plugin also respects the access policy rules defined in the schema. If a operation is fully open (e.g., `create` operation with `@@allow('create', true)` rule), an empty security array will be added to the operation to mark it as unprotected.
+
+You can override the security scheme for a specific model or operation by using the `@@openapi.meta` attribute.
+
+### Attributes
+
+-   `@@openapi.meta`
+
+    ```zmodel
+        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 it to override Http method, API endpoint path, description, summary, tags, security, and etc. Currently there's no type checking for the structure, but we'll add that in the near future.
+
+    #### Model-level metadata
+
+    The input object can contain the following top-level fields:
+
+    | Name           | Type   | Description                                     | Default                 |
+    | -------------- | ------ | ----------------------------------------------- | ----------------------- |
+    | security       | Array  | Security for this model                         |                         |
+    | tagDescription | String | Description of the tag generated for this model | [Model Name] operations |
+
+    #### Operation-level metadata
+
+    :::info
+    Only supported by "rpc" flavor.
+    :::
+
+    The input object can contain operation-level metadata keyed by the Prisma operation name. Each entry can contain the following fields:
+
+    | Name        | Type   | Description                         | Default                                 |
+    | ----------- | ------ | ----------------------------------- | --------------------------------------- |
+    | security    | Array  | Security for this operation         |                                         |
+    | description | String | Description of the operation        |                                         |
+    | method      | String | HTTP method of the operation        | Depends on the operation                |
+    | path        | String | API endpoint path of the operation  | The corresponding Prisma operation name |
+    | summary     | String | Summary of the operation            |                                         |
+    | tags        | Array  | Tags of the operation               |                                         |
+    | deprecated  | Bool   | Whether the operation is deprecated | false                                   |
+
+    #### Sample usage
+
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
+<Tabs>
+<TabItem value="rpc" label={`Using "rpc" flavor`}>
+
+```zmodel
+model User {
+    id String @id
+    email String @unique
+
+    @@openapi.meta({
+        security: [ { basic: [] } ],
+        tagDescription: 'Operations for managing users',
+        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'],
+        },
+        createMany: {
+            security: [],
+            deprecated: true
+        }
+    })
+}
+```
+
+</TabItem>
+
+<TabItem value="rest" label={`Using "rest" flavor`}>
+
+```zmodel
+model User {
+    id String @id
+    email String @unique
+
+    @@openapi.meta({
+        security: [ { basic: [] } ],
+        tagDescription: 'Operations for managing users',
+    })
+}
+```
+
+</TabItem>
+</Tabs>
+
+-   `@@openapi.ignore`
+
+    ```zmodel
+    attribute @@openapi.ignore()
+    ```
+
+    Mark a data model to be ignored when generating OpenAPI specification.
+
+### Example
+
+```zmodel 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'
+    summary = 'Created with ZenStack'
+    description = 'My awesome API created with ZenStack'
+    prefix = '/api'
+}
+```
diff --git a/versioned_docs/version-1.x/reference/plugins/prisma.md b/versioned_docs/version-1.x/reference/plugins/prisma.md
new file mode 100644
index 00000000..0815803d
--- /dev/null
+++ b/versioned_docs/version-1.x/reference/plugins/prisma.md
@@ -0,0 +1,35 @@
+---
+description: Built-in plugin for generating Prisma schema
+sidebar_position: 1
+---
+
+# @core/prisma
+
+The `@core/prisma` plugin generates a Prisma schema from ZModel.
+
+:::info
+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.
+:::
+
+### Installation
+
+This plugin is built-in to ZenStack and does not need to be installed separately.
+
+### Options
+
+| Name           | Type    | Description                                       | Required | Default                |
+| -------------- | ------- | ------------------------------------------------- | -------- | ---------------------- |
+| output         | String  | Output file path (relative to the path of ZModel)                                  | No       | ./prisma/schema.prisma |
+| generateClient | Boolean | Whether to run `prisma generate` after generation | No       | true                   |
+| generateArgs | String | Command line arguments appended to the `prisma generate` call, e.g., "--no-engine". Please note that the "--schema" option is automatically passed, and you shouldn't provide it explicitly. | No       |                    |
+| format         | Boolean | Whether to run `prisma format` after generation   | No       | false                  |
+
+### Example
+
+```zmodel title='/schema.zmodel'
+plugin prisma {
+    provider = '@core/prisma'
+    output = './src/prisma/schema.prisma'
+    format = true
+}
+```
diff --git a/versioned_docs/version-1.x/reference/plugins/swr.mdx b/versioned_docs/version-1.x/reference/plugins/swr.mdx
new file mode 100644
index 00000000..9aa6df66
--- /dev/null
+++ b/versioned_docs/version-1.x/reference/plugins/swr.mdx
@@ -0,0 +1,286 @@
+---
+description: Plugin for generating SWR query and mutation hooks
+sidebar_position: 5
+---
+
+import Invalidation from './_invalidation.md';
+import OptimisticBehavior from './_optimistic-behavior.md';
+import OptmisticLimitation from './_optimistic-limitation.md';
+
+# @zenstackhq/swr
+
+:::info
+If you're looking for generating hooks for [Tanstack Query](https://tanstack.com/query/latest), please checkout the [`@zenstackhq/tanstack-query`](./tanstack-query) plugin.
+:::
+
+The `@zenstackhq/swr` plugin generates [SWR](https://swr.vercel.app/) hooks that call into the CRUD services provided by the [server adapters](../../category/server-adapters).
+
+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).
+
+To use the generated hooks, you need to install "swr" version 2.0.0 or above.
+
+### Installation
+
+```bash
+npm install --save-dev @zenstackhq/swr
+```
+
+### Context Provider
+
+The plugin generates a React context provider which you can use to configure the behavior of the hooks. The following options are available on the provider:
+
+- endpoint
+
+    The endpoint to use for the queries. Defaults to "/api/model".
+
+- fetch
+
+    A custom `fetch` function to use for the queries. Defaults to the browser's built-in `fetch`. 
+
+Example for using the context provider:
+
+```tsx
+import { FetchFn, Provider as ZenStackHooksProvider } from '../lib/hooks';
+
+// custom fetch function that adds a custom header
+const myFetch: FetchFn = (url, options) => {
+    options = options ?? {};
+    options.headers = {
+        ...options.headers,
+        'x-my-custom-header': 'hello world',
+    };
+    return fetch(url, options);
+};
+
+function MyApp({ Component, pageProps: { session, ...pageProps } }: AppProps) {
+    return (
+        <ZenStackHooksProvider value={{ endpoint: '/api/model', fetch: myFetch }}>
+            <AppContent />
+        </ZenStackHooksProvider>
+    );
+}
+
+export default MyApp;
+
+```
+
+### Options
+
+| Name    | Type   | Description                                             | Required | Default |
+| ------- | ------ | ------------------------------------------------------- | -------- | ------- |
+| output  | String | Output directory (relative to the path of ZModel)                                        | Yes      |         |
+
+### Example
+
+Here's a quick example with a blogging app. You can find a fully functional Todo app example [here](https://github.com/zenstackhq/sample-todo-nextjs).
+
+#### Schema
+
+```zmodel title='/schema.zmodel'
+plugin hooks {
+  provider = '@zenstackhq/swr'
+  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)
+}
+```
+
+#### Using Query and Mutation Hooks
+
+```tsx title='/src/components/posts.tsx'
+import type { Post } from '@prisma/client';
+import { useFindManyPost, useCreatePost } from '../lib/hooks';
+
+// post list component
+const Posts = ({ userId }: { userId: string }) => {
+    const { trigger: createPost } = useCreatePost();
+
+    // list all posts that're visible to the current user, together with their authors
+    const { data: posts } = useFindManyPost({
+        include: { author: true },
+        orderBy: { createdAt: 'desc' },
+    });
+
+    function onCreatePost() {
+        createPost({
+            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>
+        </>
+    );
+};
+```
+
+### Automatic Optimistic Update (Preview)
+
+Optimistic update is a technique that allows you to update the data cache immediately when a mutation executes while waiting for the server response. It helps achieve a more responsive UI. SWR provides the [infrastructure for implementing it](https://swr.vercel.app/docs/mutation#optimistic-updates).
+
+The ZenStack-generated mutation hooks allow you to opt-in to "automatic optimistic update" by passing the `optimisticUpdate` option when calling the hook. When the mutation executes, it analyzes the current queries in the cache and tries to find the ones that need to be updated. When the mutation settles (either succeeded or failed), the queries are invalidated to trigger a re-fetch.
+
+Here's an example:
+
+```ts
+const { trigger: create } = useCreatePost({ optimisticUpdate: true });
+
+function onCreatePost() {
+    create({ ... })
+}
+```
+
+When `create` executes, if there are active queries like `useFindManyPost()`, the data of the mutation call will be optimistically inserted into the head of the query result.
+
+#### Details of the optimistic behavior
+
+<OptimisticBehavior />
+
+#### Limitations
+
+<OptmisticLimitation />
+
+#### Opt-out
+
+By default, all queries opt into automatic optimistic update. You can opt-out on a per-query basis by passing `false` to the `optimisticUpdate` option.
+
+```ts
+// arguments are query args, query options, and optimisticUpdate
+const { data } = useFindManyPost(
+    { where: { published: true } },
+    { optimisticUpdate: false }
+);
+```
+
+When a query opts out, it won't be updated by a mutation, even if the mutation is set to update optimistically.
+
+#### Using Infinite Query
+
+See [SWR's documentation](https://swr.vercel.app/docs/pagination) for more details.
+
+```tsx title='/src/components/posts.tsx'
+import type { Post } from '@prisma/client';
+import { useInfiniteFindManyPost } from '../lib/hooks';
+
+// post list component with infinite loading
+const Posts = ({ userId }: { userId: string }) => {
+
+    const PAGE_SIZE = 10;
+
+    const { data: pages, size, setSize } = useInfiniteFindManyPost(
+        (pageIndex, previousPageData) => {
+            if (previousPageData && !previousPageData.length) {
+                return null;
+            }
+            return {
+                include: { author: true },
+                orderBy: { createdAt: 'desc' },
+                take: PAGE_SIZE,
+                skip: pageIndex * PAGE_SIZE,
+            };
+        }
+    );
+
+    const isEmpty = pages?.[0]?.length === 0;
+    const isReachingEnd = isEmpty || (pages && pages[pages.length - 1].length < PAGE_SIZE);
+
+    return (
+        <>
+            <ul>
+                {pages?.map((posts, index) => (
+                    <React.Fragment key={index}>
+                        {posts?.map((post) => (
+                            <li key={post.id}>
+                                {post.title} by {post.author.email}
+                            </li>
+                        ))}
+                    </React.Fragment>
+                ))}
+            </ul>
+
+            {!isReachingEnd && (
+                <button onClick={() => setSize(size + 1)}>
+                    Load more
+                </button>
+            )}
+        </>
+    );
+};
+```
+
+
+## Advanced
+
+### Query Invalidation
+
+<Invalidation />
+
+:::info
+
+The automatic invalidation is enabled by default, and you can use the `revalidate` option to opt-out and handle revalidation by yourself.
+
+```ts
+useCreatePost({ revalidate: false });
+```
+
+:::
+
+### Query Key
+
+Query keys serve as unique identifiers for organizing the query cache. The generated hooks use the following query key scheme:
+
+```ts
+JSON.stringify({ prefix: 'zenstack', model, operation, args, flags })
+```
+
+For example, the query key for
+
+```ts
+useFindUniqueUser({ where: { id: '1' } })
+```
+
+will be:
+
+```ts
+JSON.stringify({ 
+    prefix: 'zenstack',
+    model: 'User', 
+    operation: 'findUnique', 
+    args: { where: { id: '1' } }, 
+    flags: { infinite: false } 
+})
+```
+
+You can use the generated `getQueryKey` function to compute it.
diff --git a/versioned_docs/version-1.x/reference/plugins/tanstack-query.mdx b/versioned_docs/version-1.x/reference/plugins/tanstack-query.mdx
new file mode 100644
index 00000000..7292d370
--- /dev/null
+++ b/versioned_docs/version-1.x/reference/plugins/tanstack-query.mdx
@@ -0,0 +1,648 @@
+---
+description: Plugin for generating Tanstack Query query and mutation hooks
+sidebar_position: 6
+---
+
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+import Invalidation from './_invalidation.md';
+import OptimisticBehavior from './_optimistic-behavior.md';
+import OptmisticLimitation from './_optimistic-limitation.md';
+
+# @zenstackhq/tanstack-query
+
+:::info
+If you're looking for generating hooks for [SWR](https://swr.vercel.app/), please checkout the [`@zenstackhq/swr`](./swr) plugin.
+:::
+
+The `@zenstackhq/tanstack-query` plugin generates [Tanstack Query](https://tanstack.com/query/latest) hooks that call into the CRUD services provided by the [server adapters](../../category/server-adapters). The plugin currently supports React and Svelte. Vue support is coming soon.
+
+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).
+
+To use the generated hooks, you need to install "tanstack-query" for the target framework with version 4.0.0 or above.
+
+### Installation
+
+```bash
+npm install --save-dev @zenstackhq/tanstack-query
+```
+
+### Options
+
+| Name    | Type   | Description                                             | Required | Default |
+| ------- | ------ | ------------------------------------------------------- | -------- | ------- |
+| output  | String | Output directory (relative to the path of ZModel)                                        | Yes     |         |
+| target  | String | Target framework to generate for. Choose from "react", "vue", and "svelte".              | Yes     |         |
+| version | String | Version of TanStack Query to generate for. Choose from "v4" and "v5".                    | No      | v4      |
+
+### Context Provider
+
+The generated hooks allow you to control their behavior by setting up context. The following options are available on the context:
+
+- endpoint
+
+    The endpoint to use for the queries. Defaults to "/api/model".
+
+- fetch
+
+    A custom `fetch` function to use for the queries. Defaults to using [cross-fetch](https://github.com/lquixada/cross-fetch). 
+
+Example for using the context provider:
+
+<Tabs>
+
+<TabItem value="react" label="React">
+
+```tsx title='_app.tsx'
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
+import { Provider as ZenStackHooksProvider } from '../lib/hooks';
+import type { FetchFn } from '@zenstackhq/tanstack-query/runtime';
+
+// custom fetch function that adds a custom header
+const myFetch: FetchFn = (url, options) => {
+    options = options ?? {};
+    options.headers = {
+        ...options.headers,
+        'x-my-custom-header': 'hello world',
+    };
+    return fetch(url, options);
+};
+
+const queryClient = new QueryClient();
+
+function MyApp({ Component, pageProps: { session, ...pageProps } }: AppProps) {
+    return (
+        <QueryClientProvider client={queryClient}>
+            <ZenStackHooksProvider value={{ endpoint: '/api/model', fetch: myFetch }}>
+                <AppContent />
+            </ZenStackHooksProvider>
+        </QueryClientProvider>
+    );
+}
+
+export default MyApp;
+```
+
+</TabItem>
+
+<TabItem value="vue" label="Vue">
+
+```html title='App.vue'
+<script setup lang="ts">
+import { provideHooksContext } from '@/lib/hooks';
+import type { FetchFn } from '@zenstackhq/tanstack-query/runtime';
+
+const myFetch: FetchFn = (url, options) => {
+    options = options ?? {};
+    options.headers = {
+        ...options.headers,
+        'x-my-custom-header': 'hello world',
+    };
+    return fetch(url, options);
+};
+
+provideHooksContext({
+    endpoint: 'http://localhost:3000/api/model',
+    fetch: myFetch
+});
+</script>
+
+<template>
+    <!-- App Content -->
+</template>
+```
+</TabItem>
+
+<TabItem value="svelte" label="Svelte">
+
+```svelte title='+layout.svelte'
+<script lang="ts">
+    import { QueryClient, QueryClientProvider } from '@tanstack/svelte-query';
+    import { setHooksContext } from '$lib/hooks';
+    import type { FetchFn } from '@zenstackhq/tanstack-query/runtime';
+
+    // custom fetch function that adds a custom header
+    const myFetch: FetchFn = (url, options) => {
+        options = options ?? {};
+        options.headers = {
+            ...options.headers,
+            'x-my-custom-header': 'hello world',
+        };
+        return fetch(url, options);
+    };
+
+    setHooksContext({
+        endpoint: '/api/model',
+        fetch: myFetch,
+    });
+
+    const queryClient = new QueryClient();
+</script>
+
+<div>
+    <QueryClientProvider client={queryClient}>
+        <slot />
+    </QueryClientProvider>
+</div>
+```
+</TabItem>
+
+</Tabs>
+
+### Example
+
+Here's a quick example with a ReactJs blogging app. You can find a fully functional Todo app example [here](https://github.com/zenstackhq/sample-todo-nextjs-tanstack).
+
+#### Schema
+
+```zmodel title='/schema.zmodel'
+plugin hooks {
+  provider = '@zenstackhq/tanstack-query'
+  output = "./src/lib/hooks"
+  target = "react"
+}
+
+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)
+}
+```
+
+#### App Setup
+
+<Tabs>
+
+<TabItem value="react" label="React">
+
+```tsx title='_app.tsx'
+
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
+import { Provider as ZenStackHooksProvider } from '../lib/hooks';
+
+const queryClient = new QueryClient();
+
+function App({ Component, pageProps: { session, ...pageProps } }: AppProps) {
+    return (
+        <QueryClientProvider client={queryClient}>
+            <ZenStackHooksProvider value={{ endpoint: '/api/model' }}>
+                <AppContent />
+            </ZenStackHooksProvider>
+        </QueryClientProvider>
+    );
+}
+
+```
+
+</TabItem>
+
+<TabItem value="vue" label="Vue">
+
+```ts title='main.ts'
+import { createApp } from 'vue';
+import App from './App.vue';
+import { QueryClient, VueQueryPlugin } from '@tanstack/vue-query';
+
+createApp(App)
+    .use(VueQueryPlugin, { queryClient: new QueryClient() })
+    .mount('#app');
+```
+
+```html title='App.vue'
+<script setup lang="ts">
+import { provideHooksContext } from '@/lib/hooks';
+
+provideHooksContext({
+    endpoint: 'http://localhost:3000/api/model'
+});
+</script>
+
+<template>
+    <!-- App Content -->
+</template>
+```
+</TabItem>
+
+<TabItem value="svelte" label="Svelte">
+
+```svelte title='+layout.svelte'
+
+<script lang="ts">
+    import { setHooksContext } from '$lib/hooks';
+    import { QueryClient, QueryClientProvider } from '@tanstack/svelte-query';
+
+    const queryClient = new QueryClient();
+    setHooksContext({ endpoint: '/api/model' });
+</script>
+
+<div>
+    <QueryClientProvider client={queryClient}>
+        <slot />
+    </QueryClientProvider>
+</div>
+
+```
+
+:::tip
+
+If you're using [SvelteKit](https://kit.svelte.dev/), you need to make sure the package `@zenstackhq/tanstack-query` is not treated as an external dependency by Vite, otherwise you'll encounter errors when the SvelteKit server renders the app. To do this, add the following section into `vite.config.js`:
+
+```js title='/vite.config.js'
+
+const config = {
+    ...
+    ssr: {
+        noExternal: ['@zenstackhq/tanstack-query'],
+    },
+};
+
+export default config;
+
+```
+:::
+
+</TabItem>
+
+</Tabs>
+
+#### Using Query and Mutation Hooks
+
+<Tabs>
+<TabItem value="react" label="React">
+
+```tsx title='/src/components/Posts.tsx'
+import { useFindManyPost, useCreatePost } from '../lib/hooks';
+
+// post list component
+const Posts = ({ userId }: { userId: string }) => {
+    const create = useCreatePost();
+
+    // list all posts that're visible to the current user, together with their authors
+    const { data: posts } = useFindManyPost({
+        include: { author: true },
+        orderBy: { createdAt: 'desc' },
+    });
+
+    async function onCreatePost() {
+        create.mutate({
+            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>
+        </>
+    );
+};
+```
+
+</TabItem>
+
+<TabItem value="vue" label="Vue">
+
+```html title='/src/components/Posts.vue'
+<script setup lang="ts">
+import { useFindManyPost, useCreatePost } from '@/lib/hooks';
+
+const props = defineProps({
+    userId: String
+});
+
+// list all posts that're visible to the current user, together with their authors
+const { data: posts } = useFindManyPost({
+    include: { author: true },
+    orderBy: { createdAt: 'desc' },
+});
+
+const onCreate = () => {
+    create.mutate({
+        data: {
+            title: 'My awesome post',
+            authorId: userId,
+        }
+    });
+};
+</script>
+
+<template>
+    <div>
+        <button @click="onCreatePost">Create</button>
+        <ul v-if="posts">
+            <li v-for="post in posts" :key="post.id">
+                {{ post.title }} by {{ post.author.email }}
+            </li>
+        </ul>
+    </div>
+</template>
+```
+
+</TabItem>
+
+<TabItem value="svelte" label="Svelte">
+
+```svelte title='/src/components/Posts.svelte'
+<script lang="ts">
+    import { useFindManyPost, useCreatePost } from '../lib/hooks';
+
+    export let userId: string;
+
+    // list all posts that're visible to the current user, together with their authors
+    const query = useFindManyPost({
+        include: { author: true },
+        orderBy: { createdAt: 'desc' },
+    });
+
+    const create = useCreatePost();
+
+    function onCreatePost() {
+        $create.mutate({
+            data: {
+                title: 'My awesome post',
+                authorId: userId,
+            },
+        });
+    }
+</script>
+
+<div>
+    <button on:click={onCreatePost}>Create</button>
+    <ul>
+        {#each $query.data as post (post.id)}
+            <li>{post.title} by {post.author.email}</li>
+        {/each}
+    </ul>
+</div>
+```
+
+</TabItem>
+</Tabs>
+
+### Automatic Optimistic Update (Preview)
+
+Optimistic update is a technique that allows you to update the data cache immediately when a mutation executes while waiting for the server response. It helps achieve a more responsive UI. TanStack Query provides the [infrastructure for implementing it](https://tanstack.com/query/v5/docs/react/guides/optimistic-updates).
+
+The ZenStack-generated mutation hooks allow you to opt-in to "automatic optimistic update" by passing an extra `optimisticUpdate` argument when calling the hook. When the mutation executes, it analyzes the current queries in the cache and tries to find the ones that need to be updated. When the mutation settles (either succeeded or failed), the queries are invalidated to trigger a re-fetch.
+
+Here's an example:
+
+```ts
+// arguments are mutation options, invalidate, and optimisticUpdate
+const create = useCreatePost(undefined, true, true);
+
+function onCreatePost() {
+    create.mutate({ ... })
+}
+```
+
+When `mutate` executes, if there are active queries like `useFindManyPost()`, the data of the mutation call will be optimistically inserted into the head of the query result.
+
+#### Details of the optimistic behavior
+
+<OptimisticBehavior />
+
+#### Limitations
+
+<OptmisticLimitation />
+
+#### Opt-out
+
+By default, all queries opt into automatic optimistic update. You can opt-out on a per-query basis by passing `false` to the `optimisticUpdate` argument.
+
+```ts
+// arguments are query args, query options, and optimisticUpdate
+const { data } = useFindManyPost({ where: { published: true } }, undefined, false);
+```
+
+When a query opts out, it won't be updated by a mutation, even if the mutation is set to update optimistically.
+
+### Using Infinite Query
+
+<Tabs>
+<TabItem value="react" label="React">
+
+Here's a quick example of using infinite query to load a list of posts with infinite pagination. See [Tanstack Query documentation](https://tanstack.com/query/v4/docs/react/guides/infinite-queries) for more details.
+
+```tsx title='/src/components/Posts.tsx'
+import { useInfiniteFindManyPost } from '../lib/hooks';
+
+// post list component with infinite loading
+const Posts = () => {
+    const PAGE_SIZE = 10;
+
+    const fetchArgs = {
+        include: { author: true },
+        orderBy: { createdAt: 'desc' as const },
+        take: PAGE_SIZE,
+    };
+
+    const { data, fetchNextPage, hasNextPage } = useInfiniteFindManyPost(fetchArgs, {
+        getNextPageParam: (lastPage, pages) => {
+            if (lastPage.length < PAGE_SIZE) {
+                return undefined;
+            }
+            const fetched = pages.flatMap((item) => item).length;
+            return {
+                ...fetchArgs,
+                skip: fetched,
+            };
+        },
+    });    
+
+    return (
+        <>
+            <ul>
+                {data?.pages.map((posts, i) => (
+                    <React.Fragment key={i}>
+                        {posts?.map((post) => (
+                            <li key={post.id}>
+                                {post.title} by {post.author.email}
+                            </li>
+                        ))}
+                    </React.Fragment>
+                ))}
+            </ul>
+            {hasNextPage && (
+                <button onClick={() => fetchNextPage()}>
+                    Load more
+                </button>
+            )}
+        </>
+    );
+};
+```
+
+</TabItem>
+
+<TabItem value="vue" label="Vue">
+
+Here's a quick example of using infinite query to load a list of posts with infinite pagination. See [Tanstack Query documentation](https://tanstack.com/query/v4/docs/vue/guides/infinite-queries) for more details.
+
+```html title='/src/components/Posts.vue'
+<script setup lang="ts">
+// post list component with infinite loading
+
+import { useInfiniteFindManyPost } from '@/lib/hooks';
+
+const PAGE_SIZE = 10;
+
+const fetchArgs = {
+    include: { author: true },
+    orderBy: { createdAt: 'desc' as const },
+    take: PAGE_SIZE,
+};
+
+const { data, hasNextPage, fetchNextPage } = useInfiniteFindManyPost(
+    fetchArgs,
+    {
+        getNextPageParam: (lastPage, pages) => {
+            if (lastPage.length < PAGE_SIZE) {
+                return undefined;
+            }
+            const fetched = pages.flatMap((item) => item).length;
+            return {
+                ...fetchArgs,
+                skip: fetched,
+            };
+        },
+    }
+);
+</script>
+
+<template>
+    <div>
+        <ul v-if="data">
+            <template v-for="(posts, i) in data.pages" :key="i">
+                <li v-for="post in posts" :key="post.id">
+                    {{ post.title }} by {{ post.author.email }}
+                </li>
+            </template>
+        </ul>
+    </div>
+
+    <button v-if="hasNextPage" @click="() => fetchNextPage()">Load More</button>
+</template>
+
+```
+
+</TabItem>
+
+<TabItem value="svelte" label="Svelte">
+
+Here's a quick example of using infinite query to load a list of posts with infinite pagination. See [Tanstack Query documentation](https://tanstack.com/query/v4/docs/svelte/examples/svelte/load-more-infinite-scroll) for more details.
+
+```svelte title='/src/components/Posts.svelte'
+<script lang="ts">
+    // post list component with infinite loading
+
+    import { useInfiniteFindManyPost } from '../lib/hooks';
+
+    const PAGE_SIZE = 10;
+
+    const fetchArgs = {
+        include: { author: true },
+        orderBy: { createdAt: 'desc' as const },
+        take: PAGE_SIZE,
+    };
+
+    const query = useInfiniteFindManyPost(fetchArgs, {
+        getNextPageParam: (lastPage, pages) => {
+            if (lastPage.length < PAGE_SIZE) {
+                return undefined;
+            }
+            const fetched = pages.flatMap((item) => item).length;
+            return {
+                ...fetchArgs,
+                skip: fetched,
+            };
+        },
+    });    
+</script>
+
+<div>
+    <ul>
+        <div>
+            {#if $query.data}
+                {#each $query.data.pages as posts, i (i)}
+                    {#each posts as post (post.id)}
+                        <li>{post.title} by {post.author.email}</li>
+                    {/each}
+                {/each}
+            {/if}
+        </div>
+    </ul>
+    {#if $query.hasNextPage}
+        <button on:click={() => $query.fetchNextPage()}>
+            Load more
+        </button>
+    {/if}
+</div>
+```
+
+</TabItem>
+</Tabs>
+
+## Advanced
+
+### Query Invalidation
+
+<Invalidation />
+
+:::info
+
+The automatic invalidation is enabled by default, and you can use the `invalidateQueries` argument to opt-out and handle revalidation by yourself.
+
+```ts
+// arguments are mutation options and invalidateQueries
+useCreatePost(undefined, false);
+```
+:::
+
+### Query Key
+
+Query keys serve as unique identifiers for organizing the query cache. The generated hooks use the following query key scheme:
+
+```ts
+['zenstack', model, operation, args, flags]
+```
+
+For example, the query key for
+
+```ts
+useFindUniqueUser({ where: { id: '1' } })
+```
+
+will be:
+
+```ts
+['zenstack', 'User', 'findUnique', { where: { id: '1' } }, { infinite: false }]
+```
+
+You can use the generated `getQueryKey` function to compute it.
diff --git a/versioned_docs/version-1.x/reference/plugins/trpc.md b/versioned_docs/version-1.x/reference/plugins/trpc.md
new file mode 100644
index 00000000..135fec88
--- /dev/null
+++ b/versioned_docs/version-1.x/reference/plugins/trpc.md
@@ -0,0 +1,148 @@
+---
+description: Plugin for generating tRPC data query routes
+sidebar_position: 7
+---
+
+# @zenstackhq/trpc
+
+The `@zenstackhq/trpc` plugin generates a [tRPC router](https://trpc.io/docs/router) to make database CRUD calls. You can use it directly as your main router or merge it with other routers to create a more complex setup. The router syntactically mirrors the APIs of a standard Prisma client, including the function names and shapes of parameters (hooks directly use types generated by Prisma). 
+
+:::info
+The `@zenstackhq/trpc` plugin depends on the [`@core/zod`](./zod) plugin to generate Zod schemas for the routers' input, and will enable that plugin automatically if it's not already enabled.
+:::
+
+This plugin is based on [prisma-trpc-generator](https://github.com/omar-dulaimi/prisma-trpc-generator). Thanks to [Omar Dulaimi](https://github.com/omar-dulaimi) for making this happen!
+
+### Installation
+
+```bash
+npm install --save-dev @zenstackhq/trpc
+```
+
+### Options
+
+| Name   | Type   | Description      | Required | Default |
+| ------ | ------ | ---------------- | -------- | ------- |
+| output | String | Output directory (relative to the path of ZModel) | Yes      | |
+| generateModels | String, String[] | Array or comma separated string for the models to generate routers for. | No      | All models |
+| generateModelActions | String, String[] | Array or comma separated string for actions to generate for each model: `create`, `findUnique`, `update`, etc. | No      | All supported Prisma actions |
+| generateClientHelpers | String, String[] | Array or comma separated string for the types of client helpers to generate. Supported values: "react" or "next". See [here](#client-helpers) for more details. | No      | |
+| zodSchemasImport | String | Import path for the generated zod schemas. The trpc plugin relies on the `@core/zod` plugin to generate zod schemas for input validation. If you set a custom output location for the zod schemas, you can use this option to override the import path. | No      | @zenstackhq/runtime/zod |
+
+:::info
+When `@core/zod` plugin is automatically enabled by the `@zenstackhq/trpc` plugin, if the `@zenstackhq/trpc` plugin has a `generateModels` option specified, it'll be carried over to the `@core/zod` plugin as well.
+:::
+
+## Dependencies
+
+- [`@core/zod`](./zod)
+
+### Details
+
+#### Preparing tRPC Context
+
+The generated tRPC routers require a `prisma` field in the context and you need to make sure to include it when creating the context. You can use any PrismaClient instance for it, but most likely you want to use one created with [`enhance`](../runtime-api#enhance) so that the client enforces access control policies.
+
+```ts
+export const createContext = async ({ req, res }: CreateNextContextOptions) => {
+    const session = await getServerAuthSession({ req, res });
+    return {
+        session,
+        // use access-control-enabled db client
+        prisma: enhance(prisma, { user: session?.user }),
+    };
+};
+```
+
+#### Client Helpers
+
+TRPC relies on TypeScript's type inference to provide the client-side API call signatures. This is very neat and powerful, but it has some limitations regarding generic APIs. For example, one of the best features of Prisma's API is its output type automatically adapts to the input's shape. e.g.:
+
+```ts
+const post = await prisma.post.findFirst({ include: { author: true } });
+```
+
+For the code above, the `post` variable is smartly inferred to be of type `Post & { author: User }`. However, if you wrap it with tRPC, such typing adaptivity is lost:
+
+```ts
+// trpc router
+const router = t.router({
+  findFirst: t.procedure
+    .input(PostInputSchema)
+    .query(({ ctx, input }) => ctx.prisma.findFirst(input))
+})
+
+// client side
+const { data } = trpc.post.findFirst({ include: { author: true } });
+```
+
+Now the `data` field has a fixed `Post` type, even though at runtime it carries the extra `author` field.
+
+To mitigate this limitation, ZenStack's tRPC plugin can generate a "type-fixing" helper that re-enables the inference power from the client side. To use it, you need to specify the `generateClientHelpers` option:
+
+```zmodel
+plugin trpc {
+  provider = '@zenstackhq/trpc'
+  output = 'server/routers/generated'
+  generateClientHelpers = 'next'
+}
+```
+
+Use value "next" if you're using tRPC with Next.js, or "react" if using it with react-query. With that option on, the plugin generates an extra "client" folder containing helpers that do the type fixing. The only code change you need to make is instead of calling the `createTRPCNext` or `createTRPCReact` API, you call the one generated instead.
+
+Here's an example of how to use it with Next.js:
+
+```ts
+import { createTRPCNext } from 'server/routers/generated/client/next';
+export const trpc = createTRPCNext<AppRouter>({
+    ...
+});
+```
+
+### Example
+
+Here's an example with a blogging app:
+
+```zmodel title='/schema.zmodel'
+plugin trpc {
+  provider = '@zenstackhq/trpc'
+  output = 'server/routers/generated'
+  generateModelActions = 'create,update,findUnique,findMany'
+}
+
+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)
+}
+```
+
+```ts title='/server/routers/_app.ts'
+import { createRouter as createCRUDRouter } from './generated/routers';
+import { initTRPC } from '@trpc/server';
+import { type Context } from '../context';
+
+const t = initTRPC.context<Context>().create();
+
+export const appRouter = createCRUDRouter(t.router, t.procedure);
+export type AppRouter = typeof appRouter;
+```
+
+Check out the [Using With tRPC](../../guides/trpc) guide for more details about using ZenStack in a tRPC project.
diff --git a/versioned_docs/version-1.x/reference/plugins/zod.md b/versioned_docs/version-1.x/reference/plugins/zod.md
new file mode 100644
index 00000000..05caf4de
--- /dev/null
+++ b/versioned_docs/version-1.x/reference/plugins/zod.md
@@ -0,0 +1,191 @@
+---
+description: Built-in plugin for generating Zod schemas for validating CRUD input arguments
+sidebar_position: 4
+---
+
+# @core/zod
+
+The `@core/zod` plugin generates [Zod](https://github.com/colinhacks/zod) schemas for models and input arguments of Prisma CRUD operations. The plugin is automatically enabled when any of the following conditions meets:
+- If any model carries [field validation attributes](../zmodel-language#data-validation).
+- If any plugin that depends on it is used, e.g., the [tRPC plugin](./trpc).
+
+:::info
+You need to explicitly enable `@core/zod` plugin if you use the [Express.js](../server-adapters/express) or [Fastify](../server-adapters/fastify) server adapter with `zodSchemas` option enabled.
+:::
+
+By default, the Zod schemas are generated into `node_modules/.zenstack/zod` directory, and are reexported through `@zenstackhq/runtime/zod`. If you configure the plugin to output to a custom location, you can just directly import from there.
+
+The generated schemas have the following three parts:
+
+- `zod/models`
+    
+    The schemas for validating the models and input data for creating and updating the models. They contain field's typing and [validation rules](../zmodel-language#data-validation). The module exports several Zod schemas for each model for different use cases. To facilitate the discussion, let's use the following model as an example:
+
+    ```zmodel
+    model Post {
+      id Int @id @default(autoincrement())
+      title String @length(10, 255)
+      published Boolean @default(false)
+      createdAt DateTime @default(now())
+      updatedAt DateTime @updatedAt
+      author User @relation(fields: [authorId], references: [id])
+      authorId Int
+
+      @@validate(regex(title, "^[A-Za-z0-9 ]+$"))
+    }
+    ```
+    
+    - *[Model]Schema*
+
+        The schema for validating the model itself, including all scalar fields, foreign key fields, and relation fields. All fields are optional (**we may revisit this design in a future release**).
+
+        ```ts
+        const PostSchema = z.object({
+          id: z.number(),
+          title: z.string().min(10).max(255),
+          published: z.boolean(),
+          createdAt: z.date(),
+          updatedAt: z.date(),
+          author: z.record(z.unknown()),
+          authorId: z.number(),
+        }).partial();
+        ```
+
+    - *[Model]CreateScalarSchema*
+
+        The schema for validating the input for creating the model. It only includes scalar fields. All fields are required unless it's optional in the model or has a default value.
+
+        ```ts
+        const PostCreateScalarSchema = z.object({
+          id: z.number().optional(),
+          title: z.string().min(10).max(255),
+          published: z.boolean().optional(),
+          createdAt: z.date().optional(),
+          updatedAt: z.date().optional(),
+        });
+        ```
+
+    - *[Model]CreateSchema*
+  
+        The schema for validating the input for creating the model. It's similar to `[Model]CreateScalarSchema` but includes foreign key fields. You can use it to validate create input that involves setting foreign keys.
+
+        ```ts
+        const PostCreateSchema = z.object({
+          id: z.number().optional(),
+          title: z.string().min(10).max(255),
+          published: z.boolean().optional(),
+          createdAt: z.date().optional(),
+          updatedAt: z.date().optional(),
+          authorId: z.number(),
+        });
+        ```
+
+    - *[Model]UpdateScalarSchema*
+    
+        The schema for validating the input for updating the model. It only includes scalar fields. All fields are optional (since in Prisma's semantic, all updates are patching).
+
+        ```ts
+        const PostUpdateScalarSchema = z.object({
+          id: z.number(),
+          title: z.string().min(10).max(255),
+          published: z.boolean(),
+          createdAt: z.date(),
+          updatedAt: z.date(),
+        }).partial();
+        ```
+
+    - *[Model]UpdateSchema*
+    
+        Similar to `[Model]UpdateScalarSchema` but includes foreign key fields (all optional). You can use it to validate update input that involves setting foreign keys.
+
+        ```ts
+        const PostUpdateSchema = z.object({
+          id: z.number(),
+          title: z.string().min(10).max(255),
+          published: z.boolean(),
+          createdAt: z.date(),
+          updatedAt: z.date(),
+          authorId: z.number(),
+        }).partial();
+        ```
+
+    - *[Model]???WithoutRefinementSchema*
+  
+        This includes `[Model]WithoutRefinementSchema`, `[Model]CreateWithoutRefinementSchema`, and `[Model]UpdateWithoutRefinementSchema`. These are only generated when a model contains `@@validate` model-level data validation rules.
+
+        The `@@validate` rules are translated into `z.refine()` calls. For example, for the `Post` model, the following refinement function is generated:
+
+        ```ts
+        function refinePost<T, D extends z.ZodTypeDef>(schema: z.ZodType<T, D, T>) {
+          return schema.refine((value: any) => new RegExp('^[A-Za-z0-9 ]+$').test(value?.title));
+        }
+        ```
+
+        However, zod has a limitation that, when you call `.refine()` on a `ZodObject` schema, the result won't be an object schema anymore, which means that you can't use `.omit()`, `.partial()`, etc. `ZodObject` methods to further tune it for your needs anymore. That's why a series of `WithoutRefinementSchema` schemas are exported. They are the Zod schemas prior to calling the `refine()` method. If you need to make changes to the schema while still preserving the `@@validate` rules, you can manipulate the `WithoutRefinementSchema` schemas and then call the `refine` function manually on the result. E.g.:
+
+        ```ts
+        const myPostCreateSchema = refinePost(PostCreateWithoutRefinementSchema.omit({id: true}));
+        ```
+
+    - *[Model]PrismaCreateSchema*
+  
+        Used internally by ZenStack.
+
+    - *[Model]PrismaUpdateSchema*
+  
+        Used internally by ZenStack.
+
+- `zod/input`
+
+    The schema for validating the input arguments of Prisma CRUD operations. You usually won't use them directly. The [tRPC plugin](./trpc) relies on them to validate the input arguments in the generated routers.
+
+- `zod/objects`
+
+    The schema for objects and enums used by the `zod/input` schemas. You usually won't use them directly.
+
+### Installation
+
+This plugin is built-in to ZenStack and does not need to be installed separately.
+
+### Options
+
+| Name   | Type   | Description      | Required | Default                    |
+| ------ | ------ | ---------------- | -------- | -------------------------- |
+| output | String | Output directory (relative to the path of ZModel) | No       | node_modules/.zenstack/zod |
+| modelOnly | Boolean | Only generate schemas for the models, but not for the Prisma CRUD input arguments | No | false |
+| generateModels | String, String[] | Array or comma separated string for the models to generate routers for. | No      | All models |
+| compile | Boolean | If the generated TS code should be compiled to JS | No | true |
+| preserveTsFiles | Boolean | If the generated TS files should be preserved (after compiled to JS) | No | true if `compile` is set to false, otherwise false |
+| noUncheckedInput | Boolean | Disables schema generation for Prisma's ["Unchecked"](https://github.com/prisma/prisma/discussions/10121#discussioncomment-1621254) input types | No | false |
+
+:::info
+When the `generateModels` option is used to specify a list of models to generate, the plugin will also recursively traverse and include all models that are referenced by the specified models. This can result in more code being generated than you expect.
+:::
+
+## Example
+
+Declare the plugin in your ZModel file:
+
+```zmodel title='/schema.zmodel'
+plugin zod {
+  provider = '@core/zod'
+}
+```
+
+Then you can import and use the generated schemas:
+
+```ts
+import { PostCreateSchema } from '@zenstackhq/runtime/zod/models';
+
+PostCreateSchema.parse(data);
+```
+
+You can turn off the `compile` option and use a custom `output` location if you want the generated Zod schema to be compiled along with your own Typescript project:
+
+```zmodel title='/schema.zmodel'
+plugin zod {
+  provider = '@core/zod'
+  output = 'src/lib/zod'
+  compile = false
+}
+```
diff --git a/versioned_docs/version-1.x/reference/runtime-api.md b/versioned_docs/version-1.x/reference/runtime-api.md
new file mode 100644
index 00000000..16c4d22e
--- /dev/null
+++ b/versioned_docs/version-1.x/reference/runtime-api.md
@@ -0,0 +1,295 @@
+---
+description: Runtime API references
+sidebar_position: 3
+sidebar_label: Runtime API
+---
+
+# Runtime API Reference
+
+This document provides references for runtime APIs exported from the `@zenstackhq/runtime` package.
+
+## Prisma Enhancement API
+
+Enhancement APIs create wrappers around a Prisma client to add additional behavior. These wrappers can be freely combined to fine-tune what behavior to include.
+
+### enhance
+
+#### Description
+
+Calling `enhance` is the simplest way to include all essential enhancements offered by ZenStack, including access policies, field validation, field omission, and password hashing. It's equivalent to calling:
+
+```ts
+withOmit(withPassword(withPolicy(prisma, options)));
+```
+
+#### Signature
+
+```ts
+function enhance<DbClient extends object>(
+    prisma: DbClient,
+    context?: WithPolicyContext,
+    options?: EnhancementOptions
+): DbClient;
+```
+
+##### Parameter `prisma`
+
+The PrismaClient instance to enhance.
+
+##### Parameter `context`
+
+The context to for evaluating access policies with the following typing.
+
+```ts
+type WithPolicyContext = {
+    user?: Record<string, unknown>
+};
+```
+
+| Field | Description |
+| ----- | ----------- |
+| user  | The user object that provides value for the `auth()` function call in access policies. If provided. Its shape should be consistent with the `User` model in your ZModel, with all fields optional except for id field(s). Pass `undefined` to represent an anonymous user, and the `auth()` function call will evaluate to `null` in that case. |
+
+##### Parameter `options`
+
+Options with the following typing.
+
+```ts
+type TransactionIsolationLevel =
+    | 'ReadUncommitted'
+    | 'ReadCommitted'
+    | 'RepeatableRead'
+    | 'Snapshot'
+    | 'Serializable';
+
+type EnhancementOptions = {
+    loadPath?: string;
+    policy?: PolicyDef;
+    modelMeta?: ModelMeta;
+    logPrismaQuery?: boolean;
+    prismaModule?: any;
+    transactionMaxWait?: number;
+    transactionTimeout?: number;
+    transactionIsolationLevel?: TransactionIsolationLevel;
+};
+```
+
+| Field          | Description                                                                                      | Default                                                                         |
+| -------------- | ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------- |
+| loadPath       | Path for loading CLI-generated code, including model metadata, access policies, and zod schemas. | node_modules/.zenstack                                                          |
+| policy         | The access policy data, generated by `@core/access-policy` plugin.                               | Loaded from the default location or the path specified by the "loadPath" option |
+| modelMeta      | The model metadata, generated by `@core/model-meta` plugin.                                      | Loaded from the default location or the path specified by the "loadPath" option |
+| zodSchemas     | The zod schemas, generated by `@core/zod` plugin.                                                | Loaded from the default location or the path specified by the "loadPath" option |
+| logPrismaQuery | Whether to log queries sent to Prisma client. Log will be emitted with "info" level, so please make sure you [turn that level on](https://www.prisma.io/docs/concepts/components/prisma-client/working-with-prismaclient/logging#log-to-stdout) when creating Prisma client | false                        |
+| prismaModule     | The `Prisma` module generated together with `PrismaClient`. You only need to pass it when you specified a custom `PrismaClient` output path. The module can be loaded like: `import { Prisma } from '<your PrismaClient output path>';`. |
+| transactionMaxWait        | The `maxWait` option (in ms) passed to `prisma.$transaction()` call for transactions initiated by ZenStack. |       100000                  |
+| transactionTimeout        | The `timeout` option (in ms) passed to `prisma.$transaction()` call for transactions initiated by ZenStack. |       100000                  |
+| transactionIsolationLevel | The `isolationLevel` option passed to `prisma.$transaction()` call for transactions initiated by ZenStack. |  Database default                       |
+
+#### Example
+
+```ts
+const session = getSession();
+const enhancedClient = enhance(prisma, { user: session.user });
+```
+
+### withPresets
+
+#### Description
+
+:::warning
+This API is equivalent to [`enhance`](#enhance) and will be deprecated soon.
+:::
+
+### withPolicy
+
+Use `withPolicy` to include enhancements of access policy (`@@allow` and `@@deny`) and field validation (`@length`, `@email`, etc.).
+
+Access policies and validation rules are processed by the `@core/access-policy` plugin, which transforms the attributes into objects that can be loaded at runtime by default into `node_modules/.zenstack` folder. `withPolicy` function also loads from there by default.
+
+If your access policy rules use the `auth()` function, evaluating it requires access to the current user's identity. You need to pass in a context user object that at least contains the user id.
+
+#### Signature
+
+```ts
+function withPolicy<DbClient extends object>(
+    prisma: DbClient,
+    context?: WithPolicyContext,
+    options?: WithPolicyOptions
+): DbClient;
+```
+
+##### Parameter `prisma`
+
+The PrismaClient instance to enhance.
+
+##### Parameter `context`
+
+The context to for evaluating access policies with the following typing.
+
+```ts
+type WithPolicyContext = {
+    user?: Record<string, unknown>
+};
+```
+
+| Field | Description | Default |
+| ----- | ----------- | ------- |
+| user  | The user object that provides value for the `auth()` function call in access policies. If provided. Its shape should be consistent with the `User` model in your ZModel, with all fields optional except for id field(s). Pass `undefined` to represent an anonymous user, and the `auth()` function call will evaluate to `null` in that case. | undefined |
+
+##### Parameter `options`
+
+Options with the following typing.
+
+```ts
+type TransactionIsolationLevel =
+    | 'ReadUncommitted'
+    | 'ReadCommitted'
+    | 'RepeatableRead'
+    | 'Snapshot'
+    | 'Serializable';
+
+type WithPolicyOptions = {
+    loadPath?: string;
+    policy?: PolicyDef;
+    modelMeta?: ModelMeta;
+    logPrismaQuery?: boolean;
+    transactionMaxWait?: number;
+    transactionTimeout?: number;
+    transactionIsolationLevel?: TransactionIsolationLevel;
+};
+```
+
+| Field          | Description                                                                                      | Default                                                                         |
+| -------------- | ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------- |
+| loadPath       | Path for loading CLI-generated code, including model metadata, access policies, and zod schemas. | node_modules/.zenstack                                                          |
+| policy         | The access policy data, generated by `@core/access-policy` plugin.                               | Loaded from the default location or the path specified by the "loadPath" option |
+| modelMeta      | The model metadata, generated by `@core/model-meta` plugin.                                      | Loaded from the default location or the path specified by the "loadPath" option |
+| zodSchemas     | The zod schemas, generated by `@core/zod` plugin.                                                | Loaded from the default location or the path specified by the "loadPath" option |
+| logPrismaQuery | Whether to log queries sent to Prisma client. Log will be emitted with "info" level, so please make sure you [turn that level on](https://www.prisma.io/docs/concepts/components/prisma-client/working-with-prismaclient/logging#log-to-stdout) when creating Prisma client | false                        |
+| transactionMaxWait        | The `maxWait` option (in ms) passed to `prisma.$transaction()` call for transactions initiated by ZenStack. |       100000                  |
+| transactionTimeout        | The `timeout` option (in ms) passed to `prisma.$transaction()` call for transactions initiated by ZenStack. |       100000                  |
+| transactionIsolationLevel | The `isolationLevel` option passed to `prisma.$transaction()` call for transactions initiated by ZenStack. |  Database default                       |
+
+#### Example
+
+```zmodel
+// ZModel
+model Post {
+    id String @id
+    title String
+    author User @relation(fields: [authorId], references: [id])
+    authorId String
+
+    @@allow('read', auth() == author)
+}
+```
+
+```ts
+const session = getSession();
+const enhanced = withPolicy(prisma, { user: session.user });
+// only posts belonging to the current user are returned
+const posts = enhanced.post.findMany();
+```
+
+### withOmit
+
+Use `withOmit` function to include support for the `@omit` attribute. Fields marked with the attribute will be removed from the entities when they're returned.
+
+#### Signature
+
+```ts
+function withOmit<DbClient extends object>(
+    prisma: DbClient,
+    options?: WithOmitOptions
+): DbClient;
+```
+
+##### Parameter `prisma`
+
+The PrismaClient instance to enhance.
+
+##### Parameter `options`
+
+Options with the following typing.
+
+```ts
+type WithOmitOptions = {
+    loadPath?: string;
+    modelMeta?: ModelMeta;
+};
+```
+
+| Field          | Description                                                                                      | Default                                                                         |
+| -------------- | ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------- |
+| loadPath       | Path for loading CLI-generated code, including model metadata, access policies, and zod schemas. | node_modules/.zenstack                                                          |
+| modelMeta      | The model metadata, generated by `@core/model-meta` plugin. Only need to pass it if you configured the plugin to generate into custom location.        | Loaded from default location |
+
+#### Example
+
+```zmodel
+// ZModel
+model User {
+    id String @id
+    email String
+    password String @omit
+}
+```
+
+```ts
+const enhanced = withOmit(prisma);
+// password field is removed from user entities
+const user = enhanced.user.findMany();
+```
+
+### withPassword
+
+Use `withPassword` function to include support for the `@password` attribute. Fields marked with the attribute will be automatically hashed (using [bcryptjs](https://www.npmjs.com/package/bcryptjs)) before being stored.
+
+#### Signature
+
+```ts
+function withPassword<DbClient extends object = any>(
+    prisma: DbClient,
+    options?: WithPasswordOptions
+): DbClient;
+```
+
+##### Parameter `prisma`
+
+The PrismaClient instance to enhance.
+
+##### Parameter `options`
+
+Options with the following typing.
+
+```ts
+type WithPasswordOptions = {
+    loadPath?: string;
+    modelMeta?: ModelMeta;
+};
+```
+
+| Field          | Description                                                                                      | Default                                                                         |
+| -------------- | ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------- |
+| loadPath       | Path for loading CLI-generated code, including model metadata, access policies, and zod schemas. | node_modules/.zenstack                                                          |
+| modelMeta      | The model metadata, generated by `@core/model-meta` plugin.                                      | Loaded from the default location or the path specified by the "loadPath" option |
+
+#### Example
+
+```zmodel
+// ZModel
+model User {
+    id String @id
+    email String
+    password String @password
+}
+```
+
+```ts
+const enhanced = withPassword(prisma);
+// password field is hashed before stored into the database
+const user = enhanced.user.create({
+    data: { email: 'foo@bar.com', password: 'mysecurepassword' },
+});
+```
diff --git a/versioned_docs/version-1.x/reference/server-adapters/_category_.yml b/versioned_docs/version-1.x/reference/server-adapters/_category_.yml
new file mode 100644
index 00000000..21ccbbc0
--- /dev/null
+++ b/versioned_docs/version-1.x/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/versioned_docs/version-1.x/reference/server-adapters/_error-handling.md b/versioned_docs/version-1.x/reference/server-adapters/_error-handling.md
new file mode 100644
index 00000000..db2a9b05
--- /dev/null
+++ b/versioned_docs/version-1.x/reference/server-adapters/_error-handling.md
@@ -0,0 +1,3 @@
+### Error Handling
+
+Refer to the specific sections for [RPC Handler](./api-handlers/rpc#http-status-code-and-error-responses) and [RESTful Handler](./api-handlers/rest#error-handling).
diff --git a/versioned_docs/version-1.x/reference/server-adapters/_options.mdx b/versioned_docs/version-1.x/reference/server-adapters/_options.mdx
new file mode 100644
index 00000000..89663c37
--- /dev/null
+++ b/versioned_docs/version-1.x/reference/server-adapters/_options.mdx
@@ -0,0 +1,74 @@
+import CodeBlock from '@theme/CodeBlock';
+
+-   getPrisma (required)
+
+    <blockquote>{props.getPrisma}</blockquote>
+
+    A callback for getting a PrismaClient instance for talking to the database. Usually you'll use an enhanced instance created with ZenStack's [`enhance`](../runtime-api#enhance) API to ensure access policies are enforced.
+
+-   logger (optional)
+
+    <blockquote>{'LoggerConfig'}</blockquote>
+
+    Configuration for customizing logging behavior.
+
+-   modelMeta (optional)
+
+    <blockquote>{'ModelMeta'}</blockquote>
+
+    Model metadata. By default loaded from the `node_module/.zenstack/model-meta` module. You can pass it in explicitly if you configured ZenStack to output to a different location. E.g.: `require('output/model-meta').default`.
+
+-   zodSchemas (optional)
+
+    <blockquote>{'ModelZodSchema | boolean | undefined'}</blockquote>
+
+    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. E.g.:
+
+    ```ts
+    factory({
+        ...
+        zodSchemas: require('./zod'),
+    });
+    ```
+
+    Not passing this option or passing in `undefined` disables input validation.
+
+-   useSuperJson (optional)
+
+    <blockquote>boolean</blockquote>
+
+    Whether to use [superjson](https://github.com/blitz-js/superjson) for data serialization. Defaults to `false`.
+
+    _NOTE_: This options is decprecated. The server adapters now always use superjson for data serialization.
+
+-   handler (optional)
+
+    <blockquote>{'(req: RequestContext) => Promise<Response>'}</blockquote>
+
+    The request handler function. This option determines the API endpoints and its input and output formats. Currently ZenStack supports two styles of APIs: RPC (the default) and RESTful.
+
+    - RPC
+
+        The goal of the RPC-style API handler is to fully mirror [PrismaClient's API](https://www.prisma.io/docs/reference/api-reference/prisma-client-reference#model-queries) across the network, so that developers can continue enjoying the convenience and flexibility of Prisma's query syntax. This is the default choice for the `handler` option.
+
+        The RPC-style handler can be created like:
+
+        ```ts
+        import RPCApiHandler from '@zenstackhq/server/api/rpc';
+        const handler = RPCApiHandler();
+        ```
+
+        For more details, please check out [RPC API Handler](./api-handlers/rpc).
+
+    - RESTful
+
+        The goal of RESTful-style API handler is to provide a resource-centric RESTful API using [JSON:API](https://jsonapi.org/) as transportation format.
+
+        The RESTful-style handler can be created like:
+
+        ```ts
+        import RestApiHandler from '@zenstackhq/server/api/rest';
+        const handler = RestApiHandler({ endpoint: 'http://myhost/api' });
+        ```
+
+        For more details, please check out [RESTful API Handler](./api-handlers/rest).
diff --git a/versioned_docs/version-1.x/reference/server-adapters/_using-api.mdx b/versioned_docs/version-1.x/reference/server-adapters/_using-api.mdx
new file mode 100644
index 00000000..1d16241c
--- /dev/null
+++ b/versioned_docs/version-1.x/reference/server-adapters/_using-api.mdx
@@ -0,0 +1,137 @@
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
+The APIs can be used in the following three ways:
+
+1. With generated client hooks
+
+    ZenStack provides plugins to generate client hooks from the ZModel targeting the most popular frontend data fetching libraries: [TanStack Query](https://tanstack.com/query/latest) and [SWR](https://swr.vercel.app/). The generated hooks can be used to make API calls to the server adapters. Refer to the follow docs for detailed usage:
+
+    - [`@zenstackhq/tanstack-query`](../plugins/tanstack-query)
+    - [`@zenstackhq/swr`](../plugins/swr)
+    <br/><br/>
+
+    :::info
+    The generated client hooks assumes the server adapter uses [RPC-style API handler](./api-handlers/rpc) (which is the default setting).
+    :::
+
+1. With direct HTTP calls
+
+    You can make direct HTTP calls to the server adapter using your favorite client libraries like `fetch` or `axios`. Refer to the documentation of the [API Handlers](./api-handlers/) for the API endpoints and data formats.
+
+    Here's an example using `fetch`:
+
+    <Tabs>
+    <TabItem value="rpc" label="RPC Handler">
+
+    ```ts
+    // create a user with two posts
+    const r = await fetch(`/api/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": 1,
+        "email": "user1@abc.com",
+        "posts": [
+            {
+                "id": 1,
+                "createdAt": "2023-03-14T07:45:04.036Z",
+                "updatedAt": "2023-03-14T07:45:04.036Z",
+                "title": "Post 1",
+                "authorId": 1
+            },
+            {
+                "id": 2,
+                "createdAt": "2023-03-14T07:45:04.036Z",
+                "updatedAt": "2023-03-14T07:45:04.036Z",
+                "title": "Post 2",
+                "authorId": 1
+            }
+        ]
+    }
+    ```
+
+    </TabItem>
+
+    <TabItem value="rest" label="RESTful Handler">
+
+    ```ts
+    // create a user and attach two posts
+    const r = await fetch(`/api/user`, {
+        method: 'POST',
+        headers: { 'Content-Type': 'application/vnd.api+json' },
+        body: JSON.stringify({
+            data: {
+                type: 'user',
+                attributes: {
+                    email: 'user1@abc.com'
+                },
+                relationships: {
+                    posts: {
+                        data: [
+                            { type: 'post', id: 1 },
+                            { type: 'post', id: 2 }
+                        ]
+                    }
+                }
+            }
+        })
+    });
+
+    console.log(await r.json());
+    ```
+
+    Output:
+
+    ```json
+    {
+        "jsonapi": { "version": "1.1" },
+        "data": {
+            "type": "user",
+            "id": 1,
+            "attributes": {
+                "email": "user1@abc.com",
+            },
+            "links": {
+                "self": "http://localhost/api/user/1",
+            },
+            "relationships": {
+                "posts": {
+                    "links": {
+                        "self": "http://localhost/api/user/1/relationships/posts",
+                        "related": "http://localhost/api/user/1/posts",
+                    },
+                    "data": [
+                        { "type": "post", "id": 1 },
+                        { "type": "post", "id": 2 },
+                    ],
+                },
+            },
+        },
+    }
+    ```
+
+    </TabItem>
+
+    </Tabs>
+
+1. With third-party client generators
+
+    ZenStack provides an [OpenAPI](../plugins/openapi) plugin for generating Open API 3.x specification from the ZModel. The generated OpenAPI spec can be used to generate client libraries for various languages and frameworks. For example, you can use [openapi-typescript](https://github.com/drwpow/openapi-typescript) to generate a typescript client.
diff --git a/versioned_docs/version-1.x/reference/server-adapters/api-handlers/_data_type_serialization.md b/versioned_docs/version-1.x/reference/server-adapters/api-handlers/_data_type_serialization.md
new file mode 100644
index 00000000..d22a602f
--- /dev/null
+++ b/versioned_docs/version-1.x/reference/server-adapters/api-handlers/_data_type_serialization.md
@@ -0,0 +1,16 @@
+- `DateTime`
+
+    ISO 8601 string
+
+- `Bytes`
+
+    Base64-encoded string
+
+- `BigInt`
+
+    String representation
+
+- `Decimal`
+
+    String representation
+    
\ No newline at end of file
diff --git a/versioned_docs/version-1.x/reference/server-adapters/api-handlers/index.mdx b/versioned_docs/version-1.x/reference/server-adapters/api-handlers/index.mdx
new file mode 100644
index 00000000..df4074ec
--- /dev/null
+++ b/versioned_docs/version-1.x/reference/server-adapters/api-handlers/index.mdx
@@ -0,0 +1,31 @@
+---
+sidebar_position: 100
+---
+
+import useBaseUrl from '@docusaurus/useBaseUrl';
+import ThemedImage from '@theme/ThemedImage';
+
+# API Handlers
+
+ZenStack supports two API styles: ***rpc*** and ***rest***:
+
+- ***rpc*** style API directly mirrors Prisma client's API, thus provides endpoints like `[model]/findMany`, `[model]/create`, etc. Its input and output data format also aligns with that of Prisma client.
+
+- ***rest*** style API is designed to be more RESTful, thus provides endpoints like `[model]`, `[model]/[id]`, `[model]/[id]/relationships/[relationship]`, etc. It uses [JSON:API](https://jsonapi.org/) as its data format.
+
+API handlers are framework-agnostic and deals with canonicalized request and response objects. It's the responsibility of the server adapters to translate the framework-specific request and response types. You can use either of these two API handlers with any server adapter.
+
+The following diagram illustrates their relationships with server adapters.
+
+<ThemedImage
+    alt="ZModel Generation"
+    sources={{
+        light: useBaseUrl('/img/intro/api-handler-light.png'),
+        dark: useBaseUrl('/img/intro/api-handler-dark.png'),
+    }}
+/>
+
+Checkout the reference pages for detailed information about these API handlers:
+
+- [RPC API Handler](./rpc)
+- [RESTful API Handler](./rest)
diff --git a/versioned_docs/version-1.x/reference/server-adapters/api-handlers/rest.mdx b/versioned_docs/version-1.x/reference/server-adapters/api-handlers/rest.mdx
new file mode 100644
index 00000000..1b73d963
--- /dev/null
+++ b/versioned_docs/version-1.x/reference/server-adapters/api-handlers/rest.mdx
@@ -0,0 +1,867 @@
+---
+description: RESTful-style API handler that provides resource-centric endpoints
+sidebar_position: 2
+title: RESTful API Handler
+---
+
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+import DataTypeSerialization from './_data_type_serialization.md';
+
+# RESTful API Handler
+
+## Introduction
+
+The RESTful-style API handler exposes CRUD APIs as RESTful endpoints using [JSON:API](https://jsonapi.org/) as transportation format. The API handler is not meant to be used directly; instead, you should use it together with a [server adapter](../../../category/server-adapters) which handles the request and response API for a specific framework.
+
+It can be created as the following:
+
+<Tabs>
+<TabItem value="next" label="Next.js">
+
+```ts title='/src/pages/api/model/[...path].ts'
+import { NextRequestHandler } from '@zenstackhq/server/next';
+import RestApiHandler from '@zenstackhq/server/api/rest';
+import { getPrisma } from '../../lib/db';
+
+export default NextRequestHandler({ 
+    getPrisma,
+    handler: RestApiHandler({ endpoint: 'http://myhost/api' }) 
+});
+```
+
+</TabItem>
+
+<TabItem value="sveltekit" label="SvelteKit">
+
+```ts title='/src/hooks.server.ts'
+import zenstack from '@zenstackhq/server/sveltekit';
+import RestApiHandler from '@zenstackhq/server/api/rest';
+import { getPrisma } from './lib/db';
+
+export const handle = zenstack.SvelteKitHandler({
+    prefix: '/api/model',
+    getPrisma,
+    handler: RestApiHandler({ endpoint: 'http://myhost/api/model' })
+});
+```
+
+</TabItem>
+
+<TabItem value="nuxt" label="Nuxt">
+
+```
+🚧 Coming soon
+```
+
+</TabItem>
+
+</Tabs>
+
+The factory function accepts an options object with the following fields:
+
+- endpoint
+
+  Required. A `string` field representing the base URL of the RESTful API, used for generating resource links.
+
+- pageSize
+
+  Optional. A `number` field representing the default page size for listing resources and relationships. Defaults to 100. Set to Infinity to disable pagination.
+
+## Endpoints and Features
+
+The RESTful API handler conforms to the the [JSON:API](https://jsonapi.org/format/) v1.1 specification for its URL design and input/output format. The following sections list the endpoints and features are implemented. The examples refer to the following schema modeling a blogging app:
+
+```zmodel
+model User {
+    id Int @id @default(autoincrement())
+    email String
+    posts Post[]
+}
+
+model Profile {
+    id Int @id @default(autoincrement())
+    gender String
+    user User @relation(fields: [userId], references: [id])
+    userId Int @unique
+}
+
+model Post {
+    id Int @id @default(autoincrement())
+    title String
+    published Boolean @default(false)
+    viewCount Int @default(0)
+    author User @relation(fields: [authorId], references: [id])
+    authorId Int
+    comments Comment[]
+}
+
+model Comment {
+    id Int @id @default(autoincrement())
+    content String
+    post Post @relation(fields: [postId], references: [id])
+    postId Int
+}
+```
+
+### Listing resources
+
+A specific type of resource can be listed using the following endpoint:
+
+```
+GET /:type
+```
+
+#### Status codes
+
+- 200: The request was successful and the response body contains the requested resources.
+- 400: The request was malformed.
+- 403: The request was forbidden.
+- 404: The requested resource type does not exist.
+
+#### Examples
+
+```ts
+GET /post
+```
+
+```json
+{
+    "meta": {
+        "total": 1
+    },
+    "data": [
+        {
+            "attributes": {
+                "authorId": 1,
+                "published": true,
+                "title": "My Awesome Post",
+                "viewCount": 0
+            },
+            "id": 1,
+            "links": {
+                "self": "http://myhost/api/post/1"
+            },
+            "relationships": {
+                "author": {
+                    "data": { "id": 1, "type": "user" },
+                    "links": {
+                        "related": "http://myhost/api/post/1/author/1",
+                        "self": "http://myhost/api/post/1/relationships/author/1"
+                    }
+                }
+            },
+            "type": "post"
+        }
+    ],
+    "jsonapi": {
+        "version": "1.1"
+    },
+    "links": {
+        "first": "http://myhost/api/post?page%5Blimit%5D=100",
+        "last": "http://myhost/api/post?page%5Boffset%5D=0",
+        "next": null,
+        "prev": null,
+        "self": "http://myhost/api/post"
+    }
+}
+```
+
+### Fetching a resource
+
+A unique resource can be fetched using the following endpoint:
+
+```ts
+GET /:type/:id
+```
+
+#### Status codes
+
+- 200: The request was successful and the response body contains the requested resource.
+- 400: The request was malformed.
+- 403: The request was forbidden.
+- 404: The requested resource type or ID does not exist.
+
+#### Examples
+
+```ts
+GET /post/1
+```
+
+```json
+{
+    "data": {
+        "attributes": {
+            "authorId": 1,
+            "published": true,
+            "title": "My Awesome Post",
+            "viewCount": 0
+        },
+        "id": 1,
+        "links": {
+            "self": "http://myhost/api/post/1"
+        },
+        "relationships": {
+            "author": {
+                "data": { "id": 1, "type": "user" },
+                "links": {
+                    "related": "http://myhost/api/post/1/author/1",
+                    "self": "http://myhost/api/post/1/relationships/author/1"
+                }
+            }
+        },
+        "type": "post"
+    },
+    "jsonapi": {
+        "version": "1.1"
+    },
+    "links": {
+        "self": "http://myhost/api/post/1"
+    }
+}
+```
+
+### Fetching relationships
+
+A resource's relationships can be fetched using the following endpoint:
+
+```ts
+GET /:type/:id/relationships/:relationship
+```
+
+#### Status codes
+
+- 200: The request was successful and the response body contains the requested relationships.
+- 400: The request was malformed.
+- 403: The request was forbidden.
+- 404: The requested resource type, ID, or relationship does not exist.
+
+#### Examples
+
+1. Fetching a to-one relationship
+
+    ```ts
+    GET /post/1/relationships/author
+    ```
+
+    ```json
+    {
+        "data" : { "id" : 1, "type" : "user" },
+        "jsonapi" : {
+            "version" : "1.1"
+        },
+        "links" : {
+            "self" : "http://myhost/api/post/1/relationships/author"
+        }
+    }
+    ```
+
+1. Fetching a to-many relationship
+
+    ```ts
+    GET /user/1/relationships/posts
+    ```
+
+    ```json
+    {
+        "data" : [
+            { "id" : 1, "type" : "post" },
+            { "id" : 2, "type" : "post" }
+        ],
+        "jsonapi" : {
+            "version" : "1.1"
+        },
+        "links" : {
+            "first" : "http://myhost/api/user/1/relationships/posts?page%5Blimit%5D=100",
+            "last" : "http://myhost/api/user/1/relationships/posts?page%5Boffset%5D=0",
+            "next" : null,
+            "prev" : null,
+            "self" : "http://myhost/api/user/1/relationships/posts"
+        }
+    }
+    ```
+
+### Fetching related resources
+
+```ts
+GET /:type/:id/:relationship
+```
+
+#### Status codes
+
+- 200: The request was successful and the response body contains the requested relationship.
+- 400: The request was malformed.
+- 403: The request was forbidden.
+- 404: The requested resource type, ID, or relationship does not exist.
+
+#### Examples
+
+```ts
+GET /post/1/author
+```
+
+```json
+{
+   "data" : {
+      "attributes" : {
+         "email" : "emily@zenstack.dev",
+         "name" : "Emily"
+      },
+      "id" : 1,
+      "links" : {
+         "self" : "http://myhost/api/user/1"
+      },
+      "relationships" : {
+         "posts" : {
+            "links" : {
+               "related" : "http://myhost/api/user/1/posts",
+               "self" : "http://myhost/api/user/1/relationships/posts"
+            }
+         }
+      },
+      "type" : "user"
+   },
+   "jsonapi" : {
+      "version" : "1.1"
+   },
+   "links" : {
+      "self" : "http://myhost/api/post/1/author"
+   }
+}
+```
+
+### Fine-grained data fetching
+
+#### Filtering
+
+You can use the `filter[:selector1][:selector2][...]=value` [query parameter family](https://jsonapi.org/format/#query-parameters-families) to filter resource collections or relationship collections.
+
+##### Examples
+
+1. Equality filter against plain field
+
+    ```ts
+    GET /api/post?filter[published]=false
+    ```
+
+1. Equality filter against relationship
+
+    Relationship field can be filtered directly by its id.
+
+    ```ts
+    GET /api/post?filter[author]=1
+    ```
+
+    If the relationship is to-many, the filter has "some" semantic and evaluates to `true` if any of the items in the relationship matches.
+
+    ```ts
+    GET /api/user?filter[posts]=1
+    ```
+
+1. Filtering with multiple values
+
+    Multiple filter values can be separated by comma. Items statisfying any of the values will be returned.
+
+    ```ts
+    GET /api/post?filter[author]=1,2
+    ```
+
+1. Multiple filters
+
+    A request can carry multiple filters. Only items statisfying all filters will be returned.
+
+    ```ts
+    GET /api/post?filter[author]=1&filter[published]=true
+    ```
+
+1. Deep filtering
+
+    A filter can carry multiple field selectors to reach into relationships.
+
+    ```ts
+    GET /api/post?filter[author][name]=Emily
+    ```
+
+    When reaching into a to-many relationship, the filter has "some" semantic and evaluates to `true` if any of the items in the relationship matches.
+
+    ```
+    GET /api/user?filter[posts][published]=true
+    ```
+    
+1. Filtering with comparison operators
+
+    Filters can go beyond equality by appending an "operator suffix".
+
+    ```ts
+    GET /api/post?filter[viewCount$gt]=100
+    ```
+
+    The following operators are supported:
+
+    - **$lt**
+
+        Less than
+
+    - **$lte**
+    
+        Less than or equal to
+
+    - **$gt**
+    
+        Greater than
+
+    - **$gte**
+    
+        Greater than or equal to
+
+    - **$contains**
+        
+        String contains
+
+    - **$icontains**
+    
+        Case-insensitive string contains
+
+    - **$search**
+        
+        String full-text search
+
+    - **$startsWith**
+        
+        String starts with
+
+    - **$endsWith**
+    
+        String ends with
+
+    - **$has**
+    
+        Collection has value
+
+    - **$hasEvery**
+    
+        Collection has every element in value
+
+    - **$hasSome**
+    
+        Collection has some elements in value
+
+    - **$isEmpty**
+    
+        Collection is empty
+
+#### Sorting
+
+You can use the `sort` query parameter to sort resource collections or relationship collections. The value of the parameter is a comma-separated list of fields names. The order of the fields in the list determines the order of sorting. By default, sorting is done in ascending order. To sort in descending order, prefix the field name with a minus sign.
+
+##### Examples
+
+```ts
+GET /api/post?sort=createdAt,-viewCount
+```
+
+#### Pagination
+
+When creating a RESTful API handler, you can pass in a `pageSize` option to control pagination behavior of fetching a collection of resources, related resources, and relationships. By default the page size is 100, and you can disable pagination by setting `pageSize` option to `Infinity`.
+
+When fetching a collection resource or relationship, you can use the `page[offset]=value` and `page[limit]=value` [query parameter family](https://jsonapi.org/format/#query-parameters-families) to fetch a specific page. They're mapped to `skip` and `take` parameters in the query arguments sent to PrismaClient.
+
+The response data of collection fetching contains pagination links that facilitate navigating through the collection. The "meta" section also contains the total count available. E.g.:
+
+```json
+{
+    "meta": {
+        "total": 10
+    },
+    "data" : [
+        ...
+    ],
+    "links" : {
+        "first" : "http://myhost/api/post?page%5Blimit%5D=2",
+        "last" : "http://myhost/api/post?page%5Boffset%5D=4",
+        "next" : "http://myhost/api/post?page%5Boffset%5D=4&page%5Blimit%5D=2",
+        "prev" : "http://myhost/api/post?page%5Boffset%5D=0&page%5Blimit%5D=2",
+        "self" : "http://myhost/api/post"
+    }
+}
+```
+
+##### Examples
+
+1. Fetching a specific page of resources
+
+    ```ts
+    GET /api/post?page[offset]=10&page[limit]=5
+    ```
+
+1. Fetching a specific page of relationships
+
+    ```ts
+    GET /api/user/1/relationships/posts?page[offset]=10&page[limit]=5
+    ```
+
+1. Fetching a specific page of related resources
+
+    ```ts
+    GET /api/user/1/posts?page[offset]=10&page[limit]=5
+    ```
+
+#### Including related resources
+
+You can use the `include` query parameter to include related resources in the response. The value of the parameter is a comma-separated list of fields names. Field names can contain dots to reach into nested relationships.
+
+When including related resources, the response data takes the form of [Compound Documents](https://jsonapi.org/format/#document-compound-documents) and contains a `included` field carrying normalized related resources. E.g.:
+
+```json
+{
+   "data" : [
+      {
+         "attributes" : {
+            ...
+         },
+         "id" : 1,
+         "relationships" : {
+            "author" : {
+               "data" : { "id" : 1, "type" : "user" }
+            }
+         },
+         "type" : "post"
+      }
+   ],
+   "included" : [
+      {
+         "attributes" : {
+            "email" : "emily@zenstack.dev",
+            "name" : "Emily"
+         },
+         "id" : 1,
+         "links" : {
+            "self" : "http://myhost/api/user/1"
+         },
+         "relationships" : {
+            "posts" : {
+               "links" : {
+                  "related" : "http://myhost/api/user/1/posts",
+                  "self" : "http://myhost/api/user/1/relationships/posts"
+               }
+            }
+         },
+         "type" : "user"
+      }
+   ]
+}
+```
+
+##### Examples
+
+1. Including a direct relationship
+
+    ```ts
+    GET /api/post?include=author
+    ```
+
+1. Including a deep relationship
+
+    ```ts
+    GET /api/post?include=author.profile
+    ```
+
+1. Including multiple relationships
+
+    ```ts
+    GET /api/post?include=author,comments
+    ```
+
+### Creating a resource
+
+A new resource can be created using the following endpoint:
+
+```
+POST /:type
+```
+
+#### Status codes
+
+- 201: The request was successful and the resource was created.
+- 400: The request was malformed.
+- 403: The request was forbidden.
+- 404: The requested resource type does not exist.
+
+#### Examples
+
+1. Creating a resource
+    ```json
+    POST /user
+    {
+        "data": {
+            "type": "user",
+            "attributes": {
+                "name": "Emily",
+                "email": "emily@zenstack.dev"
+            }
+        }
+    }
+    ```
+
+1. Creating a resource with relationships attached
+
+    ```json
+    POST /user
+    {
+        "data": {
+            "type": "user",
+            "attributes": {
+                "name": "Emily",
+                "email": "emily@zenstack.dev"
+            },
+            "relationships": {
+                "posts": {
+                    "data": [{ "type": "post", "id": 1 }]
+                }
+            }
+        }
+    }
+    ```
+
+### Updating a resource
+
+A resource can be updated using the following endpoints:
+
+```ts
+PUT /:type/:id
+PATCH /:type/:id
+```
+
+Both `PUT` and `PATCH` do partial update and has exactly the same behavior.
+
+:::info
+Besides plain fields, you can also include relationships in the request body. Please note that this won't update the related resource; instead if only replaces the relationships. If you update a to-many relationship, the new collection will entirely replace the old one.
+
+Relationships can also be manipulated directly. See [Manipulating Relationships](#manipulating-relationships) for more details.
+:::
+
+#### Status codes
+
+- 200: The request was successful and the resource was updated.
+- 400: The request was malformed.
+- 403: The request was forbidden.
+- 404: The requested resource type or ID does not exist.
+
+#### Examples
+
+1. Updating a resource
+
+    ```json
+    PUT /post/1
+    {
+        "data": {
+            "type": "post",
+            "attributes": {
+                "title": "My Awesome Post"
+            }
+        }
+    }
+    ```
+
+1. Updating a resource's relationships
+
+    ```json
+    PUT /user/1
+    {
+        "data": {
+            "type": "user",
+            "relationships": {
+                "posts": {
+                    "data": [{ "type": "post", "id": 2 }]
+                }
+            }
+        }
+    }
+    ```
+
+
+### Deleting a resource
+
+A resource can be deleted using the following endpoint:
+
+#### Status codes
+
+- 204: The request was successful and the resource was deleted.
+- 403: The request was forbidden.
+- 404: The requested resource type or ID does not exist.
+
+```ts
+DELETE /:type/:id
+```
+
+### Manipulating relationships
+
+Relationships can be manipulated using the following endpoints:
+
+#### Adding to a to-many relationship
+
+```ts
+POST /:type/:id/relationships/:relationship
+```
+
+##### Status codes
+
+- 200: The request was successful and the relationship was updated.
+- 403: The request was forbidden.
+- 404: The requested resource type, ID, or relationship does not exist.
+
+##### Examples
+
+```json
+POST /user/1/relationships/posts
+{
+    "data": [
+        { "type": "post", "id": "1" },
+        { "type": "post", "id": "2" }
+    ]
+}
+```
+
+#### Updating a relationship (to-one or to-many)
+
+```ts
+PUT /:type/:id/relationships/:relationship
+PATCH /:type/:id/relationships/:relationship
+```
+
+:::info
+`PUT` and `PATCH` has exactly the same behavior and both relace the existing relationships with the new ones entirely.
+:::
+
+##### Status codes
+
+- 200: The request was successful and the relationship was updated.
+- 403: The request was forbidden.
+- 404: The requested resource type, ID, or relationship does not exist.
+
+##### Examples
+
+1. Replacing a to-many relationship
+
+    ```json
+    PUT /user/1/relationships/posts
+    {
+        "data": [
+            { "type": "post", "id": "1" },
+            { "type": "post", "id": "2" }
+        ]
+    }
+    ```
+
+1. Replacing a to-one relationship
+
+    ```json
+    PUT /post/1/author
+    {
+        "data": { "type": "user", "id": "2" }
+    }
+    ```
+
+1. Clearing a to-many relationship
+
+    ```json
+    PUT /user/1/relationships/posts
+    {
+        "data": []
+    }
+    ```
+
+1. Clearing a to-one relationship
+
+    ```json
+    PUT /post/1/author
+    {
+        "data": null
+    }
+    ```
+
+## Serialization
+
+ZenStack uses [superjson](https://github.com/blitz-js/superjson) to serialize and deserialize data. Superjson generates two parts during serialization:
+
+- json:
+
+    The JSON-compatible serialization result.
+
+- meta:
+
+    The serialization metadata including information like field types that facilitates deserialization.
+
+If the data only involves simple data types, the serialization result is the same as regular `JSON.stringify`, and no `meta` part is generated. However, for complex data types (like `Bytes`, `Decimal`, etc.), a `meta` object will be generated, which needs to be carried along when sending the request, and will also be included in the response.
+
+When sending requests, if superjson-serializing the request body results in a `meta` object, it should be put into a ```{ "serialization": meta }``` object and included in the `meta` field of the request body. For example, if you have a `bytes` field of type `Bytes`, the request body should look like:
+
+```json
+POST /post
+{
+    "data": {
+        "type": "post",
+        "attributes": {
+            ...
+            "bytes": "AQID" // base64-encoded bytes
+        }
+    },
+    "meta": { 
+        "serialization": {"values": { "data.attributes.bytes": [[ "custom", "Bytes"]] } } 
+    } 
+}
+```
+
+Correspondingly, the response body of a query may look like:
+
+```json
+GET /post/1
+{
+    "data": {
+        "id": "1",
+        "type": "post",
+        "attributes": {
+            ...
+            "bytes": "AQID" // base64-encoded bytes
+        }
+    },
+    "meta": { 
+        "serialization": {"values": { "data.attributes.bytes": [[ "custom", "Bytes"]] } } 
+    } 
+}
+```
+
+You should use the `meta.serialization` field value to superjson-deserialize the response body.
+
+### Data Type Serialization Format
+
+<DataTypeSerialization />
+
+## Error Handling
+
+An error response is an object containing the following fields:
+
+- errors
+
+    An array of error objects, each containing the following fields:
+
+    - code: `string`, error code
+    - status: `number`, HTTP status code
+    - title: `string`, error title
+    - detail: `string`, error detail
+    - prismaCode: `string`, Prisma error code, if the error is thrown by Prisma
+
+### Example
+
+```json
+{
+   "errors" : [
+      {
+         "code" : "unsupported-model",
+         "detail" : "Model foo doesn't exist",
+         "status" : 404,
+         "title" : "Unsupported model type"
+      }
+   ]
+}
+```
diff --git a/versioned_docs/version-1.x/reference/server-adapters/api-handlers/rpc.mdx b/versioned_docs/version-1.x/reference/server-adapters/api-handlers/rpc.mdx
new file mode 100644
index 00000000..689c0cb2
--- /dev/null
+++ b/versioned_docs/version-1.x/reference/server-adapters/api-handlers/rpc.mdx
@@ -0,0 +1,254 @@
+---
+description: RPC-style API handler that fully mirrors PrismaClient's query API
+sidebar_position: 1
+title: RPC API Handler
+---
+
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+import DataTypeSerialization from './_data_type_serialization.md';
+
+# RPC API Handler
+
+## Introduction
+
+The RPC-style API handler exposes CRUD endpoints that fully mirror [PrismaClient's query API](https://www.prisma.io/docs/reference/api-reference/prisma-client-reference#model-queries). Consuming the APIs feels like making RPC calls to a PrismaClient then. The API handler is not meant to be used directly; instead, you should use it together with a [server adapter](../../../category/server-adapters) which handles the request and response API for a specific framework.
+
+It can be created and used as the following:
+
+<Tabs>
+<TabItem value="next" label="Next.js">
+
+```ts title='/src/pages/api/model/[...path].ts'
+import { NextRequestHandler } from '@zenstackhq/server/next';
+import RPCApiHandler from '@zenstackhq/server/api/rpc';
+import { getPrisma } from '../../lib/db';
+
+export default NextRequestHandler({ 
+    getPrisma, 
+    handler: RPCApiHandler() // you can also omit it since `RPCApiHandler` is the default
+});
+```
+
+</TabItem>
+
+<TabItem value="sveltekit" label="SvelteKit">
+
+```ts title='/src/hooks.server.ts'
+import zenstack from '@zenstackhq/server/sveltekit';
+import RPCApiHandler from '@zenstackhq/server/api/rpc';
+import { getPrisma } from './lib/db';
+
+export const handle = zenstack.SvelteKitHandler({
+    prefix: '/api/model',
+    handler: RPCApiHandler(), // you can also omit it since `RPCApiHandler` is the default
+    getPrisma
+});
+```
+
+</TabItem>
+
+<TabItem value="nuxt" label="Nuxt">
+
+```
+🚧 Coming soon
+```
+
+</TabItem>
+
+</Tabs>
+
+## Wire Format
+
+### Input
+
+For endpoints using `GET` and `DELETE` Http verbs, the query body is serialized and passed as the `q` query parameter. E.g.:
+
+```ts
+GET /api/post/findMany?q=%7B%22where%22%3A%7B%22public%22%3Atrue%7D%7D
+```
+
+- Endpoint: /api/post/findMany
+- Query parameters: `q` -> `{ "where" : { "public": true } }`
+
+For endpoints using other HTTP verbs, the query body is passed as `application/json` in the request body. E.g.:
+
+```json
+POST /api/post/create
+{ "data": { "title": "Hello World" } }
+```
+
+### Output
+
+The output shape conforms to the data structure returned by the corresponding PrismaClient API, wrapped into a `data` field. E.g.:
+
+```json
+GET /api/post/findMany
+
+{
+    "data": [ { "id": 1, "title": "Hello World" } ]
+}
+```
+
+### Serialization
+
+This section explains the details about data serialization. If you're using generated hooks to consume the API, the generated code already automatically deals with serialization for you, and you don't need to do any further processing.
+
+ZenStack uses [superjson](https://github.com/blitz-js/superjson) to serialize and deserialize data - including the `q` query parameter, the request body, and the response body. Superjson generates two parts during serialization:
+
+- json:
+
+    The JSON-compatible serialization result.
+
+- meta:
+
+    The serialization metadata including information like field types that facilitates deserialization.
+
+If the data only involves simple data types, the serialization result is the same as regular `JSON.stringify`, and no `meta` part is generated. However, for complex data types (like `Bytes`, `Decimal`, etc.), a `meta` object will be generated, which needs to be carried along when sending the request, and will also be included in the response.
+
+The following part explains how the `meta` information is included for different situations:
+
+- The `q` query parameter
+
+    If during superjson-serialization of the `q` parameter, a `meta` object is generated, it should be put into an object `{ serialization: meta }`, JSON-stringified, and included as an additional query parameter `meta`. For example, if you have a field named `bytes` of `Bytes` type, and you may want to query with a filter like ```{ where: { bytes: Buffer.from([1,2,3]) } }```. Superjson-serializing the query object results in:
+    ```json
+    {
+        "json": { "where": { "bytes": "AQID" } }, // base-64 encoded bytes
+        "meta": { "values": { "where.bytes": [["custom","Bytes"]] } }
+    }
+    ```
+    Your query URL should look like:
+    ```json
+    GET /api/post/findMany?q={"where":{"bytes":"AQID"}}&meta={"serialization":{"values":{"where.bytes":[["custom","Bytes"]]}}}
+    ```
+
+- The request body
+
+    If during superjson-serialization of the request body, a `meta` object is generated, it should be put into an object `{ serialization: meta }`, and included as an additional field `meta` field in the request body. For example, if you have a field named `bytes` of `Bytes` type, and you may want to create a record with a value like ```{ data: { bytes: Buffer.from([1,2,3]) } }```. Superjson-serializing the request body results in:
+    ```json
+    {
+        "json": { "bytes": "AQID" }, // base-64 encoded bytes
+        "meta": { "values": { "bytes": [[ "custom", "Bytes" ]] } }
+    }
+    ```
+    Your request body should look like:
+    ```json
+    POST /api/post/create
+
+    { 
+        "data": { "bytes": "AQID" },
+        "meta": { "serialization": {"values": { "bytes": [[ "custom","Bytes" ]] } } } 
+    }
+    ```
+
+- The response body
+
+    If during superjson-serialization of the response body, a `meta` object is generated, it will be put into an object `{ serialization: meta }`, and included as an additional field `meta` field in the response body. For example, if you have a field named `bytes` of `Bytes` type, and a `findFirst` query returns ```{ id: 1, bytes: Buffer.from([1,2,3]) }```. Superjson-serializing the request body results in:
+    ```json
+    {
+        "json": { "id": 1, "bytes":"AQID" }, // base-64 encoded bytes
+        "meta": { "values": { "bytes": [[ "custom", "Bytes" ]] } }
+    }
+    ```
+    Your response body will look like:
+    ```json
+    GET /api/post/findFirst
+
+    { 
+        "data": { "id": 1, "bytes": "AQID" },
+        "meta": { "serialization": {"values": { "bytes": [[ "custom","Bytes"]] } } } 
+    }
+    ```
+
+    You should use the meta.serialization field value to superjson-deserialize the response body.
+
+#### Data Type Serialization Format
+
+<DataTypeSerialization />
+
+## Endpoints
+
+-   **[model]/findMany**
+
+    _Http method:_ `GET`
+
+-   **[model]/findUnique**
+
+    _Http method:_ `GET`
+
+-   **[model]/findFirst**
+
+    _Http method:_ `GET`
+
+-   **[model]/count**
+
+    _Http method:_ `GET`
+
+-   **[model]/aggregate**
+
+    _Http method:_ `GET`
+
+-   **[model]/groupBy**
+
+    _Http method:_ `GET`
+
+-   **[model]/create**
+
+    _Http method:_ `POST`
+
+-   **[model]/createMany**
+
+    _Http method:_ `POST`
+
+-   **[model]/update**
+
+    _Http method:_ `PATCH` or `PUT`
+
+-   **[model]/updateMany**
+
+    _Http method:_ `PATCH` or `PUT`
+
+-   **[model]/upsert**
+
+    _Http method:_ `POST`
+
+-   **[model]/delete**
+
+    _Http method:_ `DELETE`
+
+-   **[model]/deleteMany**
+
+    _Http method:_ `DELETE`
+
+## HTTP Status Code and Error Responses
+
+### Status code
+
+The HTTP status code used by the endpoints follows the following rules:
+
+- `create` and `createMany` use `201` for success. Other endpoints use `200`.
+- `403` is used for to indicate the request is denied due to lack of permissions, usually caused by access policy violation.
+- `400` is used for invalid requests, e.g., malformed request body.
+- `500` is used for other unexpected errors.
+
+### Error response format
+
+```ts
+{
+    // true to indicate the failure is due to a Prisma error
+    prisma?: boolean;
+
+    // true to indicate the failure is due to access policy violation
+    rejectedByPolicy?: boolean;
+
+    // original Prisma error code, available when `prisma` is true
+    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/versioned_docs/version-1.x/reference/server-adapters/express.mdx b/versioned_docs/version-1.x/reference/server-adapters/express.mdx
new file mode 100644
index 00000000..abc413ec
--- /dev/null
+++ b/versioned_docs/version-1.x/reference/server-adapters/express.mdx
@@ -0,0 +1,66 @@
+---
+title: Express.js
+description: Adapter for integrating with Express.js
+sidebar_position: 4
+---
+
+import ErrorHandling from './_error-handling.md';
+import AdapterOptions from './_options.mdx';
+import UsingAPI from './_using-api.mdx'
+
+# Express.js Adapter
+
+The `@zenstackhq/server/express` module provides a quick way to install API routes onto a [Express.js](https://expressjs.com/) project for database CRUD operations. 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.
+
+### Installation
+
+```bash
+npm install @zenstackhq/server
+```
+
+### Mounting the API
+
+You can integrate ZenStack into your project with the `ZenStackMiddleware` [express middleware](https://expressjs.com/en/guide/using-middleware.html):
+
+```ts
+import { PrismaClient } from '@prisma/client';
+import { enhance } 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/model',
+    ZenStackMiddleware({
+        // getSessionUser extracts the current session user from the request, its
+        // implementation depends on your auth solution
+        getPrisma: (request) => enhance(prisma, { user: getSessionUser(request) }),
+    })
+);
+```
+
+The Express.js adapter takes the following options to initialize:
+
+<AdapterOptions getPrisma='(request: Request, response: Response) => unknown | Promise<unknown>' />
+
+- sendResponse (optional)
+
+    <blockquote>boolean</blockquote>
+
+    Controls if the middleware directly sends a response. If set to false, the response is stored in the `res.locals` object and then the middleware calls the `next()` function to pass the control to the next middleware. Subsequent middleware or request handlers need to make sure to send a response.
+    
+    Defaults to `true`.
+
+### Using the API
+
+<UsingAPI />
+
+<ErrorHandling />
+
+### Fully working example
+
+You can find a fully working example [here](https://github.com/zenstackhq/docs-tutorial-express).
diff --git a/versioned_docs/version-1.x/reference/server-adapters/fastify.mdx b/versioned_docs/version-1.x/reference/server-adapters/fastify.mdx
new file mode 100644
index 00000000..be568e77
--- /dev/null
+++ b/versioned_docs/version-1.x/reference/server-adapters/fastify.mdx
@@ -0,0 +1,56 @@
+---
+title: Fastify
+description: Adapter for integrating with Fastify
+sidebar_position: 5
+---
+
+import ErrorHandling from './_error-handling.md';
+import AdapterOptions from './_options.mdx';
+import UsingAPI from './_using-api.mdx';
+
+# Fastify Adapter
+
+The `@zenstackhq/server/fastify` module provides a quick way to install API routes onto a [Fastify](https://www.fastify.io/) project for database CRUD operations. 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.
+
+### Installation
+
+```bash
+npm install @zenstackhq/server
+```
+
+### Mounting the API
+
+You can integrate ZenStack into your project with the `ZenStackFastifyPlugin` [fastify plugin](https://www.fastify.io/docs/latest/Reference/Plugins/):
+
+```ts
+import { enhance } 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/model
+server.register(ZenStackFastifyPlugin, {
+    prefix: '/api/model',
+    // getSessionUser extracts the current session user from the request, its
+    // implementation depends on your auth solution
+    getPrisma: (request) => enhance(prisma, { user: getSessionUser(request) }),
+});
+```
+
+The Fastify adapter takes the following options to initialize:
+
+- prefix
+
+    <blockquote>string</blockquote>
+
+    Prefix for the mounted API endpoints. E.g.: /api/model.
+
+<AdapterOptions getPrisma='(request: FastifyRequest, reply: FastifyReply) => unknown | Promise<unknown>' />
+
+### Using the API
+
+<UsingAPI />
+
+<ErrorHandling />
diff --git a/versioned_docs/version-1.x/reference/server-adapters/next.mdx b/versioned_docs/version-1.x/reference/server-adapters/next.mdx
new file mode 100644
index 00000000..7b7eedcb
--- /dev/null
+++ b/versioned_docs/version-1.x/reference/server-adapters/next.mdx
@@ -0,0 +1,140 @@
+---
+title: Next.js
+description: Adapter for integrating with Next.js
+sidebar_position: 1
+---
+
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+import ErrorHandling from './_error-handling.md';
+import AdapterOptions from './_options.mdx';
+import UsingAPI from './_using-api.mdx';
+
+# Next.js Server Adapter
+
+The `@zenstackhq/server/next` module provides a quick way to install API endpoints onto a [Next.js](https://nextjs.org/) project for database CRUD operations. 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.
+
+The server adapter supports both the "pages" routes for older versions of Next.js and the new "app" routes for Next.js 13.
+
+### Installation
+
+```bash
+npm install @zenstackhq/server
+```
+
+### Mounting the API
+
+You can use it to create a request handler in an API endpoint like:
+
+<Tabs>
+
+<TabItem value="appdir" label={`App Router`}>
+
+```ts title='/src/app/api/model/[...path]/route.ts'
+import { NextRequestHandler } from '@zenstackhq/server/next';
+import type { NextRequest } from "next/server";
+import { enhance } from '@zenstackhq/runtime';
+import { prisma } from '~/lib/db.ts';
+import { getSessionUser } from '~/lib/auth.ts';
+
+// create an enhanced Prisma client with user context
+function getPrisma(req: NextRequest) {
+    // getSessionUser extracts the current session user from the request, its
+    // implementation depends on your auth solution
+    return enhance(prisma, { user: getSessionUser(req) });
+}
+
+const handler = NextRequestHandler({ getPrisma, useAppDir: true });
+
+export {
+  handler as GET,
+  handler as POST,
+  handler as PUT,
+  handler as PATCH,
+  handler as DELETE,
+};
+```
+
+:::info
+Next.js 13 has issues handling `@zenstackhq/runtime` and `@zenstackhq/server` packages when compiling server components. Please add the following config section to your `next.config.mjs` to exclude them:
+
+```js title='next.config.mjs'
+const config = {
+    experimental: {
+        serverComponentsExternalPackages: [
+            "@zenstackhq/runtime",
+            "@zenstackhq/server",
+        ],
+    },
+}
+```
+:::
+
+The Next.js API route handler takes the following options to initialize:
+
+<AdapterOptions getPrisma='(req: NextRequest) => unknown | Promise<unknown>' />
+
+</TabItem>
+
+<TabItem value="pages" label={`Pages Router`}>
+
+```ts title='/src/pages/api/model/[...path].ts'
+import { NextRequestHandler } from '@zenstackhq/server/next';
+import type { NextApiRequest, NextApiResponse } from 'next';
+import { enhance } from '@zenstackhq/runtime';
+import { prisma } from '~/lib/db.ts';
+import { getSessionUser } from '~/lib/auth.ts';
+
+// create an enhanced Prisma client with user context
+function getPrisma(req: NextApiRequest, res: NextApiResponse) {
+    // getSessionUser extracts the current session user from the request, its
+    // implementation depends on your auth solution
+    return enhance(prisma, { user: getSessionUser(req, res) });
+}
+
+// create the request handler with the `getPrisma` hook
+export default NextRequestHandler({ getPrisma });
+```
+
+The Next.js API route handler takes the following options to initialize:
+
+<AdapterOptions getPrisma='(req: NextApiRequest, res: NextApiResponse) => unknown | Promise<unknown>' />
+
+</TabItem>
+
+
+</Tabs>
+
+### Controlling what endpoints to expose
+
+You can use a [Next.js middleware](https://nextjs.org/docs/pages/building-your-application/routing/middleware) to further control what endpoints to expose. For example, if you're using a RESTful API handler installed at "/api/model", you can disallow listing all `User` entities by adding a middleware like:
+
+```ts title='/src/middleware.ts'
+import { type NextRequest, NextResponse } from 'next/server';
+
+export function middleware(request: NextRequest) {
+    const url = new URL(request.url);
+    if (
+        request.method === 'GET' &&
+        url.pathname.match(/^\/api\/model\/user\/?$/)
+    ) {
+        return NextResponse.json({ error: 'Not allowed' }, { status: 405 });
+    }
+}
+
+export const config = {
+    matcher: '/api/model/:path*',
+};
+```
+
+### Using the API
+
+<UsingAPI />
+
+<ErrorHandling />
+
+### Fully working example
+
+You can find the fully working examples below:
+- [Pages router](https://github.com/zenstackhq/docs-tutorial-nextjs)
+- [Apps router](https://github.com/zenstackhq/docs-tutorial-nextjs-app-dir)
diff --git a/versioned_docs/version-1.x/reference/server-adapters/nuxt.mdx b/versioned_docs/version-1.x/reference/server-adapters/nuxt.mdx
new file mode 100644
index 00000000..c254ccf1
--- /dev/null
+++ b/versioned_docs/version-1.x/reference/server-adapters/nuxt.mdx
@@ -0,0 +1,44 @@
+---
+title: Nuxt
+description: Adapter for integrating with Nuxt
+sidebar_position: 2
+---
+
+import ErrorHandling from './_error-handling.md';
+import AdapterOptions from './_options.mdx';
+import UsingAPI from './_using-api.mdx';
+
+# Nuxt Server Adapter
+
+The `@zenstackhq/server/nuxt` module provides a quick way to install API endpoints onto a [Nuxt V3](https://nuxt.com/) project for database CRUD operations. 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 API
+
+You can mount the API by creating a Nuxt server event handler like:
+
+```ts title='/server/api/model/[...].ts'
+import { enhance } from '@zenstackhq/runtime';
+import { createEventHandler } from '@zenstackhq/server/nuxt';
+import { getSessionUser } from '~/server/auth';
+import { prisma } from '~/server/prisma';
+
+export default createEventHandler({
+    getPrisma: async (event) => {
+        return enhance(prisma, { user: getSessionUser(event) });
+    },
+});
+```
+
+The Nuxt event handler takes the following options to initialize:
+
+<AdapterOptions getPrisma='(event: H3Event<EventHandlerRequest>) => unknown | Promise<unknown>' />
+
+### Using the API
+
+<UsingAPI />
+
+<ErrorHandling />
+
+### Fully working example
+
+You can find a fully working example [here](https://github.com/zenstackhq/sample-todo-nuxt).
diff --git a/versioned_docs/version-1.x/reference/server-adapters/sveltekit.mdx b/versioned_docs/version-1.x/reference/server-adapters/sveltekit.mdx
new file mode 100644
index 00000000..2976149f
--- /dev/null
+++ b/versioned_docs/version-1.x/reference/server-adapters/sveltekit.mdx
@@ -0,0 +1,63 @@
+---
+title: SvelteKit
+description: Adapter for integrating with SvelteKit
+sidebar_position: 3
+---
+
+import ErrorHandling from './_error-handling.md';
+import AdapterOptions from './_options.mdx';
+import UsingAPI from './_using-api.mdx';
+
+# SvelteKit Server Adapter
+
+The `@zenstackhq/server/sveltekit` module provides a quick way to install API endpoints onto a [SvelteKit](https://kit.svelte.dev/) project for database CRUD operations. 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.
+
+### Installation
+
+```bash
+npm install @zenstackhq/server
+```
+
+### Mounting the API
+
+You can mount the API by creating a SvelteKit server hooks like:
+
+```ts title='/src/hooks.server.ts'
+import { SvelteKitHandler } from '@zenstackhq/server/sveltekit';
+import { enhance } from '@zenstackhq/runtime';
+import { getSessionUser } from '$lib/auth.ts';
+
+// create an enhanced Prisma client with user context
+function getPrisma(event: RequestEvent) {
+    // getSessionUser extracts the current session user from the request, its
+    // implementation depends on your auth solution
+    return enhance({ user: getSessionUser(event) });
+}
+
+// create the hooks handler with the `getPrisma` hook
+export const handle = SvelteKitHandler({ prefix: '/api/model', getPrisma });
+```
+
+:::tip
+You can use the [sequence helper](https://kit.svelte.dev/docs/modules#sveltejs-kit-hooks) to compose multiple server hooks.
+:::
+
+The SvelteKit hooks handler takes the following options to initialize:
+
+- prefix
+
+    <blockquote>string</blockquote>
+
+    Prefix for the mounted API endpoints. E.g.: /api/model.
+
+<AdapterOptions getPrisma='(event: RequestEvent) => unknown | Promise<unknown>' />
+
+### Using the API
+
+<UsingAPI />
+
+<ErrorHandling />
+
+### Fully working example
+
+You can find a fully working example [here](https://github.com/zenstackhq/sample-todo-sveltekit).
diff --git a/versioned_docs/version-1.x/reference/zmodel-language.md b/versioned_docs/version-1.x/reference/zmodel-language.md
new file mode 100644
index 00000000..3f41a417
--- /dev/null
+++ b/versioned_docs/version-1.x/reference/zmodel-language.md
@@ -0,0 +1,1712 @@
+---
+description: ZModel language references
+sidebar_position: 1
+sidebar_label: ZModel Language
+toc_max_heading_level: 3
+---
+
+# ZModel Language Reference
+
+## Overview
+
+**ZModel**, the modeling DSL of ZenStack, is the main concept you'll deal with when using this toolkit. The ZModel syntax is a superset of [Prisma Schema](https://www.prisma.io/docs/concepts/components/prisma-schema). Therefore, every valid Prisma schema is a valid ZModel.
+
+:::info
+
+We made that choice to extend the Prisma schema for several reasons:
+
+-   Creating a new ORM adds little value to the community. Instead, extending Prisma - the overall best ORM toolkit for Typescript - sounds more sensible.
+
+-   Prisma's schema language is simple and intuitive.
+
+-   Extending an existing popular language lowers the learning curve compared to inventing a new one.
+
+:::
+
+However, the standard capability of Prisma schema doesn't allow us to build the functionalities we want in a natural way, so we made a few extensions to the language by adding the following:
+
+1. Custom attributes
+1. Custom attribute functions
+1. Built-in attributes and functions for defining access policies
+1. Built-in attributes for defining field validation rules
+1. Utility attributes like `@password` and `@omit`
+1. Multi-schema files support 
+
+Some of these extensions have been asked for by the Prisma community for some time, so we hope that ZenStack can be helpful even just as an extensible version of Prisma.
+
+This section provides detailed descriptions of all aspects of the ZModel language, so you don't have to jump over to Prisma's documentation for extra learning.
+
+## Import 
+ZModel allows to import other ZModel files. This is useful when you want to split your schema into multiple files for better organization. Under the hood, it will recursively merge all the imported schemas, and generate a single Prisma schema file for the Prisma CLI to consume.
+
+### Syntax
+
+```zmodel
+import [IMPORT_SPECIFICATION]
+```
+
+- **[IMPORT_SPECIFICATION]**: 
+    Path to the ZModel file to be imported. It can be:
+    
+    - An absolute path, e.g., "/path/to/user".
+    - A relative path, e.g., "./user".
+    - A module resolved to an installed NPM package, e.g., "my-package/base".
+
+    If the import specification doesn't end with ".zmodel", the resolver will automatically append it. Once a file is imported, all the declarations in that file will be included in the building process.
+
+### Examples
+
+```zmodel
+// there is a file called "user.zmodel" in the same directory
+import "user"
+```
+
+
+## Data source
+
+Every model needs to include exactly one `datasource` declaration, providing information on how to connect to the underlying database.
+
+### Syntax
+
+```zmodel
+datasource [NAME] {
+    provider = [PROVIDER]
+    url = [DB_URL]
+}
+```
+
+-   **[NAME]**:
+
+    Name of the data source. Needs to be a valid identifier matching regular expression `[A-Za-z][a-za-z0-9_]\*`. Name is only informational and serves no other purposes.
+
+-   **[PROVIDER]**:
+
+    Name of database connector. Valid values:
+
+    -   sqlite
+    -   postgresql
+    -   mysql
+    -   sqlserver
+    -   cockroachdb
+
+-   **[DB_URL]**:
+
+    Database connection string. Either a plain string or an invocation of `env` function to fetch from an environment variable.
+
+### Examples
+
+```zmodel
+datasource db {
+    provider = "postgresql"
+    url = "postgresql://postgres:abc123@localhost:5432/todo?schema=public"
+}
+```
+
+It's highly recommended that you not commit sensitive database connection strings into source control. Alternatively, you can load it from an environment variable:
+
+```zmodel
+datasource db {
+    provider = "postgresql"
+    url = env("DATABASE_URL")
+}
+```
+
+### Supported databases
+
+ZenStack uses [Prisma](https://prisma.io ':target=_blank') to talk to databases, so all relational databases supported by Prisma are also supported by ZenStack.
+
+Here's a list for your reference:
+
+| Database              | Version |
+| --------------------- | ------- |
+| PostgreSQL            | 9.6     |
+| PostgreSQL            | 10      |
+| PostgreSQL            | 11      |
+| PostgreSQL            | 12      |
+| PostgreSQL            | 13      |
+| PostgreSQL            | 14      |
+| PostgreSQL            | 15      |
+| MySQL                 | 5.6     |
+| MySQL                 | 5.7     |
+| MySQL                 | 8       |
+| MariaDB               | 10      |
+| SQLite                | \*      |
+| AWS Aurora            | \*      |
+| AWS Aurora Serverless | \*      |
+| Microsoft SQL Server  | 2022    |
+| Microsoft SQL Server  | 2019    |
+| Microsoft SQL Server  | 2017    |
+| Azure SQL             | \*      |
+| CockroachDB           | 21.2.4+ |
+
+You can find the orignal list [here](https://www.prisma.io/docs/reference/database-reference/supported-databases ':target=_blank').
+
+## Generator
+
+Generators are used for creating assets (usually code) from a Prisma schema. Check [here](https://www.prisma.io/docs/concepts/components/prisma-schema/generators) for a list of official and community generators.
+
+### Syntax
+
+```zmodel
+generator [GENERATOR_NAME] {
+    [OPTION]*
+}
+```
+
+-   **[GENERATOR_NAME]**
+
+    Name of the generator. Needs to be unique in the entire model. Needs to be a valid identifier matching regular expression `[A-Za-z][a-za-z0-9_]\*`.
+
+-   **[OPTION]**
+
+    A generator configuration option, in form of "[NAME] = [VALUE]". A generator needs to have at least a "provider" option that specify its provider.
+
+### Example
+
+```zmodel
+generator client {
+  provider = "prisma-client-js"
+  output   = "./generated/prisma-client-js"
+```
+
+## Plugin
+
+Plugins are ZenStack's extensibility mechanism. It's usage is similar to [Generator](#generator). Users can define their own plugins to generate artifacts from the ZModel schema. Plugins differ from generators mainly in the following ways:
+
+-   They have a cleaner interface without the complexity of JSON-RPC.
+-   They use an easier-to-program AST representation than generators.
+-   They have access to language features that ZenStack adds to Prisma, like custom attributes and functions.
+
+### Syntax
+
+```zmodel
+plugin [PLUGIN_NAME] {
+    [OPTION]*
+}
+```
+
+-   **[PLUGIN_NAME]**
+
+    Name of the plugin. Needs to be unique in the entire model. Needs to be a valid identifier matching regular expression `[A-Za-z][a-za-z0-9_]\*`.
+
+-   **[OPTION]**
+
+    A plugin configuration option, in form of "[NAME] = [VALUE]". A plugin needs to have at least a "provider" option that specify its provider.
+
+### Example
+
+```zmodel
+plugin swr {
+    provider = '@zenstackhq/swr'
+    output = 'lib/hooks'
+}
+```
+
+## Enum
+
+Enums are container declarations for grouping constant identifiers. You can use them to express concepts like user roles, product categories, etc.
+
+### Syntax
+
+```prsima
+enum [ENUM_NAME] {
+    [FIELD]*
+}
+```
+
+-   **[ENUM_NAME]**
+
+    Name of the enum. Needs to be unique in the entire model. Needs to be a valid identifier matching regular expression `[A-Za-z][a-za-z0-9_]\*`.
+
+-   **[FIELD]**
+
+    Field identifier. Needs to be unique in the model. Needs to be a valid identifier matching regular expression `[A-Za-z][a-za-z0-9_]\*`.
+
+### Example
+
+```zmodel
+enum UserRole {
+    USER
+    ADMIN
+}
+```
+
+## Model
+
+Models represent the business entities of your application. A model inherits all fields and attributes from extended abstract models. Abstract models are eliminated in the generated prisma schema file. 
+
+### Syntax
+```zmodel
+(abstract)? model [NAME] (extends [ABSTRACT_MODEL_NAME](,[ABSTRACT_MODEL_NAME])*)? {
+    [FIELD]*
+}
+```
+-  **[abstract]**:
+
+    Optional. If present, the model is marked as abstract would not be mapped to a database table. Abstract models are only used as base classes for other models.
+-   **[NAME]**:
+
+    Name of the model. Needs to be unique in the entire model. Needs to be a valid identifier matching regular expression `[A-Za-z][a-za-z0-9_]\*`.
+
+-   **[FIELD]**:
+
+    Arbitrary number of fields. See [next section](#field) for details.
+
+-   **[ABSTRACT_MODEL_NAME]**:
+    
+    Name of an abstract model. 
+
+### Note
+
+A model must include a field marked with `@id` attribute. The `id` field serves as a unique identifier for a model entity and is mapped to the database table's primary key.
+
+See [here](#attribute) for more details about attributes.
+
+### Example
+
+```zmodel
+abstract model Basic {
+    id String @id
+    createdAt DateTime @default(now())
+    updatedAt DateTime @updatedAt
+}
+
+model User extends Basic {
+    name String 
+}
+```
+
+The generated prisma file only contains one `User` model:
+```zmodel
+model User {
+    id String @id
+    createdAt DateTime @default(now())
+    updatedAt DateTime @updatedAt
+    name String @id
+}
+```
+
+
+## Attribute
+
+Attributes decorate fields and models and attach extra behaviors or constraints to them.
+
+### Syntax
+
+#### Field attribute
+
+Field attribute name is prefixed by a single `@`.
+
+```zmodel
+id String @[ATTR_NAME](ARGS)?
+```
+
+-   **[ATTR_NAME]**
+
+Attribute name. See [below](#predefined-attributes) for a full list of attributes.
+
+-   **[ARGS]**
+
+See [attribute arguments](#arguments).
+
+#### Model attribute
+
+Field attribute name is prefixed double `@@`.
+
+```zmodel
+model Model {
+    @@[ATTR_NAME](ARGS)?
+}
+```
+
+-   **[ATTR_NAME]**
+
+Attribute name. See [below](#predefined-attributes) for a full list of attributes.
+
+-   **[ARGS]**
+
+See [attribute arguments](#arguments).
+
+### Arguments
+
+Attribute can be declared with a list of parameters and applied with a comma-separated list of arguments.
+
+Arguments are mapped to parameters by position or by name. For example, for the `@default` attribute declared as:
+
+```zmodel
+attribute @default(_ value: ContextType)
+```
+
+, the following two ways of applying it are equivalent:
+
+```zmodel
+published Boolean @default(value: false)
+```
+
+```zmodel
+published Boolean @default(false)
+```
+
+### Parameter types
+
+Attribute parameters are typed. The following types are supported:
+
+-   Int
+
+    Integer literal can be passed as argument.
+
+    E.g., declaration:
+
+    ```zmodel
+    attribute @password(saltLength: Int?, salt: String?)
+
+    ```
+
+    application:
+
+    ```zmodel
+    password String @password(saltLength: 10)
+    ```
+
+-   String
+
+    String literal can be passed as argument.
+
+    E.g., declaration:
+
+    ```zmodel
+    attribute @id(map: String?)
+    ```
+
+    application:
+
+    ```zmodel
+    id String @id(map: "_id")
+    ```
+
+-   Boolean
+
+    Boolean literal or expression can be passed as argument.
+
+    E.g., declaration:
+
+    ```zmodel
+    attribute @@allow(_ operation: String, _ condition: Boolean)
+    ```
+
+    application:
+
+    ```zmodel
+    @@allow("read", true)
+    @@allow("update", auth() != null)
+    ```
+
+-   ContextType
+
+    A special type that represents the type of the field onto which the attribute is attached.
+
+    E.g., declaration:
+
+    ```zmodel
+    attribute @default(_ value: ContextType)
+    ```
+
+    application:
+
+    ```zmodel
+    f1 String @default("hello")
+    f2 Int @default(1)
+    ```
+
+-   FieldReference
+
+    References to fields defined in the current model.
+
+    E.g., declaration:
+
+    ```zmodel
+    attribute @relation(
+        _ name: String?,
+        fields: FieldReference[]?,
+        references: FieldReference[]?,
+        onDelete: ReferentialAction?,
+        onUpdate: ReferentialAction?,
+        map: String?)
+    ```
+
+    application:
+
+    ```zmodel
+    model Model {
+        ...
+        // [ownerId] is a list of FieldReference
+        owner Owner @relation(fields: [ownerId], references: [id])
+        ownerId
+    }
+    ```
+
+-   Enum
+
+    Attribute parameter can also be typed as predefined enum.
+
+    E.g., declaration:
+
+    ```zmodel
+    attribute @relation(
+        _ name: String?,
+        fields: FieldReference[]?,
+        references: FieldReference[]?,
+        // ReferentialAction is a predefined enum
+        onDelete: ReferentialAction?,
+        onUpdate: ReferentialAction?,
+        map: String?)
+    ```
+
+    application:
+
+    ```zmodel
+    model Model {
+        // 'Cascade' is a predefined enum value
+        owner Owner @relation(..., onDelete: Cascade)
+    }
+    ```
+
+An attribute parameter can be typed as any of the types above, a list of the above type, or an optional of the types above.
+
+```zmodel
+    model Model {
+        ...
+        f1 String
+        f2 String
+        // a list of FieldReference
+        @@unique([f1, f2])
+    }
+```
+
+### Attribute functions
+
+Attribute functions are used for providing values for attribute arguments, e.g., current `DateTime`, an autoincrement `Int`, etc. They can be used in place of attribute arguments, like:
+
+```zmodel
+model Model {
+    ...
+    serial Int @default(autoincrement())
+    createdAt DateTime @default(now())
+}
+```
+
+You can find a list of predefined attribute functions [here](#predefined-attribute-functions).
+
+### Predefined attributes
+
+#### Field attributes
+
+##### @id
+
+```zmodel
+attribute @id(map: String?)
+```
+
+Defines an ID on the model.
+
+_Params_:
+
+| Name | Description                                                       |
+| ---- | ----------------------------------------------------------------- |
+| map  | The name of the underlying primary key constraint in the database |
+
+##### @default
+
+```zmodel
+attribute @default(_ value: ContextType)
+```
+
+Defines a default value for a field.
+
+_Params_:
+
+| Name  | Description                  |
+| ----- | ---------------------------- |
+| value | The default value expression |
+
+##### @unique
+
+```zmodel
+attribute @unique(map: String?)
+```
+
+Defines a unique constraint for this field.
+
+_Params_:
+
+| Name | Description                                                       |
+| ---- | ----------------------------------------------------------------- |
+| map  | The name of the underlying primary key constraint in the database |
+
+##### @relation
+
+```zmodel
+attribute @relation(
+    _ name: String?,
+    fields: FieldReference[]?, 
+    references: FieldReference[]?, 
+    onDelete: ReferentialAction?,
+    onUpdate: ReferentialAction?,
+    map: String?)
+```
+
+Defines meta information about a relation.
+
+_Params_:
+
+| Name       | Description                                                                    |
+| ---------- | ------------------------------------------------------------------------------ |
+| name       | The name of the relationship                                                   |
+| fields     | A list of fields defined in the current model                                  |
+| references | A list of fields of the model on the other side of the relation                |
+| onDelete   | Referential action to take on delete. See details [here](#referential-action). |
+| onUpdate   | Referential action to take on update. See details [here](#referential-action). |
+
+##### @map
+
+```zmodel
+attribute @map(_ name: String)
+```
+
+Maps a field name or enum value from the schema to a column with a different name in the database.
+
+_Params_:
+
+| Name | Description                                       |
+| ---- | ------------------------------------------------- |
+| map  | The name of the underlying column in the database |
+
+##### @updatedAt
+
+```zmodel
+attribute @updatedAt()
+```
+
+Automatically stores the time when a record was last updated.
+
+##### @ignore
+
+```zmodel
+attribute @ignore()
+```
+
+Exclude a field from the Prisma Client (for example, a field that you do not want Prisma users to update).
+
+##### @allow
+
+```zmodel
+attribute @allow(_ operation: String, _ condition: Boolean)
+```
+
+Defines an access policy that allows the annotated field to be read or updated. Read more about access policies [here](#access-policy).
+
+_Params_:
+
+| Name      | Description                                                                                                                                                              |
+| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| operation | Comma separated list of operations to control, including `"read"` and `"update"`. Pass` "all"` as an abbreviation for including all operations. |
+| condition | Boolean expression indicating if the operations should be allowed                                                                                                        |
+
+##### @deny
+
+```zmodel
+attribute @deny(_ operation: String, _ condition: Boolean)
+```
+
+Defines an access policy that denies the annotated field to be read or updated. Read more about access policies [here](#access-policy).
+
+_Params_:
+
+| Name      | Description                                                                                                                                                              |
+| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| operation | Comma separated list of operations to control, including `"read"` and `"update"`. Pass` "all"` as an abbreviation for including all operations. |
+| condition | Boolean expression indicating if the operations should be denied       
+
+##### @password
+
+```zmodel
+attribute @password(saltLength: Int?, salt: String?)
+```
+
+Indicates that the field is a password field and needs to be hashed before persistence.
+
+_NOTE_: ZenStack uses the "bcryptjs" library to hash passwords. You can use the `saltLength` parameter to configure the cost of hashing or use `salt` parameter to provide an explicit salt. By default, a salt length of 12 is used. See [here](https://www.npmjs.com/package/bcryptjs ':target=blank') for more details.
+
+_Params_:
+
+| Name       | Description                                                   |
+| ---------- | ------------------------------------------------------------- |
+| saltLength | The length of salt to use (cost factor for the hash function) |
+| salt       | The salt to use (a pregenerated valid salt)                   |
+
+##### @omit
+
+```zmodel
+attribute @omit()
+```
+
+Indicates that the field should be omitted when read from the generated services. Commonly used together with `@password` attribute.
+
+##### @prisma.passthrough
+
+```zmodel
+attribute @prisma.passthrough(_ text: String)
+```
+
+A utility attribute for passing arbitrary text to the generated Prisma schema. This is useful as a workaround for dealing with discrepancies between Prisma schema and ZModel.
+
+_Params_:
+
+| Name | Description                          |
+| ---- | ------------------------------------ |
+| text | Text to passthrough to Prisma schema |
+
+E.g., the following ZModel content:
+
+```zmodel
+model User {
+    id Int @id @default(autoincrement())
+    name String @prisma.passthrough("@unique")
+}
+```
+
+wil be translated to the following Prisma schema:
+
+```zmodel
+model User {
+    id Int @id @default(autoincrement())
+    name String @unique
+}
+```
+
+#### Model attributes
+
+##### @@id
+
+```zmodel
+attribute @@id(_ fields: FieldReference[], name: String?, map: String?)
+```
+
+Defines a multi-field ID (composite ID) on the model.
+
+_Params_:
+
+| Name   | Description                                                                   |
+| ------ | ----------------------------------------------------------------------------- |
+| fields | A list of fields defined in the current model                                 |
+| name   | The name that the Client API will expose for the argument covering all fields |
+| map    | The name of the underlying primary key constraint in the database             |
+
+##### @@unique
+
+```zmodel
+attribute @@unique(_ fields: FieldReference[], name: String?, map: String?)
+```
+
+Defines a compound unique constraint for the specified fields.
+
+_Params_:
+
+| Name   | Description                                                  |
+| ------ | ------------------------------------------------------------ |
+| fields | A list of fields defined in the current model                |
+| name   | The name of the unique combination of fields                 |
+| map    | The name of the underlying unique constraint in the database |
+
+##### @@schema
+
+```zmodel
+attribute @@schema(_ name: String)
+```
+
+Specifies the database schema to use in a [multi-schema setup](https://www.prisma.io/docs/guides/database/multi-schema).
+
+_Params_:
+
+| Name | Description                     |
+| ---- | ------------------------------- |
+| name | The name of the database schema |
+
+##### @@index
+
+```zmodel
+attribute @@index(_ fields: FieldReference[], map: String?)
+```
+
+Defines an index in the database.
+
+_Params_:
+
+| Name   | Description                                      |
+| ------ | ------------------------------------------------ |
+| fields | A list of fields defined in the current model    |
+| map    | The name of the underlying index in the database |
+
+##### @@map
+
+```zmodel
+attribute @@map(_ name: String)
+```
+
+Maps the schema model name to a table with a different name, or an enum name to a different underlying enum in the database.
+
+_Params_:
+
+| Name | Description                                              |
+| ---- | -------------------------------------------------------- |
+| name | The name of the underlying table or enum in the database |
+
+##### @@ignore
+
+```zmodel
+attribute @@ignore()
+```
+
+Exclude a model from the Prisma Client (for example, a model that you do not want Prisma users to update).
+
+##### @@allow
+
+```zmodel
+attribute @@allow(_ operation: String, _ condition: Boolean)
+```
+
+Defines an access policy that allows a set of operations when the given condition is true. Read more about access policies [here](#access-policy).
+
+_Params_:
+
+| Name      | Description                                                                                                                                                              |
+| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| operation | Comma separated list of operations to control, including `"create"`, `"read"`, `"update"`, and `"delete"`. Pass` "all"` as an abbriviation for including all operations. |
+| condition | Boolean expression indicating if the operations should be allowed                                                                                                        |
+
+##### @@deny
+
+```zmodel
+attribute @@deny(_ operation: String, _ condition: Boolean)
+```
+
+Defines an access policy that denies a set of operations when the given condition is true. Read more about access policies [here](#access-policy).
+
+_Params_:
+
+| Name      | Description                                                                                                                                                              |
+| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| operation | Comma separated list of operations to control, including `"create"`, `"read"`, `"update"`, and `"delete"`. Pass` "all"` as an abbriviation for including all operations. |
+| condition | Boolean expression indicating if the operations should be denied                                                                                                         |
+
+##### @@auth
+
+```zmodel
+attribute @@auth()
+```
+
+Specify the model for resolving `auth()` function call in access policies. By default, the model named "User" is used. You can use this attribute to override the default behavior. A Zmodel can have at most one model with this attribute. 
+
+##### @@prisma.passthrough
+
+```zmodel
+attribute @@prisma.passthrough(_ text: String)
+```
+
+A utility attribute for passing arbitrary text to the generated Prisma schema. This is useful as a workaround for dealing with discrepancies between Prisma schema and ZModel.
+
+_Params_:
+
+| Name | Description                          |
+| ---- | ------------------------------------ |
+| text | Text to passthrough to Prisma schema |
+
+E.g., the following ZModel content:
+
+```zmodel
+model User {
+    id Int @id @default(autoincrement())
+    name String
+    @@prisma.passthrough("@@unique([name])")
+}
+```
+
+wil be translated to the following Prisma schema:
+
+```zmodel
+model User {
+    id Int @id @default(autoincrement())
+    name String
+    @@unique([name])
+}
+```
+
+### Predefined attribute functions
+
+##### uuid()
+
+```zmodel
+function uuid(): String {}
+```
+
+Generates a globally unique identifier based on the UUID spec.
+
+##### cuid()
+
+```zmodel
+function cuid(): String {}
+```
+
+Generates a globally unique identifier based on the [CUID](https://github.com/ericelliott/cuid) spec.
+
+##### now()
+
+```zmodel
+function now(): DateTime {}
+```
+
+Gets current date-time.
+
+##### autoincrement()
+
+```zmodel
+function autoincrement(): Int {}
+```
+
+Creates a sequence of integers in the underlying database and assign the incremented
+values to the ID values of the created records based on the sequence.
+
+##### dbgenerated()
+
+```zmodel
+function dbgenerated(expr: String): Any {}
+```
+
+Represents default values that cannot be expressed in the Prisma schema (such as random()).
+
+##### auth()
+
+```zmodel
+function auth(): User {}
+```
+
+Gets the current login user. The return type of the function is the `User` model defined in the current ZModel.
+
+##### future()
+
+```zmodel
+function future(): Any {}
+```
+
+Gets the "post-update" state of an entity. Only valid when used in a "update" access policy. Read more about access policies [here](#access-policy).
+
+##### contains()
+
+```zmodel
+    function contains(field: String, search: String, caseInSensitive: Boolean?): Boolean {}
+```
+
+Checks if the given field contains the search string. The search string is case-sensitive by default. Use `caseInSensitive` to toggle the case sensitivity.
+
+Equivalent to Prisma's [contains](https://www.prisma.io/docs/reference/api-reference/prisma-client-reference#contains) operator.
+
+##### search()
+
+```zmodel
+function search(field: String, search: String): Boolean {}
+```
+
+Checks if the given field contains the search string using [full-text-search](https://www.prisma.io/docs/concepts/components/prisma-client/full-text-search).
+
+Equivalent to Prisma's [search](https://www.prisma.io/docs/reference/api-reference/prisma-client-reference#search) operator.
+
+##### startsWith()
+
+```zmodel
+function startsWith(field: String, search: String): Boolean {}
+```
+
+Checks if the given field starts with the search string.
+
+Equivalent to Prisma's [startsWith](https://www.prisma.io/docs/reference/api-reference/prisma-client-reference#startswith) operator.
+
+##### endsWith()
+
+```zmodel
+function endsWith(field: String, search: String): Boolean {}
+```
+
+Checks if the given field ends with the search string.
+
+Equivalent to Prisma's [endsWith](https://www.prisma.io/docs/reference/api-reference/prisma-client-reference#endswith) operator.
+
+##### has()
+
+```zmodel
+function has(field: Any[], search: Any): Boolean {}
+```
+
+Check if the given field (list) contains the search value.
+
+Equivalent to Prisma's [has](https://www.prisma.io/docs/reference/api-reference/prisma-client-reference#has) operator.
+
+##### hasEvery()
+
+```zmodel
+function hasEvery(field: Any[], search: Any[]): Boolean {}
+```
+
+Check if the given field (list) contains every element of the search list.
+
+Equivalent to Prisma's [hasEvery](https://www.prisma.io/docs/reference/api-reference/prisma-client-reference#hasevery) operator.
+
+##### hasSome
+
+```zmodel
+function hasSome(field: Any[], search: Any[]): Boolean {}
+```
+
+Check if the given field (list) contains at least one element of the search list.
+
+Equivalent to Prisma's [hasSome](https://www.prisma.io/docs/reference/api-reference/prisma-client-reference#hassome) operator.
+
+##### isEmpty
+
+```zmodel
+function isEmpty(field: Any[]): Boolean {}
+```
+
+Check if the given field (list) is empty.
+
+Equivalent to Prisma's [isEmpty](https://www.prisma.io/docs/reference/api-reference/prisma-client-reference#isempty) operator.
+
+### Examples
+
+Here're some examples on using field and model attributes:
+
+```zmodel
+model User {
+    // unique id field with a default UUID value
+    id String @id @default(uuid())
+
+    // require email field to be unique
+    email String @unique
+
+    // password is hashed with bcrypt with length of 16, omitted when returned from the CRUD services
+    password String @password(saltLength: 16) @omit
+
+    // default to current date-time
+    createdAt DateTime @default(now())
+
+    // auto-updated when the entity is modified
+    updatedAt DateTime @updatedAt
+
+    // mapping to a different column name in database
+    description String @map("desc")
+
+    // mapping to a different table name in database
+    @@map("users")
+
+    // use @@index to specify fields to create database index for
+    @@index([email])
+
+    // use @@allow to specify access policies
+    @@allow("create,read", true)
+
+    // use auth() to reference the current user
+    // use future() to access the "post-update" state
+    @@allow("update", auth() == this && future().email == email)
+}
+```
+
+### Custom attributes and functions
+
+You can find examples of custom attributes and functions in [ZModel Standard Library](https://github.com/zenstackhq/zenstack/blob/main/packages/schema/src/res/stdlib.zmodel).
+
+## Field
+
+Fields are typed members of models.
+
+### Syntax
+
+```zmodel
+model Model {
+    [FIELD_NAME] [FIELD_TYPE] (FIELD_ATTRIBUTES)?
+}
+```
+
+-   **[FIELD_NAME]**
+
+    Name of the field. Needs to be unique in the containing model. Needs to be a valid identifier matching regular expression `[A-Za-z][a-za-z0-9_]\*`.
+
+-   **[FIELD_TYPE]**
+
+    Type of the field. Can be a scalar type or a reference to another model.
+
+    The following scalar types are supported:
+
+    -   String
+    -   Boolean
+    -   Int
+    -   BigInt
+    -   Float
+    -   Decimal
+    -   Json
+    -   Bytes
+    -   [Unsupported types](https://www.prisma.io/docs/concepts/components/prisma-schema/data-model#unsupported-types)
+
+    A field's type can be any of the scalar or reference type, a list of the aforementioned type (suffixed with `[]`), or an optional of the aforementioned type (suffixed with `?`).
+
+-   **[FIELD_ATTRIBUTES]**
+
+    Field attributes attach extra behaviors or constraints to the field. See [Attribute](#attribute) for more information.
+
+### Example
+
+```zmodel
+model Post {
+    // "id" field is a mandatory unique identifier of this model
+    id String @id @default(uuid())
+
+    // fields can be DateTime
+    createdAt DateTime @default(now())
+    updatedAt DateTime @updatedAt
+
+    // or string
+    title String
+
+    // or integer
+    viewCount Int @default(0)
+
+    // and optional
+    content String?
+
+    // and a list too
+    tags String[]
+
+    // and can reference another model too
+    comments Comment[]
+}
+```
+
+## Relation
+
+Relations are connections among models. There're three types of relations:
+
+-   One-to-one
+-   One-to-many
+-   Many-to-many
+
+Relations are expressed with a pair of fields and together with the special `@relation` field attribute. One side of the relation field carries the `@relation` attribute to indicate how the connection is established.
+
+### One-to-one relation
+
+The _owner_ side of the relation declares an optional field typed as the model of the _owned_ side of the relation.
+
+On the _owned_ side, a reference field is declared with `@relation` attribute, together with a **foreign key** field storing the id of the owner entity.
+
+```zmodel
+model User {
+    id String @id
+    profile Profile?
+}
+
+model Profile {
+    id String @id
+    user User @relation(fields: [userId], references: [id])
+    userId String @unique
+}
+```
+
+### One-to-many relation
+
+The _owner_ side of the relation declares a list field typed as the model of the _owned_ side of the relation.
+
+On the _owned_ side, a reference field is declared with `@relation` attribute, together with a **foreign key** field storing the id of the owner entity.
+
+```zmodel
+model User {
+    id String @id
+    posts Post[]
+}
+
+model Post {
+    id String @id
+    author User? @relation(fields: [authorId], references: [id])
+    authorId String?
+}
+```
+
+### Many-to-many relation
+
+A _join model_ is declared to connect the two sides of the relation using two one-to-one relations.
+
+Each side of the relation then establishes a one-to-many relation with the _join model_.
+
+```zmodel
+model Space {
+    id String @id
+    // one-to-many with the "join-model"
+    members Membership[]
+}
+
+// Membership is the "join-model" between User and Space
+model Membership {
+    id String @id()
+
+    // one-to-many from Space
+    space Space @relation(fields: [spaceId], references: [id])
+    spaceId String
+
+    // one-to-many from User
+    user User @relation(fields: [userId], references: [id])
+    userId String
+
+    // a user can be member of a space for only once
+    @@unique([userId, spaceId])
+}
+
+model User {
+    id String @id
+    // one-to-many with the "join-model"
+    membership Membership[]
+}
+
+```
+
+### Self-relations
+
+A relation field referencing its own model is called "self-relation". ZModel's represents self-relation in the same way as Prisma does. Please refer to the [Prisma documentation](https://www.prisma.io/docs/concepts/components/prisma-schema/relations/self-relations) for more details.
+
+### Referential action
+
+When defining a relation, you can specify what happens when one side of a relation is updated or deleted. See [Referential action](#referential-action) for details.
+
+## Access policy
+
+### Model-level policy
+
+Model-level access policies are defined with `@@allow` and `@@deny` attributes. They specify the eligibility of an operation over a model entity. The signatures of the attributes are:
+
+-   `@@allow`
+
+    ```zmodel
+        attribute @@allow(_ operation: String, _ condition: Boolean)
+    ```
+
+    _Params_:
+
+    | Name      | Description                                                                                                                                                              |
+    | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+    | operation | Comma separated list of operations to control, including `"create"`, `"read"`, `"update"`, and `"delete"`. Pass` "all"` as an abbreviation for including all operations. |
+    | condition | Boolean expression indicating if the operations should be allowed                                                                                                        |
+
+-   `@@deny`
+
+    ```zmodel
+        attribute @@deny(_ operation: String, _ condition: Boolean)
+    ```
+
+    _Params_:
+
+    | Name      | Description                                                                                                                                                              |
+    | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+    | operation | Comma separated list of operations to control, including `"create"`, `"read"`, `"update"`, and `"delete"`. Pass` "all"` as an abbreviation for including all operations. |
+    | condition | Boolean expression indicating if the operations should be denied                                                                                                         |
+
+### Field-level policy
+
+Field-level access policies are defined with `@allow` and `@deny` attributes. They control whether the annotated field can be read or updated. If a field fails "read" check, it'll be deleted when returned. If a field is set to be updated but fails "update" check, the update operation will be rejected.
+
+Note that it's not allowed to put "update" rule on relation fields, because whether an entity can be updated shouldn't be determined indirectly by a relation, but directly by the entity itself. However, you can put "update" rule on a foreign key field to control how a a relation can be updated.
+
+The signatures of the attributes are:
+
+-   `@allow`
+
+    ```zmodel
+        attribute @allow(_ operation: String, _ condition: Boolean, _ override: Boolean?)
+    ```
+
+    _Params_:
+
+    | Name      | Description                                                                                                                                                              | Default |
+    | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------- |
+    | operation | Comma separated list of operations to control, including `"read"` and `"update"`. Pass` "all"` as an abbreviation for including all operations.                          |         |
+    | condition | Boolean expression indicating if the operations should be allowed                                                                                                        |         |
+    | override  | Boolean indicating if the field-level policy should override model-level ones. See [here](../the-complete-guide/part1/access-policy/field-level#overriding-model-level-policies) for more details. | false   |
+
+-   `@deny`
+
+    ```zmodel
+        attribute @deny(_ operation: String, _ condition: Boolean)
+    ```
+
+    _Params_:
+
+    | Name      | Description                                                                                                                                                              |
+    | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+    | operation | Comma separated list of operations to control, including ``"read"` and `"update"`. Pass` "all"` as an abbreviation for including all operations. |
+    | condition | Boolean expression indicating if the operations should be denied                                                                                                         |
+
+### Policy expressions
+
+Policy rules use boolean expressions to make verdicts. ZModel provides a set of literals and operators for constructing expressions of arbitrary complexity.
+
+```
+
+Expression ::= Literal | Array | This | Null | Reference | MemberAccess | Invocation | Binary | Unary | CollectionPredicate
+
+Literal ::= String | Number | Boolean
+
+Array ::= "[" Expression [, Expression]* "]"
+
+This ::= "this"
+
+Null ::= "null"
+
+Reference ::= Identifier
+
+MemberAccess ::= Expression "." Identifier
+
+Operator_Precedence#table
+Binary ::= Expression ("==" | "!=" | ">" | "<" | ">=" | "<=" | "&&" | "||" || "in")
+
+Unary ::= "!" Expression
+
+CollectionPredicate ::= Expression ("?" | "!" | "^") "[" Expression "]"
+
+```
+
+Binary operator precedence follows [Javascript's rules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/).
+
+Collection predicate expressions are used for reaching into relation fields. You can find more details [here](#collection-predicate-expressions).
+
+### Using authentication in policy rules
+
+It's very common to use the current login user to verdict if an operation should be permitted. Therefore, ZenStack provides a built-in `auth()` attribute function that evaluates to the `User` entity corresponding to the current user. To use the function, your ZModel file must define a `User` model or a model marked with the `@@auth` attribute.
+
+You can use `auth()` to:
+
+-   Check if a user is logged in
+
+    ```zmodel
+    @@deny('all', auth() == null)
+    ```
+
+-   Access user's fields
+
+    ```zmodel
+    @@allow('update', auth().role == 'ADMIN')
+    ```
+
+-   Compare user identity
+
+    ```zmodel
+    // owner is a relation field to User model
+    @@allow('update', auth() == owner)
+    ```
+
+### Accessing relation fields in policy
+
+As you've seen in the examples above, you can access fields from relations in policy expressions. For example, to express "a user can be read by any user sharing a space" in the `User` model, you can directly read into its `membership` field.
+
+```zmodel
+    @@allow('read', membership?[space.members?[user == auth()]])
+```
+
+In most cases, when you use a "to-many" relation in a policy rule, you'll use "Collection Predicate" to express a condition. See [next section](#collection-predicate-expressions) for details.
+
+### Collection predicate expressions
+
+Collection predicate expressions are boolean expressions used to express conditions over a list. It's mainly designed for building policy rules for "to-many" relations. It has three forms of syntaxes:
+
+-   Any
+
+    ```
+    <collection>?[condition]
+    ```
+
+    Any element in `collection` matches `condition`
+
+-   All
+
+    ```
+    <collection>![condition]
+    ```
+
+    All elements in `collection` match `condition`
+
+-   None
+
+    ```
+    <collection>^[condition]
+    ```
+
+    None element in `collection` matches `condition`
+
+The `condition` expression has direct access to fields defined in the model of `collection`. E.g.:
+
+```zmodel
+    @@allow('read', members?[user == auth()])
+```
+
+, in condition `user == auth()`, `user` refers to the `user` field in model `Membership`, because the collection `members` is resolved to `Membership` model.
+
+Also, collection predicates can be nested to express complex conditions involving multi-level relation lookup. E.g.:
+
+```zmodel
+    @@allow('read', membership?[space.members?[user == auth()]])
+```
+
+In this example, `user` refers to `user` field of `Membership` model because `space.members` is resolved to `Membership` model.
+
+### Combining multiple rules
+
+A model can contain an arbitrary number of policy rules. The logic of combining model-level rules is as follows:
+
+-   The operation is rejected if any of the conditions in `@@deny` rules evaluate to `true`.
+-   Otherwise, the operation is permitted if any of the conditions in `@@allow` rules evaluate to `true`.
+-   Otherwise, the operation is rejected.
+
+A field can also contain an arbitrary number of policy rules. The logic of combining field-level rules is as follows:
+
+-   The operation is rejected if any of the conditions in `@deny` rules evaluate to `true`.
+-   Otherwise, if there exists any `@allow` rule and at least one of them evaluates to `true`, the operation is permitted.
+-   Otherwise, if there exists any `@allow` rule but none one of them evaluates to `true`, the operation is rejected.
+-   Otherwise, the operation is permitted.
+
+Please note the difference between model-level and field-level rules. Model-level access are by-default denied, while field-level access are by-default allowed.
+
+### Pre-update vs. post-update
+
+When an access policy rule is applied to a mutate operation, the entities under operation have a "pre" and "post" state. For a "create" rule, its "pre" state is empty, so the rule implicitly refers to the "post" state. For a "delete" rule, its "post" state is empty, so the rule implicitly refers to the "pre" state.
+
+However, for "update" rules it is ambiguous; both the "pre" and the "post" states exist. By default, for "update" rules, fields referenced in the expressions refer to the "pre" state, and you can use the `future()` function to refer to the "post" state explicitly.
+
+In the following example, the "update" rule uses `future()` to ensure an update cannot alter the post's owner.
+
+```zmodel
+model Post {
+    id String @id @default(uuid())
+    title String @length(1, 100)
+    author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
+    authorId String
+
+    // update can only be done by the author, and is not allowed to change author
+    @@allow('update', author == auth() && future().author == author)
+}
+```
+
+:::info
+The `future()` function is not supported in field-level access policies. To express post-update rules, put them into model-level policies.
+:::
+
+### Examples
+
+#### A simple example with Post model
+
+```zmodel
+model Post {
+    // reject all operations if user's not logged in
+    @@deny('all', auth() == null)
+
+    // allow all operations if the entity's owner matches the current user
+    @@allow('all', auth() == owner)
+
+    // posts are readable to anyone
+    @allow('read', true)
+}
+```
+
+#### A more complex example with multi-user spaces
+
+```zmodel
+model Space {
+    id String @id
+    members Membership[]
+    owner User @relation(fields: [ownerId], references: [id])
+    ownerId String
+
+    // require login
+    @@deny('all', auth() == null)
+
+    // everyone can create a space
+    @@allow('create', true)
+
+    // owner can do everything
+    @@allow('all', auth() == owner)
+
+    // any user in the space can read the space
+    //
+    // Here the <collection>?[condition] syntax is called
+    // "Collection Predicate", used to check if any element
+    // in the "collection" matches the "condition"
+    @@allow('read', members?[user == auth()])
+}
+
+// Membership is the "join-model" between User and Space
+model Membership {
+    id String @id()
+
+    // one-to-many from Space
+    space Space @relation(fields: [spaceId], references: [id])
+    spaceId String
+
+    // one-to-many from User
+    user User @relation(fields: [userId], references: [id])
+    userId String
+
+    // a user can be member of a space for only once
+    @@unique([userId, spaceId])
+
+    // require login
+    @@deny('all', auth() == null)
+
+    // space owner can create/update/delete
+    @@allow('create,update,delete', space.owner == auth())
+
+    // user can read entries for spaces which he's a member of
+    @@allow('read', space.members?[user == auth()])
+}
+
+model User {
+    id String @id
+    email String @unique
+    membership Membership[]
+    ownedSpaces Space[]
+
+    // allow signup
+    @@allow('create', true)
+
+    // user can do everything to herself; note that "this" represents
+    // the current entity
+    @@allow('all', auth() == this)
+
+    // can be read by users sharing a space
+    @@allow('read', membership?[space.members?[user == auth()]])
+}
+
+```
+
+## Data validation
+
+### Overview
+
+Data validation is used for attaching constraints to field values. Unlike access policies, field validation rules cannot access the current user with the `auth()` function and are only checked for 'create' and 'update' operations. The main purpose of field validation is to ensure data integrity and consistency, not for access control.
+
+The [`@core/zod`](./plugins/zod) plugin recognizes the validation attributes and includes them into the generated Zod schemas.
+
+### Field-level validation attributes
+
+The following attributes can be used to attach validation rules to individual fields:
+
+#### String
+
+-   `@length(_ min: Int?, _ max: Int?, _ message: String?)`
+
+    Validates length of a string field.
+
+-   `@startsWith(_ text: String, _ message: String?)`
+
+    Validates a string field value starts with the given text.
+
+-   `@endsWith(_ text: String, _ message: String?)`
+
+    Validates a string field value ends with the given text.
+
+-   `@contains(_text: String, _ message: String?)`
+
+    Validates a string field value contains the given text.
+
+-   `@email(_ message: String?)`
+
+    Validates a string field value is a valid email address.
+
+-   `@url(_ message: String?)`
+
+    Validates a string field value is a valid url.
+
+-   `@datetime(_ message: String?)`
+
+    Validates a string field value is a valid ISO datetime.
+
+-   `@regex(_ regex: String, _ message: String?)`
+
+    Validates a string field value matches a regex.
+
+-   `@trim(_ value: String)`
+
+    Trims whitespace.
+
+-   `@lower(_ value: String)`
+
+    Converts to lowercase.
+
+-   `@upper(_ value: String)`
+
+    Converts to uppercase.
+
+:::info
+Attributes `@trim`, `@lower`, and `@upper` are actually "transformation" instead of "validation". They make sure the values are transformed before storing into the database.
+:::
+
+#### Number
+
+-   `@gt(_ value: Int, _ message: String?)`
+
+    Validates a number field is greater than the given value.
+
+-   `@gte(_ value: Int, _ message: String?)`
+
+    Validates a number field is greater than or equal to the given value.
+
+-   `@lt(_ value: Int, _ message: String?)`
+
+    Validates a number field is less than the given value.
+
+-   `@lte(_ value: Int, _ message: String?)`
+
+    Validates a number field is less than or equal to the given value.
+
+### Model-level validation attributes
+
+You can use the `@@validate` attribute to attach validation rules to a model. 
+
+```
+@@validate(_ value: Boolean, _ message: String?)
+```
+
+Model-level rules can reference multiple fields, use relation operators (`==`, `!=`, `>`, `>=`, `<`, `<=`) to compare fields, use boolean operators (`&&`, `||`, and `!`) to compose conditions, and can use the following functions to evaluate conditions for fields:
+
+-   `function length(field: String, min: Int, max: Int?): Boolean`
+
+    Validates length of a string field.
+
+-   `function regex(field: String, regex: String): Boolean`
+
+    Validates a string field value matches a regex.
+
+-   `function email(field: String): Boolean`
+
+    Validates a string field value is a valid email address.
+
+-   `function datetime(field: String): Boolean`
+
+    Validates a string field value is a valid ISO datetime.
+
+-   `function url(field: String)`
+
+    Validates a string field value is a valid url.
+
+-   `function contains(field: String, search: String, caseInSensitive: Boolean?): Boolean`
+
+    Validates a string field contains the search string.
+
+-   `function startsWith(field: String, search: String): Boolean`
+
+    Validates a string field starts with the search string.
+
+-   `function endsWith(field: String, search: String): Boolean`
+
+    Validates a string field ends with the search string.
+
+-   `function has(field: Any[], search: Any): Boolean`
+
+    Validates a list field contains the search value.
+
+-   `function hasEvery(field: Any[], search: Any[]): Boolean`
+
+    Validates a list field contains every element in the search list.
+
+-   `function hasSome(field: Any[], search: Any[]): Boolean`
+
+    Validates a list field contains some elements in the search list.
+
+-   `function isEmpty(field: Any[]): Boolean`
+
+    Validates a list field is null or empty.
+
+### Example
+
+```zmodel
+model User {
+    id String @id
+    handle String @regex("^[0-9a-zA-Z]{4,16}$")
+    email String? @email @endsWith("@myorg.com", "must be an email from myorg.com")
+    profileImage String? @url
+    age Int @gte(18)
+    activated Boolean @default(false)
+
+    @@validate(!activated || email != null, "activated user must have an email")
+}
+```
+
+## Referential action
+
+### Overview
+
+When defining a relation, you can use referential action to control what happens when one side of a relation is updated or deleted by setting the `onDelete` and `onUpdate` parameters in the `@relation` attribute.
+
+```zmodel
+attribute @relation(
+    _ name: String?,
+    fields: FieldReference[]?,
+    references: FieldReference[]?,
+    onDelete: ReferentialAction?,
+    onUpdate: ReferentialAction?,
+    map: String?)
+```
+
+The `ReferentialAction` enum is defined as:
+
+```zmodel
+enum ReferentialAction {
+    Cascade
+    Restrict
+    NoAction
+    SetNull
+    SetDefault
+}
+```
+
+-   `Cascade`
+
+    -   **onDelete**: deleting a referenced record will trigger the deletion of referencing record.
+
+    -   **onUpdate**: updates the relation scalar fields if the referenced scalar fields of the dependent record are updated.
+
+-   `Restrict`
+
+    -   **onDelete**: prevents the deletion if any referencing records exist.
+    -   **onUpdate**: prevents the identifier of a referenced record from being changed.
+
+-   `NoAction`
+
+    Similar to 'Restrict', the difference between the two is dependent on the database being used.
+
+    See details [here](https://www.prisma.io/docs/concepts/components/prisma-schema/relations/referential-actions#noaction ':target=blank')
+
+-   `SetNull`
+
+    -   **onDelete**: the scalar field of the referencing object will be set to NULL.
+    -   **onUpdate**: when updating the identifier of a referenced object, the scalar fields of the referencing objects will be set to NULL.
+
+-   `SetDefault`
+    -   **onDelete**: the scalar field of the referencing object will be set to the fields default value.
+    -   **onUpdate**: the scalar field of the referencing object will be set to the fields default value.
+
+### Example
+
+```zmodel
+model User {
+    id String @id
+    profile Profile?
+}
+
+model Profile {
+    id String @id
+    user @relation(fields: [userId], references: [id], onUpdate: Cascade, onDelete: Cascade)
+    userId String @unique
+}
+```
diff --git a/docs/sample-catalog.md b/versioned_docs/version-1.x/sample-catalog.md
similarity index 58%
rename from docs/sample-catalog.md
rename to versioned_docs/version-1.x/sample-catalog.md
index 31870baf..0266a003 100644
--- a/docs/sample-catalog.md
+++ b/versioned_docs/version-1.x/sample-catalog.md
@@ -5,15 +5,32 @@ sidebar_label: Sample Catalog
 
 # A Catalog of Sample Projects
 
-The ZenStack team maintains the following two main series of sample projects:
+The ZenStack team maintains the following three series of sample projects.
+
+- **Schema Samples**
+
+    A set of ZModel schemas demonstrating how to model different types of authorization patterns.
 
 - **Multi-Tenant Todo App**
 
-    This series demonstrates a complete implementation of a multi-tenant, SaaS-like application with a relatively complex authorization model. It is also the sample we used throughout [The Complete Guide](/docs/the-complete-guide/).
+    This series demonstrates a complete implementation of a multi-tenant, SaaS-like application with a relatively complex authorization model. It is also the sample we used throughout [The Complete Guide](./the-complete-guide/).
 
 - **Simple Blog App**
 
-    This series is for those who want to quickly understand the structure of a project using ZenStack, without the burden of comprehending a complex application. These samples are used in the [Quick Start](/docs/category/quick-start) guides.
+    This series is for those who want to quickly understand the structure of a project using ZenStack, without the burden of comprehending a complex application. These samples are used in the [Quick Start](./category/quick-start) guides.
+
+We also actively collect and share community-contributed samples.
+
+### Schema Samples
+
+The [sample repo](https://github.com/zenstackhq/authz-modeling-samples) includes the following patterns:
+
+- ACL
+- RBAC
+- ABAC
+- Multi-Tenancy
+
+You can use [this blog post](https://zenstack.dev/blog/model-authz) as an introduction.
 
 ### Multi-Tenant Todo Samples
 
@@ -22,6 +39,7 @@ The ZenStack team maintains the following two main series of sample projects:
 -   [Next.js 13 + NextAuth + tRPC](https://github.com/zenstackhq/sample-todo-trpc)
 -   [Nuxt V3 + TanStack Query](https://github.com/zenstackhq/sample-todo-nuxt)
 -   [SvelteKit + TanStack Query](https://github.com/zenstackhq/sample-todo-sveltekit)
+-   [RedwoodJS](https://github.com/zenstackhq/sample-todo-redwood)
 
 ### Blog Samples
 
@@ -33,3 +51,17 @@ The ZenStack team maintains the following two main series of sample projects:
 -   [Remix](https://github.com/zenstackhq/docs-tutorial-remix)
 -   [Express Backend API](https://github.com/zenstackhq/docs-tutorial-express)
 -   [Clerk Integration](https://github.com/zenstackhq/docs-tutorial-clerk)
+
+### Community Samples
+
+- [Monorepo Todo App](https://github.com/ErikDakoda/sample-todo-nextjs-turbo) by [ErikDakoda](https://github.com/ErikDakoda)
+
+    - PNPM workspace
+    - Turborepo
+    - Next.js + NextAuth
+
+- [Warehouse Management System](https://github.com/Eliav2/zenstack-warehouse-demo) by [Eliav2](https://github.com/Eliav2)
+
+    - Decoupled frontend and backend with monorepo
+    - Backend: ZenStack + Express + GitHub SSO
+    - Frontend: React + TanStack-Query + TanStack-Router + MUI5 + Vite
diff --git a/versioned_docs/version-1.x/the-complete-guide/epilogue.md b/versioned_docs/version-1.x/the-complete-guide/epilogue.md
new file mode 100644
index 00000000..4d0e1473
--- /dev/null
+++ b/versioned_docs/version-1.x/the-complete-guide/epilogue.md
@@ -0,0 +1,29 @@
+---
+description: Epilogue
+sidebar_position: 5
+sidebar_label: Epilogue
+---
+
+# Epilogue
+
+🎉 Congratulations on completing the guide! We hope you've enjoyed the journey and got ready to build your own ZenStack-powered application.
+
+### Let The Schema Drive Your App
+
+Our final version of the Todo app consists of roughly 500 lines of manually written code. That's quite a small codebase for a fully functional application!
+
+ZenStack takes a schema-first approach to model your application. It originated from the Prisma ORM, evolved its schema for extensibility, added access control, introduced a plugin system, and served automatic CRUD APIs. Although it has been a long way, the schema has always been the core. The key is to boil down the application's essential business logic into a concise representation and then automate as many things as possible with it, so you can write less boilerplate code and focus on creating real values.
+
+As its name suggests, we want the project to help bring simplicity, tranquility, and mindfulness back to software development.
+
+<div align="center">
+    <img src="/img/the-complete-guide/zen-coder.png" width="320" />
+</div>
+
+### What's Next
+
+- The [Quick Start](../category/quick-start) documents guide you through creating a simple app using ZenStack with various frameworks. They are fast-paced and can get you up running in minutes.
+- The [Recipes](../category/recipes) include guides for achieving specific tasks. For example, you may want to learn how to [deploy to production](../guides/deploy).
+- The best way to get support or send feedback is to join our [Discord server](https://discord.gg/Ykhr738dUe). The community is responsive and helpful.
+- But you're also welcome to create issues and feature requests directly on [GitHub](https://github.com/zenstackhq/zenstack).
+- Finally, don't forget to [⭐️Star Us⭐️](https://github.com/zenstackhq/zenstack). We appreciate it!
diff --git a/versioned_docs/version-1.x/the-complete-guide/index.md b/versioned_docs/version-1.x/the-complete-guide/index.md
new file mode 100644
index 00000000..01c190e3
--- /dev/null
+++ b/versioned_docs/version-1.x/the-complete-guide/index.md
@@ -0,0 +1,76 @@
+---
+sidebar_label: "The Complete Guide"
+sidebar_position: 4
+title: "ZenStack: The Complete Guide"
+description: The Complete Guide of ZenStack
+---
+
+import Requirements from '../_components/_todo-requirements.md';
+
+# ZenStack: The Complete Guide
+
+## Introduction
+
+Welcome to *The Complete Guide of ZenStack*. This guide is designed to systematically introduce the key concepts in ZenStack and guide you through how to use its essential features.
+
+## Sample Project
+
+Throughout the guide, we'll learn the key concepts and features by gradually building a sample project. The project is a multi-tenant Todo app, which allows users to create and join "Spaces", and collaborate with users in the same space by creating and managing Todo Lists.
+
+Here are the detailed requirements:
+
+<Requirements />
+
+The app is not too complex but covers many important aspects of a modern multi-tenant web application. You can find the complete source code of the project at [https://github.com/zenstackhq/the-complete-guide-sample/tree/v1](https://github.com/zenstackhq/the-complete-guide-sample/tree/v1). It contains several branches, each corresponding to the end state of a part of the guide. For example, you can use branch "part1" as the starting point of your study of Part II.
+
+:::info Hands-on indicator
+
+Many chapters include hands-on sections where we gradually build the sample project. They are all marked with 🛠️ symbol.
+
+:::
+
+You can have a preview of the fully completed version below:
+
+[![Sample Todo App](../assets/todo-app-splash.png)](https://zenstack-todo.vercel.app/)
+
+## Organization
+
+ZenStack consists of several layers of functionalities, each depends on the previous one. These layers are explained in detail in the following four parts. In each part, we'll add more functionalities to our Todo app and eventually finish it by the end of the guide.
+
+### Part I: Supercharged ORM
+
+The first layer works as an extension to Prisma ORM - both for the schema language and the runtime PrismaClient. We made several extensions to the Prisma Schema Language to make it possible to express more than just the database schema. For example, you can attach access policies to the data models. At runtime, ZenStack creates a transparent proxy around PrismaClient to add extra behavior to it - one of the best examples is the automatic access control enforcement.
+
+The extension to Prisma ORM is the foundation of ZenStack and enables all other features above it. These features are also agnostic to the framework you use for the backend. It's simply a drop-in replacement for Prisma.
+
+In this part of the guide, we'll finish building the schema of the sample project - including the data models, access policies, and data validation rules. There's no API or UI yet, but the business logic will be mostly complete.
+
+[Go to Part I](./part1)
+
+### Part II: Plugin System
+
+ZenStack adopts a plugin-based architecture for great extensibility. The plugin system allows you to add new features to ZenStack without modifying the core codebase. In fact, most built-in features of ZenStack are implemented as plugins.
+
+In this part of the guide, you'll see how to use plugins to achieve various goals, and we'll also build a simple plugin for our Todo app from scratch.
+
+[Go to Part II](./part2)
+
+### Part III: Automatic CRUD API
+
+Developers build APIs to support the client side of their applications. One of the most common type of APIs is CRUD - Create, Read, Update, and Delete. Building CRUD APIs is a tedious task but error-prone at the same time.
+
+Since ZenStack already secures data access at the ORM layer, as we learned from the previous part, it can automatically generate CRUD APIs for you. You'll learn about the details in this part of the guide.
+
+In this part of the guide, we'll add a CRUD API layer to our Todo app and turn it into a fully functional backend service.
+
+[Go to Part III](./part3)
+
+### Part IV: Frontend Data Query
+
+Modern web apps often use data query libraries (like [TanStack Query](https://tanstack.com/query) and [SWR](https://swr.vercel.app/)) to fetch data from the backend. These libraries help you build reactive data binding UI with ease.
+
+ZenStack can generate hooks code that talks to the automatic CRUD API introduced in the previous part, targeting these libraries. The hooks help you implement front-end data queries with minimum code and complete type safety. Part IV focuses on this topic.
+
+In this part of the guide, we'll use the generated data query hooks to build a frontend UI for our Todo app. We'll finally complete our full-stack Todo application!
+
+[Go to Part IV](./part4)
diff --git a/versioned_docs/version-1.x/the-complete-guide/part1/0-prisma.md b/versioned_docs/version-1.x/the-complete-guide/part1/0-prisma.md
new file mode 100644
index 00000000..05cb0909
--- /dev/null
+++ b/versioned_docs/version-1.x/the-complete-guide/part1/0-prisma.md
@@ -0,0 +1,115 @@
+---
+sidebar_label: 0. Prisma Crash Course
+---
+
+# Prisma Crash Course
+
+:::info
+Feel free to skip to the [next chapter](./zmodel) if you are already familiar with Prisma.
+:::
+
+ZenStack is built above Prisma ORM, so it'll be important to have a basic understanding of it.
+
+Prisma is a so-called "schema-first" ORM that simplifies database access for Node.js and TypeScript applications. It provides an intuitive and concise DSL (Domain-Specific Language) for defining data models and generates a type-safe client for accessing the database.
+
+This guide is by no means a comprehensive introduction to Prisma, but it covers the most essential parts of understanding and using it.
+
+### Prisma Schema
+
+You can define your data models in a file called `schema.prisma`. Here's an example:
+
+```zmodel
+
+model User {
+    id Int @id @default(autoincrement())
+    email String @unique
+    name String?
+}
+
+```
+
+The `User` model contains a primary key `id` (indicated by the `@id` attribute), a unique `email` field, and an optional `name` field. The `@default` attribute specifies the field's default value, and the `autoincrement` function instructs the database to generate incrementing values automatically.
+
+Modeling relationships is also easy. The following example shows a `Post` model with a one-to-many relationship with the `User` model. The `@relation` attribute is the key for connecting the two models by associating them with a foreign key.
+
+```zmodel
+
+model User {
+    id Int @id @default(@autoincrement())
+    ...
+    posts Post[]
+}
+
+model Post {
+    id Int @id @default(@autoincrement())
+    title String
+    author User @relation(fields: [authorId], references: [id])
+    authorId Int
+}
+
+```
+
+### Prisma Client
+
+You can run the Prisma CLI to generate a type-safe client for accessing the database.
+
+```bash
+npx prisma generate
+```
+
+The client is generated into the `@prisma/client` package and can be used as the following:
+
+```ts
+import { PrismaClient } from '@prisma/client'
+
+const prisma = new PrismaClient();
+
+// create a user
+await prisma.user.create({
+    data: { email: 'user1@abc.com' }
+});
+
+// create a user together with two related posts
+await prisma.user.create({ 
+    data: {
+        email: 'user2@abc.com',
+        posts: {
+            create: [
+                { title: 'Post 1' },
+                { title: 'Post 2' }
+            ]
+        }
+    }
+});
+
+// find posts with title containing some text, and return the author of each post together
+const posts = prisma.post.findMany({
+    where: { title: { contains: 'ZenStack' } },
+    include: { author: true }
+});
+
+// here the `posts` is smartly inferred to be typed `Array<Post & { author: User }>`
+console.log(posts[0].author.email);
+```
+
+### Prisma Migrate
+
+To synchronize your schema to the database tables and fields, run the "migrate" command:
+
+```bash
+npx prisma migrate dev
+```
+
+It synchronizes the local database with the schema and generates a migration record (used for reconstructing database's schema when your app deploys).
+
+When deploying your app to an integration environment (e.g, staging or production), you should run
+
+```bash
+npx prisma migrate deploy
+```
+
+to apply the migration records to the database.
+
+---
+
+Prisma has a rich set of other features not covered here, like schema migration, data browsing, etc., but we've got enough knowledge to understand and use ZenStack. Check out [Prisma Documentation](https://www.prisma.io/docs/getting-started) for a more comprehensive introduction.
diff --git a/versioned_docs/version-1.x/the-complete-guide/part1/1-zmodel.md b/versioned_docs/version-1.x/the-complete-guide/part1/1-zmodel.md
new file mode 100644
index 00000000..9d68a153
--- /dev/null
+++ b/versioned_docs/version-1.x/the-complete-guide/part1/1-zmodel.md
@@ -0,0 +1,180 @@
+---
+sidebar_label: 1. ZModel Language
+---
+
+# ZModel Language
+
+The first thing that ZenStack may surprise you is that, unlike other Prisma tools, we created a new schema language called *ZModel*. It's a superset of the Prisma Schema Language (PSL) with syntax elements to support additional features. The `zenstack` CLI takes a ZModel file as input and generates a Prisma Schema file out of it - which in turn can be fed to the standard `prisma` CLI for generating a Prisma Client or migrating the database.
+
+Why did we invent a new schema language?
+
+### Why ZModel?
+
+#### Custom Attributes
+
+While Prisma Schema Language provides a terse and intuitive way to define data models, it has a major extensibility limitation: you can't add custom attributes. Prisma provides a set of pre-defined attributes to control detailed aspects of your tables and fields, but you're stuck when you need custom ones for your special modeling purposes. Traditionally, Prisma community tools have been hacking around this limitation by smuggling custom information in code comments, like the following example with 
+[TypeGraphQL Prisma](https://prisma.typegraphql.com/):
+
+```zmodel
+model User {
+    id Int @default(autoincrement()) @id
+    email String @unique
+    /// @TypeGraphQL.omit(output: true, input: true)
+    password String
+}
+```
+
+It works, but it's ugly and gets no protection from the compiler. The model can become messy if it's littered with such hacks everywhere. One of the biggest reasons for introducing the ZModel language is to systematically remove this obstacle so that we have a solid foundation for adding custom semantics into the schema down the road.
+
+Here's a quick example of the custom `@password` and `@omit` attributes ZModel added for automatically hashing passwords upon saving and omitting them from query results:
+
+```zmodel
+model User {
+    id Int @default(autoincrement()) @id
+    email String @unique
+    password String @omit @password
+}
+```
+
+The access policies and data validation rules are also implemented with custom attributes. Don't worry. We'll cover them in detail in the following chapters.
+
+#### Other Language Features
+
+A custom schema language also allows us to add new language features besides custom attributes. For example:
+
+1. The `import` syntax for breaking down a large schema into multiple files
+2. The `extends` syntax for inheriting fields from a base model
+
+Here's an example of how to use them to manage large schemas more effectively:
+
+```zmodel title='base.zmodel'
+abstract model Base {
+    id Int @id @default(autoincrement())
+    createdAt DateTime @default(now())
+    updatedAt DateTime @updatedAt()
+    published Boolean @default(true)
+
+    // author has full access
+    @@allow('all', auth() != null && published)
+}
+```
+
+```zmodel title='schema.zmodel'
+import "base"
+model User extends Base {
+    name String
+}
+
+model Post extends Base {
+    title String
+    content String?
+    viewCount Int @default(0)
+    comment Comment[]
+}
+
+model Comment extends Base {
+    content String
+    post Post @relation(fields: [postId], references: [id])
+    postId Int
+}
+```
+
+#### A Better Plugin System
+
+Prisma allows you to write custom generators. However, the generator development API is undocumented and difficult to understand. ZenStack provides a plugin system that enables you to generate custom artifacts with a simple API and object model. In fact, almost all the features of ZenStack itself are implemented as plugins. [Part II](../part2) of this guide is dedicated to covering the plugin system.
+
+### ZModel Structure
+
+The ZModel language is a superset of Prisma Schema Language (PSL). All Prisma schema syntaxes are valid in ZModel. A ZModel can contain the following declarations.
+
+#### Imports
+
+The `import` syntax is an extension to PSL. You can use it to break down a large schema into multiple files.
+
+```zmodel title='schema.zmodel'
+import "user"
+import "post"
+```
+
+```zmodel title='user.zmodel'
+model User {
+    id Int @id @default(autoincrement())
+    email String @unique
+    posts Post[]
+}
+```
+
+```zmodel title='post.zmodel'
+model Post {
+    id Int @id @default(autoincrement())
+    title String
+    author User @relation(fields: [authorId], references: [id])
+    authorId Int
+}
+```
+
+When the `zenstack` CLI compiles an input schema, it merges the content of all imported files into a single schema before further processing.
+
+#### Data Source
+
+The `datasource` declaration of ZModel is exactly the same as PSL. ZenStack passes it to the generated Prisma schema without modification.
+
+```zmodel
+datasource db {
+    provider = "postgresql"
+    url      = env("DATABASE_URL")
+}
+```
+
+#### Prisma Generators
+
+The `generator` declarations of ZModel are exactly the same as PSL. ZenStack passes it to the generated Prisma schema without modification.
+
+```zmodel
+generator client {
+    provider = "prisma-client-js"
+}
+```
+
+#### Plugins
+
+Plugins are the new extensibility mechanism provided by ZModel. Its syntax is similar to generators but with the `plugin` declaration keyword. Here's an example for generating tRPC CRUD routers:
+
+```zmodel
+plugin trpc {
+    provider = "@zenstackhq/trpc"
+    output   = "src/generated"
+}
+```
+
+[Part II](../part2) of this guide will cover the plugin system in detail.
+
+#### Models
+
+The `model` declaration of ZModel is exactly the same as PSL, except for the new set of attributes ZenStack added.
+
+```zmodel
+model User {
+    id Int @id @default(autoincrement())
+
+    // during create and update, ZenStack validates the field is a valid email address
+    email String @unique @email
+
+    // the field is automatically hashed upon saving, and omitted from query results
+    password String @omit @password
+
+    // access policy: open to sign up
+    @@allow('create', true)
+
+    // access policy: the user has full access to self
+    @@allow('all', auth() == this)
+}
+```
+
+### IDE Support
+
+ZenStack comes with a [VSCode extension](https://marketplace.visualstudio.com/items?itemName=zenstack.zenstack) and a [JetBrains IDE plugin](https://plugins.jetbrains.com/plugin/23397-zenstack-language-tools). You can find more information about IDE support [here](../../guides/ide).
+
+### Full Documentation
+
+Check out the [ZModel Language](../../reference/zmodel-language) reference documentation for a complete language description.
diff --git a/versioned_docs/version-1.x/the-complete-guide/part1/2-cli.md b/versioned_docs/version-1.x/the-complete-guide/part1/2-cli.md
new file mode 100644
index 00000000..4ce4b5fd
--- /dev/null
+++ b/versioned_docs/version-1.x/the-complete-guide/part1/2-cli.md
@@ -0,0 +1,392 @@
+---
+sidebar_label: 2. ZenStack CLI
+---
+
+import useBaseUrl from '@docusaurus/useBaseUrl';
+import ThemedImage from '@theme/ThemedImage';
+import ZenStackInit from '../../_components/_zenstack-init.md';
+
+# ZenStack CLI
+
+The `zenstack` CLI is the primary tool for using ZenStack in a project. The CLI is intentionally designed to have similar commands to the `prisma` CLI.
+
+In this chapter, we'll use the CLI to scaffold our Todo sample project. You can find the complete documentation in the [CLI reference guide](../../reference/cli).
+
+### 🛠️ Creating Our Sample Project
+
+#### 1. Initializing a Project
+
+:::info Prerequisites
+- Node.js 18 or above installed.
+- VSCode
+- [ZenStack VSCode Extension](https://marketplace.visualstudio.com/items?itemName=zenstack.zenstack)
+:::
+
+You can initialize an existing TypeScript project using the `zenstack init` command. To save some typing, we'll first use the "try-prisma" utility to create a simple TypeScript CLI project.
+
+```bash
+npx try-prisma@latest -p . -n my-todo-app -t typescript/script --install npm
+cd my-todo-app
+```
+
+Then initialize the project for ZenStack:
+
+```bash
+npx zenstack@1 init
+```
+
+<ZenStackInit />
+
+#### 2. Preparing The Schema
+
+Replace the content of `schema.zmodel` with the following:
+
+```zmodel title='schema.zmodel'
+datasource db {
+  provider = "sqlite"
+  url      = "file:./dev.db"
+}
+
+generator js {
+  provider = "prisma-client-js"
+}
+
+// where users can collaborate on todo lists and items
+model Space {
+  id Int @id @default(autoincrement())
+  createdAt DateTime @default(now())
+  updatedAt DateTime @updatedAt
+  name String
+  slug String @unique
+  owner User @relation(fields: [ownerId], references: [id], onDelete: Cascade)
+  ownerId Int
+  members SpaceUser[]
+  lists List[]
+}
+
+// a join table for many-to-many relation between `Space` and `User`
+model SpaceUser {
+  id Int @id @default(autoincrement())
+  createdAt DateTime @default(now())
+  updatedAt DateTime @updatedAt
+  space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
+  spaceId Int
+  user User @relation(fields: [userId], references: [id], onDelete: Cascade)
+  userId Int
+  role String @default("USER")
+
+  @@unique([userId, spaceId])
+}
+
+// user
+model User {
+  id Int @id @default(autoincrement())
+  createdAt DateTime @default(now())
+  updatedAt DateTime @updatedAt
+  email String @unique
+  name String?
+  spaces SpaceUser[] // user's space membership
+  lists List[]
+  todos Todo[]
+  ownedSpaces Space[] // spaces owned by the user
+}
+
+// todo list
+model List {
+  id Int @id @default(autoincrement())
+  createdAt DateTime @default(now())
+  updatedAt DateTime @updatedAt
+  space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
+  spaceId Int
+  owner User @relation(fields: [ownerId], references: [id], onDelete: Cascade)
+  ownerId Int
+  title String
+  private Boolean @default(false)
+  todos Todo[]
+}
+
+// todo item
+model Todo {
+  id Int @id @default(autoincrement())
+  createdAt DateTime @default(now())
+  updatedAt DateTime @updatedAt
+  owner User @relation(fields: [ownerId], references: [id], onDelete: Cascade)
+  ownerId Int
+  list List @relation(fields: [listId], references: [id], onDelete: Cascade)
+  listId Int
+  title String
+  completedAt DateTime?
+}
+```
+
+:::info
+
+Our sample project uses a SQLite database. You can use any other database supported by Prisma. Please refer to the [Prisma docs](https://www.prisma.io/docs/concepts/database-connectors) for more details.
+
+:::
+
+Take some time to familiarize yourself with the entities and their relations.
+
+![Todo App ERD](../../assets/todo-app-erd.svg)
+
+#### 3. Generating Artifacts
+
+The CLI command you'll most often use is `zenstack generate`. It runs plugins (including the built-in ones that are automatically enabled and the ones you explicitly put into ZModel) to generate various artifacts.
+
+```bash
+npx zenstack generate
+```
+
+The most important artifact is the Prisma schema. It's generated into the `prisma/schema.prisma` file by default. The command also automatically runs `prisma generate` to regenerate the Prisma Client.
+
+You should see output like the following in your terminal:
+
+```
+⌛️ ZenStack CLI vx.y.z, running plugins
+✔ Running plugin Prisma
+✔ Running plugin Model Metadata
+✔ Running plugin Access Policy
+
+👻 All plugins completed successfully!
+Don't forget to restart your dev server to let the changes take effect.
+```
+
+A bunch of plugins are run. When it is complete, the `prisma/schema.prisma` file should be updated to contain the new models we added. You can also use the models in your TS code now since the Prisma Client code has also been regenerated.
+
+Finally, push the schema to the database:
+
+```bash
+npx prisma db push
+```
+
+#### 4. Seeding The Database
+
+Let's create a seed script to populate some test data into our database. Create a new file `prisma/seed.ts` with the following content:
+
+```ts title='prisma/seed.ts'
+import { PrismaClient } from '@prisma/client';
+
+const prisma = new PrismaClient();
+
+async function main() {
+    // clean up
+    await prisma.user.deleteMany();
+    await prisma.space.deleteMany();
+
+    const joey = await prisma.user.create({
+        data: {
+            email: 'joey@zenstack.dev',
+            name: 'Joey'
+        }
+    });
+    console.log('User created:', joey);
+
+    const rachel = await prisma.user.create({
+        data: {
+            email: 'rachel@zenstack.dev',
+            name: 'Rachel'
+        }
+    });
+    console.log('User created:', rachel);
+
+    const centralPerk = await prisma.space.create({
+        data: {
+            name: 'Central Perk',
+            slug: 'central-perk',
+            owner: { connect: { id: rachel.id } },
+            members: {
+                create: [
+                    {
+                        user: { connect: { id: joey.id } },
+                        role: 'USER'
+                    },
+                    {
+                        user: { connect: { id: rachel.id } },
+                        role: 'ADMIN'
+                    }
+                ]
+            }
+        }
+    });
+    console.log('Space created:', centralPerk);
+
+    const rachelPersonal = await prisma.space.create({
+        data: {
+            name: "Rachel's Personal Space",
+            slug: 'rachel',
+            owner: { connect: { id: rachel.id } },
+            members: {
+                create: [
+                    {
+                        user: { connect: { id: rachel.id } },
+                        role: 'ADMIN'
+                    }
+                ]
+            }
+        }
+    });
+    console.log('Space created:', rachelPersonal);
+}
+
+main()
+    .then(async () => {
+        await prisma.$disconnect();
+    })
+    .catch(async (e) => {
+        console.error(e);
+        await prisma.$disconnect();
+        process.exit(1);
+    });
+```
+
+Then add the seed script into "package.json":
+
+```json title='package.json'
+{
+    ...
+    "prisma": {
+        "seed": "ts-node prisma/seed.ts"
+    }
+}
+```
+
+Finally, run the seed command to load the data into the database:
+
+```bash
+npx prisma db seed
+```
+
+```js
+Running seed command `ts-node prisma/seed.ts` ...
+User created: {
+  id: 1,
+  createdAt: 2023-11-07T21:37:22.506Z,
+  updatedAt: 2023-11-07T21:37:22.506Z,
+  email: 'joey@zenstack.dev',
+  name: 'Joey'
+}
+User created: {
+  id: 2,
+  createdAt: 2023-11-07T21:37:22.509Z,
+  updatedAt: 2023-11-07T21:37:22.509Z,
+  email: 'rachel@zenstack.dev',
+  name: null
+}
+Space created: {
+  id: 1,
+  createdAt: 2023-11-07T21:37:22.510Z,
+  updatedAt: 2023-11-07T21:37:22.510Z,
+  name: 'Central Perk',
+  slug: 'central-perk'
+}
+Space created: {
+  id: 2,
+  createdAt: 2023-11-07T21:37:22.512Z,
+  updatedAt: 2023-11-07T21:37:22.512Z,
+  name: "Rachel's Personal Space",
+  slug: 'rachel'
+}
+
+🌱  The seed command has been executed.
+```
+
+#### 5. Querying Data With REPL
+
+ZenStack CLI comes with a convenient REPL mode that you can use to query data interactively. We'll use it to demonstrate ZenStack's features throughout the guide. Let's try a few commands here:
+
+Enter the REPL mode:
+
+```bash
+npx zenstack repl
+```
+
+The REPL provides a global `prisma` instance that you can use directly. Execute a few queries (the REPL automatically awaits the result Promises):
+
+```js
+prisma.user.findFirst()
+```
+
+```js
+{
+  id: 1,
+  createdAt: 2023-11-07T21:37:22.506Z,
+  updatedAt: 2023-11-07T21:37:22.506Z,
+  email: 'joey@zenstack.dev',
+  name: 'Joey'
+}
+```
+
+```js
+prisma.space.findFirst({ include: { members: true } })
+```
+
+```js
+{
+  id: 1,
+  createdAt: 2023-11-07T21:37:22.510Z,
+  updatedAt: 2023-11-07T21:37:22.510Z,
+  name: 'Central Perk',
+  slug: 'central-perk',
+  members: [
+    {
+      id: 1,
+      createdAt: 2023-11-07T21:37:22.510Z,
+      updatedAt: 2023-11-07T21:37:22.510Z,
+      spaceId: 1,
+      userId: 2,
+      role: 'ADMIN'
+    },
+    {
+      id: 2,
+      createdAt: 2023-11-07T21:37:22.510Z,
+      updatedAt: 2023-11-07T21:37:22.510Z,
+      spaceId: 1,
+      userId: 1,
+      role: 'USER'
+    }
+  ]
+}
+```
+
+The REPL also provides a `db` variable for querying data with access control. We'll get back to it in [chapter 4](./access-policy).
+
+You can exit the REPL with the `.exit` command.
+
+#### 6. Listing Installed ZenStack Packages
+
+ZenStack provides multiple npm packages that implement either plugins or runtime functionalities. The `zenstack` and `@zenstackhq/runtime` packages are almost always needed. The other packages are optional and can be installed based on your needs.
+
+When you upgrade ZenStack, it's important to keep all package versions in sync to avoid unexpected results. The `zenstack info` command can help you list all installed ZenStack packages and their versions. It also checks and prompts if there are newer versions available.
+
+Try running it in our project:
+
+```bash
+npx zenstack info
+```
+
+```
+Installed ZenStack Packages:
+    zenstack                    x.y.z
+    @zenstackhq/runtime         x.y.z
+✔ Checking npm registry
+You are using the latest version of ZenStack.
+```
+
+### ZenStack CLI & Prisma CLI
+
+The ZenStack CLI is not meant for replacing the Prisma CLI. After `zenstack generate` generates the Prisma schema, you can use the Prisma CLI as you normally do to complete tasks like:
+
+- Creating migration records with `prisma migrate dev`
+- Pushing schema changes to the db with `prima db push`
+- Launching Prisma Studio with `prisma studio`
+- ...
+
+The Prisma development workflow that you're already familiar with should work seamlessly with ZenStack. See [here](../../guides/dev-workflow) for a detailed explanation of the development workflow.
+
+<ThemedImage
+    alt="ZModel Generation"
+    sources={{
+        light: useBaseUrl('/img/intro/zmodel-generation-light.png'),
+        dark: useBaseUrl('/img/intro/zmodel-generation-dark.png'),
+    }}
+/>
diff --git a/versioned_docs/version-1.x/the-complete-guide/part1/3-enhancement.md b/versioned_docs/version-1.x/the-complete-guide/part1/3-enhancement.md
new file mode 100644
index 00000000..1fed9a98
--- /dev/null
+++ b/versioned_docs/version-1.x/the-complete-guide/part1/3-enhancement.md
@@ -0,0 +1,141 @@
+---
+sidebar_label: 3. Enhanced Prisma Client
+---
+
+# Enhanced Prisma Client
+
+Here we are, the most exciting part of ZenStack.
+
+In the previous chapters, we've been focusing on the design-time concepts: the ZModel language and the `zenstack` CLI. Now, let's get to ZenStack's power at runtime. You'll understand why we say ZenStack ⚡️supercharges⚡️ Prisma.
+
+### What Is Enhanced Prisma Client?
+
+An enhanced Prisma Client is a transparent proxy that wraps a regular Prisma Client instance. It has the same API as the original Prisma Client but adds additional behaviors by intercepting API calls. The added behaviors include:
+
+- Enforcing access policies
+- Data validation
+- Hashing passwords
+- Omitting fields from query results
+
+More will come in the future.
+
+Creating an enhanced Prisma Client is easy, just call the `enhance` API with a regular Prisma Client:
+
+```ts
+import { PrismaClient } from '@prisma/client';
+import { enhance } from '@zenstackhq/runtime';
+
+const prisma = new PrismaClient();
+const db = enhance(prisma);
+
+// db has the same typing as prisma
+await db.user.findMany();
+await db.user.create({ data: { email: 'zen@stack.dev'} });
+```
+
+In a real-world application, you'll usually call `enhance` with an extra context argument to provide the current user identity, so that the access policy engine knows which user is making the call.
+
+```ts
+import { getSessionUser } from './auth';
+
+// the `getSessionUser` implementation depends on your authentication solution
+const db = enhance(prisma, { user: getSessionUser() });
+```
+
+We'll get to that in detail in the next chapter.
+
+A few extra notes about enhanced Prisma Client:
+
+- **Creating an enhanced client is cheap**
+  
+  It doesn't cause new database connections to be made. It's common to create a new enhanced Prisma Client per request.
+
+- **Using Prisma Client Extensions with enhanced client**
+  
+  Enhanced Prisma Client can work with [Prisma Client Extensions](https://www.prisma.io/docs/orm/prisma-client/client-extensions) with some caveats. Please refer to [Using With Prisma Client Extensions](../../guides/client-extensions) for more details.
+
+- **Using the original client and an enhanced one together**
+  
+  You can use both in your application. For example, you may want to use an enhanced client in part of the logic where you want access policy enforcement, while using the original client where you need unrestricted access to the database.
+
+:::warning Limitations
+
+We try to make the enhanced Prisma Client as compatible as possible with the original Prisma Client, but there are still some limitations:
+
+1. No Sequential Operations Transaction
+
+  Enhanced Prisma CLient doesn't support [sequential operations transaction](https://www.prisma.io/docs/concepts/components/prisma-client/transactions#sequential-prisma-client-operations). Use [interactive transaction](https://www.prisma.io/docs/concepts/components/prisma-client/transactions#interactive-transactions) instead, or simply use the original Prisma Client.
+
+1. Raw SQL APIs Are Not Enhanced
+
+  Although you can call raw sql APIs like `$queryRaw` or `$executeRaw`, these APIs are not enhanced, so their behavior is the same as the original Prisma Client. It means that, for example, if you use `@omit` to mark a field to be dropped on return:
+
+  ```zmodel
+  model User {
+      ...
+      password String @omit
+  }
+  ```
+
+  If you query via `$queryRaw`, the `password` field will still be returned.
+
+You should fall back to using the original Prisma Client in such cases.
+:::
+
+### 🛠️ Using Enhanced Prisma Client In REPL
+
+We saw in the previous chapter that in the REPL environment, you can use the built-in `prisma` variable to access Prisma Client directly. Another variable named `db` gives you access to an enhanced Prisma Client.
+
+Let's try it out:
+
+```bash
+npx zenstack repl
+```
+
+```js
+db.user.findMany();
+```
+
+```js
+[]
+```
+
+It works but gives an empty array. Why? With an enhanced Prisma Client, all operations are denied by default unless you explicitly open them up with access policies. Let's see how to do that in the next chapter.
+
+### Inner Workings
+
+:::info
+This part is for those interested in the inner workings of ZenStack. It's not necessary to understand it to use ZenStack.
+:::
+
+If you know the inner workings of Prisma Client, you'll find ZenStack shares some similarities.. When `zenstack generate` is run, besides generating the `prisma/schema.prisma` file, it also runs several other plugins that transform different pieces of information in the ZModel into Javascript code that can be efficiently loaded and executed at runtime. The `enhance` API relies on the generated code to get its job done.
+
+- `model-meta.js`
+
+    Lightweight representation of ZModel's AST.
+
+- `policy.js`
+  
+    Partial Prisma query input objects compiled from access policy expressions.
+
+- `zod/**/*.js|ts`
+
+    Zod schemas for validating input data according to ZModel.
+
+The generation by default outputs to the `node_modules/.zenstack` folder. You can pass a `--output, -o` CLI switch when running `zenstack generate` to use a custom output location.
+
+```bash
+npx zenstack generate -o lib/zenstack
+```
+
+The `enhance` API, by default, loads the generated code from the `node_modules/.zenstack` folder. If you use a custom output location, make sure to use the `loadPath` option to specify it.
+
+```ts
+const db = enhance(prisma, { user: getSessionUser() }, { loadPath: 'lib/zenstack' });
+```
+
+### Next
+
+This chapter gave an abstract overview of the enhanced Prisma Client. In the following chapters, you'll see how each kind of enhancement helps simplify your development work.
+
+Let's roll on.
diff --git a/versioned_docs/version-1.x/the-complete-guide/part1/4-access-policy/4.1-model-level.md b/versioned_docs/version-1.x/the-complete-guide/part1/4-access-policy/4.1-model-level.md
new file mode 100644
index 00000000..128fe480
--- /dev/null
+++ b/versioned_docs/version-1.x/the-complete-guide/part1/4-access-policy/4.1-model-level.md
@@ -0,0 +1,225 @@
+---
+sidebar_label: 4.1 Model-Level Policies
+slug: model-level
+---
+
+# Model-Level Policies
+
+The most frequently used access policies are those declared at the model level - written using `@@allow` and `@@deny` model-level attributes.
+
+:::info Model-level vs field-level attributes
+
+ZenStack follows Prisma's convention: model-level attributes are prefixed with `@@`, and field-level attributes with `@`.
+
+:::
+
+Here's a basic example:
+
+```zmodel
+model Post {
+    id Int @id
+    title String
+    published Boolean @default(false)
+
+    // published posts are readable
+    @@allow('read', published)
+}
+```
+
+You can also achieve the same goal with `@@deny`:
+
+```zmodel
+model Post {
+    id Int @id
+    title String
+    published Boolean @default(false)
+
+    @@allow('read', true)
+    @@deny('read', !published)
+}
+```
+
+Both `@@allow` and `@@deny` take two arguments: 
+
+1. Operation
+   
+   `create`, `read`, `update`, `delete`, or a comma-separated list of them. You can also use `all` to abbreviate all operations.
+
+2. Condition: a boolean expression
+
+    You can use boolean literals: `true` and `false`. Operators `==`, `!=`, `>`, `>=`, `<`, and `<=` can be used to compare values. You can also use `&&`, `||`, and `!` to compose boolean expressions.
+
+    There are functions and special expressions that help you write more advanced conditions. We'll cover them in later chapters.
+
+### Evaluation of Model-Level Policies
+
+You can write as many policy rules as you want for a model. The order of the rules doesn't matter.
+
+ZenStack determines whether a CRUD operation is allowed using the following logic:
+
+1. If any `@@deny` rule evaluates to true, it's denied.
+1. If any `@@allow` rule evaluates to true, it's allowed.
+1. Otherwise, it's denied (secure by default).
+
+Each of the CRUD operation types governs a set of Prisma Client methods, as follows:
+
+- **create**
+  
+    `create`, `createMany`, `upsert`, and `create`/`createMany`/`connectOrCreate` nested in create/update calls.
+
+- **read**
+  
+    `findUnique`, `findUniqueOrThrow`, `findFirst`, `findFirstOrThrow`, `count`, `aggregate`, and `groupBy`.
+    
+    The "read" operation also determines whether the value returned from `create`, `update` and `delete` method can be read.
+
+- **update**
+
+    `update`, `updateMany`, `upsert`, and `update`/`updateMany`/`set`/`connect`/`connectOrCreate`/`disconnect` nested in create/update calls.
+
+- **delete**
+
+    `delete`, `deleteMany`, and `delete` nested in update calls.
+
+:::info Relation manipulation and policies
+
+When a Prisma call involves relation manipulation, it can be unobvious which side the relation needs to satisfy the "update" policies. For example, given the following model:
+
+```zmodel
+model User {
+    ...
+    posts Post[]
+}
+
+model Post {
+    ...
+    author User @relation(fields: [authorId], references: [id])
+    authorId Int
+}
+```
+
+If we add a post to a user like:
+
+```ts
+
+db.user.update({
+    where: { id: 1 },
+    data: {
+        posts: { connect: { id: 1 } }
+    }
+})
+```
+
+Does `Post#1` need to satisfy its "update" policies?
+
+The trick is to consider which side of the relation will have a foreign key update. In this example, the foreign key `authorId` of `Post#1` will be updated, so `Post#1` needs to satisfy its "update" policies, and `User`'s "update" policies are not relevant.
+
+A special case is Prisma's [implicit many-to-many relation](https://www.prisma.io/docs/orm/prisma-schema/data-model/relations/many-to-many-relations#implicit-many-to-many-relations). Since there are no explicit foreign keys in this case, manipulating such a relation requires both sides to satisfy the "update" policies.
+
+:::
+
+### How Do Policies Affect Prisma Client's Behavior?
+
+Enforcing access policies causes an enhanced Prisma Client to behave differently from the original. You can predict what happens with the following principles:
+
+- Read methods behave as if the rows not satisfying the policies don't exist
+
+    For example, `findMany` only returns rows that satisfy the policies. `count` only counts rows that satisfy the policies. This applies to nested reads as well. ZenStack enforces "read" policies by injecting `where` clauses into the Prisma query.
+
+:::warning Nested read can filter out parent records
+
+There's one thing that may catch you off guard. When you do a find and include a to-one relation field, if the field is not nullable and cannot be ready by the current user, it'll result in the parent record being filtered out. For example:
+
+```ts
+// if `author` is not readable, the parent `Post` is excluded
+const posts = await db.post.findMany({ include: { author: true }});
+```
+
+The reason for this design choice is two-fold:
+
+1. Type safety
+
+    If we silently drop the `author` field, we'll break type-safety because the `author` field is not optional in its TS definition.
+
+2. Performance
+
+    Prisma doesn't provide a direct way to include a non-nullable to-one relation conditionally. If we choose to drop the field, we'll have to do post-read processing, which may require additional database queries.
+
+:::
+
+- Bulk update and bulk delete methods behave as if the rows that don't satisfy the policies don't exist
+
+    For example, `updateMany` only updates rows that satisfy the policies. Same for `deleteMany`. ZenStack enforces "update" and "delete" policies by injecting `where` clauses into the Prisma query.
+
+- Other write methods throw errors if the corresponding policies are not satisfied
+
+    For example, `create` throws an error if the policies are not satisfied. When possible, ZenStack determines policy satisfaction by inspecting the input object of the Prisma query. Otherwise, it wraps the write into a transaction and checks the policies after the write is complete but before the transaction commits.
+
+    If a nested write causes a policy violation, the top-level write will be rejected as a whole.
+
+:::info A write can imply a read
+
+Here's another fun fact about access policies. A `create`, `update` or `delete` call may succeed but still throw an error. Why?
+
+The reason is that a write can imply a read. When you call `db.user.create`, the created result will be returned and subject to the "read" policy check. If it fails, an error will be thrown even though the write is persisted.
+
+:::
+
+### 🛠️ Giving It a Try
+
+Let's continue working on our Todo app and add some access policies. Add the following rule to the `User` model:
+
+```zmodel title="schema.zmodel"
+model User {
+    ...
+
+    @@allow('read', startsWith(email, 'joey'))
+}
+```
+
+:::info
+`startsWith` is an attribute function that checks whether a field starts with a given string. We'll cover functions in detail in [Expressions and Functions](./expr-func).
+:::
+
+Then rerun generation and start the REPL:
+
+```bash
+npx zenstack generate
+npx zenstack repl
+```
+
+Now query users with the enhanced Prisma Client:
+
+```js
+db.user.findMany();
+```
+
+This time, we should get back a user. Our policy rule is working!
+
+```js
+[
+  {
+    id: 1,
+    createdAt: 2023-11-07T21:37:22.506Z,
+    updatedAt: 2023-11-07T21:37:22.506Z,
+    email: 'joey@zenstack.dev',
+    name: 'Joey'
+  }
+]
+```
+
+You'll still encounter an error if you try to create a user since we haven't added any "create" rule yet:
+
+```js
+db.user.create({ data: { email: 'ross@zenstack.dev', name: 'Ross' } });
+```
+
+```js
+denied by policy: user entities failed 'create' check
+Code: P2004
+Meta: { reason: 'ACCESS_POLICY_VIOLATION' }
+```
+
+### Next
+
+Using only the direct fields of a model for access control isn't super useful. In the following chapters, you'll learn how to the requesting user's information and relation fields to write more advanced rules.
diff --git a/versioned_docs/version-1.x/the-complete-guide/part1/4-access-policy/4.2-authn-authz.md b/versioned_docs/version-1.x/the-complete-guide/part1/4-access-policy/4.2-authn-authz.md
new file mode 100644
index 00000000..2375759c
--- /dev/null
+++ b/versioned_docs/version-1.x/the-complete-guide/part1/4-access-policy/4.2-authn-authz.md
@@ -0,0 +1,55 @@
+---
+sidebar_label: 4.2 Authentication and Authorization
+slug: authn-authz
+---
+
+# Authentication and Authorization
+
+Authentication and authorization are the two most important concepts for designing a secure application. To set up a solid knowledge foundation, let's first clarify what they mean respectively in the context of ZenStack.
+
+### Authentication
+
+Authentication is the process of verifying the identity of a user. It can be a simple email and password verification or an OAuth workflow that interacts with a third-party identity provider.
+
+ZenStack is not an authentication solution, but it usually depends on one to work. In a real-world application, access control rules usually need to leverage the information of the requesting user. An authentication solution provides the user information.
+
+ZenStack is agnostic to the authentication solution you use. The authentication system only needs to be able to provide an object that represents the current user. ZenStack uses it to create a user-scoped enhanced Prisma Client that enforces access policies.
+
+We'll cover this topic in more detail in the next chapter.
+
+#### Popular Authentication Solutions
+
+Here are some popular authentication solutions in the Javascript/Typescript ecosystem:
+
+- [Auth.js (formerly NextAuth)](https://authjs.dev/)
+- [Clerk](https://clerk.com)
+- [Supabase Auth](https://supabase.com/docs/guides/auth)
+- [Auth0](https://auth0.com)
+- [Firebase Authentication](https://firebase.google.com/docs/auth)
+- [Lucia](https://lucia-auth.com/)
+
+You can find how to integrate some of these solutions in the [Integration With Authentication](../../../category/integrating-with-authentication) guides.
+
+### Authorization
+
+Authorization is the process of determining whether a user has permission to perform a specific action. ZenStack's core part is an authorization solution that directly couples with the database. It controls what CRUD actions a user can perform on a table or a field.
+
+Some commonly used patterns for modeling authorization are listed below (with increasing flexibility and complexity):
+
+1. Access Control List (ACL)
+
+    Users are directly assigned with permissions. For example, a user can be given `read` permission on the `Post` table.
+
+2. Role-Based Access Control (RBAC)
+
+    Users are assigned roles, and roles are configured with permissions. For example, a user can be assigned an `admin` role, which is granted with full permission on the `Post` table.
+
+3. Attribute-Based Access Control (ABAC)
+
+    Permissions are defined over attributes of the user and the resource. For example, a user can be assigned with 'read' permission to a `Post` row if the `Post` is published and the user is a subscribed member.
+
+4. Relation-Based Access Control (ReBAC)
+   
+    Permissions are defined by the presence of relationships between the subjects and resources. For example, a user can edit a `Post` if the user has an "edit" relationship with the `Post`.
+
+A complex application often needs to use a combination of these patterns. ZenStack's access policy system is designed to be easy to use yet have excellent flexibility to tackle these challenges.
diff --git a/versioned_docs/version-1.x/the-complete-guide/part1/4-access-policy/4.3-current-user.md b/versioned_docs/version-1.x/the-complete-guide/part1/4-access-policy/4.3-current-user.md
new file mode 100644
index 00000000..54081c18
--- /dev/null
+++ b/versioned_docs/version-1.x/the-complete-guide/part1/4-access-policy/4.3-current-user.md
@@ -0,0 +1,307 @@
+---
+sidebar_label: 4.3 Accessing Current User
+slug: current-user
+---
+
+# Accessing Current User
+
+In most cases, whether a CRUD operation is allowed depends on the current user. ZenStack provides a special function `auth()` to access the current user in policy rules.
+
+### Auth Model
+
+When ZModel is compiled, the `auth()` function needs to be resolved to a data model so that the compiler knows which fields are accessible from the function. By default, the model named "User" is used as the auth model. Here's an example:
+
+```zmodel
+model User {
+    id Int @id
+    role String
+    posts Post[]
+}
+
+model Post {
+    id Int @id
+    title String
+    author User @relation(fields: [authorId], references: [id])
+    authorId Int
+
+    // ✅ valid rule
+    @@allow('all', auth().role == 'ADMIN')
+
+    // ❌ invalid rule, `subscribed` field doesn't exist in `User` model
+    @@allow('all', auth().subscribed == true) 
+}
+```
+
+You can use a model not named `User` as the auth model by adding the `@@auth` attribute to it.
+
+```zmodel
+model MyUser {
+    id Int @id
+    role String
+    posts Post[]
+
+    @@auth
+}
+```
+
+:::info What if I don't store users in the database?
+
+If you use an external authentication service like Clerk or Supabase, you can choose not to store users in the database, since the auth services are already doing this for you. However, in ZModel, the `auth()` function call always needs to be resolved to an auth model.
+
+The trick here is to still define an auth model, but mark it with the `@@ignore` attribute so that it's excluded from the generated PrismaClient.
+
+```zmodel
+model User {
+    id String @id
+    ...
+
+    @@ignore
+}
+```
+
+Also, when you generate Prisma migrations, make sure to remove the code generated for the `User` model.
+
+Since the `User` model is not mapped to a database table, you shouldn't have other models to have relations to it. Instead, you can store the user id provided by the authentication service in your models, which is like a "foreign key" but pointing to an external system.
+
+:::
+
+### Providing Current User
+
+Since ZenStack is not an authentication solution, it doesn't know who the current user is. It's up to the developer to get it from the authentication side and pass it to ZenStack (when calling the `enhance` function).
+
+Here's the pseudo-code:
+
+```ts
+
+// `getCurrentUser` is an authentication API that extracts 
+// the current user from the request
+const user = await getCurrentUser(request);
+
+// create an enhanced Prisma Client for the user, the `user` object
+// provides value for the `auth()` function in policy rules
+const db = enhance(prisma, { user })
+```
+
+The minimum requirement for the `user` object is to have a value for the "id" field of the auth model (if the model uses compound id fields, all fields need to be assigned). However, if your access policies involve other fields of the auth model, you need to provide them as well. 
+
+:::warning Provide all fields needed by policy rules
+
+ZenStack doesn't query the database automatically to fetch the missing fields, but you can do that as needed. Let's say if you have a policy rule like:
+
+```zmodel
+@@allow('update', auth().role == 'ADMIN')
+```
+
+Before calling `enhance()`, you can hit the database (using the original Prisma Client) to fetch the user's role:
+
+```ts
+const userId = getCurrentUserId(request);
+const user = await prisma.user.findUniqueOrThrow({ where: { id: userId }, select: { id: true, role: true } });
+const db = enhance(prisma, { user });
+```
+
+:::
+
+### Writing Conditions With `auth()`
+
+#### Checking Anonymous User
+
+You can indicate that the current user is anonymous by not passing the `user` object (or passing `undefined`) when calling `enhance`:
+
+```ts
+const db = enhance(prisma);
+```
+
+In policy rules, check `auth() == null` for anonymous user:
+
+```zmodel
+model Post {
+    ...
+
+    // allow all login users to read
+    @@allow('read', auth() != null)
+}
+```
+
+#### Comparing `auth()` With Other Fields
+
+You can compare `auth()` against a field of the same type (i.e., the auth model). Such comparison is equivalent to an id field comparison. For example, the following policy rule:
+
+```zmodel
+model Post {
+    ...
+    author User @relation(fields: [authorId], references: [id])
+    authorId Int
+    @@allow('update', auth() == author)
+}
+```
+
+is equivalent to:
+
+```zmodel
+model Post {
+    ...
+    author User @relation(fields: [authorId], references: [id])
+    authorId Int
+    @@allow('update', auth().id == author.id)
+}
+```
+
+, and is also equivalent to:
+
+```zmodel
+model Post {
+    ...
+    author User @relation(fields: [authorId], references: [id])
+    authorId Int
+    @@allow('update', auth().id == authorId)
+}
+```
+
+#### Traversing Relation Fields
+
+You can access auth model's relation fields from `auth()` function and chain them with other fields. Remember ZenStack doesn't fetch the fields accessed from `auth()` automatically, so you need to provide them (recursively if there's multi-level traversing) when calling `enhance()`.
+
+```zmodel
+model User {
+    ...
+    role Role
+}
+
+model Role {
+    ...
+    permissions Permission[]
+}
+
+model Permission {
+    ...
+    name String  // READ, WRITE, etc.
+}
+
+model Post {
+    ...
+
+    @@allow('read', auth().role.permissions?[name == 'READ'])
+}
+```
+
+:::info
+
+The `expression?[condition]` syntax used above is called "Collection Predicate Expression". It's used for computing a boolean value from a "*-to-many" relation field. You'll learn more about it in the next chapter.
+
+:::
+
+### 🛠️ Adding User-Based Access Control {#add-user-based-access-control}
+
+Add the following policies to the schema:
+
+```zmodel title="schema.zmodel"
+model User {
+  ...
+
+  // everyone can sign up
+  @@allow('create', true)
+
+  // full access by oneself
+  @@allow('all', auth() == this)
+}
+
+model List {
+  ...
+
+  // owner has full access
+  @@allow('all', auth() == owner)
+}
+
+model Todo {
+  ...
+
+  // owner and list owner has full access
+  @@allow('all', auth() == owner || auth() == list.owner)
+}
+```
+
+Rerun generation and start REPL:
+
+```bash
+npx zenstack generate
+npx zenstack repl
+```
+
+:::info Setting Current User
+
+The REPL provides a ".auth" command for setting the current user. The user object passed to the command will serve value for the `auth()` function in policy rules at runtime. Run ".auth" without arguments to switch back to the anonymous user.
+
+:::
+
+Switch to user#1 and make a query:
+
+```js
+.auth { id: 1 }
+db.user.findMany();
+```
+
+```js
+[
+  {
+    id: 1,
+    createdAt: 2023-11-07T21:37:22.506Z,
+    updatedAt: 2023-11-07T21:37:22.506Z,
+    email: 'joey@zenstack.dev',
+    name: 'Joey'
+  }
+]
+```
+
+Switch to user#2 and observe the difference:
+
+```js
+.auth { id: 2 }
+db.user.findMany();
+```
+
+```js
+[
+  {
+    id: 2,
+    createdAt: 2023-11-07T21:37:22.509Z,
+    updatedAt: 2023-11-07T21:37:22.509Z,
+    email: 'rachel@zenstack.dev',
+    name: 'Rachel'
+  }
+]
+```
+
+We can also create Todo List now:
+
+```js
+db.list.create({ data: { title: 'Grocery', owner: { connect: { email: 'rachel@zenstack.dev' } }, space: { connect: { slug: 'central-perk' } } } })
+```
+
+```js
+{
+  id: 1,
+  createdAt: 2023-11-08T04:38:53.385Z,
+  updatedAt: 2023-11-08T04:38:53.385Z,
+  spaceId: 1,
+  ownerId: 2,
+  title: 'Grocery',
+  private: false
+}
+```
+
+However, as user Rachel, we can't create a Todo List on behalf of Joey because it violates the `@@allow('all', auth() == owner)` rule:
+
+```js
+db.list.create({ data: { title: 'Grocery', owner: { connect: { email: 'joey@zenstack.dev' } }, space: { connect: { slug: 'central-perk' } } } })
+```
+
+```js
+denied by policy: list entities failed 'create' check, entity { id: 2 } failed policy check
+Code: P2004
+Meta: { reason: 'ACCESS_POLICY_VIOLATION' }
+```
+
+---
+
+Allowing only the owners to read and write their own data is a good start, but we would like to let users in the same `Space` to collaborate on the Todo lists and items in the space. Achieving this requires traversing relations in policy rules, which we'll cover in the next chapter.
diff --git a/versioned_docs/version-1.x/the-complete-guide/part1/4-access-policy/4.4-relations.md b/versioned_docs/version-1.x/the-complete-guide/part1/4-access-policy/4.4-relations.md
new file mode 100644
index 00000000..8578e9d9
--- /dev/null
+++ b/versioned_docs/version-1.x/the-complete-guide/part1/4-access-policy/4.4-relations.md
@@ -0,0 +1,165 @@
+---
+sidebar_label: 4.4 Working With Relations
+slug: relations
+---
+
+# Working With Relations
+
+In the previous chapters, we've learned how to write basic policy rules involving model fields and the current user. However, real-world applications usually have authorization requirements that need to access relations. Here're some examples:
+
+- A `Todo` is readable if its parent `List` is not private.
+- A `Post` is readable if the current user is a member of the `Organization` that the `Post` belongs to.
+
+In this chapter, we'll learn how to write policy rules that involve relations.
+
+### *-to-one Relations
+
+Accessing *-to-one relation is straightforward. You simply reference the field directly. You can further visit the relation's fields using the dot notation.
+
+```zmodel
+model List {
+    id Int
+    private Boolean
+}
+
+model Todo {
+    id Int
+    list List @relation(...)
+
+    // `list` references a to-one relation
+    @@allow('update', !list.private)
+}
+```
+
+You can chain the dot notation to make deep traversal in the relation graph. There's no limit on how deep you can go.
+
+### *-to-many Relations
+
+To-many relations are a bit more complicated because you're dealing with a list of objects instead of one. The authorization conditions written with to-many relations can be one of the following three forms:
+
+- Does any of the objects in the list satisfy the condition?
+- Does all of the objects in the list satisfy the condition?
+- Does none of the objects in the list satisfy the condition?
+
+ZenStack provides a succinct syntax called "Collection Predicate Expression" for writing such rules:
+
+```
+Any:  relation?[condition]
+All:  relation![condition]
+None: relation^[condition]
+```
+
+The `relation` part refers to a to-many relation field. The `condition` part is a boolean expression scoped to the type of `relation`. I.e., it can reference the member fields of `relation` without any qualification.
+
+:::info
+
+A collection predicate expression must start with a to-many relation field. You can't use a simple array field (like `String[]`) with it. In the next chapter you'll learn about helper functions that help you work with simple arrays.
+
+:::
+
+### 🛠️ Adding Relation-Based Access Control
+
+Let's continue working on the schema of our Todo app and add policies that control data access for users within the same space.
+
+```zmodel title="schema.zmodel"
+model Space {
+  ...
+
+  // require login
+  @@deny('all', auth() == null)
+
+  // everyone can create a space
+  @@allow('create', true)
+
+  // users in the space can read the space
+  @@allow('read', members?[user == auth()])
+
+  // space admin can update and delete
+  @@allow('update,delete', members?[user == auth() && role == 'ADMIN'])
+}
+
+model SpaceUser {
+    ...
+
+    // require login
+    @@deny('all', auth() == null)
+
+    // space owner and admins have full access
+    @@allow('all', space.owner == auth() || space.members?[user == auth() && role == 'ADMIN'])
+
+    // user can read members of spaces that he's a member of
+    @@allow('read', space.members?[user == auth()])
+}
+
+model User {
+    ...
+
+    // everyone can sign up
+    @@allow('create', true)
+
+    // full access by oneself
+    @@allow('all', auth() == this)
+
+    // can be read by users sharing any space
+    @@allow('read', spaces?[space.members?[user == auth()]])
+}
+
+model List {
+    ...
+
+    // require login
+    @@deny('all', auth() == null)
+
+    // can be read by space members if not private
+    @@allow('read', owner == auth() || (space.members?[user == auth()] && !private))
+
+    // when create, owner must be set to current user, and user must be in the space
+    @@allow('create,update', owner == auth() && space.members?[user == auth()])
+
+    // can be deleted by owner
+    @@allow('delete', owner == auth())
+}
+
+model Todo {
+    ...
+
+    // require login
+    @@deny('all', auth() == null)
+
+    // owner has full access
+    @@allow('all', list.owner == auth())
+
+    // space members have full access if the parent List is not private
+    @@allow('all', list.space.members?[user == auth()] && !list.private)
+}
+```
+
+Let's test it out. Rerun generation and start REPL:
+
+```bash
+npx zenstack generate
+npx zenstack repl
+```
+
+Switch to user Joey and fetch Todo Lists:
+
+```js
+.auth { id: 1 }
+db.list.findMany()
+```
+
+```js
+[
+  {
+    id: 1,
+    createdAt: 2023-11-08T04:38:53.385Z,
+    updatedAt: 2023-11-08T04:38:53.385Z,
+    spaceId: 1,
+    ownerId: 2,
+    title: 'Grocery',
+    private: false
+  }
+]
+```
+
+We queried with user Joey and now can get the `List` created by Rachel because Joey is a member of the same space.
diff --git a/versioned_docs/version-1.x/the-complete-guide/part1/4-access-policy/4.5-expr-func.md b/versioned_docs/version-1.x/the-complete-guide/part1/4-access-policy/4.5-expr-func.md
new file mode 100644
index 00000000..139d0c99
--- /dev/null
+++ b/versioned_docs/version-1.x/the-complete-guide/part1/4-access-policy/4.5-expr-func.md
@@ -0,0 +1,186 @@
+---
+sidebar_label: 4.5 Expressions and Functions
+slug: expr-func
+---
+
+# Expressions and Functions
+
+In the previous chapters, we've informally introduced how policy expressions are written. This chapter will list all expression types supported and functions that help you construct complex rules.
+
+### Expressions
+
+#### Literal Expression
+
+- String: both single or double quoted, `"foo"`, `'bar'`.
+- Number: `123`, `3.14`.
+- Boolean: `true`, `false`.
+- Null: `null`.
+
+#### Array Expression
+
+Array of expressions. E.g, `[1, 2, 3]`, `['foo', 'bar']`, `[ADMIN, MEMBER]`.
+
+#### Reference Expression
+
+Used to reference a model field or an enum field.
+
+```zmodel
+model Post {
+    ...
+    published Boolean
+    @@allow('read', published) // `published` is a Reference Expression
+}
+```
+
+#### This Expression
+
+Represented by `this` keyword. Used to address the value of the containing model.
+
+```zmodel
+model User {
+    id Int @id
+    @@allow('all', auth() == this) // `this` is a This Expression
+}
+```
+
+#### Member Access Expression
+
+Used to access a field from an expression.
+
+```zmodel
+model Post {
+    id Int @id
+    published Boolean
+    @@allow('read', auth().role == 'READ') // `auth().role` is a Member Access Expression
+}
+```
+
+#### Invocation Expression
+
+Used to invoke a function. E.g, `auth()` is an Invocation Expression.
+
+#### Unary Expression
+
+- `!` Logical NOT, operand must be boolean
+
+#### Binary Expression
+
+- `==` Equality, translated to id comparison when model types are compared
+- `!=` Inequality, translated to id comparison when model types are compared
+- `>` Greater than, both operands must be number
+- `>=` Greater than or equal to, both operands must be number
+- `<` Less than, both operands must be number
+- `<=` Less than or equal to, both operands must be number
+- `&&` Logical AND, both operands must be boolean
+- `||` Logical OR, both operands must be boolean
+- `in` Membership, with the left operand being an array and the right operand being a literal or a reference to an enum field
+
+### Functions
+
+You can find the detailed signature of each function in the [Predefined attribute functions](../../../reference/zmodel-language#predefined-attribute-functions) document.
+
+#### auth()
+
+Returns the current user. The function call has the type of the [auth model](./current-user#auth-model).
+
+#### now()
+
+Returns the current timestamp. The function call is typed `DateTime`.
+
+```zmodel
+@@allow('read', future().updatedAt < now())
+```
+
+#### future()
+
+Represents the post-update model value. You'll learn more about it in the [next chapter](./post-update).
+
+#### contains()
+
+Returns if a string field contains a value.
+
+```zmodel
+model Post {
+    id Int @id
+    title String
+    @@allow('read', contains(title, 'zenstack'))
+}
+```
+
+The comparison is case-sensitive by default. You can also pass a third argument to make the comparison case insensitive:
+
+```zmodel
+@@allow('read', contains(title, 'zenstack', true))
+```
+
+#### search()
+
+Returns if a string field matches the search condition with [full-text-search](https://www.prisma.io/docs/concepts/components/prisma-client/full-text-search). Need to enable Prisma's "fullTextSearch" preview feature to use.
+
+```zmodel
+@@allow('read', search(title, 'zenstack is awesome'))
+```
+
+#### startsWith()
+
+Returns if a string field starts with a value.
+
+```zmodel
+@@allow('read', startsWith(title, 'zenstack'))
+```
+
+#### endsWith()
+
+Returns if a string field ends with a value.
+
+```zmodel
+@@allow('read', endsWith(title, 'zenstack'))
+```
+
+#### has()
+
+Returns if an array field contains a value.
+
+```zmodel
+model Post {
+    id Int @id
+    tags String[]
+    @@allow('read', has(tags, 'zenstack'))
+}
+```
+
+#### hasEvery()
+
+Returns if an array field contains every value in the provided array.
+
+```zmodel
+model Post {
+    id Int @id
+    tags String[]
+    @@allow('read', has(tags, ['zenstack', 'prisma']))
+}
+```
+
+#### hasSome()
+
+Returns if an array field contains some value in the provided array.
+
+```zmodel
+model Post {
+    id Int @id
+    tags String[]
+    @@allow('read', hasSome(tags, ['zenstack', 'prisma']))
+}
+```
+
+#### isEmpty()
+
+Returns if an array field is empty.
+
+```zmodel
+model Post {
+    id Int @id
+    tags String[]
+    @@allow('read', isEmpty(tags))
+}
+```
diff --git a/versioned_docs/version-1.x/the-complete-guide/part1/4-access-policy/4.6-post-update.md b/versioned_docs/version-1.x/the-complete-guide/part1/4-access-policy/4.6-post-update.md
new file mode 100644
index 00000000..341e9212
--- /dev/null
+++ b/versioned_docs/version-1.x/the-complete-guide/part1/4-access-policy/4.6-post-update.md
@@ -0,0 +1,45 @@
+---
+sidebar_label: 4.6 Post-Update Checks
+slug: post-update
+---
+
+# Post-Update Checks
+
+Among the "create", "read", "update", and "delete" operations, "update" is a special one, because its data has two states: pre-update and post-update. When you write a policy rule for "update", by default, you're checking against the pre-update state. But sometimes, you may want to inspect the post-update state. For example, you may wish to prevent a user from setting a "revision" to a smaller value than the current one.
+
+To define a post-update rule, you use the `future()` function to access the post-update state. Here's an example:
+
+```zmodel
+model Post {
+    ...
+    revision Int
+
+    @@allow('update', future().revision > revision)
+}
+```
+
+Although there are two kinds of update rules, ZenStack uses a single "update" operation to represent both pre-update and post-update checks. The only difference is that you use the `future()` function to access the post-update state. What happens when pre-update and post-update rules are mixed? For example:
+
+```zmodel
+model Post {
+    ...
+    published Boolean
+    revision Int
+
+    @@allow('update', !published && future().revision > revision)
+}
+```
+
+When ZenStack detects that a model has "update" rules involving `future()` calls, it'll postpone the evaluation of all "update" rules of that model to the "post-update" stage. It analyzes what fields' pre-update state is needed and collects them before the update happens.
+
+One of the use cases of post-update rules is to prevent modification of specific fields. For example, in our Todo app, we allow `List`'s owner to update its fields, but we shouldn't allow its owner to be changed. We can use a post-update rule to enforce this:
+
+```zmodel
+model List {
+    ...
+
+    @@deny('update', future().owner != owner)
+}
+```
+
+It works, but it feels a bit awkward for such a common use case. In the next chapter, we'll learn a better way to handle this.
diff --git a/versioned_docs/version-1.x/the-complete-guide/part1/4-access-policy/4.7-field-level.md b/versioned_docs/version-1.x/the-complete-guide/part1/4-access-policy/4.7-field-level.md
new file mode 100644
index 00000000..82fd2581
--- /dev/null
+++ b/versioned_docs/version-1.x/the-complete-guide/part1/4-access-policy/4.7-field-level.md
@@ -0,0 +1,123 @@
+---
+sidebar_label: 4.7 Field-Level Policies
+slug: field-level
+---
+
+# Field-Level Policies
+
+In the previous parts, we've introduced how to write model-level policies to control CRUD permissions. In database terms, this is called row-level security. With the power of relation traversal, current user access, and flexible expressions and functions, you are fully equipped to handle most real-world authorization scenarios. Sometimes, however, you'll find yourself needing more fine-grained access control.
+
+ZenStack's field-level policies allow you to define access rules for individual fields. For example, you can allow a blog post's owner to update its title and content, but only users with the "EDITOR" role can change the "published" field.
+
+The combination of model-level and field-level policies gives you the ultimate granularity and flexibility and lets ZenStack surpass Postgres's native [row-level-security](https://www.postgresql.org/docs/current/ddl-rowsecurity.html) capabilities.
+
+### Defining Field-Level Policies
+
+To define field-level policies, you use the `@allow` and `@deny` field-level attributes to attach rules to fields. Beware that field-level attributes always start with a single `@`. Here's an example:
+
+```zmodel
+model Post {
+    ...
+    published Boolean @allow('update', auth().role == EDITOR)
+}
+```
+
+:::info
+
+You can't use `future()` function in field-level access policies. To express post-update rules, put them into model-level policies. We have an active [feature request](https://github.com/zenstackhq/zenstack/issues/671) for it.
+
+:::
+
+### Difference Between Field-Level and Model-Level Policies
+
+A few key differences between field-level policies and model-level policies:
+
+- Field-level policies only support "read", "update", and "all" operations. It's not meaningful to control "create" and "delete" permissions at the field level.
+- Field-level access is allowed by default. If you don't attach any rule to a field, it's accessible as long as the model is accessible. On the contrary, model-level access is denied by default. You'll have to explicitly open up access using `@@allow` attributes.
+
+### Behavior of Field-Level Access Control
+
+A field's accessibility is determined in the following order:
+
+- If no access policies are defined for the field, the operation is allowed
+- If any `@deny` rule is satisfied, the operation is denied
+- If some `@allow` rules are defined, and none of them are satisfied, the operation is denied
+- Otherwise, the operation is allowed
+
+When a "read" operation is denied, the field is dropped from the result. When an "update" operation is denied, the operation is rejected with an error.
+
+### Overriding Model-Level Policies
+
+To successfully "read" or "update" a field, the current user must first satisfy the corresponding model-level policies unless the field-level `@allow` attribute is passed with a third argument `true` to override.
+
+```zmodel
+
+model Post {
+    ...
+    published Boolean @allow('update', auth().role == EDITOR, true)
+
+    @@allow('update', !published)
+}
+```
+
+In the example above, if a `Post` is published, although the model-level policy denies updates, an EDITOR user can still update the `published` field (and only this field) because of the overriding setting on the field-level policy. Without the override flag, the update operation will be denied.
+
+You can use this feature with "read" policies, too. If a field has an override "read" policy, the field can be read if it's explicitly selected in the query, even if the model-level policy denies access.
+
+### 🛠️ Adding Field-Level Policies
+
+Back to where we left off in the previous chapter, let's tighten up our schema and prevent the `ownerId` field from being updated for `List` and `Todo`:
+
+```zmodel title="schema.zmodel"
+model List {
+    ...
+    ownerId Int @deny('update', true)
+}
+
+model Todo {
+    ...
+    ownerId Int @deny('update', true)
+}
+```
+
+Rerun generation and start REPL:
+
+```bash
+npx zenstack generate
+npx zenstack repl
+```
+
+First, create a new `List` using Joey (id #1):
+
+```js
+.auth { id: 1 }
+db.list.create({ data: { title: "Joey' List", private: true, owner: { connect: { id: 1 } }, space: { connect: { id: 1 } } } })
+```
+
+Result:
+
+```js
+{
+  id: 4,
+  createdAt: 2023-11-09T05:36:20.264Z,
+  updatedAt: 2023-11-09T05:36:20.264Z,
+  spaceId: 1,
+  ownerId: 1,
+  title: "Joey' List",
+  private: true
+}
+```
+
+Try to update the `owner` field to Rachel (id #2):
+
+```js
+db.list.update({ where: { id: 1 }, data: { owner: { connect: 2 } } })
+```
+
+The operation is denied:
+
+```js
+denied by policy: list entities failed 'update' check, entity { id: 1 } failed update policy check for field "owner"
+Code: P2004
+Meta: { reason: 'ACCESS_POLICY_VIOLATION' }
+```
diff --git a/versioned_docs/version-1.x/the-complete-guide/part1/4-access-policy/index.md b/versioned_docs/version-1.x/the-complete-guide/part1/4-access-policy/index.md
new file mode 100644
index 00000000..b380d1d4
--- /dev/null
+++ b/versioned_docs/version-1.x/the-complete-guide/part1/4-access-policy/index.md
@@ -0,0 +1,62 @@
+---
+sidebar_label: 4. Access Policy
+---
+
+# Access Policy
+
+### Introduction
+
+Among ZenStack's enhancements to Prisma, access policy is the most important one. It is also the central pillar that supports all the other features in the upper layers.
+
+The features that ZenStack's access policy provides include:
+
+- Row-level and field-level access control
+- Accessing current user in rules
+- Accessing relations in rules
+- Pre and post-update rules
+- Functions that help you write advanced rules
+
+:::info Secure By Default
+
+When using an enhanced Prisma Client, the first thing that may surprise you is that you can't read or write any data. For example, given the following model:
+
+```zmodel
+model User {
+    id Int @id
+    email String @unique
+}
+```
+
+The following code will result in a runtime error:
+
+```ts
+const prisma = new PrismaClient();
+const db = enhance(prisma);
+await db.user.create({ data: { email: 'zen@stack.dev'} });
+```
+
+The reason is that, with an enhanced client, all CRUD operations are denied by default. You'll need to allow them with policy rules explicitly. This "secure by default" design may cause some inconvenience, but it prevents you from accidentally exposing data to unauthorized users.
+
+:::
+
+### How Access Policies Change Prisma Client's Behavior
+
+Enforcing access policies causes an enhanced Prisma Client to behave differently from the original:
+
+- Read operations can return fewer rows than with a raw Prisma Client.
+- Write operations can fail with an error if policies are violated. See [Error Handling](../../../reference/error-handling) for more details.
+
+### ZenStack Access Policy vs Postgres Row-Level Security
+
+If you are an experienced Postgres user, or are familiar with products like [Supabase](https://supabase.com/) or [PostgREST](https://postgrest.org/), you may find ZenStack's access policy similar to Postgres' [row-level security](https://www.postgresql.org/docs/current/ddl-rowsecurity.html). Conceptually, they're identical: they both let you define access control rules at the schema level and enforce them automatically during data access.
+
+ZenStack's access policy is NOT implemented with Postgres RLS. Here are some key differences between them:
+
+- RLS is implemented at the database engine level, while ZenStack's access policy is at the ORM level.
+- Due to the different levels of implementation, RLS is likely more performant, but ZenStack's access policy is more portable (it supports databases other than Postgres).
+- RLS is defined in SQL, while ZenStack's access policies are defined in a more intuitive expression language.
+- RLS, as its name suggests, is limited to row-level access control. ZenStack's access policy supports both row-level and field-level access control.
+
+---
+
+Following this part of the guide, you'll build an in-depth understanding of how access policy works in ZenStack and how to use it to secure your data.
diff --git a/versioned_docs/version-1.x/the-complete-guide/part1/5-data-validation.md b/versioned_docs/version-1.x/the-complete-guide/part1/5-data-validation.md
new file mode 100644
index 00000000..09043e7b
--- /dev/null
+++ b/versioned_docs/version-1.x/the-complete-guide/part1/5-data-validation.md
@@ -0,0 +1,111 @@
+---
+sidebar_label: 5. Data Validation
+---
+
+#  Data Validation
+
+### Overview
+
+Besides permission control, an application often also has a notion of data validity: what form of data is allowed to be stored in the database? Database schema provides a preliminary mechanism for defining that, but many rules still need to be enforced outside of it. Here are a few quick examples:
+
+- `email` field must be a valid email address.
+- `password` field must be at least 8 characters long.
+- A post's `slug` must only contain letters, numbers, and dashes.
+- `price` field must be a positive number.
+- `password` field is only allowed to have value if `identityProvider` field is set to `credentials`.
+
+Traditionally, developers either write imperative code to validate data or use a declarative validation library like [Zod](https://zod.dev). ZenStack allows you to define such rules right inside the ZModel schema and automatically enforces them with the enhanced Prisma Client. When validation rules are violated during a "create" or "update" operation, the operation is rejected with an error.
+
+:::info
+
+Data validation and access policy may have some similarities, but there are some fundamental differences:
+
+1. Access policies are usually defined regarding the current user, while validation rules are defined against the data itself.
+2. Access policies are mostly evaluated on the database side (through injection into Prisma queries), while data validation is evaluated entirely on the application side.
+3. Access policies govern CRUD operations, while data validation only covers "create" and "update" actions.
+
+:::
+
+### Field-Level Validation
+
+ZenStack provides a set of field-level attributes for defining validation rules. You can find the full list of such attributes [here](../../reference/zmodel-language#field-level-validation-attributes). Here are a few usage examples:
+
+```zmodel
+model User {
+    id Int @id
+    email String @unique @email @endsWith('@zenstack.dev')
+    imgUrl String? @url
+    password String @length(min: 8, max: 32)
+    age Int @gt(0) @lt(120)
+}
+```
+
+All validation attributes also accept an extra parameter `message` for specifying a custom error message. For example:
+
+```zmodel
+model User {
+    ...
+    email String @unique @email('must be a valid email')
+    password String @length(min: 8, max: 32, message: 'must be between 8 and 32 characters long')
+}
+```
+
+### Model-Level Validation
+
+You can use the model-level `@@validate` attribute to define validation rules that involve multiple fields. A set of helper functions are provided for building complex validation expressions. You can find the full list of such functions [here](../../reference/zmodel-language#model-level-validation-attributes). Here's an example:
+
+```zmodel
+model User {
+    id String @id
+    email String? @unique
+    activated Boolean @default(false)
+    @@validate(!activated || email != null, "activated user must have an email")
+}
+```
+
+### 🛠️ Adding Data Validation
+
+We can use data validation to improve our app's robustness in many places. Two such examples are shown here:
+
+1. Make sure `User`'s email is a valid email address.
+
+    ```zmodel
+    model User {
+        ...
+        email String @unique @email
+    }
+    ```
+
+2. Limit the format of `Space`'s slug.
+   
+   ```zmodel
+    model Space {
+        ...
+        slug String @unique @regex('^[0-9a-zA-Z]{4,16}$')
+    }
+    ```
+
+Rerun generation and start REPL:
+
+```bash
+npx zenstack generate
+npx zenstack repl
+```
+
+Try to create a user with an invalid email address:
+
+```js
+db.user.create({ data: { email: 'xyz.abc' } })
+```
+
+Observe the validation error:
+
+```js
+denied by policy: user entities failed 'create' check, input failed validation: Validation error:
+Invalid email at "email"
+Code: P2004
+Meta: {
+  reason: 'DATA_VALIDATION_VIOLATION',
+  zodErrors: ZodError: [...]
+}
+```
diff --git a/versioned_docs/version-1.x/the-complete-guide/part1/6-other-enhancements.md b/versioned_docs/version-1.x/the-complete-guide/part1/6-other-enhancements.md
new file mode 100644
index 00000000..c7692932
--- /dev/null
+++ b/versioned_docs/version-1.x/the-complete-guide/part1/6-other-enhancements.md
@@ -0,0 +1,144 @@
+---
+sidebar_label: 6. Other Enhancements
+---
+
+# Other Enhancements
+
+Besides core enhancements like access policies, ZenStack also provides a few other lightweight enhancements targeting specific use cases.
+
+### Hashing Passwords
+
+When using a credential-based authentication system, it's important never to store the password in plain text. ZenStack provides a simple way to automatically hash passwords before storing them in the database. To enable it, simply mark the password field with the `@password` attribute.
+
+```zmodel
+model User {
+    id Int @id @default(autoincrement())
+    email String @unique
+    password String? @password
+}
+```
+
+Under the hood, ZenStack uses [bcryptjs](https://github.com/dcodeIO/bcrypt.js/tree/master) to generate a hash.
+
+See [here](../../reference/zmodel-language#password) for more details about the `@password` attribute.
+
+#### 🛠️ Adding User Password
+
+Let's add a `password` field to our `User` model, so we can implement credential-based authentication in the future.
+
+```zmodel
+model User {
+    ...
+    password String? @password
+}
+```
+
+Rerun generation, push database schema, and start REPL:
+
+```bash
+npx zenstack generate
+npx prisma db push
+npx zenstack repl
+```
+
+Try to create a new user with a password:
+
+```js
+db.user.create({ data: { email: 'ross@zenstack.dev', password: 'abc123' }})
+```
+
+You may see a surprising error:
+
+```js
+denied by policy: user entities failed 'create' check, result is not allowed to be read back
+Code: P2004
+Meta: { reason: 'RESULT_NOT_READABLE' }
+```
+
+Recall that in previous chapters, we mentioned that "write can imply read". In this case, the `create` operation returns the created user object, which is subject to the "read" policy check. Since we're using an anonymous context, the read operation is denied.
+
+However, if you use the raw Prisma Client to query, you can see the user is created, and his password is hashed:
+
+```js
+prisma.user.findFirst({ orderBy: { id: 'desc' } });
+```
+
+```js
+{
+  id: 3,
+  createdAt: 2023-11-09T05:53:28.793Z,
+  updatedAt: 2023-11-09T05:53:28.793Z,
+  email: 'ross@zenstack.dev',
+  password: '$2a$12$jYdALEg7gtIi.tc9JmFKuOG3X//0Cdo801xYotdtUw5pXV6Ahb.2m',
+  name: null
+}
+```
+
+:::info
+
+This "result not readable" issue is often not a problem in practice, since a user with write privilege can also usually read. Sign-up flow is a special case where a user transitions from anonymous to authenticated. You can catch and ignore the error in the sign-up part. After the user logs in, you can use his identity for the subsequent operations.
+
+Or alternatively, you can use the raw Prisma Client in the authentication part of your system.
+
+:::
+
+### Omitting Fields
+
+Some database fields can be sensitive and should not be exposed to the client. Password is a good example (even when it's hashed). You can use the `@omit` attribute to mark a field, and it'll be automatically omitted when queried from an enhanced Prisma Client.
+
+```zmodel
+model User {
+    ...
+    password String? @password @omit
+}
+```
+
+:::info
+
+You can use a field-level access policy to achieve the same goal:
+
+```zmodel
+model User {
+    ...
+    password String? @password @allow('read', false)
+}
+```
+
+However, using `@omit` is more appropriate since conceptually omitting sensitive field is not a permission issue.
+:::
+
+#### 🛠️ Omitting Password Field
+
+Let's mark the `password` field with `@omit`:
+
+```zmodel
+model User {
+    ...
+    password String? @password @omit
+}
+```
+
+Rerun generation, push database schema, and start REPL:
+
+```bash
+npx zenstack generate
+npx prisma db push
+npx zenstack repl
+```
+
+Query users, and you'll find the `password` field is omitted:
+
+```js
+.auth { id: 1 }
+db.user.findFirst();
+```
+
+```js
+{
+  id: 1,
+  createdAt: 2023-11-07T21:37:22.506Z,
+  updatedAt: 2023-11-07T21:37:22.506Z,
+  email: 'joey@zenstack.dev',
+  name: 'Joey'
+}
+```
diff --git a/versioned_docs/version-1.x/the-complete-guide/part1/7-debugging.md b/versioned_docs/version-1.x/the-complete-guide/part1/7-debugging.md
new file mode 100644
index 00000000..1adb96dd
--- /dev/null
+++ b/versioned_docs/version-1.x/the-complete-guide/part1/7-debugging.md
@@ -0,0 +1,61 @@
+---
+sidebar_label: 7. Debugging
+---
+
+#  Debugging
+
+ZenStack provides you with a set of powerful tools to model the authorization aspects of your application. However, it can be challenging to understand why an operation is rejected or why a query gives seemingly incorrect results.
+
+You can let ZenStack log all queries sent to the wrapped Prisma Client and use it to inspect the effect of your policy rules. To enable the logging, pass an extra `logPrismaQuery` option when calling the `enhance` function:
+
+```ts
+const db = enhance(prisma, { user }, { logPrismaQuery: true });
+```
+
+The logs are output to the logger of Prisma Client with "info" level, so to be able to see it on the console, you'll also need to make sure it's turned on in the Prisma Client options:
+
+```ts
+const prisma = new PrismaClient({ log: ['info'] });
+```
+
+After setting these up, you should be able to see all Prisma queries ZenStack makes in the console.
+
+### 🛠️ Debugging In REPL
+
+The REPL environment provides a ".debug" command to turn on/off debugging output on the fly.
+
+```js
+.auth { id: 1 }
+.debug
+db.list.findMany()
+```
+
+You should see the Prisma queries injected by ZenStack printed in the console.
+
+```js
+prisma:info [policy] `findMany` list:
+{
+  where: {
+    AND: [
+      { NOT: { OR: [] } },
+      {
+        OR: [
+          { owner: { is: { id: 1 } } },
+          {
+            AND: [
+              {
+                space: {
+                  members: {
+                    some: { user: { is: { id: 1 } } }
+                  }
+                }
+              },
+              { NOT: { private: true } }
+            ]
+          }
+        ]
+      }
+    ]
+  }
+}
+```
diff --git a/versioned_docs/version-1.x/the-complete-guide/part1/8-conclusion.md b/versioned_docs/version-1.x/the-complete-guide/part1/8-conclusion.md
new file mode 100644
index 00000000..20c077eb
--- /dev/null
+++ b/versioned_docs/version-1.x/the-complete-guide/part1/8-conclusion.md
@@ -0,0 +1,21 @@
+---
+sidebar_label: 8. Conclusion
+---
+
+import Requirements from '../../_components/_todo-requirements.md';
+
+#  Conclusion
+
+In this part of the guide, we've focused on the core layer of ZenStack - an enhanced Prisma Client. The enhanced Prisma Client essentially turns the ORM into a fully secured data access layer, enabling the upper layer features like the automatic CRUD APIs and the generation of frontend data query libraries.
+
+The design choice of enhancing Prisma Client makes ZenStack framework-agnostic. It can be used wherever Prisma is used - a CLI application, a Node.js backend, or a full-stack application - as a non-intrusive drop-in replacement.
+
+As you have seen, the ZModel language provides great flexibility in modeling the authorization aspects of your application. If you want to learn how commonly used patterns like RBAC, ABAC, etc., can be modeled in ZModel, this blog post can be a good reference: [Modeling Authorization in Prisma - No Theory, Just Code](https://zenstack.dev/blog/model-authz).
+
+---
+
+The completed sample project up to the end of this part can be found in the "part1" branch of the [the-complete-guide-sample](https://github.com/zenstackhq/the-complete-guide-sample/tree/part1) repo. You can clone it and check out to that branch with the following command:
+
+```bash
+git clone --branch v1-part1 https://github.com/zenstackhq/the-complete-guide-sample.git my-todo-app
+```
diff --git a/versioned_docs/version-1.x/the-complete-guide/part1/index.md b/versioned_docs/version-1.x/the-complete-guide/part1/index.md
new file mode 100644
index 00000000..7bbd9da3
--- /dev/null
+++ b/versioned_docs/version-1.x/the-complete-guide/part1/index.md
@@ -0,0 +1,21 @@
+---
+sidebar_label: "Part I: Supercharged ORM"
+sidebar_position: 1
+---
+
+# Part I: Supercharged ORM
+
+Welcome to the first part of *ZenStack: The Complete Guide*!
+
+As the creators of ZenStack, we love Prisma and enjoy its excellent developer experience. And we want to achieve more with it! The most fundamental thing that ZenStack does is turning Prisma into a more powerful database tool and a lot more than an ORM:
+
+- Access policies
+- Data validation
+- Custom attributes and functions
+- Multi-file schema
+- Model inheritance
+- ...
+
+Essentially, your schema becomes the single source of truth of data and rules, and at runtime, your database client not only handles CRUD operations but also does it in a secure way.
+
+In this part of the guide, you'll learn all the concepts of using ZenStack as a drop-in replacement for Prisma in backend development. Let's get started.
diff --git a/versioned_docs/version-1.x/the-complete-guide/part1/under-the-hood.md b/versioned_docs/version-1.x/the-complete-guide/part1/under-the-hood.md
new file mode 100644
index 00000000..d840fe91
--- /dev/null
+++ b/versioned_docs/version-1.x/the-complete-guide/part1/under-the-hood.md
@@ -0,0 +1,290 @@
+---
+sidebar_label: "Extra: Under The Hood"
+---
+
+#  How Does ZenStack Work Under the Hood?
+
+:::warning Advanced Topic
+
+This document is for advanced users who want to understand how ZenStack works under the hood. It's not required for using ZenStack.
+
+:::
+
+ZenStack extends Prisma ORM mainly at two levels. First, it provides a modeling DSL (ZModel) - a superset of Prisma schema. Second, it allows the creation of enhanced wrappers for Prisma clients at runtime for injecting extra behaviors.
+
+This document explains how these extensions work so that you can make a more informed judgment on whether ZenStack is the right choice for you.
+
+## ZModel Language
+
+ZenStack implemented the ZModel DSL from scratch, including the CLI and the VSCode extension, using the fantastic language toolkit [Langium](https://langium.org/). The DLS includes a plugin system, allowing a modular and extensible way to generate different artifacts from the schema. The core functionality of the toolkit is supported by the following three core plugins:
+
+-   Prisma: `@core/prisma`
+
+    The Prisma plugin generates the Prisma schema and Prisma client from the ZModel schema. The Prisma schema can then be used for common Prisma tasks like `db push`, `migrate dev`, etc.
+
+-   Model-meta: `@core/model-meta`
+
+    The model-meta plugin generates the model metadata, which provides basic information about models and fields at runtime. The metadata is much more lightweight than the whole ZModel AST and is much cheaper to load.
+
+    The default output location is `node_modules/.zenstack/model-meta.ts`.
+
+-   Policy: `@core/access-policy`
+
+    The access policy plugin converts access policy rules (expressed with `@@allow` and `@@deny` attributes) into checker functions. The functions take a context object as input and return partial Prisma query objects, which will be injected into Prisma query arguments at runtime. The context object contains the following properties:
+
+    -   `user`: the current user, which serves as the return value of [`auth()`](./access-policy/current-user) in the policy rules.
+    -   `preValue`: the previous value of an entity before update (for supporting the [`future()`](./access-policy/post-update) function in the policy rules).
+
+    The default output location is `node_modules/.zenstack/policy.ts`.
+
+    For example, for the following `Post` model:
+
+    ```zmodel
+    model Post {
+        id        String @id @default(cuid())
+        createdAt DateTime @default(now())
+        updatedAt DateTime @updatedAt
+        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)
+    }
+    ```
+
+    , the following checker functions are generated for "read" and "update" respectively:
+
+    ```ts
+    function Post_read(context: QueryContext) {
+        const user = hasAllFields(context.user, ['id']) ? context.user : null;
+        return {
+            OR: [
+                user == null
+                    ? { OR: [] } // false condition
+                    : {
+                          author: {
+                              is: {
+                                  id: user.id,
+                              },
+                          },
+                      },
+                {
+                    AND: [
+                        user == null ? { OR: [] } : { AND: [] },
+                        {
+                            published: true,
+                        },
+                    ],
+                },
+            ],
+        };
+    }
+
+    function Post_update(context: QueryContext) {
+        const user = hasAllFields(context.user, ['id']) ? context.user : null;
+        return user == null
+            ? { OR: [] } // false condition
+            : {
+                  author: {
+                      is: {
+                          id: user.id,
+                      },
+                  },
+              };
+    }
+    ```
+
+## Runtime Enhancements
+
+The primary responsibility of ZenStack's runtime is to create _enhanced_ Prisma client instances:
+
+-   `enhance` creates an enhanced client that includes all behaviors below.
+-   `withPolicy` creates an enhanced client that enforces access policies expressed with `@@allow` and `@@deny` attributes.
+-   `withPassword` creates an enhanced client that automatically hashes fields marked with the `@password` attribute before storing them in the database.
+-   `withOmit` creates an enhanced client that automatically strips fields marked with the `@omit` attribute before returning to the caller.
+
+### Proxies
+
+Runtime enhancements are achieved by creating transparent proxies around raw Prisma clients. The proxies intercept all Prisma client methods, inject into query arguments, and manipulate the query results returned by the client. The proxies work independently from each other so that they can be freely combined. In fact, the `enhance` helper is a direct combination of `withPassword`, `withOmit`, and `withPolicy`.
+
+```ts
+export function enhance<DbClient extends object>(
+    prisma: DbClient,
+    context?: WithPolicyContext,
+    options?: EnhancementOptions
+) {
+    return withPolicy(withOmit(withPassword(prisma, modelMeta), modelMeta), context, policy, modelMeta);
+}
+```
+
+### Access Policies
+
+Access policies, enabled by the `withPolicy` enhancer, are the most complex parts of the system. Part of the complexity comes from the great flexibility Prisma offers in querying and mutating data. For example, to enforce "read" rules on a `Post` model, we need to consider several possibilities:
+
+```ts
+// a direct where condition
+prisma.post.findMany({
+    where: {...}
+});
+
+// nested fetch for relations
+prisma.user.findUnique({
+    where: {id: ...},
+    include: { posts: true }
+});
+
+// nested fetch during mutation
+prisma.user.update({
+    where: {id: ...},
+    data: { ... },
+    select: { id: true, email: true, posts: true }
+});
+
+// ...
+```
+
+We need the following measures to enforce access policies systematically:
+
+1. **Inject filter conditions into the "where" clause in the context of "find many"**
+
+    This covers cases like direct `findMany`/`findUnique`/`findFirst`/... calls:
+
+    ```ts
+    prisma.user.findMany({ where: { ... } });
+
+    // to
+
+    prisma.user.findMany({
+        where: {
+            AND: [
+                { /* original conditions */ },
+                { /* read conditions */ },
+            ],
+        },
+    });
+    ```
+
+    Or nested "find" for a to-many relation:
+
+    ```ts
+    prisma.user.findMany({ include: { posts: true } });
+
+    // to
+
+    prisma.user.findMany({
+        include: {
+            posts: {
+                where: {
+                    /* read conditions */
+                },
+            },
+        },
+    });
+    ```
+
+    Or an implicit "find" carried with a mutation:
+
+    ```ts
+    prisma.user.update({ data: { ... }, include: { posts: true } });
+
+    // to
+
+    prisma.user.update({ data: { ... }, include: { posts: { where: { /* read conditions */ } } } });
+    ```
+
+1. **Inject filter conditions into the "where" clause of "mutate many"**
+
+    This covers cases like `updateMany` and `deleteMany`:
+
+    ```ts
+    prisma.user.updateMany({ where: { ... }, data: { ... } });
+
+    // to
+
+    prisma.user.updateMany({
+        where: {
+            AND: [ { /* original conditions */ }, { /* update conditions */ } ]
+        },
+        data: { ... }
+    });
+    ```
+
+    Or nested usage of them:
+
+    ```ts
+    prisma.user.update({ where: { ... }, data: { posts: { deleteMany: { where: { ... } } } } });
+
+    // to
+
+    prisma.user.update({ where: { ... },
+        data: {
+            posts: {
+                deleteMany: {
+                    AND: [ { /* original conditions */ }, { /* delete conditions */ } ]
+                }
+            }
+        }
+    });
+    ```
+
+1. **Post-read check for entities fetched as a to-one relation**
+
+    To-one relation is a special case for reading because there's no way to do filtering at the read time: you either include it or not. So, we need to do a post-read check to ensure the fetched entity can be read.
+
+    ```ts
+    const user = prisma.user.findUnique({ where: { id: ... }, include: { profile: true } });
+
+    // to
+
+    const user = prisma.user.findUnique({ where: { id: ... }, include: { profile: true } });
+    if (profile && !readable(user.profile)) {
+       // throw rejected error
+    }
+    ```
+
+1. **Transaction-protected mutations**
+
+    Policies that do "post-mutation" checks, including "create" and "post-update" ("update" rule calling [`future()`](#the-future-function) function) rules, are protected by a transaction. The mutation is conducted first, and then post-mutation checks are performed. If any of the checks fail, the transaction rolls back.
+
+    Although, for simple cases, we can enforce policies by checking the mutation input, there are many cases where we can't safely rely on that. Instead, employing a transaction is the most reliable way to achieve a consistent result. In the future, we may add input checking as an optimization where possible.
+
+    ```ts
+    prisma.user.create({ data: { ... } });
+
+    // to
+
+    prisma.$transaction((tx) => {
+        const user = prisma.user.create({ data: { ... } });
+        if (!createable(user)) {
+            // throw rejected error
+        }
+    })
+    ```
+
+### The `auth()` function
+
+The `auth()` function connects authentication with access control. It's typed as the auth model (the model named "User" by default) in ZModel and represents the current authenticated user. The most common way of setup is to read the auth model entity from the database after authentication is completed and pass the result to the `enhance` function as context.
+
+Although `auth()` resolves to the auth model, since it's provided by the user, there's no way to guarantee its value fully conforms to the model' typing: e.g., non-nullable fields can be passed as `null` or `undefined`. We employ some simple rules to deal with such cases:
+
+-   If `auth()` is `undefined`, it's normalized to `null` when evaluating access policies.
+-   If `auth()` itself is `null`, any member access (or chained member access) is `null`.
+-   `expression == null` evaluates to `true` if `expression` is `null`.
+-   Otherwise, a boolean expression evaluates to `false` if a `null` value is involved.
+
+Here are a few examples (assuming `auth()` is `null`):
+
+1. `auth() == null` -> `true`
+2. `auth() != null` -> `false`
+3. `auth().name == null` -> `true`
+4. `auth().age > 0` -> `false`
+5. `auth().age < 0` -> `false`
+
+### The `future()` function
+
+An "update" policy rule is treated as a "post-update" rule if it involves a `future()` function call. `future()` represents the value of the model entity after the update is completed. In a "post-update" policy rule, any member accesses that are not prefixed with `future().` is treated as referencing the entity's value before the update. To support the evaluation of such rules, the entity value before the update is captured and passed as the `preValue` field in the context object passed to the checker function.
diff --git a/versioned_docs/version-1.x/the-complete-guide/part2/1-using-plugins.md b/versioned_docs/version-1.x/the-complete-guide/part2/1-using-plugins.md
new file mode 100644
index 00000000..93fe8385
--- /dev/null
+++ b/versioned_docs/version-1.x/the-complete-guide/part2/1-using-plugins.md
@@ -0,0 +1,32 @@
+---
+sidebar_label: 1. Using Plugins
+---
+
+# Using Plugins
+
+To use a plugin, install the plugin npm package:
+
+```bash
+npm install --save-dev @zenstackhq/openapi
+```
+
+:::info
+
+Many plugins are design-time only, and you should install them as dev dependencies. However, some plugins are also used at runtime and need to be installed as regular dependencies. Refer to the plugin's documentation for details.
+
+:::
+
+Then add a plugin declaration in your ZModel:
+
+```zmodel title="schema.zmodel"
+plugin openapi {
+    provider = "@zenstackhq/openapi"
+    output = "openapi.yaml"
+    title = "My Todo API"
+    version = "1.0.0"
+}
+```
+
+A plugin declaration requires a mandatory `provider` field that specifies the package name or Javascript module path of the plugin (which the `zenstack` CLI imports directly with `require()`). It can also contain other mandatory and optional fields specific to the plugin.
+
+When you run `zenstack generate`, the plugin will be executed and generate the output files.
diff --git a/versioned_docs/version-1.x/the-complete-guide/part2/2-builtin-plugins.md b/versioned_docs/version-1.x/the-complete-guide/part2/2-builtin-plugins.md
new file mode 100644
index 00000000..a145ef7e
--- /dev/null
+++ b/versioned_docs/version-1.x/the-complete-guide/part2/2-builtin-plugins.md
@@ -0,0 +1,58 @@
+---
+sidebar_label: 2. Built-in Plugins
+---
+
+# Built-in Plugins
+
+ZenStack ships with a set of built-in plugins. They fall into the following two categories.
+
+### Core Plugins
+
+Most of the core functionalities of ZenStack are implemented as or supported by a few core plugins. They're vital to the operation of ZenStack and are enabled automatically as needed. Core plugins are implemented inside the `zenstack` npm package and have names starting with `@core/`.
+
+Here's a quick overview of them:
+
+- [@core/prisma](../../reference/plugins/prisma)
+
+    Transforms ZModel to Prisma schema and runs `prisma generate` to generate Prisma Client.
+
+- [@core/model-meta](../../reference/plugins/model-meta)
+
+    Transforms ZModel to lightweight Javascript to be used at runtime.
+
+- [@core/access-policy](../../reference/plugins/access-policy)
+
+    Transforms access policies into partial Prisma query objects for injecting Prisma queries at runtime.
+
+- [@core/zod](../../reference/plugins/zod)
+
+    Transforms ZModel into [Zod](https://zod.dev/) schemas for validating input data at runtime.
+
+The ZenStack CLI automatically decides whether a core plugin should be enabled based on the ZModel. You can also explicitly declare a core plugin to override its options. For example, declare a `@core/prisma` plugin to output the generated Prisma schema file to a custom location:
+
+```zmodel
+plugin prisma {
+    provider = "@core/prisma"
+    output = "src/db/prisma/schema.prisma"
+}
+```
+
+### Maintained Plugins
+
+Besides core plugins, the ZenStack team maintains a set of useful but non-essential plugins. They are implemented in separate npm packages under the `@zenstackhq` organization.
+
+- [@zenstackhq/tanstack-query](../../reference/plugins/tanstack-query)
+
+    Generates frontend data query hooks for targeting [Tanstack Query](https://tanstack.com/query). In [Part III](../part3/) and [Part IV](../part4/) of this guide, you'll learn more about using ZenStack for full-stack development.
+
+- [@zenstackhq/swr](../../reference/plugins/swr)
+
+    Generates frontend data query hooks for targeting [SWR](https://swr.vercel.app/). In [Part III](../part3/) and [Part IV](../part4/) of this guide, you'll learn more about using ZenStack for full-stack development.
+
+- [@zenstackhq/trpc](../../reference/plugins/trpc)
+
+    Generates [tRPC](https://trpc.io/) routers for database queries and mutations.
+  
+- [@zenstackhq/openapi](../../reference/plugins/openapi)
+
+    Generates [OpenAPI](https://www.openapis.org/) specs from the ZModel schema.
diff --git a/versioned_docs/version-1.x/the-complete-guide/part2/3-writing-plugins.md b/versioned_docs/version-1.x/the-complete-guide/part2/3-writing-plugins.md
new file mode 100644
index 00000000..60be0303
--- /dev/null
+++ b/versioned_docs/version-1.x/the-complete-guide/part2/3-writing-plugins.md
@@ -0,0 +1,318 @@
+---
+sidebar_label: 3. Writing Plugins
+---
+
+# Writing Plugins
+
+The ZModel schema is the single source of truth for your data and access control. It can be used to generate many other valuable things based on your specific needs. In this part of the guide, we'll learn the fundamentals of how to write plugins and create a simple one that generates a markdown documentation from the ZModel schema.
+
+### Structure of a Plugin
+
+A plugin is a Node.js module that has the following exports:
+
+1. A named export `name` that specifies the name of the plugin used for logging and error reporting.
+2. A default function export containing the plugin logic.
+
+Here's what a bare minimum plugin looks like:
+
+```ts
+import type { DMMF } from '@prisma/generator-helper';
+import { type PluginOptions } from '@zenstackhq/sdk';
+import { type Model } from '@zenstackhq/sdk/ast';
+
+export const name = 'My Plugin';
+
+export default async function run(model: Model, options: PluginOptions, dmmf: DMMF.Document) {
+    ...
+}
+```
+
+When `zenstack generate` runs, it identifies all the plugins declared in the ZModel, loads their Javascript modules, and calls the default export functions. The following sections explain the arguments passed to the plugin function.
+
+#### ZModel AST
+
+The first input of the plugin function is the ZModel AST. It is the result object model of parsing and linking the ZModel schema - a tree structure containing all the information in the schema. Most plugins traverse the AST, extract information of interest, and transform it into another form.
+
+The following diagram shows the (abbreviated) AST of a simple ZModel schema:
+
+```zmodel
+datasource db {
+    provider = 'sqlite'
+    url = 'file:.dev.db'
+}
+
+model User {
+    id Int @id @default(autoincrement())
+    posts Post[]
+}
+
+model Post {
+    id Int @id @default(autoincrement())
+    title String
+    author User @relation(fields: [authorId], references: [id])
+    authorId Int
+
+    @@allow('all', auth() == author)
+}
+```
+
+```mermaid
+---
+title: ZModel AST
+---
+flowchart TD
+    Model --> DS(datasource db) & DM1(model User) & DM2(model Post)
+    DS --> CF1(provider='sqlite') & CF2(url='file:.dev.db')
+    DM1 --> DM1F1("id Int @id @default(autoincrement())") & DM1F2("posts Post[]")
+    DM1F2 --> Post
+    DM1F1 --> DM1F1A1("@id") & DM1F1A2("@default(autoincrement())")
+    DM1F1A2 --> DM1F1A2A1("@default") & DM1F1A2A2("autoincrement()")
+    DM2 --> DM2F1("title String") & DM2F2("author User @relation(...)") & DM2A1("@@allow('update', auth() == author)")
+    DM2F2 --> User & REL("@relation")
+    REL --> Ellipsis(...)
+    DM2A1 --> DM2A1A1("@@allow") & Expr1("auth() == author")
+    Expr1 --> Auth("auth()") & Author("author")
+```
+
+The best way to understand the AST structure is to inspect the [ZModel language definition](https://github.com/zenstackhq/zenstack/blob/main/packages/language/src/zmodel.langium).
+
+#### Plugin Options
+
+The second input of the plugin function is the options - an object containing the config fields that the plugin user specified in the ZModel. As a plugin developer, you should define and document what config options your plugin supports.
+
+#### Prisma DMMF
+
+The third input of the plugin function is the Prisma DMMF (Data Model Meta Format). DMMF is essentially Prisma Schema's AST enriched with other information needed to generate Prisma Client. It is the main API that developers use to write custom Prisma generators. However, the API is undocumented and considered internal.
+
+For ZenStack plugins, the DMMF is provided as an auxiliary input. If you only need information from the ZModel schema, the ZModel AST is all you need. However, if you need to access types generated for the Prisma Client (e.g., the parameter shapes of `prisma.user.findMany()`), DMMF is the only way to get them. The best way to learn how to use DMMF is to study the source code of [Prisma's community generators](https://www.prisma.io/docs/concepts/components/prisma-schema/generators#community-generators).
+
+### Adding Custom Attributes
+
+A plugin can contribute custom attributes and functions to the ZModel language. To do that, simply include a `plugin.zmodel` file in the same directory as the plugin's entry point, and declare the attributes and functions there. Check [here](https://github.com/zenstackhq/zenstack/blob/main/packages/plugins/openapi/src/plugin.zmodel) for an example. As a convention for conflict avoidance, please qualify the declaration names with your plugin name. E.g.:
+
+```zmodel
+attribute @@myplugin.include()
+```
+
+And you can use it in the ZModel when the plugin is enabled:
+
+```zmodel
+model Foo {
+    ...
+
+    @@myplugin.include()
+}
+```
+
+### Publishing a Plugin
+
+If your plugin is specific to your project, you can implement it inside the project and load it with a relative path in the ZModel. No publishing is needed. We'll see how to do that in the next section.
+
+If you're building something reusable, please publish it as an NPM package and [tweet us](https://twitter.com/intent/tweet?text=I've%20published%20a%20%40zenstackhq%20plugin%3A&url=) or reach us on [Discord](https://discord.gg/Ykhr738dUe). We'll be happy to share your work with the community.
+
+### 🛠️ Building a Markdown Generator Plugin
+
+Let's build a simple plugin that generates a markdown documentation from the ZModel schema. The generated documentation will look like this:
+
+```md
+# My Awesome Todo App
+
+A multi-tenant Todo app built with ZenStack.
+
+- [Space](#space)
+- [User](#user)
+- ...
+
+## Space
+
+- CREATE
+  - ❌ auth() == null
+  - ✅ true
+- READ
+  - ❌ auth() == null
+  - ✅ members?[user == auth()]
+- UPDATE
+  - ❌ auth() == null
+  - ✅ members?[user == auth() && role == ADMIN]
+- DELETE
+  - ❌ auth() == null
+  - ✅ members?[user == auth() && role == ADMIN]
+
+## User
+
+...
+
+```
+
+#### 1. Creating the plugin
+
+In our sample project, create a `markdown-plugin.ts` file at the root:
+
+```ts title='markdown-plugin.ts'
+import { type PluginOptions } from '@zenstackhq/sdk';
+import {
+    isDataModel,
+    type DataModel,
+    type Model,
+} from '@zenstackhq/sdk/ast';
+import fs from 'fs';
+
+export const name = 'Markdown';
+
+export default async function run(model: Model, options: PluginOptions) {
+    // process options
+    const title = options.title ?? 'My Application Model';
+    const description = options.description ?? 'Description of my application';
+
+    // get all data models
+    const dataModels = model.declarations.filter((x): x is DataModel =>
+        isDataModel(x)
+    );
+
+    // TOC
+    const toc = dataModels.map((x) => `- [${x.name}](#${x.name})`).join('\n');
+
+    // data model docs
+    const dataModelDocs = generateDataModelDocs(dataModels);
+
+    fs.writeFileSync(
+        'schema.md',
+        `# ${title}
+    
+${description}
+    
+${toc}
+    
+${dataModelDocs}
+    `
+    );
+}
+```
+
+The code sets up the high-level structure of the markdown document. Let's now add the `generateDataModelDocs` function implementation in the same file.
+
+```ts title='markdown-plugin.ts'
+import { ZModelCodeGenerator, getLiteral } from '@zenstackhq/sdk';
+import { type DataModelAttribute } from '@zenstackhq/sdk/ast';
+
+function generateDataModelDocs(dataModels: DataModel[]) {
+    // AST -> ZModel source generator
+    const zmodelCodeGen = new ZModelCodeGenerator();
+
+    // all CRUD operations
+    const CRUD = ['create', 'read', 'update', 'delete'];
+
+    const docs = dataModels.map((dataModel) => {
+        // first, group model attributes by CRUD operations
+        const groupByCrud = dataModel.attributes
+            .filter((attr) =>
+                ['@@allow', '@@deny'].includes(attr.decl.ref?.name || '')
+            )
+            .reduce<Record<string, DataModelAttribute[]>>((group, attr) => {
+                const ops = getLiteral<string>(attr.args[0].value);
+                if (ops) {
+                    const splitOps =
+                        ops == 'all'
+                            ? CRUD
+                            : ops.split(',').map((op) => op.trim());
+
+                    splitOps.forEach((op) => {
+                        group[op] = group[op] || [];
+                        group[op].push(attr);
+                    });
+                }
+
+                return group;
+            }, {});
+
+        // then generate rules for each operation
+        const policies = CRUD.map((op) => {
+            const rules = groupByCrud[op]
+                ? groupByCrud[op]
+                      // output `@@deny` before `@@allow`
+                      .sort((a) => {
+                          return a.decl.ref?.name == '@@deny' ? -1 : 1;
+                      })
+                      .map(
+                          (attr) =>
+                              `  - ${
+                                  attr.decl.ref?.name == '@@deny'
+                                      ? '❌ '
+                                      : '✅ '
+                              }${zmodelCodeGen.generate(attr.args[1].value)}`
+                      )
+                      .join('\n')
+                : [];
+
+            return [`- ${op.toUpperCase()}`, rules].join('\n');
+        }).join('\n');
+
+        return `## ${dataModel.name}\n\n${policies}`;
+    });
+
+    return docs.join('\n\n');
+}
+```
+
+Finally, compile the plugin code:
+
+```bash
+npx tsc
+```
+
+The JavaScript output should have been generated into the `dist` folder.
+
+#### 2. Testing the plugin
+
+We can now add the plugin to our ZModel:
+
+```zmodel
+plugin markdown {
+    provider = './dist/markdown-plugin'
+    title = 'My Awesome Todo App'
+    description = 'A multi-tenant Todo application built with ZenStack.'
+}
+```
+
+:::info Plugin module resolution
+
+When you use a relative path (starting with ".") in the "provider" field, ZenStack resolves the path relative to the ZModel file and loads the plugin module from it with `require()`. If the plugin is a published NPM package, use the package name directly in the "provider" field.
+
+:::
+
+Now run the CLI to regenerate:
+
+```bash
+npx zenstack generate
+```
+
+You should see the following output and the `schema.md` file generated in the current directory:
+
+```
+⌛️ ZenStack CLI v1.3.1, running plugins
+✔ Running plugin Prisma
+✔ Running plugin Model Metadata
+✔ Running plugin Access Policy
+✔ Running plugin Zod
+✔ Running plugin Markdown
+
+👻 All plugins completed successfully!
+Don't forget to restart your dev server to let the changes take effect.
+```
+
+#### 3. Full source code
+
+The sample plugin we implemented here is a simplified version of the [zenstack-markdown](https://github.com/jiashengguo/zenstack-markdown) plugin created by [@jiashengguo](https://github.com/jiashengguo). Besides outputting access policies, it also generates mini entity-relation diagrams in [mermaid](https://mermaid-js.github.io) format.
+
+```mermaid
+erDiagram
+"Space" {
+  String id PK 
+  DateTime createdAt  
+  DateTime updatedAt  
+  String name  
+  String slug  
+}
+"Space" ||--o{ "SpaceUser": members
+"Space" ||--o{ "List": lists
+```
diff --git a/versioned_docs/version-1.x/the-complete-guide/part2/4-conclusion.md b/versioned_docs/version-1.x/the-complete-guide/part2/4-conclusion.md
new file mode 100644
index 00000000..2d4037fe
--- /dev/null
+++ b/versioned_docs/version-1.x/the-complete-guide/part2/4-conclusion.md
@@ -0,0 +1,17 @@
+---
+sidebar_label: 4. Conclusion
+---
+
+# Conclusion
+
+In this part of the guide, we've focused on the plugin system of ZenStack. We've learned how to use plugins and how to write one.
+
+ZModel contains a formal representation of how data is organized and its constraints. Besides serving as database schema and access control, it's an excellent source for powering other aspects of your app. Don't hesitate to write your own plugins when you feel the need.
+
+In [Part III](../part3/) and [Part IV](../part4/) of the guide, you'll see in action how we use plugins to turn our Todo project from a schema to a full-stack application without writing much code.
+
+The completed sample project up to the end of this part can be found in the "part2" branch of the [the-complete-guide-sample](https://github.com/zenstackhq/the-complete-guide-sample/tree/v1-part2) repo. You can clone it and check out to that branch with the following command:
+
+```bash
+git clone --branch v1-part2 https://github.com/zenstackhq/the-complete-guide-sample.git my-todo-app
+```
diff --git a/versioned_docs/version-1.x/the-complete-guide/part2/index.md b/versioned_docs/version-1.x/the-complete-guide/part2/index.md
new file mode 100644
index 00000000..a99858c4
--- /dev/null
+++ b/versioned_docs/version-1.x/the-complete-guide/part2/index.md
@@ -0,0 +1,54 @@
+---
+sidebar_label: "Part II: Plugin System"
+sidebar_position: 2
+---
+
+# Part II: Plugin System
+
+Welcome to the second part of *ZenStack: The Complete Guide*!
+
+Like many wonderful tools in the web development world, ZenStack adopts a plugin-based architecture. At the core of the system is the ZModel schema, around which features are implemented as plugins. It's perfectly fine to use ZenStack just as enhanced Prisma ORM, as we explained in Part I. However, plugins enable many other use cases and allow you to extend the system as needed.
+
+A plugin can include the following parts:
+
+- A function that takes the ZModel AST as input and executes custom logic
+
+    The function usually transforms the AST to another format. The [@core/prisma](../../reference/plugins/prisma) built-in plugin is a good example. It strips out all ZenStack-specific syntax from the ZModel schema and generates a standard Prisma schema file that can be used with Prisma's toolchain.
+
+- Custom attributes and functions to extend the ZModel language
+
+    For example, the [@zenstackhq/openapi](../../reference/plugins/openapi) plugin provides attributes for controlling how an OpenAPI spec is generated from the ZModel schema. The attributes are commonly interpreted by the generator function.
+
+- Runtime modules that are shipped with the app during deployment
+
+    The [@zenstackhq/tanstack-query](../../reference/plugins/tanstack-query) plugin is a good example. Besides generating frontend data query hooks from the schema, it also provides a runtime part that facilitates the execution of the hooks.
+
+:::info ZenStack Plugin vs Prisma Generator
+
+If you're experienced with Prisma, you've probably used some generators, like [prisma-erd-generator](https://github.com/keonik/prisma-erd-generator) and [typegraphql-prisma](https://github.com/MichalLytek/typegraphql-prisma). ZenStack's plugins share similarities with Prisma generators but also differ in several ways:
+
+- Ease of programming
+
+    Prisma's generator API uses the internal [DMMF format](https://github.com/prisma/prisma/blob/main/ARCHITECTURE.md#the-dmmf-or-data-model-meta-format) that's undocumented and not very friendly to work with. ZenStack's plugin API uses the ZModel AST, which is much more intuitive. The ZenStack plugin still receives a DMMF object as input in case you need it.
+
+- Extending the schema language
+
+    Prisma's schema language is not open to extension, and the community has been using the [triple slash hack](../../guides/existing-prisma#prisma-generators-triple-slash-hack) to inject custom attributes. ZenStack allows plugins to introduce custom attributes and functions directly and type-check their usage at generation time.
+
+Plugins and generators are not mutually exclusive. ZenStack fully supports Prisma generators, and you can use them alongside plugins.
+
+:::
+
+In this part of the guide, we'll explore the following topics:
+
+1. How to use plugins
+2. An overview of the built-in plugins
+3. How to write your own plugins
+
+As with Part I, we'll continue evolving our Todo project and see how to create a helpful custom plugin. If you're unfamiliar with the project, please first revisit the [Sample Project](..#sample-project) part first. You can use the "part1" branch of the [the-complete-guide-sample](https://github.com/zenstackhq/the-complete-guide-sample/tree/part1) repo as a starting point, and clone it and check out to that branch with the following command:
+
+```bash
+git clone --branch v1-part1 https://github.com/zenstackhq/the-complete-guide-sample.git my-todo-app
+```
+
+Let's roll on.
diff --git a/versioned_docs/version-1.x/the-complete-guide/part3/1-api-flavor/1.1-rpc.md b/versioned_docs/version-1.x/the-complete-guide/part3/1-api-flavor/1.1-rpc.md
new file mode 100644
index 00000000..7bfb6147
--- /dev/null
+++ b/versioned_docs/version-1.x/the-complete-guide/part3/1-api-flavor/1.1-rpc.md
@@ -0,0 +1,49 @@
+---
+sidebar_label: 1.1 RPC
+---
+
+# RPC Flavor API
+
+The RPC flavor of API is designed to fully preserve Prisma's query API for both its input and output format. The name "RPC" indicates it's literally like remote procedure calls into a Prisma Client. This API flavor is a good choice if you want to expose Prisma's full power and familiar query syntax to your frontend and care less about API taxonomy.
+
+Here are a few examples of how to use the RPC flavor of API:
+
+```ts
+// find all posts
+GET /api/model/post/findMany
+=> [{ id: 1, title: 'Hello World', ... }, ...}]
+
+// find all published posts, the "q" parameter is `{"where":{"published":true}}` url-encoded
+GET /api/model/post/findMany?q=%7B%22where%22%3A%7B%22published%22%3Atrue%7D%7D
+=> [{ id: 1, title: 'Hello World', ... }, ...}]
+
+// find all posts with their authors, the "q" parameter is `{"include":{"author":true}}` url-encoded
+GET /api/model/post/findMany?q=%7B%22include%22%3A%7B%22author%22%3Atrue%7D%7D
+=> [{ id: 1, title: 'Hello World', author: { id: 1, name: 'Joey', ... } }, ...}]
+
+// create a post for user#1
+POST /api/model/post/create
+{
+    "data": {
+        "title": "Hello World",
+        "author": { "connect": { "id": 1 } }
+    }
+}
+
+// update a post
+POST /api/model/post/update
+{
+    "where": { "id": 1 },
+    "data": { "title": "Hello New World!" }
+}
+
+// delete a post
+DELETE /api/model/post/delete
+{
+    "where": { "id": 1 }
+}
+```
+
+You can find the complete API reference [here](../../../reference/server-adapters/api-handlers/rpc).
+
+In [Part IV](../../part4/) of this guide, you'll see how to use the auto-generated frontend data query hooks to consume the API, so you don't need to construct the HTTP calls manually.
diff --git a/versioned_docs/version-1.x/the-complete-guide/part3/1-api-flavor/1.2-rest.md b/versioned_docs/version-1.x/the-complete-guide/part3/1-api-flavor/1.2-rest.md
new file mode 100644
index 00000000..1b05bbda
--- /dev/null
+++ b/versioned_docs/version-1.x/the-complete-guide/part3/1-api-flavor/1.2-rest.md
@@ -0,0 +1,89 @@
+---
+sidebar_label: 1.2 RESTful
+---
+
+# RESTful Flavor API
+
+The RESTful flavor of API is designed to rigorously follow the RESTful conventions - resources, verbs, and links for further navigation based on the result dataset. It's a good choice if you want to expose a formal RESTful API to your client. The API adopts [JSON:API](https://jsonapi.org/format/) as data format.
+
+Here are a few examples of how to use the REST flavor of API:
+
+```ts
+// find all posts
+GET /api/model/post
+=> 
+{
+    "jsonapi": { "version": "1.0" },
+    "data": [
+        { "type": "post", "id": "1", "attributes": { "title": "Hello World", ... }, ... },
+        ...
+    ],
+    ...
+}
+
+// find all published posts
+GET /api/model/post?filter[published]=true
+=>
+{
+    "jsonapi": { "version": "1.0" },
+    "data": [
+        { "type": "post", "id": "1", "attributes": { "title": "Hello World", ... }, ... },
+        ...
+    ],
+    ...
+}
+
+// find all posts with their authors
+GET /api/model/post?include=author
+=>
+{
+    "jsonapi": { "version": "1.0" },
+    "data": [
+        { 
+            "type": "post", 
+            "id": "1", 
+            "attributes": { "title": "Hello World", ... },
+            "relationships": {
+                "author": { "data": { "type": "user", "id": "1" } }
+            }
+        },
+        ...
+    ],
+    "included": [
+        { "type": "user", "id": "1", "attributes": { "name": "Joey", ... } },
+        ...
+    ],
+    ...
+}
+
+// create a post for user#1
+POST /api/model/post
+{
+    "data": {
+        "type": "post",
+        "attributes": {
+            "title": "Hello World"
+        },
+        "relationships": {
+            "author": { "data": { "type": "user", "id": "1" } }
+        }
+    }
+}
+
+// update a post
+PATCH /api/model/post/1
+{
+    "data": {
+        "type": "post",
+        "id": "1",
+        "attributes": {
+            "title": "Hello New World!"
+        }
+    }
+}
+
+// delete a post
+DELETE /api/model/post/1
+```
+
+You can find the complete API reference [here](../../../reference/server-adapters/api-handlers/rest).
diff --git a/versioned_docs/version-1.x/the-complete-guide/part3/1-api-flavor/1.3-api-handler.md b/versioned_docs/version-1.x/the-complete-guide/part3/1-api-flavor/1.3-api-handler.md
new file mode 100644
index 00000000..9e7d1b5c
--- /dev/null
+++ b/versioned_docs/version-1.x/the-complete-guide/part3/1-api-flavor/1.3-api-handler.md
@@ -0,0 +1,22 @@
+---
+sidebar_label: 1.3 API Handler
+---
+
+import useBaseUrl from '@docusaurus/useBaseUrl';
+import ThemedImage from '@theme/ThemedImage';
+
+# API Handlers
+
+Although many different backend and full-stack frameworks exist in the Javascript world, how APIs are designed is entirely independent. We've implemented the API flavors as framework-agnostic API handlers. They take canonical input (HTTP verb, URL, body, etc.) and generate canonical output (status code and body).
+
+The server adapters, as we will see in the next chapter, are responsible for dealing with the framework details and handling the transformation between the framework-specific request/response and the canonical format. If, in the future, we want to introduce a new API flavor, we don't need to change the server adapters.
+
+The following diagram depicts the relationship between the API handlers and the server adapters:
+
+<ThemedImage
+    alt="ZModel Generation"
+    sources={{
+        light: useBaseUrl('/img/intro/api-handler-light.png'),
+        dark: useBaseUrl('/img/intro/api-handler-dark.png'),
+    }}
+/>
diff --git a/versioned_docs/version-1.x/the-complete-guide/part3/1-api-flavor/index.md b/versioned_docs/version-1.x/the-complete-guide/part3/1-api-flavor/index.md
new file mode 100644
index 00000000..06c18951
--- /dev/null
+++ b/versioned_docs/version-1.x/the-complete-guide/part3/1-api-flavor/index.md
@@ -0,0 +1,17 @@
+---
+sidebar_label: 1. API Flavors
+---
+
+# API Flavors
+
+API design is a complex topic. While thinking about wrapping CRUD APIs around ORM, we feel there are two main competing goals:
+
+- Query flexibility
+
+    Prisma's API is very flexible, especially regarding nested reads and writes. It'd be nice to preserve this flexibility fully in the derived APIs. This way, you can also carry your knowledge of using Prisma over to using the APIs.
+
+- RESTfulness
+  
+    When designing CRUD APIs, making it RESTful is a natural choice. Resource-oriented URLs and semantic HTTP verbs match the problem well. While RESTful APIs traditionally suffer from the [N + 1 Problem](https://restfulapi.net/rest-api-n-1-problem/) (as traditionally ORM did, too), there are some conventions we can use to mitigate it.
+
+It is tough to choose one over the other. So, we decided to provide both API flavors. You can choose the one that fits your use case better.
diff --git a/versioned_docs/version-1.x/the-complete-guide/part3/2-server-adapters.md b/versioned_docs/version-1.x/the-complete-guide/part3/2-server-adapters.md
new file mode 100644
index 00000000..a226f875
--- /dev/null
+++ b/versioned_docs/version-1.x/the-complete-guide/part3/2-server-adapters.md
@@ -0,0 +1,288 @@
+---
+sidebar_label: 2. Server Adapters
+---
+
+# Server Adapters
+
+### Overview
+
+ZenStack is a toolkit but not a framework. It doesn't come with its own web server. Instead, it provides a set of server adapters that "mount" APIs to the server of your choice.
+
+Server adapters are framework-specific. ZenStack ships a `@zenstackhq/server` package that contains adapters for the most popular backend and full-stack frameworks:
+
+- [Express](../../reference/server-adapters/express)
+- [Fastify](../../reference/server-adapters/fastify)
+- [Next.js](../../reference/server-adapters/next)
+- [Nuxt](../../reference/server-adapters/nuxt)
+- [SvelteKit](../../reference/server-adapters/sveltekit)
+  
+Check out their respective documentation to learn more details.
+
+As mentioned in the previous chapter, server adapters handle framework-specific request/response formats and transform them to the canonical form that the underlying API handlers understand. All server adapters share the following two initialization options:
+
+- `handler`
+
+    The API handler to use. As mentioned in the previous chapter, ZenStack provides two built-in API handler implementations. If you don't specify this option, the default API handler is RPC.
+    
+    - RPC handler: `@zenstackhq/server/api/rpc`
+    - RESTful handler: `@zenstackhq/server/api/rest`
+
+- `getPrisma`
+
+    A callback function for getting a PrismaClient instance used for handling a CRUD request. The function is passed with a framework-specific request object. Although you can return a vanilla Prisma Client, you most likely should use an enhanced one to enforce access policies.
+
+### 🛠️ Serving CRUD API
+
+Let's see how we can automagically turn our ZModel schema into a web API without really coding it 🚀! We'll use Express for simplicity for now, but working with another framework is essentially the same.
+
+#### 1. Installing dependencies
+
+First, install Express and the ZenStack server package:
+
+```bash
+npm install express @zenstackhq/server
+npm install --save-dev @types/express tsx
+```
+
+#### 2. Creating an Express app
+
+Create a file `main.ts` with the following content:
+
+```ts title="main.ts"
+import express from 'express';
+
+const app = express();
+
+app.use(express.json());
+
+app.get('/', (req, res) => {
+    res.json({ greet: 'Hello World!' });
+});
+
+app.listen(3000, () => console.log('🚀 Server ready at: http://localhost:3000'));
+```
+
+Start the server:
+
+```bash
+npx tsx --watch main.ts
+```
+
+Make a request in another terminal to verify everything is working:
+
+```bash
+curl http://localhost:3000
+```
+
+> &#123; "greet": "Hello World!" &#125;
+
+#### 3. Adding ZenStack server adapter (RPC flavor)
+
+Let's create an express middleware and mount the CRUD API to the `/api/rpc` path. Replace `main.ts` with the following content:
+
+```ts title="main.ts"
+import { PrismaClient } from '@prisma/client';
+import { ZenStackMiddleware } from '@zenstackhq/server/express';
+import express from 'express';
+
+const app = express();
+app.use(express.json());
+
+const prisma = new PrismaClient();
+app.use('/api/rpc', ZenStackMiddleware({ getPrisma: () => prisma }));
+
+app.listen(3000, () => console.log('🚀 Server ready at: http://localhost:3000'));
+```
+
+:::info
+
+The `ZenStackMiddleware` server adapter uses RPC-flavor API by default.
+
+:::
+
+We've configured the server adapter to use a vanilla Prisma Client for now for quick testing. By default, the server adapter uses RPC-style API. We can hit the endpoint to do a few tests now:
+
+- Find a `List`
+
+    ```bash
+    curl "http://localhost:3000/api/rpc/list/findFirst"
+    ```
+
+    ```json
+    {
+        "data" : {
+            "createdAt" : "2023-11-08T04:38:53.385Z",
+            "id" : 1,
+            "ownerId" : 1,
+            "private" : false,
+            "spaceId" : 1,
+            "title" : "Grocery",
+            "updatedAt" : "2023-11-09T04:52:57.987Z"
+        },
+        "meta" : { ... }
+    }
+    ```
+
+- Find a private `List`
+
+    Note the parameter `q` is url-encoded `{"where":{"private":true}}`.
+
+    ```bash
+    curl "http://localhost:3000/api/rpc/list/findFirst?q=%7B%22where%22%3A%7B%22private%22%3Atrue%7D%7D"
+    ```
+
+    ```json
+    {
+        "data" : {
+            "createdAt" : "2023-11-11T02:34:29.880Z",
+            "id" : 5,
+            "ownerId" : 1,
+            "private" : true,
+            "spaceId" : 1,
+            "title" : "Joey' List",
+            "updatedAt" : "2023-11-11T02:34:29.880Z"
+        },
+        "meta" : { ...}
+    }
+    ```
+
+- Create a `List`
+
+    ```bash
+    curl -XPOST "http://localhost:3000/api/rpc/list/create" \
+        -d '{"data":{"title":"Jobs to be done","owner":{"connect":{"id":2}},"space":{"connect":{"id":1}}}}' \
+        -H 'Content-Type: application/json'
+    ```
+
+    ```json
+    {
+        "data": {
+            "id": 6,
+            "createdAt": "2023-11-11T02:40:58.765Z",
+            "updatedAt": "2023-11-11T02:40:58.765Z",
+            "spaceId": 1,
+            "ownerId": 2,
+            "title": "Jobs to be done",
+            "private": false
+        },
+        "meta": { ... }
+    }
+    ```
+
+#### 4. Making access policies work
+
+To make access policies work, we need to create an enhanced Prisma Client, and to do that, we need to be able to get the current user from the request. Since we haven't implemented authentication yet, we'll use a special `x-user-id` header to simulate and pass the requesting user's ID. It's definitely not a secure implementation, but it's sufficient for demonstration. We'll hook up a real authentication system in [Part IV](../part4/).
+
+Replace the content of `main.ts` with the following code:
+
+```ts title="main.ts"
+import { PrismaClient } from '@prisma/client';
+import { enhance } from '@zenstackhq/runtime';
+import { ZenStackMiddleware } from '@zenstackhq/server/express';
+import express, { Request } from 'express';
+
+const app = express();
+app.use(express.json());
+
+const prisma = new PrismaClient();
+
+function getUser(req: Request) {
+    if (req.headers['x-user-id']) {
+        return { id: parseInt(req.headers['x-user-id'] as string) };
+    } else {
+        return undefined;
+    }
+}
+
+app.use('/api/rpc', 
+    ZenStackMiddleware({ 
+        getPrisma: (req) => enhance(prisma, { user: getUser(req) })
+    })
+);
+
+app.listen(3000, () => console.log('🚀 Server ready at: http://localhost:3000'));
+```
+
+Now, if we hit the endpoint again without the `x-user-id` header, we'll get a null response:
+
+```bash
+curl "http://localhost:3000/api/rpc/list/findFirst"
+```
+
+```json
+{ "data" : null }
+```
+
+Add the header and request again. We should get back a result then:
+
+```bash
+curl "http://localhost:3000/api/rpc/list/findFirst" -H "x-user-id: 1"
+```
+
+```json
+{
+  "data": {
+    "id": 1,
+    "createdAt": "2023-11-08T04:38:53.385Z",
+    "updatedAt": "2023-11-09T04:52:57.987Z",
+    "spaceId": 1,
+    "ownerId": 1,
+    "title": "Grocery",
+    "private": false
+  },
+  "meta": { ... }
+}
+```
+
+You can try other operations with different user identities. The service's behavior should be consistent with what we've seen in the REPL with the enhanced Prisma in [Part I](../part1/access-policy/current-user#add-user-based-access-control).
+
+#### 5. Trying Out The RESTful API Flavor
+
+Let's mount a RESTful-flavor API under another path `/api/rest`. Add the following code to `main.ts` before the line of `app.listen(...)`:
+
+```ts
+import RESTHandler from '@zenstackhq/server/api/rest';
+
+app.use('/api/rest', 
+    ZenStackMiddleware({ 
+        handler: RESTHandler({ endpoint: 'http://localhost:3000/api/rest' }),
+        getPrisma: (req) => enhance(prisma, { user: getUser(req) })
+    })
+);
+```
+
+As you've seen above, we're using the same server adapter implementation and swapped the API handler. Now we can fetch the first `List` item by making a RESTful-style request.
+
+:::info
+The "-g" parameter passed to curl is for allowing square brackets in the URL.
+:::
+
+```bash
+curl -g 'http://localhost:3000/api/rest/list?page[limit]=1' -H "x-user-id: 1"
+```
+
+```json
+{
+   "data" : [
+      {
+         "attributes" : {
+            "createdAt" : "2023-11-08T04:38:53.385Z",
+            "ownerId" : 1,
+            "private" : false,
+            "spaceId" : 1,
+            "title" : "Grocery",
+            "updatedAt" : "2023-11-09T04:52:57.987Z"
+         },
+         "id" : 1,
+         "links" : { ... },
+         "relationships" : { ... },
+         "type" : "list"
+      }
+   ],
+   "jsonapi" : {
+      "version" : "1.1"
+   },
+   "links" : { ... },
+   "meta" : { ... }
+}
+```
\ No newline at end of file
diff --git a/versioned_docs/version-1.x/the-complete-guide/part3/3-serialization.md b/versioned_docs/version-1.x/the-complete-guide/part3/3-serialization.md
new file mode 100644
index 00000000..cb8bb811
--- /dev/null
+++ b/versioned_docs/version-1.x/the-complete-guide/part3/3-serialization.md
@@ -0,0 +1,105 @@
+---
+sidebar_label: 3. Data Serialization
+---
+
+# Data Serialization
+
+One problem with passing Prisma requests and responses across the network is that the data is not always serializable. The following Prisma types are problematic:
+
+- `Date`: Javascript's `Date` object is serializable, but its type is lost
+- `Bytes`: not serializable
+- `BigInt`: not serializable
+- `Decimal`: not serializable
+
+ZenStack solves this problem by using [superjson](https://github.com/blitz-js/superjson) to deserialize input and serialize output. Since data is still JSON-serializable most of the time, ZenStack adopts superjson in a non-intrusive way. The serialized data is directly put inside the request or response data. When there is extra serialization information (superjson generates it when it encounters data that's not JSON-serializable), it's put inside the `meta` field in the request or response.
+
+The benefit of this design is that if the data is fully JSON-serializable (not involving the types mentioned above), the wire format is just plain JSON serialization without any pollution.
+
+You can find more details about serialization in the [RPC API Handler](../../reference/server-adapters/api-handlers/rpc#serialization) and [RESTful API Handler](../../reference/server-adapters/api-handlers/rest#serialization) documentation.
+
+### 🛠️ Observing Serialization Behavior
+
+To illustrate this, let's try a few requests to observe the serialization behavior.
+
+Make the following request to find the first `List` with `{"select":{"id":true,"title":true}}`:
+
+```bash
+curl "http://localhost:3000/api/rpc/list/findFirst?q=%7B%22select%22%3A%7B%22id%22%3Atrue%2C%22title%22%3Atrue%7D%7D" -H "x-user-id: 1"
+```
+
+You should get a simple response. Since the result data is fully JSON-serializable, the response has no extra serialization metadata.
+
+```json
+{
+    "data": {
+        "id":1,
+        "title":"Grocery"
+    }
+}
+```
+
+Now, if we send a different request and ask for the full `List` object
+
+```bash
+curl "http://localhost:3000/api/rpc/list/findFirst" -H "x-user-id: 1"
+```
+
+We get back a richer response. The `createdAt` and `updatedAt` fields are not directly JSON-serializable, and the information contained in the `meta.serialization` field is generated by superjson to facilitate deserialization.
+
+```json
+{
+   "data" : {
+      "createdAt" : "2023-11-08T04:38:53.385Z",
+      "id" : 1,
+      "ownerId" : 1,
+      "private" : false,
+      "spaceId" : 1,
+      "title" : "Grocery",
+      "updatedAt" : "2023-11-09T04:52:57.987Z"
+   },
+   "meta" : {
+      "serialization" : {
+         "values" : {
+            "createdAt" : [ "Date" ],
+            "updatedAt" : [ "Date" ]
+         }
+      }
+   }
+}
+```
+
+The API consumer should pass the data and the serialization metadata to the superjson API to get back data with full fidelity. The following code shows how to deserialize and restore the proper `Date` type for the `createdAt` and `updatedAt` fields.
+
+```ts
+import SuperJSON from 'superjson';
+
+SuperJSON.deserialize({
+    json: {
+        "createdAt" : "2023-11-08T04:38:53.385Z",
+        "id" : 1,
+        "ownerId" : 1,
+        "private" : false,
+        "spaceId" : 1,
+        "title" : "Grocery",
+        "updatedAt" : "2023-11-09T04:52:57.987Z"
+   },
+   meta: {
+        "values" : {
+            "createdAt" : [ "Date" ],
+            "updatedAt" : [ "Date" ]
+        }
+    }
+});
+```
+
+```js
+{
+    createdAt: 2023-11-08T04:38:53.385Z,
+    id: 1,
+    ownerId: 1,
+    private: false,
+    spaceId: 1,
+    title: 'Grocery',
+    updatedAt: 2023-11-09T04:52:57.987Z
+}
+```
\ No newline at end of file
diff --git a/versioned_docs/version-1.x/the-complete-guide/part3/4-customization.md b/versioned_docs/version-1.x/the-complete-guide/part3/4-customization.md
new file mode 100644
index 00000000..f612c551
--- /dev/null
+++ b/versioned_docs/version-1.x/the-complete-guide/part3/4-customization.md
@@ -0,0 +1,68 @@
+---
+sidebar_label: 4. Customization
+---
+
+# Customizing The Generated API
+
+Having APIs derived from the schema is great, but sometimes, you want to customize them to fit your needs. Some of the common use cases are:
+
+- Disallowing specific operations
+- Transforming the result data into a different format
+- Adding custom headers to responses
+
+In this chapter, let's explore a few options to do that.
+
+### Framework Middleware
+
+Most server frameworks provide some kind of "middleware" mechanism for developers to intercept requests and responses. For example, you can use a separate Express middleware to add custom a custom header to the response before it's sent to the client:
+
+```ts
+app.use('/api/rpc', 
+    ZenStackMiddleware({
+        getPrisma: (req) => enhance(prisma, { user: getUser(req) }),
+        // instructs the middleware to not send the response,
+        // instead, store it in `res.locals`
+        sendResponse: false,
+    }),
+    (req: Request, res: Response) => {
+        // another middleware to intercept the CRUD response,
+        // add a header and send the response
+        const { status, body } = res.locals;
+        res.header('x-requested-by', req.headers['x-user-id']);
+        res.status(status).json(body);
+    }
+);
+```
+
+### Custom Server Adapter
+
+Since server adapters are just lightweight wrappers around API handlers, you can also implement a custom adapter to interact with the API handler directly. This way, you have complete control over how the API handler's result data is processed and sent back to the client as a framework response.
+
+The following code demonstrates how to add the 'x-requested-by' header with this approach:
+
+```ts
+import RESTHandler from '@zenstackhq/server/api/rest';
+
+app.use('/api/rpc-custom', async (req: Request, res: Response) => {
+    const url = req.protocol + '://' + req.get('host') + req.originalUrl;
+    const searchParams = new URL(url).searchParams;
+    const query = Object.fromEntries(searchParams);
+    const handler = RPCHandler();
+
+    const { status, body } = await handler({
+        method: req.method,
+        path: req.path,
+        query,
+        requestBody: req.body,
+        prisma: enhance(prisma, { user: getUser(req) }) as any,
+    });
+
+    res.status(status)
+        .header('x-requested-by', req.header('x-user-id'))
+        .json(body);
+});
+```
+
+### Custom API
+
+Using the automatic CRUD API doesn't mean you can't implement your own APIs. You can always build fully customized APIs from scratch and leverage the enhanced Prisma Client.
diff --git a/versioned_docs/version-1.x/the-complete-guide/part3/5-documentation.md b/versioned_docs/version-1.x/the-complete-guide/part3/5-documentation.md
new file mode 100644
index 00000000..f76bad2e
--- /dev/null
+++ b/versioned_docs/version-1.x/the-complete-guide/part3/5-documentation.md
@@ -0,0 +1,60 @@
+---
+sidebar_label: 5. API Documentation
+---
+
+# Generating API Documentation
+
+An API is only complete when documented. Similar to the API itself, the documentation can also be derived from the ZModel.
+
+In [Part II](../part2/) of the guide, we've learned how to use plugins. ZenStack ships the [@zenstackhq/openapi](../../reference/plugins/openapi) plugin to generate [OpenAPI](https://www.openapis.org/) specs from the ZModel schema. Like the API handlers, the openapi plugin also supports two flavors: "rpc" and "rest".
+
+Once the documentation is generated, it's easy to render it using tools like [Swagger UI](https://swagger.io/tools/swagger-ui/) and [Redocly](https://redocly.com/).
+
+### 🛠️ Serving API Documentation
+
+Let's generate an OpenAPI spec for our Todo API and serve it using Swagger UI.
+
+#### 1. Installing Dependencies
+
+```bash
+npm install --save-dev @zenstackhq/openapi
+npm install swagger-ui-express
+npm install -D @types/swagger-ui-express
+```
+
+#### 2. Adding OpenAPI Plugin to ZModel
+
+```zmodel title="schema.zmodel"
+plugin openapi {
+    provider = "@zenstackhq/openapi"
+    output = "todo-api.json"
+    title = "My Todo API"
+    version = "1.0.0"
+    flavor = "rpc"
+}
+```
+
+Rerun generation:
+
+```bash
+npx zenstack generate
+```
+
+The `todo-api.json` file should be generated in the project root.
+
+#### 3. Serving the OpenAPI Spec
+
+Add the following code to `main.ts` before the line of `app.listen(...)`:
+
+```ts title="main.ts"
+import swaggerUI from 'swagger-ui-express';
+app.use(
+    '/api/docs',
+    swaggerUI.serve,
+    swaggerUI.setup(require('./todo-api.json'))
+);
+```
+
+Now, you can visit the documentation at [http://localhost:3000/api/docs](http://localhost:3000/api/docs) in a browser.
+
+![Swagger UI](../../assets/todo-app-openapi.png)
diff --git a/versioned_docs/version-1.x/the-complete-guide/part3/6-conclusion.md b/versioned_docs/version-1.x/the-complete-guide/part3/6-conclusion.md
new file mode 100644
index 00000000..472b6024
--- /dev/null
+++ b/versioned_docs/version-1.x/the-complete-guide/part3/6-conclusion.md
@@ -0,0 +1,15 @@
+---
+sidebar_label: 6. Conclusion
+---
+
+# Conclusion
+
+In this part of the guide, we've focused on ZenStack's powers for deriving CRUD APIs automatically from the ZModel schema. We had an overview of the different API flavors and learned how to mount the APIs onto a backend framework.
+
+In the next part, we'll see how effortless it is to consume the APIs from a frontend application and eventually turn our Todo project into a full-stack application.
+
+The completed sample project up to the end of this part can be found in the "part3" branch of the [the-complete-guide-sample](https://github.com/zenstackhq/the-complete-guide-sample/tree/v1-part3) repo. You can clone it and check out to that branch with the following command:
+
+```bash
+git clone --branch v1-part3 https://github.com/zenstackhq/the-complete-guide-sample.git my-todo-app
+```
diff --git a/versioned_docs/version-1.x/the-complete-guide/part3/index.md b/versioned_docs/version-1.x/the-complete-guide/part3/index.md
new file mode 100644
index 00000000..478d8b6c
--- /dev/null
+++ b/versioned_docs/version-1.x/the-complete-guide/part3/index.md
@@ -0,0 +1,20 @@
+---
+sidebar_label: "Part III: Automatic CRUD API"
+sidebar_position: 3
+---
+
+# Part III: Automatic CRUD API
+
+In Part I, we learned how ZenStack extended Prisma ORM on the schema and runtime levels. By making these extensions, ZenStack unleashed many new potentials of the ORM and turned it into a data access layer that enforces access control and additional data integrity.
+
+One interesting side effect of this extension is that we can automatically derive CRUD APIs from the schema. The data models tell us how the input/output of the APIs should look like, and the enhanced Prisma Client ensures that the APIs are secure.
+
+In this part of the guide, we'll learn how such automatic APIs work - the general architecture, the different flavors of APIs, and how they integrate into the framework of your choice.
+
+We'll also continue evolving our Todo project into a fully functional backend service. If you're unfamiliar with the project, please first revisit the [Sample Project](..#sample-project) part. You can use the "part2" branch of [the-complete-guide-sample](https://github.com/zenstackhq/the-complete-guide-sample/tree/v1-part2) repo as a starting point, and clone it and check out to that branch with the following command:
+
+```bash
+git clone --branch v1-part2 https://github.com/zenstackhq/the-complete-guide-sample.git my-todo-app
+```
+
+Let's get started.
diff --git a/versioned_docs/version-1.x/the-complete-guide/part4/1-prepare-project.md b/versioned_docs/version-1.x/the-complete-guide/part4/1-prepare-project.md
new file mode 100644
index 00000000..cafd114a
--- /dev/null
+++ b/versioned_docs/version-1.x/the-complete-guide/part4/1-prepare-project.md
@@ -0,0 +1,258 @@
+---
+sidebar_label: 1. Creating a Full-Stack Project
+---
+
+# 🛠️ Creating a Full-Stack Project
+
+To simplify the process of building our full-stack Todo app, we'll recreate the project from scratch using the [create-t3-app](https://create.t3.gg/) scaffolding tool - saving us a lot of time manually integrating different tools and libraries. We'll reuse the ZModel schema we built in Part I.
+
+:::info ZenStack is framework-agnostic
+
+For ease of demonstration, we'll use the [Next.js](https://nextjs.org/) framework for full-stack development. However, ZenStack is framework-agnostic. Most of the content apply to other choices, including full-stack ones like like [Nuxt](https://nuxt.com/) and [SvelteKit](https://kit.svelte.dev/), or SPA + backend frameworks like [Express](https://expressjs.com/), [Fastify](https://fastify.dev/), [NestJS](https://nestjs.com/).
+
+:::
+
+### 1. Creating the Project
+
+Create a new Next.js project using `create-t3-app`:
+
+```bash
+npx create-t3-app@latest --tailwind --nextAuth --prisma --appRouter --CI my-todo-app
+```
+
+It sets up a project with the following features:
+
+- [Next.js](https://nextjs.org) with app router
+- TypeScript
+- Prisma for ORM
+- [NextAuth.js](https://next-auth.js.org/) for authentication
+- [Tailwind CSS](https://tailwindcss.com/) for styling
+- SQLite for database
+
+We'll also use "daisyUI" for UI components. Run the following command to install it:
+
+```bash
+npm i -D daisyui@latest
+```
+
+Then add the "daisyui" plugin to `tailwind.config.ts`:
+
+```js
+module.exports = {
+  //...
+  plugins: [require("daisyui")],
+}
+```
+
+Finally, add some utility packages we'll use later:
+
+```bash
+npm install nanoid
+```
+
+### 2. Setting Up ZenStack
+
+Initialize the project for ZenStack:
+
+```bash
+npx zenstack@1 init
+```
+
+Besides installing dependencies, the command also copies the `prisma/schema.prisma` file to `schema.zmodel`. We're going to continue using the ZModel we've developed in the previous part, but with a few tweaks:
+
+1. All "id" fields are changed to String type (as required by NextAuth).
+2. The "markdown" and "openapi" plugins are removed (not needed for this part).
+
+You can also find the updated version here: [https://github.com/zenstackhq/the-complete-guide-sample/blob/v1-part4-start/schema.zmodel](https://github.com/zenstackhq/the-complete-guide-sample/blob/v1-part4-start/schema.zmodel). Replace the `schema.zmodel` file in your project with it.
+
+Run generation and push the schema to the database:
+
+```bash
+npx zenstack generate && npx prisma db push
+```
+
+:::tip
+
+If you ran into any trouble creating the project, you can also use the "part4-start" branch of [https://github.com/zenstackhq/the-complete-guide-sample](https://github.com/zenstackhq/the-complete-guide-sample/tree/v1-part4-start) as the starting point and continue from there.
+
+:::
+
+### 3. Implementing Authentication
+
+#### 3.1 NextAuth Session Provider
+
+To use NextAuth, we'll need to install a session provider at the root of our app. First, create a file `src/components/SessionProvider.tsx` with the following content:
+
+```tsx title="src/components/SessionProvider.tsx"
+'use client';
+
+import { SessionProvider } from 'next-auth/react';
+import React from 'react';
+
+type Props = {
+  children: React.ReactNode;
+};
+function NextAuthSessionProvider({ children }: Props) {
+  return <SessionProvider>{children}</SessionProvider>;
+}
+
+export default NextAuthSessionProvider;
+```
+
+Then, update the `src/app/layout.tsx` file to use it
+
+```tsx title="src/app/layout.tsx"
+import NextAuthSessionProvider from '~/components/SessionProvider';
+
+export default function RootLayout({ children }: { children: React.ReactNode }) {
+  return (
+    <html lang="en">
+      <body className={`font-sans ${inter.variable}`}>
+        <NextAuthSessionProvider>{children}</NextAuthSessionProvider>
+      </body>
+    </html>
+  );
+}
+```
+
+#### 3.2 Credential-Based Auth
+
+The default project created by `create-t3-app` uses Discord OAuth for authentication. We'll replace it with credential-based authentication for simplicity.
+
+Replace the content of `/src/server/auth.ts` with the following:
+
+```ts title="/src/server/auth.ts"
+import { PrismaAdapter } from '@next-auth/prisma-adapter';
+import type { PrismaClient } from '@prisma/client';
+import { compare } from 'bcryptjs';
+import NextAuth, { type DefaultSession, type NextAuthOptions } from 'next-auth';
+import CredentialsProvider from 'next-auth/providers/credentials';
+import { db } from './db';
+
+declare module 'next-auth' {
+  interface Session extends DefaultSession {
+    user: {
+      id: string;
+    } & DefaultSession['user'];
+  }
+}
+
+export const authOptions: NextAuthOptions = {
+  session: {
+    strategy: 'jwt',
+  },
+  // Include user.id on session
+  callbacks: {
+    session({ session, token }) {
+      if (session.user) {
+        session.user.id = token.sub!;
+      }
+      return session;
+    },
+  },
+  // Configure one or more authentication providers
+  adapter: PrismaAdapter(db),
+  providers: [
+    CredentialsProvider({
+      credentials: {
+        email: { type: 'email' },
+        password: { type: 'password' },
+      },
+      authorize: authorize(db),
+    }),
+  ],
+};
+
+function authorize(prisma: PrismaClient) {
+  return async (credentials: Record<'email' | 'password', string> | undefined) => {
+    if (!credentials?.email) throw new Error('"email" is required in credentials');
+    if (!credentials?.password) throw new Error('"password" is required in credentials');
+
+    const maybeUser = await prisma.user.findFirst({
+      where: { email: credentials.email },
+      select: { id: true, email: true, password: true },
+    });
+    if (!maybeUser?.password) return null;
+
+    // verify the input password with stored hash
+    const isValid = await compare(credentials.password, maybeUser.password);
+    if (!isValid) return null;
+
+    return { id: maybeUser.id, email: maybeUser.email };
+  };
+}
+
+export default NextAuth(authOptions);
+```
+
+Remove code related to `DISCORD_CLIENT_ID` and `DISCORD_CLIENT_SECRET` from `/src/env.js`, and update the `.env` file under project root to the following:
+
+```bash title=".env"
+DATABASE_URL="file:./db.sqlite"
+NEXTAUTH_SECRET="abc123"
+NEXTAUTH_URL="http://localhost:3000"
+```
+
+:::info
+You should use a strong `NEXTAUTH_SECRET` in a real application.
+:::
+
+
+### 4. Mounting the CRUD API
+
+ZenStack uses server adapters to mount CRUD APIs to frameworks, and it has several pre-built adapters for popular frameworks - one of which is Next.js. First, install the server adapter package:
+
+```bash
+npm install @zenstackhq/server
+```
+
+Then, create a file `src/app/api/model/[...path]/route.ts` with the following content:
+
+```ts title='src/app/api/model/[...path]/route.ts'
+import { enhance } from '@zenstackhq/runtime';
+import { NextRequestHandler } from '@zenstackhq/server/next';
+import { getServerSession } from 'next-auth';
+import { authOptions } from '~/server/auth';
+import { db } from '~/server/db';
+
+async function getPrisma() {
+  const session = await getServerSession(authOptions);
+  return enhance(db, { user: session?.user });
+}
+
+const handler = NextRequestHandler({ getPrisma, useAppDir: true });
+
+export { handler as DELETE, handler as GET, handler as PATCH, handler as POST, handler as PUT };
+```
+
+:::info
+
+The crucial part is that we use an enhanced PrismaClient with the server adapter, so all API calls are automatically subject to the access policies defined in the ZModel schema.
+
+:::
+
+In the next chapter, we'll learn how to use a plugin to generate frontend data query hooks that help us consume it.
+
+Finally, make a change to the `next.config.js` file to exclude the `@zenstackhq/runtime` package from the server component bundler:
+
+```js title="next.config.js"
+const config = {
+  experimental: {
+    serverComponentsExternalPackages: ['@zenstackhq/runtime']
+  }
+};
+```
+
+:::info Why is this needed?
+
+Next.js's server component bundler automatically bundles dependencies, but it has some restrictions on the set of Node.js features a package can use. The `@zenstackhq/runtime` package makes [unsupported](https://nextjs.org/docs/app/api-reference/next-config-js/serverComponentsExternalPackages) `require()` calls. We'll try to make it compatible in a future release. 
+
+:::
+
+### 5. Compile the Project
+
+Compile the project and see if everything is working correctly:
+
+```bash
+npm run build
+```
diff --git a/versioned_docs/version-1.x/the-complete-guide/part4/2-generate-hooks.md b/versioned_docs/version-1.x/the-complete-guide/part4/2-generate-hooks.md
new file mode 100644
index 00000000..09a6cc29
--- /dev/null
+++ b/versioned_docs/version-1.x/the-complete-guide/part4/2-generate-hooks.md
@@ -0,0 +1,122 @@
+---
+sidebar_label: 2. Generating Hooks
+---
+
+# 🛠️ Generating Hooks
+
+If you use a frontend framework like React or Vue, you've probably used or heard of libraries like [TanStack Query](https://tanstack.com/query/latest) and [SWR](https://swr.vercel.app/). These libraries make it a breeze to build data-driven UI by providing excellent encapsulation for reactive data query, cache management, and data invalidation (aka refetching).
+
+ZenStack provides plugins for generating frontend hooks targeting these two libraries. In this guide, we'll focus on using TanStack Query, but the concepts and usage are similar for SWR.
+
+### Installing Dependencies
+
+```bash
+npm install @tanstack/react-query @zenstackhq/tanstack-query
+```
+
+The `@zenstackhq/tanstack-query` package contains both the plugin and a runtime part, so it needs to be installed as a regular dependency.
+
+### Setting Up TanStack Query
+
+To use TanStack Query, we need to set up a `QueryClientProvider` context provider in the root component of our app. First, create a file `src/components/QueryClientProvider.tsx` with the following content:
+
+```tsx title="src/components/QueryClientProvider.tsx"
+'use client';
+
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
+
+type Props = {
+  children: React.ReactNode;
+};
+
+const queryClient = new QueryClient();
+
+function Provider({ children }: Props) {
+  return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>;
+}
+
+export default Provider;
+```
+
+Then, update the `src/app/layout.tsx` file to use it:
+
+```tsx title="src/app/layout.tsx"
+import QueryClientProvider from '~/components/QueryClientProvider';
+
+export default function RootLayout({ children }: { children: React.ReactNode }) {
+  return (
+    <html lang="en">
+      <body className={`font-sans ${inter.variable}`}>
+        <QueryClientProvider>
+          <NextAuthSessionProvider>{children}</NextAuthSessionProvider>
+        </QueryClientProvider>
+      </body>
+    </html>
+  );
+}
+```
+
+### Adding TanStack Query Plugin
+
+Now add the plugin to the ZModel schema:
+
+```zmodel title="schema.zmodel"
+plugin hooks {
+  provider = '@zenstackhq/tanstack-query'
+  target = 'react'
+  version = 'v5'
+  output = 'src/lib/hooks'
+}
+```
+
+TanStack Query is a multi-framework library and supports React, Vue, and Svelte. The "target" field specifies the frontend framework to generate hooks for. The "version" field specifies the version of TanStack Query to target. Here, we're using the latest "v5" version.
+
+Then, rerun generation and observe that the hooks code is generated in the `src/lib/hooks` directory.
+
+```bash
+npx zenstack generate
+```
+
+### A Peek Into the Hooks
+
+The generated hooks allow you to query and mutate data without worrying about the API details. They're designed to fully preserve the Prisma query syntax you already know. Here are some quick examples of their usage:
+
+- Nested read with filtering and sorting
+
+    ```tsx
+    // `data` is typed as `(Todo & { list: List })[]`
+    const { data } = useFindManyTodo({
+      where: { owner: { id: 1 } },
+      include: { list: true },
+      orderBy: { createdAt: 'desc' }
+    });
+    ```
+
+- Nested create
+
+    ```tsx
+    const { mutate } = useCreateList();
+
+    function onCreateList() {
+      mutate({
+        data: { 
+          title: 'My List',
+          owner: { connect: { id: userId } },
+          todos: {
+            create: [
+              { name: 'Todo 1' },
+              { name: 'Todo 2' }
+            ]
+          }
+        }
+      })
+    }
+    ```
+
+- Count
+
+    ```tsx
+    const { data } = useCountTodo({ where: { owner: { id: 1 } } });
+    ```
+
+We'll cover the usage of the hooks in detail in the upcoming chapters.
diff --git a/versioned_docs/version-1.x/the-complete-guide/part4/3-auth-ui.md b/versioned_docs/version-1.x/the-complete-guide/part4/3-auth-ui.md
new file mode 100644
index 00000000..e011889b
--- /dev/null
+++ b/versioned_docs/version-1.x/the-complete-guide/part4/3-auth-ui.md
@@ -0,0 +1,221 @@
+---
+sidebar_label: 3. Building Authentication UI
+---
+
+# 🛠️ Building Authentication UI
+
+The TanStack Query hooks generated in the previous chapter provide all the data-access building blocks for implementing the UI. Let's first use it to implement the authentication UI.
+
+### Signup Page
+
+Create a `src/app/signup/page.tsx` file with the following content:
+
+```tsx title="/src/app/signup/page.tsx"
+'use client';
+
+import { signIn } from 'next-auth/react';
+import Link from 'next/link';
+import { useState, type FormEvent } from 'react';
+import { useCreateUser } from '~/lib/hooks';
+
+export default function Signup() {
+  const [email, setEmail] = useState('');
+  const [password, setPassword] = useState('');
+  const { mutate: signup, error: signupError } = useCreateUser({
+    onSuccess: async () => {
+      // sign-up succeeded, sign in with the credentials
+      const signInResult = await signIn('credentials', {
+        redirect: false,
+        email,
+        password,
+      });
+      if (signInResult?.ok) {
+        window.location.href = '/';
+      } else {
+        console.error('Signin failed:', signInResult?.error);
+      }
+    },
+  });
+
+  const _err = signupError as { info?: { code?: string } };
+  const errMsg = _err
+    ? _err.info?.code === 'P2002' // P2002 is the Prisma error code for unique constraint failure
+      ? 'Email already exists'
+      : `Unexpected error occurred: ${JSON.stringify(_err)}`
+    : '';
+
+  function onSignup(e: FormEvent<HTMLFormElement>) {
+    e.preventDefault();
+    signup({ data: { email, password } });
+  }
+
+  return (
+    <div className="mx-auto flex h-screen flex-col items-center justify-center">
+      <div className="mb-10 flex items-center space-x-4">
+        <h1 className="text-4xl">Welcome to Todo</h1>
+      </div>
+      <div className="flex w-full max-w-screen-sm items-center justify-center rounded-lg">
+        <div className="w-full space-y-8 p-16">
+          <h2 className="text-3xl font-bold">Create a Free Account</h2>
+          <form className="mt-8 space-y-6" action="#" onSubmit={onSignup}>
+            <div>
+              <label htmlFor="email" className="label">
+                Your email
+              </label>
+              <input
+                type="email"
+                value={email}
+                onChange={(e) => setEmail(e.target.value)}
+                className="input input-bordered w-full"
+                placeholder="Email address"
+                required
+              />
+            </div>
+            <div>
+              <label htmlFor="password" className="label">
+                Your password
+              </label>
+              <input
+                type="password"
+                value={password}
+                onChange={(e) => setPassword(e.target.value)}
+                placeholder="••••••••"
+                className="input input-bordered w-full"
+                required
+              />
+            </div>
+
+            {errMsg && <p className="text-sm text-red-600">{errMsg}</p>}
+
+            <button className="btn btn-primary mt-4" type="submit">
+              Create account
+            </button>
+            <div>
+              Already have an account?{' '}
+              <Link href="/signin" className="text-primary">
+                Login here
+              </Link>
+            </div>
+          </form>
+        </div>
+      </div>
+    </div>
+  );
+}
+```
+
+Next, replace the homepage `src/app/page.tsx` with the following content to show the current user:
+
+```tsx title="/src/app/page.tsx"
+'use client';
+
+import { useSession } from "next-auth/react";
+
+export default function Home() {
+  const { data: session } = useSession();
+  return (
+    <div className="flex h-screen w-screen items-center justify-center">
+      {session?.user && <h1>Welcome {session?.user?.email}</h1>}
+    </div>
+  );
+}
+```
+
+Our bare minimum signup page is ready now. Start the dev server and give it a try at [http://localhost:3000/signup](http://localhost:3000/signup).
+
+```bash
+npm run dev
+```
+
+![Sign-up UI](./signup-ui.gif)
+
+**What's happening here？**
+
+We're using the generated `useCreateUser` mutation hook to create a new user. The hook calls into the CRUD API that we mounted to the "/api/model" endpoint in the previous chapter.
+
+### Sign-In Page
+
+The sign-in part is purely made with NextAuth's API. Create a `src/app/signin/page.tsx` file with the following content:
+
+```tsx title="/src/app/signin/page.tsx"
+'use client';
+
+import { signIn } from 'next-auth/react';
+import Link from 'next/link';
+import { useState, type FormEvent } from 'react';
+
+export default function Signup() {
+  const [email, setEmail] = useState('');
+  const [password, setPassword] = useState('');
+  const [errMsg, setErrMsg] = useState('');
+
+  async function onSignin(e: FormEvent<HTMLFormElement>) {
+    e.preventDefault();
+    const signInResult = await signIn('credentials', {
+      redirect: false,
+      email,
+      password,
+    });
+    if (signInResult?.ok) {
+      window.location.href = '/';
+    } else {
+      setErrMsg(`Signin failed. Please check your email and password.`);
+    }
+  }
+
+  return (
+    <div className="mx-auto flex h-screen flex-col items-center justify-center">
+      <div className="mb-10 flex items-center space-x-4">
+        <h1 className="text-4xl">Welcome to Todo</h1>
+      </div>
+      <div className="flex w-full max-w-screen-sm items-center justify-center rounded-lg">
+        <div className="w-full space-y-8 p-16">
+          <h2 className="text-3xl font-bold">Sign in to your account</h2>
+          <form className="mt-8 space-y-6" action="#" onSubmit={onSignin}>
+            <div>
+              <label htmlFor="email" className="label">
+                Your email
+              </label>
+              <input
+                type="email"
+                value={email}
+                onChange={(e) => setEmail(e.target.value)}
+                className="input input-bordered w-full"
+                placeholder="Email address"
+                required
+              />
+            </div>
+            <div>
+              <label htmlFor="password" className="label">
+                Your password
+              </label>
+              <input
+                type="password"
+                value={password}
+                onChange={(e) => setPassword(e.target.value)}
+                placeholder="••••••••"
+                className="input input-bordered w-full"
+                required
+              />
+            </div>
+
+            {errMsg && <p className="text-sm text-red-600">{errMsg}</p>}
+
+            <button className="btn btn-primary mt-4" type="submit">
+              Create account
+            </button>
+            <div>
+              Not registered?{' '}
+              <Link href="/signup" className="text-primary">
+                Create account
+              </Link>
+            </div>
+          </form>
+        </div>
+      </div>
+    </div>
+  );
+}
+```
+
+By now, we've got a fully running signup and sign-in flow. You can try signin in with the account you just created at [http://localhost:3000/signin](http://localhost:3000/signin).
diff --git a/versioned_docs/version-1.x/the-complete-guide/part4/4-invalidation.md b/versioned_docs/version-1.x/the-complete-guide/part4/4-invalidation.md
new file mode 100644
index 00000000..81bff92d
--- /dev/null
+++ b/versioned_docs/version-1.x/the-complete-guide/part4/4-invalidation.md
@@ -0,0 +1,226 @@
+---
+sidebar_label: 4. Automatic Invalidation
+---
+
+# Automatic Invalidation
+
+Data query libraries like TanStack Query and SWR are essentially cache managers. They cache the data fetched from the server, let you bind it to the UI, and allow you to invalidate the query cache when the data is updated.
+
+In application development, query invalidation is the developers' job. After making a mutation, a developer should know what queries are impacted, compute their keys, and trigger invalidation. For example, after creating a `Todo`, you'll need to remember to invalidate the queries that directly or indirectly fetch a list of Todos. This can be hard to manage when your app gets more complex.
+
+Fortunately, ZenStack can fully automate it for you by exploiting an important fact:
+
+> It knows what mutations impact what kind of queries for the hooks it generates.
+
+For example, when you use `useCreateTodo()` to create a `Todo`, ZenStack can go through the query cache, identify all queries potentially impacted by the mutation, and invalidate them. For example, a query made with:
+
+```ts
+useFindManyList({ include: { todos: true } });
+```
+
+will be invalidated because it fetches a list of Todos with nested reading.
+
+:::info How ZenStack uses query keys
+
+ZenStack uses a quintuple to form a query key:
+
+```ts
+['zenstack', model, operation, args, flags]
+```
+
+- `'zenstack'`: a string literal prefix
+- `model`: the model name, e.g., "Todo"
+- `operation`: the Prisma operation name, e.g., "findMany"
+- `args`: the arguments passed to the operation, e.g., `{ include: { todos: true } }`
+- `flags`: a set of flags that further defines the query, e.g., if it's an infinite query, etc
+
+You usually don't need to use query keys directly, but the generated hooks provide a `getQueryKey` helper function to help you compute them.
+
+:::
+
+In this chapter, we'll add a `Space` and a `List` management UI to our Todo app and see how the automatic invalidation works in action.
+
+### 🛠️ Adding Space Management
+
+We'll use the homepage to manage spaces. Replace the content of `src/app/page.tsx` with the following:
+
+```tsx title="src/app/page.tsx"
+'use client';
+
+import { nanoid } from 'nanoid';
+import type { NextPage } from 'next';
+import { signOut, useSession } from 'next-auth/react';
+import Link from 'next/link';
+import { useCreateSpace, useFindManySpace } from '~/lib/hooks';
+
+const Home: NextPage = () => {
+  const { data: session } = useSession();
+  const { mutate: createSpace } = useCreateSpace();
+  const { data: spaces } = useFindManySpace({ orderBy: { createdAt: 'desc' } });
+
+  function onCreateSpace() {
+    const name = prompt('Enter a name for your space');
+    if (name) {
+      createSpace({
+        data: {
+          name,
+          slug: nanoid(6),
+          owner: { connect: { id: session?.user.id } },
+          // add the creating user as an admin member
+          members: {
+            create: {
+              user: { connect: { id: session?.user.id } },
+              role: 'ADMIN',
+            },
+          },
+        },
+      });
+    }
+  }
+
+  return (
+    <div className="container mx-auto flex justify-center">
+      {session?.user ? (
+        <div className="mt-8 flex w-full flex-col items-center">
+          <h1 className="text-center text-2xl">
+            Welcome {session.user.email}{' '}
+            <button
+                className="btn btn-ghost btn-xs mt-4"
+                onClick={() => signOut({ callbackUrl: '/signin' })}
+            >
+              Logout
+            </button>
+          </h1>
+
+          <div className="w-full p-8">
+            <h2 className="mb-8 text-xl">
+              Choose a space to start, or{' '}
+              <button className="btn btn-link p-0 text-xl" onClick={onCreateSpace}>
+                create a new one.
+              </button>
+            </h2>
+
+            <ul className="flex gap-4">
+              {spaces?.map((space) => (
+                <Link href={`/spaces/${space.slug}`} key={space.id}>
+                  <li className="flex h-32 w-72 items-center justify-center rounded-lg border text-2xl">
+                    {space.name}
+                  </li>
+                </Link>
+              ))}
+            </ul>
+          </div>
+        </div>
+      ) : (
+        <div>
+          Please{' '}
+          <Link href="/signin">
+            <button className="btn btn-link p-0">login</button>
+          </Link>{' '}
+          to get started
+        </div>
+      )}
+    </div>
+  );
+};
+
+export default Home;
+```
+
+The key part is the usage of the `useFindManySpace` query hook and the `useCreateSpace` mutation hook. Some quick notes about the logic:
+
+- Calling `useFindManySpace` is conceptually equivalent to calling `db.space.findMany()` in the backend. The access policies ensure only the spaces readable to the user are returned.
+
+- The `onCreateSpace` function shows the flexibility of the hooks. When creating a space, we make a nested write to create a `SpaceUser` record that adds the user as the admin member of the space. The call is also executed in the backend as a nested write, so the two create operations are atomic.
+
+Now, you can head to the homepage and create a few spaces. The UI should look like this:
+
+![Space management](space-mgmt.png)
+
+You've probably already noticed this: after creating a space, you don't need to refresh the UI to see the result. ZenStack figures out that the `useCreateSpace` mutation can potentially impact the `useFindManySpace` query and automatically invalidates it.
+
+:::tip Opt-out of automatic invalidation
+
+When calling a mutation hook, you can pass an extra `invalidateQueries` argument as `false` to opt out of automatic invalidation. For example:
+
+```ts
+const { mutate: createSpace } = useCreateSpace(undefined, false);
+```
+
+With this change, the created space won't appear in the list until you refresh the page.
+
+:::
+
+### 🛠️ Adding List Management
+
+List management is essentially the same as space, so we'll skip the code walkthrough. Here's the content of `src/app/spaces/[slug]/page.tsx`:
+
+```tsx title="src/app/spaces/[slug]/page.tsx"
+'use client';
+
+import { useSession } from 'next-auth/react';
+import Link from 'next/link';
+import { useParams } from 'next/navigation';
+import { useCreateList, useFindManyList, useFindUniqueSpace } from '~/lib/hooks';
+
+export default function SpaceHome() {
+    const { slug } = useParams<{ slug: string }>();
+
+    const { data: session } = useSession();
+
+    const { data: space } = useFindUniqueSpace({ where: { slug } }, { enabled: !!session?.user });
+
+    const { data: lists } = useFindManyList(
+        {
+            where: { space: { slug } },
+            include: { owner: true },
+            orderBy: { updatedAt: 'desc' },
+        },
+        { enabled: !!session?.user },
+    );
+
+    const { mutate: createList } = useCreateList();
+
+    function onCreateList() {
+        const title = prompt('Enter a title for your list');
+        if (title) {
+            createList({
+                data: {
+                    title,
+                    space: { connect: { id: space?.id } },
+                    owner: { connect: { id: session?.user.id } },
+                },
+            });
+        }
+    }
+
+    if (!session?.user || !space || !lists) return null;
+
+    return (
+        <div className="container mx-auto mt-16">
+            <h1 className="text-center text-3xl">
+                Welcome to Space <span className="italic">{space.name}</span>
+            </h1>
+            <div className="p-8">
+                <button className="btn btn-primary btn-wide" onClick={onCreateList}>
+                    Create a list
+                </button>
+
+                <ul className="mt-8 flex flex-wrap gap-6">
+                    {lists?.map((list) => (
+                        <Link href={`/spaces/${slug}/${list.id}`} key={list.id}>
+                            <li className="flex h-32 w-72 items-center justify-center rounded-lg border text-2xl">
+                                {list.title}
+                            </li>
+                        </Link>
+                    ))}
+                </ul>
+            </div>
+        </div>
+    );
+}
+```
+
+The result should look like this:
+
+![List management](list-mgmt.png)
diff --git a/versioned_docs/version-1.x/the-complete-guide/part4/5-optimistic-update.md b/versioned_docs/version-1.x/the-complete-guide/part4/5-optimistic-update.md
new file mode 100644
index 00000000..a86820d8
--- /dev/null
+++ b/versioned_docs/version-1.x/the-complete-guide/part4/5-optimistic-update.md
@@ -0,0 +1,213 @@
+---
+sidebar_label: 5. Optimistic Update
+---
+
+# Optimistic Update
+
+Optimistic update is a technique that allows us to update the UI immediately after a user action without waiting for the server response. It's a great way to improve the perceived performance of an application and is especially useful for the parts where users interact with the UI frequently.
+
+Libraries like TanStack Query and SWR provides the framework for developers to implement optimistic update. The approach is essentially to update the query cache immediately after the user action and revert or invalidate the cache after the server response comes back (either success or error). However, similar to the query invalidation problem we discussed in the previous chapter, the challenge is identifying which queries need to be updated and how.
+
+ZenStack supports automatic optimistic update for the hooks it generates. It uses a set of rules to update queries and aims to cover the most common use cases. You can find more on how it works [here](../../reference/plugins/tanstack-query#details-of-the-optimistic-behavior).
+
+:::info Automatic optimistic update is an approximation
+
+It's often impossible to accurately determine the correct way to update a query cache from the frontend. For example, you may have a query that fetches a filtered list of Todos using the `useFindManyTodo()` hook. When creating a new Todo, we can't precisely determine if the new item should be inserted into the query result (i.e., matching the filter conditions) without querying the database. But paradoxically, the update is no longer optimistic if we query the database.
+
+ZenStack takes an "optimistic" action here and inserts the new Todo into the query result. It's not always correct, but the worst-case scenario is that the item shows up on the list for a short while until the query is invalidated upon getting the server response.
+
+:::
+
+### 🛠️ Adding Todo Management
+
+The main missing part of our sample app is managing Todos in a List. This is a great place to implement optimistic update because users may add, update, or delete Todos in quick paces, and waiting for the server response to update the UI will result in a very sluggish experience.
+
+Let's first implement the Todo management UI without optimistic update. First, create a component `src/components/TodoComponent.tsx` to manage one single Todo:
+
+```tsx title="src/components/TodoComponent.tsx"
+import type { Todo } from "@prisma/client";
+import { useDeleteTodo, useUpdateTodo } from "~/lib/hooks";
+
+type Props = {
+  value: Todo;
+  optimistic?: boolean;
+};
+
+export default function TodoComponent({ value, optimistic }: Props) {
+  const update = useUpdateTodo();
+  const del = useDeleteTodo();
+
+  function onDelete() {
+    del.mutate({ where: { id: value.id } });
+  }
+
+  function onToggleCompleted(completed: boolean) {
+    if (completed === !!value.completedAt) {
+      return;
+    }
+    update.mutate({
+      where: { id: value.id },
+      data: { completedAt: completed ? new Date() : null },
+    });
+  }
+
+  return (
+    <div className="flex w-96 flex-col items-center rounded-lg border px-8 py-4">
+      <div className="flex w-full justify-between">
+        <h3
+          className={`flex items-center text-xl
+                        ${
+                          value.completedAt
+                            ? "italic text-gray-400 line-through"
+                            : "text-gray-700"
+                        }
+                    }`}
+        >
+          {value.title}
+          {optimistic && (
+            <span className="loading loading-spinner loading-sm ml-1"></span>
+          )}
+        </h3>
+        <div className="flex">
+          <input
+            type="checkbox"
+            className="checkbox mr-2"
+            checked={!!value.completedAt}
+            onChange={(e) => onToggleCompleted(e.currentTarget.checked)}
+          />
+          <button className="btn btn-ghost btn-xs" onClick={onDelete}>
+            Delete
+          </button>
+        </div>
+      </div>
+    </div>
+  );
+}
+```
+
+Then, create a page at `src/app/spaces/[slug]/[listId]/page.tsx` to manage Todos in a List:
+
+```tsx title="src/app/spaces/[slug]/[listId]/page.tsx"
+"use client";
+
+import { useSession } from "next-auth/react";
+import { useParams } from "next/navigation";
+import { useState } from "react";
+import TodoComponent from "~/components/TodoComponent";
+import { useCreateTodo, useFindManyTodo, useFindUniqueList } from "~/lib/hooks";
+
+export default function TodoList() {
+  const { listId } = useParams<{ listId: string }>();
+  const { data: session } = useSession();
+
+  const { data: list } = useFindUniqueList(
+    {
+      where: { id: listId },
+    },
+    { enabled: !!session?.user },
+  );
+
+  const { mutate: create } = useCreateTodo();
+  const { data: todos } = useFindManyTodo(
+    {
+      where: { listId },
+      orderBy: { createdAt: "desc" as const },
+    },
+    { enabled: !!session?.user },
+  );
+
+  const [title, setTitle] = useState("");
+
+  function onCreate() {
+    create({
+      data: {
+        title,
+        owner: { connect: { id: session?.user.id } },
+        list: { connect: { id: listId } },
+      },
+    });
+    setTitle("");
+  }
+
+  if (!session?.user || !list) {
+    return <></>;
+  }
+
+  return (
+    <div>
+      <div className="container mx-auto flex w-full flex-col items-center py-12">
+        <h1 className="mb-4 text-2xl font-semibold">{list.title}</h1>
+        <div className="flex space-x-2">
+          <input
+            type="text"
+            placeholder="Type a title and press enter"
+            className="input input-bordered mt-2 w-72 max-w-xs"
+            value={title}
+            autoFocus
+            onKeyUp={(e) => {
+              if (e.key === "Enter") {
+                onCreate();
+              }
+            }}
+            onChange={(e) => {
+              setTitle(e.currentTarget.value);
+            }}
+          />
+        </div>
+        <ul className="flex w-auto flex-col space-y-4 py-8">
+          {todos?.map((todo) => (
+            <TodoComponent
+              key={todo.id}
+              value={todo}
+              optimistic={todo.$optimistic}
+            />
+          ))}
+        </ul>
+      </div>
+    </div>
+  );
+}
+```
+
+To simulate usage in the real world, I've enabled Chrome's network throttling to "Fast 3G" mode. Here's what it looks like:
+
+![Todo management without optimistic update](todo-mgmt-non-optimistic.gif)
+
+It's obviously very sluggish. It's not something a user wants to pay for.
+
+Let's make some small changes to enable automatic optimistic update. In `src/pages/spaces/[slug]/[listId]/index.tsx`, change the `useCreateTodo` call to the following:
+
+```tsx
+// the three arguments are: 
+//     - mutation options
+//     - auto invalidate (default true)
+//     - auto optimistic-update (default false)
+const { mutate: create } = useCreateTodo(undefined, true, true);
+```
+
+And likewise, change the `useUpdateTodo` and `useDeleteTodo` calls in `Todo.tsx` to the following:
+
+```tsx
+const update = useUpdateTodo(undefined, true, true); // optimistic update
+const del = useDeleteTodo(undefined, true, true); // optimistic update
+```
+
+Now, if we repeat the same test with network throttling, we'll see a much better experience:
+
+![Todo management with optimistic update](todo-mgmt-optimistic.gif)
+
+:::tip Opt-out of automatic optimistic update
+
+When you enable automatic optimistic update for a mutation, it applies to all queries that may be impacted. If this is not desirable, you can pass an extra `optimisticUpdate` argument as `false` to opt out of automatic optimistic update. For example:
+
+```ts
+const { data: todos } = useFindManyTodo(
+    { where: { listId } },
+    { enabled: !!session?.user },
+    false, // <- opt-out automatic optimistic update
+);
+```
+
+With this change, the `useCreateTodo` mutation will not optimistically update this query even if the mutation is set to do so.
+
+:::
\ No newline at end of file
diff --git a/versioned_docs/version-1.x/the-complete-guide/part4/6-conclusion.md b/versioned_docs/version-1.x/the-complete-guide/part4/6-conclusion.md
new file mode 100644
index 00000000..6fb7607b
--- /dev/null
+++ b/versioned_docs/version-1.x/the-complete-guide/part4/6-conclusion.md
@@ -0,0 +1,14 @@
+---
+sidebar_label: 6. Conclusion
+---
+
+# Conclusion
+
+In this part of the guide, we've focused on using plugins to generate frontend data query hooks and use them to build highly responsive data-driven UIs with ease. We've also evolved our sample project into a fully functional application. There are still a few parts unimplemented (like space member management), but since we've covered all essential ZenStack concepts and features with what we've built, we'll skip them in this guide. You can find the completed source code for this part in the "main" branch of [https://github.com/zenstackhq/the-complete-guide-sample](https://github.com/zenstackhq/the-complete-guide-sample).
+
+There are also variants of the Todo app built with different stacks in the following repositories. They are feature complete and have better UI styling.
+
+- [Next.js (pages route) + SWR](https://github.com/zenstackhq/sample-todo-nextjs)
+- [Next.js (pages route) + TanStack Query](https://github.com/zenstackhq/sample-todo-nextjs-tanstack)
+- [Nuxt + TanStack Query](https://github.com/zenstackhq/sample-todo-nuxt)
+- [SvelteKit + TanStack Query](https://github.com/zenstackhq/sample-todo-sveltekit)
diff --git a/versioned_docs/version-1.x/the-complete-guide/part4/index.md b/versioned_docs/version-1.x/the-complete-guide/part4/index.md
new file mode 100644
index 00000000..3b652383
--- /dev/null
+++ b/versioned_docs/version-1.x/the-complete-guide/part4/index.md
@@ -0,0 +1,12 @@
+---
+sidebar_label: "Part IV: Frontend Data Query"
+sidebar_position: 4
+---
+
+# Part IV: Frontend Data Query
+
+In previous parts of this guide, we have learned how ZenStack turns Prisma into a data access layer with access control and how, based on that, we can automatically serve secure CRUD APIs.
+
+Consuming a CRUD API is straightforward, but it's a tedious and error-prone job if you do it manually. In this part, we'll see how ZenStack can help us generate frontend data query libraries (aka hooks) that make it effortless to consume the APIs from the frontend. We'll also continue evolving our Todo project into a full-fledged application.
+
+Let's roll on!
diff --git a/versioned_docs/version-1.x/the-complete-guide/part4/list-mgmt.png b/versioned_docs/version-1.x/the-complete-guide/part4/list-mgmt.png
new file mode 100644
index 00000000..94532adc
Binary files /dev/null and b/versioned_docs/version-1.x/the-complete-guide/part4/list-mgmt.png differ
diff --git a/versioned_docs/version-1.x/the-complete-guide/part4/signup-ui.gif b/versioned_docs/version-1.x/the-complete-guide/part4/signup-ui.gif
new file mode 100644
index 00000000..732bf14b
Binary files /dev/null and b/versioned_docs/version-1.x/the-complete-guide/part4/signup-ui.gif differ
diff --git a/versioned_docs/version-1.x/the-complete-guide/part4/space-mgmt.png b/versioned_docs/version-1.x/the-complete-guide/part4/space-mgmt.png
new file mode 100644
index 00000000..e185b9a5
Binary files /dev/null and b/versioned_docs/version-1.x/the-complete-guide/part4/space-mgmt.png differ
diff --git a/versioned_docs/version-1.x/the-complete-guide/part4/todo-mgmt-non-optimistic.gif b/versioned_docs/version-1.x/the-complete-guide/part4/todo-mgmt-non-optimistic.gif
new file mode 100644
index 00000000..5249c5c5
Binary files /dev/null and b/versioned_docs/version-1.x/the-complete-guide/part4/todo-mgmt-non-optimistic.gif differ
diff --git a/versioned_docs/version-1.x/the-complete-guide/part4/todo-mgmt-optimistic.gif b/versioned_docs/version-1.x/the-complete-guide/part4/todo-mgmt-optimistic.gif
new file mode 100644
index 00000000..ee033ae8
Binary files /dev/null and b/versioned_docs/version-1.x/the-complete-guide/part4/todo-mgmt-optimistic.gif differ
diff --git a/versioned_docs/version-1.x/welcome.md b/versioned_docs/version-1.x/welcome.md
new file mode 100644
index 00000000..6155bcf5
--- /dev/null
+++ b/versioned_docs/version-1.x/welcome.md
@@ -0,0 +1,50 @@
+---
+description: Welcome to ZenStack
+slug: /welcome
+sidebar_label: Welcome
+sidebar_position: 1
+---
+
+# Welcome to ZenStack
+
+Hey 👋, I'm glad you found us here.
+
+If you are using TypeScript to build APIs or web apps above a SQL database, you are in the right place. ZenStack is an open-source toolkit built above [Prisma](https://prisma.io) - the most popular ORM for Node.js. ZenStack pushes Prisma's power to a new level and boosts the development efficiency of every layer of your stack - from access control, to API development, and all the way up to the frontend.
+
+Some of the most common use cases of ZenStack include:
+- Multi-tenant SaaS
+- Applications with complex access control requirements
+- CRUD-intensive API or web apps
+
+ZenStack is not opinionated about your choice of framework. It can be used with any of them.
+
+## Features
+
+- ORM with built-in access control and data validation
+
+- Auto-generated CRUD API - RESTful & tRPC
+
+- Auto-generated OpenAPI documentation
+
+- Auto-generated frontend data query hooks - SWR & TanStack Query
+
+- Integrations with popular authentication services and full-stack/backend frameworks
+
+- A plugin system for great extensibility
+
+## Find the Learning Path That Fits You
+
+ZenStack is a comprehensive toolkit that touches a wide range of topics. For most developers, we'd highly recommend you start with the [The Complete Guide](./the-complete-guide) to build a solid understanding of each layer of ZenStack's functionalities. The guide is designed to lay out concepts gradually and lead you through the learning journey from using ZenStack just as a better ORM, to leveraging its total power in full-stack development. The guide is pretty long, but trust me you'll enjoy the reading 😉.
+
+If you're the kind of inpatient developer who wants to get your hand dirty ASAP, you can also jump straight to the [Quick Start](./category/quick-start) guide. It shows how to quickly set up a ZenStack project targeting the framework of your choice.
+
+<!-- It explains how to use the CLI to quickly scaffold a ZenStack project targeting the framework of your choice. -->
+
+## Other Resources
+
+- [Recipes](./category/recipes/) - Mini guides for completing specific tasks using ZenStack
+- [Sample Catalog](./sample-catalog) - A catalog of sample projects using different frameworks
+- [Reference](./category/reference/) - Detailed documentation for the DSL, CLI, APIs, and plugins
+- [FAQ](./faq) - Frequently asked questions
+  
+Ready to roll? Let's get started 🚀
\ No newline at end of file
diff --git a/versioned_sidebars/version-1.x-sidebars.json b/versioned_sidebars/version-1.x-sidebars.json
new file mode 100644
index 00000000..fc4fe139
--- /dev/null
+++ b/versioned_sidebars/version-1.x-sidebars.json
@@ -0,0 +1,8 @@
+{
+  "mySidebar": [
+    {
+      "type": "autogenerated",
+      "dirName": "."
+    }
+  ]
+}
diff --git a/versions.json b/versions.json
new file mode 100644
index 00000000..c339c072
--- /dev/null
+++ b/versions.json
@@ -0,0 +1 @@
+["1.x"]
