server-act 1.6.0 → 1.6.1
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 +114 -34
- package/dist/index.d.mts +19 -3
- package/dist/index.d.ts +19 -3
- package/dist/index.js +30 -3
- package/dist/index.mjs +30 -3
- package/dist/utils.d.mts +74 -0
- package/dist/utils.d.ts +74 -0
- package/dist/utils.js +110 -0
- package/dist/utils.mjs +109 -0
- package/package.json +11 -13
package/README.md
CHANGED
|
@@ -91,57 +91,137 @@ export const sayHelloAction = serverAct
|
|
|
91
91
|
>
|
|
92
92
|
> - https://react.dev/reference/react/useActionState
|
|
93
93
|
|
|
94
|
-
We recommend using [zod-form-data](https://www.npmjs.com/package/zod-form-data) for input validation.
|
|
95
|
-
|
|
96
94
|
```ts
|
|
97
95
|
// action.ts;
|
|
98
96
|
"use server";
|
|
99
97
|
|
|
100
98
|
import { serverAct } from "server-act";
|
|
99
|
+
import { formDataToObject } from "server-act/utils";
|
|
101
100
|
import { z } from "zod";
|
|
102
|
-
|
|
101
|
+
|
|
102
|
+
function zodFormData<T extends z.ZodType>(
|
|
103
|
+
schema: T,
|
|
104
|
+
): z.ZodPipe<z.ZodTransform<Record<string, unknown>, FormData>, T> {
|
|
105
|
+
return z.preprocess<Record<string, unknown>, T, FormData>(
|
|
106
|
+
(v) => formDataToObject(v),
|
|
107
|
+
schema,
|
|
108
|
+
);
|
|
109
|
+
}
|
|
103
110
|
|
|
104
111
|
export const sayHelloAction = serverAct
|
|
105
112
|
.input(
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
z
|
|
109
|
-
.string(
|
|
110
|
-
.
|
|
111
|
-
|
|
112
|
-
|
|
113
|
+
zodFormData(
|
|
114
|
+
z.object({
|
|
115
|
+
name: z
|
|
116
|
+
.string()
|
|
117
|
+
.min(1, { error: `You haven't told me your name` })
|
|
118
|
+
.max(20, { error: "Any shorter name? You name is too long 😬" }),
|
|
119
|
+
}),
|
|
120
|
+
),
|
|
113
121
|
)
|
|
114
|
-
.stateAction(async ({
|
|
115
|
-
if (
|
|
116
|
-
return { formData,
|
|
122
|
+
.stateAction(async ({ rawInput, input, inputErrors, ctx }) => {
|
|
123
|
+
if (inputErrors) {
|
|
124
|
+
return { formData: rawInput, inputErrors: inputErrors.fieldErrors };
|
|
117
125
|
}
|
|
118
126
|
return { message: `Hello, ${input.name}!` };
|
|
119
127
|
});
|
|
120
128
|
```
|
|
121
129
|
|
|
122
|
-
|
|
123
|
-
// client-component.tsx
|
|
124
|
-
"use client";
|
|
130
|
+
## Utilities
|
|
125
131
|
|
|
126
|
-
|
|
127
|
-
import { sayHelloAction } from "./action";
|
|
132
|
+
### `formDataToObject`
|
|
128
133
|
|
|
129
|
-
|
|
130
|
-
const [state, dispatch] = useActionState(sayHelloAction, undefined);
|
|
134
|
+
The `formDataToObject` utility converts FormData to a structured JavaScript object, supporting nested objects, arrays, and complex form structures.
|
|
131
135
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
136
|
+
```ts
|
|
137
|
+
import { formDataToObject } from "server-act/utils";
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
#### Basic Usage
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
const formData = new FormData();
|
|
144
|
+
formData.append('name', 'John');
|
|
145
|
+
|
|
146
|
+
const result = formDataToObject(formData);
|
|
147
|
+
// Result: { name: 'John' }
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
#### Nested Objects and Arrays
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
const formData = new FormData();
|
|
154
|
+
formData.append('user.name', 'John');
|
|
155
|
+
|
|
156
|
+
const result = formDataToObject(formData);
|
|
157
|
+
// Result: { user: { name: 'John' } }
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
#### With Zod
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
"use server";
|
|
164
|
+
|
|
165
|
+
import { serverAct } from "server-act";
|
|
166
|
+
import { formDataToObject } from "server-act/utils";
|
|
167
|
+
import { z } from "zod";
|
|
168
|
+
|
|
169
|
+
function zodFormData<T extends z.ZodType>(
|
|
170
|
+
schema: T,
|
|
171
|
+
): z.ZodPipe<z.ZodTransform<Record<string, unknown>, FormData>, T> {
|
|
172
|
+
return z.preprocess<Record<string, unknown>, T, FormData>(
|
|
173
|
+
(v) => formDataToObject(v),
|
|
174
|
+
schema,
|
|
145
175
|
);
|
|
146
|
-
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export const createUserAction = serverAct
|
|
179
|
+
.input(
|
|
180
|
+
zodFormData(
|
|
181
|
+
z.object({
|
|
182
|
+
name: z.string().min(1, "Name is required"),
|
|
183
|
+
}),
|
|
184
|
+
),
|
|
185
|
+
)
|
|
186
|
+
.stateAction(async ({ rawInput, input, inputErrors }) => {
|
|
187
|
+
if (inputErrors) {
|
|
188
|
+
return { formData: rawInput, errors: inputErrors.fieldErrors };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Process the validated input
|
|
192
|
+
console.log("User:", input.name);
|
|
193
|
+
|
|
194
|
+
return { success: true, userId: "123" };
|
|
195
|
+
});
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
#### With Valibot
|
|
199
|
+
|
|
200
|
+
```ts
|
|
201
|
+
"use server";
|
|
202
|
+
|
|
203
|
+
import { serverAct } from "server-act";
|
|
204
|
+
import { formDataToObject } from "server-act/utils";
|
|
205
|
+
import * as v from "valibot";
|
|
206
|
+
|
|
207
|
+
export const createPostAction = serverAct
|
|
208
|
+
.input(
|
|
209
|
+
v.pipe(
|
|
210
|
+
v.custom<FormData>((value) => value instanceof FormData),
|
|
211
|
+
v.transform(formDataToObject),
|
|
212
|
+
v.object({
|
|
213
|
+
title: v.pipe(v.string(), v.minLength(1, "Title is required")),
|
|
214
|
+
}),
|
|
215
|
+
),
|
|
216
|
+
)
|
|
217
|
+
.stateAction(async ({ rawInput, input, inputErrors }) => {
|
|
218
|
+
if (inputErrors) {
|
|
219
|
+
return { formData: rawInput, errors: inputErrors.fieldErrors };
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Process the validated input
|
|
223
|
+
console.log("Post:", input.title);
|
|
224
|
+
|
|
225
|
+
return { success: true, postId: "456" };
|
|
226
|
+
});
|
|
147
227
|
```
|
package/dist/index.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { StandardSchemaV1 } from "@standard-schema/spec";
|
|
2
2
|
|
|
3
|
-
//#region src/
|
|
4
|
-
declare function
|
|
3
|
+
//#region src/internal/schema.d.ts
|
|
4
|
+
declare function getInputErrors(issues: ReadonlyArray<StandardSchemaV1.Issue>): {
|
|
5
5
|
messages: string[];
|
|
6
6
|
fieldErrors: Record<string, string[]>;
|
|
7
7
|
};
|
|
@@ -48,6 +48,22 @@ interface ActionBuilder<TParams extends ActionParams> {
|
|
|
48
48
|
* Create an action for React `useActionState`
|
|
49
49
|
*/
|
|
50
50
|
stateAction: <TState, TPrevState = UnsetMarker>(action: (params: Prettify<{
|
|
51
|
+
ctx: InferContextType<TParams["_context"]>;
|
|
52
|
+
prevState: RemoveUnsetMarker<TPrevState>;
|
|
53
|
+
rawInput: InferInputType<TParams["_input"], "in">;
|
|
54
|
+
} & ({
|
|
55
|
+
input: InferInputType<TParams["_input"], "out">;
|
|
56
|
+
inputErrors?: undefined;
|
|
57
|
+
} | {
|
|
58
|
+
input?: undefined;
|
|
59
|
+
inputErrors: ReturnType<typeof getInputErrors>;
|
|
60
|
+
})>) => Promise<TState>) => (prevState: TState | RemoveUnsetMarker<TPrevState>, input: InferInputType<TParams["_input"], "in">) => Promise<TState | RemoveUnsetMarker<TPrevState>>;
|
|
61
|
+
/**
|
|
62
|
+
* Create an action for React `useActionState`
|
|
63
|
+
*
|
|
64
|
+
* @deprecated Use `stateAction` instead.
|
|
65
|
+
*/
|
|
66
|
+
formAction: <TState, TPrevState = UnsetMarker>(action: (params: Prettify<{
|
|
51
67
|
ctx: InferContextType<TParams["_context"]>;
|
|
52
68
|
prevState: RemoveUnsetMarker<TPrevState>;
|
|
53
69
|
formData: FormData;
|
|
@@ -56,7 +72,7 @@ interface ActionBuilder<TParams extends ActionParams> {
|
|
|
56
72
|
formErrors?: undefined;
|
|
57
73
|
} | {
|
|
58
74
|
input?: undefined;
|
|
59
|
-
formErrors: ReturnType<typeof
|
|
75
|
+
formErrors: ReturnType<typeof getInputErrors>;
|
|
60
76
|
})>) => Promise<TState>) => (prevState: TState | RemoveUnsetMarker<TPrevState>, formData: InferInputType<TParams["_input"], "in">) => Promise<TState | RemoveUnsetMarker<TPrevState>>;
|
|
61
77
|
}
|
|
62
78
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { StandardSchemaV1 } from "@standard-schema/spec";
|
|
2
2
|
|
|
3
|
-
//#region src/
|
|
4
|
-
declare function
|
|
3
|
+
//#region src/internal/schema.d.ts
|
|
4
|
+
declare function getInputErrors(issues: ReadonlyArray<StandardSchemaV1.Issue>): {
|
|
5
5
|
messages: string[];
|
|
6
6
|
fieldErrors: Record<string, string[]>;
|
|
7
7
|
};
|
|
@@ -48,6 +48,22 @@ interface ActionBuilder<TParams extends ActionParams> {
|
|
|
48
48
|
* Create an action for React `useActionState`
|
|
49
49
|
*/
|
|
50
50
|
stateAction: <TState, TPrevState = UnsetMarker>(action: (params: Prettify<{
|
|
51
|
+
ctx: InferContextType<TParams["_context"]>;
|
|
52
|
+
prevState: RemoveUnsetMarker<TPrevState>;
|
|
53
|
+
rawInput: InferInputType<TParams["_input"], "in">;
|
|
54
|
+
} & ({
|
|
55
|
+
input: InferInputType<TParams["_input"], "out">;
|
|
56
|
+
inputErrors?: undefined;
|
|
57
|
+
} | {
|
|
58
|
+
input?: undefined;
|
|
59
|
+
inputErrors: ReturnType<typeof getInputErrors>;
|
|
60
|
+
})>) => Promise<TState>) => (prevState: TState | RemoveUnsetMarker<TPrevState>, input: InferInputType<TParams["_input"], "in">) => Promise<TState | RemoveUnsetMarker<TPrevState>>;
|
|
61
|
+
/**
|
|
62
|
+
* Create an action for React `useActionState`
|
|
63
|
+
*
|
|
64
|
+
* @deprecated Use `stateAction` instead.
|
|
65
|
+
*/
|
|
66
|
+
formAction: <TState, TPrevState = UnsetMarker>(action: (params: Prettify<{
|
|
51
67
|
ctx: InferContextType<TParams["_context"]>;
|
|
52
68
|
prevState: RemoveUnsetMarker<TPrevState>;
|
|
53
69
|
formData: FormData;
|
|
@@ -56,7 +72,7 @@ interface ActionBuilder<TParams extends ActionParams> {
|
|
|
56
72
|
formErrors?: undefined;
|
|
57
73
|
} | {
|
|
58
74
|
input?: undefined;
|
|
59
|
-
formErrors: ReturnType<typeof
|
|
75
|
+
formErrors: ReturnType<typeof getInputErrors>;
|
|
60
76
|
})>) => Promise<TState>) => (prevState: TState | RemoveUnsetMarker<TPrevState>, formData: InferInputType<TParams["_input"], "in">) => Promise<TState | RemoveUnsetMarker<TPrevState>>;
|
|
61
77
|
}
|
|
62
78
|
/**
|
package/dist/index.js
CHANGED
|
@@ -23,13 +23,13 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
23
23
|
//#endregion
|
|
24
24
|
const __standard_schema_utils = __toESM(require("@standard-schema/utils"));
|
|
25
25
|
|
|
26
|
-
//#region src/
|
|
26
|
+
//#region src/internal/schema.ts
|
|
27
27
|
async function standardValidate(schema, input) {
|
|
28
28
|
let result = schema["~standard"].validate(input);
|
|
29
29
|
if (result instanceof Promise) result = await result;
|
|
30
30
|
return result;
|
|
31
31
|
}
|
|
32
|
-
function
|
|
32
|
+
function getInputErrors(issues) {
|
|
33
33
|
const messages = [];
|
|
34
34
|
const fieldErrors = {};
|
|
35
35
|
for (const issue of issues) {
|
|
@@ -84,6 +84,33 @@ function createServerActionBuilder(initDef = {}) {
|
|
|
84
84
|
};
|
|
85
85
|
},
|
|
86
86
|
stateAction: (action) => {
|
|
87
|
+
return async (prevState, rawInput) => {
|
|
88
|
+
const ctx = await _def.middleware?.();
|
|
89
|
+
if (_def.input) {
|
|
90
|
+
const inputSchema = typeof _def.input === "function" ? await _def.input({ ctx }) : _def.input;
|
|
91
|
+
const result = await standardValidate(inputSchema, rawInput);
|
|
92
|
+
if (result.issues) return await action({
|
|
93
|
+
ctx,
|
|
94
|
+
prevState,
|
|
95
|
+
rawInput,
|
|
96
|
+
inputErrors: getInputErrors(result.issues)
|
|
97
|
+
});
|
|
98
|
+
return await action({
|
|
99
|
+
ctx,
|
|
100
|
+
prevState,
|
|
101
|
+
rawInput,
|
|
102
|
+
input: result.value
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
return await action({
|
|
106
|
+
ctx,
|
|
107
|
+
prevState,
|
|
108
|
+
rawInput,
|
|
109
|
+
input: void 0
|
|
110
|
+
});
|
|
111
|
+
};
|
|
112
|
+
},
|
|
113
|
+
formAction: (action) => {
|
|
87
114
|
return async (prevState, formData) => {
|
|
88
115
|
const ctx = await _def.middleware?.();
|
|
89
116
|
if (_def.input) {
|
|
@@ -93,7 +120,7 @@ function createServerActionBuilder(initDef = {}) {
|
|
|
93
120
|
ctx,
|
|
94
121
|
prevState,
|
|
95
122
|
formData,
|
|
96
|
-
formErrors:
|
|
123
|
+
formErrors: getInputErrors(result.issues)
|
|
97
124
|
});
|
|
98
125
|
return await action({
|
|
99
126
|
ctx,
|
package/dist/index.mjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { SchemaError, getDotPath } from "@standard-schema/utils";
|
|
2
2
|
|
|
3
|
-
//#region src/
|
|
3
|
+
//#region src/internal/schema.ts
|
|
4
4
|
async function standardValidate(schema, input) {
|
|
5
5
|
let result = schema["~standard"].validate(input);
|
|
6
6
|
if (result instanceof Promise) result = await result;
|
|
7
7
|
return result;
|
|
8
8
|
}
|
|
9
|
-
function
|
|
9
|
+
function getInputErrors(issues) {
|
|
10
10
|
const messages = [];
|
|
11
11
|
const fieldErrors = {};
|
|
12
12
|
for (const issue of issues) {
|
|
@@ -61,6 +61,33 @@ function createServerActionBuilder(initDef = {}) {
|
|
|
61
61
|
};
|
|
62
62
|
},
|
|
63
63
|
stateAction: (action) => {
|
|
64
|
+
return async (prevState, rawInput) => {
|
|
65
|
+
const ctx = await _def.middleware?.();
|
|
66
|
+
if (_def.input) {
|
|
67
|
+
const inputSchema = typeof _def.input === "function" ? await _def.input({ ctx }) : _def.input;
|
|
68
|
+
const result = await standardValidate(inputSchema, rawInput);
|
|
69
|
+
if (result.issues) return await action({
|
|
70
|
+
ctx,
|
|
71
|
+
prevState,
|
|
72
|
+
rawInput,
|
|
73
|
+
inputErrors: getInputErrors(result.issues)
|
|
74
|
+
});
|
|
75
|
+
return await action({
|
|
76
|
+
ctx,
|
|
77
|
+
prevState,
|
|
78
|
+
rawInput,
|
|
79
|
+
input: result.value
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
return await action({
|
|
83
|
+
ctx,
|
|
84
|
+
prevState,
|
|
85
|
+
rawInput,
|
|
86
|
+
input: void 0
|
|
87
|
+
});
|
|
88
|
+
};
|
|
89
|
+
},
|
|
90
|
+
formAction: (action) => {
|
|
64
91
|
return async (prevState, formData) => {
|
|
65
92
|
const ctx = await _def.middleware?.();
|
|
66
93
|
if (_def.input) {
|
|
@@ -70,7 +97,7 @@ function createServerActionBuilder(initDef = {}) {
|
|
|
70
97
|
ctx,
|
|
71
98
|
prevState,
|
|
72
99
|
formData,
|
|
73
|
-
formErrors:
|
|
100
|
+
formErrors: getInputErrors(result.issues)
|
|
74
101
|
});
|
|
75
102
|
return await action({
|
|
76
103
|
ctx,
|
package/dist/utils.d.mts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
//#region src/utils.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Converts FormData to a structured JavaScript object
|
|
4
|
+
*
|
|
5
|
+
* This function parses FormData entries and converts them into a nested object structure.
|
|
6
|
+
* It supports dot notation, array notation, and mixed nested structures.
|
|
7
|
+
*
|
|
8
|
+
* @param formData - The FormData object to convert
|
|
9
|
+
* @returns A structured object representing the form data
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* Basic usage:
|
|
13
|
+
* ```ts
|
|
14
|
+
* const formData = new FormData();
|
|
15
|
+
* formData.append('name', 'John');
|
|
16
|
+
* formData.append('email', 'john@example.com');
|
|
17
|
+
*
|
|
18
|
+
* const result = formDataToObject(formData);
|
|
19
|
+
* // Result: { name: 'John', email: 'john@example.com' }
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* Nested objects with dot notation:
|
|
24
|
+
* ```ts
|
|
25
|
+
* const formData = new FormData();
|
|
26
|
+
* formData.append('user.name', 'John');
|
|
27
|
+
* formData.append('user.profile.age', '30');
|
|
28
|
+
*
|
|
29
|
+
* const result = formDataToObject(formData);
|
|
30
|
+
* // Result: { user: { name: 'John', profile: { age: '30' } } }
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* Arrays with bracket notation:
|
|
35
|
+
* ```ts
|
|
36
|
+
* const formData = new FormData();
|
|
37
|
+
* formData.append('items[0]', 'apple');
|
|
38
|
+
* formData.append('items[1]', 'banana');
|
|
39
|
+
*
|
|
40
|
+
* const result = formDataToObject(formData);
|
|
41
|
+
* // Result: { items: ['apple', 'banana'] }
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* Multiple values for the same key:
|
|
46
|
+
* ```ts
|
|
47
|
+
* const formData = new FormData();
|
|
48
|
+
* formData.append('tags', 'javascript');
|
|
49
|
+
* formData.append('tags', 'typescript');
|
|
50
|
+
*
|
|
51
|
+
* const result = formDataToObject(formData);
|
|
52
|
+
* // Result: { tags: ['javascript', 'typescript'] }
|
|
53
|
+
* ```
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* Mixed nested structures:
|
|
57
|
+
* ```ts
|
|
58
|
+
* const formData = new FormData();
|
|
59
|
+
* formData.append('users[0].name', 'John');
|
|
60
|
+
* formData.append('users[0].emails[0]', 'john@work.com');
|
|
61
|
+
* formData.append('users[0].emails[1]', 'john@personal.com');
|
|
62
|
+
*
|
|
63
|
+
* const result = formDataToObject(formData);
|
|
64
|
+
* // Result: {
|
|
65
|
+
* // users: [{
|
|
66
|
+
* // name: 'John',
|
|
67
|
+
* // emails: ['john@work.com', 'john@personal.com']
|
|
68
|
+
* // }]
|
|
69
|
+
* // }
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
declare function formDataToObject(formData: FormData): Record<string, unknown>;
|
|
73
|
+
//#endregion
|
|
74
|
+
export { formDataToObject };
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
//#region src/utils.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Converts FormData to a structured JavaScript object
|
|
4
|
+
*
|
|
5
|
+
* This function parses FormData entries and converts them into a nested object structure.
|
|
6
|
+
* It supports dot notation, array notation, and mixed nested structures.
|
|
7
|
+
*
|
|
8
|
+
* @param formData - The FormData object to convert
|
|
9
|
+
* @returns A structured object representing the form data
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* Basic usage:
|
|
13
|
+
* ```ts
|
|
14
|
+
* const formData = new FormData();
|
|
15
|
+
* formData.append('name', 'John');
|
|
16
|
+
* formData.append('email', 'john@example.com');
|
|
17
|
+
*
|
|
18
|
+
* const result = formDataToObject(formData);
|
|
19
|
+
* // Result: { name: 'John', email: 'john@example.com' }
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* Nested objects with dot notation:
|
|
24
|
+
* ```ts
|
|
25
|
+
* const formData = new FormData();
|
|
26
|
+
* formData.append('user.name', 'John');
|
|
27
|
+
* formData.append('user.profile.age', '30');
|
|
28
|
+
*
|
|
29
|
+
* const result = formDataToObject(formData);
|
|
30
|
+
* // Result: { user: { name: 'John', profile: { age: '30' } } }
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* Arrays with bracket notation:
|
|
35
|
+
* ```ts
|
|
36
|
+
* const formData = new FormData();
|
|
37
|
+
* formData.append('items[0]', 'apple');
|
|
38
|
+
* formData.append('items[1]', 'banana');
|
|
39
|
+
*
|
|
40
|
+
* const result = formDataToObject(formData);
|
|
41
|
+
* // Result: { items: ['apple', 'banana'] }
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* Multiple values for the same key:
|
|
46
|
+
* ```ts
|
|
47
|
+
* const formData = new FormData();
|
|
48
|
+
* formData.append('tags', 'javascript');
|
|
49
|
+
* formData.append('tags', 'typescript');
|
|
50
|
+
*
|
|
51
|
+
* const result = formDataToObject(formData);
|
|
52
|
+
* // Result: { tags: ['javascript', 'typescript'] }
|
|
53
|
+
* ```
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* Mixed nested structures:
|
|
57
|
+
* ```ts
|
|
58
|
+
* const formData = new FormData();
|
|
59
|
+
* formData.append('users[0].name', 'John');
|
|
60
|
+
* formData.append('users[0].emails[0]', 'john@work.com');
|
|
61
|
+
* formData.append('users[0].emails[1]', 'john@personal.com');
|
|
62
|
+
*
|
|
63
|
+
* const result = formDataToObject(formData);
|
|
64
|
+
* // Result: {
|
|
65
|
+
* // users: [{
|
|
66
|
+
* // name: 'John',
|
|
67
|
+
* // emails: ['john@work.com', 'john@personal.com']
|
|
68
|
+
* // }]
|
|
69
|
+
* // }
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
declare function formDataToObject(formData: FormData): Record<string, unknown>;
|
|
73
|
+
//#endregion
|
|
74
|
+
export { formDataToObject };
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
|
|
2
|
+
//#region src/internal/assert.ts
|
|
3
|
+
function assert(condition, message) {
|
|
4
|
+
if (!condition) throw new Error(message || "Assertion failed");
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
//#endregion
|
|
8
|
+
//#region src/utils.ts
|
|
9
|
+
function isNumberString(str) {
|
|
10
|
+
return /^\d+$/.test(str);
|
|
11
|
+
}
|
|
12
|
+
function set(obj, path, value) {
|
|
13
|
+
if (path.length > 1) {
|
|
14
|
+
const newPath = [...path];
|
|
15
|
+
const key = newPath.shift();
|
|
16
|
+
assert(key != null);
|
|
17
|
+
const nextKey = newPath[0];
|
|
18
|
+
assert(nextKey != null);
|
|
19
|
+
if (!obj[key]) obj[key] = isNumberString(nextKey) ? [] : {};
|
|
20
|
+
else if (Array.isArray(obj[key]) && !isNumberString(nextKey)) obj[key] = Object.fromEntries(Object.entries(obj[key]));
|
|
21
|
+
set(obj[key], newPath, value);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const p = path[0];
|
|
25
|
+
assert(p != null);
|
|
26
|
+
if (obj[p] === void 0) obj[p] = value;
|
|
27
|
+
else if (Array.isArray(obj[p])) obj[p].push(value);
|
|
28
|
+
else obj[p] = [obj[p], value];
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Converts FormData to a structured JavaScript object
|
|
32
|
+
*
|
|
33
|
+
* This function parses FormData entries and converts them into a nested object structure.
|
|
34
|
+
* It supports dot notation, array notation, and mixed nested structures.
|
|
35
|
+
*
|
|
36
|
+
* @param formData - The FormData object to convert
|
|
37
|
+
* @returns A structured object representing the form data
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* Basic usage:
|
|
41
|
+
* ```ts
|
|
42
|
+
* const formData = new FormData();
|
|
43
|
+
* formData.append('name', 'John');
|
|
44
|
+
* formData.append('email', 'john@example.com');
|
|
45
|
+
*
|
|
46
|
+
* const result = formDataToObject(formData);
|
|
47
|
+
* // Result: { name: 'John', email: 'john@example.com' }
|
|
48
|
+
* ```
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* Nested objects with dot notation:
|
|
52
|
+
* ```ts
|
|
53
|
+
* const formData = new FormData();
|
|
54
|
+
* formData.append('user.name', 'John');
|
|
55
|
+
* formData.append('user.profile.age', '30');
|
|
56
|
+
*
|
|
57
|
+
* const result = formDataToObject(formData);
|
|
58
|
+
* // Result: { user: { name: 'John', profile: { age: '30' } } }
|
|
59
|
+
* ```
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* Arrays with bracket notation:
|
|
63
|
+
* ```ts
|
|
64
|
+
* const formData = new FormData();
|
|
65
|
+
* formData.append('items[0]', 'apple');
|
|
66
|
+
* formData.append('items[1]', 'banana');
|
|
67
|
+
*
|
|
68
|
+
* const result = formDataToObject(formData);
|
|
69
|
+
* // Result: { items: ['apple', 'banana'] }
|
|
70
|
+
* ```
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* Multiple values for the same key:
|
|
74
|
+
* ```ts
|
|
75
|
+
* const formData = new FormData();
|
|
76
|
+
* formData.append('tags', 'javascript');
|
|
77
|
+
* formData.append('tags', 'typescript');
|
|
78
|
+
*
|
|
79
|
+
* const result = formDataToObject(formData);
|
|
80
|
+
* // Result: { tags: ['javascript', 'typescript'] }
|
|
81
|
+
* ```
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* Mixed nested structures:
|
|
85
|
+
* ```ts
|
|
86
|
+
* const formData = new FormData();
|
|
87
|
+
* formData.append('users[0].name', 'John');
|
|
88
|
+
* formData.append('users[0].emails[0]', 'john@work.com');
|
|
89
|
+
* formData.append('users[0].emails[1]', 'john@personal.com');
|
|
90
|
+
*
|
|
91
|
+
* const result = formDataToObject(formData);
|
|
92
|
+
* // Result: {
|
|
93
|
+
* // users: [{
|
|
94
|
+
* // name: 'John',
|
|
95
|
+
* // emails: ['john@work.com', 'john@personal.com']
|
|
96
|
+
* // }]
|
|
97
|
+
* // }
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
100
|
+
function formDataToObject(formData) {
|
|
101
|
+
const obj = {};
|
|
102
|
+
for (const [key, value] of formData.entries()) {
|
|
103
|
+
const parts = key.split(/[\.\[\]]/).filter(Boolean);
|
|
104
|
+
set(obj, parts, value);
|
|
105
|
+
}
|
|
106
|
+
return obj;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
//#endregion
|
|
110
|
+
exports.formDataToObject = formDataToObject;
|
package/dist/utils.mjs
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
//#region src/internal/assert.ts
|
|
2
|
+
function assert(condition, message) {
|
|
3
|
+
if (!condition) throw new Error(message || "Assertion failed");
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
//#endregion
|
|
7
|
+
//#region src/utils.ts
|
|
8
|
+
function isNumberString(str) {
|
|
9
|
+
return /^\d+$/.test(str);
|
|
10
|
+
}
|
|
11
|
+
function set(obj, path, value) {
|
|
12
|
+
if (path.length > 1) {
|
|
13
|
+
const newPath = [...path];
|
|
14
|
+
const key = newPath.shift();
|
|
15
|
+
assert(key != null);
|
|
16
|
+
const nextKey = newPath[0];
|
|
17
|
+
assert(nextKey != null);
|
|
18
|
+
if (!obj[key]) obj[key] = isNumberString(nextKey) ? [] : {};
|
|
19
|
+
else if (Array.isArray(obj[key]) && !isNumberString(nextKey)) obj[key] = Object.fromEntries(Object.entries(obj[key]));
|
|
20
|
+
set(obj[key], newPath, value);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const p = path[0];
|
|
24
|
+
assert(p != null);
|
|
25
|
+
if (obj[p] === void 0) obj[p] = value;
|
|
26
|
+
else if (Array.isArray(obj[p])) obj[p].push(value);
|
|
27
|
+
else obj[p] = [obj[p], value];
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Converts FormData to a structured JavaScript object
|
|
31
|
+
*
|
|
32
|
+
* This function parses FormData entries and converts them into a nested object structure.
|
|
33
|
+
* It supports dot notation, array notation, and mixed nested structures.
|
|
34
|
+
*
|
|
35
|
+
* @param formData - The FormData object to convert
|
|
36
|
+
* @returns A structured object representing the form data
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* Basic usage:
|
|
40
|
+
* ```ts
|
|
41
|
+
* const formData = new FormData();
|
|
42
|
+
* formData.append('name', 'John');
|
|
43
|
+
* formData.append('email', 'john@example.com');
|
|
44
|
+
*
|
|
45
|
+
* const result = formDataToObject(formData);
|
|
46
|
+
* // Result: { name: 'John', email: 'john@example.com' }
|
|
47
|
+
* ```
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* Nested objects with dot notation:
|
|
51
|
+
* ```ts
|
|
52
|
+
* const formData = new FormData();
|
|
53
|
+
* formData.append('user.name', 'John');
|
|
54
|
+
* formData.append('user.profile.age', '30');
|
|
55
|
+
*
|
|
56
|
+
* const result = formDataToObject(formData);
|
|
57
|
+
* // Result: { user: { name: 'John', profile: { age: '30' } } }
|
|
58
|
+
* ```
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* Arrays with bracket notation:
|
|
62
|
+
* ```ts
|
|
63
|
+
* const formData = new FormData();
|
|
64
|
+
* formData.append('items[0]', 'apple');
|
|
65
|
+
* formData.append('items[1]', 'banana');
|
|
66
|
+
*
|
|
67
|
+
* const result = formDataToObject(formData);
|
|
68
|
+
* // Result: { items: ['apple', 'banana'] }
|
|
69
|
+
* ```
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* Multiple values for the same key:
|
|
73
|
+
* ```ts
|
|
74
|
+
* const formData = new FormData();
|
|
75
|
+
* formData.append('tags', 'javascript');
|
|
76
|
+
* formData.append('tags', 'typescript');
|
|
77
|
+
*
|
|
78
|
+
* const result = formDataToObject(formData);
|
|
79
|
+
* // Result: { tags: ['javascript', 'typescript'] }
|
|
80
|
+
* ```
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* Mixed nested structures:
|
|
84
|
+
* ```ts
|
|
85
|
+
* const formData = new FormData();
|
|
86
|
+
* formData.append('users[0].name', 'John');
|
|
87
|
+
* formData.append('users[0].emails[0]', 'john@work.com');
|
|
88
|
+
* formData.append('users[0].emails[1]', 'john@personal.com');
|
|
89
|
+
*
|
|
90
|
+
* const result = formDataToObject(formData);
|
|
91
|
+
* // Result: {
|
|
92
|
+
* // users: [{
|
|
93
|
+
* // name: 'John',
|
|
94
|
+
* // emails: ['john@work.com', 'john@personal.com']
|
|
95
|
+
* // }]
|
|
96
|
+
* // }
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
function formDataToObject(formData) {
|
|
100
|
+
const obj = {};
|
|
101
|
+
for (const [key, value] of formData.entries()) {
|
|
102
|
+
const parts = key.split(/[\.\[\]]/).filter(Boolean);
|
|
103
|
+
set(obj, parts, value);
|
|
104
|
+
}
|
|
105
|
+
return obj;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
//#endregion
|
|
109
|
+
export { formDataToObject };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "server-act",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.1",
|
|
4
4
|
"homepage": "https://github.com/chungweileong94/server-act#readme",
|
|
5
5
|
"author": "chungweileong94",
|
|
6
6
|
"license": "MIT",
|
|
@@ -11,20 +11,18 @@
|
|
|
11
11
|
"bugs": {
|
|
12
12
|
"url": "https://github.com/chungweileong94/server-act/issues"
|
|
13
13
|
},
|
|
14
|
-
"main": "dist/index.js",
|
|
15
|
-
"module": "dist/index.mjs",
|
|
16
|
-
"types": "./dist/index.d.ts",
|
|
17
14
|
"exports": {
|
|
18
15
|
".": {
|
|
19
|
-
"
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"import": "./dist/index.mjs",
|
|
18
|
+
"require": "./dist/index.js"
|
|
19
|
+
},
|
|
20
|
+
"./utils": {
|
|
21
|
+
"types": "./dist/utils.d.ts",
|
|
22
|
+
"import": "./dist/utils.mjs",
|
|
23
|
+
"require": "./dist/utils.js"
|
|
24
|
+
},
|
|
25
|
+
"./package.json": "./package.json"
|
|
28
26
|
},
|
|
29
27
|
"files": [
|
|
30
28
|
"dist",
|