Skip to content

Commit 4fd1902

Browse files
committed
doc: v2 to v3 migration guide
1 parent fbf6940 commit 4fd1902

File tree

7 files changed

+325
-19
lines changed

7 files changed

+325
-19
lines changed

src/pages/index.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,19 @@ function Header() {
3232
A TypeScript toolkit that enhances Prisma ORM with flexible Authorization and
3333
auto-generated, type-safe APIs/hooks, simplifying full-stack development
3434
</p>
35-
<div className={styles.buttons}>
35+
<div className="flex flex-wrap justify-center gap-4">
3636
<Link
3737
className="button button--secondary button--lg lg:text-2xl lg:px-8 lg:py-4"
3838
to="/docs/welcome"
3939
>
4040
Get Started →
4141
</Link>
42+
<a
43+
href="/v3"
44+
className="button button--outline button--lg border-solid lg:text-2xl lg:px-8 lg:py-4 hover:text-gray-200 dark:hover:text-gray-600"
45+
>
46+
Check V3 Beta
47+
</a>
4248
</div>
4349
</div>
4450
</div>

versioned_docs/version-3.x/migrate-prisma.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
description: How to migrate from a Prisma project to ZenStack v3
3-
sidebar_position: 10
3+
sidebar_position: 11
44
---
55

66
import PackageInstall from './_components/PackageInstall';
@@ -260,7 +260,7 @@ export const db = new ZenStackClient(schema, {
260260

261261
A key difference is that ZenStack's computed fields are evaluated on the database side, which much more efficient and flexible than client-side computation. Read more in the [Computed Fields](./orm/computed-fields.md) documentation.
262262

263-
## Feature Gap
263+
## Feature Gaps
264264

265265
Here's a list of Prisma features that are not supported in ZenStack v3:
266266

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
---
2+
description: How to migrate from a ZenStack v2 project to v3
3+
sidebar_position: 10
4+
---
5+
6+
import PackageInstall from './_components/PackageInstall';
7+
8+
# Migrating From ZenStack V2
9+
10+
## Overview
11+
12+
ZenStack v3 is a major rewrite of v2, with a focus on simplicity and flexibility. It replaced Prisma ORM with its own ORM component built above [Kysely](https://kysely.dev/), resulting in a much more light-weighted architecture and the level extensibility that we couldn't achieve in v2.
13+
14+
A few v3 design decisions should make an upgrade much less painful:
15+
16+
- The ZModel schema is essentially compatible with v2.
17+
- The ORM query API compatible with that of `PrismaClient`, thus compatible with v2.
18+
19+
However, given the architectural changes, some effort is required to adapt to the new system. This guide will help you migrate an existing ZenStack v2 project.
20+
21+
## Compatibility Check
22+
23+
Here are a few important items to verify before preparing your migration:
24+
25+
- Database support
26+
27+
V3 only supports PostgreSQL and SQLite databases for now. MySQL will be added later.
28+
29+
For PostgreSQL, only native the traditional TCP-based connection is supported. Newer HTTP-based protocols like supported by newer providers like Neon and Prisma PG are not supported yet, but will be in the future.
30+
31+
- Prisma feature gaps
32+
33+
A few Prisma ORM's features are not implemented or not planned. Please check the [Prisma Feature Gaps](./migrate-prisma.md#feature-gaps) for details.
34+
35+
- V2 feature gaps
36+
37+
A few ZenStack v2's features are not implemented. Some less popular features are planned to be dropped. Please check the [Feature Gaps](#feature-gaps) for details.
38+
39+
## Migrating Prisma
40+
41+
Since ZenStack v3 is not based on Prisma ORM anymore, the first thing to do is to replace Prisma dependencies with ZenStack and update code where `PrismaClient` is created. Please follow the [Prisma Migration Guide](./migrate-prisma.md) for detailed instructions.
42+
43+
## Migrating Access Policies
44+
45+
Since v3's ZModel language is backward compatible with v2, no much work is needed in ZModel files. One change necessary is that, if you use access policies (`@@allow`, `@@deny`, etc.), they are now in self-contained plugins, and you need to install the package separately, add a plugin declaration in ZModel, and install the plugin at runtime.
46+
47+
1. Install the package
48+
49+
<PackageInstall dependencies={["@zenstackhq/plugin-policy"]} />
50+
51+
2. Add a plugin declaration in ZModel
52+
53+
```zmodel title="schema.zmodel"
54+
55+
// adding the plugin makes attributes like `@@allow` and `@@deny` work
56+
plugin policy {
57+
provider = '@zenstackhq/plugin-policy'
58+
}
59+
60+
```
61+
62+
3. Install the plugin at runtime
63+
64+
```ts title="db.ts"
65+
import { PolicyPlugin } from '@zenstackhq/plugin-policy';
66+
67+
// ORM client without access control
68+
export const db = new ZenStackClient({ ... });
69+
70+
// ORM client with access control
71+
export const authDb = db.$use(new PolicyPlugin());
72+
```
73+
74+
When you need to query the database with a specific user identity, call the `$setAuth()` method on the ORM client (with the access policy plugin installed) to get a user-bound instance.
75+
76+
```ts
77+
async function processRequest(request: Request) {
78+
// get the validated user identity from your auth system
79+
const user = await getCurrentUser(request);
80+
81+
// create a user-bound ORM client
82+
const userDb = authDb.$setAuth(user);
83+
84+
// process the request with `userDb`
85+
...
86+
}
87+
```
88+
89+
The policy rules in ZModel are mostly backward compatible, except for a breaking change about post-update policies. In v3, post-update rules are expressed with its own "post-update" policy type and separate from regular "update" rules. Inside "post-update" rules, by default fields refer to the entity's "after-update" state, and you can use the `before()` function to refer to the "before-update" state. See [Post Update Rules](./orm/access-control/post-update.md) for more details.
90+
91+
Here's a quick example for how to migrate:
92+
93+
V2:
94+
95+
```zmodel
96+
model Post {
97+
...
98+
ownerId Int
99+
owner User @relation(fields: [ownerId], references: [id])
100+
101+
// update is not allowed to change the owner
102+
@@deny('update', future().ownerId != ownerId)
103+
}
104+
```
105+
106+
V3:
107+
```zmodel
108+
model Post {
109+
...
110+
ownerId Int
111+
owner User @relation(fields: [ownerId], references: [id])
112+
113+
// update is not allowed to change the owner
114+
@@deny('post-update', ownerId != before().ownerId)
115+
}
116+
```
117+
118+
## Migrating Server Adapters
119+
120+
Server adapters are mostly backward compatible. One small change needed is that, when creating a server adapter, it's now mandatory to explicitly pass in an API handler instance (RPC or RESTful). The API handlers are now created with the `schema` object as input. See [Server Adapters](./service/server-adapter.md) for more details.
121+
122+
Here's an example with Express.js:
123+
124+
```ts
125+
import { schema } from '~/zenstack/schema';
126+
import { authDb } from '~/db';
127+
128+
app.use(
129+
'/api/model',
130+
ZenStackMiddleware({
131+
// an API handler needs to be explicitly passed in
132+
apiHandler: new RPCApiHandler({ schema }),
133+
134+
// `getPrisma` is renamed to `getClient` in v3
135+
getClient: (request) => getClientForRequest(request),
136+
})
137+
);
138+
139+
function getClientForRequest(request: Request) {
140+
const user = getCurrentUser(request);
141+
return authDb.$setAuth(user);
142+
}
143+
```
144+
145+
## Migrating Client-Side Hooks
146+
147+
V3 brings a new implementation of [TanStack Query](https://tanstack.com/query) implementation that doesn't require code generation. Instead, TS types are fully inferred from the schema type at compile time, and the runtime logic is based on interpretation of the schema object. As a result, the new integration becomes a simple library that you call, and no plugin is involved.
148+
149+
To support such an architecture change. Query hooks are now grouped into an object that mirrors the API style of the ORM client. You need to adjust v2 code (that uses the flat `useFindMany[Model]` style hooks) into this new structure.
150+
151+
V2:
152+
```ts
153+
import { useFindManyUser } from '~/hooks';
154+
155+
export function MyComponent() {
156+
const { data } = useFindManyUser({ ... });
157+
...
158+
}
159+
```
160+
161+
V3:
162+
```ts
163+
import { useClientQueries } from '@zenstackhq/tanstack-query';
164+
import { schema } from '~/zenstack/schema';
165+
166+
export function MyComponent() {
167+
const client = useClientQueries(schema);
168+
const { data } = client.user.useFindMany({ ... });
169+
...
170+
}
171+
```
172+
173+
[SWR](https://swr.vercel.app/) support has been dropped due to low popularity.
174+
175+
## Migration Custom Plugins
176+
177+
V3 comes with a completely revised plugin system that offers great power and flexibility. You can check the concepts in the [Data Model Plugin](./modeling/plugin.md) and [ORM Plugin](./orm/plugins/) documentation.
178+
179+
The plugin development document is still WIP. This part of the migration guide will be added later when it's ready.
180+
181+
## Feature Gaps
182+
183+
This section lists v2 features that haven't been migrated to v3 yet, or that are planned to be removed in v3. Please feel free to share your thoughts about these decisions in [Discord](https://discord.gg/Ykhr738dUe) and we'll be happy to discuss them.
184+
185+
### Automatic password hashing
186+
187+
The `@password` attribute is removed in v3. We believe most people will use a more sophisticated authentication system than a simple id/password mechanism.
188+
189+
### Field-level access control
190+
191+
Not supported yet but will be added soon with some design changes.
192+
193+
### Zod integration
194+
195+
Not supported yet but will be added soon with some design changes.
196+
197+
### Checking permissions without querying the database
198+
199+
The [check()](/docs/guides/check-permission) feature is removed due to low popularity.
200+
201+
### tRPC integration
202+
203+
[TRPC](https://trpc.io/) is TypeScript inference heavy, and stacking it over ZenStack generates additional complexities and pressure to the compiler. We're evaluating the best way to integrate it in v3, and no concrete plan is in place yet. At least there's no plan to directly migrate the code-generation based approach in v2.
204+
205+
### OpenAPI spec generation
206+
207+
The [OpenAPI plugin](/docs/reference/plugins/openapi) is not migrated to v3 yet and will be added later with some redesign.
208+
209+
### SWR integration
210+
211+
The [SWR plugin](https://swr.vercel.app/) is removed due to low popularity.
212+
213+
## FAQ
214+
215+
### Is data migration needed?
216+
217+
No. From database schema point of view, v3 is fully backward compatible with v2.

versioned_docs/version-3.x/orm/errors.md

Lines changed: 89 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,101 @@ description: ORM Errors
55

66
# Errors
77

8-
The ORM uses the following error classes from `@zenstackhq/orm` to represent different types of failures:
8+
The ORM throws an `ORMError` in case of failures. The class has the following fields:
99

10-
## `InputValidationError`
10+
```ts
11+
/**
12+
* ZenStack ORM error.
13+
*/
14+
export class ORMError extends Error {
15+
/**
16+
* The name of the model that the error pertains to.
17+
*/
18+
model?: string;
1119

12-
This error is thrown when the argument passed to the ORM methods is invalid, e.g., missing required fields, or containing unknown fields. The `cause` property is set to the original error thrown during validation.
20+
/**
21+
* The error code given by the underlying database driver.
22+
*/
23+
dbErrorCode?: unknown;
1324

14-
If [input validation](../orm/validation.md) is used, this error is also thrown when the validation rules are violated.
25+
/**
26+
* The error message given by the underlying database driver.
27+
*/
28+
dbErrorMessage?: string;
1529

16-
## `NotFoundError`
30+
/**
31+
* The reason code for policy rejection. Only available when `reason` is `REJECTED_BY_POLICY`.
32+
*/
33+
rejectedByPolicyReason?: RejectedByPolicyReason;
1734

18-
This error is thrown when a requested record is not found in the database, e.g., when calling `findUniqueOrThrow`, `update`, etc.
35+
/**
36+
* The SQL query that was executed. Only available when `reason` is `DB_QUERY_ERROR`.
37+
*/
38+
sql?: string;
1939

20-
## `QueryError`
40+
/**
41+
* The parameters used in the SQL query. Only available when `reason` is `DB_QUERY_ERROR`.
42+
*/
43+
sqlParams?: readonly unknown[];
44+
}
2145

22-
This error is used to encapsulate all other errors thrown from the underlying database driver. The `cause` property is set to the original error thrown.
46+
/**
47+
* Reason code for ORM errors.
48+
*/
49+
export enum ORMErrorReason {
50+
/**
51+
* ORM client configuration error.
52+
*/
53+
CONFIG_ERROR = 'config-error',
2354

24-
## `RejectedByPolicyError`
55+
/**
56+
* Invalid input error.
57+
*/
58+
INVALID_INPUT = 'invalid-input',
2559

26-
This error is thrown when an operation is rejected by [access control policies](../orm/access-control/index.md).
60+
/**
61+
* The specified record was not found.
62+
*/
63+
NOT_FOUND = 'not-found',
64+
65+
/**
66+
* Operation is rejected by access policy.
67+
*/
68+
REJECTED_BY_POLICY = 'rejected-by-policy',
69+
70+
/**
71+
* Error was thrown by the underlying database driver.
72+
*/
73+
DB_QUERY_ERROR = 'db-query-error',
74+
75+
/**
76+
* The requested operation is not supported.
77+
*/
78+
NOT_SUPPORTED = 'not-supported',
79+
80+
/**
81+
* An internal error occurred.
82+
*/
83+
INTERNAL_ERROR = 'internal-error',
84+
}
85+
86+
/**
87+
* Reason code for policy rejection.
88+
*/
89+
export enum RejectedByPolicyReason {
90+
/**
91+
* Rejected because the operation is not allowed by policy.
92+
*/
93+
NO_ACCESS = 'no-access',
94+
95+
/**
96+
* Rejected because the result cannot be read back after mutation due to policy.
97+
*/
98+
CANNOT_READ_BACK = 'cannot-read-back',
99+
100+
/**
101+
* Other reasons.
102+
*/
103+
OTHER = 'other',
104+
}
105+
```

versioned_docs/version-3.x/roadmap.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ This is a list of major features that are planned for the future releases of Zen
1010
- [x] Data Validation
1111
- [x] Performance benchmark
1212
- [x] Query-as-a-Service (automatic CRUD API)
13-
- [ ] TanStack Query integration
13+
- [x] TanStack Query integration
1414
- [ ] Zod utility
1515
- [ ] Custom functions
1616
- [ ] Custom procedures
1717
- [ ] Field encryption
1818
- [ ] Soft delete
19-
- [ ] Audit trail
19+
- [ ] Audit trail

versioned_docs/version-3.x/service/api-handler/rest.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -949,6 +949,7 @@ An error response is an object containing the following fields:
949949
- title: `string`, error title
950950
- detail: `string`, detailed error message
951951
- reason: `string`, extra reason for the error
952+
- dbErrorCode: `unknown`, the error code given by the underlying database driver
952953

953954
### Example
954955

0 commit comments

Comments
 (0)