From 3b3dfab2e93aa66c6aaeff6615edafeceb9fb18a Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Sat, 30 Aug 2025 10:56:28 +0800 Subject: [PATCH 1/2] chore: update readme --- README.md | 337 +----------------------------------------------------- 1 file changed, 6 insertions(+), 331 deletions(-) diff --git a/README.md b/README.md index 1d51b715..fdf8fb1a 100644 --- a/README.md +++ b/README.md @@ -29,11 +29,11 @@ ZenStack is a TypeScript database toolkit for developing full-stack or backend N - A modern schema-first ORM that's compatible with [Prisma](https://github.com/prisma/prisma)'s schema and API - Versatile data access APIs: high-level (Prisma-style) ORM queries + low-level ([Kysely](https://github.com/kysely-org/kysely)) query builder -- Built-in access control and data validation +- Built-in access control and data validation (coming soon) - Advanced data modeling patterns like [polymorphism](https://zenstack.dev/blog/polymorphism) - Designed for extensibility and flexibility: plugins, life-cycle hooks, etc. -- Automatic CRUD web APIs with adapters for popular frameworks -- Automatic [TanStack Query](https://github.com/TanStack/query) hooks for easy CRUD from the frontend +- Automatic CRUD web APIs with adapters for popular frameworks (coming soon) +- Automatic [TanStack Query](https://github.com/TanStack/query) hooks for easy CRUD from the frontend (coming soon) # What's new with V3 @@ -49,7 +49,7 @@ Even without using advanced features, ZenStack offers the following benefits as > Although ZenStack v3's runtime doesn't depend on Prisma anymore (specifically, `@prisma/client`), it still relies on Prisma to handle database migration. See [database migration](#database-migration) for more details. -# Get started +# Quick start > You can also check the [blog sample](./samples/blog) for a complete example. @@ -82,331 +82,6 @@ npm install @zenstackhq/runtime@next Then create a `zenstack` folder and a `schema.zmodel` file in it. -## Writing ZModel schema +# Documentation -ZenStack uses a DSL named ZModel to model different aspects of database: - -- Tables and fields -- Validation rules (coming soon) -- Access control policies (coming soon) -- ... - -ZModel is a super set of [Prisma Schema Language](https://www.prisma.io/docs/orm/prisma-schema/overview), i.e., every valid Prisma schema is a valid ZModel. - -## Installing a database driver - -ZenStack doesn't bundle any database drivers. You need to install by yourself based on the database provider you use. - -> The project scaffolded by `npm create zenstack` is pre-configured to use SQLite. You only need to follow instructions here if you want to change it. - -For SQLite: - -```bash -npm install better-sqlite3 -npm install -D @types/better-sqlite3 -``` - -For Postgres: - -```bash -npm install pg -npm install -D @types/pg -``` - -## Pushing schema to the database - -Run the following command to sync schema to the database for local development: - -```bash -npx zen db push -``` - -> Under the hood, the command uses `prisma db push` to do the job. - -See [database migration](#database-migration) for how to use migration to manage schema changes for production. - -## Compiling ZModel schema - -ZModel needs to be compiled to TypeScript before being used to create a database db. Simply run the following command: - -```bash -npx zen generate -``` - -A `schema.ts` file will be created inside the `zenstack` folder. The file should be included as part of your source tree for compilation/bundling. You may choose to include or ignore it in source control (and generate on the fly during build). Just remember to rerun the "generate" command whenever you make changes to the ZModel schema. - -## Creating ZenStack client - -Now you can use the compiled TypeScript schema to instantiate a database db. - -### SQLite - -```ts -import { ZenStackClient } from '@zenstackhq/runtime'; -import { schema } from './zenstack/schema'; -import SQLite from 'better-sqlite3'; -import { SqliteDialect } from 'kysely'; - -const db = new ZenStackClient(schema, { - dialect: new SqliteDialect({ database: new SQLite('./dev.db') }), -}); -``` - -### Postgres - -```ts -import { ZenStackClient } from '@zenstackhq/runtime'; -import { schema } from './zenstack/schema'; -import { PostgresDialect } from 'kysely'; -import { Pool } from 'pg'; - -const db = new ZenStackClient(schema, { - dialect: new PostgresDialect({ - pool: new Pool({ connectionString: process.env.DATABASE_URL }), - }), -}); -``` - -## Using `ZenStackClient` - -### ORM API - -`ZenStackClient` offers the full set of CRUD APIs that `PrismaClient` has - `findMany`, `create`, `aggregate`, etc. See [prisma documentation](https://www.prisma.io/docs/orm/prisma-client/queries) for detailed guide. - -A few quick examples: - -```ts -const user = await db.user.create({ - data: { - name: 'Alex', - email: 'alex@zenstack.dev', - posts: { create: { title: 'Hello world' } }, - }, -}); - -const userWithPosts = await db.user.findUnique({ - where: { id: user.id }, - include: { posts: true }, -}); - -const groupedPosts = await db.post.groupBy({ - by: 'published', - _count: true, -}); -``` - -Under the hood, all ORM queries are transformed into Kysely queries for execution. - -### Query builder API - -ZenStack uses Kysely to handle database operations, and it also directly exposes Kysely's query builder. You can use it when your use case outgrows the ORM API's capabilities. The query builder API is fully typed, and its types are directly inferred from `schema.ts` so no extra set up is needed. - -Please check [Kysely documentation](https://kysely.dev/docs/intro) for more details. Here're a few quick examples: - -```ts -await db.$qb - .selectFrom('User') - .leftJoin('Post', 'Post.authorId', 'User.id') - .select(['User.id', 'User.email', 'Post.title']) - .execute(); -``` - -Query builder can also be "blended" into ORM API calls as a local escape hatch for building complex filter conditions. It allows for greater flexibility without forcing you to entirely resort to the query builder API. - -```ts -await db.user.findMany({ - where: { - age: { gt: 18 }, - // "eb" is a Kysely expression builder - $expr: (eb) => eb('email', 'like', '%@zenstack.dev'), - }, -}); -``` - -It provides a good solution to the long standing `whereRaw` [Prisma feature request](https://github.com/prisma/prisma/issues/5560). We may make similar extensions to the `select` and `orderBy` clauses in the future. - -### Computed fields - -ZenStack v3 allows you to define database-evaluated computed fields with the following two steps: - -1. Declare it in ZModel - - ```prisma - model User { - ... - /// number of posts owned by the user - postCount Int @computed - } - ``` - -2. Provide its implementation using query builder when constructing `ZenStackClient` - - ```ts - const db = new ZenStackClient(schema, { - ... - computedFields: { - User: { - postCount: (eb) => - eb - .selectFrom('Post') - .whereRef('Post.authorId', '=', 'id') - .select(({ fn }) => - fn.countAll().as('postCount') - ), - }, - }, - }); - ``` - -You can then use the computed field anywhere a regular field can be used, for field selection, filtering, sorting, etc. The field is fully evaluated at the database side so performance will be optimal. - -### Polymorphic models - -_Coming soon..._ - -### Access policies - -_Coming soon..._ - -### Validation rules - -_Coming soon..._ - -### Custom procedures - -_Coming soon..._ - -### Runtime plugins - -V3 introduces a new runtime plugin mechanism that allows you to tap into the ORM's query execution in various ways. A plugin implements the [RuntimePlugin](./packages/runtime/src/client/plugin.ts#L121) interface, and can be installed with the `ZenStackdb.$use` API. - -You can use a plugin to achieve the following goals: - -#### 1. ORM query interception - -ORM query interception allows you to intercept the high-level ORM API calls. The interceptor's configuration is compatible with Prisma's [query client extension](https://www.prisma.io/docs/orm/prisma-client/client-extensions/query). - -```ts -db.$use({ - id: 'cost-logger', - onQuery: async ({ model, operation, args, proceed }) => { - const start = Date.now(); - const result = await proceed(args); - console.log(`[cost] ${model} ${operation} took ${Date.now() - start}ms`); - return result; - }, -}); -``` - -Usually a plugin would call the `proceed` callback to trigger the execution of the original query, but you can choose to completely override the query behavior with custom logic. - -#### 2. Kysely query interception - -Kysely query interception allows you to intercept the low-level query builder API calls. Since ORM queries are transformed into Kysely queries before execution, they are automatically captured as well. - -Kysely query interception works against the low-level Kysely `OperationNode` structures. It's harder to implement but can guarantee intercepting all CRUD operations. - -```ts -db.$use({ - id: 'insert-interception-plugin', - onKyselyQuery({query, proceed}) { - if (query.kind === 'InsertQueryNode') { - query = sanitizeInsertData(query); - } - return proceed(query); - }, -}); - -function sanitizeInsertData(query: InsertQueryNode) { - ... -} -``` - -#### 3. Entity mutation interception - -Another popular interception use case is, instead of intercepting calls, "listening on" entity changes. - -```ts -db.$use({ - id: 'mutation-hook-plugin', - onEntityMutation: { - beforeEntityMutation({ model, action }) { - console.log(`Before ${model} ${action}`); - }, - - afterEntityMutation({ model, action }) { - console.log(`After ${model} ${action}`); - }, - }, -}); -``` - -You can provide an extra `mutationInterceptionFilter` to control what to intercept, and opt in for loading the affected entities before and/or after the mutation. - -```ts -db.$use({ - id: 'mutation-hook-plugin', - onEntityMutation: { - mutationInterceptionFilter: ({ model }) => { - return { - intercept: model === 'User', - // load entities affected before the mutation (defaults to false) - loadBeforeMutationEntities: true, - // load entities affected after the mutation (defaults to false) - loadAfterMutationEntities: true, - }; - }, - - beforeEntityMutation({ model, action, entities }) { - console.log(`Before ${model} ${action}: ${entities}`); - }, - - afterEntityMutation({ model, action, afterMutationEntities }) { - console.log(`After ${model} ${action}: ${afterMutationEntities}`); - }, - }, -}); -``` - -# Other guides - -## Database migration - -ZenStack v3 delegates database schema migration to Prisma. The CLI provides Prisma CLI wrappers for managing migrations. - -- Sync schema to dev database and create a migration record: - - ```bash - npx zen migrate dev - ``` - -- Deploy new migrations: - - ```bash - npx zen migrate deploy - ``` - -- Reset dev database - - ```bash - npx zen migrate reset - ``` - -See [Prisma Migrate](https://www.prisma.io/docs/orm/prisma-migrate) documentation for more details. - -## Migrating Prisma projects - -1. Install "@zenstackhq/cli@next" and "@zenstackhq/runtime@next" packages -1. Remove "@prisma/client" dependency -1. Install "better-sqlite3" or "pg" based on database type -1. Move "schema.prisma" to "zenstack" folder and rename it to "schema.zmodel" -1. Run `npx zen generate` -1. Replace `new PrismaClient()` with `new ZenStackClient(schema, { ... })` - -# Limitations - -1. Only SQLite (better-sqlite3) and Postgres (pg) database providers are supported for now. -1. Prisma client extensions are not supported. -1. Prisma custom generators are not supported (may add support in the future). -1. [Filtering on JSON fields](https://www.prisma.io/docs/orm/prisma-client/special-fields-and-types/working-with-json-fields#filter-on-a-json-field-advanced) is not supported yet. -1. Raw SQL query APIs (`$queryRaw`, `$executeRaw`) are not supported. +Please visit the [doc site](https://zenstack.dev/docs/3.x/) for detailed documentation. From 6c53a4ef5e615b7283e31806f6da85091c771d7d Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Sat, 30 Aug 2025 17:55:08 +0800 Subject: [PATCH 2/2] update --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fdf8fb1a..c63f2681 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ Even without using advanced features, ZenStack offers the following benefits as 2. More TypeScript type inference, less code generation. 3. Fully-typed query-builder API as a better escape hatch compared to Prisma's [raw queries](https://www.prisma.io/docs/orm/prisma-client/using-raw-sql/raw-queries) or [typed SQL](https://www.prisma.io/docs/orm/prisma-client/using-raw-sql/typedsql). -> Although ZenStack v3's runtime doesn't depend on Prisma anymore (specifically, `@prisma/client`), it still relies on Prisma to handle database migration. See [database migration](#database-migration) for more details. +> Although ZenStack v3's runtime doesn't depend on Prisma anymore (specifically, `@prisma/client`), it still relies on Prisma to handle database migration. See [database migration](<[#database-migration](https://zenstack.dev/docs/3.x/orm/migration)>) for more details. # Quick start