server-act 1.7.0 → 1.8.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
@@ -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
74
  const userId = "...";
75
- return { t, 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
  });
@@ -87,11 +87,12 @@ export const sayHelloAction = serverAct
87
87
 
88
88
  #### Chaining Middlewares
89
89
 
90
- You can chain multiple middlewares by calling `.middleware(...)` repeatedly.
90
+ You can chain multiple middlewares by calling `.use(...)` repeatedly.
91
91
 
92
92
  - Middlewares run in registration order.
93
- - Each middleware receives the current `ctx` and can return additional context.
94
- - Returned objects are shallow-merged into `ctx`.
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.
95
96
  - Later middleware values override earlier values for the same key.
96
97
  - Errors thrown in middleware propagate and stop later middleware from running.
97
98
 
@@ -102,22 +103,73 @@ You can chain multiple middlewares by calling `.middleware(...)` repeatedly.
102
103
  import { serverAct } from "server-act";
103
104
 
104
105
  export const createGreetingAction = serverAct
105
- .middleware(() => ({
106
- requestId: crypto.randomUUID(),
107
- role: "user",
108
- }))
109
- .middleware(({ ctx }) => ({
110
- role: "admin", // overrides previous role
111
- actorLabel: `${ctx.role}-actor`,
112
- }))
113
- .middleware(({ ctx }) => ({
114
- trace: `${ctx.requestId}:${ctx.actorLabel}`,
115
- }))
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
+ )
116
129
  .action(async ({ ctx }) => {
117
130
  return `${ctx.role} -> ${ctx.trace}`;
118
131
  });
