server-act 1.6.1 → 1.8.0

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
@@ -69,12 +69,12 @@ import { serverAct } from "server-act";
69
69
  import { z } from "zod";
70
70
 
71
71
  export const sayHelloAction = serverAct
72
- .middleware(() => {
72
+ .use(({ next }) => {
73
73
  const t = i18n();
74
- const userId = "..."
75
- return { t, userId };
74
+ const userId = "...";
75
+ return next({ ctx: { t, userId } });
76
76
  })
77
- .input((ctx) => {
77
+ .input(({ ctx }) => {
78
78
  return z.object({
79
79
  name: z.string().min(1, { message: ctx.t("form.name.required") }),
80
80
  });
@@ -85,6 +85,91 @@ export const sayHelloAction = serverAct
85
85
  });
86
86
  ```
87
87
 
88
+ #### Chaining Middlewares
89
+
90
+ You can chain multiple middlewares by calling `.use(...)` repeatedly.
91
+
92
+ - Middlewares run in registration order.
93
+ - Each middleware receives the current `ctx` and forwards additions with `next({ ctx })`.
94
+ - `next()` can be called without params when nothing needs to be added.
95
+ - `next({ ctx })` shallow-merges the provided keys into the current context.
96
+ - Later middleware values override earlier values for the same key.
97
+ - Errors thrown in middleware propagate and stop later middleware from running.
98
+
99
+ ```ts
100
+ // action.ts
101
+ "use server";
102
+
103
+ import { serverAct } from "server-act";
104
+
105
+ export const createGreetingAction = serverAct
106
+ .use(({ next }) =>
107
+ next({
108
+ ctx: {
109
+ requestId: crypto.randomUUID(),
110
+ role: "user",
111
+ },
112
+ }),
113
+ )
114
+ .use(({ ctx, next }) =>
115
+ next({
116
+ ctx: {
117
+ role: "admin", // overrides previous role
118
+ actorLabel: `${ctx.role}-actor`,
119
+ },
120
+ }),
121
+ )
122
+ .use(({ ctx, next }) =>
123
+ next({
124
+ ctx: {
125
+ trace: `${ctx.requestId}:${ctx.actorLabel}`,
126
+ },
127
+ }),
128
+ )
129
+ .action(async ({ ctx }) => {
130
+ return `${ctx.role} -> ${ctx.trace}`;
131
+ });
132
+ ```
133
+
134
+ #### Migrating From `.middleware()`
135
+
136
+ `.middleware()` is still supported for backward compatibility, but it is deprecated in favor of `.use()`.
137
+
138
+ ```ts
139
+ const legacyAction = serverAct.middleware(({ ctx }) => ({
140
+ user: getUser(),
141
+ }));
142
+
143
+ const nextStyleAction = serverAct.use(({ ctx, next }) =>
144
+ next({
145
+ ctx: {
146
+ user: getUser(),
147
+ },
148
+ }),
149
+ );
150
+ ```
151
+
152
+ #### Reusable Middleware
153
+
154
+ Use `createServerActMiddleware` to define middleware once and reuse it across actions.
155
+
156
+ ```ts
157
+ import { createServerActMiddleware, serverAct } from "server-act";
158
+
159
+ const requestIdMiddleware = createServerActMiddleware(({ next }) =>
160
+ next({ ctx: { requestId: crypto.randomUUID() } }),
161
+ );
162
+
163
+ const traceMiddleware = createServerActMiddleware(({ ctx, next }) =>
164
+ next({ ctx: { trace: `${ctx.requestId}-trace` } }),
165
+ );
166
+
167
+ export const action = serverAct
168
+ .use(requestIdMiddleware)
169
+ .use(traceMiddleware)
170
+ .action(async ({ ctx }) => `${ctx.requestId}:${ctx.trace}`);
171
+ ```
172
+
88
173
  ### `useActionState` Support
89
174
 
90
175
  > `useActionState` Documentation:
@@ -99,9 +184,7 @@ import { serverAct } from "server-act";
99
184
  import { formDataToObject } from "server-act/utils";
100
185
  import { z } from "zod";
101
186
 
102
- function zodFormData<T extends z.ZodType>(
103
- schema: T,
104
- ): z.ZodPipe<z.ZodTransform<Record<string, unknown>, FormData>, T> {
187
+ function zodFormData<T extends z.ZodType>(schema: T) {
105
188
  return z.preprocess<Record<string, unknown>, T, FormData>(
106
189
  (v) => formDataToObject(v),
107
190
  schema,
@@ -141,7 +224,7 @@ import { formDataToObject } from "server-act/utils";
141
224
 
142
225
  ```ts
143
226
  const formData = new FormData();
144
- formData.append('name', 'John');
227
+ formData.append("name", "John");
145
228
 
146
229
  const result = formDataToObject(formData);
147
230
  // Result: { name: 'John' }
@@ -151,7 +234,7 @@ const result = formDataToObject(formData);
151
234
 
152
235
  ```ts
153
236
  const formData = new FormData();
154
- formData.append('user.name', 'John');
237
+ formData.append("user.name", "John");
155
238
 
156
239
  const result = formDataToObject(formData);
157
240
  // Result: { user: { name: 'John' } }
@@ -166,9 +249,7 @@ import { serverAct } from "server-act";
166
249
  import { formDataToObject } from "server-act/utils";
167
250
  import { z } from "zod";
168
251
 
169
- function zodFormData<T extends z.ZodType>(
170
- schema: T,
171
- ): z.ZodPipe<z.ZodTransform<Record<string, unknown>, FormData>, T> {
252
+ function zodFormData<T extends z.ZodType>(schema: T) {
172
253
  return z.preprocess<Record<string, unknown>, T, FormData>(
173
254
  (v) => formDataToObject(v),
174
255
  schema,
package/dist/index.cjs ADDED
@@ -0,0 +1,173 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let _standard_schema_utils = require("@standard-schema/utils");
3
+ //#region src/internal/middleware.ts
4
+ function normalizeCtx(ctx) {
5
+ return ctx && typeof ctx === "object" ? { ...ctx } : {};
6
+ }
7
+ /**
8
+ * Executes an array of middleware functions with the given initial context.
9
+ */
10
+ async function executeMiddlewares(middlewares, initialCtx) {
11
+ const executeAt = async (index, ctx) => {
12
+ const entry = middlewares[index];
13
+ if (!entry) return ctx;
14
+ if (entry.kind === "legacy") {
15
+ const result = await entry.middleware({ ctx });
16
+ const nextCtx = result && typeof result === "object" ? {
17
+ ...ctx,
18
+ ...result
19
+ } : ctx;
20
+ return await executeAt(index + 1, nextCtx);
21
+ }
22
+ let nextCalled = false;
23
+ const result = await entry.middleware({
24
+ ctx,
25
+ next: async (opts) => {
26
+ nextCalled = true;
27
+ const nextCtx = opts?.ctx ? {
28
+ ...ctx,
29
+ ...opts.ctx
30
+ } : ctx;
31
+ return await executeAt(index + 1, nextCtx);
32
+ }
33
+ });
34
+ if (!nextCalled) throw new Error(".use() middleware must call next()");
35
+ return normalizeCtx(result);
36
+ };
37
+ return await executeAt(0, normalizeCtx(initialCtx));
38
+ }
39
+ //#endregion
40
+ //#region src/internal/schema.ts
41
+ async function standardValidate(schema, input) {
42
+ let result = schema["~standard"].validate(input);
43
+ if (result instanceof Promise) result = await result;
44
+ return result;
45
+ }
46
+ function getInputErrors(issues) {
47
+ const messages = [];
48
+ const fieldErrors = {};
49
+ for (const issue of issues) {
50
+ const dotPath = (0, _standard_schema_utils.getDotPath)(issue);
51
+ if (dotPath) if (fieldErrors[dotPath]) fieldErrors[dotPath].push(issue.message);
52
+ else fieldErrors[dotPath] = [issue.message];
53
+ else messages.push(issue.message);
54
+ }
55
+ return {
56
+ messages,
57
+ fieldErrors
58
+ };
59
+ }
60
+ //#endregion
61
+ //#region src/index.ts
62
+ function createNewServerActionBuilder(def) {
63
+ return createServerActionBuilder(def);
64
+ }
65
+ function createServerActMiddleware(middleware) {
66
+ return middleware;
67
+ }
68
+ function createServerActionBuilder(initDef = {}) {
69
+ const _def = {
70
+ input: void 0,
71
+ middleware: [],
72
+ ...initDef
73
+ };
74
+ return {
75
+ middleware: (middleware) => createNewServerActionBuilder({
76
+ ..._def,
77
+ middleware: [..._def.middleware, {
78
+ kind: "legacy",
79
+ middleware
80
+ }]
81
+ }),
82
+ use: ((middleware) => createNewServerActionBuilder({
83
+ ..._def,
84
+ middleware: [..._def.middleware, {
85
+ kind: "use",
86
+ middleware
87
+ }]
88
+ })),
89
+ input: (input) => createNewServerActionBuilder({
90
+ ..._def,
91
+ input
92
+ }),
93
+ action: (action) => {
94
+ return async (input) => {
95
+ let ctx = {};
96
+ if (_def.middleware.length > 0) ctx = await executeMiddlewares(_def.middleware, ctx);
97
+ if (_def.input) {
98
+ const result = await standardValidate(typeof _def.input === "function" ? await _def.input({ ctx }) : _def.input, input);
99
+ if (result.issues) throw new _standard_schema_utils.SchemaError(result.issues);
100
+ return await action({
101
+ ctx,
102
+ input: result.value
103
+ });
104
+ }
105
+ return await action({
106
+ ctx,
107
+ input: void 0
108
+ });
109
+ };
110
+ },
111
+ stateAction: (action) => {
112
+ return async (prevState, rawInput) => {
113
+ let ctx = {};
114
+ if (_def.middleware.length > 0) ctx = await executeMiddlewares(_def.middleware, ctx);
115
+ if (_def.input) {
116
+ const result = await standardValidate(typeof _def.input === "function" ? await _def.input({ ctx }) : _def.input, rawInput);
117
+ if (result.issues) return await action({
118
+ ctx,
119
+ prevState,
120
+ rawInput,
121
+ inputErrors: getInputErrors(result.issues)
122
+ });
123
+ return await action({
124
+ ctx,
125
+ prevState,
126
+ rawInput,
127
+ input: result.value
128
+ });
129
+ }
130
+ return await action({
131
+ ctx,
132
+ prevState,
133
+ rawInput,
134
+ input: void 0
135
+ });
136
+ };
137
+ },
138
+ formAction: (action) => {
139
+ return async (prevState, formData) => {
140
+ let ctx = {};
141
+ if (_def.middleware.length > 0) ctx = await executeMiddlewares(_def.middleware, ctx);
142
+ if (_def.input) {
143
+ const result = await standardValidate(typeof _def.input === "function" ? await _def.input({ ctx }) : _def.input, formData);
144
+ if (result.issues) return await action({
145
+ ctx,
146
+ prevState,
147
+ formData,
148
+ formErrors: getInputErrors(result.issues)
149
+ });
150
+ return await action({
151
+ ctx,
152
+ prevState,
153
+ formData,
154
+ input: result.value
155
+ });
156
+ }
157
+ return await action({
158
+ ctx,
159
+ prevState,
160
+ formData,
161
+ input: void 0
162
+ });
163
+ };
164
+ }
165
+ };
166
+ }
167
+ /**
168
+ * Server action builder
169
+ */
170
+ const serverAct = createServerActionBuilder();
171
+ //#endregion
172
+ exports.createServerActMiddleware = createServerActMiddleware;
173
+ exports.serverAct = serverAct;
@@ -1,5 +1,19 @@
1
1
  import { StandardSchemaV1 } from "@standard-schema/spec";
2
2
 
3
+ //#region src/internal/middleware.d.ts
4
+ type LegacyMiddlewareFunction<TContext, TReturn> = (params: {
5
+ ctx: TContext;
6
+ }) => Promise<TReturn> | TReturn;
7
+ type MiddlewareContext = Record<string, unknown>;
8
+ type Prettify$1<T> = { [P in keyof T]: T[P] } & {};
9
+ type MiddlewareNextFunction<TContext extends MiddlewareContext> = <TAddedContext extends MiddlewareContext>(opts?: {
10
+ ctx?: TAddedContext;
11
+ }) => Promise<Prettify$1<TContext & TAddedContext>>;
12
+ type UseMiddlewareFunction<TContext extends MiddlewareContext, TNextContext extends MiddlewareContext> = (params: {
13
+ ctx: TContext;
14
+ next: MiddlewareNextFunction<TContext>;
15
+ }) => Promise<TNextContext> | TNextContext;
16
+ //#endregion
3
17
  //#region src/internal/schema.d.ts
4
18
  declare function getInputErrors(issues: ReadonlyArray<StandardSchemaV1.Issue>): {
5
19
  messages: string[];
@@ -10,12 +24,12 @@ declare function getInputErrors(issues: ReadonlyArray<StandardSchemaV1.Issue>):
10
24
  declare const unsetMarker: unique symbol;
11
25
  type UnsetMarker = typeof unsetMarker;
12
26
  type RemoveUnsetMarker<T> = T extends UnsetMarker ? undefined : T;
27
+ type NormalizeContext<T> = RemoveUnsetMarker<T> extends Record<string, unknown> ? RemoveUnsetMarker<T> : {};
13
28
  type Equals<X, Y> = (<T>() => T extends X ? 1 : 2) extends (<T>() => T extends Y ? 1 : 2) ? true : false;
14
29
  type Prettify<T> = { [P in keyof T]: T[P] } & {};
15
30
  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;
16
31
  type InferParserType<T, TType extends "in" | "out"> = T extends StandardSchemaV1 ? TType extends "in" ? StandardSchemaV1.InferInput<T> : StandardSchemaV1.InferOutput<T> : never;
17
32
  type InferInputType<T, TType extends "in" | "out"> = T extends UnsetMarker ? undefined : InferParserType<T, TType>;
18
- type InferContextType<T> = RemoveUnsetMarker<T>;
19
33
  interface ActionParams<TInput = unknown, TContext = unknown> {
20
34
  _input: TInput;
21
35
  _context: TContext;
@@ -23,16 +37,28 @@ interface ActionParams<TInput = unknown, TContext = unknown> {
23
37
  interface ActionBuilder<TParams extends ActionParams> {
24
38
  /**
25
39
  * Middleware allows you to run code before the action, its return value will pass as context to the action.
40
+ *
41
+ * Chaining multiple middlewares is possible, each middleware receives context from previous middlewares
42
+ * and returns additional context that gets merged.
43
+ *
44
+ * @deprecated Use `.use()` instead.
45
+ */
46
+ middleware: <TNewContext>(middleware: LegacyMiddlewareFunction<NormalizeContext<TParams["_context"]>, TNewContext>) => ActionBuilder<{
47
+ _input: TParams["_input"];
48
+ _context: TParams["_context"] extends UnsetMarker ? TNewContext : Prettify<TParams["_context"] & TNewContext>;
49
+ }>;
50
+ /**
51
+ * tRPC-style middleware that forwards context via `next()`.
26
52
  */
27
- middleware: <TContext>(middleware: () => Promise<TContext> | TContext) => Omit<ActionBuilder<{
53
+ use: <TNextContext extends Record<string, unknown>>(middleware: UseMiddlewareFunction<NormalizeContext<TParams["_context"]>, TNextContext>) => ActionBuilder<{
28
54
  _input: TParams["_input"];
29
- _context: TContext;
30
- }>, "middleware">;
55
+ _context: TNextContext;
56
+ }>;
31
57
  /**
32
58
  * Input validation for the action.
33
59
  */
34
60
  input: <TParser extends StandardSchemaV1>(input: ((params: {
35
- ctx: InferContextType<TParams["_context"]>;
61
+ ctx: NormalizeContext<TParams["_context"]>;
36
62
  }) => Promise<TParser> | TParser) | TParser) => Omit<ActionBuilder<{
37
63
  _input: TParser;
38
64
  _context: TParams["_context"];
@@ -41,14 +67,14 @@ interface ActionBuilder<TParams extends ActionParams> {
41
67
  * Create an action.
42
68
  */
43
69
  action: <TOutput>(action: (params: {
44
- ctx: InferContextType<TParams["_context"]>;
70
+ ctx: NormalizeContext<TParams["_context"]>;
45
71
  input: InferInputType<TParams["_input"], "out">;
46
72
  }) => Promise<TOutput>) => SanitizeFunctionParam<(input: InferInputType<TParams["_input"], "in">) => Promise<TOutput>>;
47
73
  /**
48
74
  * Create an action for React `useActionState`
49
75
  */
50
76
  stateAction: <TState, TPrevState = UnsetMarker>(action: (params: Prettify<{
51
- ctx: InferContextType<TParams["_context"]>;
77
+ ctx: NormalizeContext<TParams["_context"]>;
52
78
  prevState: RemoveUnsetMarker<TPrevState>;
53
79
  rawInput: InferInputType<TParams["_input"], "in">;
54
80
  } & ({
@@ -64,7 +90,7 @@ interface ActionBuilder<TParams extends ActionParams> {
64
90
  * @deprecated Use `stateAction` instead.
65
91
  */
66
92
  formAction: <TState, TPrevState = UnsetMarker>(action: (params: Prettify<{
67
- ctx: InferContextType<TParams["_context"]>;
93
+ ctx: NormalizeContext<TParams["_context"]>;
68
94
  prevState: RemoveUnsetMarker<TPrevState>;
69
95
  formData: FormData;
70
96
  } & ({
@@ -75,6 +101,7 @@ interface ActionBuilder<TParams extends ActionParams> {
75
101
  formErrors: ReturnType<typeof getInputErrors>;
76
102
  })>) => Promise<TState>) => (prevState: TState | RemoveUnsetMarker<TPrevState>, formData: InferInputType<TParams["_input"], "in">) => Promise<TState | RemoveUnsetMarker<TPrevState>>;
77
103
  }
104
+ declare function createServerActMiddleware<TAddedContext extends Record<string, unknown>, TContext extends Record<string, unknown> = {}>(middleware: UseMiddlewareFunction<TContext, Prettify<TContext & TAddedContext>>): UseMiddlewareFunction<TContext, Prettify<TContext & TAddedContext>>;
78
105
  /**
79
106
  * Server action builder
80
107
  */
@@ -83,4 +110,4 @@ declare const serverAct: ActionBuilder<{
83
110
  _context: UnsetMarker;
84
111
  }>;
85
112
  //#endregion
86
- export { serverAct };
113
+ export { createServerActMiddleware, serverAct };
package/dist/index.d.mts CHANGED
@@ -1,5 +1,19 @@
1
1
  import { StandardSchemaV1 } from "@standard-schema/spec";
2
2
 
3
+ //#region src/internal/middleware.d.ts
4
+ type LegacyMiddlewareFunction<TContext, TReturn> = (params: {
5
+ ctx: TContext;
6
+ }) => Promise<TReturn> | TReturn;
7
+ type MiddlewareContext = Record<string, unknown>;
8
+ type Prettify$1<T> = { [P in keyof T]: T[P] } & {};
9
+ type MiddlewareNextFunction<TContext extends MiddlewareContext> = <TAddedContext extends MiddlewareContext>(opts?: {
10
+ ctx?: TAddedContext;
11
+ }) => Promise<Prettify$1<TContext & TAddedContext>>;
12
+ type UseMiddlewareFunction<TContext extends MiddlewareContext, TNextContext extends MiddlewareContext> = (params: {
13
+ ctx: TContext;
14
+ next: MiddlewareNextFunction<TContext>;
15
+ }) => Promise<TNextContext> | TNextContext;
16
+ //#endregion
3
17
  //#region src/internal/schema.d.ts
4
18
  declare function getInputErrors(issues: ReadonlyArray<StandardSchemaV1.Issue>): {
5
19
  messages: string[];
@@ -10,12 +24,12 @@ declare function getInputErrors(issues: ReadonlyArray<StandardSchemaV1.Issue>):
10
24
  declare const unsetMarker: unique symbol;
11
25
  type UnsetMarker = typeof unsetMarker;
12
26
  type RemoveUnsetMarker<T> = T extends UnsetMarker ? undefined : T;
27
+ type NormalizeContext<T> = RemoveUnsetMarker<T> extends Record<string, unknown> ? RemoveUnsetMarker<T> : {};
13
28
  type Equals<X, Y> = (<T>() => T extends X ? 1 : 2) extends (<T>() => T extends Y ? 1 : 2) ? true : false;
14
29
  type Prettify<T> = { [P in keyof T]: T[P] } & {};
15
30
  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;
16
31
  type InferParserType<T, TType extends "in" | "out"> = T extends StandardSchemaV1 ? TType extends "in" ? StandardSchemaV1.InferInput<T> : StandardSchemaV1.InferOutput<T> : never;
17
32
  type InferInputType<T, TType extends "in" | "out"> = T extends UnsetMarker ? undefined : InferParserType<T, TType>;
18
- type InferContextType<T> = RemoveUnsetMarker<T>;
19
33
  interface ActionParams<TInput = unknown, TContext = unknown> {
20
34
  _input: TInput;
21
35
  _context: TContext;
@@ -23,16 +37,28 @@ interface ActionParams<TInput = unknown, TContext = unknown> {
23
37
  interface ActionBuilder<TParams extends ActionParams> {
24
38
  /**
25
39
  * Middleware allows you to run code before the action, its return value will pass as context to the action.
40
+ *
41
+ * Chaining multiple middlewares is possible, each middleware receives context from previous middlewares
42
+ * and returns additional context that gets merged.
43
+ *
44
+ * @deprecated Use `.use()` instead.
45
+ */
46
+ middleware: <TNewContext>(middleware: LegacyMiddlewareFunction<NormalizeContext<TParams["_context"]>, TNewContext>) => ActionBuilder<{
47
+ _input: TParams["_input"];
48
+ _context: TParams["_context"] extends UnsetMarker ? TNewContext : Prettify<TParams["_context"] & TNewContext>;
49
+ }>;
50
+ /**
51
+ * tRPC-style middleware that forwards context via `next()`.
26
52
  */
27
- middleware: <TContext>(middleware: () => Promise<TContext> | TContext) => Omit<ActionBuilder<{
53
+ use: <TNextContext extends Record<string, unknown>>(middleware: UseMiddlewareFunction<NormalizeContext<TParams["_context"]>, TNextContext>) => ActionBuilder<{
28
54
  _input: TParams["_input"];
29
- _context: TContext;
30
- }>, "middleware">;
55
+ _context: TNextContext;
56
+ }>;
31
57
  /**
32
58
  * Input validation for the action.
33
59
  */
34
60
  input: <TParser extends StandardSchemaV1>(input: ((params: {
35
- ctx: InferContextType<TParams["_context"]>;
61
+ ctx: NormalizeContext<TParams["_context"]>;
36
62
  }) => Promise<TParser> | TParser) | TParser) => Omit<ActionBuilder<{
37
63
  _input: TParser;
38
64
  _context: TParams["_context"];
@@ -41,14 +67,14 @@ interface ActionBuilder<TParams extends ActionParams> {
41
67
  * Create an action.
42
68
  */
43
69
  action: <TOutput>(action: (params: {
44
- ctx: InferContextType<TParams["_context"]>;
70
+ ctx: NormalizeContext<TParams["_context"]>;
45
71
  input: InferInputType<TParams["_input"], "out">;
46
72
  }) => Promise<TOutput>) => SanitizeFunctionParam<(input: InferInputType<TParams["_input"], "in">) => Promise<TOutput>>;
47
73
  /**
48
74
  * Create an action for React `useActionState`
49
75
  */
50
76
  stateAction: <TState, TPrevState = UnsetMarker>(action: (params: Prettify<{
51
- ctx: InferContextType<TParams["_context"]>;
77
+ ctx: NormalizeContext<TParams["_context"]>;
52
78
  prevState: RemoveUnsetMarker<TPrevState>;
53
79
  rawInput: InferInputType<TParams["_input"], "in">;
54
80
  } & ({
@@ -64,7 +90,7 @@ interface ActionBuilder<TParams extends ActionParams> {
64
90
  * @deprecated Use `stateAction` instead.
65
91
  */
66
92
  formAction: <TState, TPrevState = UnsetMarker>(action: (params: Prettify<{
67
- ctx: InferContextType<TParams["_context"]>;
93
+ ctx: NormalizeContext<TParams["_context"]>;
68
94
  prevState: RemoveUnsetMarker<TPrevState>;
69
95
  formData: FormData;
70
96
  } & ({
@@ -75,6 +101,7 @@ interface ActionBuilder<TParams extends ActionParams> {
75
101
  formErrors: ReturnType<typeof getInputErrors>;
76
102
  })>) => Promise<TState>) => (prevState: TState | RemoveUnsetMarker<TPrevState>, formData: InferInputType<TParams["_input"], "in">) => Promise<TState | RemoveUnsetMarker<TPrevState>>;
77
103
  }
104
+ declare function createServerActMiddleware<TAddedContext extends Record<string, unknown>, TContext extends Record<string, unknown> = {}>(middleware: UseMiddlewareFunction<TContext, Prettify<TContext & TAddedContext>>): UseMiddlewareFunction<TContext, Prettify<TContext & TAddedContext>>;
78
105
  /**
79
106
  * Server action builder
80
107
  */
@@ -83,4 +110,4 @@ declare const serverAct: ActionBuilder<{
83
110
  _context: UnsetMarker;
84
111
  }>;
85
112
  //#endregion
86
- export { serverAct };
113
+ export { createServerActMiddleware, serverAct };
package/dist/index.mjs CHANGED
@@ -1,5 +1,41 @@
1
1
  import { SchemaError, getDotPath } from "@standard-schema/utils";
2
-
2
+ //#region src/internal/middleware.ts
3
+ function normalizeCtx(ctx) {
4
+ return ctx && typeof ctx === "object" ? { ...ctx } : {};
5
+ }
6
+ /**
7
+ * Executes an array of middleware functions with the given initial context.
8
+ */
9
+ async function executeMiddlewares(middlewares, initialCtx) {
10
+ const executeAt = async (index, ctx) => {
11
+ const entry = middlewares[index];
12
+ if (!entry) return ctx;
13
+ if (entry.kind === "legacy") {
14
+ const result = await entry.middleware({ ctx });
15
+ const nextCtx = result && typeof result === "object" ? {
16
+ ...ctx,
17
+ ...result
18
+ } : ctx;
19
+ return await executeAt(index + 1, nextCtx);
20
+ }
21
+ let nextCalled = false;
22
+ const result = await entry.middleware({
23
+ ctx,
24
+ next: async (opts) => {
25
+ nextCalled = true;
26
+ const nextCtx = opts?.ctx ? {
27
+ ...ctx,
28
+ ...opts.ctx
29
+ } : ctx;
30
+ return await executeAt(index + 1, nextCtx);
31
+ }
32
+ });
33
+ if (!nextCalled) throw new Error(".use() middleware must call next()");
34
+ return normalizeCtx(result);
35
+ };
36
+ return await executeAt(0, normalizeCtx(initialCtx));
37
+ }
38
+ //#endregion
3
39
  //#region src/internal/schema.ts
4
40
  async function standardValidate(schema, input) {
5
41
  let result = schema["~standard"].validate(input);
@@ -20,34 +56,45 @@ function getInputErrors(issues) {
20
56
  fieldErrors
21
57
  };
22
58
  }
23
-
24
59
  //#endregion
25
60
  //#region src/index.ts
26
- const unsetMarker = Symbol("unsetMarker");
27
61
  function createNewServerActionBuilder(def) {
28
62
  return createServerActionBuilder(def);
29
63
  }
64
+ function createServerActMiddleware(middleware) {
65
+ return middleware;
66
+ }
30
67
  function createServerActionBuilder(initDef = {}) {
31
68
  const _def = {
32
69
  input: void 0,
33
- middleware: void 0,
70
+ middleware: [],
34
71
  ...initDef
35
72
  };
36
73
  return {
37
74
  middleware: (middleware) => createNewServerActionBuilder({
38
75
  ..._def,
39
- middleware
76
+ middleware: [..._def.middleware, {
77
+ kind: "legacy",
78
+ middleware
79
+ }]
40
80
  }),
81
+ use: ((middleware) => createNewServerActionBuilder({
82
+ ..._def,
83
+ middleware: [..._def.middleware, {
84
+ kind: "use",
85
+ middleware
86
+ }]
87
+ })),
41
88
  input: (input) => createNewServerActionBuilder({
42
89
  ..._def,
43
90
  input
44
91
  }),
45
92
  action: (action) => {
46
93
  return async (input) => {
47
- const ctx = await _def.middleware?.();
94
+ let ctx = {};
95
+ if (_def.middleware.length > 0) ctx = await executeMiddlewares(_def.middleware, ctx);
48
96
  if (_def.input) {
49
- const inputSchema = typeof _def.input === "function" ? await _def.input({ ctx }) : _def.input;
50
- const result = await standardValidate(inputSchema, input);
97
+ const result = await standardValidate(typeof _def.input === "function" ? await _def.input({ ctx }) : _def.input, input);
51
98
  if (result.issues) throw new SchemaError(result.issues);
52
99
  return await action({
53
100
  ctx,
@@ -62,10 +109,10 @@ function createServerActionBuilder(initDef = {}) {
62
109
  },
63
110
  stateAction: (action) => {
64
111
  return async (prevState, rawInput) => {
65
- const ctx = await _def.middleware?.();
112
+ let ctx = {};
113
+ if (_def.middleware.length > 0) ctx = await executeMiddlewares(_def.middleware, ctx);
66
114
  if (_def.input) {
67
- const inputSchema = typeof _def.input === "function" ? await _def.input({ ctx }) : _def.input;
68
- const result = await standardValidate(inputSchema, rawInput);
115
+ const result = await standardValidate(typeof _def.input === "function" ? await _def.input({ ctx }) : _def.input, rawInput);
69
116
  if (result.issues) return await action({
70
117
  ctx,
71
118
  prevState,
@@ -89,10 +136,10 @@ function createServerActionBuilder(initDef = {}) {
89
136
  },
90
137
  formAction: (action) => {
91
138
  return async (prevState, formData) => {
92
- const ctx = await _def.middleware?.();
139
+ let ctx = {};
140
+ if (_def.middleware.length > 0) ctx = await executeMiddlewares(_def.middleware, ctx);
93
141
  if (_def.input) {
94
- const inputSchema = typeof _def.input === "function" ? await _def.input({ ctx }) : _def.input;
95
- const result = await standardValidate(inputSchema, formData);
142
+ const result = await standardValidate(typeof _def.input === "function" ? await _def.input({ ctx }) : _def.input, formData);
96
143
  if (result.issues) return await action({
97
144
  ctx,
98
145
  prevState,
@@ -120,6 +167,5 @@ function createServerActionBuilder(initDef = {}) {
120
167
  * Server action builder
121
168
  */
122
169
  const serverAct = createServerActionBuilder();
123
-
124
170
  //#endregion
125
- export { serverAct };
171
+ export { createServerActMiddleware, serverAct };
@@ -1,9 +1,8 @@
1
-
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  //#region src/internal/assert.ts
3
3
  function assert(condition, message) {
4
4
  if (!condition) throw new Error(message || "Assertion failed");
5
5
  }
6
-
7
6
  //#endregion
8
7
  //#region src/utils.ts
9
8
  function isNumberString(str) {
@@ -99,12 +98,8 @@ function set(obj, path, value) {
99
98
  */
100
99
  function formDataToObject(formData) {
101
100
  const obj = {};
102
- for (const [key, value] of formData.entries()) {
103
- const parts = key.split(/[\.\[\]]/).filter(Boolean);
104
- set(obj, parts, value);
105
- }
101
+ for (const [key, value] of formData.entries()) set(obj, key.split(/[.[\]]/).filter(Boolean), value);
106
102
  return obj;
107
103
  }
108
-
109
104
  //#endregion
110
- exports.formDataToObject = formDataToObject;
105
+ exports.formDataToObject = formDataToObject;
package/dist/utils.mjs CHANGED
@@ -2,7 +2,6 @@
2
2
  function assert(condition, message) {
3
3
  if (!condition) throw new Error(message || "Assertion failed");
4
4
  }
5
-
6
5
  //#endregion
7
6
  //#region src/utils.ts
8
7
  function isNumberString(str) {
@@ -98,12 +97,8 @@ function set(obj, path, value) {
98
97
  */
99
98
  function formDataToObject(formData) {
100
99
  const obj = {};
101
- for (const [key, value] of formData.entries()) {
102
- const parts = key.split(/[\.\[\]]/).filter(Boolean);
103
- set(obj, parts, value);
104
- }
100
+ for (const [key, value] of formData.entries()) set(obj, key.split(/[.[\]]/).filter(Boolean), value);
105
101
  return obj;
106
102
  }
107
-
108
103
  //#endregion
109
- export { formDataToObject };
104
+ export { formDataToObject };
package/package.json CHANGED
@@ -1,16 +1,33 @@
1
1
  {
2
2
  "name": "server-act",
3
- "version": "1.6.1",
3
+ "version": "1.8.0",
4
+ "keywords": [
5
+ "action",
6
+ "next",
7
+ "nextjs",
8
+ "react",
9
+ "react server action",
10
+ "react server component",
11
+ "rsc",
12
+ "server action",
13
+ "server component"
14
+ ],
4
15
  "homepage": "https://github.com/chungweileong94/server-act#readme",
5
- "author": "chungweileong94",
16
+ "bugs": {
17
+ "url": "https://github.com/chungweileong94/server-act/issues"
18
+ },
6
19
  "license": "MIT",
20
+ "author": "chungweileong94",
7
21
  "repository": {
8
22
  "type": "git",
9
23
  "url": "git+https://github.com/chungweileong94/server-act.git"
10
24
  },
11
- "bugs": {
12
- "url": "https://github.com/chungweileong94/server-act/issues"
13
- },
25
+ "files": [
26
+ "dist",
27
+ "LICENSE",
28
+ "package.json",
29
+ "README.md"
30
+ ],
14
31
  "exports": {
15
32
  ".": {
16
33
  "types": "./dist/index.d.ts",
@@ -24,23 +41,6 @@
24
41
  },
25
42
  "./package.json": "./package.json"
26
43
  },
27
- "files": [
28
- "dist",
29
- "package.json",
30
- "LICENSE",
31
- "README.md"
32
- ],
33
- "keywords": [
34
- "next",
35
- "nextjs",
36
- "react",
37
- "react server component",
38
- "react server action",
39
- "rsc",
40
- "server component",
41
- "server action",
42
- "action"
43
- ],
44
44
  "dependencies": {
45
45
  "@standard-schema/spec": "^1.0.0",
46
46
  "@standard-schema/utils": "^0.3.0"
package/dist/index.js DELETED
@@ -1,148 +0,0 @@
1
- //#region rolldown:runtime
2
- var __create = Object.create;
3
- var __defProp = Object.defineProperty;
4
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
- var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __copyProps = (to, from, except, desc) => {
9
- if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
10
- key = keys[i];
11
- if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
12
- get: ((k) => from[k]).bind(null, key),
13
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
14
- });
15
- }
16
- return to;
17
- };
18
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
19
- value: mod,
20
- enumerable: true
21
- }) : target, mod));
22
-
23
- //#endregion
24
- const __standard_schema_utils = __toESM(require("@standard-schema/utils"));
25
-
26
- //#region src/internal/schema.ts
27
- async function standardValidate(schema, input) {
28
- let result = schema["~standard"].validate(input);
29
- if (result instanceof Promise) result = await result;
30
- return result;
31
- }
32
- function getInputErrors(issues) {
33
- const messages = [];
34
- const fieldErrors = {};
35
- for (const issue of issues) {
36
- const dotPath = (0, __standard_schema_utils.getDotPath)(issue);
37
- if (dotPath) if (fieldErrors[dotPath]) fieldErrors[dotPath].push(issue.message);
38
- else fieldErrors[dotPath] = [issue.message];
39
- else messages.push(issue.message);
40
- }
41
- return {
42
- messages,
43
- fieldErrors
44
- };
45
- }
46
-
47
- //#endregion
48
- //#region src/index.ts
49
- const unsetMarker = Symbol("unsetMarker");
50
- function createNewServerActionBuilder(def) {
51
- return createServerActionBuilder(def);
52
- }
53
- function createServerActionBuilder(initDef = {}) {
54
- const _def = {
55
- input: void 0,
56
- middleware: void 0,
57
- ...initDef
58
- };
59
- return {
60
- middleware: (middleware) => createNewServerActionBuilder({
61
- ..._def,
62
- middleware
63
- }),
64
- input: (input) => createNewServerActionBuilder({
65
- ..._def,
66
- input
67
- }),
68
- action: (action) => {
69
- return async (input) => {
70
- const ctx = await _def.middleware?.();
71
- if (_def.input) {
72
- const inputSchema = typeof _def.input === "function" ? await _def.input({ ctx }) : _def.input;
73
- const result = await standardValidate(inputSchema, input);
74
- if (result.issues) throw new __standard_schema_utils.SchemaError(result.issues);
75
- return await action({
76
- ctx,
77
- input: result.value
78
- });
79
- }
80
- return await action({
81
- ctx,
82
- input: void 0
83
- });
84
- };
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
- },
113
- formAction: (action) => {
114
- return async (prevState, formData) => {
115
- const ctx = await _def.middleware?.();
116
- if (_def.input) {
117
- const inputSchema = typeof _def.input === "function" ? await _def.input({ ctx }) : _def.input;
118
- const result = await standardValidate(inputSchema, formData);
119
- if (result.issues) return await action({
120
- ctx,
121
- prevState,
122
- formData,
123
- formErrors: getInputErrors(result.issues)
124
- });
125
- return await action({
126
- ctx,
127
- prevState,
128
- formData,
129
- input: result.value
130
- });
131
- }
132
- return await action({
133
- ctx,
134
- prevState,
135
- formData,
136
- input: void 0
137
- });
138
- };
139
- }
140
- };
141
- }
142
- /**
143
- * Server action builder
144
- */
145
- const serverAct = createServerActionBuilder();
146
-
147
- //#endregion
148
- exports.serverAct = serverAct;
File without changes