use-server-action 1.1.3
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/LICENSE +21 -0
- package/README.md +87 -0
- package/dist/index.d.mts +32 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.js +129 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +102 -0
- package/dist/index.mjs.map +1 -0
- package/dist/server-action-HthxxP-Y.d.mts +39 -0
- package/dist/server-action-HthxxP-Y.d.ts +39 -0
- package/dist/server.d.mts +107 -0
- package/dist/server.d.ts +107 -0
- package/dist/server.js +135 -0
- package/dist/server.js.map +1 -0
- package/dist/server.mjs +97 -0
- package/dist/server.mjs.map +1 -0
- package/package.json +73 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Jack Humphries
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# use-server-action
|
|
2
|
+
|
|
3
|
+
A type-safe React hook and utilities for working with Next.js server actions.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install use-server-action
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
### 1. Create a server action
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
// app/actions.ts
|
|
17
|
+
"use server";
|
|
18
|
+
|
|
19
|
+
import { serverAction, success, error } from "use-server-action/server";
|
|
20
|
+
|
|
21
|
+
export const createUser = serverAction(async (name: string) => {
|
|
22
|
+
if (!name.trim()) {
|
|
23
|
+
throw new Error("Name is required");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const user = await db.user.create({ data: { name } });
|
|
27
|
+
return user;
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// Or handle errors manually for more control:
|
|
31
|
+
export const deleteUserAction = async (id: string) => {
|
|
32
|
+
try {
|
|
33
|
+
await db.user.delete({ where: { id } });
|
|
34
|
+
return success({ deleted: true });
|
|
35
|
+
} catch (e) {
|
|
36
|
+
return error("Failed to delete user", "DELETE_FAILED");
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### 2. Use in a client component
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
"use client";
|
|
45
|
+
|
|
46
|
+
import { useServerAction } from "use-server-action";
|
|
47
|
+
import { createUser } from "./actions";
|
|
48
|
+
|
|
49
|
+
export function CreateUserForm() {
|
|
50
|
+
const {
|
|
51
|
+
execute,
|
|
52
|
+
data,
|
|
53
|
+
error,
|
|
54
|
+
isPending,
|
|
55
|
+
isSuccess,
|
|
56
|
+
isError,
|
|
57
|
+
reset,
|
|
58
|
+
} = useServerAction({
|
|
59
|
+
action: createUser,
|
|
60
|
+
onSuccess: (user) => {
|
|
61
|
+
console.log("User created:", user);
|
|
62
|
+
},
|
|
63
|
+
onError: (message, code) => {
|
|
64
|
+
console.error(`Error [${code}]:`, message);
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<form action={(formData) => execute(formData.get("name") as string)}>
|
|
70
|
+
<input name="name" placeholder="Name" disabled={isPending} />
|
|
71
|
+
<button type="submit" disabled={isPending}>
|
|
72
|
+
{isPending ? "Creating..." : "Create User"}
|
|
73
|
+
</button>
|
|
74
|
+
{isError && <p className="error">{error}</p>}
|
|
75
|
+
{isSuccess && <p className="success">Created: {data?.name}</p>}
|
|
76
|
+
</form>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Documentation
|
|
82
|
+
|
|
83
|
+
You can view the documentation [here](https://use-server-action.jackh.sh)
|
|
84
|
+
|
|
85
|
+
## License
|
|
86
|
+
|
|
87
|
+
MIT
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { S as ServerActionResult } from './server-action-HthxxP-Y.mjs';
|
|
2
|
+
export { b as ServerActionError, c as ServerActionFn, a as ServerActionSuccess } from './server-action-HthxxP-Y.mjs';
|
|
3
|
+
|
|
4
|
+
type UseServerActionInput<P extends unknown[], T> = {
|
|
5
|
+
action: (...args: P) => Promise<ServerActionResult<T>>;
|
|
6
|
+
onSuccess?: (data: T) => void;
|
|
7
|
+
onError?: (message: string, code?: string) => void;
|
|
8
|
+
onSettled?: () => void;
|
|
9
|
+
};
|
|
10
|
+
type UseServerActionReturn<P extends unknown[], T> = {
|
|
11
|
+
/** Whether the action is currently executing */
|
|
12
|
+
isPending: boolean;
|
|
13
|
+
/** Error message from the last failed execution */
|
|
14
|
+
error: string | null;
|
|
15
|
+
/** Error code from the last failed execution */
|
|
16
|
+
errorCode: string | null;
|
|
17
|
+
/** Data from the last successful execution */
|
|
18
|
+
data: T | null;
|
|
19
|
+
/** Whether the last execution was successful */
|
|
20
|
+
isSuccess: boolean;
|
|
21
|
+
/** Whether the last execution was an error */
|
|
22
|
+
isError: boolean;
|
|
23
|
+
/** Execute the action (wrapped in React transition) */
|
|
24
|
+
execute: (...args: P) => void;
|
|
25
|
+
/** Execute the action and return the result (not wrapped in transition) */
|
|
26
|
+
executeAsync: (...args: P) => Promise<ServerActionResult<T>>;
|
|
27
|
+
/** Reset state to initial values */
|
|
28
|
+
reset: () => void;
|
|
29
|
+
};
|
|
30
|
+
declare function useServerAction<P extends unknown[], T>(input: UseServerActionInput<P, T>): UseServerActionReturn<P, T>;
|
|
31
|
+
|
|
32
|
+
export { ServerActionResult, type UseServerActionInput, type UseServerActionReturn, useServerAction };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { S as ServerActionResult } from './server-action-HthxxP-Y.js';
|
|
2
|
+
export { b as ServerActionError, c as ServerActionFn, a as ServerActionSuccess } from './server-action-HthxxP-Y.js';
|
|
3
|
+
|
|
4
|
+
type UseServerActionInput<P extends unknown[], T> = {
|
|
5
|
+
action: (...args: P) => Promise<ServerActionResult<T>>;
|
|
6
|
+
onSuccess?: (data: T) => void;
|
|
7
|
+
onError?: (message: string, code?: string) => void;
|
|
8
|
+
onSettled?: () => void;
|
|
9
|
+
};
|
|
10
|
+
type UseServerActionReturn<P extends unknown[], T> = {
|
|
11
|
+
/** Whether the action is currently executing */
|
|
12
|
+
isPending: boolean;
|
|
13
|
+
/** Error message from the last failed execution */
|
|
14
|
+
error: string | null;
|
|
15
|
+
/** Error code from the last failed execution */
|
|
16
|
+
errorCode: string | null;
|
|
17
|
+
/** Data from the last successful execution */
|
|
18
|
+
data: T | null;
|
|
19
|
+
/** Whether the last execution was successful */
|
|
20
|
+
isSuccess: boolean;
|
|
21
|
+
/** Whether the last execution was an error */
|
|
22
|
+
isError: boolean;
|
|
23
|
+
/** Execute the action (wrapped in React transition) */
|
|
24
|
+
execute: (...args: P) => void;
|
|
25
|
+
/** Execute the action and return the result (not wrapped in transition) */
|
|
26
|
+
executeAsync: (...args: P) => Promise<ServerActionResult<T>>;
|
|
27
|
+
/** Reset state to initial values */
|
|
28
|
+
reset: () => void;
|
|
29
|
+
};
|
|
30
|
+
declare function useServerAction<P extends unknown[], T>(input: UseServerActionInput<P, T>): UseServerActionReturn<P, T>;
|
|
31
|
+
|
|
32
|
+
export { ServerActionResult, type UseServerActionInput, type UseServerActionReturn, useServerAction };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var src_exports = {};
|
|
22
|
+
__export(src_exports, {
|
|
23
|
+
useServerAction: () => useServerAction
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(src_exports);
|
|
26
|
+
|
|
27
|
+
// src/use-server-action.ts
|
|
28
|
+
var import_react = require("react");
|
|
29
|
+
function useServerAction(input) {
|
|
30
|
+
const { action, onSuccess, onError, onSettled } = input;
|
|
31
|
+
const [isPending, startTransition] = (0, import_react.useTransition)();
|
|
32
|
+
const [error, setError] = (0, import_react.useState)(null);
|
|
33
|
+
const [errorCode, setErrorCode] = (0, import_react.useState)(null);
|
|
34
|
+
const [data, setData] = (0, import_react.useState)(null);
|
|
35
|
+
const [isSuccess, setIsSuccess] = (0, import_react.useState)(false);
|
|
36
|
+
const [isError, setIsError] = (0, import_react.useState)(false);
|
|
37
|
+
const isMountedRef = (0, import_react.useRef)(true);
|
|
38
|
+
const callbacksRef = (0, import_react.useRef)({ onSuccess, onError, onSettled });
|
|
39
|
+
callbacksRef.current = { onSuccess, onError, onSettled };
|
|
40
|
+
(0, import_react.useEffect)(() => {
|
|
41
|
+
isMountedRef.current = true;
|
|
42
|
+
return () => {
|
|
43
|
+
isMountedRef.current = false;
|
|
44
|
+
};
|
|
45
|
+
}, []);
|
|
46
|
+
const handleResult = (0, import_react.useCallback)((res) => {
|
|
47
|
+
if (!isMountedRef.current) return;
|
|
48
|
+
if (res.ok) {
|
|
49
|
+
setData(res.data);
|
|
50
|
+
setError(null);
|
|
51
|
+
setErrorCode(null);
|
|
52
|
+
setIsSuccess(true);
|
|
53
|
+
setIsError(false);
|
|
54
|
+
callbacksRef.current.onSuccess?.(res.data);
|
|
55
|
+
} else {
|
|
56
|
+
setError(res.message);
|
|
57
|
+
setErrorCode(res.code ?? null);
|
|
58
|
+
setIsSuccess(false);
|
|
59
|
+
setIsError(true);
|
|
60
|
+
callbacksRef.current.onError?.(res.message, res.code);
|
|
61
|
+
}
|
|
62
|
+
callbacksRef.current.onSettled?.();
|
|
63
|
+
}, []);
|
|
64
|
+
const handleException = (0, import_react.useCallback)((err) => {
|
|
65
|
+
if (!isMountedRef.current) return;
|
|
66
|
+
const message = err instanceof Error ? err.message : "Unknown error";
|
|
67
|
+
const code = err instanceof Error ? err.name : "UNKNOWN_ERROR";
|
|
68
|
+
setError(message);
|
|
69
|
+
setErrorCode(code);
|
|
70
|
+
setIsSuccess(false);
|
|
71
|
+
setIsError(true);
|
|
72
|
+
callbacksRef.current.onError?.(message, code);
|
|
73
|
+
callbacksRef.current.onSettled?.();
|
|
74
|
+
}, []);
|
|
75
|
+
const execute = (0, import_react.useCallback)(
|
|
76
|
+
(...params) => {
|
|
77
|
+
setError(null);
|
|
78
|
+
setErrorCode(null);
|
|
79
|
+
setIsSuccess(false);
|
|
80
|
+
setIsError(false);
|
|
81
|
+
startTransition(() => {
|
|
82
|
+
action(...params).then(handleResult).catch(handleException);
|
|
83
|
+
});
|
|
84
|
+
},
|
|
85
|
+
[action, handleResult, handleException]
|
|
86
|
+
);
|
|
87
|
+
const executeAsync = (0, import_react.useCallback)(
|
|
88
|
+
async (...params) => {
|
|
89
|
+
setError(null);
|
|
90
|
+
setErrorCode(null);
|
|
91
|
+
setIsSuccess(false);
|
|
92
|
+
setIsError(false);
|
|
93
|
+
try {
|
|
94
|
+
const res = await action(...params);
|
|
95
|
+
handleResult(res);
|
|
96
|
+
return res;
|
|
97
|
+
} catch (err) {
|
|
98
|
+
handleException(err);
|
|
99
|
+
const message = err instanceof Error ? err.message : "Unknown error";
|
|
100
|
+
const code = err instanceof Error ? err.name : "UNKNOWN_ERROR";
|
|
101
|
+
return { ok: false, message, code };
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
[action, handleResult, handleException]
|
|
105
|
+
);
|
|
106
|
+
const reset = (0, import_react.useCallback)(() => {
|
|
107
|
+
setError(null);
|
|
108
|
+
setErrorCode(null);
|
|
109
|
+
setData(null);
|
|
110
|
+
setIsSuccess(false);
|
|
111
|
+
setIsError(false);
|
|
112
|
+
}, []);
|
|
113
|
+
return {
|
|
114
|
+
isPending,
|
|
115
|
+
error,
|
|
116
|
+
errorCode,
|
|
117
|
+
data,
|
|
118
|
+
isSuccess,
|
|
119
|
+
isError,
|
|
120
|
+
execute,
|
|
121
|
+
executeAsync,
|
|
122
|
+
reset
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
126
|
+
0 && (module.exports = {
|
|
127
|
+
useServerAction
|
|
128
|
+
});
|
|
129
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/use-server-action.ts"],"sourcesContent":["export { useServerAction } from \"./use-server-action\";\nexport type {\n UseServerActionInput,\n UseServerActionReturn,\n} from \"./use-server-action\";\n\n// Re-export types only from server (safe for client bundles)\nexport type {\n ServerActionResult,\n ServerActionSuccess,\n ServerActionError,\n ServerActionFn,\n} from \"./server/server-action\";\n","\"use client\";\n\nimport { useCallback, useEffect, useRef, useState, useTransition } from \"react\";\nimport type { ServerActionResult } from \"./server/server-action\";\n\nexport type UseServerActionInput<P extends unknown[], T> = {\n action: (...args: P) => Promise<ServerActionResult<T>>;\n onSuccess?: (data: T) => void;\n onError?: (message: string, code?: string) => void;\n onSettled?: () => void;\n};\n\nexport type UseServerActionReturn<P extends unknown[], T> = {\n /** Whether the action is currently executing */\n isPending: boolean;\n /** Error message from the last failed execution */\n error: string | null;\n /** Error code from the last failed execution */\n errorCode: string | null;\n /** Data from the last successful execution */\n data: T | null;\n /** Whether the last execution was successful */\n isSuccess: boolean;\n /** Whether the last execution was an error */\n isError: boolean;\n /** Execute the action (wrapped in React transition) */\n execute: (...args: P) => void;\n /** Execute the action and return the result (not wrapped in transition) */\n executeAsync: (...args: P) => Promise<ServerActionResult<T>>;\n /** Reset state to initial values */\n reset: () => void;\n};\n\nexport function useServerAction<P extends unknown[], T>(\n input: UseServerActionInput<P, T>,\n): UseServerActionReturn<P, T> {\n const { action, onSuccess, onError, onSettled } = input;\n\n const [isPending, startTransition] = useTransition();\n const [error, setError] = useState<string | null>(null);\n const [errorCode, setErrorCode] = useState<string | null>(null);\n const [data, setData] = useState<T | null>(null);\n const [isSuccess, setIsSuccess] = useState(false);\n const [isError, setIsError] = useState(false);\n\n // Track if component is mounted to prevent state updates after unmount\n const isMountedRef = useRef(true);\n\n // Use ref to always have latest callbacks without causing re-renders\n const callbacksRef = useRef({ onSuccess, onError, onSettled });\n callbacksRef.current = { onSuccess, onError, onSettled };\n\n // Cleanup on unmount\n useEffect(() => {\n isMountedRef.current = true;\n return () => {\n isMountedRef.current = false;\n };\n }, []);\n\n const handleResult = useCallback((res: ServerActionResult<T>) => {\n if (!isMountedRef.current) return;\n\n if (res.ok) {\n setData(res.data);\n setError(null);\n setErrorCode(null);\n setIsSuccess(true);\n setIsError(false);\n callbacksRef.current.onSuccess?.(res.data);\n } else {\n setError(res.message);\n setErrorCode(res.code ?? null);\n setIsSuccess(false);\n setIsError(true);\n callbacksRef.current.onError?.(res.message, res.code);\n }\n callbacksRef.current.onSettled?.();\n }, []);\n\n const handleException = useCallback((err: unknown) => {\n if (!isMountedRef.current) return;\n\n const message = err instanceof Error ? err.message : \"Unknown error\";\n const code = err instanceof Error ? err.name : \"UNKNOWN_ERROR\";\n setError(message);\n setErrorCode(code);\n setIsSuccess(false);\n setIsError(true);\n callbacksRef.current.onError?.(message, code);\n callbacksRef.current.onSettled?.();\n }, []);\n\n const execute = useCallback(\n (...params: P) => {\n setError(null);\n setErrorCode(null);\n setIsSuccess(false);\n setIsError(false);\n\n startTransition(() => {\n action(...params).then(handleResult).catch(handleException);\n });\n },\n [action, handleResult, handleException],\n );\n\n const executeAsync = useCallback(\n async (...params: P): Promise<ServerActionResult<T>> => {\n setError(null);\n setErrorCode(null);\n setIsSuccess(false);\n setIsError(false);\n\n try {\n const res = await action(...params);\n handleResult(res);\n return res;\n } catch (err) {\n handleException(err);\n const message =\n err instanceof Error ? err.message : \"Unknown error\";\n const code =\n err instanceof Error ? err.name : \"UNKNOWN_ERROR\";\n return { ok: false, message, code };\n }\n },\n [action, handleResult, handleException],\n );\n\n const reset = useCallback(() => {\n setError(null);\n setErrorCode(null);\n setData(null);\n setIsSuccess(false);\n setIsError(false);\n }, []);\n\n return {\n isPending,\n error,\n errorCode,\n data,\n isSuccess,\n isError,\n execute,\n executeAsync,\n reset,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACEA,mBAAwE;AA+BjE,SAAS,gBACZ,OAC2B;AAC3B,QAAM,EAAE,QAAQ,WAAW,SAAS,UAAU,IAAI;AAElD,QAAM,CAAC,WAAW,eAAe,QAAI,4BAAc;AACnD,QAAM,CAAC,OAAO,QAAQ,QAAI,uBAAwB,IAAI;AACtD,QAAM,CAAC,WAAW,YAAY,QAAI,uBAAwB,IAAI;AAC9D,QAAM,CAAC,MAAM,OAAO,QAAI,uBAAmB,IAAI;AAC/C,QAAM,CAAC,WAAW,YAAY,QAAI,uBAAS,KAAK;AAChD,QAAM,CAAC,SAAS,UAAU,QAAI,uBAAS,KAAK;AAG5C,QAAM,mBAAe,qBAAO,IAAI;AAGhC,QAAM,mBAAe,qBAAO,EAAE,WAAW,SAAS,UAAU,CAAC;AAC7D,eAAa,UAAU,EAAE,WAAW,SAAS,UAAU;AAGvD,8BAAU,MAAM;AACZ,iBAAa,UAAU;AACvB,WAAO,MAAM;AACT,mBAAa,UAAU;AAAA,IAC3B;AAAA,EACJ,GAAG,CAAC,CAAC;AAEL,QAAM,mBAAe,0BAAY,CAAC,QAA+B;AAC7D,QAAI,CAAC,aAAa,QAAS;AAE3B,QAAI,IAAI,IAAI;AACR,cAAQ,IAAI,IAAI;AAChB,eAAS,IAAI;AACb,mBAAa,IAAI;AACjB,mBAAa,IAAI;AACjB,iBAAW,KAAK;AAChB,mBAAa,QAAQ,YAAY,IAAI,IAAI;AAAA,IAC7C,OAAO;AACH,eAAS,IAAI,OAAO;AACpB,mBAAa,IAAI,QAAQ,IAAI;AAC7B,mBAAa,KAAK;AAClB,iBAAW,IAAI;AACf,mBAAa,QAAQ,UAAU,IAAI,SAAS,IAAI,IAAI;AAAA,IACxD;AACA,iBAAa,QAAQ,YAAY;AAAA,EACrC,GAAG,CAAC,CAAC;AAEL,QAAM,sBAAkB,0BAAY,CAAC,QAAiB;AAClD,QAAI,CAAC,aAAa,QAAS;AAE3B,UAAM,UAAU,eAAe,QAAQ,IAAI,UAAU;AACrD,UAAM,OAAO,eAAe,QAAQ,IAAI,OAAO;AAC/C,aAAS,OAAO;AAChB,iBAAa,IAAI;AACjB,iBAAa,KAAK;AAClB,eAAW,IAAI;AACf,iBAAa,QAAQ,UAAU,SAAS,IAAI;AAC5C,iBAAa,QAAQ,YAAY;AAAA,EACrC,GAAG,CAAC,CAAC;AAEL,QAAM,cAAU;AAAA,IACZ,IAAI,WAAc;AACd,eAAS,IAAI;AACb,mBAAa,IAAI;AACjB,mBAAa,KAAK;AAClB,iBAAW,KAAK;AAEhB,sBAAgB,MAAM;AAClB,eAAO,GAAG,MAAM,EAAE,KAAK,YAAY,EAAE,MAAM,eAAe;AAAA,MAC9D,CAAC;AAAA,IACL;AAAA,IACA,CAAC,QAAQ,cAAc,eAAe;AAAA,EAC1C;AAEA,QAAM,mBAAe;AAAA,IACjB,UAAU,WAA8C;AACpD,eAAS,IAAI;AACb,mBAAa,IAAI;AACjB,mBAAa,KAAK;AAClB,iBAAW,KAAK;AAEhB,UAAI;AACA,cAAM,MAAM,MAAM,OAAO,GAAG,MAAM;AAClC,qBAAa,GAAG;AAChB,eAAO;AAAA,MACX,SAAS,KAAK;AACV,wBAAgB,GAAG;AACnB,cAAM,UACF,eAAe,QAAQ,IAAI,UAAU;AACzC,cAAM,OACF,eAAe,QAAQ,IAAI,OAAO;AACtC,eAAO,EAAE,IAAI,OAAO,SAAS,KAAK;AAAA,MACtC;AAAA,IACJ;AAAA,IACA,CAAC,QAAQ,cAAc,eAAe;AAAA,EAC1C;AAEA,QAAM,YAAQ,0BAAY,MAAM;AAC5B,aAAS,IAAI;AACb,iBAAa,IAAI;AACjB,YAAQ,IAAI;AACZ,iBAAa,KAAK;AAClB,eAAW,KAAK;AAAA,EACpB,GAAG,CAAC,CAAC;AAEL,SAAO;AAAA,IACH;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACJ;AACJ;","names":[]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// src/use-server-action.ts
|
|
2
|
+
import { useCallback, useEffect, useRef, useState, useTransition } from "react";
|
|
3
|
+
function useServerAction(input) {
|
|
4
|
+
const { action, onSuccess, onError, onSettled } = input;
|
|
5
|
+
const [isPending, startTransition] = useTransition();
|
|
6
|
+
const [error, setError] = useState(null);
|
|
7
|
+
const [errorCode, setErrorCode] = useState(null);
|
|
8
|
+
const [data, setData] = useState(null);
|
|
9
|
+
const [isSuccess, setIsSuccess] = useState(false);
|
|
10
|
+
const [isError, setIsError] = useState(false);
|
|
11
|
+
const isMountedRef = useRef(true);
|
|
12
|
+
const callbacksRef = useRef({ onSuccess, onError, onSettled });
|
|
13
|
+
callbacksRef.current = { onSuccess, onError, onSettled };
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
isMountedRef.current = true;
|
|
16
|
+
return () => {
|
|
17
|
+
isMountedRef.current = false;
|
|
18
|
+
};
|
|
19
|
+
}, []);
|
|
20
|
+
const handleResult = useCallback((res) => {
|
|
21
|
+
if (!isMountedRef.current) return;
|
|
22
|
+
if (res.ok) {
|
|
23
|
+
setData(res.data);
|
|
24
|
+
setError(null);
|
|
25
|
+
setErrorCode(null);
|
|
26
|
+
setIsSuccess(true);
|
|
27
|
+
setIsError(false);
|
|
28
|
+
callbacksRef.current.onSuccess?.(res.data);
|
|
29
|
+
} else {
|
|
30
|
+
setError(res.message);
|
|
31
|
+
setErrorCode(res.code ?? null);
|
|
32
|
+
setIsSuccess(false);
|
|
33
|
+
setIsError(true);
|
|
34
|
+
callbacksRef.current.onError?.(res.message, res.code);
|
|
35
|
+
}
|
|
36
|
+
callbacksRef.current.onSettled?.();
|
|
37
|
+
}, []);
|
|
38
|
+
const handleException = useCallback((err) => {
|
|
39
|
+
if (!isMountedRef.current) return;
|
|
40
|
+
const message = err instanceof Error ? err.message : "Unknown error";
|
|
41
|
+
const code = err instanceof Error ? err.name : "UNKNOWN_ERROR";
|
|
42
|
+
setError(message);
|
|
43
|
+
setErrorCode(code);
|
|
44
|
+
setIsSuccess(false);
|
|
45
|
+
setIsError(true);
|
|
46
|
+
callbacksRef.current.onError?.(message, code);
|
|
47
|
+
callbacksRef.current.onSettled?.();
|
|
48
|
+
}, []);
|
|
49
|
+
const execute = useCallback(
|
|
50
|
+
(...params) => {
|
|
51
|
+
setError(null);
|
|
52
|
+
setErrorCode(null);
|
|
53
|
+
setIsSuccess(false);
|
|
54
|
+
setIsError(false);
|
|
55
|
+
startTransition(() => {
|
|
56
|
+
action(...params).then(handleResult).catch(handleException);
|
|
57
|
+
});
|
|
58
|
+
},
|
|
59
|
+
[action, handleResult, handleException]
|
|
60
|
+
);
|
|
61
|
+
const executeAsync = useCallback(
|
|
62
|
+
async (...params) => {
|
|
63
|
+
setError(null);
|
|
64
|
+
setErrorCode(null);
|
|
65
|
+
setIsSuccess(false);
|
|
66
|
+
setIsError(false);
|
|
67
|
+
try {
|
|
68
|
+
const res = await action(...params);
|
|
69
|
+
handleResult(res);
|
|
70
|
+
return res;
|
|
71
|
+
} catch (err) {
|
|
72
|
+
handleException(err);
|
|
73
|
+
const message = err instanceof Error ? err.message : "Unknown error";
|
|
74
|
+
const code = err instanceof Error ? err.name : "UNKNOWN_ERROR";
|
|
75
|
+
return { ok: false, message, code };
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
[action, handleResult, handleException]
|
|
79
|
+
);
|
|
80
|
+
const reset = useCallback(() => {
|
|
81
|
+
setError(null);
|
|
82
|
+
setErrorCode(null);
|
|
83
|
+
setData(null);
|
|
84
|
+
setIsSuccess(false);
|
|
85
|
+
setIsError(false);
|
|
86
|
+
}, []);
|
|
87
|
+
return {
|
|
88
|
+
isPending,
|
|
89
|
+
error,
|
|
90
|
+
errorCode,
|
|
91
|
+
data,
|
|
92
|
+
isSuccess,
|
|
93
|
+
isError,
|
|
94
|
+
execute,
|
|
95
|
+
executeAsync,
|
|
96
|
+
reset
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
export {
|
|
100
|
+
useServerAction
|
|
101
|
+
};
|
|
102
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/use-server-action.ts"],"sourcesContent":["\"use client\";\n\nimport { useCallback, useEffect, useRef, useState, useTransition } from \"react\";\nimport type { ServerActionResult } from \"./server/server-action\";\n\nexport type UseServerActionInput<P extends unknown[], T> = {\n action: (...args: P) => Promise<ServerActionResult<T>>;\n onSuccess?: (data: T) => void;\n onError?: (message: string, code?: string) => void;\n onSettled?: () => void;\n};\n\nexport type UseServerActionReturn<P extends unknown[], T> = {\n /** Whether the action is currently executing */\n isPending: boolean;\n /** Error message from the last failed execution */\n error: string | null;\n /** Error code from the last failed execution */\n errorCode: string | null;\n /** Data from the last successful execution */\n data: T | null;\n /** Whether the last execution was successful */\n isSuccess: boolean;\n /** Whether the last execution was an error */\n isError: boolean;\n /** Execute the action (wrapped in React transition) */\n execute: (...args: P) => void;\n /** Execute the action and return the result (not wrapped in transition) */\n executeAsync: (...args: P) => Promise<ServerActionResult<T>>;\n /** Reset state to initial values */\n reset: () => void;\n};\n\nexport function useServerAction<P extends unknown[], T>(\n input: UseServerActionInput<P, T>,\n): UseServerActionReturn<P, T> {\n const { action, onSuccess, onError, onSettled } = input;\n\n const [isPending, startTransition] = useTransition();\n const [error, setError] = useState<string | null>(null);\n const [errorCode, setErrorCode] = useState<string | null>(null);\n const [data, setData] = useState<T | null>(null);\n const [isSuccess, setIsSuccess] = useState(false);\n const [isError, setIsError] = useState(false);\n\n // Track if component is mounted to prevent state updates after unmount\n const isMountedRef = useRef(true);\n\n // Use ref to always have latest callbacks without causing re-renders\n const callbacksRef = useRef({ onSuccess, onError, onSettled });\n callbacksRef.current = { onSuccess, onError, onSettled };\n\n // Cleanup on unmount\n useEffect(() => {\n isMountedRef.current = true;\n return () => {\n isMountedRef.current = false;\n };\n }, []);\n\n const handleResult = useCallback((res: ServerActionResult<T>) => {\n if (!isMountedRef.current) return;\n\n if (res.ok) {\n setData(res.data);\n setError(null);\n setErrorCode(null);\n setIsSuccess(true);\n setIsError(false);\n callbacksRef.current.onSuccess?.(res.data);\n } else {\n setError(res.message);\n setErrorCode(res.code ?? null);\n setIsSuccess(false);\n setIsError(true);\n callbacksRef.current.onError?.(res.message, res.code);\n }\n callbacksRef.current.onSettled?.();\n }, []);\n\n const handleException = useCallback((err: unknown) => {\n if (!isMountedRef.current) return;\n\n const message = err instanceof Error ? err.message : \"Unknown error\";\n const code = err instanceof Error ? err.name : \"UNKNOWN_ERROR\";\n setError(message);\n setErrorCode(code);\n setIsSuccess(false);\n setIsError(true);\n callbacksRef.current.onError?.(message, code);\n callbacksRef.current.onSettled?.();\n }, []);\n\n const execute = useCallback(\n (...params: P) => {\n setError(null);\n setErrorCode(null);\n setIsSuccess(false);\n setIsError(false);\n\n startTransition(() => {\n action(...params).then(handleResult).catch(handleException);\n });\n },\n [action, handleResult, handleException],\n );\n\n const executeAsync = useCallback(\n async (...params: P): Promise<ServerActionResult<T>> => {\n setError(null);\n setErrorCode(null);\n setIsSuccess(false);\n setIsError(false);\n\n try {\n const res = await action(...params);\n handleResult(res);\n return res;\n } catch (err) {\n handleException(err);\n const message =\n err instanceof Error ? err.message : \"Unknown error\";\n const code =\n err instanceof Error ? err.name : \"UNKNOWN_ERROR\";\n return { ok: false, message, code };\n }\n },\n [action, handleResult, handleException],\n );\n\n const reset = useCallback(() => {\n setError(null);\n setErrorCode(null);\n setData(null);\n setIsSuccess(false);\n setIsError(false);\n }, []);\n\n return {\n isPending,\n error,\n errorCode,\n data,\n isSuccess,\n isError,\n execute,\n executeAsync,\n reset,\n };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
type ServerActionSuccess<T> = {
|
|
2
|
+
ok: true;
|
|
3
|
+
data: T;
|
|
4
|
+
};
|
|
5
|
+
type ServerActionError = {
|
|
6
|
+
ok: false;
|
|
7
|
+
message: string;
|
|
8
|
+
code?: string;
|
|
9
|
+
};
|
|
10
|
+
type ServerActionResult<T> = ServerActionSuccess<T> | ServerActionError;
|
|
11
|
+
declare function success<T>(data: T): ServerActionSuccess<T>;
|
|
12
|
+
declare function error(message: string, code?: string): ServerActionError;
|
|
13
|
+
type ServerActionFn<P extends unknown[], T> = (...args: P) => Promise<ServerActionResult<T>>;
|
|
14
|
+
type ServerActionOptions = {
|
|
15
|
+
onError?: (error: unknown) => void;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Wraps an async function to return a standardized ServerActionResult.
|
|
19
|
+
* Catches any thrown errors and converts them to error results.
|
|
20
|
+
*/
|
|
21
|
+
declare function serverAction<P extends unknown[], T>(fn: (...args: P) => Promise<T>, options?: ServerActionOptions): ServerActionFn<P, T>;
|
|
22
|
+
/**
|
|
23
|
+
* Type guard to check if a result is successful
|
|
24
|
+
*/
|
|
25
|
+
declare function isSuccess<T>(result: ServerActionResult<T>): result is ServerActionSuccess<T>;
|
|
26
|
+
/**
|
|
27
|
+
* Type guard to check if a result is an error
|
|
28
|
+
*/
|
|
29
|
+
declare function isError<T>(result: ServerActionResult<T>): result is ServerActionError;
|
|
30
|
+
/**
|
|
31
|
+
* Unwraps a successful result or throws the error message
|
|
32
|
+
*/
|
|
33
|
+
declare function unwrap<T>(result: ServerActionResult<T>): T;
|
|
34
|
+
/**
|
|
35
|
+
* Unwraps a successful result or returns a default value
|
|
36
|
+
*/
|
|
37
|
+
declare function unwrapOr<T>(result: ServerActionResult<T>, defaultValue: T): T;
|
|
38
|
+
|
|
39
|
+
export { type ServerActionResult as S, type ServerActionSuccess as a, type ServerActionError as b, type ServerActionFn as c, success as d, error as e, isError as f, unwrapOr as g, isSuccess as i, serverAction as s, unwrap as u };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
type ServerActionSuccess<T> = {
|
|
2
|
+
ok: true;
|
|
3
|
+
data: T;
|
|
4
|
+
};
|
|
5
|
+
type ServerActionError = {
|
|
6
|
+
ok: false;
|
|
7
|
+
message: string;
|
|
8
|
+
code?: string;
|
|
9
|
+
};
|
|
10
|
+
type ServerActionResult<T> = ServerActionSuccess<T> | ServerActionError;
|
|
11
|
+
declare function success<T>(data: T): ServerActionSuccess<T>;
|
|
12
|
+
declare function error(message: string, code?: string): ServerActionError;
|
|
13
|
+
type ServerActionFn<P extends unknown[], T> = (...args: P) => Promise<ServerActionResult<T>>;
|
|
14
|
+
type ServerActionOptions = {
|
|
15
|
+
onError?: (error: unknown) => void;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Wraps an async function to return a standardized ServerActionResult.
|
|
19
|
+
* Catches any thrown errors and converts them to error results.
|
|
20
|
+
*/
|
|
21
|
+
declare function serverAction<P extends unknown[], T>(fn: (...args: P) => Promise<T>, options?: ServerActionOptions): ServerActionFn<P, T>;
|
|
22
|
+
/**
|
|
23
|
+
* Type guard to check if a result is successful
|
|
24
|
+
*/
|
|
25
|
+
declare function isSuccess<T>(result: ServerActionResult<T>): result is ServerActionSuccess<T>;
|
|
26
|
+
/**
|
|
27
|
+
* Type guard to check if a result is an error
|
|
28
|
+
*/
|
|
29
|
+
declare function isError<T>(result: ServerActionResult<T>): result is ServerActionError;
|
|
30
|
+
/**
|
|
31
|
+
* Unwraps a successful result or throws the error message
|
|
32
|
+
*/
|
|
33
|
+
declare function unwrap<T>(result: ServerActionResult<T>): T;
|
|
34
|
+
/**
|
|
35
|
+
* Unwraps a successful result or returns a default value
|
|
36
|
+
*/
|
|
37
|
+
declare function unwrapOr<T>(result: ServerActionResult<T>, defaultValue: T): T;
|
|
38
|
+
|
|
39
|
+
export { type ServerActionResult as S, type ServerActionSuccess as a, type ServerActionError as b, type ServerActionFn as c, success as d, error as e, isError as f, unwrapOr as g, isSuccess as i, serverAction as s, unwrap as u };
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { S as ServerActionResult } from './server-action-HthxxP-Y.mjs';
|
|
2
|
+
export { b as ServerActionError, c as ServerActionFn, a as ServerActionSuccess, e as error, f as isError, i as isSuccess, s as serverAction, d as success, u as unwrap, g as unwrapOr } from './server-action-HthxxP-Y.mjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A middleware function that wraps a server action.
|
|
6
|
+
* Receives the next function in the chain and the parameters passed to the action.
|
|
7
|
+
*/
|
|
8
|
+
type Middleware<P extends unknown[], T> = (next: (...params: P) => Promise<ServerActionResult<T>>, ...params: P) => Promise<ServerActionResult<T>>;
|
|
9
|
+
/**
|
|
10
|
+
* Creates a type-safe middleware function.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* const withLogging = createMiddleware(async (next, ...params) => {
|
|
15
|
+
* console.log("Calling action with:", params);
|
|
16
|
+
* const result = await next(...params);
|
|
17
|
+
* console.log("Result:", result);
|
|
18
|
+
* return result;
|
|
19
|
+
* });
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
declare function createMiddleware<P extends unknown[], T>(handler: Middleware<P, T>): Middleware<P, T>;
|
|
23
|
+
/**
|
|
24
|
+
* Applies middleware to a server action.
|
|
25
|
+
* Middleware is executed in order (first middleware wraps second, etc).
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* const protectedAction = applyMiddleware(
|
|
30
|
+
* myServerAction,
|
|
31
|
+
* [withAuth, withLogging, withValidation]
|
|
32
|
+
* );
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
declare function applyMiddleware<P extends unknown[], T>(action: (...params: P) => Promise<ServerActionResult<T>>, middleware: Middleware<P, T>[]): (...params: P) => Promise<ServerActionResult<T>>;
|
|
36
|
+
/**
|
|
37
|
+
* Composes multiple middleware into a single middleware.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```ts
|
|
41
|
+
* const combined = composeMiddleware(withAuth, withLogging, withValidation);
|
|
42
|
+
* const protectedAction = applyMiddleware(myAction, [combined]);
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
declare function composeMiddleware<P extends unknown[], T>(...middleware: Middleware<P, T>[]): Middleware<P, T>;
|
|
46
|
+
/**
|
|
47
|
+
* A Zod-like schema interface for validation.
|
|
48
|
+
* Works with Zod, Valibot, or any schema library with a compatible safeParse method.
|
|
49
|
+
*/
|
|
50
|
+
type ValidationSchema<T> = {
|
|
51
|
+
safeParse(data: unknown): {
|
|
52
|
+
success: true;
|
|
53
|
+
data: T;
|
|
54
|
+
} | {
|
|
55
|
+
success: false;
|
|
56
|
+
error: {
|
|
57
|
+
message?: string;
|
|
58
|
+
errors?: Array<{
|
|
59
|
+
message: string;
|
|
60
|
+
}>;
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
type WithValidationOptions = {
|
|
65
|
+
/** Error code to return on validation failure. Defaults to "VALIDATION_ERROR" */
|
|
66
|
+
code?: string;
|
|
67
|
+
/** Custom error message formatter */
|
|
68
|
+
formatError?: (error: {
|
|
69
|
+
message?: string;
|
|
70
|
+
errors?: Array<{
|
|
71
|
+
message: string;
|
|
72
|
+
}>;
|
|
73
|
+
}) => string;
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* Creates a middleware that validates the first parameter against a schema.
|
|
77
|
+
* Works with Zod, Valibot, or any library with a compatible safeParse method.
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* ```ts
|
|
81
|
+
* import { z } from "zod";
|
|
82
|
+
* import { withValidation, applyMiddleware } from "use-server-action/server";
|
|
83
|
+
*
|
|
84
|
+
* const CreateUserSchema = z.object({
|
|
85
|
+
* name: z.string().min(1),
|
|
86
|
+
* email: z.string().email(),
|
|
87
|
+
* });
|
|
88
|
+
*
|
|
89
|
+
* const createUser = applyMiddleware(
|
|
90
|
+
* serverAction(async (input: z.infer<typeof CreateUserSchema>) => {
|
|
91
|
+
* return await db.user.create({ data: input });
|
|
92
|
+
* }),
|
|
93
|
+
* [withValidation(CreateUserSchema)]
|
|
94
|
+
* );
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
declare function withValidation<TInput, T>(schema: ValidationSchema<TInput>, options?: WithValidationOptions): Middleware<[TInput], T>;
|
|
98
|
+
/**
|
|
99
|
+
* Creates a middleware that logs action calls and results.
|
|
100
|
+
*/
|
|
101
|
+
declare function withLogging<P extends unknown[], T>(logger?: {
|
|
102
|
+
onCall?: (params: P) => void;
|
|
103
|
+
onSuccess?: (data: T, params: P) => void;
|
|
104
|
+
onError?: (message: string, code: string | undefined, params: P) => void;
|
|
105
|
+
}): Middleware<P, T>;
|
|
106
|
+
|
|
107
|
+
export { type Middleware, ServerActionResult, type ValidationSchema, type WithValidationOptions, applyMiddleware, composeMiddleware, createMiddleware, withLogging, withValidation };
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { S as ServerActionResult } from './server-action-HthxxP-Y.js';
|
|
2
|
+
export { b as ServerActionError, c as ServerActionFn, a as ServerActionSuccess, e as error, f as isError, i as isSuccess, s as serverAction, d as success, u as unwrap, g as unwrapOr } from './server-action-HthxxP-Y.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A middleware function that wraps a server action.
|
|
6
|
+
* Receives the next function in the chain and the parameters passed to the action.
|
|
7
|
+
*/
|
|
8
|
+
type Middleware<P extends unknown[], T> = (next: (...params: P) => Promise<ServerActionResult<T>>, ...params: P) => Promise<ServerActionResult<T>>;
|
|
9
|
+
/**
|
|
10
|
+
* Creates a type-safe middleware function.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* const withLogging = createMiddleware(async (next, ...params) => {
|
|
15
|
+
* console.log("Calling action with:", params);
|
|
16
|
+
* const result = await next(...params);
|
|
17
|
+
* console.log("Result:", result);
|
|
18
|
+
* return result;
|
|
19
|
+
* });
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
declare function createMiddleware<P extends unknown[], T>(handler: Middleware<P, T>): Middleware<P, T>;
|
|
23
|
+
/**
|
|
24
|
+
* Applies middleware to a server action.
|
|
25
|
+
* Middleware is executed in order (first middleware wraps second, etc).
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* const protectedAction = applyMiddleware(
|
|
30
|
+
* myServerAction,
|
|
31
|
+
* [withAuth, withLogging, withValidation]
|
|
32
|
+
* );
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
declare function applyMiddleware<P extends unknown[], T>(action: (...params: P) => Promise<ServerActionResult<T>>, middleware: Middleware<P, T>[]): (...params: P) => Promise<ServerActionResult<T>>;
|
|
36
|
+
/**
|
|
37
|
+
* Composes multiple middleware into a single middleware.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```ts
|
|
41
|
+
* const combined = composeMiddleware(withAuth, withLogging, withValidation);
|
|
42
|
+
* const protectedAction = applyMiddleware(myAction, [combined]);
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
declare function composeMiddleware<P extends unknown[], T>(...middleware: Middleware<P, T>[]): Middleware<P, T>;
|
|
46
|
+
/**
|
|
47
|
+
* A Zod-like schema interface for validation.
|
|
48
|
+
* Works with Zod, Valibot, or any schema library with a compatible safeParse method.
|
|
49
|
+
*/
|
|
50
|
+
type ValidationSchema<T> = {
|
|
51
|
+
safeParse(data: unknown): {
|
|
52
|
+
success: true;
|
|
53
|
+
data: T;
|
|
54
|
+
} | {
|
|
55
|
+
success: false;
|
|
56
|
+
error: {
|
|
57
|
+
message?: string;
|
|
58
|
+
errors?: Array<{
|
|
59
|
+
message: string;
|
|
60
|
+
}>;
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
type WithValidationOptions = {
|
|
65
|
+
/** Error code to return on validation failure. Defaults to "VALIDATION_ERROR" */
|
|
66
|
+
code?: string;
|
|
67
|
+
/** Custom error message formatter */
|
|
68
|
+
formatError?: (error: {
|
|
69
|
+
message?: string;
|
|
70
|
+
errors?: Array<{
|
|
71
|
+
message: string;
|
|
72
|
+
}>;
|
|
73
|
+
}) => string;
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* Creates a middleware that validates the first parameter against a schema.
|
|
77
|
+
* Works with Zod, Valibot, or any library with a compatible safeParse method.
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* ```ts
|
|
81
|
+
* import { z } from "zod";
|
|
82
|
+
* import { withValidation, applyMiddleware } from "use-server-action/server";
|
|
83
|
+
*
|
|
84
|
+
* const CreateUserSchema = z.object({
|
|
85
|
+
* name: z.string().min(1),
|
|
86
|
+
* email: z.string().email(),
|
|
87
|
+
* });
|
|
88
|
+
*
|
|
89
|
+
* const createUser = applyMiddleware(
|
|
90
|
+
* serverAction(async (input: z.infer<typeof CreateUserSchema>) => {
|
|
91
|
+
* return await db.user.create({ data: input });
|
|
92
|
+
* }),
|
|
93
|
+
* [withValidation(CreateUserSchema)]
|
|
94
|
+
* );
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
declare function withValidation<TInput, T>(schema: ValidationSchema<TInput>, options?: WithValidationOptions): Middleware<[TInput], T>;
|
|
98
|
+
/**
|
|
99
|
+
* Creates a middleware that logs action calls and results.
|
|
100
|
+
*/
|
|
101
|
+
declare function withLogging<P extends unknown[], T>(logger?: {
|
|
102
|
+
onCall?: (params: P) => void;
|
|
103
|
+
onSuccess?: (data: T, params: P) => void;
|
|
104
|
+
onError?: (message: string, code: string | undefined, params: P) => void;
|
|
105
|
+
}): Middleware<P, T>;
|
|
106
|
+
|
|
107
|
+
export { type Middleware, ServerActionResult, type ValidationSchema, type WithValidationOptions, applyMiddleware, composeMiddleware, createMiddleware, withLogging, withValidation };
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/server/index.ts
|
|
21
|
+
var server_exports = {};
|
|
22
|
+
__export(server_exports, {
|
|
23
|
+
applyMiddleware: () => applyMiddleware,
|
|
24
|
+
composeMiddleware: () => composeMiddleware,
|
|
25
|
+
createMiddleware: () => createMiddleware,
|
|
26
|
+
error: () => error,
|
|
27
|
+
isError: () => isError,
|
|
28
|
+
isSuccess: () => isSuccess,
|
|
29
|
+
serverAction: () => serverAction,
|
|
30
|
+
success: () => success,
|
|
31
|
+
unwrap: () => unwrap,
|
|
32
|
+
unwrapOr: () => unwrapOr,
|
|
33
|
+
withLogging: () => withLogging,
|
|
34
|
+
withValidation: () => withValidation
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(server_exports);
|
|
37
|
+
|
|
38
|
+
// src/server/server-action.ts
|
|
39
|
+
function success(data) {
|
|
40
|
+
return { ok: true, data };
|
|
41
|
+
}
|
|
42
|
+
function error(message, code) {
|
|
43
|
+
return { ok: false, message, code };
|
|
44
|
+
}
|
|
45
|
+
function serverAction(fn, options) {
|
|
46
|
+
return async (...args) => {
|
|
47
|
+
try {
|
|
48
|
+
const data = await fn(...args);
|
|
49
|
+
return success(data);
|
|
50
|
+
} catch (err) {
|
|
51
|
+
options?.onError?.(err);
|
|
52
|
+
if (err instanceof Error) {
|
|
53
|
+
return error(err.message, err.name);
|
|
54
|
+
}
|
|
55
|
+
return error("An unexpected error occurred", "UNKNOWN_ERROR");
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function isSuccess(result) {
|
|
60
|
+
return result.ok === true;
|
|
61
|
+
}
|
|
62
|
+
function isError(result) {
|
|
63
|
+
return result.ok === false;
|
|
64
|
+
}
|
|
65
|
+
function unwrap(result) {
|
|
66
|
+
if (result.ok) {
|
|
67
|
+
return result.data;
|
|
68
|
+
}
|
|
69
|
+
throw new Error(result.message);
|
|
70
|
+
}
|
|
71
|
+
function unwrapOr(result, defaultValue) {
|
|
72
|
+
if (result.ok) {
|
|
73
|
+
return result.data;
|
|
74
|
+
}
|
|
75
|
+
return defaultValue;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// src/server/middleware.ts
|
|
79
|
+
function createMiddleware(handler) {
|
|
80
|
+
return handler;
|
|
81
|
+
}
|
|
82
|
+
function applyMiddleware(action, middleware) {
|
|
83
|
+
return middleware.reduceRight(
|
|
84
|
+
(next, mw) => (...params) => mw(next, ...params),
|
|
85
|
+
action
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
function composeMiddleware(...middleware) {
|
|
89
|
+
return (next, ...params) => {
|
|
90
|
+
const chain = middleware.reduceRight(
|
|
91
|
+
(nextFn, mw) => (...p) => mw(nextFn, ...p),
|
|
92
|
+
next
|
|
93
|
+
);
|
|
94
|
+
return chain(...params);
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
function withValidation(schema, options = {}) {
|
|
98
|
+
const { code = "VALIDATION_ERROR", formatError } = options;
|
|
99
|
+
return async (next, input) => {
|
|
100
|
+
const result = schema.safeParse(input);
|
|
101
|
+
if (!result.success) {
|
|
102
|
+
const message = formatError ? formatError(result.error) : result.error.errors?.[0]?.message ?? result.error.message ?? "Validation failed";
|
|
103
|
+
return { ok: false, message, code };
|
|
104
|
+
}
|
|
105
|
+
return next(result.data);
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function withLogging(logger = {}) {
|
|
109
|
+
return async (next, ...params) => {
|
|
110
|
+
logger.onCall?.(params);
|
|
111
|
+
const result = await next(...params);
|
|
112
|
+
if (result.ok) {
|
|
113
|
+
logger.onSuccess?.(result.data, params);
|
|
114
|
+
} else {
|
|
115
|
+
logger.onError?.(result.message, result.code, params);
|
|
116
|
+
}
|
|
117
|
+
return result;
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
121
|
+
0 && (module.exports = {
|
|
122
|
+
applyMiddleware,
|
|
123
|
+
composeMiddleware,
|
|
124
|
+
createMiddleware,
|
|
125
|
+
error,
|
|
126
|
+
isError,
|
|
127
|
+
isSuccess,
|
|
128
|
+
serverAction,
|
|
129
|
+
success,
|
|
130
|
+
unwrap,
|
|
131
|
+
unwrapOr,
|
|
132
|
+
withLogging,
|
|
133
|
+
withValidation
|
|
134
|
+
});
|
|
135
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/server/index.ts","../src/server/server-action.ts","../src/server/middleware.ts"],"sourcesContent":["export {\n serverAction,\n success,\n error,\n isSuccess,\n isError,\n unwrap,\n unwrapOr,\n} from \"./server-action\";\nexport type {\n ServerActionResult,\n ServerActionSuccess,\n ServerActionError,\n ServerActionFn,\n} from \"./server-action\";\n\nexport {\n createMiddleware,\n applyMiddleware,\n composeMiddleware,\n withValidation,\n withLogging,\n} from \"./middleware\";\nexport type {\n Middleware,\n ValidationSchema,\n WithValidationOptions,\n} from \"./middleware\";\n","\"use server\";\n\nexport type ServerActionSuccess<T> = {\n ok: true;\n data: T;\n};\n\nexport type ServerActionError = {\n ok: false;\n message: string;\n code?: string;\n};\n\nexport type ServerActionResult<T> = ServerActionSuccess<T> | ServerActionError;\n\nexport function success<T>(data: T): ServerActionSuccess<T> {\n return { ok: true, data };\n}\n\nexport function error(message: string, code?: string): ServerActionError {\n return { ok: false, message, code };\n}\n\nexport type ServerActionFn<P extends unknown[], T> = (\n ...args: P\n) => Promise<ServerActionResult<T>>;\n\ntype ServerActionOptions = {\n onError?: (error: unknown) => void;\n};\n\n/**\n * Wraps an async function to return a standardized ServerActionResult.\n * Catches any thrown errors and converts them to error results.\n */\nexport function serverAction<P extends unknown[], T>(\n fn: (...args: P) => Promise<T>,\n options?: ServerActionOptions,\n): ServerActionFn<P, T> {\n return async (...args: P): Promise<ServerActionResult<T>> => {\n try {\n const data = await fn(...args);\n return success(data);\n } catch (err) {\n options?.onError?.(err);\n\n if (err instanceof Error) {\n return error(err.message, err.name);\n }\n\n return error(\"An unexpected error occurred\", \"UNKNOWN_ERROR\");\n }\n };\n}\n\n/**\n * Type guard to check if a result is successful\n */\nexport function isSuccess<T>(\n result: ServerActionResult<T>,\n): result is ServerActionSuccess<T> {\n return result.ok === true;\n}\n\n/**\n * Type guard to check if a result is an error\n */\nexport function isError<T>(\n result: ServerActionResult<T>,\n): result is ServerActionError {\n return result.ok === false;\n}\n\n/**\n * Unwraps a successful result or throws the error message\n */\nexport function unwrap<T>(result: ServerActionResult<T>): T {\n if (result.ok) {\n return result.data;\n }\n throw new Error(result.message);\n}\n\n/**\n * Unwraps a successful result or returns a default value\n */\nexport function unwrapOr<T>(result: ServerActionResult<T>, defaultValue: T): T {\n if (result.ok) {\n return result.data;\n }\n return defaultValue;\n}\n","\"use server\";\n\nimport type { ServerActionResult } from \"./server-action\";\n\n/**\n * A middleware function that wraps a server action.\n * Receives the next function in the chain and the parameters passed to the action.\n */\nexport type Middleware<P extends unknown[], T> = (\n next: (...params: P) => Promise<ServerActionResult<T>>,\n ...params: P\n) => Promise<ServerActionResult<T>>;\n\n/**\n * Creates a type-safe middleware function.\n *\n * @example\n * ```ts\n * const withLogging = createMiddleware(async (next, ...params) => {\n * console.log(\"Calling action with:\", params);\n * const result = await next(...params);\n * console.log(\"Result:\", result);\n * return result;\n * });\n * ```\n */\nexport function createMiddleware<P extends unknown[], T>(\n handler: Middleware<P, T>,\n): Middleware<P, T> {\n return handler;\n}\n\n/**\n * Applies middleware to a server action.\n * Middleware is executed in order (first middleware wraps second, etc).\n *\n * @example\n * ```ts\n * const protectedAction = applyMiddleware(\n * myServerAction,\n * [withAuth, withLogging, withValidation]\n * );\n * ```\n */\nexport function applyMiddleware<P extends unknown[], T>(\n action: (...params: P) => Promise<ServerActionResult<T>>,\n middleware: Middleware<P, T>[],\n): (...params: P) => Promise<ServerActionResult<T>> {\n return middleware.reduceRight(\n (next, mw) =>\n (...params: P) =>\n mw(next, ...params),\n action,\n );\n}\n\n/**\n * Composes multiple middleware into a single middleware.\n *\n * @example\n * ```ts\n * const combined = composeMiddleware(withAuth, withLogging, withValidation);\n * const protectedAction = applyMiddleware(myAction, [combined]);\n * ```\n */\nexport function composeMiddleware<P extends unknown[], T>(\n ...middleware: Middleware<P, T>[]\n): Middleware<P, T> {\n return (next, ...params) => {\n const chain = middleware.reduceRight(\n (nextFn, mw) =>\n (...p: P) =>\n mw(nextFn, ...p),\n next,\n );\n return chain(...params);\n };\n}\n\n/**\n * A Zod-like schema interface for validation.\n * Works with Zod, Valibot, or any schema library with a compatible safeParse method.\n */\nexport type ValidationSchema<T> = {\n safeParse(data: unknown):\n | { success: true; data: T }\n | { success: false; error: { message?: string; errors?: Array<{ message: string }> } };\n};\n\nexport type WithValidationOptions = {\n /** Error code to return on validation failure. Defaults to \"VALIDATION_ERROR\" */\n code?: string;\n /** Custom error message formatter */\n formatError?: (error: {\n message?: string;\n errors?: Array<{ message: string }>;\n }) => string;\n};\n\n/**\n * Creates a middleware that validates the first parameter against a schema.\n * Works with Zod, Valibot, or any library with a compatible safeParse method.\n *\n * @example\n * ```ts\n * import { z } from \"zod\";\n * import { withValidation, applyMiddleware } from \"use-server-action/server\";\n *\n * const CreateUserSchema = z.object({\n * name: z.string().min(1),\n * email: z.string().email(),\n * });\n *\n * const createUser = applyMiddleware(\n * serverAction(async (input: z.infer<typeof CreateUserSchema>) => {\n * return await db.user.create({ data: input });\n * }),\n * [withValidation(CreateUserSchema)]\n * );\n * ```\n */\nexport function withValidation<TInput, T>(\n schema: ValidationSchema<TInput>,\n options: WithValidationOptions = {},\n): Middleware<[TInput], T> {\n const { code = \"VALIDATION_ERROR\", formatError } = options;\n\n return async (next, input) => {\n const result = schema.safeParse(input);\n\n if (!result.success) {\n const message = formatError\n ? formatError(result.error)\n : result.error.errors?.[0]?.message ??\n result.error.message ??\n \"Validation failed\";\n\n return { ok: false, message, code };\n }\n\n return next(result.data);\n };\n}\n\n/**\n * Creates a middleware that logs action calls and results.\n */\nexport function withLogging<P extends unknown[], T>(\n logger: {\n onCall?: (params: P) => void;\n onSuccess?: (data: T, params: P) => void;\n onError?: (\n message: string,\n code: string | undefined,\n params: P,\n ) => void;\n } = {},\n): Middleware<P, T> {\n return async (next, ...params) => {\n logger.onCall?.(params);\n const result = await next(...params);\n if (result.ok) {\n logger.onSuccess?.(result.data, params);\n } else {\n logger.onError?.(result.message, result.code, params);\n }\n return result;\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACeO,SAAS,QAAW,MAAiC;AACxD,SAAO,EAAE,IAAI,MAAM,KAAK;AAC5B;AAEO,SAAS,MAAM,SAAiB,MAAkC;AACrE,SAAO,EAAE,IAAI,OAAO,SAAS,KAAK;AACtC;AAcO,SAAS,aACZ,IACA,SACoB;AACpB,SAAO,UAAU,SAA4C;AACzD,QAAI;AACA,YAAM,OAAO,MAAM,GAAG,GAAG,IAAI;AAC7B,aAAO,QAAQ,IAAI;AAAA,IACvB,SAAS,KAAK;AACV,eAAS,UAAU,GAAG;AAEtB,UAAI,eAAe,OAAO;AACtB,eAAO,MAAM,IAAI,SAAS,IAAI,IAAI;AAAA,MACtC;AAEA,aAAO,MAAM,gCAAgC,eAAe;AAAA,IAChE;AAAA,EACJ;AACJ;AAKO,SAAS,UACZ,QACgC;AAChC,SAAO,OAAO,OAAO;AACzB;AAKO,SAAS,QACZ,QAC2B;AAC3B,SAAO,OAAO,OAAO;AACzB;AAKO,SAAS,OAAU,QAAkC;AACxD,MAAI,OAAO,IAAI;AACX,WAAO,OAAO;AAAA,EAClB;AACA,QAAM,IAAI,MAAM,OAAO,OAAO;AAClC;AAKO,SAAS,SAAY,QAA+B,cAAoB;AAC3E,MAAI,OAAO,IAAI;AACX,WAAO,OAAO;AAAA,EAClB;AACA,SAAO;AACX;;;ACjEO,SAAS,iBACZ,SACgB;AAChB,SAAO;AACX;AAcO,SAAS,gBACZ,QACA,YACgD;AAChD,SAAO,WAAW;AAAA,IACd,CAAC,MAAM,OACH,IAAI,WACA,GAAG,MAAM,GAAG,MAAM;AAAA,IAC1B;AAAA,EACJ;AACJ;AAWO,SAAS,qBACT,YACa;AAChB,SAAO,CAAC,SAAS,WAAW;AACxB,UAAM,QAAQ,WAAW;AAAA,MACrB,CAAC,QAAQ,OACL,IAAI,MACA,GAAG,QAAQ,GAAG,CAAC;AAAA,MACvB;AAAA,IACJ;AACA,WAAO,MAAM,GAAG,MAAM;AAAA,EAC1B;AACJ;AA4CO,SAAS,eACZ,QACA,UAAiC,CAAC,GACX;AACvB,QAAM,EAAE,OAAO,oBAAoB,YAAY,IAAI;AAEnD,SAAO,OAAO,MAAM,UAAU;AAC1B,UAAM,SAAS,OAAO,UAAU,KAAK;AAErC,QAAI,CAAC,OAAO,SAAS;AACjB,YAAM,UAAU,cACV,YAAY,OAAO,KAAK,IACxB,OAAO,MAAM,SAAS,CAAC,GAAG,WAC1B,OAAO,MAAM,WACb;AAEN,aAAO,EAAE,IAAI,OAAO,SAAS,KAAK;AAAA,IACtC;AAEA,WAAO,KAAK,OAAO,IAAI;AAAA,EAC3B;AACJ;AAKO,SAAS,YACZ,SAQI,CAAC,GACW;AAChB,SAAO,OAAO,SAAS,WAAW;AAC9B,WAAO,SAAS,MAAM;AACtB,UAAM,SAAS,MAAM,KAAK,GAAG,MAAM;AACnC,QAAI,OAAO,IAAI;AACX,aAAO,YAAY,OAAO,MAAM,MAAM;AAAA,IAC1C,OAAO;AACH,aAAO,UAAU,OAAO,SAAS,OAAO,MAAM,MAAM;AAAA,IACxD;AACA,WAAO;AAAA,EACX;AACJ;","names":[]}
|
package/dist/server.mjs
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// src/server/server-action.ts
|
|
2
|
+
function success(data) {
|
|
3
|
+
return { ok: true, data };
|
|
4
|
+
}
|
|
5
|
+
function error(message, code) {
|
|
6
|
+
return { ok: false, message, code };
|
|
7
|
+
}
|
|
8
|
+
function serverAction(fn, options) {
|
|
9
|
+
return async (...args) => {
|
|
10
|
+
try {
|
|
11
|
+
const data = await fn(...args);
|
|
12
|
+
return success(data);
|
|
13
|
+
} catch (err) {
|
|
14
|
+
options?.onError?.(err);
|
|
15
|
+
if (err instanceof Error) {
|
|
16
|
+
return error(err.message, err.name);
|
|
17
|
+
}
|
|
18
|
+
return error("An unexpected error occurred", "UNKNOWN_ERROR");
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function isSuccess(result) {
|
|
23
|
+
return result.ok === true;
|
|
24
|
+
}
|
|
25
|
+
function isError(result) {
|
|
26
|
+
return result.ok === false;
|
|
27
|
+
}
|
|
28
|
+
function unwrap(result) {
|
|
29
|
+
if (result.ok) {
|
|
30
|
+
return result.data;
|
|
31
|
+
}
|
|
32
|
+
throw new Error(result.message);
|
|
33
|
+
}
|
|
34
|
+
function unwrapOr(result, defaultValue) {
|
|
35
|
+
if (result.ok) {
|
|
36
|
+
return result.data;
|
|
37
|
+
}
|
|
38
|
+
return defaultValue;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// src/server/middleware.ts
|
|
42
|
+
function createMiddleware(handler) {
|
|
43
|
+
return handler;
|
|
44
|
+
}
|
|
45
|
+
function applyMiddleware(action, middleware) {
|
|
46
|
+
return middleware.reduceRight(
|
|
47
|
+
(next, mw) => (...params) => mw(next, ...params),
|
|
48
|
+
action
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
function composeMiddleware(...middleware) {
|
|
52
|
+
return (next, ...params) => {
|
|
53
|
+
const chain = middleware.reduceRight(
|
|
54
|
+
(nextFn, mw) => (...p) => mw(nextFn, ...p),
|
|
55
|
+
next
|
|
56
|
+
);
|
|
57
|
+
return chain(...params);
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function withValidation(schema, options = {}) {
|
|
61
|
+
const { code = "VALIDATION_ERROR", formatError } = options;
|
|
62
|
+
return async (next, input) => {
|
|
63
|
+
const result = schema.safeParse(input);
|
|
64
|
+
if (!result.success) {
|
|
65
|
+
const message = formatError ? formatError(result.error) : result.error.errors?.[0]?.message ?? result.error.message ?? "Validation failed";
|
|
66
|
+
return { ok: false, message, code };
|
|
67
|
+
}
|
|
68
|
+
return next(result.data);
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function withLogging(logger = {}) {
|
|
72
|
+
return async (next, ...params) => {
|
|
73
|
+
logger.onCall?.(params);
|
|
74
|
+
const result = await next(...params);
|
|
75
|
+
if (result.ok) {
|
|
76
|
+
logger.onSuccess?.(result.data, params);
|
|
77
|
+
} else {
|
|
78
|
+
logger.onError?.(result.message, result.code, params);
|
|
79
|
+
}
|
|
80
|
+
return result;
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
export {
|
|
84
|
+
applyMiddleware,
|
|
85
|
+
composeMiddleware,
|
|
86
|
+
createMiddleware,
|
|
87
|
+
error,
|
|
88
|
+
isError,
|
|
89
|
+
isSuccess,
|
|
90
|
+
serverAction,
|
|
91
|
+
success,
|
|
92
|
+
unwrap,
|
|
93
|
+
unwrapOr,
|
|
94
|
+
withLogging,
|
|
95
|
+
withValidation
|
|
96
|
+
};
|
|
97
|
+
//# sourceMappingURL=server.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/server/server-action.ts","../src/server/middleware.ts"],"sourcesContent":["\"use server\";\n\nexport type ServerActionSuccess<T> = {\n ok: true;\n data: T;\n};\n\nexport type ServerActionError = {\n ok: false;\n message: string;\n code?: string;\n};\n\nexport type ServerActionResult<T> = ServerActionSuccess<T> | ServerActionError;\n\nexport function success<T>(data: T): ServerActionSuccess<T> {\n return { ok: true, data };\n}\n\nexport function error(message: string, code?: string): ServerActionError {\n return { ok: false, message, code };\n}\n\nexport type ServerActionFn<P extends unknown[], T> = (\n ...args: P\n) => Promise<ServerActionResult<T>>;\n\ntype ServerActionOptions = {\n onError?: (error: unknown) => void;\n};\n\n/**\n * Wraps an async function to return a standardized ServerActionResult.\n * Catches any thrown errors and converts them to error results.\n */\nexport function serverAction<P extends unknown[], T>(\n fn: (...args: P) => Promise<T>,\n options?: ServerActionOptions,\n): ServerActionFn<P, T> {\n return async (...args: P): Promise<ServerActionResult<T>> => {\n try {\n const data = await fn(...args);\n return success(data);\n } catch (err) {\n options?.onError?.(err);\n\n if (err instanceof Error) {\n return error(err.message, err.name);\n }\n\n return error(\"An unexpected error occurred\", \"UNKNOWN_ERROR\");\n }\n };\n}\n\n/**\n * Type guard to check if a result is successful\n */\nexport function isSuccess<T>(\n result: ServerActionResult<T>,\n): result is ServerActionSuccess<T> {\n return result.ok === true;\n}\n\n/**\n * Type guard to check if a result is an error\n */\nexport function isError<T>(\n result: ServerActionResult<T>,\n): result is ServerActionError {\n return result.ok === false;\n}\n\n/**\n * Unwraps a successful result or throws the error message\n */\nexport function unwrap<T>(result: ServerActionResult<T>): T {\n if (result.ok) {\n return result.data;\n }\n throw new Error(result.message);\n}\n\n/**\n * Unwraps a successful result or returns a default value\n */\nexport function unwrapOr<T>(result: ServerActionResult<T>, defaultValue: T): T {\n if (result.ok) {\n return result.data;\n }\n return defaultValue;\n}\n","\"use server\";\n\nimport type { ServerActionResult } from \"./server-action\";\n\n/**\n * A middleware function that wraps a server action.\n * Receives the next function in the chain and the parameters passed to the action.\n */\nexport type Middleware<P extends unknown[], T> = (\n next: (...params: P) => Promise<ServerActionResult<T>>,\n ...params: P\n) => Promise<ServerActionResult<T>>;\n\n/**\n * Creates a type-safe middleware function.\n *\n * @example\n * ```ts\n * const withLogging = createMiddleware(async (next, ...params) => {\n * console.log(\"Calling action with:\", params);\n * const result = await next(...params);\n * console.log(\"Result:\", result);\n * return result;\n * });\n * ```\n */\nexport function createMiddleware<P extends unknown[], T>(\n handler: Middleware<P, T>,\n): Middleware<P, T> {\n return handler;\n}\n\n/**\n * Applies middleware to a server action.\n * Middleware is executed in order (first middleware wraps second, etc).\n *\n * @example\n * ```ts\n * const protectedAction = applyMiddleware(\n * myServerAction,\n * [withAuth, withLogging, withValidation]\n * );\n * ```\n */\nexport function applyMiddleware<P extends unknown[], T>(\n action: (...params: P) => Promise<ServerActionResult<T>>,\n middleware: Middleware<P, T>[],\n): (...params: P) => Promise<ServerActionResult<T>> {\n return middleware.reduceRight(\n (next, mw) =>\n (...params: P) =>\n mw(next, ...params),\n action,\n );\n}\n\n/**\n * Composes multiple middleware into a single middleware.\n *\n * @example\n * ```ts\n * const combined = composeMiddleware(withAuth, withLogging, withValidation);\n * const protectedAction = applyMiddleware(myAction, [combined]);\n * ```\n */\nexport function composeMiddleware<P extends unknown[], T>(\n ...middleware: Middleware<P, T>[]\n): Middleware<P, T> {\n return (next, ...params) => {\n const chain = middleware.reduceRight(\n (nextFn, mw) =>\n (...p: P) =>\n mw(nextFn, ...p),\n next,\n );\n return chain(...params);\n };\n}\n\n/**\n * A Zod-like schema interface for validation.\n * Works with Zod, Valibot, or any schema library with a compatible safeParse method.\n */\nexport type ValidationSchema<T> = {\n safeParse(data: unknown):\n | { success: true; data: T }\n | { success: false; error: { message?: string; errors?: Array<{ message: string }> } };\n};\n\nexport type WithValidationOptions = {\n /** Error code to return on validation failure. Defaults to \"VALIDATION_ERROR\" */\n code?: string;\n /** Custom error message formatter */\n formatError?: (error: {\n message?: string;\n errors?: Array<{ message: string }>;\n }) => string;\n};\n\n/**\n * Creates a middleware that validates the first parameter against a schema.\n * Works with Zod, Valibot, or any library with a compatible safeParse method.\n *\n * @example\n * ```ts\n * import { z } from \"zod\";\n * import { withValidation, applyMiddleware } from \"use-server-action/server\";\n *\n * const CreateUserSchema = z.object({\n * name: z.string().min(1),\n * email: z.string().email(),\n * });\n *\n * const createUser = applyMiddleware(\n * serverAction(async (input: z.infer<typeof CreateUserSchema>) => {\n * return await db.user.create({ data: input });\n * }),\n * [withValidation(CreateUserSchema)]\n * );\n * ```\n */\nexport function withValidation<TInput, T>(\n schema: ValidationSchema<TInput>,\n options: WithValidationOptions = {},\n): Middleware<[TInput], T> {\n const { code = \"VALIDATION_ERROR\", formatError } = options;\n\n return async (next, input) => {\n const result = schema.safeParse(input);\n\n if (!result.success) {\n const message = formatError\n ? formatError(result.error)\n : result.error.errors?.[0]?.message ??\n result.error.message ??\n \"Validation failed\";\n\n return { ok: false, message, code };\n }\n\n return next(result.data);\n };\n}\n\n/**\n * Creates a middleware that logs action calls and results.\n */\nexport function withLogging<P extends unknown[], T>(\n logger: {\n onCall?: (params: P) => void;\n onSuccess?: (data: T, params: P) => void;\n onError?: (\n message: string,\n code: string | undefined,\n params: P,\n ) => void;\n } = {},\n): Middleware<P, T> {\n return async (next, ...params) => {\n logger.onCall?.(params);\n const result = await next(...params);\n if (result.ok) {\n logger.onSuccess?.(result.data, params);\n } else {\n logger.onError?.(result.message, result.code, params);\n }\n return result;\n };\n}\n"],"mappings":";AAeO,SAAS,QAAW,MAAiC;AACxD,SAAO,EAAE,IAAI,MAAM,KAAK;AAC5B;AAEO,SAAS,MAAM,SAAiB,MAAkC;AACrE,SAAO,EAAE,IAAI,OAAO,SAAS,KAAK;AACtC;AAcO,SAAS,aACZ,IACA,SACoB;AACpB,SAAO,UAAU,SAA4C;AACzD,QAAI;AACA,YAAM,OAAO,MAAM,GAAG,GAAG,IAAI;AAC7B,aAAO,QAAQ,IAAI;AAAA,IACvB,SAAS,KAAK;AACV,eAAS,UAAU,GAAG;AAEtB,UAAI,eAAe,OAAO;AACtB,eAAO,MAAM,IAAI,SAAS,IAAI,IAAI;AAAA,MACtC;AAEA,aAAO,MAAM,gCAAgC,eAAe;AAAA,IAChE;AAAA,EACJ;AACJ;AAKO,SAAS,UACZ,QACgC;AAChC,SAAO,OAAO,OAAO;AACzB;AAKO,SAAS,QACZ,QAC2B;AAC3B,SAAO,OAAO,OAAO;AACzB;AAKO,SAAS,OAAU,QAAkC;AACxD,MAAI,OAAO,IAAI;AACX,WAAO,OAAO;AAAA,EAClB;AACA,QAAM,IAAI,MAAM,OAAO,OAAO;AAClC;AAKO,SAAS,SAAY,QAA+B,cAAoB;AAC3E,MAAI,OAAO,IAAI;AACX,WAAO,OAAO;AAAA,EAClB;AACA,SAAO;AACX;;;ACjEO,SAAS,iBACZ,SACgB;AAChB,SAAO;AACX;AAcO,SAAS,gBACZ,QACA,YACgD;AAChD,SAAO,WAAW;AAAA,IACd,CAAC,MAAM,OACH,IAAI,WACA,GAAG,MAAM,GAAG,MAAM;AAAA,IAC1B;AAAA,EACJ;AACJ;AAWO,SAAS,qBACT,YACa;AAChB,SAAO,CAAC,SAAS,WAAW;AACxB,UAAM,QAAQ,WAAW;AAAA,MACrB,CAAC,QAAQ,OACL,IAAI,MACA,GAAG,QAAQ,GAAG,CAAC;AAAA,MACvB;AAAA,IACJ;AACA,WAAO,MAAM,GAAG,MAAM;AAAA,EAC1B;AACJ;AA4CO,SAAS,eACZ,QACA,UAAiC,CAAC,GACX;AACvB,QAAM,EAAE,OAAO,oBAAoB,YAAY,IAAI;AAEnD,SAAO,OAAO,MAAM,UAAU;AAC1B,UAAM,SAAS,OAAO,UAAU,KAAK;AAErC,QAAI,CAAC,OAAO,SAAS;AACjB,YAAM,UAAU,cACV,YAAY,OAAO,KAAK,IACxB,OAAO,MAAM,SAAS,CAAC,GAAG,WAC1B,OAAO,MAAM,WACb;AAEN,aAAO,EAAE,IAAI,OAAO,SAAS,KAAK;AAAA,IACtC;AAEA,WAAO,KAAK,OAAO,IAAI;AAAA,EAC3B;AACJ;AAKO,SAAS,YACZ,SAQI,CAAC,GACW;AAChB,SAAO,OAAO,SAAS,WAAW;AAC9B,WAAO,SAAS,MAAM;AACtB,UAAM,SAAS,MAAM,KAAK,GAAG,MAAM;AACnC,QAAI,OAAO,IAAI;AACX,aAAO,YAAY,OAAO,MAAM,MAAM;AAAA,IAC1C,OAAO;AACH,aAAO,UAAU,OAAO,SAAS,OAAO,MAAM,MAAM;AAAA,IACxD;AACA,WAAO;AAAA,EACX;AACJ;","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "use-server-action",
|
|
3
|
+
"version": "1.1.3",
|
|
4
|
+
"description": "A React hook for working with Next.js server actions",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": {
|
|
11
|
+
"types": "./dist/index.d.mts",
|
|
12
|
+
"default": "./dist/index.mjs"
|
|
13
|
+
},
|
|
14
|
+
"require": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"default": "./dist/index.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"./server": {
|
|
20
|
+
"import": {
|
|
21
|
+
"types": "./dist/server.d.mts",
|
|
22
|
+
"default": "./dist/server.mjs"
|
|
23
|
+
},
|
|
24
|
+
"require": {
|
|
25
|
+
"types": "./dist/server.d.ts",
|
|
26
|
+
"default": "./dist/server.js"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"dist",
|
|
32
|
+
"package.json",
|
|
33
|
+
"README.md",
|
|
34
|
+
"LICENSE"
|
|
35
|
+
],
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "tsup",
|
|
38
|
+
"dev": "tsup --watch",
|
|
39
|
+
"lint": "eslint src/",
|
|
40
|
+
"typecheck": "tsc --noEmit",
|
|
41
|
+
"test": "vitest run",
|
|
42
|
+
"test:watch": "vitest",
|
|
43
|
+
"prepublishOnly": "npm run build"
|
|
44
|
+
},
|
|
45
|
+
"keywords": [
|
|
46
|
+
"react",
|
|
47
|
+
"nextjs",
|
|
48
|
+
"server-actions",
|
|
49
|
+
"hook",
|
|
50
|
+
"typescript"
|
|
51
|
+
],
|
|
52
|
+
"author": "Jack Humphries <jack@jackh.sh>",
|
|
53
|
+
"license": "MIT",
|
|
54
|
+
"peerDependencies": {
|
|
55
|
+
"react": ">=18.0.0",
|
|
56
|
+
"zod": ">=3.0.0"
|
|
57
|
+
},
|
|
58
|
+
"peerDependenciesMeta": {
|
|
59
|
+
"zod": {
|
|
60
|
+
"optional": true
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
"devDependencies": {
|
|
64
|
+
"@testing-library/react": "^16.3.1",
|
|
65
|
+
"@types/react": "^19.2.7",
|
|
66
|
+
"jsdom": "^27.3.0",
|
|
67
|
+
"react": "^19.2.0",
|
|
68
|
+
"react-dom": "^19.2.3",
|
|
69
|
+
"tsup": "^8.5.1",
|
|
70
|
+
"typescript": "^5.9.3",
|
|
71
|
+
"vitest": "^4.0.16"
|
|
72
|
+
}
|
|
73
|
+
}
|