zenstack 0.3.19 → 0.3.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -246
- package/bundle/cli/index.js +181 -168
- package/bundle/language-server/main.js +67 -67
- package/package.json +5 -5
- package/src/cli/cli-util.ts +2 -0
- package/src/generator/react-hooks/index.ts +20 -5
- package/src/generator/service/index.ts +40 -4
- package/src/language-server/utils.ts +21 -0
- package/src/language-server/validator/datamodel-validator.ts +32 -0
- package/src/language-server/validator/expression-validator.ts +48 -0
- package/src/language-server/validator/zmodel-validator.ts +7 -0
- package/src/language-server/zmodel-linker.ts +14 -3
package/README.md
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<div align="center">
|
|
2
|
-
<img src="https://user-images.githubusercontent.com/104139426/
|
|
2
|
+
<img src="https://user-images.githubusercontent.com/104139426/204459273-35c65630-9faf-42bb-bd2c-07c1580a0772.png" style="max-width: 512px; width: 100%; height: auto; margin-bottom: 1rem;"
|
|
3
|
+
>
|
|
3
4
|
<div></div>
|
|
4
5
|
<a href="https://www.npmjs.com/package/zenstack">
|
|
5
6
|
<img src="https://img.shields.io/npm/v/zenstack">
|
|
@@ -16,260 +17,36 @@
|
|
|
16
17
|
</a>
|
|
17
18
|
</div>
|
|
18
19
|
|
|
19
|
-
##
|
|
20
|
+
## What it is
|
|
20
21
|
|
|
21
|
-
|
|
22
|
+
ZenStack is a toolkit for building secure CRUD apps with Next.js + Typescript. It lets you define data models, relations and access policies all in one place, and generates database schema, backend CRUD services and frontend React hooks for you automatically.
|
|
22
23
|
|
|
23
|
-
|
|
24
|
+
Our goal is to let you save time writing boilerplate code and focus on building real features!
|
|
24
25
|
|
|
25
|
-
|
|
26
|
+
## Links
|
|
26
27
|
|
|
27
|
-
|
|
28
|
+
- [Documentation](https://zenstack.dev)
|
|
29
|
+
- [Community chat](https://go.zenstack.dev/chat)
|
|
30
|
+
- [Twitter](https://twitter.com/zenstackhq)
|
|
31
|
+
- [Blog](https://dev.to/zenstack)
|
|
28
32
|
|
|
29
|
-
|
|
33
|
+
## Features
|
|
30
34
|
|
|
31
|
-
|
|
35
|
+
- Intuitive data & authorization modeling language
|
|
36
|
+
- Generating RESTful CRUD services and React hooks
|
|
37
|
+
- End-to-end type safety
|
|
38
|
+
- Support for all major relational databases
|
|
39
|
+
- Integration with authentication libraries (like [NextAuth](https://next-auth.js.org/) and [iron-session](https://www.npmjs.com/package/iron-session))
|
|
40
|
+
- [VSCode extension](https://marketplace.visualstudio.com/items?itemName=zenstack.zenstack) for model authoring
|
|
32
41
|
|
|
33
|
-
|
|
42
|
+
## Examples
|
|
34
43
|
|
|
35
|
-
|
|
36
|
-
model User {
|
|
37
|
-
id String @id @default(cuid())
|
|
38
|
-
email String @unique
|
|
44
|
+
Check out the [Collaborative Todo App](https://zenstack-todo.vercel.app/) for a running example. You can find the source code [here](https://github.com/zenstackhq/zenstack/tree/main/samples/todo).
|
|
39
45
|
|
|
40
|
-
|
|
41
|
-
posts Post[]
|
|
42
|
-
}
|
|
46
|
+
## Community
|
|
43
47
|
|
|
44
|
-
|
|
45
|
-
id String @id @default(cuid())
|
|
46
|
-
title String
|
|
47
|
-
content String
|
|
48
|
-
published Boolean @default(false)
|
|
48
|
+
Join our [discord server](https://go.zenstack.dev/chat) for chat and updates!
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
author User? @relation(fields: [authorId], references: [id])
|
|
52
|
-
authorId String?
|
|
50
|
+
## License
|
|
53
51
|
|
|
54
|
-
|
|
55
|
-
@@deny('all', auth() == null)
|
|
56
|
-
|
|
57
|
-
// allow CRUD by author
|
|
58
|
-
@@allow('all', author == auth())
|
|
59
|
-
}
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
- Auto-generated CRUD services and strongly typed React hooks
|
|
63
|
-
|
|
64
|
-
```jsx
|
|
65
|
-
// React example
|
|
66
|
-
|
|
67
|
-
const { find } = usePost();
|
|
68
|
-
const posts = get({ where: { public: true } });
|
|
69
|
-
// only posts owned by current login user are returned
|
|
70
|
-
return (
|
|
71
|
-
<>
|
|
72
|
-
{posts?.map((post) => (
|
|
73
|
-
<Post key={post.id} post={post} />
|
|
74
|
-
))}
|
|
75
|
-
</>
|
|
76
|
-
);
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
Since CRUD APIs are automatically generated with access policies injected, you can safely implement most of your business logic in your front-end code. Read operations never return data that's not supposed to be visible to the current user, and writes will be rejected if unauthorized.
|
|
80
|
-
|
|
81
|
-
## Getting started
|
|
82
|
-
|
|
83
|
-
[A step by step guide for getting started](docs/get-started/next-js.md)
|
|
84
|
-
|
|
85
|
-
[A complete sample with a collaborative todo app](https://github.com/zenstackhq/zenstack/tree/main/samples/todo)
|
|
86
|
-
|
|
87
|
-
## How does it work?
|
|
88
|
-
|
|
89
|
-
ZenStack has four essential responsibilities:
|
|
90
|
-
|
|
91
|
-
1. Modeling data and mapping the model to DB schema and programmable client library
|
|
92
|
-
1. Integrating with authentication
|
|
93
|
-
1. Generating CRUD APIs and enforcing data access policies
|
|
94
|
-
1. Providing type-safe React hooks
|
|
95
|
-
|
|
96
|
-
Let's briefly go through each of them in this section.
|
|
97
|
-
|
|
98
|
-
### Data modeling
|
|
99
|
-
|
|
100
|
-
ZenStack uses a schema language called `ZModel` to define data types and their relations. The `zenstack` CLI takes a schema file as input and generates database client code. Such client code allows you to program against database in server-side code in a fully typed way without writing any SQL. It also provides commands for synchronizing data models with DB schema, and generating "migration records" when your data model evolves.
|
|
101
|
-
|
|
102
|
-
Internally, ZenStack entirely relies on Prisma for ORM tasks. The ZModel language is a superset of Prisma's schema language. When `zenstack generate` is run, a Prisma schema named 'schema.prisma' is generated beside your ZModel file. You don't need to commit schema.prisma to source control. The recommended practice is to run `zenstack generate` during deployment, so Prisma schema is regenerated on the fly.
|
|
103
|
-
|
|
104
|
-
### Authentication
|
|
105
|
-
|
|
106
|
-
ZenStack is not an authentication library, but it gets involved in two ways.
|
|
107
|
-
|
|
108
|
-
Firstly, if you use any authentication method that involves persisting users' identity, you'll model the user's shape in ZModel. Some auth libraries, like [NextAuth](https://next-auth.js.org/), require user entity to include specific fields, and your model should fulfill such requirements. In addition, credential-based authentication requires validating user-provided credentials, and you should implement this using the database client generated by ZenStack.
|
|
109
|
-
|
|
110
|
-
To simplify the task, ZenStack automatically generates an adapter for NextAuth when it detects that the `next-auth` npm package is installed. Please refer to [the starter code](https://github.com/zenstackhq/nextjs-auth-starter/blob/main/pages/api/auth/%5B...nextauth%5D.ts) for how to use it. We'll keep adding integrations/samples for other auth libraries in the future.
|
|
111
|
-
|
|
112
|
-
Secondly, authentication is almost always connected to authorization. ZModel allows you to reference the current login user via `auth()` function in access policy expressions. Like,
|
|
113
|
-
|
|
114
|
-
```prisma
|
|
115
|
-
model Post {
|
|
116
|
-
author User @relation(fields: [authorId], references: [id])
|
|
117
|
-
...
|
|
118
|
-
|
|
119
|
-
@@deny('all', auth() == null)
|
|
120
|
-
@@allow('all', auth() == author)
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
The value returned by `auth()` is provided by your auth solution via the `getServerUser` hook function you provide when mounting ZenStack APIs. Check [this code](https://github.com/zenstackhq/nextjs-auth-starter/blob/main/pages/api/zenstack/%5B...path%5D.ts) for an example.
|
|
124
|
-
|
|
125
|
-
### Data access policy
|
|
126
|
-
|
|
127
|
-
The primary value that ZenStack adds over a traditional ORM is the built-in data access policy engine. This allows most business logic to be safely implemented in front-end code. Since ZenStack delegates database access to Prisma, it enforces access policies by analyzing queries sent to Prisma and injecting guarding conditions. For example, suppose we have a policy saying "a post can only be accessed by its author if it's not published", expressed in ZModel as:
|
|
128
|
-
|
|
129
|
-
```prisma
|
|
130
|
-
@@deny('all', auth() != author && !published)
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
When client code sends a query to list all `Post`s, ZenStack's generated code intercepts it and injects the `where` clause before passing it through to Prisma (conceptually):
|
|
134
|
-
|
|
135
|
-
```js
|
|
136
|
-
{
|
|
137
|
-
where: {
|
|
138
|
-
AND: [
|
|
139
|
-
{ ...userProvidedFilter },
|
|
140
|
-
{
|
|
141
|
-
// injected by ZenStack, "user" object is fetched from context
|
|
142
|
-
NOT: {
|
|
143
|
-
AND: [
|
|
144
|
-
{ author: { not: { id: user.id } } },
|
|
145
|
-
{ published: { not: true } },
|
|
146
|
-
],
|
|
147
|
-
},
|
|
148
|
-
},
|
|
149
|
-
];
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
Similar procedures are applied to write operations and more complex queries involving nested reads and writes. To ensure good performance, ZenStack generates conditions statically, so it doesn't need to introspect ZModel at runtime. The engine also makes the best effort to push down policy constraints to the database to avoid fetching data unnecessarily and discarding afterward.
|
|
155
|
-
|
|
156
|
-
Please **BEWARE** that policy checking is only applied when data access is done using the generated client-side hooks or, equivalently, the RESTful API. If you use `service.db` to access the database directly from server-side code, policies are bypassed, and you have to do all necessary checking by yourself. We've planned to add helper functions for "injecting" the policy checking on the server side in the future.
|
|
157
|
-
|
|
158
|
-
### Type-safe React hooks
|
|
159
|
-
|
|
160
|
-
Strongly-typed React hooks are generated for CRUD operations, saving the need to write boilerplate code.
|
|
161
|
-
|
|
162
|
-
Thanks to Prisma's power, ZenStack generates accurate Typescript types for your data models:
|
|
163
|
-
|
|
164
|
-
- The model itself
|
|
165
|
-
- Argument types for listing models, including filtering, sorting, pagination, and nested reads for related models
|
|
166
|
-
- Argument types for creating and updating models, including nested writes for related models
|
|
167
|
-
|
|
168
|
-
The cool thing is that the generated types are shared between client-side and server-side code, so no matter which side of code you're writing, you can always enjoy the pleasant IDE intellisense and typescript compiler's error checking.
|
|
169
|
-
|
|
170
|
-
## Programming with the generated code
|
|
171
|
-
|
|
172
|
-
### Client-side
|
|
173
|
-
|
|
174
|
-
The generated CRUD services should be mounted at `/api/zenstack` route. The following React hooks are generated for each data model:
|
|
175
|
-
|
|
176
|
-
- find: listing entities with filtering, ordering, pagination, and nested relations
|
|
177
|
-
|
|
178
|
-
```ts
|
|
179
|
-
const { find } = usePost();
|
|
180
|
-
// lists unpublished posts with their author's data
|
|
181
|
-
const posts = find({
|
|
182
|
-
where: { published: false },
|
|
183
|
-
include: { author: true },
|
|
184
|
-
orderBy: { updatedAt: 'desc' },
|
|
185
|
-
});
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
- get: fetching a single entity by id, with nested relations
|
|
189
|
-
|
|
190
|
-
```ts
|
|
191
|
-
const { get } = usePost();
|
|
192
|
-
// fetches a post with its author's data
|
|
193
|
-
const post = get(id, {
|
|
194
|
-
include: { author: true },
|
|
195
|
-
});
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
- create: creating a new entity, with the support for nested creation of related models
|
|
199
|
-
|
|
200
|
-
```ts
|
|
201
|
-
const { create } = usePost();
|
|
202
|
-
// creating a new post for current user with a nested comment
|
|
203
|
-
const post = await create({
|
|
204
|
-
data: {
|
|
205
|
-
title: 'My New Post',
|
|
206
|
-
author: {
|
|
207
|
-
connect: { id: session.user.id },
|
|
208
|
-
},
|
|
209
|
-
comments: {
|
|
210
|
-
create: [{ content: 'First comment' }],
|
|
211
|
-
},
|
|
212
|
-
},
|
|
213
|
-
});
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
- update: updating an entity, with the support for nested creation/update of related models
|
|
217
|
-
|
|
218
|
-
```ts
|
|
219
|
-
const { update } = usePost();
|
|
220
|
-
// updating a post's content and create a new comment
|
|
221
|
-
const post = await update(id, {
|
|
222
|
-
data: {
|
|
223
|
-
const: 'My post content',
|
|
224
|
-
comments: {
|
|
225
|
-
create: [{ content: 'A new comment' }],
|
|
226
|
-
},
|
|
227
|
-
},
|
|
228
|
-
});
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
- del: deleting an entity
|
|
232
|
-
|
|
233
|
-
```js
|
|
234
|
-
const { del } = usePost();
|
|
235
|
-
const post = await del(id);
|
|
236
|
-
```
|
|
237
|
-
|
|
238
|
-
Internally ZenStack generated code uses [SWR](https://swr.vercel.app/) to do data fetching so that you can enjoy its caching, polling, and automatic revalidation features.
|
|
239
|
-
|
|
240
|
-
### Server-side
|
|
241
|
-
|
|
242
|
-
If you need to do server-side coding, either through implementing an API endpoint or by using `getServerSideProps` for SSR, you can directly access the database client generated by Prisma:
|
|
243
|
-
|
|
244
|
-
```ts
|
|
245
|
-
import service from '@zenstackhq/runtime';
|
|
246
|
-
|
|
247
|
-
export const getServerSideProps: GetServerSideProps = async () => {
|
|
248
|
-
const posts = await service.db.post.findMany({
|
|
249
|
-
where: { published: true },
|
|
250
|
-
include: { author: true },
|
|
251
|
-
});
|
|
252
|
-
return {
|
|
253
|
-
props: { posts },
|
|
254
|
-
};
|
|
255
|
-
};
|
|
256
|
-
```
|
|
257
|
-
|
|
258
|
-
**Please 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.
|
|
259
|
-
|
|
260
|
-
## Learning more
|
|
261
|
-
|
|
262
|
-
### [Learning the ZModel language](/docs/get-started/learning-the-zmodel-language.md)
|
|
263
|
-
|
|
264
|
-
### [Evolving data model with migration](/docs/ref/evolving-data-model-with-migration.md)
|
|
265
|
-
|
|
266
|
-
### [Database hosting considerations](/docs/ref/database-hosting-considerations.md)
|
|
267
|
-
|
|
268
|
-
### [Setting up logging](/docs/ref/setup-logging.md)
|
|
269
|
-
|
|
270
|
-
### [Telemetry](/docs/ref/telemetry.md)
|
|
271
|
-
|
|
272
|
-
## Reach out to us for issues, feedback and ideas!
|
|
273
|
-
|
|
274
|
-
[Discord](https://go.zenstack.dev/chat) | [Twitter](https://twitter.com/zenstackhq) |
|
|
275
|
-
[Discussions](../discussions) | [Issues](../issues)
|
|
52
|
+
[MIT](LICENSE)
|