server-act 1.1.4 → 1.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,140 @@
1
+ # Server-Act
2
+
3
+ [![npm version](https://badge.fury.io/js/server-act.svg)](https://badge.fury.io/js/server-act)
4
+
5
+ A simple React server action builder that provides input validation with zod.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ # npm
11
+ npm install server-act zod
12
+
13
+ # yarn
14
+ yarn add server-act zod
15
+
16
+ # pnpm
17
+ pnpm add server-act zod
18
+ ```
19
+
20
+ ## Usage
21
+
22
+ ```ts
23
+ // action.ts
24
+ 'use server';
25
+
26
+ import {serverAct} from 'server-act';
27
+ import {z} from 'zod';
28
+
29
+ export const sayHelloAction = serverAct
30
+ .input(
31
+ z.object({
32
+ name: z.string(),
33
+ }),
34
+ )
35
+ .action(async ({input}) => {
36
+ return `Hello, ${input.name}`;
37
+ });
38
+ ```
39
+
40
+ ```tsx
41
+ // client-component.tsx
42
+ 'use client';
43
+
44
+ import {sayHelloAction} from './action';
45
+
46
+ export const ClientComponent = () => {
47
+ const onClick = () => {
48
+ const message = await sayHelloAction({name: 'John'});
49
+ console.log(message); // Hello, John
50
+ };
51
+
52
+ return (
53
+ <div>
54
+ <button onClick={onClick}>Trigger action</button>
55
+ </div>
56
+ );
57
+ };
58
+ ```
59
+
60
+ ### With Middleware
61
+
62
+ ```ts
63
+ // action.ts
64
+ 'use server';
65
+
66
+ import {serverAct} from 'server-act';
67
+ import {z} from 'zod';
68
+
69
+ export const sayHelloAction = serverAct
70
+ .middleware(() => {
71
+ const userId = '...';
72
+ return {userId};
73
+ })
74
+ .input(
75
+ z.object({
76
+ name: z.string(),
77
+ }),
78
+ )
79
+ .action(async ({ctx, input}) => {
80
+ console.log('User ID', ctx.userId);
81
+ return `Hello, ${input.name}`;
82
+ });
83
+ ```
84
+
85
+ ### `useFormState` Support
86
+
87
+ > `useFormState` Documentation:
88
+ >
89
+ > - https://nextjs.org/docs/app/building-your-application/data-fetching/forms-and-mutations#error-handling
90
+ > - https://react.dev/reference/react-dom/hooks/useFormState
91
+
92
+ We recommend using [zod-form-data](https://www.npmjs.com/package/zod-form-data) for input validation.
93
+
94
+ ```ts
95
+ // action.ts;
96
+ 'use server';
97
+
98
+ import {serverAct} from 'server-act';
99
+ import {z} from 'zod';
100
+ import {zfd} from 'zod-form-data';
101
+
102
+ export const sayHelloAction = serverAct
103
+ .input(
104
+ zfd.formData({
105
+ name: zfd.text(
106
+ z
107
+ .string({required_error: `You haven't told me your name`})
108
+ .nonempty({message: 'You need to tell me your name!'}),
109
+ ),
110
+ }),
111
+ )
112
+ .formAction(async ({input, formErrors, ctx}) => {
113
+ if (formErrors) {
114
+ return {formErrors: formErrors.formErrors.fieldErrors};
115
+ }
116
+ return {message: `Hello, ${input.name}!`};
117
+ });
118
+ ```
119
+
120
+ ```tsx
121
+ // client-component.tsx
122
+ 'use client';
123
+
124
+ import {sayHelloAction} from './action';
125
+
126
+ export const ClientComponent = () => {
127
+ const [state, dispatch] = useFormState(sayHelloAction, {formErrors: {}});
128
+
129
+ return (
130
+ <form action={dispatch}>
131
+ <input name="name" required />
132
+ {state.formErrors?.name?.map((error) => <p key={error}>{error}</p>)}
133
+
134
+ <button type="submit">Submit</button>
135
+
136
+ {!!state.message && <p>{state.message}</p>}
137
+ </form>
138
+ );
139
+ };
140
+ ```
package/dist/index.d.mts CHANGED
@@ -2,10 +2,11 @@ import { z } from 'zod';
2
2
 
3
3
  declare const unsetMarker: unique symbol;
4
4
  type UnsetMarker = typeof unsetMarker;
5
+ type Equals<X, Y> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2 ? true : false;
5
6
  type Prettify<T> = {
6
7
  [P in keyof T]: T[P];
7
8
  } & {};
8
- type OptionalizeUndefined<T> = undefined extends T ? [param?: T] : [param: T];
9
+ type SanitizeFunctionParam<T extends (param: any) => any> = T extends (param: infer P) => infer R ? Equals<P, undefined> extends true ? () => R : Equals<P, P | undefined> extends true ? (param?: P) => R : (param: P) => R : never;
9
10
  type InferParserType<T, TType extends 'in' | 'out'> = T extends z.ZodEffects<infer I, any, any> ? I[TType extends 'in' ? '_input' : '_output'] : T extends z.ZodType ? T[TType extends 'in' ? '_input' : '_output'] : never;
10
11
  type InferInputType<T, TType extends 'in' | 'out'> = T extends UnsetMarker ? undefined : InferParserType<T, TType>;
11
12
  type InferContextType<T> = T extends UnsetMarker ? undefined : T;
@@ -34,7 +35,7 @@ interface ActionBuilder<TParams extends ActionParams> {
34
35
  action: <TOutput>(action: (params: {
35
36
  ctx: InferContextType<TParams['_context']>;
36
37
  input: InferInputType<TParams['_input'], 'out'>;
37
- }) => Promise<TOutput>) => (...[input]: OptionalizeUndefined<InferInputType<TParams['_input'], 'in'>>) => Promise<TOutput>;
38
+ }) => Promise<TOutput>) => SanitizeFunctionParam<(input: InferInputType<TParams['_input'], 'in'>) => Promise<TOutput>>;
38
39
  /**
39
40
  * Create an action for React `useFormState`
40
41
  */
package/dist/index.d.ts CHANGED
@@ -2,10 +2,11 @@ import { z } from 'zod';
2
2
 
3
3
  declare const unsetMarker: unique symbol;
4
4
  type UnsetMarker = typeof unsetMarker;
5
+ type Equals<X, Y> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2 ? true : false;
5
6
  type Prettify<T> = {
6
7
  [P in keyof T]: T[P];
7
8
  } & {};
8
- type OptionalizeUndefined<T> = undefined extends T ? [param?: T] : [param: T];
9
+ type SanitizeFunctionParam<T extends (param: any) => any> = T extends (param: infer P) => infer R ? Equals<P, undefined> extends true ? () => R : Equals<P, P | undefined> extends true ? (param?: P) => R : (param: P) => R : never;
9
10
  type InferParserType<T, TType extends 'in' | 'out'> = T extends z.ZodEffects<infer I, any, any> ? I[TType extends 'in' ? '_input' : '_output'] : T extends z.ZodType ? T[TType extends 'in' ? '_input' : '_output'] : never;
10
11
  type InferInputType<T, TType extends 'in' | 'out'> = T extends UnsetMarker ? undefined : InferParserType<T, TType>;
11
12
  type InferContextType<T> = T extends UnsetMarker ? undefined : T;
@@ -34,7 +35,7 @@ interface ActionBuilder<TParams extends ActionParams> {
34
35
  action: <TOutput>(action: (params: {
35
36
  ctx: InferContextType<TParams['_context']>;
36
37
  input: InferInputType<TParams['_input'], 'out'>;
37
- }) => Promise<TOutput>) => (...[input]: OptionalizeUndefined<InferInputType<TParams['_input'], 'in'>>) => Promise<TOutput>;
38
+ }) => Promise<TOutput>) => SanitizeFunctionParam<(input: InferInputType<TParams['_input'], 'in'>) => Promise<TOutput>>;
38
39
  /**
39
40
  * Create an action for React `useFormState`
40
41
  */
package/package.json CHANGED
@@ -1,7 +1,9 @@
1
1
  {
2
2
  "name": "server-act",
3
- "version": "1.1.4",
3
+ "version": "1.1.6",
4
4
  "homepage": "https://github.com/chungweileong94/server-act#readme",
5
+ "author": "chungweileong94",
6
+ "license": "MIT",
5
7
  "repository": {
6
8
  "type": "git",
7
9
  "url": "git+https://github.com/chungweileong94/server-act.git"
@@ -41,8 +43,6 @@
41
43
  "server action",
42
44
  "action"
43
45
  ],
44
- "author": "chungweileong94",
45
- "license": "MIT",
46
46
  "devDependencies": {
47
47
  "bunchee": "^4.3.3",
48
48
  "eslint": "^8.49.0",