You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardexpand all lines: website/docs/contributing.md
+1-1
Original file line number
Diff line number
Diff line change
@@ -11,4 +11,4 @@ Have you found bugs or just want to ask a question? Please open a [GitHub issue]
11
11
12
12
### Donations
13
13
14
-
If you find this project useful, please consider making a [donation](https://www.paypal.com/donate/?hosted_button_id=ES9JRPSC66XKW). This is absolutely not required, but is very much appreciated, since it will help to cover the time and resources required to maintain this project. Thank you!
14
+
If you find this project useful, please consider making a [donation](https://github.com/sponsors/TheEdoRan). This is absolutely not required, but is very much appreciated, since it will help to cover the time and resources required to maintain this project. Thank you!
description: Learn how to use both a basic and an authorization client at the same time in your project.
4
+
---
5
+
6
+
7
+
# Extending a base client
8
+
9
+
A common and recommended pattern with this library is to extend the base safe action client, to cover different use cases that you might want and/or need in your applicaton.
10
+
11
+
The most simple case that comes to mind is to define a base client for unauthenticated actions, and then extend it to create a client for authenticated actions, thanks to an authorization middleware:
This is a basic client, without any options. If you want to explore the full set of options, check out the [safe action client](/docs/safe-action-client) section.
46
+
This is a basic client, without any options or middleware functions. If you want to explore the full set of options, check out the [safe action client](/docs/safe-action-client) section.
45
47
46
48
### 2. Define a new action
47
49
48
-
This is how a safe action is created. Providing a validation input schema to the function, we're sure that data that comes in is type safe and validated.
49
-
The second argument of this function is an async function that receives the parsed input, and defines what happens on the server when the action is called from client. In short, this is your server code. It never runs on the client:
50
+
This is how a safe action is created. Providing a validation input schema to the function via `schema()`, we're sure that data that comes in is type safe and validated.
51
+
The `define()` method lets you define what happens on the server when the action is called from client, via an async function that receives the parsed input and context as arguments. In short, this is your _server code_. **It never runs on the client**:
50
52
51
53
```typescript title="src/app/login-action.ts"
52
54
"use server"; // don't forget to add this!
53
55
54
56
import { z } from"zod";
55
-
import { action } from"@/lib/safe-action";
57
+
import { actionClient } from"@/lib/safe-action";
56
58
57
59
// This schema is used to validate input from client.
`action` returns a new function that can be called from the client.
75
79
76
80
### 3. Import and execute the action
77
81
78
-
In this example, we're **directly** calling the Server Actions from a Client Component. The action is passed as a prop to the component, and we can infer its type by simply using `typeof`:
82
+
In this example, we're **directly** calling the Server Action from a Client Component:
Copy file name to clipboardexpand all lines: website/docs/introduction.md
+3-3
Original file line number
Diff line number
Diff line change
@@ -1,11 +1,11 @@
1
1
---
2
2
sidebar_position: 1
3
-
description: next-safe-action is a library that takes full advantage of the latest and greatest Next.js, React and TypeScript features, using validation libraries of your choice, to let you define typesafe Server Actions and execute them inside Client Components.
3
+
description: next-safe-action is a library that takes full advantage of the latest and greatest Next.js, React and TypeScript features, using validation libraries of your choice, to let you define type safe Server Actions and execute them inside React Components.
4
4
---
5
5
6
6
# Introduction
7
7
8
-
**next-safe-action** is a library that takes full advantage of the latest and greatest Next.js, React and TypeScript features, using validation libraries of your choice, to let you define **typesafe** Server Actions and execute them inside Client Components.
8
+
**next-safe-action** is a library that takes full advantage of the latest and greatest Next.js, React and TypeScript features, using validation libraries of your choice, to let you define **type safe** Server Actions and execute React Components.
9
9
10
10
## How does it work?
11
11
@@ -19,7 +19,7 @@ Your browser does not support the video tag.
19
19
## Features
20
20
- ✅ Pretty simple
21
21
- ✅ End-to-end type safety
22
-
- ✅ Context based clients (with middlewares)
22
+
- ✅ Powerful middleware system
23
23
- ✅ Input validation using multiple validation libraries
Copy file name to clipboardexpand all lines: website/docs/safe-action-client/index.md
+3-4
Original file line number
Diff line number
Diff line change
@@ -5,14 +5,13 @@ description: Safe action client is the instance that you can use to create types
5
5
6
6
# Safe action client
7
7
8
-
The safe action client instance is created by the `createSafeActionClient()` function. The instance is used to create safe actions, as you have already seen in previous sections of the documentation. You can create multiple clients too, for different purposes, as explained [in this section](/docs/safe-action-client/defining-multiple-clients).
8
+
The safe action client instance is created by the `createSafeActionClient()` function. The instance is used to create safe actions, as you have already seen in previous sections of the documentation. You can create multiple clients too, for different purposes, as explained [in this section](/docs/examples/extending-base-client).
9
9
10
10
You can also provide functions to the client, to customize the behavior for every action you then create with it. We will explore them in detail in the following sections.
11
11
12
12
Here's a reference of all the available optional functions:
|`middleware?`| Performs custom logic before action server code is executed, but after input from the client is validated. More information [here](/docs/safe-action-client/using-a-middleware). |
17
-
|`handleReturnedServerError?`| When an error occurs on the server after executing the action on the client, it lets you define custom logic to returns a custom `serverError` message instead of the default one. More information [here](/docs/safe-action-client/custom-server-error-handling#handlereturnedservererror). |
18
-
|`handleServerErrorLog?`| When an error occurs on the server after executing the action on the client, it lets you define custom logic to log the error on the server. By default the error is logged via `console.error`. More information [here](/docs/safe-action-client/custom-server-error-handling#handleservererrorlog). |
16
+
|`handleReturnedServerError?`| When an error occurs on the server after executing the action on the client, it lets you define custom logic to returns a custom `serverError` message instead of the default one. More information [here](/docs/safe-action-client/initialization-options#handlereturnedservererror). |
17
+
|`handleServerErrorLog?`| When an error occurs on the server after executing the action on the client, it lets you define custom logic to log the error on the server. By default the error is logged via `console.error`. More information [here](/docs/safe-action-client/initialization-options#handleservererrorlog). |
Copy file name to clipboardexpand all lines: website/docs/safe-action-client/initialization-options.md
+8-8
Original file line number
Diff line number
Diff line change
@@ -1,18 +1,18 @@
1
1
---
2
-
sidebar_position: 2
3
-
description: Learn how to customize server error handling.
2
+
sidebar_position: 1
3
+
description: You can initialize a safe action client with these options.
4
4
---
5
5
6
-
# Custom server error handling
6
+
# Initialization options
7
7
8
-
###`handleReturnedServerError?`
8
+
## `handleReturnedServerError?`
9
9
10
10
You can provide this optional function to the safe action client. It is used to customize the server error message returned to the client, if one occurs during action's server execution. This includes errors thrown by the action server code, and errors thrown by the middleware.
11
11
12
12
Here's a simple example, changing the message for every error thrown on the server:
13
13
14
14
```typescript title=src/lib/safe-action.ts
15
-
exportconstaction=createSafeActionClient({
15
+
exportconstactionClient=createSafeActionClient({
16
16
// Can also be an async function.
17
17
handleReturnedServerError(e) {
18
18
return"Oh no, something went wrong!";
@@ -29,7 +29,7 @@ import { DEFAULT_SERVER_ERROR } from "next-safe-action";
29
29
30
30
classMyCustomErrorextendsError {}
31
31
32
-
exportconstaction=createSafeActionClient({
32
+
exportconstactionClient=createSafeActionClient({
33
33
// Can also be an async function.
34
34
handleReturnedServerError(e) {
35
35
// In this case, we can use the 'MyCustomError` class to unmask errors
You can provide this optional function to the safe action client. This is used to define how errors should be logged when one occurs while the server is executing an action. This includes errors thrown by the action server code, and errors thrown by the middleware. Here you get as argument the **original error object**, not a message customized by `handleReturnedServerError`, if provided.
50
50
51
51
Here's a simple example, logging error to the console while also reporting it to an error handling system:
52
52
53
53
```typescript title=src/lib/safe-action.ts
54
-
exportconstaction=createSafeActionClient({
54
+
exportconstactionClient=createSafeActionClient({
55
55
// Can also be an async function.
56
56
handleServerErrorLog(e) {
57
57
// We can, for example, also send the error to a dedicated logging system.
description: List of methods of the safe action client.
4
+
---
5
+
6
+
# Instance methods
7
+
8
+
`createSafeActionClient` creates an instance of the safe action client, which has the following methods:
9
+
10
+
## `clone`
11
+
12
+
```typescript
13
+
actionClient.clone() =>newSafeActionClient()
14
+
```
15
+
16
+
`clone` returns a new instance of the safe action client with the same initialization options and middleware functions as the original one. It is used to extend a base client with additional middleware functions. If you don't use `clone` when creating a new client, the middleware function list of the original one will be mutated and extended with the new ones, which is not desirable.
`use` accepts a middleware function of type [`MiddlewareFn`](/docs/types#middlewarefn) as argument and returns a new instance of the safe action client with that middleware function added to the stack, that will be executed after the last one, if any. Check out how to `use` middleware in [the related section](/docs/usage/middleware) of the usage guide.
25
+
26
+
## `metadata`
27
+
28
+
```typescript
29
+
metadata(data: ActionMetadata) => { schema() }
30
+
```
31
+
32
+
`metadata` expects an object of type [`ActionMetadata`](/docs/types#actionmetadata) that lets you specify useful data about the safe action you're defining, and it returns the [`schema`](#schema) method, since metadata is action specific and not shared with other actions. As of now, the only data you can pass in is the `actionName`, but that could be extended in the future. You can then access it in the `middlewareFn` passed to [`use`](#use) and in [`serverCodeFn`](#servercodefn) passed to [`define`](#define).
`schema` accepts an input schema of type `Schema` (from TypeSchema), which is used to define the arguments that the safe action will receive, and returns the [`define`](#define) method, which allows you to define a new action using that input schema.
`define` is the final method in the list. It accepts a [`serverCodeFn`](#servercodefn) of type [`ServerCodeFn`](/docs/types#servercodefn) and returns a new safe action function of type [`SafeActionFn`](/docs/types#safeactionfn), which can be called from your components.
49
+
50
+
When the action is executed, all middleware functions in the chain will be called at runtime, in the order they were defined.
`serverCodeFn` is the async function that will be executed on the **server side** when the action is invoked. If input validation fails, or execution gets halted in a middleware function, the server code function will not be called.
|`data?`| Execution is successful. | What you returned in action's server code. |
14
14
| `validationErrors?` | Input data doesn't pass validation. | An object whose keys are the names of the fields that failed validation. Each key's value is either an `ErrorList` or a nested key with an `ErrorList` inside.<br />`ErrorList` is defined as: `{ errors?: string[] }`.<br />It follows the same structure as [Zod's `format` function](https://zod.dev/ERROR_HANDLING?id=formatting-errors).
15
-
|`serverError?`| An error occurs during action's server code execution. | A `string` that by default is "Something went wrong while executing the operation" for every server error that occurs, but this is [configurable](/docs/safe-action-client/custom-server-error-handling#handlereturnedservererror) when instantiating a new client. |
15
+
|`serverError?`| An error occurs during action's server code execution. | A `string` that by default is "Something went wrong while executing the operation" for every server error that occurs, but this is [configurable](/docs/safe-action-client/initialization-options#handlereturnedservererror) when instantiating a new client. |
Copy file name to clipboardexpand all lines: website/docs/usage/client-components/direct-execution.md
+2-2
Original file line number
Diff line number
Diff line change
@@ -5,7 +5,7 @@ description: You can execute safe actions by directrly calling them inside Clien
5
5
6
6
# 1. Direct execution
7
7
8
-
The first way to execute Server Actions inside Client Components is by passing the action from a Server Component to a Client Component and directly calling it in a function. This method is the most simple one, but in some cases it could be all you need, for example if you just need the action result inside an `onClick` or `onSubmit`handler, without overcomplicating things:
8
+
The first way to execute Server Actions inside Client Components is by importing it and directly calling it in a function. This method is the simplest one, but in some cases it could be all you need, for example if you just need the action result inside an `onClick` or `onSubmit`handlers, without overcomplicating things:
Every action you execute returns an object with the same structure. This is described in the [action result object](/docs/usage/action-result-object) section.
29
29
30
-
Explore a working example [here](https://github.com/TheEdoRan/next-safe-action/tree/main/packages/example-app/src/app).
30
+
Explore a working example [here](https://github.com/TheEdoRan/next-safe-action/tree/main/packages/example-app/src/app/(examples)/direct).
|`safeAction`|[SafeAction](/docs/types#safeaction)| This is the action that will be called when you use `execute` from hook's return object. |
64
+
|`safeActionFn`|[SafeActionFn](/docs/types#safeactionfn)| This is the action that will be called when you use `execute` from hook's return object. |
63
65
|`callbacks?`|[HookCallbacks](/docs/types#hookcallbacks)| Optional callbacks. More information about them [here](/docs/usage/client-components/hooks/callbacks). |
64
66
65
67
### `useAction` return object
@@ -73,4 +75,4 @@ As you can see, here we display a greet message after the action is performed, i
73
75
|`status`|[`HookActionStatus`](/docs/types#hookresult)| The action current status. |
74
76
|`reset`|`() => void`| You can programmatically reset the `result` object with this function. |
75
77
76
-
Explore a working example [here](https://github.com/TheEdoRan/next-safe-action/tree/main/packages/example-app/src/app/hook).
78
+
Explore a working example [here](https://github.com/TheEdoRan/next-safe-action/tree/main/packages/example-app/src/app/(examples)/hook).
|`safeAction`|[`SafeAction`](/docs/types#safeaction)| This is the action that will be called when you use `execute` from hook's return object. |
106
-
|`initialOptimisticData`|`Data` (return type of the `safeAction` you passed as first argument) | An initializer for the optimistic state. Usually this value comes from the parent Server Component. |
107
+
|`safeActionFn`|[`SafeActionFn`](/docs/types#safeactionfn)| This is the action that will be called when you use `execute` from hook's return object. |
108
+
|`initialOptimisticData`|`Data` (return type of the `safeActionFn` you passed as first argument) | An initializer for the optimistic state. Usually this value comes from the parent Server Component. |
107
109
|`reducer`|`(state: Data, input: InferIn<S>) => Data`| When you call the action via `execute`, this function determines how the optimistic update is performed. Basically, here you define what happens **immediately** after `execute` is called, and before the actual result comes back from the server. |
108
110
|`callbacks?`|[`HookCallbacks`](/docs/types#hookcallbacks)| Optional callbacks. More information about them [here](/docs/usage/client-components/hooks/callbacks). |
|`result`|[`HookResult`](/docs/types#hookresult)| When the action gets called via `execute`, this is the result object. |
119
121
|`status`|[`HookActionStatus`](/docs/types#hookresult)| The action current status. |
120
122
|`reset`|`() => void`| You can programmatically reset the `result` object with this function. |
121
-
|`optimisticData`|`Data` (return type of the `safeAction` you passed as first argument) | This is the data that gets updated immediately after `execute` is called, with the behavior you defined in the `reducer` function hook argument. The initial state is what you provided to the hook via `initialOptimisticData` argument. |
123
+
|`optimisticData`|`Data` (return type of the `safeActionFn` you passed as first argument) | This is the data that gets updated immediately after `execute` is called, with the behavior you defined in the `reducer` function hook argument. The initial state is what you provided to the hook via `initialOptimisticData` argument. |
122
124
123
-
Explore a working example [here](https://github.com/TheEdoRan/next-safe-action/tree/main/packages/example-app/src/app/optimistic-hook).
125
+
Explore a working example [here](https://github.com/TheEdoRan/next-safe-action/tree/main/packages/example-app/src/app/(examples)/optimistic-hook).
Copy file name to clipboardexpand all lines: website/docs/usage/client-components/index.md
+1-1
Original file line number
Diff line number
Diff line change
@@ -6,4 +6,4 @@ sidebar_label: Client Components
6
6
7
7
# Usage with Client Components
8
8
9
-
There are three different ways to execute Server Actions from Client Components. First one is the "direct way", the most simple one, but the least powerful too. The other two are by using `useAction` and `useOptimisticAction` hooks, which we will cover in the next sections.
9
+
There are three different ways to execute Server Actions from Client Components. First one is the "direct way", the simplest one, but the least powerful too. The other two are by using `useAction` and `useOptimisticAction` hooks, which we will cover in the next sections.
description: Learn how to use middleware functions in your actions.
4
+
---
5
+
6
+
# Middleware
7
+
8
+
next-safe-action, since version 7, ships with a composable and powerful middleware system, which allows you to create functions for almost every kind of use case you can imagine (authorization, logging, role based access, etc.). It works very similarly to the [tRPC implementation](https://trpc.io/docs/server/middlewares), with some minor differences.
9
+
10
+
Middleware functions are defined using [`use`](/docs/safe-action-client/instance-methods#use) method in your action clients, via the `middlewareFn` argument.
11
+
12
+
## Usage
13
+
14
+
You can chain multiple middleware functions, that will be executed in the order they were defined. You can also await the next middleware function(s) in the stack (useful, for instance, for logging), and then return the result of the execution. Chaining functions is very useful when you want to dynamically extend the context and/or halt execution based on your use case.
15
+
16
+
### Instance level middleware
17
+
18
+
Instance level is the right place when you want to share middleware behavior for all the actions you're going to define; for example when you need to log the result of actions execution, or verify if the user intending to execute the operation is authorized to do so, and if not, halt the execution at that point, by throwing an error.
19
+
20
+
Here we'll use a logging middleware in the base client and then extend it with an authorization middleware in `authActionClient`. We'll also define a safe action called `editProfile`, that will use `authActionClient` as its client. Note that the `handleReturnedServerError` function passed to the base client will also be used for `authActionClient`:
Note that `userId` in `ctx` comes from the `authActionClient` middleware, and console output comes from the logging middleware defined in the based client.
116
+
117
+
### Action level middleware
118
+
119
+
Server Action level is the right place for middleware checks that only specific actions need to make. For instance, when you want to restrict the execution to specific user roles.
120
+
121
+
In this example we'll use the same `authActionClient` defined above to define a `deleteUser` action that chains a middleware function which restricts the execution of this operation to just admins:
If a regular user tries to do the same, the execution will be stopped at the last middleware function, defined at the action level, that checks the user role. This is the console output in this case:
|`clientInput`|`ClientInput` (generic) | The raw input (not parsed) passed from client. |
189
+
|`ctx`|`Ctx` (generic) | Type safe context value from previous middleware function(s). |
190
+
|`metadata`|[`ActionMetadata`](/docs/types/#actionmetadata)| Metadata for the safe action execution. |
191
+
|`next`|`<const NC>(opts: { ctx: NC }): Promise<MiddlewareResult<NC>>`| Function that will execute the next function in the middleware stack or the server code function. It expects, as argument, the next `ctx` value for the next function in the chain. |
192
+
193
+
## `middlewareFn` return value
194
+
195
+
`middlewareFn` returns a Promise of a [`MiddlewareResult`](/docs/types#middlewareresult) object. It extends the result of a safe action with `success` property, and `parsedInput` and `ctx` optional properties. This is the exact return type of the `next` function, so you must always return it (or its result) to continue executing the middleware chain.
0 commit comments