@solidjs/start 0.4.10 → 0.5.0
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/config/api-handler.ts +64 -0
- package/config/fs-router.js +39 -14
- package/config/index.d.ts +2 -1
- package/config/index.js +52 -24
- package/config/{server-fns-runtime.jsx → server-fns-runtime.ts} +4 -1
- package/config/{server-handler.js → server-handler.ts} +71 -38
- package/config/{server-runtime.jsx → server-runtime.ts} +50 -27
- package/{server/middleware.ts → middleware/index.ts} +12 -46
- package/package.json +12 -12
- package/server/StartServer.tsx +3 -3
- package/server/fetchEvent.ts +115 -0
- package/{entry/index.ts → server/handler.ts} +17 -19
- package/server/index.tsx +2 -2
- package/server/{page-event.tsx → pageEvent.ts} +5 -36
- package/{entry/spa/index.ts → server/spa/handler.ts} +2 -2
- package/server/spa/index.tsx +1 -2
- package/server/types.ts +14 -4
- package/shared/FileRoutes.tsx +2 -2
- package/shared/GET.ts +5 -0
- package/shared/HttpHeader.tsx +18 -1
- package/shared/HttpStatusCode.tsx +3 -1
- package/shared/dev-overlay/DevOverlayDialog.tsx +51 -49
- package/shared/dev-overlay/createStackFrame.ts +9 -1
- package/shared/dev-overlay/styles.css +5 -1
- package/shared/index.tsx +2 -1
- package/shared/routes.ts +1 -132
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/// <reference types="vinxi/types/server" />
|
|
2
|
+
import { createRouter } from "radix3";
|
|
3
|
+
import { sharedConfig } from "solid-js";
|
|
4
|
+
import fileRoutes from "vinxi/routes";
|
|
5
|
+
|
|
6
|
+
// @ts-ignore
|
|
7
|
+
import { provideRequestEvent } from "solid-js/web/storage";
|
|
8
|
+
import { eventHandler } from "vinxi/server";
|
|
9
|
+
import { getFetchEvent } from "../server/fetchEvent";
|
|
10
|
+
import { APIEvent } from "../server/types";
|
|
11
|
+
|
|
12
|
+
export default eventHandler(h3Event => {
|
|
13
|
+
const event = getFetchEvent(h3Event);
|
|
14
|
+
const match = matchAPIRoute(new URL(event.request.url).pathname, event.request.method);
|
|
15
|
+
if (match) {
|
|
16
|
+
return provideRequestEvent(event, async () => {
|
|
17
|
+
const mod = await match.handler.import();
|
|
18
|
+
const fn = mod[event.request.method];
|
|
19
|
+
(event as APIEvent).params = match.params;
|
|
20
|
+
// @ts-ignore
|
|
21
|
+
sharedConfig.context = { event };
|
|
22
|
+
const res = await fn(event);
|
|
23
|
+
if (res === undefined && event.request.method !== "GET") {
|
|
24
|
+
throw new Error(
|
|
25
|
+
`API handler for ${event.request.method} "${event.request.url}" did not return a response.`
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
return res;
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
interface Route {
|
|
34
|
+
path: string;
|
|
35
|
+
id: string;
|
|
36
|
+
type: "api" | "page";
|
|
37
|
+
children?: Route[];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const router = createRouter({
|
|
41
|
+
routes: (fileRoutes as unknown as Route[]).reduce((memo, route) => {
|
|
42
|
+
if (route.type !== "api") return memo;
|
|
43
|
+
const path = route.path.replace(/\(.*\)\/?/g, "");
|
|
44
|
+
if (memo[path]) {
|
|
45
|
+
throw new Error(
|
|
46
|
+
`Duplicate API routes for "${path}" found at "${memo[path].route.path}" and "${route.path}"`
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
memo[path] = { route };
|
|
50
|
+
return memo;
|
|
51
|
+
}, {} as Record<string, { route: Route }>)
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
function matchAPIRoute(path: string, method: string) {
|
|
55
|
+
const match = router.lookup(path);
|
|
56
|
+
if (match) {
|
|
57
|
+
const handler = match.route[`$${method}`];
|
|
58
|
+
if (handler === "skip" || handler === undefined) return;
|
|
59
|
+
return {
|
|
60
|
+
handler,
|
|
61
|
+
params: match.params
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
}
|
package/config/fs-router.js
CHANGED
|
@@ -57,19 +57,7 @@ export class SolidStartClientFileRouter extends BaseFileSystemRouter {
|
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
|
-
|
|
61
|
-
function createHTTPHandlers(src, exports) {
|
|
62
|
-
const handlers = {};
|
|
63
|
-
for (const exp of exports) {
|
|
64
|
-
if (HTTP_METHODS.includes(exp.n)) {
|
|
65
|
-
handlers[`$${exp.n}`] = {
|
|
66
|
-
src: src,
|
|
67
|
-
pick: [exp.n]
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
return handlers;
|
|
72
|
-
}
|
|
60
|
+
|
|
73
61
|
export class SolidStartServerFileRouter extends BaseFileSystemRouter {
|
|
74
62
|
toPath(src) {
|
|
75
63
|
const routePath = cleanPath(src, this.config)
|
|
@@ -123,7 +111,44 @@ export class SolidStartServerFileRouter extends BaseFileSystemRouter {
|
|
|
123
111
|
path,
|
|
124
112
|
filePath: src
|
|
125
113
|
};
|
|
126
|
-
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const HTTP_METHODS = ["GET", "POST", "PUT", "DELETE", "PATCH"];
|
|
119
|
+
function createHTTPHandlers(src, exports) {
|
|
120
|
+
const handlers = {};
|
|
121
|
+
for (const exp of exports) {
|
|
122
|
+
if (HTTP_METHODS.includes(exp.n)) {
|
|
123
|
+
handlers[`$${exp.n}`] = {
|
|
124
|
+
src: src,
|
|
125
|
+
pick: [exp.n]
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return handlers;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export class SolidStartAPIFileRouter extends BaseFileSystemRouter {
|
|
133
|
+
toPath(src) {
|
|
134
|
+
const routePath = cleanPath(src, this.config)
|
|
135
|
+
// remove the initial slash
|
|
136
|
+
.slice(1)
|
|
137
|
+
.replace(/index$/, "")
|
|
138
|
+
.replace(/\[([^\/]+)\]/g, (_, m) => {
|
|
139
|
+
if (m.length > 2 && m.startsWith("...")) {
|
|
140
|
+
return `**${m.length > 3 ? ":" + m.slice(3) : ""}`;
|
|
141
|
+
}
|
|
142
|
+
return `:${m}`;
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
return routePath?.length > 0 ? `/${routePath}` : "/";
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
toRoute(src) {
|
|
149
|
+
let path = this.toPath(src);
|
|
150
|
+
const [_, exports] = analyzeModule(src);
|
|
151
|
+
if (exports.find(exp => HTTP_METHODS.includes(exp.n))) {
|
|
127
152
|
return {
|
|
128
153
|
type: "api",
|
|
129
154
|
...createHTTPHandlers(src, exports),
|
package/config/index.d.ts
CHANGED
|
@@ -9,8 +9,9 @@ type SolidStartInlineConfig = Omit<InlineConfig, "router"> & {
|
|
|
9
9
|
* true: streaming mode
|
|
10
10
|
* false: csr only
|
|
11
11
|
* async: ssr is in async mode
|
|
12
|
+
* sync: ssr is in sync mode
|
|
12
13
|
*/
|
|
13
|
-
ssr?: boolean | "async",
|
|
14
|
+
ssr?: boolean | "async" | "sync",
|
|
14
15
|
solid?: Options,
|
|
15
16
|
extensions?: string[],
|
|
16
17
|
server?: AppOptions['server'],
|
package/config/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import { createApp, resolve } from "vinxi";
|
|
|
8
8
|
import { normalize } from "vinxi/lib/path";
|
|
9
9
|
import { config } from "vinxi/plugins/config";
|
|
10
10
|
import solid from "vite-plugin-solid";
|
|
11
|
-
import { SolidStartClientFileRouter, SolidStartServerFileRouter } from "./fs-router.js";
|
|
11
|
+
import { SolidStartAPIFileRouter, SolidStartClientFileRouter, SolidStartServerFileRouter } from "./fs-router.js";
|
|
12
12
|
import { serverComponents } from "./server-components.js";
|
|
13
13
|
|
|
14
14
|
const DEFAULT_EXTENSIONS = ["js", "jsx", "ts", "tsx"];
|
|
@@ -18,7 +18,7 @@ function solidStartClientFsRouter(config) {
|
|
|
18
18
|
new SolidStartClientFileRouter(
|
|
19
19
|
{
|
|
20
20
|
dir: resolve.absolute(config.dir, router.root),
|
|
21
|
-
extensions: config.extensions ??
|
|
21
|
+
extensions: config.extensions ?? DEFAULT_EXTENSIONS
|
|
22
22
|
},
|
|
23
23
|
router,
|
|
24
24
|
app
|
|
@@ -30,7 +30,19 @@ function solidStartServerFsRouter(config) {
|
|
|
30
30
|
new SolidStartServerFileRouter(
|
|
31
31
|
{
|
|
32
32
|
dir: resolve.absolute(config.dir, router.root),
|
|
33
|
-
extensions: config.extensions ??
|
|
33
|
+
extensions: config.extensions ?? DEFAULT_EXTENSIONS
|
|
34
|
+
},
|
|
35
|
+
router,
|
|
36
|
+
app
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function solidStartAPIFileRouter(config) {
|
|
41
|
+
return (router, app) =>
|
|
42
|
+
new SolidStartAPIFileRouter(
|
|
43
|
+
{
|
|
44
|
+
dir: resolve.absolute(config.dir, router.root),
|
|
45
|
+
extensions: config.extensions ?? DEFAULT_EXTENSIONS
|
|
34
46
|
},
|
|
35
47
|
router,
|
|
36
48
|
app
|
|
@@ -44,7 +56,12 @@ export function defineConfig(baseConfig = {}) {
|
|
|
44
56
|
appRoot: "./src",
|
|
45
57
|
ssr: true,
|
|
46
58
|
islands: false,
|
|
47
|
-
solid: {}
|
|
59
|
+
solid: {},
|
|
60
|
+
server: {
|
|
61
|
+
experimental: {
|
|
62
|
+
asyncContext: true
|
|
63
|
+
}
|
|
64
|
+
}
|
|
48
65
|
});
|
|
49
66
|
let server = start.server;
|
|
50
67
|
if (!start.ssr) {
|
|
@@ -65,14 +82,25 @@ export function defineConfig(baseConfig = {}) {
|
|
|
65
82
|
routers: [
|
|
66
83
|
{
|
|
67
84
|
name: "public",
|
|
68
|
-
|
|
85
|
+
type: "static",
|
|
69
86
|
dir: "./public",
|
|
70
87
|
base: "/"
|
|
71
88
|
},
|
|
72
|
-
|
|
89
|
+
{
|
|
90
|
+
name: "api",
|
|
91
|
+
type: "http",
|
|
92
|
+
target: "server",
|
|
93
|
+
base: "/",
|
|
94
|
+
handler: normalize(fileURLToPath(new URL("./api-handler.ts", import.meta.url))),
|
|
95
|
+
routes: solidStartAPIFileRouter({ dir: `${start.appRoot}/routes` }),
|
|
96
|
+
middleware: start.middleware
|
|
97
|
+
},
|
|
73
98
|
{
|
|
74
99
|
name: "ssr",
|
|
75
|
-
|
|
100
|
+
type: "http",
|
|
101
|
+
link: {
|
|
102
|
+
client: start.islands ? undefined : "client"
|
|
103
|
+
},
|
|
76
104
|
handler: `${start.appRoot}/entry-server${entryExtension}`,
|
|
77
105
|
middleware: start.middleware,
|
|
78
106
|
routes: solidStartServerFsRouter({ dir: `${start.appRoot}/routes`, extensions }),
|
|
@@ -85,15 +113,15 @@ export function defineConfig(baseConfig = {}) {
|
|
|
85
113
|
...(userConfig.optimizeDeps || {}),
|
|
86
114
|
include: [
|
|
87
115
|
...(userConfig.optimizeDeps?.include || []),
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
]
|
|
91
|
-
}
|
|
116
|
+
"@solidjs/start > source-map-js",
|
|
117
|
+
"@solidjs/start > error-stack-parser"
|
|
118
|
+
]
|
|
119
|
+
}
|
|
92
120
|
}),
|
|
93
121
|
...(typeof plugins === "function" ? [...(await plugins())] : plugins),
|
|
94
122
|
|
|
95
123
|
serverTransform({
|
|
96
|
-
runtime: normalize(fileURLToPath(new URL("./server-fns-runtime.
|
|
124
|
+
runtime: normalize(fileURLToPath(new URL("./server-fns-runtime.ts", import.meta.url)))
|
|
97
125
|
}),
|
|
98
126
|
start.islands ? serverComponents.server() : null,
|
|
99
127
|
solid({ ...start.solid, ssr: true, extensions: extensions.map(ext => `.${ext}`) }),
|
|
@@ -122,7 +150,7 @@ export function defineConfig(baseConfig = {}) {
|
|
|
122
150
|
},
|
|
123
151
|
{
|
|
124
152
|
name: "client",
|
|
125
|
-
|
|
153
|
+
type: "client",
|
|
126
154
|
handler: `${start.appRoot}/entry-client${entryExtension}`,
|
|
127
155
|
...(start.islands
|
|
128
156
|
? {}
|
|
@@ -138,14 +166,14 @@ export function defineConfig(baseConfig = {}) {
|
|
|
138
166
|
...(userConfig.optimizeDeps || {}),
|
|
139
167
|
include: [
|
|
140
168
|
...(userConfig.optimizeDeps?.include || []),
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
]
|
|
144
|
-
}
|
|
169
|
+
"@solidjs/start > source-map-js",
|
|
170
|
+
"@solidjs/start > error-stack-parser"
|
|
171
|
+
]
|
|
172
|
+
}
|
|
145
173
|
}),
|
|
146
174
|
...(typeof plugins === "function" ? [...(await plugins())] : plugins),
|
|
147
175
|
serverFunctions.client({
|
|
148
|
-
runtime: normalize(fileURLToPath(new URL("./server-runtime.
|
|
176
|
+
runtime: normalize(fileURLToPath(new URL("./server-runtime.ts", import.meta.url)))
|
|
149
177
|
}),
|
|
150
178
|
start.islands ? serverComponents.client() : null,
|
|
151
179
|
solid({ ...start.solid, ssr: start.ssr, extensions: extensions.map(ext => `.${ext}`) }),
|
|
@@ -179,8 +207,8 @@ export function defineConfig(baseConfig = {}) {
|
|
|
179
207
|
base: "/_build"
|
|
180
208
|
},
|
|
181
209
|
serverFunctions.router({
|
|
182
|
-
handler: normalize(fileURLToPath(new URL("./server-handler.
|
|
183
|
-
runtime: normalize(fileURLToPath(new URL("./server-fns-runtime.
|
|
210
|
+
handler: normalize(fileURLToPath(new URL("./server-handler.ts", import.meta.url))),
|
|
211
|
+
runtime: normalize(fileURLToPath(new URL("./server-fns-runtime.ts", import.meta.url))),
|
|
184
212
|
// routes: solidStartServerFsRouter({ dir: `${start.appRoot}/routes`, extensions }),
|
|
185
213
|
plugins: async () => [
|
|
186
214
|
config("user", {
|
|
@@ -189,10 +217,10 @@ export function defineConfig(baseConfig = {}) {
|
|
|
189
217
|
...(userConfig.optimizeDeps || {}),
|
|
190
218
|
include: [
|
|
191
219
|
...(userConfig.optimizeDeps?.include || []),
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
]
|
|
195
|
-
}
|
|
220
|
+
"@solidjs/start > source-map-js",
|
|
221
|
+
"@solidjs/start > error-stack-parser"
|
|
222
|
+
]
|
|
223
|
+
}
|
|
196
224
|
}),
|
|
197
225
|
...(typeof plugins === "function" ? [...(await plugins())] : plugins),
|
|
198
226
|
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import { getRequestEvent } from "solid-js/web";
|
|
2
|
+
//@ts-ignore
|
|
2
3
|
import { provideRequestEvent } from "solid-js/web/storage";
|
|
3
|
-
import { cloneEvent } from "../server/
|
|
4
|
+
import { cloneEvent } from "../server/fetchEvent";
|
|
4
5
|
|
|
5
6
|
export function createServerReference(fn, id, name) {
|
|
7
|
+
if (typeof fn !== "function") throw new Error("Export from a 'use server' module must be a function");
|
|
6
8
|
const baseURL = import.meta.env.SERVER_BASE_URL;
|
|
7
9
|
return new Proxy(fn, {
|
|
8
10
|
get(target, prop, receiver) {
|
|
9
11
|
if (prop === "url") {
|
|
10
12
|
return `${baseURL}/_server?id=${encodeURIComponent(id)}&name=${encodeURIComponent(name)}`;
|
|
11
13
|
}
|
|
14
|
+
if (prop === "GET") return receiver;
|
|
12
15
|
},
|
|
13
16
|
apply(target, thisArg, args) {
|
|
14
17
|
const ogEvt = getRequestEvent();
|
|
@@ -16,16 +16,13 @@ import { sharedConfig } from "solid-js";
|
|
|
16
16
|
/* @ts-ignore */
|
|
17
17
|
import { provideRequestEvent } from "solid-js/web/storage";
|
|
18
18
|
import invariant from "vinxi/lib/invariant";
|
|
19
|
-
import {
|
|
20
|
-
|
|
21
|
-
setHeader
|
|
22
|
-
} from "vinxi/server";
|
|
23
|
-
import { getFetchEvent } from "../server/middleware";
|
|
19
|
+
import { eventHandler, setHeader } from "vinxi/server";
|
|
20
|
+
import { getFetchEvent, mergeResponseHeaders } from "../server/fetchEvent";
|
|
24
21
|
|
|
25
22
|
function createChunk(data) {
|
|
26
23
|
const bytes = data.length;
|
|
27
24
|
const baseHex = bytes.toString(16);
|
|
28
|
-
const totalHex =
|
|
25
|
+
const totalHex = "00000000".substring(0, 8 - baseHex.length) + baseHex; // 32-bit
|
|
29
26
|
return new TextEncoder().encode(`;0x${totalHex};${data}`);
|
|
30
27
|
}
|
|
31
28
|
|
|
@@ -47,7 +44,9 @@ function serializeToStream(id, value) {
|
|
|
47
44
|
URLPlugin
|
|
48
45
|
],
|
|
49
46
|
onSerialize(data, initial) {
|
|
50
|
-
controller.enqueue(
|
|
47
|
+
controller.enqueue(
|
|
48
|
+
createChunk(initial ? `(${getCrossReferenceHeader(id)},${data})` : data)
|
|
49
|
+
);
|
|
51
50
|
},
|
|
52
51
|
onDone() {
|
|
53
52
|
controller.close();
|
|
@@ -61,7 +60,6 @@ function serializeToStream(id, value) {
|
|
|
61
60
|
}
|
|
62
61
|
|
|
63
62
|
async function handleServerFunction(h3Event) {
|
|
64
|
-
invariant(h3Event.method === "POST", `Invalid method ${h3Event.method}. Expected POST.`);
|
|
65
63
|
const event = getFetchEvent(h3Event);
|
|
66
64
|
const request = event.request;
|
|
67
65
|
|
|
@@ -84,39 +82,67 @@ async function handleServerFunction(h3Event) {
|
|
|
84
82
|
let parsed = [];
|
|
85
83
|
|
|
86
84
|
// grab bound arguments from url when no JS
|
|
87
|
-
if (!instance) {
|
|
85
|
+
if (!instance || h3Event.method === "GET") {
|
|
88
86
|
const args = url.searchParams.get("args");
|
|
89
87
|
if (args) JSON.parse(args).forEach(arg => parsed.push(arg));
|
|
90
88
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
89
|
+
if (h3Event.method === "POST") {
|
|
90
|
+
const contentType = request.headers.get("content-type");
|
|
91
|
+
if (
|
|
92
|
+
contentType.startsWith("multipart/form-data") ||
|
|
93
|
+
contentType.startsWith("application/x-www-form-urlencoded")
|
|
94
|
+
) {
|
|
95
|
+
// workaround for https://github.com/unjs/nitro/issues/1721
|
|
96
|
+
// (issue only in edge runtimes)
|
|
97
|
+
parsed.push(await new Request(request, { ...request, body: h3Event.node.req.body }).formData());
|
|
98
|
+
// what should work when #1721 is fixed
|
|
99
|
+
// parsed.push(await request.formData);
|
|
100
|
+
} else {
|
|
101
|
+
// workaround for https://github.com/unjs/nitro/issues/1721
|
|
102
|
+
// (issue only in edge runtimes)
|
|
103
|
+
const tmpReq = new Request(request, { ...request, body: h3Event.node.req.body })
|
|
104
|
+
// what should work when #1721 is fixed
|
|
105
|
+
// just use request.json() here
|
|
106
|
+
parsed = fromJSON(await tmpReq.json(), {
|
|
107
|
+
plugins: [
|
|
108
|
+
CustomEventPlugin,
|
|
109
|
+
DOMExceptionPlugin,
|
|
110
|
+
EventPlugin,
|
|
111
|
+
FormDataPlugin,
|
|
112
|
+
HeadersPlugin,
|
|
113
|
+
ReadableStreamPlugin,
|
|
114
|
+
RequestPlugin,
|
|
115
|
+
ResponsePlugin,
|
|
116
|
+
URLSearchParamsPlugin,
|
|
117
|
+
URLPlugin
|
|
118
|
+
]
|
|
119
|
+
});
|
|
120
|
+
}
|
|
112
121
|
}
|
|
113
122
|
try {
|
|
114
|
-
|
|
123
|
+
let result = await provideRequestEvent(event, () => {
|
|
115
124
|
/* @ts-ignore */
|
|
116
125
|
sharedConfig.context = { event };
|
|
117
126
|
return action(...parsed);
|
|
118
127
|
});
|
|
119
128
|
|
|
129
|
+
// handle responses
|
|
130
|
+
if (result instanceof Response) {
|
|
131
|
+
if (result.status === 302) {
|
|
132
|
+
return new Response(null, {
|
|
133
|
+
status: instance ? 204 : 302,
|
|
134
|
+
headers: {
|
|
135
|
+
Location: result.headers.get("Location")
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
// forward headers
|
|
140
|
+
if (result.headers) mergeResponseHeaders(h3Event, result.headers);
|
|
141
|
+
if ((result as any).customBody) {
|
|
142
|
+
result = await (result as any).customBody();
|
|
143
|
+
} else if (result.body == undefined) result = undefined;
|
|
144
|
+
}
|
|
145
|
+
|
|
120
146
|
// handle no JS success case
|
|
121
147
|
if (!instance) {
|
|
122
148
|
const isError = result instanceof Error;
|
|
@@ -142,13 +168,20 @@ async function handleServerFunction(h3Event) {
|
|
|
142
168
|
setHeader(h3Event, "content-type", "text/javascript");
|
|
143
169
|
return serializeToStream(instance, result);
|
|
144
170
|
} catch (x) {
|
|
145
|
-
if (x instanceof Response
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
171
|
+
if (x instanceof Response) {
|
|
172
|
+
if (x.status === 302) {
|
|
173
|
+
return new Response(null, {
|
|
174
|
+
status: instance ? 204 : 302,
|
|
175
|
+
headers: {
|
|
176
|
+
Location: x.headers.get("Location")
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
// forward headers
|
|
181
|
+
if (x.headers) mergeResponseHeaders(h3Event, x.headers);
|
|
182
|
+
if ((x as any).customBody) {
|
|
183
|
+
x = await (x as any).customBody();
|
|
184
|
+
} else if (x.body == undefined) x = undefined;
|
|
152
185
|
}
|
|
153
186
|
return x;
|
|
154
187
|
}
|
|
@@ -9,14 +9,17 @@ import {
|
|
|
9
9
|
RequestPlugin,
|
|
10
10
|
ResponsePlugin,
|
|
11
11
|
URLPlugin,
|
|
12
|
-
URLSearchParamsPlugin
|
|
13
|
-
} from
|
|
12
|
+
URLSearchParamsPlugin
|
|
13
|
+
} from "seroval-plugins/web";
|
|
14
14
|
import { createIslandReference } from "../server/islands";
|
|
15
15
|
|
|
16
16
|
class SerovalChunkReader {
|
|
17
|
+
reader: ReadableStreamDefaultReader<Uint8Array>;
|
|
18
|
+
buffer: string;
|
|
19
|
+
done: boolean;
|
|
17
20
|
constructor(stream) {
|
|
18
21
|
this.reader = stream.getReader();
|
|
19
|
-
this.buffer =
|
|
22
|
+
this.buffer = "";
|
|
20
23
|
this.done = false;
|
|
21
24
|
}
|
|
22
25
|
|
|
@@ -33,12 +36,12 @@ class SerovalChunkReader {
|
|
|
33
36
|
|
|
34
37
|
async next() {
|
|
35
38
|
// Check if the buffer is empty
|
|
36
|
-
if (this.buffer ===
|
|
39
|
+
if (this.buffer === "") {
|
|
37
40
|
// if we are already done...
|
|
38
41
|
if (this.done) {
|
|
39
42
|
return {
|
|
40
43
|
done: true,
|
|
41
|
-
value: undefined
|
|
44
|
+
value: undefined
|
|
42
45
|
};
|
|
43
46
|
}
|
|
44
47
|
// Otherwise, read a new chunk
|
|
@@ -55,7 +58,7 @@ class SerovalChunkReader {
|
|
|
55
58
|
// If it's not enough, and the reader is done
|
|
56
59
|
// then the chunk is invalid.
|
|
57
60
|
if (this.done) {
|
|
58
|
-
throw new Error(
|
|
61
|
+
throw new Error("Malformed server function stream.");
|
|
59
62
|
}
|
|
60
63
|
// Otherwise, we read more chunks
|
|
61
64
|
await this.readChunk();
|
|
@@ -67,7 +70,7 @@ class SerovalChunkReader {
|
|
|
67
70
|
// Deserialize the chunk
|
|
68
71
|
return {
|
|
69
72
|
done: false,
|
|
70
|
-
value: deserialize(partial)
|
|
73
|
+
value: deserialize(partial)
|
|
71
74
|
};
|
|
72
75
|
}
|
|
73
76
|
|
|
@@ -92,6 +95,7 @@ async function deserializeStream(id, response) {
|
|
|
92
95
|
if (!result.done) {
|
|
93
96
|
reader.drain().then(
|
|
94
97
|
() => {
|
|
98
|
+
// @ts-ignore
|
|
95
99
|
delete $R[id];
|
|
96
100
|
},
|
|
97
101
|
() => {
|
|
@@ -105,7 +109,7 @@ async function deserializeStream(id, response) {
|
|
|
105
109
|
|
|
106
110
|
let INSTANCE = 0;
|
|
107
111
|
|
|
108
|
-
function createRequest(base, id, instance, body, contentType) {
|
|
112
|
+
function createRequest(base: string, id: string, instance: string, body: any, contentType?: string) {
|
|
109
113
|
return fetch(base, {
|
|
110
114
|
method: "POST",
|
|
111
115
|
headers: {
|
|
@@ -117,31 +121,47 @@ function createRequest(base, id, instance, body, contentType) {
|
|
|
117
121
|
});
|
|
118
122
|
}
|
|
119
123
|
|
|
120
|
-
async function fetchServerFunction(base, id, args) {
|
|
124
|
+
async function fetchServerFunction(base, id, method, args) {
|
|
121
125
|
const instance = `server-fn:${INSTANCE++}`;
|
|
122
|
-
const response = await (
|
|
126
|
+
const response = await (method === "GET"
|
|
127
|
+
? fetch(base + (args.length ? `&args=${JSON.stringify(args)}` : ""), {
|
|
128
|
+
headers: {
|
|
129
|
+
"x-server-instance": instance
|
|
130
|
+
}
|
|
131
|
+
})
|
|
132
|
+
: args.length === 1 && args[0] instanceof FormData
|
|
123
133
|
? createRequest(base, id, instance, args[0])
|
|
124
|
-
: createRequest(
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
134
|
+
: createRequest(
|
|
135
|
+
base,
|
|
136
|
+
id,
|
|
137
|
+
instance,
|
|
138
|
+
JSON.stringify(
|
|
139
|
+
await Promise.resolve(
|
|
140
|
+
toJSONAsync(args, {
|
|
141
|
+
plugins: [
|
|
142
|
+
CustomEventPlugin,
|
|
143
|
+
DOMExceptionPlugin,
|
|
144
|
+
EventPlugin,
|
|
145
|
+
FormDataPlugin,
|
|
146
|
+
HeadersPlugin,
|
|
147
|
+
ReadableStreamPlugin,
|
|
148
|
+
RequestPlugin,
|
|
149
|
+
ResponsePlugin,
|
|
150
|
+
URLSearchParamsPlugin,
|
|
151
|
+
URLPlugin
|
|
152
|
+
]
|
|
153
|
+
})
|
|
154
|
+
)
|
|
155
|
+
),
|
|
156
|
+
"application/json"
|
|
157
|
+
));
|
|
138
158
|
|
|
139
159
|
if (response.headers.get("Location")) throw response;
|
|
140
160
|
if (response.headers.get("X-Revalidate")) {
|
|
141
|
-
/* ts-ignore-next-line */
|
|
161
|
+
/* @ts-ignore-next-line */
|
|
142
162
|
response.customBody = () => {
|
|
143
163
|
return deserializeStream(instance, response);
|
|
144
|
-
}
|
|
164
|
+
};
|
|
145
165
|
throw response;
|
|
146
166
|
}
|
|
147
167
|
const contentType = response.headers.get("Content-Type");
|
|
@@ -166,9 +186,12 @@ export function createServerReference(fn, id, name) {
|
|
|
166
186
|
if (prop === "url") {
|
|
167
187
|
return `${baseURL}/_server?id=${encodeURIComponent(id)}&name=${encodeURIComponent(name)}`;
|
|
168
188
|
}
|
|
189
|
+
if (prop === "GET") {
|
|
190
|
+
return (...args) => fetchServerFunction(`${baseURL}/_server/?id=${encodeURIComponent(id)}&name=${encodeURIComponent(name)}`, `${id}#${name}`, "GET", args);
|
|
191
|
+
}
|
|
169
192
|
},
|
|
170
193
|
apply(target, thisArg, args) {
|
|
171
|
-
return fetchServerFunction(`${baseURL}/_server`, `${id}#${name}`, args);
|
|
194
|
+
return fetchServerFunction(`${baseURL}/_server`, `${id}#${name}`, "POST", args);
|
|
172
195
|
}
|
|
173
196
|
});
|
|
174
197
|
}
|