@@ -14,9 +14,15 @@ import type {
14
14
ServerCodeFn ,
15
15
StateServerCodeFn ,
16
16
} from "./index.types" ;
17
- import { ActionMetadataError , DEFAULT_SERVER_ERROR_MESSAGE , isError } from "./utils" ;
17
+ import { DEFAULT_SERVER_ERROR_MESSAGE , isError } from "./utils" ;
18
18
import type { MaybePromise } from "./utils.types" ;
19
- import { ActionServerValidationError , ActionValidationError , buildValidationErrors } from "./validation-errors" ;
19
+ import {
20
+ ActionMetadataValidationError ,
21
+ ActionOutputDataValidationError ,
22
+ ActionServerValidationError ,
23
+ ActionValidationError ,
24
+ buildValidationErrors ,
25
+ } from "./validation-errors" ;
20
26
import type {
21
27
BindArgsValidationErrors ,
22
28
HandleBindArgsValidationErrorsShapeFn ,
@@ -27,18 +33,20 @@ import type {
27
33
export function actionBuilder <
28
34
ServerError ,
29
35
MetadataSchema extends Schema | undefined = undefined ,
30
- MD = MetadataSchema extends Schema ? Infer < Schema > : undefined ,
36
+ MD = MetadataSchema extends Schema ? Infer < Schema > : undefined , // metadata type (inferred from metadata schema)
31
37
Ctx extends object = { } ,
32
- SF extends ( ( ) => Promise < Schema > ) | undefined = undefined , // schema function
33
- S extends Schema | undefined = SF extends Function ? Awaited < ReturnType < SF > > : undefined ,
38
+ ISF extends ( ( ) => Promise < Schema > ) | undefined = undefined , // input schema function
39
+ IS extends Schema | undefined = ISF extends Function ? Awaited < ReturnType < ISF > > : undefined , // input schema
40
+ OS extends Schema | undefined = undefined , // output schema
34
41
const BAS extends readonly Schema [ ] = [ ] ,
35
42
CVE = undefined ,
36
43
CBAVE = undefined ,
37
44
> ( args : {
38
- schemaFn ?: SF ;
45
+ inputSchemaFn ?: ISF ;
39
46
bindArgsSchemas ?: BAS ;
47
+ outputSchema ?: OS ;
40
48
validationAdapter : ValidationAdapter ;
41
- handleValidationErrorsShape : HandleValidationErrorsShapeFn < S , CVE > ;
49
+ handleValidationErrorsShape : HandleValidationErrorsShapeFn < IS , CVE > ;
42
50
handleBindArgsValidationErrorsShape : HandleBindArgsValidationErrorsShapeFn < BAS , CBAVE > ;
43
51
metadataSchema : MetadataSchema ;
44
52
metadata : MD ;
@@ -53,29 +61,29 @@ export function actionBuilder<
53
61
const bindArgsSchemas = ( args . bindArgsSchemas ?? [ ] ) as BAS ;
54
62
55
63
function buildAction ( { withState } : { withState : false } ) : {
56
- action : < Data > (
57
- serverCodeFn : ServerCodeFn < MD , Ctx , S , BAS , Data > ,
58
- utils ?: SafeActionUtils < ServerError , MD , Ctx , S , BAS , CVE , CBAVE , Data >
59
- ) => SafeActionFn < ServerError , S , BAS , CVE , CBAVE , Data > ;
64
+ action : < Data extends OS extends Schema ? Infer < OS > : any > (
65
+ serverCodeFn : ServerCodeFn < MD , Ctx , IS , BAS , Data > ,
66
+ utils ?: SafeActionUtils < ServerError , MD , Ctx , IS , BAS , CVE , CBAVE , Data >
67
+ ) => SafeActionFn < ServerError , IS , BAS , CVE , CBAVE , Data > ;
60
68
} ;
61
69
function buildAction ( { withState } : { withState : true } ) : {
62
- action : < Data > (
63
- serverCodeFn : StateServerCodeFn < ServerError , MD , Ctx , S , BAS , CVE , CBAVE , Data > ,
64
- utils ?: SafeActionUtils < ServerError , MD , Ctx , S , BAS , CVE , CBAVE , Data >
65
- ) => SafeStateActionFn < ServerError , S , BAS , CVE , CBAVE , Data > ;
70
+ action : < Data extends OS extends Schema ? Infer < OS > : any > (
71
+ serverCodeFn : StateServerCodeFn < ServerError , MD , Ctx , IS , BAS , CVE , CBAVE , Data > ,
72
+ utils ?: SafeActionUtils < ServerError , MD , Ctx , IS , BAS , CVE , CBAVE , Data >
73
+ ) => SafeStateActionFn < ServerError , IS , BAS , CVE , CBAVE , Data > ;
66
74
} ;
67
75
function buildAction ( { withState } : { withState : boolean } ) {
68
76
return {
69
- action : < Data > (
77
+ action : < Data extends OS extends Schema ? Infer < OS > : any > (
70
78
serverCodeFn :
71
- | ServerCodeFn < MD , Ctx , S , BAS , Data >
72
- | StateServerCodeFn < ServerError , MD , Ctx , S , BAS , CVE , CBAVE , Data > ,
73
- utils ?: SafeActionUtils < ServerError , MD , Ctx , S , BAS , CVE , CBAVE , Data >
79
+ | ServerCodeFn < MD , Ctx , IS , BAS , Data >
80
+ | StateServerCodeFn < ServerError , MD , Ctx , IS , BAS , CVE , CBAVE , Data > ,
81
+ utils ?: SafeActionUtils < ServerError , MD , Ctx , IS , BAS , CVE , CBAVE , Data >
74
82
) => {
75
83
return async ( ...clientInputs : unknown [ ] ) => {
76
84
let currentCtx : object = { } ;
77
85
const middlewareResult : MiddlewareResult < ServerError , object > = { success : false } ;
78
- type PrevResult = SafeActionResult < ServerError , S , BAS , CVE , CBAVE , Data > | undefined ;
86
+ type PrevResult = SafeActionResult < ServerError , IS , BAS , CVE , CBAVE , Data > | undefined ;
79
87
let prevResult : PrevResult | undefined = undefined ;
80
88
const parsedInputDatas : any [ ] = [ ] ;
81
89
let frameworkError : Error | null = null ;
@@ -107,10 +115,10 @@ export function actionBuilder<
107
115
if ( idx === 0 ) {
108
116
if ( args . metadataSchema ) {
109
117
// Validate metadata input.
110
- if ( ! ( await args . validationAdapter . validate ( args . metadataSchema , args . metadata ) ) . success ) {
111
- throw new ActionMetadataError (
112
- "Invalid metadata input. Please be sure to pass metadata via `metadata` method before defining the action."
113
- ) ;
118
+ const parsedMd = await args . validationAdapter . validate ( args . metadataSchema , args . metadata ) ;
119
+
120
+ if ( ! parsedMd . success ) {
121
+ throw new ActionMetadataValidationError < MetadataSchema > ( buildValidationErrors ( parsedMd . issues ) ) ;
114
122
}
115
123
}
116
124
}
@@ -124,7 +132,6 @@ export function actionBuilder<
124
132
metadata : args . metadata ,
125
133
next : async ( nextOpts ) => {
126
134
currentCtx = deepmerge ( currentCtx , nextOpts ?. ctx ?? { } ) ;
127
- // currentCtx = { ...cloneDeep(currentCtx), ...(nextOpts?.ctx ?? {}) };
128
135
await executeMiddlewareStack ( idx + 1 ) ;
129
136
return middlewareResult ;
130
137
} ,
@@ -137,15 +144,15 @@ export function actionBuilder<
137
144
// Last client input in the array, main argument (no bind arg).
138
145
if ( i === clientInputs . length - 1 ) {
139
146
// If schema is undefined, set parsed data to undefined.
140
- if ( typeof args . schemaFn === "undefined" ) {
147
+ if ( typeof args . inputSchemaFn === "undefined" ) {
141
148
return {
142
149
success : true ,
143
150
data : undefined ,
144
151
} as const ;
145
152
}
146
153
147
154
// Otherwise, parse input with the schema.
148
- return args . validationAdapter . validate ( await args . schemaFn ( ) , input ) ;
155
+ return args . validationAdapter . validate ( await args . inputSchemaFn ( ) , input ) ;
149
156
}
150
157
151
158
// Otherwise, we're processing bind args client inputs.
@@ -172,7 +179,7 @@ export function actionBuilder<
172
179
hasBindValidationErrors = true ;
173
180
} else {
174
181
// Otherwise, we're processing the non-bind argument (the last one) in the array.
175
- const validationErrors = buildValidationErrors < S > ( parsedInput . issues ) ;
182
+ const validationErrors = buildValidationErrors < IS > ( parsedInput . issues ) ;
176
183
177
184
middlewareResult . validationErrors = await Promise . resolve (
178
185
args . handleValidationErrorsShape ( validationErrors )
@@ -193,11 +200,11 @@ export function actionBuilder<
193
200
}
194
201
195
202
// @ts -expect-error
196
- const scfArgs : Parameters < StateServerCodeFn < ServerError , MD , Ctx , S , BAS , CVE , CBAVE , Data > > = [ ] ;
203
+ const scfArgs : Parameters < StateServerCodeFn < ServerError , MD , Ctx , IS , BAS , CVE , CBAVE , Data > > = [ ] ;
197
204
198
205
// Server code function always has this object as the first argument.
199
206
scfArgs [ 0 ] = {
200
- parsedInput : parsedInputDatas . at ( - 1 ) as S extends Schema ? Infer < S > : undefined ,
207
+ parsedInput : parsedInputDatas . at ( - 1 ) as IS extends Schema ? Infer < IS > : undefined ,
201
208
bindArgsParsedInputs : parsedInputDatas . slice ( 0 , - 1 ) as InferArray < BAS > ,
202
209
ctx : currentCtx as Ctx ,
203
210
metadata : args . metadata ,
@@ -211,6 +218,15 @@ export function actionBuilder<
211
218
212
219
const data = await serverCodeFn ( ...scfArgs ) ;
213
220
221
+ // If a `outputSchema` is passed, validate the action return value.
222
+ if ( typeof args . outputSchema !== "undefined" ) {
223
+ const parsedData = await args . validationAdapter . validate ( args . outputSchema , data ) ;
224
+
225
+ if ( ! parsedData . success ) {
226
+ throw new ActionOutputDataValidationError < OS > ( buildValidationErrors ( parsedData . issues ) ) ;
227
+ }
228
+ }
229
+
214
230
middlewareResult . success = true ;
215
231
middlewareResult . data = data ;
216
232
middlewareResult . parsedInput = parsedInputDatas . at ( - 1 ) ;
@@ -227,7 +243,7 @@ export function actionBuilder<
227
243
228
244
// If error is `ActionServerValidationError`, return `validationErrors` as if schema validation would fail.
229
245
if ( e instanceof ActionServerValidationError ) {
230
- const ve = e . validationErrors as ValidationErrors < S > ;
246
+ const ve = e . validationErrors as ValidationErrors < IS > ;
231
247
middlewareResult . validationErrors = await Promise . resolve ( args . handleValidationErrorsShape ( ve ) ) ;
232
248
} else {
233
249
// If error is not an instance of Error, wrap it in an Error object with
@@ -269,9 +285,9 @@ export function actionBuilder<
269
285
data : undefined ,
270
286
metadata : args . metadata ,
271
287
ctx : currentCtx as Ctx ,
272
- clientInput : clientInputs . at ( - 1 ) as S extends Schema ? InferIn < S > : undefined ,
288
+ clientInput : clientInputs . at ( - 1 ) as IS extends Schema ? InferIn < IS > : undefined ,
273
289
bindArgsClientInputs : ( bindArgsSchemas . length ? clientInputs . slice ( 0 , - 1 ) : [ ] ) as InferInArray < BAS > ,
274
- parsedInput : parsedInputDatas . at ( - 1 ) as S extends Schema ? Infer < S > : undefined ,
290
+ parsedInput : parsedInputDatas . at ( - 1 ) as IS extends Schema ? Infer < IS > : undefined ,
275
291
bindArgsParsedInputs : parsedInputDatas . slice ( 0 , - 1 ) as InferArray < BAS > ,
276
292
hasRedirected : isRedirectError ( frameworkError ) ,
277
293
hasNotFound : isNotFoundError ( frameworkError ) ,
@@ -282,7 +298,7 @@ export function actionBuilder<
282
298
utils ?. onSettled ?.( {
283
299
metadata : args . metadata ,
284
300
ctx : currentCtx as Ctx ,
285
- clientInput : clientInputs . at ( - 1 ) as S extends Schema ? InferIn < S > : undefined ,
301
+ clientInput : clientInputs . at ( - 1 ) as IS extends Schema ? InferIn < IS > : undefined ,
286
302
bindArgsClientInputs : ( bindArgsSchemas . length ? clientInputs . slice ( 0 , - 1 ) : [ ] ) as InferInArray < BAS > ,
287
303
result : { } ,
288
304
hasRedirected : isRedirectError ( frameworkError ) ,
@@ -295,7 +311,7 @@ export function actionBuilder<
295
311
throw frameworkError ;
296
312
}
297
313
298
- const actionResult : SafeActionResult < ServerError , S , BAS , CVE , CBAVE , Data > = { } ;
314
+ const actionResult : SafeActionResult < ServerError , IS , BAS , CVE , CBAVE , Data > = { } ;
299
315
300
316
if ( typeof middlewareResult . validationErrors !== "undefined" ) {
301
317
// Throw validation errors if either `throwValidationErrors` property at the action or instance level is `true`.
@@ -333,9 +349,9 @@ export function actionBuilder<
333
349
metadata : args . metadata ,
334
350
ctx : currentCtx as Ctx ,
335
351
data : actionResult . data as Data ,
336
- clientInput : clientInputs . at ( - 1 ) as S extends Schema ? InferIn < S > : undefined ,
352
+ clientInput : clientInputs . at ( - 1 ) as IS extends Schema ? InferIn < IS > : undefined ,
337
353
bindArgsClientInputs : ( bindArgsSchemas . length ? clientInputs . slice ( 0 , - 1 ) : [ ] ) as InferInArray < BAS > ,
338
- parsedInput : parsedInputDatas . at ( - 1 ) as S extends Schema ? Infer < S > : undefined ,
354
+ parsedInput : parsedInputDatas . at ( - 1 ) as IS extends Schema ? Infer < IS > : undefined ,
339
355
bindArgsParsedInputs : parsedInputDatas . slice ( 0 , - 1 ) as InferArray < BAS > ,
340
356
hasRedirected : false ,
341
357
hasNotFound : false ,
@@ -346,7 +362,7 @@ export function actionBuilder<
346
362
utils ?. onError ?.( {
347
363
metadata : args . metadata ,
348
364
ctx : currentCtx as Ctx ,
349
- clientInput : clientInputs . at ( - 1 ) as S extends Schema ? InferIn < S > : undefined ,
365
+ clientInput : clientInputs . at ( - 1 ) as IS extends Schema ? InferIn < IS > : undefined ,
350
366
bindArgsClientInputs : ( bindArgsSchemas . length ? clientInputs . slice ( 0 , - 1 ) : [ ] ) as InferInArray < BAS > ,
351
367
error : actionResult ,
352
368
} )
@@ -358,7 +374,7 @@ export function actionBuilder<
358
374
utils ?. onSettled ?.( {
359
375
metadata : args . metadata ,
360
376
ctx : currentCtx as Ctx ,
361
- clientInput : clientInputs . at ( - 1 ) as S extends Schema ? InferIn < S > : undefined ,
377
+ clientInput : clientInputs . at ( - 1 ) as IS extends Schema ? InferIn < IS > : undefined ,
362
378
bindArgsClientInputs : ( bindArgsSchemas . length ? clientInputs . slice ( 0 , - 1 ) : [ ] ) as InferInArray < BAS > ,
363
379
result : actionResult ,
364
380
hasRedirected : false ,
0 commit comments