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 +69 -17
- package/dist/index.cjs +92 -60
- package/dist/index.d.cts +31 -7
- package/dist/index.d.mts +31 -7
- package/dist/index.mjs +90 -60
- package/dist/utils.cjs +2 -4
- package/dist/utils.mjs +1 -3
- package/package.json +1 -1
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
|
-
.
|
|
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 `.
|
|
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
|
|
94
|
-
-
|
|
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
|
-
.
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
.
|
|
114
|
-
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
if (
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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,
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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:
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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:
|
|
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
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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:
|
|
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.
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
if (
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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,
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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:
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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:
|
|
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
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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:
|
|
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 };
|