server-act 1.5.3 → 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 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
- import { zfd } from "zod-form-data";
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
- zfd.formData({
107
- name: zfd.text(
108
- z
109
- .string({ required_error: `You haven't told me your name` })
110
- .max(20, { message: "Any shorter name? You name is too long 😬" }),
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
- .formAction(async ({ formData, input, formErrors, ctx }) => {
115
- if (formErrors) {
116
- return { formData, formErrors: formErrors.fieldErrors };
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
- ```tsx
123
- // client-component.tsx
124
- "use client";
130
+ ## Utilities
125
131
 
126
- import { useActionState } from "react";
127
- import { sayHelloAction } from "./action";
132
+ ### `formDataToObject`
128
133
 
129
- export const ClientComponent = () => {
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
- return (
133
- <form action={dispatch}>
134
- <input
135
- name="name"
136
- required
137
- defaultValue={state?.formData?.get("name")?.toString()}
138
- />
139
- {state?.formErrors?.name?.map((error) => <p key={error}>{error}</p>)}
140
-
141
- <button type="submit">Submit</button>
142
-
143
- {!!state?.message && <p>{state.message}</p>}
144
- </form>
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/utils.d.ts
4
- declare function getFormErrors(issues: ReadonlyArray<StandardSchemaV1.Issue>): {
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
  };
@@ -47,6 +47,22 @@ interface ActionBuilder<TParams extends ActionParams> {
47
47
  /**
48
48
  * Create an action for React `useActionState`
49
49
  */
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
+ */
50
66
  formAction: <TState, TPrevState = UnsetMarker>(action: (params: Prettify<{
51
67
  ctx: InferContextType<TParams["_context"]>;
52
68
  prevState: RemoveUnsetMarker<TPrevState>;
@@ -56,7 +72,7 @@ interface ActionBuilder<TParams extends ActionParams> {
56
72
  formErrors?: undefined;
57
73
  } | {
58
74
  input?: undefined;
59
- formErrors: ReturnType<typeof getFormErrors>;
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/utils.d.ts
4
- declare function getFormErrors(issues: ReadonlyArray<StandardSchemaV1.Issue>): {
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
  };
@@ -47,6 +47,22 @@ interface ActionBuilder<TParams extends ActionParams> {
47
47
  /**
48
48
  * Create an action for React `useActionState`
49
49
  */
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
+ */
50
66
  formAction: <TState, TPrevState = UnsetMarker>(action: (params: Prettify<{
51
67
  ctx: InferContextType<TParams["_context"]>;
52
68
  prevState: RemoveUnsetMarker<TPrevState>;
@@ -56,7 +72,7 @@ interface ActionBuilder<TParams extends ActionParams> {
56
72
  formErrors?: undefined;
57
73
  } | {
58
74
  input?: undefined;
59
- formErrors: ReturnType<typeof getFormErrors>;
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/utils.ts
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 getFormErrors(issues) {
32
+ function getInputErrors(issues) {
33
33
  const messages = [];
34
34
  const fieldErrors = {};
35
35
  for (const issue of issues) {
@@ -83,6 +83,33 @@ function createServerActionBuilder(initDef = {}) {
83
83
  });
84
84
  };
85
85
  },
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
+ },
86
113
  formAction: (action) => {
87
114
  return async (prevState, formData) => {
88
115
  const ctx = await _def.middleware?.();
@@ -93,7 +120,7 @@ function createServerActionBuilder(initDef = {}) {
93
120
  ctx,
94
121
  prevState,
95
122
  formData,
96
- formErrors: getFormErrors(result.issues)
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/utils.ts
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 getFormErrors(issues) {
9
+ function getInputErrors(issues) {
10
10
  const messages = [];
11
11
  const fieldErrors = {};
12
12
  for (const issue of issues) {
@@ -60,6 +60,33 @@ function createServerActionBuilder(initDef = {}) {
60
60
  });
61
61
  };
62
62
  },
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
+ },
63
90
  formAction: (action) => {
64
91
  return async (prevState, formData) => {
65
92
  const ctx = await _def.middleware?.();
@@ -70,7 +97,7 @@ function createServerActionBuilder(initDef = {}) {
70
97
  ctx,
71
98
  prevState,
72
99
  formData,
73
- formErrors: getFormErrors(result.issues)
100
+ formErrors: getInputErrors(result.issues)
74
101
  });
75
102
  return await action({
76
103
  ctx,
@@ -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 };
@@ -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.5.3",
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
- "import": {
20
- "types": "./dist/index.d.mts",
21
- "default": "./dist/index.mjs"
22
- },
23
- "require": {
24
- "types": "./dist/index.d.ts",
25
- "default": "./dist/index.js"
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",