Skip to content

Commit 02d47dd

Browse files
authored
Merge pull request #224 from zenstackhq/dev
merge dev to main (v3.0.0-beta.1)
2 parents 74bbc28 + 4821aec commit 02d47dd

File tree

45 files changed

+914
-451
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+914
-451
lines changed

.github/workflows/build-test.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ on:
66
- main
77
- dev
88

9+
env:
10+
TELEMETRY_TRACKING_TOKEN: ${{ secrets.TELEMETRY_TRACKING_TOKEN }}
11+
DO_NOT_TRACK: '1'
12+
913
permissions:
1014
contents: read
1115

README.md

Lines changed: 7 additions & 332 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,11 @@ ZenStack is a TypeScript database toolkit for developing full-stack or backend N
2929

3030
- A modern schema-first ORM that's compatible with [Prisma](https://github.com/prisma/prisma)'s schema and API
3131
- Versatile data access APIs: high-level (Prisma-style) ORM queries + low-level ([Kysely](https://github.com/kysely-org/kysely)) query builder
32-
- Built-in access control and data validation
32+
- Built-in access control and data validation (coming soon)
3333
- Advanced data modeling patterns like [polymorphism](https://zenstack.dev/blog/polymorphism)
3434
- Designed for extensibility and flexibility: plugins, life-cycle hooks, etc.
35-
- Automatic CRUD web APIs with adapters for popular frameworks
36-
- Automatic [TanStack Query](https://github.com/TanStack/query) hooks for easy CRUD from the frontend
35+
- Automatic CRUD web APIs with adapters for popular frameworks (coming soon)
36+
- Automatic [TanStack Query](https://github.com/TanStack/query) hooks for easy CRUD from the frontend (coming soon)
3737

3838
# What's new with V3
3939

@@ -47,9 +47,9 @@ Even without using advanced features, ZenStack offers the following benefits as
4747
2. More TypeScript type inference, less code generation.
4848
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).
4949

50-
> 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.
50+
> 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](https://zenstack.dev/docs/3.x/orm/migration) for more details.
5151
52-
# Get started
52+
# Quick start
5353

5454
> You can also check the [blog sample](./samples/blog) for a complete example.
5555
@@ -82,331 +82,6 @@ npm install @zenstackhq/runtime@next
8282

8383
Then create a `zenstack` folder and a `schema.zmodel` file in it.
8484

85-
## Writing ZModel schema
85+
# Documentation
8686

87-
ZenStack uses a DSL named ZModel to model different aspects of database:
88-
89-
- Tables and fields
90-
- Validation rules (coming soon)
91-
- Access control policies (coming soon)
92-
- ...
93-
94-
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.
95-
96-
## Installing a database driver
97-
98-
ZenStack doesn't bundle any database drivers. You need to install by yourself based on the database provider you use.
99-
100-
> 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.
101-
102-
For SQLite:
103-
104-
```bash
105-
npm install better-sqlite3
106-
npm install -D @types/better-sqlite3
107-
```
108-
109-
For Postgres:
110-
111-
```bash
112-
npm install pg
113-
npm install -D @types/pg
114-
```
115-
116-
## Pushing schema to the database
117-
118-
Run the following command to sync schema to the database for local development:
119-
120-
```bash
121-
npx zen db push
122-
```
123-
124-
> Under the hood, the command uses `prisma db push` to do the job.
125-
126-
See [database migration](#database-migration) for how to use migration to manage schema changes for production.
127-
128-
## Compiling ZModel schema
129-
130-
ZModel needs to be compiled to TypeScript before being used to create a database db. Simply run the following command:
131-
132-
```bash
133-
npx zen generate
134-
```
135-
136-
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.
137-
138-
## Creating ZenStack client
139-
140-
Now you can use the compiled TypeScript schema to instantiate a database db.
141-
142-
### SQLite
143-
144-
```ts
145-
import { ZenStackClient } from '@zenstackhq/runtime';
146-
import { schema } from './zenstack/schema';
147-
import SQLite from 'better-sqlite3';
148-
import { SqliteDialect } from 'kysely';
149-
150-
const db = new ZenStackClient(schema, {
151-
dialect: new SqliteDialect({ database: new SQLite('./dev.db') }),
152-
});
153-
```
154-
155-
### Postgres
156-
157-
```ts
158-
import { ZenStackClient } from '@zenstackhq/runtime';
159-
import { schema } from './zenstack/schema';
160-
import { PostgresDialect } from 'kysely';
161-
import { Pool } from 'pg';
162-
163-
const db = new ZenStackClient(schema, {
164-
dialect: new PostgresDialect({
165-
pool: new Pool({ connectionString: process.env.DATABASE_URL }),
166-
}),
167-
});
168-
```
169-
170-
## Using `ZenStackClient`
171-
172-
### ORM API
173-
174-
`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.
175-
176-
A few quick examples:
177-
178-
```ts
179-
const user = await db.user.create({
180-
data: {
181-
name: 'Alex',
182-
183-
posts: { create: { title: 'Hello world' } },
184-
},
185-
});
186-
187-
const userWithPosts = await db.user.findUnique({
188-
where: { id: user.id },
189-
include: { posts: true },
190-
});
191-
192-
const groupedPosts = await db.post.groupBy({
193-
by: 'published',
194-
_count: true,
195-
});
196-
```
197-
198-
Under the hood, all ORM queries are transformed into Kysely queries for execution.
199-
200-
### Query builder API
201-
202-
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.
203-
204-
Please check [Kysely documentation](https://kysely.dev/docs/intro) for more details. Here're a few quick examples:
205-
206-
```ts
207-
await db.$qb
208-
.selectFrom('User')
209-
.leftJoin('Post', 'Post.authorId', 'User.id')
210-
.select(['User.id', 'User.email', 'Post.title'])
211-
.execute();
212-
```
213-
214-
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.
215-
216-
```ts
217-
await db.user.findMany({
218-
where: {
219-
age: { gt: 18 },
220-
// "eb" is a Kysely expression builder
221-
$expr: (eb) => eb('email', 'like', '%@zenstack.dev'),
222-
},
223-
});
224-
```
225-
226-
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.
227-
228-
### Computed fields
229-
230-
ZenStack v3 allows you to define database-evaluated computed fields with the following two steps:
231-
232-
1. Declare it in ZModel
233-
234-
```prisma
235-
model User {
236-
...
237-
/// number of posts owned by the user
238-
postCount Int @computed
239-
}
240-
```
241-
242-
2. Provide its implementation using query builder when constructing `ZenStackClient`
243-
244-
```ts
245-
const db = new ZenStackClient(schema, {
246-
...
247-
computedFields: {
248-
User: {
249-
postCount: (eb) =>
250-
eb
251-
.selectFrom('Post')
252-
.whereRef('Post.authorId', '=', 'id')
253-
.select(({ fn }) =>
254-
fn.countAll<number>().as('postCount')
255-
),
256-
},
257-
},
258-
});
259-
```
260-
261-
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.
262-
263-
### Polymorphic models
264-
265-
_Coming soon..._
266-
267-
### Access policies
268-
269-
_Coming soon..._
270-
271-
### Validation rules
272-
273-
_Coming soon..._
274-
275-
### Custom procedures
276-
277-
_Coming soon..._
278-
279-
### Runtime plugins
280-
281-
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.
282-
283-
You can use a plugin to achieve the following goals:
284-
285-
#### 1. ORM query interception
286-
287-
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).
288-
289-
```ts
290-
db.$use({
291-
id: 'cost-logger',
292-
onQuery: async ({ model, operation, args, proceed }) => {
293-
const start = Date.now();
294-
const result = await proceed(args);
295-
console.log(`[cost] ${model} ${operation} took ${Date.now() - start}ms`);
296-
return result;
297-
},
298-
});
299-
```
300-
301-
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.
302-
303-
#### 2. Kysely query interception
304-
305-
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.
306-
307-
Kysely query interception works against the low-level Kysely `OperationNode` structures. It's harder to implement but can guarantee intercepting all CRUD operations.
308-
309-
```ts
310-
db.$use({
311-
id: 'insert-interception-plugin',
312-
onKyselyQuery({query, proceed}) {
313-
if (query.kind === 'InsertQueryNode') {
314-
query = sanitizeInsertData(query);
315-
}
316-
return proceed(query);
317-
},
318-
});
319-
320-
function sanitizeInsertData(query: InsertQueryNode) {
321-
...
322-
}
323-
```
324-
325-
#### 3. Entity mutation interception
326-
327-
Another popular interception use case is, instead of intercepting calls, "listening on" entity changes.
328-
329-
```ts
330-
db.$use({
331-
id: 'mutation-hook-plugin',
332-
onEntityMutation: {
333-
beforeEntityMutation({ model, action }) {
334-
console.log(`Before ${model} ${action}`);
335-
},
336-
337-
afterEntityMutation({ model, action }) {
338-
console.log(`After ${model} ${action}`);
339-
},
340-
},
341-
});
342-
```
343-
344-
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.
345-
346-
```ts
347-
db.$use({
348-
id: 'mutation-hook-plugin',
349-
onEntityMutation: {
350-
mutationInterceptionFilter: ({ model }) => {
351-
return {
352-
intercept: model === 'User',
353-
// load entities affected before the mutation (defaults to false)
354-
loadBeforeMutationEntities: true,
355-
// load entities affected after the mutation (defaults to false)
356-
loadAfterMutationEntities: true,
357-
};
358-
},
359-
360-
beforeEntityMutation({ model, action, entities }) {
361-
console.log(`Before ${model} ${action}: ${entities}`);
362-
},
363-
364-
afterEntityMutation({ model, action, afterMutationEntities }) {
365-
console.log(`After ${model} ${action}: ${afterMutationEntities}`);
366-
},
367-
},
368-
});
369-
```
370-
371-
# Other guides
372-
373-
## Database migration
374-
375-
ZenStack v3 delegates database schema migration to Prisma. The CLI provides Prisma CLI wrappers for managing migrations.
376-
377-
- Sync schema to dev database and create a migration record:
378-
379-
```bash
380-
npx zen migrate dev
381-
```
382-
383-
- Deploy new migrations:
384-
385-
```bash
386-
npx zen migrate deploy
387-
```
388-
389-
- Reset dev database
390-
391-
```bash
392-
npx zen migrate reset
393-
```
394-
395-
See [Prisma Migrate](https://www.prisma.io/docs/orm/prisma-migrate) documentation for more details.
396-
397-
## Migrating Prisma projects
398-
399-
1. Install "@zenstackhq/cli@next" and "@zenstackhq/runtime@next" packages
400-
1. Remove "@prisma/client" dependency
401-
1. Install "better-sqlite3" or "pg" based on database type
402-
1. Move "schema.prisma" to "zenstack" folder and rename it to "schema.zmodel"
403-
1. Run `npx zen generate`
404-
1. Replace `new PrismaClient()` with `new ZenStackClient(schema, { ... })`
405-
406-
# Limitations
407-
408-
1. Only SQLite (better-sqlite3) and Postgres (pg) database providers are supported for now.
409-
1. Prisma client extensions are not supported.
410-
1. Prisma custom generators are not supported (may add support in the future).
411-
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.
412-
1. Raw SQL query APIs (`$queryRaw`, `$executeRaw`) are not supported.
87+
Please visit the [doc site](https://zenstack.dev/docs/3.x/) for detailed documentation.

TODO.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
- [x] init
88
- [x] validate
99
- [ ] format
10+
- [ ] repl
1011
- [x] plugin mechanism
1112
- [x] built-in plugins
1213
- [x] typescript
@@ -82,7 +83,6 @@
8283
- [x] Error system
8384
- [x] Custom table name
8485
- [x] Custom field name
85-
- [ ] Strict undefined checks
8686
- [ ] DbNull vs JsonNull
8787
- [ ] Migrate to tsdown
8888
- [ ] Benchmark

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "zenstack-v3",
3-
"version": "3.0.0-alpha.33",
3+
"version": "3.0.0-beta.1",
44
"description": "ZenStack",
55
"packageManager": "[email protected]",
66
"scripts": {

0 commit comments

Comments
 (0)