From 1389f1abaabf6638b262b9ee725d0e46499ddee5 Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Mon, 17 Nov 2025 22:19:08 -0800 Subject: [PATCH 1/2] doc: release 3.0.0-beta.24 --- docs/guides/multiple-schema.md | 2 +- .../quick-start/authentication/better-auth.md | 2 +- docs/quick-start/authentication/supabase.md | 2 +- .../version-1.x/guides/multiple-schema.md | 2 +- .../recipe/auth-integration/_category_.yml | 4 + .../recipe/auth-integration/better-auth.md | 150 ++++++++++++++++++ .../recipe/postgres-multi-schema.md | 2 +- .../version-3.x/reference/plugins/prisma.md | 4 +- .../reference/plugins/typescript.md | 16 +- .../reference/zmodel/datasource.md | 14 +- 10 files changed, 187 insertions(+), 11 deletions(-) create mode 100644 versioned_docs/version-3.x/recipe/auth-integration/_category_.yml create mode 100644 versioned_docs/version-3.x/recipe/auth-integration/better-auth.md diff --git a/docs/guides/multiple-schema.md b/docs/guides/multiple-schema.md index e2faad19..108a8d74 100644 --- a/docs/guides/multiple-schema.md +++ b/docs/guides/multiple-schema.md @@ -32,7 +32,7 @@ model Post extends Base viewCount Int @default(0) } -model ToDo extends Base +model Todo extends Base { title String completed Boolean @default(false) diff --git a/docs/quick-start/authentication/better-auth.md b/docs/quick-start/authentication/better-auth.md index f5c53c62..5388b396 100644 --- a/docs/quick-start/authentication/better-auth.md +++ b/docs/quick-start/authentication/better-auth.md @@ -110,7 +110,7 @@ type Auth { Here is how you could access it in the access policies: ```tsx -model ToDo { +model Todo { ... organization Organization? @relation(fields: [organizationId], references: [id], onDelete: Cascade) organizationId String? @default(auth().organizationId) @allow('update', false) diff --git a/docs/quick-start/authentication/supabase.md b/docs/quick-start/authentication/supabase.md index 7d1be034..be3d6ccf 100644 --- a/docs/quick-start/authentication/supabase.md +++ b/docs/quick-start/authentication/supabase.md @@ -16,7 +16,7 @@ To get access policies to work, ZenStack needs to be connected to the authentica 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. +This section is not directly related 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. diff --git a/versioned_docs/version-1.x/guides/multiple-schema.md b/versioned_docs/version-1.x/guides/multiple-schema.md index 60a4a5bb..39ca3327 100644 --- a/versioned_docs/version-1.x/guides/multiple-schema.md +++ b/versioned_docs/version-1.x/guides/multiple-schema.md @@ -32,7 +32,7 @@ model Post extends Base viewCount Int @default(0) } -model ToDo extends Base +model Todo extends Base { title String completed Boolean @default(false) diff --git a/versioned_docs/version-3.x/recipe/auth-integration/_category_.yml b/versioned_docs/version-3.x/recipe/auth-integration/_category_.yml new file mode 100644 index 00000000..382d3a16 --- /dev/null +++ b/versioned_docs/version-3.x/recipe/auth-integration/_category_.yml @@ -0,0 +1,4 @@ +position: 1 +label: Integrating With Authentication +collapsible: true +collapsed: true diff --git a/versioned_docs/version-3.x/recipe/auth-integration/better-auth.md b/versioned_docs/version-3.x/recipe/auth-integration/better-auth.md new file mode 100644 index 00000000..4a7debe7 --- /dev/null +++ b/versioned_docs/version-3.x/recipe/auth-integration/better-auth.md @@ -0,0 +1,150 @@ +--- +sidebar_position: 1 +--- + +import PackageInstall from '../../_components/PackageInstall'; +import PackageExec from '../../_components/PackageExec'; + +# Better-Auth Integration + +[Better-Auth](https://better-auth.com) is a comprehensive authentication library for TypeScript applications. It supports a wide range of authentication providers and offers a flexible plugin system. + +This guide will show you how to integrate better-auth with ZenStack. + +## Using ZenStack as better-auth's database provider + +ZenStack provides a better-auth database adapter in the `@zenstackhq/better-auth` package. You can use it to configure better-auth to use ZenStack ORM as its database backend. + +### Installation + + + +### Better-Auth configuration + +Add the adapter to your better-auth configuration: + +```ts +import { zenstackAdapter } from '@zenstackhq/better-auth'; +import { db } from './db'; // your ZenStack ORM client + +const auth = new BetterAuth({ + database: zenstackAdapter(db, { + provider: 'postgresql', + }), + // other better-auth options... +}); +``` + +### Schema generation + +With the adapter set up, you can use better-auth CLI to populate its database models into your ZModel schema. Make sure you've installed the CLI: + + + +Then, run the "generate" command to generate the schema: + + + +Alternatively, you can refer to [better-auth schema documentation](https://www.better-auth.com/docs/concepts/database#core-schema) to manually add the necessary models. + +After the schema is configured, you can then use the regular ZenStack database schema migration workflow to push the schema to your database. + +## Integrating better-auth with ZenStack's access control + +### Creating user-bound ORM client + +ZenStack provides a powerful [access control system](../../orm/access-control/) that allows you to define fine-grained access policies for your data models. Enforcing access control often requires fetching the validated current user's identity, which is authentication system's responsibility. + +With better-auth, you can use the `auth.api.getSession()` API to get the current session on the server side. The following code shows an example with 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 `ZenStackClient`'s `$setAuth()` method to get a user-bound ORM client. + +```tsx +const userDb = db.$setAuth({ 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, + }; +``` + +Then you can use the full `userContext` object to get a user-bound client. + +```tsx +const userDb = db.$setAuth(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 +} +``` + +Now you can access the organization information in policy rules: + +```tsx +model Todo { + ... + organization Organization? @relation(fields: [organizationId], references: [id], onDelete: Cascade) + organizationId String? @default(auth().organizationId) + + // 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') +} +``` + +## Sample project + +Here is a fully working multi-tenant sample project using better-auth, ZenStack v3, and Next.js: + +[https://github.com/ymc9/better-auth-zenstack-multitenancy/tree/zenstack-v3](https://github.com/ymc9/better-auth-zenstack-multitenancy/tree/zenstack-v3) diff --git a/versioned_docs/version-3.x/recipe/postgres-multi-schema.md b/versioned_docs/version-3.x/recipe/postgres-multi-schema.md index cedb9c3c..fd886ecb 100644 --- a/versioned_docs/version-3.x/recipe/postgres-multi-schema.md +++ b/versioned_docs/version-3.x/recipe/postgres-multi-schema.md @@ -1,5 +1,5 @@ --- -sidebar_position: 1 +sidebar_position: 2 --- # Working With PostgreSQL Schemas diff --git a/versioned_docs/version-3.x/reference/plugins/prisma.md b/versioned_docs/version-3.x/reference/plugins/prisma.md index 2e40f1b6..54e50b46 100644 --- a/versioned_docs/version-3.x/reference/plugins/prisma.md +++ b/versioned_docs/version-3.x/reference/plugins/prisma.md @@ -12,7 +12,9 @@ Please note that ZenStack's ORM runtime doesn't depend on Prisma, so you don't n ## Options -- `output`: Specifies the path of the generated Prisma schema file. If a relative path is provided, it will be resolved relative to the ZModel schema. +- `output` + + Optional string. Specifies the path of the generated Prisma schema file. If a relative path is provided, it will be resolved relative to the ZModel schema. Defaults to the same directory as the ZModel schema. ## Example diff --git a/versioned_docs/version-3.x/reference/plugins/typescript.md b/versioned_docs/version-3.x/reference/plugins/typescript.md index d851fa11..c9015b87 100644 --- a/versioned_docs/version-3.x/reference/plugins/typescript.md +++ b/versioned_docs/version-3.x/reference/plugins/typescript.md @@ -12,7 +12,21 @@ The `@core/typescript` plugin generates TypeScript code from ZModel. The generat ## Options -- `output`: Specifies the output directory for the generated TypeScript code. If a relative path is provided, it will be resolved relative to the ZModel schema. +- `output` + + Optional string. Specifies the output directory for the generated TypeScript code. If a relative path is provided, it will be resolved relative to the ZModel schema. Defaults to the same directory as the ZModel schema. + +- `lite` + + Optional boolean. If set to `true`, the plugin will generate a lite version of schema file "schema-lite.ts" with attributes removed, along side with the full schema. The lite schema is suited to be used in frontend code like with the `@zenstackhq/tanstack-query` library. Defaults to `false`. + +- `liteOnly` + + Optional boolean. If set to `true`, the plugin will only generate the lite version of schema file "schema-lite.ts" with attributes removed, and skip generating the full schema. The lite schema is suited to be used in frontend code like with the `@zenstackhq/tanstack-query` library. Defaults to `false`. + +- `importWithFileExtension` + + Optional string. Used to control the `import` statements in the generated code. If set to a string value like ".js", the generated code will import from local modules with the specified file extension. This option is useful in ESM projects with certain TypeScript `moduleResolution` mode. ## Output diff --git a/versioned_docs/version-3.x/reference/zmodel/datasource.md b/versioned_docs/version-3.x/reference/zmodel/datasource.md index a62a782b..b59ef6c1 100644 --- a/versioned_docs/version-3.x/reference/zmodel/datasource.md +++ b/versioned_docs/version-3.x/reference/zmodel/datasource.md @@ -21,24 +21,30 @@ datasource NAME { - **`provider`**: - Name of database connector. Valid values: + Required. Name of database connector. Valid values: - sqlite - postgresql - **`url`**: - Database connection string. Either a plain string or an invocation of `env` function to fetch from an environment variable. For SQLite provider, the URL should be a file protocol, like `file:./dev.db`. For PostgreSQL provider, it should be a postgres connection string, like `postgresql://user:password@localhost:5432/dbname`. + Optional. Database connection string. Either a plain string or an invocation of `env` function to fetch from an environment variable. For SQLite provider, the URL should be a file protocol, like `file:./dev.db`. For PostgreSQL provider, it should be a postgres connection string, like `postgresql://user:password@localhost:5432/dbname`. The `url` option is only used by the migration engine to connect to the database. The ORM runtime doesn't rely on it. Instead, you provide the connection information when constructing an ORM client. +- **`directUrl`**: + + Optional. Connection URL for direct connection to the database. + + If you use a connection pooler URL in the url argument, Prisma CLI commands that require a direct connection to the database use the URL in the directUrl argument. This option is passed through to Prisma Migrate for database migrations. + - **`defaultSchema`**: - (PostgreSQL only) The default schema to use for models that don't have an explicit `@@schema` attribute. Defaults to "public". See [Working With PostgreSQL Schemas](../../recipe/postgres-multi-schema.md) for more details. + Optional. (PostgreSQL only) The default schema to use for models that don't have an explicit `@@schema` attribute. Defaults to "public". See [Working With PostgreSQL Schemas](../../recipe/postgres-multi-schema.md) for more details. - **`schemas`**: - (PostgreSQL only) List of schemas to use. If specified, you can use the `@@schema` attribute to specify the schema name for a model. See [Working With PostgreSQL Schemas](../../recipe/postgres-multi-schema.md) for more details. + Optional. (PostgreSQL only) List of schemas to use. If specified, you can use the `@@schema` attribute to specify the schema name for a model. See [Working With PostgreSQL Schemas](../../recipe/postgres-multi-schema.md) for more details. ## Example From 6822a0d003d7f49a03a1df6f5597cd7f007a23c3 Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Mon, 17 Nov 2025 22:30:27 -0800 Subject: [PATCH 2/2] update CLI reference --- versioned_docs/version-3.x/reference/cli.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/versioned_docs/version-3.x/reference/cli.md b/versioned_docs/version-3.x/reference/cli.md index bab698f6..6c68a5e0 100644 --- a/versioned_docs/version-3.x/reference/cli.md +++ b/versioned_docs/version-3.x/reference/cli.md @@ -245,6 +245,21 @@ Options: -h, --help display help for command ``` +### format + +Format a ZModel schema file. + +```bash +Usage: zen format [options] + +Format a ZModel schema file. + +Options: + --schema schema file (with extension .zmodel). Defaults to "zenstack/schema.zmodel" unless + specified in package.json. + -h, --help display help for command +``` + ## Overriding Default Options ### Default Schema Location