119
132
  ```
120
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
+
121
173
  ### `useActionState` Support
122
174
 
123
175
  > `useActionState` Documentation:
package/dist/index.cjs CHANGED
@@ -1,21 +1,41 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
1
2
  let _standard_schema_utils = require("@standard-schema/utils");
2
-
3
3
  //#region src/internal/middleware.ts
4
+ function normalizeCtx(ctx) {
5
+ return ctx && typeof ctx === "object" ? { ...ctx } : {};
6
+ }
4
7
  /**
5
8
  * Executes an array of middleware functions with the given initial context.
6
9
  */
7
- async function executeMiddlewares(middlewares, initialCtx) {
8
- let ctx = initialCtx && typeof initialCtx === "object" ? { ...initialCtx } : {};
9
- for (const middleware of middlewares) {
10
- const result = await middleware({ ctx });
11
- if (result && typeof result === "object") ctx = {
12
- ...ctx,
13
- ...result
14
- };
15
- }
16
- return ctx;
10
+ async function executeMiddlewares(middlewares, initialCtx, terminal) {
11
+ const executeAt = async (index, ctx) => {
12
+ const entry = middlewares[index];
13
+ if (!entry) return await terminal(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 result;
36
+ };
37
+ return await executeAt(0, normalizeCtx(initialCtx));
17
38
  }
18
-
19
39
  //#endregion
20
40
  //#region src/internal/schema.ts
21
41
  async function standardValidate(schema, input) {
@@ -37,12 +57,14 @@ function getInputErrors(issues) {
37
57
  fieldErrors
38
58
  };
39
59
  }
40
-
41
60
  //#endregion
42
61
  //#region src/index.ts
43
62
  function createNewServerActionBuilder(def) {
44
63
  return createServerActionBuilder(def);
45
64
  }
65
+ function createServerActMiddleware(middleware) {
66
+ return middleware;
67
+ }
46
68
  function createServerActionBuilder(initDef = {}) {
47
69
  const _def = {
48
70
  input: void 0,
@@ -52,81 +74,91 @@ function createServerActionBuilder(initDef = {}) {
52
74
  return {
53
75
  middleware: (middleware) => createNewServerActionBuilder({
54
76
  ..._def,
55
- middleware: [..._def.middleware, middleware]
77
+ middleware: [..._def.middleware, {
78
+ kind: "legacy",
79
+ middleware
80
+ }]
56
81
  }),
82
+ use: ((middleware) => createNewServerActionBuilder({
83
+ ..._def,
84
+ middleware: [..._def.middleware, {
85
+ kind: "use",
86
+ middleware
87
+ }]
88
+ })),
57
89
  input: (input) => createNewServerActionBuilder({
58
90
  ..._def,
59
91
  input
60
92
  }),
61
93
  action: (action) => {
62
94
  return async (input) => {
63
- let ctx = {};
64
- if (_def.middleware.length > 0) ctx = await executeMiddlewares(_def.middleware, ctx);
65
- if (_def.input) {
66
- const result = await standardValidate(typeof _def.input === "function" ? await _def.input({ ctx }) : _def.input, input);
67
- if (result.issues) throw new _standard_schema_utils.SchemaError(result.issues);
95
+ return await executeMiddlewares(_def.middleware, {}, async (ctx) => {
96
+ if (_def.input) {
97
+ const result = await standardValidate(typeof _def.input === "function" ? await _def.input({ ctx }) : _def.input, input);
98
+ if (result.issues) throw new _standard_schema_utils.SchemaError(result.issues);
99
+ return await action({
100
+ ctx,
101
+ input: result.value
102
+ });
103
+ }
68
104
  return await action({
69
105
  ctx,
70
- input: result.value
106
+ input: void 0
71
107
  });
72
- }
73
- return await action({
74
- ctx,
75
- input: void 0
76
108
  });
77
109
  };
78
110
  },
79
111
  stateAction: (action) => {
80
112
  return async (prevState, rawInput) => {
81
- let ctx = {};
82
- if (_def.middleware.length > 0) ctx = await executeMiddlewares(_def.middleware, ctx);
83
- if (_def.input) {
84
- const result = await standardValidate(typeof _def.input === "function" ? await _def.input({ ctx }) : _def.input, rawInput);
85
- if (result.issues) return await action({
86
- ctx,
87
- prevState,
88
- rawInput,
89
- inputErrors: getInputErrors(result.issues)
90
- });
113
+ return await executeMiddlewares(_def.middleware, {}, async (ctx) => {
114
+ if (_def.input) {
115
+ const result = await standardValidate(typeof _def.input === "function" ? await _def.input({ ctx }) : _def.input, rawInput);
116
+ if (result.issues) return await action({
117
+ ctx,
118
+ prevState,
119
+ rawInput,
120
+ inputErrors: getInputErrors(result.issues)
121
+ });
122
+ return await action({
123
+ ctx,
124
+ prevState,
125
+ rawInput,
126
+ input: result.value
127
+ });
128
+ }
91
129
  return await action({
92
130
  ctx,
93
131
  prevState,
94
132
  rawInput,
95
- input: result.value
133
+ input: void 0
96
134
  });
97
- }
98
- return await action({
99
- ctx,
100
- prevState,
101
- rawInput,
102
- input: void 0
103
135
  });
104
136
  };
105
137
  },
106
138
  formAction: (action) => {
107
139
  return async (prevState, formData) => {
108
- let ctx = {};
109
- if (_def.middleware.length > 0) ctx = await executeMiddlewares(_def.middleware, ctx);
110
- if (_def.input) {
111
- const result = await standardValidate(typeof _def.input === "function" ? await _def.input({ ctx }) : _def.input, formData);
112
- if (result.issues) return await action({
113
- ctx,
114
- prevState,
115
- formData,
116
- formErrors: getInputErrors(result.issues)
117
- });
140
+ return await executeMiddlewares(_def.middleware, {}, async (ctx) => {
141
+ if (_def.input) {
142
+ const result = await standardValidate(typeof _def.input === "function" ? await _def.input({ ctx }) : _def.input, formData);
143
+ if (result.issues) return await action({
144
+ ctx,
145
+ prevState,
146
+ formData,
147
+ formErrors: getInputErrors(result.issues)
148
+ });
149
+ return await action({
150
+ ctx,
151
+ prevState,
152
+ formData,
153
+ input: result.value
154
+ });
155
+ }
118
156
  return await action({
119
157
  ctx,
120
158
  prevState,
121
159
  formData,
122
- input: result.value
160
+ input: void 0
123
161
  });
124
- }
125
- return await action({
126
- ctx,
127
- prevState,
128
- formData,
129
- input: void 0
130
162
  });
131
163
  };
132
164
  }
@@ -136,6 +168,6 @@ function createServerActionBuilder(initDef = {}) {
136
168
  * Server action builder
137
169
  */
138
170
  const serverAct = createServerActionBuilder();
139
-
140
171
  //#endregion
141
- exports.serverAct = serverAct;
172
+ exports.createServerActMiddleware = createServerActMiddleware;
173
+ exports.serverAct = serverAct;
package/dist/index.d.cts CHANGED
@@ -1,9 +1,22 @@
1
1
  import { StandardSchemaV1 } from "@standard-schema/spec";
2
2
 
3
3
  //#region src/internal/middleware.d.ts
4
- type MiddlewareFunction<TContext, TReturn> = (params: {
4
+ type LegacyMiddlewareFunction<TContext, TReturn> = (params: {
5
5
  ctx: TContext;
6
6
  }) => Promise<TReturn> | TReturn;
7
+ type MiddlewareContext = Record<string, unknown>;
8
+ type Awaitable<T> = T | Promise<T>;
9
+ declare const middlewareResultBrand: unique symbol;
10
+ type MiddlewareResult<TAddedContext extends MiddlewareContext = MiddlewareContext> = {
11
+ readonly [middlewareResultBrand]: TAddedContext;
12
+ };
13
+ type MiddlewareNextFunction = <TAddedContext extends MiddlewareContext = {}>(opts?: {
14
+ ctx?: TAddedContext;
15
+ }) => Promise<MiddlewareResult<TAddedContext>>;
16
+ type UseMiddlewareFunction<TContext extends MiddlewareContext, TAddedContext extends MiddlewareContext> = (params: {
17
+ ctx: TContext;
18
+ next: MiddlewareNextFunction;
19
+ }) => Awaitable<MiddlewareResult<TAddedContext>>;
7
20
  //#endregion
8
21
  //#region src/internal/schema.d.ts
9
22
  declare function getInputErrors(issues: ReadonlyArray<StandardSchemaV1.Issue>): {
@@ -15,6 +28,7 @@ declare function getInputErrors(issues: ReadonlyArray<StandardSchemaV1.Issue>):
15
28
  declare const unsetMarker: unique symbol;
16
29
  type UnsetMarker = typeof unsetMarker;
17
30
  type RemoveUnsetMarker<T> = T extends UnsetMarker ? undefined : T;
31
+ type NormalizeContext<T> = RemoveUnsetMarker<T> extends Record<string, unknown> ? RemoveUnsetMarker<T> : {};
18
32
  type Equals<X, Y> = (<T>() => T extends X ? 1 : 2) extends (<T>() => T extends Y ? 1 : 2) ? true : false;
19
33
  type Prettify<T> = { [P in keyof T]: T[P] } & {};
20
34
  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;
@@ -30,16 +44,25 @@ interface ActionBuilder<TParams extends ActionParams> {
30
44
  *
31
45
  * Chaining multiple middlewares is possible, each middleware receives context from previous middlewares
32
46
  * and returns additional context that gets merged.
47
+ *
48
+ * @deprecated Use `.use()` instead.
33
49
  */
34
- middleware: <TNewContext>(middleware: MiddlewareFunction<RemoveUnsetMarker<TParams["_context"]>, TNewContext>) => ActionBuilder<{
50
+ middleware: <TNewContext>(middleware: LegacyMiddlewareFunction<NormalizeContext<TParams["_context"]>, TNewContext>) => ActionBuilder<{
35
51
  _input: TParams["_input"];
36
52
  _context: TParams["_context"] extends UnsetMarker ? TNewContext : Prettify<TParams["_context"] & TNewContext>;
37
53
  }>;
54
+ /**
55
+ * tRPC-style middleware that forwards context via `next()`.
56
+ */
57
+ use: <TNextContext extends Record<string, unknown>>(middleware: UseMiddlewareFunction<NormalizeContext<TParams["_context"]>, TNextContext>) => ActionBuilder<{
58
+ _input: TParams["_input"];
59
+ _context: TParams["_context"] extends UnsetMarker ? TNextContext : Prettify<NormalizeContext<TParams["_context"]> & TNextContext>;
60
+ }>;
38
61
  /**
39
62
  * Input validation for the action.
40
63
  */
41
64
  input: <TParser extends StandardSchemaV1>(input: ((params: {
42
- ctx: RemoveUnsetMarker<TParams["_context"]>;
65
+ ctx: NormalizeContext<TParams["_context"]>;
43
66
  }) => Promise<TParser> | TParser) | TParser) => Omit<ActionBuilder<{
44
67
  _input: TParser;
45
68
  _context: TParams["_context"];
@@ -48,14 +71,14 @@ interface ActionBuilder<TParams extends ActionParams> {
48
71
  * Create an action.
49
72
  */
50
73
  action: <TOutput>(action: (params: {
51
- ctx: RemoveUnsetMarker<TParams["_context"]>;
74
+ ctx: NormalizeContext<TParams["_context"]>;
52
75
  input: InferInputType<TParams["_input"], "out">;
53
76
  }) => Promise<TOutput>) => SanitizeFunctionParam<(input: InferInputType<TParams["_input"], "in">) => Promise<TOutput>>;
54
77
  /**
55
78
  * Create an action for React `useActionState`
56
79
  */
57
80
  stateAction: <TState, TPrevState = UnsetMarker>(action: (params: Prettify<{
58
- ctx: RemoveUnsetMarker<TParams["_context"]>;
81
+ ctx: NormalizeContext<TParams["_context"]>;
59
82
  prevState: RemoveUnsetMarker<TPrevState>;
60
83
  rawInput: InferInputType<TParams["_input"], "in">;
61
84
  } & ({
@@ -71,7 +94,7 @@ interface ActionBuilder<TParams extends ActionParams> {
71
94
  * @deprecated Use `stateAction` instead.
72
95
  */
73
96
  formAction: <TState, TPrevState = UnsetMarker>(action: (params: Prettify<{
74
- ctx: RemoveUnsetMarker<TParams["_context"]>;
97
+ ctx: NormalizeContext<TParams["_context"]>;
75
98
  prevState: RemoveUnsetMarker<TPrevState>;
76
99
  formData: FormData;
77
100
  } & ({
@@ -82,6 +105,7 @@ interface ActionBuilder<TParams extends ActionParams> {
82
105
  formErrors: ReturnType<typeof getInputErrors>;
83
106
  })>) => Promise<TState>) => (prevState: TState | RemoveUnsetMarker<TPrevState>, formData: InferInputType<TParams["_input"], "in">) => Promise<TState | RemoveUnsetMarker<TPrevState>>;
84
107
  }
108
+ declare function createServerActMiddleware<TAddedContext extends Record<string, unknown>, TContext extends Record<string, unknown> = {}>(middleware: UseMiddlewareFunction<TContext, TAddedContext>): UseMiddlewareFunction<TContext, TAddedContext>;
85
109
  /**
86
110
  * Server action builder
87
111
  */
@@ -90,4 +114,4 @@ declare const serverAct: ActionBuilder<{
90
114
  _context: UnsetMarker;
91
115
  }>;
92
116
  //#endregion
93
- export { serverAct };
117
+ export { createServerActMiddleware, serverAct };
package/dist/index.d.mts CHANGED
@@ -1,9 +1,22 @@
1
1
  import { StandardSchemaV1 } from "@standard-schema/spec";
2
2
 
3
3
  //#region src/internal/middleware.d.ts
4
- type MiddlewareFunction<TContext, TReturn> = (params: {
4
+ type LegacyMiddlewareFunction<TContext, TReturn> = (params: {
5
5
  ctx: TContext;
6
6
  }) => Promise<TReturn> | TReturn;
7
+ type MiddlewareContext = Record<string, unknown>;
8
+ type Awaitable<T> = T | Promise<T>;
9
+ declare const middlewareResultBrand: unique symbol;
10
+ type MiddlewareResult<TAddedContext extends MiddlewareContext = MiddlewareContext> = {
11
+ readonly [middlewareResultBrand]: TAddedContext;
12
+ };
13
+ type MiddlewareNextFunction = <TAddedContext extends MiddlewareContext = {}>(opts?: {
14
+ ctx?: TAddedContext;
15
+ }) => Promise<MiddlewareResult<TAddedContext>>;
16
+ type UseMiddlewareFunction<TContext extends MiddlewareContext, TAddedContext extends MiddlewareContext> = (params: {
17
+ ctx: TContext;
18
+ next: MiddlewareNextFunction;
19
+ }) => Awaitable<MiddlewareResult<TAddedContext>>;
7
20
  //#endregion
8
21
  //#region src/internal/schema.d.ts
9
22
  declare function getInputErrors(issues: ReadonlyArray<StandardSchemaV1.Issue>): {
@@ -15,6 +28,7 @@ declare function getInputErrors(issues: ReadonlyArray<StandardSchemaV1.Issue>):
15
28
  declare const unsetMarker: unique symbol;
16
29
  type UnsetMarker = typeof unsetMarker;
17
30
  type RemoveUnsetMarker<T> = T extends UnsetMarker ? undefined : T;
31
+ type NormalizeContext<T> = RemoveUnsetMarker<T> extends Record<string, unknown> ? RemoveUnsetMarker<T> : {};
18
32
  type Equals<X, Y> = (<T>() => T extends X ? 1 : 2) extends (<T>() => T extends Y ? 1 : 2) ? true : false;
19
33
  type Prettify<T> = { [P in keyof T]: T[P] } & {};
20
34
  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;
@@ -30,16 +44,25 @@ interface ActionBuilder<TParams extends ActionParams> {
30
44
  *
31
45
  * Chaining multiple middlewares is possible, each middleware receives context from previous middlewares
32
46
  * and returns additional context that gets merged.
47
+ *
48
+ * @deprecated Use `.use()` instead.
33
49
  */
34
- middleware: <TNewContext>(middleware: MiddlewareFunction<RemoveUnsetMarker<TParams["_context"]>, TNewContext>) => ActionBuilder<{
50
+ middleware: <TNewContext>(middleware: LegacyMiddlewareFunction<NormalizeContext<TParams["_context"]>, TNewContext>) => ActionBuilder<{
35
51
  _input: TParams["_input"];
36
52
  _context: TParams["_context"] extends UnsetMarker ? TNewContext : Prettify<TParams["_context"] & TNewContext>;
37
53
  }>;
54
+ /**
55
+ * tRPC-style middleware that forwards context via `next()`.
56
+ */
57
+ use: <TNextContext extends Record<string, unknown>>(middleware: UseMiddlewareFunction<NormalizeContext<TParams["_context"]>, TNextContext>) => ActionBuilder<{
58
+ _input: TParams["_input"];
59
+ _context: TParams["_context"] extends UnsetMarker ? TNextContext : Prettify<NormalizeContext<TParams["_context"]> & TNextContext>;
60
+ }>;
38
61
  /**
39
62
  * Input validation for the action.
40
63
  */
41
64
  input: <TParser extends StandardSchemaV1>(input: ((params: {
42
- ctx: RemoveUnsetMarker<TParams["_context"]>;
65
+ ctx: NormalizeContext<TParams["_context"]>;
43
66
  }) => Promise<TParser> | TParser) | TParser) => Omit<ActionBuilder<{
44
67
  _input: TParser;
45
68
  _context: TParams["_context"];
@@ -48,14 +71,14 @@ interface ActionBuilder<TParams extends ActionParams> {
48
71
  * Create an action.
49
72
  */
50
73
  action: <TOutput>(action: (params: {
51
- ctx: RemoveUnsetMarker<TParams["_context"]>;
74
+ ctx: NormalizeContext<TParams["_context"]>;
52
75
  input: InferInputType<TParams["_input"], "out">;
53
76
  }) => Promise<TOutput>) => SanitizeFunctionParam<(input: InferInputType<TParams["_input"], "in">) => Promise<TOutput>>;
54
77
  /**
55
78
  * Create an action for React `useActionState`
56
79
  */
57
80
  stateAction: <TState, TPrevState = UnsetMarker>(action: (params: Prettify<{
58
- ctx: RemoveUnsetMarker<TParams["_context"]>;
81
+ ctx: NormalizeContext<TParams["_context"]>;
59
82
  prevState: RemoveUnsetMarker<TPrevState>;
60
83
  rawInput: InferInputType<TParams["_input"], "in">;
61
84
  } & ({
@@ -71,7 +94,7 @@ interface ActionBuilder<TParams extends ActionParams> {
71
94
  * @deprecated Use `stateAction` instead.
72
95
  */
73
96
  formAction: <TState, TPrevState = UnsetMarker>(action: (params: Prettify<{
74
- ctx: RemoveUnsetMarker<TParams["_context"]>;
97
+ ctx: NormalizeContext<TParams["_context"]>;
75
98
  prevState: RemoveUnsetMarker<TPrevState>;
76
99
  formData: FormData;
77
100
  } & ({
@@ -82,6 +105,7 @@ interface ActionBuilder<TParams extends ActionParams> {
82
105
  formErrors: ReturnType<typeof getInputErrors>;
83
106
  })>) => Promise<TState>) => (prevState: TState | RemoveUnsetMarker<TPrevState>, formData: InferInputType<TParams["_input"], "in">) => Promise<TState | RemoveUnsetMarker<TPrevState>>;
84
107
  }
108
+ declare function createServerActMiddleware<TAddedContext extends Record<string, unknown>, TContext extends Record<string, unknown> = {}>(middleware: UseMiddlewareFunction<TContext, TAddedContext>): UseMiddlewareFunction<TContext, TAddedContext>;
85
109
  /**
86
110
  * Server action builder
87
111
  */
@@ -90,4 +114,4 @@ declare const serverAct: ActionBuilder<{
90
114
  _context: UnsetMarker;
91
115
  }>;
92
116
  //#endregion
93
- export { serverAct };
117
+ export { createServerActMiddleware, serverAct };
package/dist/index.mjs CHANGED
@@ -1,21 +1,40 @@
1
1
  import { SchemaError, getDotPath } from "@standard-schema/utils";
2
-
3
2
  //#region src/internal/middleware.ts
3
+ function normalizeCtx(ctx) {
4
+ return ctx && typeof ctx === "object" ? { ...ctx } : {};
5
+ }
4
6
  /**
5
7
  * Executes an array of middleware functions with the given initial context.
6
8
  */
7
- async function executeMiddlewares(middlewares, initialCtx) {
8
- let ctx = initialCtx && typeof initialCtx === "object" ? { ...initialCtx } : {};
9
- for (const middleware of middlewares) {
10
- const result = await middleware({ ctx });
11
- if (result && typeof result === "object") ctx = {
12
- ...ctx,
13
- ...result
14
- };
15
- }
16
- return ctx;
9
+ async function executeMiddlewares(middlewares, initialCtx, terminal) {
10
+ const executeAt = async (index, ctx) => {
11
+ const entry = middlewares[index];
12
+ if (!entry) return await terminal(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 result;
35
+ };
36
+ return await executeAt(0, normalizeCtx(initialCtx));
17
37
  }
18
-
19
38
  //#endregion
20
39
  //#region src/internal/schema.ts
21
40
  async function standardValidate(schema, input) {
@@ -37,12 +56,14 @@ function getInputErrors(issues) {
37
56
  fieldErrors
38
57
  };
39
58
  }
40
-
41
59
  //#endregion
42
60
  //#region src/index.ts
43
61
  function createNewServerActionBuilder(def) {
44
62
  return createServerActionBuilder(def);
45
63
  }
64
+ function createServerActMiddleware(middleware) {
65
+ return middleware;
66
+ }
46
67
  function createServerActionBuilder(initDef = {}) {
47
68
  const _def = {
48
69
  input: void 0,
@@ -52,81 +73,91 @@ function createServerActionBuilder(initDef = {}) {
52
73
  return {
53
74
  middleware: (middleware) => createNewServerActionBuilder({
54
75
  ..._def,
55
- middleware: [..._def.middleware, middleware]
76
+ middleware: [..._def.middleware, {
77
+ kind: "legacy",
78
+ middleware
79
+ }]
56
80
  }),
81
+ use: ((middleware) => createNewServerActionBuilder({
82
+ ..._def,
83
+ middleware: [..._def.middleware, {
84
+ kind: "use",
85
+ middleware
86
+ }]
87
+ })),
57
88
  input: (input) => createNewServerActionBuilder({
58
89
  ..._def,
59
90
  input
60
91
  }),
61
92
  action: (action) => {
62
93
  return async (input) => {
63
- let ctx = {};
64
- if (_def.middleware.length > 0) ctx = await executeMiddlewares(_def.middleware, ctx);
65
- if (_def.input) {
66
- const result = await standardValidate(typeof _def.input === "function" ? await _def.input({ ctx }) : _def.input, input);
67
- if (result.issues) throw new SchemaError(result.issues);
94
+ return await executeMiddlewares(_def.middleware, {}, async (ctx) => {
95
+ if (_def.input) {
96
+ const result = await standardValidate(typeof _def.input === "function" ? await _def.input({ ctx }) : _def.input, input);
97
+ if (result.issues) throw new SchemaError(result.issues);
98
+ return await action({
99
+ ctx,
100
+ input: result.value
101
+ });
102
+ }
68
103
  return await action({
69
104
  ctx,
70
- input: result.value
105
+ input: void 0
71
106
  });
72
- }
73
- return await action({
74
- ctx,
75
- input: void 0
76
107
  });
77
108
  };
78
109
  },
79
110
  stateAction: (action) => {
80
111
  return async (prevState, rawInput) => {
81
- let ctx = {};
82
- if (_def.middleware.length > 0) ctx = await executeMiddlewares(_def.middleware, ctx);
83
- if (_def.input) {
84
- const result = await standardValidate(typeof _def.input === "function" ? await _def.input({ ctx }) : _def.input, rawInput);
85
- if (result.issues) return await action({
86
- ctx,
87
- prevState,
88
- rawInput,
89
- inputErrors: getInputErrors(result.issues)
90
- });
112
+ return await executeMiddlewares(_def.middleware, {}, async (ctx) => {
113
+ if (_def.input) {
114
+ const result = await standardValidate(typeof _def.input === "function" ? await _def.input({ ctx }) : _def.input, rawInput);
115
+ if (result.issues) return await action({
116
+ ctx,
117
+ prevState,
118
+ rawInput,
119
+ inputErrors: getInputErrors(result.issues)
120
+ });
121
+ return await action({
122
+ ctx,
123
+ prevState,
124
+ rawInput,
125
+ input: result.value
126
+ });
127
+ }
91
128
  return await action({
92
129
  ctx,
93
130
  prevState,
94
131
  rawInput,
95
- input: result.value
132
+ input: void 0
96
133
  });
97
- }
98
- return await action({
99
- ctx,
100
- prevState,
101
- rawInput,
102
- input: void 0
103
134
  });
104
135
  };
105
136
  },
106
137
  formAction: (action) => {
107
138
  return async (prevState, formData) => {
108
- let ctx = {};
109
- if (_def.middleware.length > 0) ctx = await executeMiddlewares(_def.middleware, ctx);
110
- if (_def.input) {
111
- const result = await standardValidate(typeof _def.input === "function" ? await _def.input({ ctx }) : _def.input, formData);
112
- if (result.issues) return await action({
113
- ctx,
114
- prevState,
115
- formData,
116
- formErrors: getInputErrors(result.issues)
117
- });
139
+ return await executeMiddlewares(_def.middleware, {}, async (ctx) => {
140
+ if (_def.input) {
141
+ const result = await standardValidate(typeof _def.input === "function" ? await _def.input({ ctx }) : _def.input, formData);
142
+ if (result.issues) return await action({
143
+ ctx,
144
+ prevState,
145
+ formData,
146
+ formErrors: getInputErrors(result.issues)
147
+ });
148
+ return await action({
149
+ ctx,
150
+ prevState,
151
+ formData,
152
+ input: result.value
153
+ });
154
+ }
118
155
  return await action({
119
156
  ctx,
120
157
  prevState,
121
158
  formData,
122
- input: result.value
159
+ input: void 0
123
160
  });
124
- }
125
- return await action({
126
- ctx,
127
- prevState,
128
- formData,
129
- input: void 0
130
161
  });
131
162
  };
132
163
  }
@@ -136,6 +167,5 @@ function createServerActionBuilder(initDef = {}) {
136
167
  * Server action builder
137
168
  */
138
169
  const serverAct = createServerActionBuilder();
139
-
140
170
  //#endregion
141
- export { serverAct };
171
+ export { createServerActMiddleware, serverAct };
package/dist/utils.cjs CHANGED
@@ -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) {
@@ -102,6 +101,5 @@ function formDataToObject(formData) {
102
101
  for (const [key, value] of formData.entries()) set(obj, key.split(/[.[\]]/).filter(Boolean), value);
103
102
  return obj;
104
103
  }
105
-
106
104
  //#endregion
107
- 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) {
@@ -101,6 +100,5 @@ function formDataToObject(formData) {
101
100
  for (const [key, value] of formData.entries()) set(obj, key.split(/[.[\]]/).filter(Boolean), value);
102
101
  return obj;
103
102
  }
104
-
105
103
  //#endregion
106
- export { formDataToObject };
104
+ export { formDataToObject };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "server-act",
3
- "version": "1.7.0",
3
+ "version": "1.8.1",
4
4
  "keywords": [
5
5
  "action",
6
6
  "next",