From 4f19913806f82ea3e380ec2e6ec501bf2b0889e4 Mon Sep 17 00:00:00 2001 From: Yiming Date: Thu, 15 Jun 2023 14:46:52 +0800 Subject: [PATCH] chore: reduce logging and update README (#493) --- README.md | 56 ++++++++++++++----- package.json | 2 +- .../src/enhancements/policy/policy-utils.ts | 22 ++++---- 3 files changed, 55 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index b71c60dac..da49a2f0a 100644 --- a/README.md +++ b/README.md @@ -21,13 +21,17 @@ ## What it is -ZenStack is a toolkit that simplifies the development of a web app's backend. It supercharges [Prisma ORM](https://prisma.io) with a powerful access control layer and unleashes its full potential for web development. +ZenStack is a Node.js/TypeScript toolkit that simplifies the development of a web app's backend. It supercharges [Prisma ORM](https://prisma.io) with a powerful access control layer and unleashes its full potential for full-stack development. Our goal is to let you save time writing boilerplate code and focus on building real features! ## How it works -ZenStack extended Prisma schema language for supporting custom attributes and functions and, based on that, implemented a flexible access control layer around Prisma. +ZenStack incrementally extends Prisma's power with the following four layers: + +### 1. ZModel - an extended Prisma schema language + +ZenStack introduces a data modeling language called "ZModel" - a superset of Prisma schema language. It extended Prisma schema with custom attributes and functions and, based on that, implemented a flexible access control layer around Prisma. ```prisma // schema.zmodel @@ -47,34 +51,58 @@ model Post { } ``` -At runtime, transparent proxies are created around Prisma clients for intercepting queries and mutations to enforce access policies. Moreover, framework integration packages help you wrap an access-control-enabled Prisma client into backend APIs that can be safely called from the frontend. +The `zenstack` CLI transpiles the ZModel into a standard Prisma schema, which you can use with the regular Prisma workflows. + +### 2. Runtime enhancements to Prisma client + +At runtime, transparent proxies are created around Prisma clients for intercepting queries and mutations to enforce access policies. ```ts -// Next.js example: pages/api/model/[...path].ts +import { withPolicy } from '@zenstackhq/runtime'; + +// a regular Prisma client +const prisma = new PrismaClient(); + +async function getPosts(userId: string) { + // create an enhanced Prisma client that has access control enabled + const enhanced = withPolicy(prisma, { user: userId }); + + // only posts that're visible to the user will be returned + return enhanced.post.findMany(); +} +``` + +### 3. Automatic RESTful APIs through server adapters + +Server adapter packages help you wrap an access-control-enabled Prisma client into backend CRUD APIs that can be safely called from the frontend. Here's an example for Next.js: + +```ts +// pages/api/model/[...path].ts import { requestHandler } from '@zenstackhq/next'; import { withPolicy } from '@zenstackhq/runtime'; import { getSessionUser } from '@lib/auth'; import { prisma } from '@lib/db'; +// Mount Prisma-style APIs: "/api/model/post/findMany", "/api/model/post/create", etc. +// Can be configured to provide standard RESTful APIs (using JSON:API) instead. export default requestHandler({ getPrisma: (req, res) => withPolicy(prisma, { user: getSessionUser(req, res) }), }); ``` -Plugins can generate strong-typed client libraries that talk to the APIs: +### 4. Generated client libraries (hooks) for data access + +Plugins can generate strong-typed client libraries that talk to the aforementioned APIs. Here's an example for React: ```tsx -// React example: components/MyPosts.tsx +// components/MyPosts.tsx -import { usePost } from '@lib/hooks'; +import { useFindManyPost } from '@lib/hooks'; const MyPosts = () => { - // Post CRUD hooks - const { findMany } = usePost(); - // list all posts that're visible to the current user, together with their authors - const { data: posts } = findMany({ + const { data: posts } = useFindManyPost({ include: { author: true }, orderBy: { createdAt: 'desc' }, }); @@ -91,7 +119,9 @@ const MyPosts = () => { }; ``` -The following diagram gives a high-level overview of how it works. +## Architecture + +The following diagram gives a high-level architecture overview of ZenStack. ![Architecture](https://zenstack.dev/img/architecture-light.png) @@ -123,7 +153,7 @@ The following diagram gives a high-level overview of how it works. ### Framework adapters -- [Next.js](https://zenstack.dev/docs/reference/server-adapters/next) +- [Next.js](https://zenstack.dev/docs/reference/server-adapters/next) (including support for the new "app directory" in Next.js 13) - [SvelteKit](https://zenstack.dev/docs/reference/server-adapters/sveltekit) - [Fastify](https://zenstack.dev/docs/reference/server-adapters/fastify) - [ExpressJS](https://zenstack.dev/docs/reference/server-adapters/express) diff --git a/package.json b/package.json index 6b7f7f4ab..6a0295dab 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "test": "pnpm -r run test --silent", "lint": "pnpm -r lint", "publish-all": "pnpm --filter \"./packages/**\" -r publish --access public", - "publish-preview": "pnpm --filter \"./packages/**\" -r publish --registry https://packagecloud.io/ymc9/zenstack-preview/npm" + "publish-preview": "pnpm --filter \"./packages/**\" -r publish --registry http://localhost:4873" }, "keywords": [], "author": "", diff --git a/packages/runtime/src/enhancements/policy/policy-utils.ts b/packages/runtime/src/enhancements/policy/policy-utils.ts index e12055d8a..653be0e33 100644 --- a/packages/runtime/src/enhancements/policy/policy-utils.ts +++ b/packages/runtime/src/enhancements/policy/policy-utils.ts @@ -360,11 +360,11 @@ export class PolicyUtil { const ids = this.getEntityIds(fieldInfo.type, fieldData); if (Object.keys(ids).length !== 0) { - if (this.logger.enabled('info')) { - this.logger.info( - `Validating read of to-one relation: ${fieldInfo.type}#${formatObject(ids)}` - ); - } + // if (this.logger.enabled('info')) { + // this.logger.info( + // `Validating read of to-one relation: ${fieldInfo.type}#${formatObject(ids)}` + // ); + // } await this.checkPolicyForFilter(fieldInfo.type, ids, operation, this.db); } } @@ -749,9 +749,9 @@ export class PolicyUtil { return; } - if (this.logger.enabled('info')) { - this.logger.info(`Checking policy for ${model}#${JSON.stringify(filter)} for ${operation}`); - } + // if (this.logger.enabled('info')) { + // this.logger.info(`Checking policy for ${model}#${JSON.stringify(filter)} for ${operation}`); + // } const queryFilter = deepcopy(filter); @@ -835,9 +835,9 @@ export class PolicyUtil { db: Record, preValue: any ) { - if (this.logger.enabled('info')) { - this.logger.info(`Checking post-update policy for ${model}#${ids}, preValue: ${formatObject(preValue)}`); - } + // if (this.logger.enabled('info')) { + // this.logger.info(`Checking post-update policy for ${model}#${ids}, preValue: ${formatObject(preValue)}`); + // } const guard = await this.getAuthGuard(model, 'postUpdate', preValue);