From 0aab560ef72f2772fffcbff092499206f9bbbe3a Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Fri, 25 Nov 2022 23:08:34 +0800 Subject: [PATCH] more documentation --- docs/_sidebar.md | 8 +- docs/building-your-app.md | 2 +- docs/choosing-a-database.md | 8 +- docs/cli-commands.md | 2 +- docs/entity-types-server.md | 84 ++++++++++++++ docs/entity-types.md | 84 ++++++++++++++ docs/integrating-authentication.md | 85 +++++++++++++++ docs/reach-out.md | 9 ++ docs/runtime-api.md | 170 +++++++++++++++++++++++++++++ docs/runtime-client.md | 3 - docs/runtime-server.md | 3 - docs/runtime-types.md | 3 - docs/telemetry.md | 21 ++++ 13 files changed, 462 insertions(+), 20 deletions(-) create mode 100644 docs/entity-types-server.md create mode 100644 docs/entity-types.md create mode 100644 docs/reach-out.md create mode 100644 docs/runtime-api.md delete mode 100644 docs/runtime-client.md delete mode 100644 docs/runtime-server.md delete mode 100644 docs/runtime-types.md create mode 100644 docs/telemetry.md diff --git a/docs/_sidebar.md b/docs/_sidebar.md index c46bfe260..87cf08cc3 100644 --- a/docs/_sidebar.md +++ b/docs/_sidebar.md @@ -22,11 +22,7 @@ - [Commands](cli-commands.md) -- Runtime API - - - [`@zenstackhq/runtime/types`](runtime-types.md) - - [`@zenstackhq/runtime/client`](runtime-client.md) - - [`@zenstackhq/runtime/server`](runtime-server.md) +- [Runtime API](runtime-api.md) - Guide @@ -34,5 +30,7 @@ - [Evolving model with migration](evolving-model-with-migration.md) - [Integrating authentication](integrating-authentication.md) - [Set up logging](setup-logging.md) + - [Telemetry](telemetry.md) - [VSCode extension](vscode-extension.md) +- [Reach out to the developers](reach-out.md) diff --git a/docs/building-your-app.md b/docs/building-your-app.md index 04f1edc6e..98d2af6e9 100644 --- a/docs/building-your-app.md +++ b/docs/building-your-app.md @@ -164,6 +164,6 @@ export const getServerSideProps: GetServerSideProps = async () => { The Typescript types of data models, filters, sorting, etc., are all shared between the frontend and the backend. -**Note** that server-side database access is not protected by access policies. This is by-design so as to provide a way of bypassing the policies. Please make sure you implement authorization properly. +_Note_ Server-side database access is **NOT PROTECTED** by access policies. This is by-design so as to provide a way of bypassing the policies. Please make sure you implement authorization properly. _TBD_ In the future we'll provide a utility for explicitly validating access policies in backend code, so that you can reuse your policy definitions in the model. diff --git a/docs/choosing-a-database.md b/docs/choosing-a-database.md index 7e8fbfb9f..44bec5d16 100644 --- a/docs/choosing-a-database.md +++ b/docs/choosing-a-database.md @@ -1,11 +1,11 @@ # Choosing a database -ZenStack is agnostic about where and how you deploy your web app, but hosting on serverless platforms like [Vercel](https://vercel.com/) is definitely a popular choice. +ZenStack is agnostic about where and how you deploy your web app, but hosting on serverless platforms like [Vercel](https://vercel.com/ ':target=blank') is definitely a popular choice. Serverless architecture has some implications on how you should care about your database hosting. Different from traditional architecture where you have a fixed number of long-running Node.js servers, in a serverless environment, a new Node.js context can potentially be created for each user request, and if traffic volume is high, this can quickly exhaust your database's connection limit, if you connect to the database directly without a proxy. You'll likely be OK if your app has a low number of concurrent users, otherwise you should consider using a proxy in front of your database server. Here's a number of (incomplete) solutions you can consider: -- [Prisma Data Proxy](https://www.prisma.io/data-platform/proxy) -- [Supabase](https://supabase.com/)'s [connection pool](https://supabase.com/docs/guides/database/connecting-to-postgres#connection-pool) -- [Deploy pgbouncer with Postgres on Heroku](https://devcenter.heroku.com/articles/postgres-connection-pooling) +- [Prisma Data Proxy](https://www.prisma.io/data-platform/proxy ':target=blank') +- [Supabase](https://supabase.com/)'s [connection pool](https://supabase.com/docs/guides/database/connecting-to-postgres#connection-pool ':target=blank') +- [Deploy pgbouncer with Postgres on Heroku](https://devcenter.heroku.com/articles/postgres-connection-pooling ':target=blank') diff --git a/docs/cli-commands.md b/docs/cli-commands.md index 6866282dc..92be36169 100644 --- a/docs/cli-commands.md +++ b/docs/cli-commands.md @@ -5,7 +5,7 @@ Set up ZenStack for an existing Next.js + Typescript project. ```bash -npx zenstack init [dir] +npx zenstack init [options] [dir] ``` _Options_: diff --git a/docs/entity-types-server.md b/docs/entity-types-server.md new file mode 100644 index 000000000..c7eaa4394 --- /dev/null +++ b/docs/entity-types-server.md @@ -0,0 +1,84 @@ +# Types + +Module `@zenstackhq/runtime/types` contains type definitions of entities, filters, sorting, etc., generated from ZModel data models. The types can be used in both the front-end and the backend code. + +Suppose a `User` model is defined in ZModel: + +```prisma +model User { + id String @id @default(cuid()) + email String @unique @email + password String @password @omit + name String? + posts Post[] +} +``` + +The following types are generated: + +## Entity type + +````ts +export type User = { + id: string + email: string + password: string | null + name: string | null + posts: Post[] +}``` + +This type serves as the return type of the generated React hooks: + +```ts +import { type User } from '@zenstackhq/runtime/types'; +import { useUser } from '@zenstackhq/runtime/client'; + +export function MyComponent() { + const { find } = useUser(); + const result = find(); + const users: User[] = result.data; + ... +} +```` + +Backend database access API also returns the same type: + +```ts +const users: User[] = await service.db.User.find(); +``` + +## Filter and sort type + +Types for filtering and sorting entites are also generated: + +```ts +export type UserFindManyArgs = { + select?: UserSelect | null; + include?: UserInclude | null; + where?: UserWhereInput; + orderBy?: Enumerable; + ... +}; +``` + +You can use it like: + +```ts +const { find } = useUser(); +const { data: users } = find({ + where: { + email: { + endsWith: '@zenstack.dev', + }, + }, + orderBy: [ + { + email: 'asc', + }, + ], + include: { + // include related Post entities + posts: true, + }, +}); +``` diff --git a/docs/entity-types.md b/docs/entity-types.md new file mode 100644 index 000000000..c7eaa4394 --- /dev/null +++ b/docs/entity-types.md @@ -0,0 +1,84 @@ +# Types + +Module `@zenstackhq/runtime/types` contains type definitions of entities, filters, sorting, etc., generated from ZModel data models. The types can be used in both the front-end and the backend code. + +Suppose a `User` model is defined in ZModel: + +```prisma +model User { + id String @id @default(cuid()) + email String @unique @email + password String @password @omit + name String? + posts Post[] +} +``` + +The following types are generated: + +## Entity type + +````ts +export type User = { + id: string + email: string + password: string | null + name: string | null + posts: Post[] +}``` + +This type serves as the return type of the generated React hooks: + +```ts +import { type User } from '@zenstackhq/runtime/types'; +import { useUser } from '@zenstackhq/runtime/client'; + +export function MyComponent() { + const { find } = useUser(); + const result = find(); + const users: User[] = result.data; + ... +} +```` + +Backend database access API also returns the same type: + +```ts +const users: User[] = await service.db.User.find(); +``` + +## Filter and sort type + +Types for filtering and sorting entites are also generated: + +```ts +export type UserFindManyArgs = { + select?: UserSelect | null; + include?: UserInclude | null; + where?: UserWhereInput; + orderBy?: Enumerable; + ... +}; +``` + +You can use it like: + +```ts +const { find } = useUser(); +const { data: users } = find({ + where: { + email: { + endsWith: '@zenstack.dev', + }, + }, + orderBy: [ + { + email: 'asc', + }, + ], + include: { + // include related Post entities + posts: true, + }, +}); +``` diff --git a/docs/integrating-authentication.md b/docs/integrating-authentication.md index 94acf565a..1379b3c07 100644 --- a/docs/integrating-authentication.md +++ b/docs/integrating-authentication.md @@ -1 +1,86 @@ # Integrating authentication + +This documentation explains how to integrate ZenStack with popular authentication frameworks. + +## NextAuth + +[NextAuth](https://next-auth.js.org/) is a comprehensive framework for implementating authentication. It offers a pluggable mechanism for configuring how user data is persisted. + +When `zenstack generate` runs, it generates an adapter for NextAuth if it finds the `next-auth` npm package is installed. The generated adapter can be configured to NextAuth as follows: + +```ts +// pages/api/auth/[...nextauth].ts + +import service from '@zenstackhq/runtime/server'; +import { NextAuthAdapter as Adapter } from '@zenstackhq/runtime/server/auth'; +import NextAuth, { type NextAuthOptions } from 'next-auth'; + +export const authOptions: NextAuthOptions = { + // install ZenStack adapter + adapter: Adapter(service), + ... +}; + +export default NextAuth(authOptions); +``` + +If you use [`CredentialsProvider`](https://next-auth.js.org/providers/credentials ':target=blank'), i.e. username/password based auth, you can also use the generated `authorize` function to implement how username/password is verified against the database: + +```ts +// pages/api/auth/[...nextauth].ts + +import service from '@zenstackhq/runtime/server'; +import { authorize } from '@zenstackhq/runtime/server/auth'; +import NextAuth, { type NextAuthOptions } from 'next-auth'; + +export const authOptions: NextAuthOptions = { + ... + providers: [ + CredentialsProvider({ + credentials: { + email: { + label: 'Email Address', + type: 'email', + }, + password: { + label: 'Password', + type: 'password', + }, + }, + + // use ZenStack's default implementation to verify credentials + authorize: authorize(service), + }), + ]}; + +export default NextAuth(authOptions); +``` + +NextAuth is agnostic about the type of underlying database, but it requires certain table structures, depending on how you configure it. Your ZModel definitions should reflect these requirements. A sample `User` model is shown here (to be used with `CredentialsProvider`): + +```prisma +model User { + id String @id @default(cuid()) + email String @unique @email + emailVerified DateTime? + password String @password @omit + name String? + image String? @url + + // open to signup + @@allow('create', true) + + // full access by oneself + @@allow('all', auth() == this) +} +``` + +You can find the detailed database model requirements [here](https://next-auth.js.org/adapters/models ':target=blank'). + +## IronSession + +[TBD] + +## Custom-built authentication + +[TBD] diff --git a/docs/reach-out.md b/docs/reach-out.md new file mode 100644 index 000000000..e57290e72 --- /dev/null +++ b/docs/reach-out.md @@ -0,0 +1,9 @@ +# Reach out to the developers + +As developers of ZenStack, we hope this toolkit can assist you to build a cool app. +Should you have any questions or ideas, please feel free to reach out to us by any of the following methods. We'll be happy to help you out. + +- [Discord](https://go.zenstack.dev/chat) +- [GitHub Discussions](https://github.com/zenstackhq/zenstack/discussions) +- [Twitter](https://twitter.com/zenstackhq) +- Email us: [contact@zenstack.dev](mailto:contact@zenstack.dev) diff --git a/docs/runtime-api.md b/docs/runtime-api.md new file mode 100644 index 000000000..335d110ce --- /dev/null +++ b/docs/runtime-api.md @@ -0,0 +1,170 @@ +# Runtime API + +## `@zenstackhq/runtime/types` + +This module contains types generated from ZModel data models. These types are shared by both the client-side and the server-side code. + +The generated types include (for each data model defined): + +- Entity type +- Data structure for creating/updating entities +- Data structure for selecting entities - including filtering and sorting + +Take `User` model as an example, here're some of the most commonly used types: + +- `User` + + The entity type which directly corresponds to the data mdoel. + +- `UserFindUniqueArgs` + + Argument type for finding a unique `User`. + +- `UserFindManyArgs` + + Argument type for finding a list of `User`s. + +- `UserCreateArgs` + + Argument for creating a new `User`. + +- `UserUpdateArgs` + + Argument for updating an existing `User`. + +## `@zenstackhq/runtime/client` + +This module contains API for client-side programming, including the generated React hooks and auxiliary types, like options and error types. + +_NOTE_ You should not import this module into server-side code, like getServerSideProps, or API endpoint. + +A `useXXX` API is generated fo each data model for getting the React hooks. The following code uses `User` model as an example. + +```ts +const { get, find, create, update, del } = useUser(); +``` + +### RequestOptions + +Options controlling hooks' fetch behavior. + +```ts +type RequestOptions = { + // indicates if fetch should be disabled + disabled: boolean; +}; +``` + +### HooksError + +Error thrown for failure of `create`, `update` and `delete` hooks. + +```ts +export type HooksError = { + status: number; + info: { + code: ServerErrorCode; + message: string; + }; +}; +``` + +#### ServerErrorCode + +| Code | Description | +| ------------------------------ | --------------------------------------------------------------------------------------------- | +| ENTITY_NOT_FOUND | The specified entity cannot be found | +| INVALID_REQUEST_PARAMS | The request parameter is invalid, either containing invalid fields or missing required fields | +| DENIED_BY_POLICY | The request is rejected by policy checks | +| UNIQUE_CONSTRAINT_VIOLATION | Violation of database unique constraints | +| REFERENCE_CONSTRAINT_VIOLATION | Violation of database reference constraint (aka. foreign key constraints) | +| READ_BACK_AFTER_WRITE_DENIED | A write operation succeeded but the result cannot be read back due to policy control | + +### get + +```ts +function get( + id: string | undefined, + args?: UserFindFirstArgs, + options?: RequestOptions +): SWRResponse; +``` + +### find + +```ts +function find( + args?: UserFindManyArgs, + options?: RequestOptions +): SWRResponse; +``` + +### create + +```ts +function create(args?: UserCreateArgs): Promise; +``` + +### update + +```ts +function update(id: string, args?: UserUpdateArgs): Promise; +``` + +### del + +```ts +function del(id: string): Promise; +``` + +## `@zenstackhq/runtime/server` + +This module contains API for server-side programming. The following declarations are exported: + +### `default` + +The default export of this module is a `service` object which encapsulates most of the server-side APIs. + +The `service.db` object contains a member field for each data model defined, which you can use to conduct database operations for that model. + +Take `User` model for example: + +```ts +import service from '@zenstackhq/runtime/server'; + +// find all users +const users = service.db.User.find(); + +// update a user +await service.db.User.update({ + where: { id: userId }, + data: { email: newEmail }, +}); +``` + +The server-side database access API uses the [same set of typing](#zenstackhqruntimetypes) as the client side. The `service.db` object is a Prisma Client, and you can find all API documentations [here](https://www.prisma.io/docs/reference/api-reference/prisma-client-reference ':target=blank'). + +### `requestHandler` + +Function for handling API endpoint requests. Used for installing the generated CRUD services onto an API route: + +```ts +// pages/api/zenstack/[...path].ts + +import service from '@zenstackhq/runtime'; +import { + requestHandler, + type RequestHandlerOptions, +} from '@zenstackhq/runtime/server'; +import { NextApiRequest, NextApiResponse } from 'next'; + +const options: RequestHandlerOptions = { + // a callback for getting the current login user + async getServerUser(req: NextApiRequest, res: NextApiResponse) { + ... + }, +}; +export default requestHandler(service, options); +``` + +The `getServerUser` callback method is used for getting the current login user on the server side. Its implementation depends on how you authenticate users. diff --git a/docs/runtime-client.md b/docs/runtime-client.md deleted file mode 100644 index 06ccedc58..000000000 --- a/docs/runtime-client.md +++ /dev/null @@ -1,3 +0,0 @@ -# @zenstackhq/runtime/client - -TBD diff --git a/docs/runtime-server.md b/docs/runtime-server.md deleted file mode 100644 index 8b7c64de3..000000000 --- a/docs/runtime-server.md +++ /dev/null @@ -1,3 +0,0 @@ -# @zenstackhq/runtime/server - -TBD diff --git a/docs/runtime-types.md b/docs/runtime-types.md deleted file mode 100644 index 3460a4497..000000000 --- a/docs/runtime-types.md +++ /dev/null @@ -1,3 +0,0 @@ -# @zenstackhq/runtime/types - -TBD diff --git a/docs/telemetry.md b/docs/telemetry.md new file mode 100644 index 000000000..42540dac0 --- /dev/null +++ b/docs/telemetry.md @@ -0,0 +1,21 @@ +# 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 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 using 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 environment variable `DO_NOT_TRACK` to `1`: + +```bash +DO_NOT_TRACK=1 npx zenstack ... +```