@@ -9,9 +9,19 @@ import {
99import { fromJSON , toCrossJSONAsync , toCrossJSONStream } from 'seroval'
1010import { getResponse } from './request-response'
1111import { getServerFnById } from './getServerFnById'
12+ import type { Plugin as SerovalPlugin } from 'seroval'
1213
1314let regex : RegExp | undefined = undefined
1415
16+ // Cache serovalPlugins at module level to avoid repeated calls
17+ let serovalPlugins : Array < SerovalPlugin < any , any > > | undefined = undefined
18+
19+ // Known FormData 'Content-Type' header values - module-level constant
20+ const FORM_DATA_CONTENT_TYPES = [
21+ 'multipart/form-data' ,
22+ 'application/x-www-form-urlencoded' ,
23+ ]
24+
1525export const handleServerAction = async ( {
1626 request,
1727 context,
@@ -29,34 +39,27 @@ export const handleServerAction = async ({
2939 }
3040
3141 const method = request . method
42+ const methodLower = method . toLowerCase ( )
3243 const url = new URL ( request . url , 'http://localhost:3000' )
3344 // extract the serverFnId from the url as host/_serverFn/:serverFnId
3445 // Define a regex to match the path and extract the :thing part
3546
3647 // Execute the regex
3748 const match = url . pathname . match ( regex )
3849 const serverFnId = match ? match [ 1 ] : null
39- const search = Object . fromEntries ( url . searchParams . entries ( ) ) as {
40- payload ?: any
41- createServerFn ?: boolean
42- }
43-
44- const isCreateServerFn = 'createServerFn' in search
4550
4651 if ( typeof serverFnId !== 'string' ) {
4752 throw new Error ( 'Invalid server action param for serverFnId: ' + serverFnId )
4853 }
4954
5055 const action = await getServerFnById ( serverFnId , { fromClient : true } )
5156
52- // Known FormData 'Content-Type' header values
53- const formDataContentTypes = [
54- 'multipart/form-data' ,
55- 'application/x-www-form-urlencoded' ,
56- ]
57+ // Initialize serovalPlugins lazily (cached at module level)
58+ if ( ! serovalPlugins ) {
59+ serovalPlugins = getDefaultSerovalPlugins ( )
60+ }
5761
5862 const contentType = request . headers . get ( 'Content-Type' )
59- const serovalPlugins = getDefaultSerovalPlugins ( )
6063
6164 function parsePayload ( payload : any ) {
6265 const parsedPayload = fromJSON ( payload , { plugins : serovalPlugins } )
@@ -65,16 +68,16 @@ export const handleServerAction = async ({
6568
6669 const response = await ( async ( ) => {
6770 try {
68- let result = await ( async ( ) => {
71+ const result = await ( async ( ) => {
6972 // FormData
7073 if (
71- formDataContentTypes . some (
74+ FORM_DATA_CONTENT_TYPES . some (
7275 ( type ) => contentType && contentType . includes ( type ) ,
7376 )
7477 ) {
7578 // We don't support GET requests with FormData payloads... that seems impossible
7679 invariant (
77- method . toLowerCase ( ) !== 'get' ,
80+ methodLower !== 'get' ,
7881 'GET requests with FormData payloads are not supported' ,
7982 )
8083 const formData = await request . formData ( )
@@ -104,21 +107,19 @@ export const handleServerAction = async ({
104107 }
105108
106109 // Get requests use the query string
107- if ( method . toLowerCase ( ) === 'get' ) {
108- invariant (
109- isCreateServerFn ,
110- 'expected GET request to originate from createServerFn' ,
111- )
112- // By default the payload is the search params
113- let payload : any = search . payload
110+ if ( methodLower === 'get' ) {
111+ // Get payload directly from searchParams
112+ const payloadParam = url . searchParams . get ( 'payload' )
114113 // If there's a payload, we should try to parse it
115- payload = payload ? parsePayload ( JSON . parse ( payload ) ) : { }
114+ const payload : any = payloadParam
115+ ? parsePayload ( JSON . parse ( payloadParam ) )
116+ : { }
116117 payload . context = { ...context , ...payload . context }
117118 // Send it through!
118119 return await action ( payload , signal )
119120 }
120121
121- if ( method . toLowerCase ( ) !== 'post' ) {
122+ if ( methodLower !== 'post' ) {
122123 throw new Error ( 'expected POST method' )
123124 }
124125
@@ -127,18 +128,9 @@ export const handleServerAction = async ({
127128 jsonPayload = await request . json ( )
128129 }
129130
130- // If this POST request was created by createServerFn,
131- // its payload will be the only argument
132- if ( isCreateServerFn ) {
133- const payload = jsonPayload ? parsePayload ( jsonPayload ) : { }
134- payload . context = { ...payload . context , ...context }
135- return await action ( payload , signal )
136- }
137-
138- // Otherwise, we'll spread the payload. Need to
139- // support `use server` functions that take multiple
140- // arguments.
141- return await action ( ...jsonPayload )
131+ const payload = jsonPayload ? parsePayload ( jsonPayload ) : { }
132+ payload . context = { ...payload . context , ...context }
133+ return await action ( payload , signal )
142134 } ) ( )
143135
144136 // Any time we get a Response back, we should just
@@ -148,37 +140,6 @@ export const handleServerAction = async ({
148140 return result . result
149141 }
150142
151- // If this is a non createServerFn request, we need to
152- // pull out the result from the result object
153- if ( ! isCreateServerFn ) {
154- result = result . result
155-
156- // The result might again be a response,
157- // and if it is, return it.
158- if ( result instanceof Response ) {
159- return result
160- }
161- }
162-
163- // TODO: RSCs Where are we getting this package?
164- // if (isValidElement(result)) {
165- // const { renderToPipeableStream } = await import(
166- // // @ts -expect-error
167- // 'react-server-dom/server'
168- // )
169-
170- // const pipeableStream = renderToPipeableStream(result)
171-
172- // setHeaders(event, {
173- // 'Content-Type': 'text/x-component',
174- // } as any)
175-
176- // sendStream(event, response)
177- // event._handled = true
178-
179- // return new Response(null, { status: 200 })
180- // }
181-
182143 if ( isNotFound ( result ) ) {
183144 return isNotFoundResponse ( result )
184145 }
0 commit comments