Skip to content

Commit f15a3ac

Browse files
committed
Add more formats
1 parent 2f5839d commit f15a3ac

File tree

3 files changed

+160
-39
lines changed

3 files changed

+160
-39
lines changed

packages/start/src/server/server-functions-handler.ts

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
serializeToJSONStream,
1414
serializeToJSStream,
1515
} from "./serialization.ts";
16+
import { BODY_FORMAL_FILE, BODY_FORMAT_KEY, BodyFormat } from "./server-functions-shared.ts";
1617
import type { FetchEvent, PageEvent } from "./types.ts";
1718
import { getExpectedRedirectStatus } from "./util.ts";
1819

@@ -43,7 +44,7 @@ export async function handleServerFunction(h3Event: H3Event) {
4344
let parsed: any[] = [];
4445

4546
// grab bound arguments from url when no JS
46-
if (!instance || h3Event.method === "GET") {
47+
if (!instance || request.method === "GET") {
4748
const args = url.searchParams.get("args");
4849
if (args) {
4950
const result = (await deserializeFromJSONString(args)) as any[];
@@ -54,17 +55,38 @@ export async function handleServerFunction(h3Event: H3Event) {
5455
}
5556
if (request.method === "POST") {
5657
const contentType = request.headers.get("content-type");
58+
const startType = request.headers.get(BODY_FORMAT_KEY);
5759
const clone = request.clone();
5860

59-
if (
60-
contentType?.startsWith("multipart/form-data") ||
61-
contentType?.startsWith("application/x-www-form-urlencoded")
62-
) {
63-
parsed.push(await clone.formData());
64-
} else if (contentType?.startsWith('application/json')) {
65-
parsed = await clone.json() as any[];
66-
} else if (request.headers.has('x-serialized')) {
67-
parsed = (await deserializeJSONStream(clone)) as any[];
61+
switch (true) {
62+
case startType === BodyFormat.Seroval:
63+
parsed = (await deserializeJSONStream(clone)) as any[];
64+
break;
65+
case startType === BodyFormat.String:
66+
parsed.push(await clone.text());
67+
break;
68+
case startType === BodyFormat.File: {
69+
const formData = await clone.formData();
70+
parsed.push(formData.get(BODY_FORMAL_FILE));
71+
break;
72+
}
73+
case startType === BodyFormat.FormData:
74+
case contentType?.startsWith("multipart/form-data"):
75+
parsed.push(await clone.formData());
76+
break;
77+
case startType === BodyFormat.URLSearchParams:
78+
case contentType?.startsWith("application/x-www-form-urlencoded"):
79+
parsed.push(new URLSearchParams(await clone.text()));
80+
break;
81+
case startType === BodyFormat.Blob:
82+
parsed.push(await clone.blob());
83+
break;
84+
case startType === BodyFormat.ArrayBuffer:
85+
parsed.push(await clone.arrayBuffer());
86+
break;
87+
case startType === BodyFormat.Uint8Array:
88+
parsed.push(await clone.bytes());
89+
break;
6890
}
6991
}
7092
try {
@@ -92,14 +114,14 @@ export async function handleServerFunction(h3Event: H3Event) {
92114
h3Event.res.status = result.status;
93115
if ((result as any).customBody) {
94116
result = await (result as any).customBody();
95-
} else if (result.body == undefined) result = null;
117+
} else if (result.body == null) result = null;
96118
}
97119
}
98120

99121
// handle no JS success case
100122
if (!instance) return handleNoJS(result, request, parsed);
101123

102-
h3Event.res.headers.set("x-serialized", "true");
124+
h3Event.res.headers.set(BODY_FORMAT_KEY, "true");
103125
if (import.meta.env.SEROVAL_MODE === "js") {
104126
h3Event.res.headers.set("content-type", "text/javascript");
105127
return serializeToJSStream(instance, result);
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
2+
export const BODY_FORMAT_KEY = "X-Start-Type";
3+
4+
export const BODY_FORMAL_FILE = "__START__";
5+
6+
export const enum BodyFormat {
7+
Seroval = "0",
8+
String = "1",
9+
FormData = "2",
10+
URLSearchParams = "3",
11+
Blob = "4",
12+
File = "5",
13+
ArrayBuffer = "6",
14+
Uint8Array = "7",
15+
}

packages/start/src/server/server-runtime.ts

Lines changed: 111 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
21
import { type Component } from "solid-js";
32
import {
43
deserializeJSONStream,
54
deserializeJSStream,
65
// serializeToJSONStream,
76
serializeToJSONString,
87
} from "./serialization";
8+
import { BODY_FORMAL_FILE, BODY_FORMAT_KEY, BodyFormat } from "./server-functions-shared";
99

1010
let INSTANCE = 0;
1111

@@ -25,38 +25,122 @@ function createRequest(
2525
},
2626
});
2727
}
28+
29+
function getHeadersAndBody(body: any): {
30+
headers?: HeadersInit;
31+
body: BodyInit;
32+
} | undefined {
33+
switch (true) {
34+
case typeof body === "string":
35+
return {
36+
headers: {
37+
"Content-Type": "text/plain",
38+
[BODY_FORMAT_KEY]: BodyFormat.String,
39+
},
40+
body,
41+
};
42+
case body instanceof FormData:
43+
return {
44+
headers: {
45+
"Content-Type": "multipart/form-data",
46+
[BODY_FORMAT_KEY]: BodyFormat.FormData,
47+
},
48+
body,
49+
};
50+
case body instanceof URLSearchParams:
51+
return {
52+
headers: {
53+
"Content-Type": "application/x-www-form-urlencoded",
54+
[BODY_FORMAT_KEY]: BodyFormat.URLSearchParams,
55+
},
56+
body,
57+
};
58+
case body instanceof Blob:
59+
return {
60+
headers: {
61+
[BODY_FORMAT_KEY]: BodyFormat.Blob,
62+
},
63+
body,
64+
};
65+
case body instanceof File: {
66+
const formData = new FormData();
67+
formData.append(BODY_FORMAL_FILE, body, body.name);
68+
return {
69+
headers: {
70+
[BODY_FORMAT_KEY]: BodyFormat.File,
71+
},
72+
body: new FormData(),
73+
};
74+
}
75+
case body instanceof ArrayBuffer:
76+
return {
77+
headers: {
78+
[BODY_FORMAT_KEY]: BodyFormat.ArrayBuffer,
79+
},
80+
body,
81+
};
82+
case body instanceof Uint8Array:
83+
return {
84+
headers: {
85+
[BODY_FORMAT_KEY]: BodyFormat.Uint8Array,
86+
},
87+
body: new Uint8Array(body),
88+
};
89+
default:
90+
return undefined;
91+
}
92+
}
93+
94+
async function initializeResponse(
95+
base: string,
96+
id: string,
97+
instance: string,
98+
options: RequestInit,
99+
args: any[],
100+
) {
101+
// No args, skip serialization
102+
if (args.length === 0) {
103+
return createRequest(base, id, instance, options);
104+
}
105+
// For single arguments, we can directly encode as body
106+
if (args.length === 1) {
107+
const body = args[0];
108+
const result = getHeadersAndBody(body);
109+
if (result) {
110+
return createRequest(base, id, instance, {
111+
...options,
112+
body: result.body,
113+
headers: {
114+
...options.headers,
115+
...result.headers,
116+
},
117+
});
118+
}
119+
}
120+
// Fallback to seroval
121+
return createRequest(base, id, instance, {
122+
...options,
123+
// TODO(Alexis): move to serializeToJSONStream
124+
body: await serializeToJSONString(args),
125+
// duplex: 'half',
126+
// body: serializeToJSONStream(args),
127+
headers: {
128+
...options.headers,
129+
"Content-Type": "text/plain",
130+
[BODY_FORMAT_KEY]: BodyFormat.Seroval,
131+
},
132+
});
133+
}
134+
28135
async function fetchServerFunction(
29136
base: string,
30137
id: string,
31138
options: Omit<RequestInit, "body">,
32139
args: any[],
33140
) {
34141
const instance = `server-fn:${INSTANCE++}`;
35-
const response = await (args.length === 0
36-
? createRequest(base, id, instance, options)
37-
: args.length === 1 && args[0] instanceof FormData
38-
? createRequest(base, id, instance, { ...options, body: args[0] })
39-
: args.length === 1 && args[0] instanceof URLSearchParams
40-
? createRequest(base, id, instance, {
41-
...options,
42-
body: args[0],
43-
headers: {
44-
...options.headers,
45-
"Content-Type": "application/x-www-form-urlencoded",
46-
},
47-
})
48-
: createRequest(base, id, instance, {
49-
...options,
50-
// TODO(Alexis): move to serializeToJSONStream
51-
body: await serializeToJSONString(args),
52-
// duplex: 'half',
53-
// body: serializeToJSONStream(args),
54-
headers: {
55-
...options.headers,
56-
"x-serialized": "true",
57-
"Content-Type": "text/plain"
58-
},
59-
}));
142+
143+
const response = await initializeResponse(base, id, instance, options, args);
60144

61145
if (
62146
response.headers.has("Location") ||
@@ -82,7 +166,7 @@ async function fetchServerFunction(
82166
result = await clone.text();
83167
} else if (contentType?.startsWith("application/json")) {
84168
result = await clone.json();
85-
} else if (response.headers.get('x-serialized')) {
169+
} else if (response.headers.get(BODY_FORMAT_KEY)) {
86170
if (import.meta.env.SEROVAL_MODE === "js") {
87171
result = await deserializeJSStream(instance, clone);
88172
} else {

0 commit comments

Comments
 (0)