Skip to content

Commit 193582b

Browse files
committed
update
1 parent 6da8385 commit 193582b

File tree

9 files changed

+159
-48
lines changed

9 files changed

+159
-48
lines changed

src/components/StackBlitzGithub.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import GithubCodeBlock from './GithubCodeBlock';
44

55
interface StackBlitzGithubProps {
66
repoPath: string;
7-
openFile?: string;
7+
openFile?: string | string[];
88
codeFiles?: string[];
99
startScript?: string;
1010
}
@@ -15,14 +15,16 @@ const StackBlitzGithub: React.FC<StackBlitzGithubProps> = ({
1515
codeFiles: plainCodeFiles = undefined,
1616
startScript,
1717
}) => {
18+
const openFiles = Array.isArray(openFile) ? openFile : openFile ? openFile.split(',') : [];
19+
1820
const options = {
19-
openFile,
21+
openFile: openFiles ? openFiles.join(',') : undefined,
2022
view: 'editor',
2123
startScript,
2224
} as const;
2325

2426
if (!plainCodeFiles) {
25-
plainCodeFiles = [openFile];
27+
plainCodeFiles = [...openFiles];
2628
}
2729

2830
return (

versioned_docs/version-3.x/orm/09-validation.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@ Input validation is a ZModel feature and doesn't exist in Prisma.
1515
Input validation is only applied to the ORM APIs like `create`, `update`, etc. It's not enforced at the query builder level.
1616
:::
1717

18-
When defining a model field in ZModel, you specify its type, which provides a basic level of runtime validation - when an incompatible value is passed to such field during creation or update, the underlying database will reject the operation.
18+
When defining a model field in ZModel, you specify its type, which provides a basic level of runtime validation - when an incompatible value is passed to the field during creation or update, the ORM engine will reject the operation.
1919

20-
However, oftentimes you want to expression more fine-grained validation rules, such as field length, string format, or numeric range, etc. ZModel provides a set of built-in field-level validation attributes, like `@length`, `@email`, etc. for this purpose. See [Input Validation](../reference/zmodel/input-validation.md) for more details.
20+
However, oftentimes you want to express more fine-grained validation rules, such as field length, string format, or numeric range, etc. ZModel provides a set of built-in field-level validation attributes, like `@length`, `@email`, etc., for this purpose. See [Input Validation](../reference/zmodel/input-validation.md) for more details.
2121

22-
To support more complex rules, a powerful `@@validate` model-level attribute is provided to allow expression rules using multiple fields and logical operators. Inside the `@@validate` attribute, functions like `length()`, `isEmail()` etc. are available for building up expressions. See [Input Validation](../reference/zmodel/input-validation.md) for the full list of available functions.
22+
To support more complex rules, a powerful `@@validate` model-level attribute is provided to allow expression rules using multiple fields and logical operators. Inside the `@@validate` attribute, functions like `length()`, `isEmail()`, etc. are available for building up expressions. See [Input Validation](../reference/zmodel/input-validation.md) for the complete list of available functions.
2323

24-
Arguments of mutation operations are validated before sending to the database. When a validation rule is violated, a `InputValidationError` exception is thrown, containing details about the violations.
24+
Arguments of mutation operations are validated before sending them to the database. When a validation rule is violated, an `InputValidationError` exception is thrown, containing details about the violations.
2525

2626
Internally, ZenStack uses [Zod](https://zod.dev/) to perform validation. Most of the attributes and functions are 1:1 mapped to Zod APIs.
2727

versioned_docs/version-3.x/orm/access-control/01-write-policies.md

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ plugin policy {
1818

1919
## Rule Types
2020

21-
Policies can be defined as whitelist rule using the `@@allow` attribute or blacklist rule using `@@deny`.
21+
Policies can be defined as whitelist rules using the `@@allow` attribute or blacklist rules using `@@deny`.
2222

23-
Here's a quick example. Don't worry about the details yet. We'll cover the CRUD operations and rule expressions in details later.
23+
Here's a quick example. Don't worry about the details yet. We'll cover the CRUD operations and rule expressions later.
2424

2525
```zmodel
2626
model User {
@@ -55,8 +55,8 @@ model Post {
5555
You can write as many policy rules as you want for a model. The order of the rules doesn't matter. ZenStack determines whether a CRUD operation is allowed using the following logic:
5656

5757
1. If any `@@deny` rule evaluates to true, it's denied.
58-
1. If any `@@allow` rule evaluates to true, it's allowed.
59-
1. Otherwise, it's denied (secure by default).
58+
2. If any `@@allow` rule evaluates to true, it's allowed.
59+
3. Otherwise, it's denied (secure by default).
6060

6161
## Operation Types
6262

@@ -76,7 +76,7 @@ Policy rules are expressed in terms of what CRUD operations they govern.
7676

7777
- `post-update`
7878

79-
A special operation type to expression conditions that should hold **after** an entity is updated. See [Post-Update Policies](./post-update.md) for details.
79+
A special operation type to express conditions that should hold **after** an entity is updated. See [Post-Update Policies](./post-update.md) for details.
8080

8181
- `delete`
8282

@@ -95,16 +95,18 @@ ZModel supports an intuitive expression language that's very similar to JavaScri
9595
- Literals like strings, numbers, and booleans
9696
- Comparisons with `==`, `>`, etc.
9797
- Logical combinations with `&&`, `||`, etc.
98-
- References to current model's fields like `published`
98+
- References to the current model's fields, like `published`
9999

100-
They provide the basic building blocks for composing complex policy rules. Functions are a extensibility mechanism that allows encapsulating specific semantics (e.g., getting the current user). Function calls are expressions so they can be combined with other expressions using operators.
100+
They provide the basic building blocks for composing complex policy rules.
101+
102+
Functions are an extensibility mechanism that allows encapsulating specific semantics (e.g., getting the current user). Function calls are expressions, so they can be combined with other expressions using operators.
101103

102104
The following sections will cover some of the functions and expression types that are specifically designed for writing policy rules. See [Functions](../../reference/zmodel/07-function.md) and [Expression](../../reference/zmodel/08-expression.md) reference for more details. Refer to the [@zenstackhq/plugin-policy](../../reference/plugins/policy.md) documentation for functions available for writing policies.
103105

104106

105107
## Accessing Current User
106108

107-
The most common type of access control rules are those that concern the current user. The built-in `auth()` function is designed to access the current user. `auth()` returns an object, and its type is inferred from the ZModel schema with the following rules:
109+
The most common type of access control rules concerns the current user. The built-in `auth()` function is designed to access the current user. `auth()` returns an object, and its type is inferred from the ZModel schema with the following rules:
108110

109111
1. If there's a `model` or `type` annotated with the `@@auth` attribute, it'll be used as the type of `auth()`.
110112
2. Otherwise, if there's a `model` or `type` named "User" (case sensitive), it'll be used.
@@ -134,25 +136,25 @@ model Post {
134136

135137
So the big question becomes: Where does the value of `auth()` come from?
136138

137-
ZenStack isn't an authentication framework, so it doesn't know how to fetch the current login user. You are responsible for providing that piece of information. We'll cover it in the next part where we talk about access control's runtime aspect. For now, just assume `auth()` gives you the **validated** current user info.
139+
ZenStack isn't an authentication library, so it doesn't know how to fetch the current login user. You are responsible for providing that piece of information. We'll cover it in the next part, where we talk about the runtime aspect of access control. For now, assume `auth()` gives you the **validated** current user info.
138140

139-
If you don't provide the current user, `auth()` will return `null`, and you can use it to write rules for anonymous users like:
141+
If you don't provide the current user, `auth()` will return `null`, and you can use it to write rules for anonymous users, like:
140142

141143
```zmodel
142144
@@deny('all', auth() == null)
143145
```
144146

145147
## Accessing Relations
146148

147-
You can achieve a lot by writing policies using model's simple fields, however, real-world access control often involves relations. For example:
149+
You can achieve a lot by writing policies using a model's simple fields; however, real-world access control often involves relations. For example:
148150

149151
> A user can only create posts if his profile is verified, where "Profile" is a relation of "User".
150152
151153
ZenStack's policy really shines when it comes to how flexible it is in traversing relations.
152154

153155
### To-One Relations
154156

155-
Accessing to-one relation is straightforward, simply use dot notation use relation's field, and you can chain as deeply as you need:
157+
Accessing to-one relation is straightforward, simply use dot notation to use the relation's field, and you can chain as deeply as you need:
156158

157159
```zmodel
158160
model User {
@@ -204,9 +206,9 @@ You can nest collection predicate expressions to build deep to-many relation tra
204206

205207
## Special Notes About `create`
206208

207-
There're limitations with what relations can be accessed in `create` rules, because such rules are evaluated before the record is created, and at that time, relations are not accessible yet.
209+
There are limitations on what relations can be accessed in `create` rules, because such rules are evaluated before the record is created. At that time, relations are not accessible yet.
208210

209-
As a result, `create` rules can only access "owned" relations - those relations that have foreign keys defined in the model where the rule is defined. For example, the following is allowed:
211+
As a result, `create` rules can only access "owned" relations - those relations that have foreign keys defined in the model where the rule is defined. During create, if the foreign key fields are set, the relations are accessible. For example, the following is allowed:
210212

211213
```zmodel
212214
model Profile {
@@ -250,7 +252,7 @@ model Profile {
250252
}
251253
```
252254

253-
You can use the `check()` function to directly delegate the policy check to the related entity:
255+
You can use the `check()` function to delegate the policy check to the related entity directly:
254256

255257
```zmodel
256258
model User {
@@ -267,7 +269,7 @@ model Profile {
267269
}
268270
```
269271

270-
You can make it even more concise by omitting the second argument and infer it from the context:
272+
You can make it even more concise by omitting the second argument and inferring it from the context:
271273

272274
```zmodel
273275
model Profile {

versioned_docs/version-3.x/orm/access-control/02-query.md

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ After defining access control policies in ZModel, it's time to enjoy their benef
66

77
## Installing Runtime Plugin
88

9-
Similar to the schema side, access control's runtime aspect is encapsulated in the `@zenstackhq/plugin-policy` package too, as a Runtime Plugin (more about this topic [later](../plugins/index.md)). You should install it onto the raw ORM client to get a new client instance with access control enforcement.
9+
Similar to the schema side, access control's runtime aspect is encapsulated in the `@zenstackhq/plugin-policy` package too, as a Runtime Plugin (more about this topic [later](../plugins/index.md)). You should install it on the raw ORM client to get a new client instance with access control enforcement.
1010

1111
```ts
1212
import { ZenStackClient } from '@zenstackhq/runtime';
@@ -26,7 +26,7 @@ const authDb = db.$use(new PolicyPlugin());
2626

2727
As mentioned in the previous part, you can use the `auth()` function in policy rules to refer to the current authenticated user. At runtime, you should use the `$setAuth()` API to provide such information. ZenStack itself is not an authentication library, so you need to determine how to achieve it based on your authentication mechanism.
2828

29-
In a web application, the typical pattern is to inspect the incoming request, extract the user information from it and validate, and then call `$setAuth()` to get an ORM client bound to that user.
29+
In a web application, the typical pattern is to inspect the incoming request, extract and validate the user information from it, and then call `$setAuth()` to get an ORM client bound to that user.
3030

3131
```ts
3232
import { getSessionUser } from './auth'; // your auth helper
@@ -49,20 +49,20 @@ Use the `$auth` property to get the user info previously set by `$setAuth()`.
4949

5050
## Making Queries
5151

52-
Access control policies are effective to both the ORM API and the query-builder API. To understand its behavior, the simplest mental model is to think that rows not satisfying the policies "don't exist".
52+
Access control policies are effective for both the ORM API and the query-builder API. To understand its behavior, the simplest mental model is to think that rows not satisfying the policies "don't exist".
5353

5454
### ORM Queries
5555

56-
For the most part, the ORM queries behavior is very intuitive.
56+
For the most part, the ORM query behavior is very intuitive:
5757

58-
Read operations like `findMany`, `findUnique`, `count`, etc., only return/involve rows that meet the "read" policies.
58+
- Read operations like `findMany`, `findUnique`, `count`, etc., only return/involve rows that meet the "read" policies.
5959

60-
Mutation operations that affect multiple rows like `updateMany` and `deleteMany` only impact rows that meet the "update" or "delete" policies respectively.
60+
- Mutation operations that affect multiple rows, like `updateMany` and `deleteMany`, only impact rows that meet the "update" or "delete" policies respectively.
6161

62-
Mutation operations that affect a single, unique row like `update` and `delete` will throw an `NotFoundError` if the target row doesn't meet the "update" or "delete" policies respectively.
62+
- Mutation operations that affect a single, unique row, like `update` and `delete`, will throw an `NotFoundError` if the target row doesn't meet the "update" or "delete" policies respectively.
6363

6464
:::info
65-
Why `NotFoundError` instead of `RejectedByPolicyError`? Remember the rationale is rows that don't satisfy the policies "don't exist".
65+
Why `NotFoundError` instead of `RejectedByPolicyError`? Because the rationale is rows that don't satisfy the policies "don't exist".
6666
:::
6767

6868
There are some complications when "read" and "write" policies affect the same query. It's ubiquitous because most mutation APIs involve reading the post-mutation entity to return to the caller. When the mutation succeeds but the post-mutation entity cannot be read, a `RejectedByPolicyError` is thrown, even though the mutation is persisted.
@@ -78,31 +78,31 @@ await db.post.update({
7878
```
7979

8080
:::info
81-
Why throwing an error instead of returning `null`? Because it'll compromise type-safety. The `create`, `update`, and `delete` APIs don't have a nullable return type.
81+
Why throw an error instead of returning `null`? Because it'll compromise type-safety. The `create`, `update`, and `delete` APIs don't have a nullable return type.
8282
:::
8383

8484
### Query-Builder Queries
8585

86-
The low-level Kysely-query-builder API is also subject to access control enforcement. Its behavior is intuitive:
86+
The low-level Kysely query-builder API is also subject to access control enforcement. Its behavior is intuitive:
8787

8888
- Calling `$qb.selectFrom()` returns readable rows only.
8989
- When you call `$qb.insertInto()`, `RejectedByPolicyError` will be thrown if the inserted row doesn't satisfy the "create" policies. Similar for `update` and `delete`.
90-
- Calling `$qb.update()` and `$qb.delete()` only affects rows that satisfy the "update" and "delete" policies respectively.
90+
- Calling `$qb.update()` and `$qb.delete()` only affects rows that satisfy the "update" and "delete" policies, respectively.
9191
- When you join tables, the joined table will be filtered to readable rows only.
92-
- When you use sub-queries, the sub-query will be filtered to readable rows only.
92+
- When you use sub-queries, the sub-queries will be filtered to readable rows only.
9393

9494
## Limitations
9595

9696
Here are some **IMPORTANT LIMITATIONS** about access control enforcement:
9797

98-
1. Mutations caused by cascade deletes/updates and database triggers are completely internal to the database, so ZenStack cannot enforce access control on them.
98+
1. Mutations caused by cascade deletes/updates and database triggers are entirely internal to the database, so ZenStack cannot enforce access control on them.
9999
2. Raw SQL queries executed via `$executeRaw()` and `$queryRaw()` are not subject to access control enforcement.
100100
3. Similarly, raw queries made with query-builder API using the `sql` tag are not subject to access control enforcement.
101101

102102
## Samples
103103

104-
<StackBlitzGithub repoPath="zenstackhq/v3-doc-orm-policy" codeFiles={['zenstack/schema.zmodel', 'main.ts']} />
104+
<StackBlitzGithub repoPath="zenstackhq/v3-doc-orm-policy" openFile={['basic/zenstack/schema.zmodel', 'basic/main.ts']} startScript="basic" />
105105

106106
## Implementation Notes
107107

108-
ZenStack v3's ORM is built above Kysely. Regardless you use the ORM API or the query-builder one, queries are eventually transformed into Kysely's SQL AST, and then compiled down to SQL and sent to the database for execution. The access control enforcement is implemented by transforming the AST and injecting proper filters.
108+
ZenStack v3's ORM is built on top of Kysely. Regardless of whether you use the ORM API or the query-builder one, queries are eventually transformed into Kysely's SQL AST and then compiled down to SQL and sent to the database for execution. The access control enforcement is implemented by transforming the AST and injecting proper filters.

versioned_docs/version-3.x/orm/access-control/03-post-update.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
# Post-Update Rules
22

3-
Among the operation types, "update" is a special one because it has a "pre" state and "post" state. The "update" policies we've seen in the previous parts refer to the "pre" state, meaning that if your polices refer to the model's fields, the fields are evaluated to their values before the update happens.
3+
import StackBlitzGithub from '@site/src/components/StackBlitzGithub';
4+
5+
:::info
6+
7+
In ZenStack v2, post-update rules were implicitly defined with the "update" operation by using the `future()` function to refer to the post-update values. We found this approach to be unclean and error-prone. V3 made a breaking change to introduce a separate "post-update" operation.
8+
9+
:::
10+
11+
## Overview
12+
13+
Among the CRUD operations, "update" is a special one because it has a "pre" state and "post" state. The "update" policies we've seen in the previous parts refer to the "pre" state, meaning that if your polices refer to the model's fields, the fields are evaluated to their values before the update happens.
414

515
However, sometimes you want to express conditions that should hold after the update happens. For example, you may want to ensure that after an update, a post's `published` field cannot be set to true unless the current user is the author. Post-update policies are designed for such scenarios.
616

@@ -24,3 +34,8 @@ model Post {
2434
}
2535
```
2636

37+
When post-update policies are violated, a `RejectedByPolicyError` is thrown.
38+
39+
## Samples
40+
41+
<StackBlitzGithub repoPath="zenstackhq/v3-doc-orm-policy" openFile={['post-update/zenstack/schema.zmodel', 'post-update/main.ts']} startScript="post-update" />

0 commit comments

Comments
 (0)