-
-
Notifications
You must be signed in to change notification settings - Fork 38
doc: custom procedures #548
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
3a23d90
doc: custom procedures
ymc9 23518a4
Fix typo: change "URL encoded" to "URL-encoded" (#549)
Copilot 273f5d1
Add AvailableSince component to custom procedures documentation (#550)
Copilot e2ca7b3
Add anchor link to Custom Procedures section in TanStack Query docume…
Copilot 1472ce8
Update docs/service/api-handler/rest.md
ymc9 c0acfa3
Update docs/service/api-handler/rest.md
ymc9 f78d5af
Update docs/modeling/custom-proc.md
ymc9 8be18a8
Update docs/orm/custom-proc.md
ymc9 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| --- | ||
| sidebar_position: 12 | ||
| description: ZenStack custom procedures | ||
| --- | ||
|
|
||
| import ZModelVsPSL from '../_components/ZModelVsPSL'; | ||
| import PreviewFeature from '../_components/PreviewFeature'; | ||
| import AvailableSince from '../_components/AvailableSince'; | ||
|
|
||
| # Custom Procedure | ||
|
|
||
| <PreviewFeature name="Custom procedure" /> | ||
|
|
||
| <AvailableSince version="v3.2.0" /> | ||
|
|
||
| <ZModelVsPSL> | ||
| Custom procedure is a ZModel feature and doesn't exist in PSL. | ||
| </ZModelVsPSL> | ||
|
|
||
| Custom procedures are like database stored procedures that allow you to define reusable routines encapsulating complex logic. | ||
|
|
||
| Use the `procedure` keyword to define a custom procedure in ZModel. Here's an example for a query procedure: | ||
|
|
||
| ```zmodel title="schema.zmodel" | ||
| procedure getUserFeeds(userId: Int, limit: Int?) : Post[] | ||
| ``` | ||
|
|
||
| Mutation procedures (that write to the database) should be defined with `mutation procedure`: | ||
|
|
||
| ```zmodel title="schema.zmodel" | ||
| mutation procedure signUp(email: String) : User | ||
| ``` | ||
|
|
||
| You can use all types supported by ZModel to define procedure parameters and return types, including: | ||
|
|
||
| - Primitive types like `Int`, `String` | ||
| - Models | ||
| - Enums | ||
| - Custom types | ||
| - Array of the types above | ||
|
|
||
| Parameter types can be marked optional with a `?` suffix. If a procedure doesn't return anything, use `Void` as the return type. | ||
|
|
||
| Custom procedures are implemented with TypeScript when constructing the ORM client, and can be invoked via the ORM client in backend code. See [Custom Procedures](../orm/custom-proc.md) in the ORM part for more details. | ||
|
|
||
| They are also accessible via Query-as-a-Service (via [RPC-style](../service/api-handler/rpc.md#endpoints) or [RESTful-style](../service/api-handler/rest.md#calling-custom-procedures) API), plus consumable via Client SDKs like [TanStack Query Client](../service/client-sdk/tanstack-query/#custom-procedures). | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,5 @@ | ||
| --- | ||
| sidebar_position: 12 | ||
| sidebar_position: 13 | ||
| description: ZenStack plugins | ||
| --- | ||
|
|
||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,95 @@ | ||
| --- | ||
| sidebar_position: 13 | ||
| description: ORM custom procedures | ||
| --- | ||
|
|
||
| import PreviewFeature from '../_components/PreviewFeature'; | ||
| import AvailableSince from '../_components/AvailableSince'; | ||
|
|
||
| # Custom Procedures | ||
|
|
||
| <PreviewFeature name="Custom procedure" /> | ||
|
|
||
| <AvailableSince version="v3.2.0" /> | ||
|
|
||
| :::info | ||
| Please refer to the [Modeling](../modeling/custom-proc.md) part for how to define custom procedures in ZModel. | ||
| ::: | ||
|
|
||
| The ORM's CRUD API is very flexible and powerful, but in real-world applications you'll often find the need to encapsulate complex logic into more high-level and reusable operations. For example, in a collaborative app, after creating new users, you may want to automatically create a default workspace for them and assign some initial roles. | ||
|
|
||
| A conventional approach is to implement a `signUp` API route that orchestrates these steps. However, since the operation is still very much database-centric, it's more natural to have the encapsulation at the ORM level. This is where custom procedures come in. They are type-safe procedures defined in ZModel and implemented with TypeScript, and can be invoked via the ORM client just like the built-in CRUD methods. | ||
|
|
||
| ## Implementing custom procedures | ||
|
|
||
| Suppose you have the following custom procedures defined in ZModel: | ||
|
|
||
| ```zmodel title="schema.zmodel" | ||
| // get blog post feeds for a given user | ||
| procedure getUserFeeds(userId: Int, limit: Int?) : Post[] | ||
|
|
||
| // sign up a new user | ||
| mutation procedure signUp(email: String) : User | ||
| ``` | ||
|
|
||
| :::info | ||
| Query procedures and mutation procedures currently don't have any semantic differences at the ORM level. However, in the future they may behave differently, for example, when features like cached queries are introduced. | ||
| ::: | ||
|
|
||
| When you construct a `ZenStackClient`, you must provide an implementation for each procedure: | ||
|
|
||
| ```ts title="db.ts" | ||
| const db = new ZenStackClient({ | ||
| ... | ||
| procedures: { | ||
| getUserFeeds: ({ client, args }) => { | ||
| return client.post.findMany({ | ||
| where: { authorId: args.userId }, | ||
| orderBy: { createdAt: 'desc' }, | ||
| take: args.limit, | ||
| }); | ||
| }, | ||
|
|
||
| signUp: ({ client, args }) => { | ||
| return client.user.create({ | ||
| data: { | ||
| email: args.email, | ||
| memberships: { | ||
| create: { | ||
| role: 'OWNER', | ||
| workspace: { | ||
| create: { name: 'Default Workspace' }, | ||
| }, | ||
| }, | ||
| } | ||
| } | ||
| }); | ||
| }, | ||
| }, | ||
| }); | ||
| ``` | ||
|
|
||
| The implementation callbacks are provided with a context argument with the following fields: | ||
|
|
||
| - `client`: an instance of `ZenStackClient` used to invoke the procedure. | ||
| - `args`: an object that contains the procedure arguments. | ||
|
|
||
| At runtime, before passing the args to the callbacks, ZenStack verifies that they conform to the types defined in ZModel. You can implement additional validations in the implementation if needed. ZenStack doesn't verify the return values. It's your responsibility to ensure they match the declared return types. | ||
|
|
||
| ## Calling custom procedures | ||
|
|
||
| The custom procedures methods are grouped under the `$procs` property of the client instance. You must provide arguments as an object under the `args` key: | ||
|
|
||
| ```ts | ||
| const user = await db.$procs.signUp({ | ||
| args: { email: '[email protected]' } | ||
| }); | ||
|
|
||
| const feeds = await db.$procs.getUserFeeds({ | ||
| args: { userId: user.id, limit: 20 } | ||
| }); | ||
| ``` | ||
|
|
||
| ## Error handling | ||
|
|
||
| The `ZenStackClient` always throws an `ORMError` to the caller when an error occurs. To follow this protocol, custom procedure implementations should ensure other types of errors are caught and wrapped into `ORMError` and re-thrown. See [Error Handling](./errors.md) for more details. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,5 @@ | ||
| --- | ||
| sidebar_position: 14 | ||
| sidebar_position: 15 | ||
| description: Setup logging | ||
| --- | ||
|
|
||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| position: 13 | ||
| position: 14 | ||
| label: Plugins | ||
| collapsible: true | ||
| collapsed: true |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,5 @@ | ||
| --- | ||
| sidebar_position: 5 | ||
| sidebar_position: 6 | ||
| description: Plugin development guide | ||
| --- | ||
|
|
||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -50,7 +50,7 @@ The factory function accepts an options object with the following fields: | |
|
|
||
| - externalIdMapping | ||
|
|
||
| Optional. An `Record<string, string>` value that provides a mapping from model names (as defined in ZModel) to unique constraint name. This is useful when you for example want to expose natural keys in place of a surrogate keys: | ||
| Optional. An `Record<string, string>` value that provides a mapping from model names (as defined in ZModel) to the model's unique field name. This is useful when you for example want to expose natural keys in place of a surrogate keys: | ||
|
|
||
| ```ts | ||
| // Expose tags by unique name and not by ID, ie. /tag/blue intead of /tag/id | ||
|
|
@@ -98,6 +98,10 @@ model Comment { | |
| post Post @relation(fields: [postId], references: [id]) | ||
| postId Int | ||
| } | ||
|
|
||
| procedure getUserFeeds(userId: Int, limit: Int?) : Post[] | ||
|
|
||
| mutation procedure signUp(email: String) : User | ||
| ``` | ||
|
|
||
| ### Listing resources | ||
|
|
@@ -835,6 +839,47 @@ PATCH /:type/:id/relationships/:relationship | |
| } | ||
| ``` | ||
|
|
||
| ### Calling custom procedures | ||
|
|
||
| Custom procedures can be invoked with the special `$procs` resource type. | ||
|
|
||
| Use `GET` for query procedures and pass the arguments as a URL-encoded object in the `args` query parameter: | ||
|
|
||
| ```ts | ||
| GET /$procs/:procName?args=<encoded arguments> | ||
| ``` | ||
|
|
||
| Use `POST` for mutation procedures and pass the arguments in the request body: | ||
|
|
||
| ```ts | ||
| POST /$procs/:procName | ||
| { | ||
| "args": { ... } | ||
| } | ||
| ``` | ||
|
|
||
| #### Status codes | ||
|
|
||
| - 200: The request was successful and the response body contains the custom procedure's return value. | ||
| - 400: Invalid custom procedure name or arguments. | ||
| - 500: An error occurred while executing the custom procedure. | ||
|
|
||
| #### Examples | ||
|
|
||
| ```ts | ||
| // for arguments `{"userId":1,"limit":10}` | ||
| GET /$procs/getUserFeeds?args=%7B%22userId%22%3A1%2C%22limit%22%3A10%7D | ||
| ``` | ||
|
|
||
| ```ts | ||
| POST /$procs/signUp | ||
| { | ||
| "args": { | ||
| "email": "[email protected]" | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ## Compound ID Fields | ||
|
|
||
| ZModel allows a model to have compound ID fields, e.g.: | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.