@solidjs/start 0.5.1 → 0.5.2
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/index.js +1 -1
- package/config/server-fns-runtime.ts +1 -1
- package/config/server-handler.ts +67 -30
- package/config/server-runtime.ts +54 -32
- package/package.json +2 -2
- package/server/fetchEvent.ts +6 -6
- package/shared/ErrorBoundary.tsx +3 -14
package/config/index.js
CHANGED
|
@@ -191,7 +191,7 @@ export function defineConfig(baseConfig = {}) {
|
|
|
191
191
|
serverFunctions.router({
|
|
192
192
|
handler: normalize(fileURLToPath(new URL("./server-handler.ts", import.meta.url))),
|
|
193
193
|
runtime: normalize(fileURLToPath(new URL("./server-fns-runtime.ts", import.meta.url))),
|
|
194
|
-
|
|
194
|
+
routes: solidStartServerFsRouter({ dir: `${start.appRoot}/routes`, extensions }),
|
|
195
195
|
plugins: async () => [
|
|
196
196
|
config("user", {
|
|
197
197
|
...userConfig,
|
|
@@ -8,7 +8,7 @@ export function createServerReference(fn, id, name) {
|
|
|
8
8
|
return new Proxy(fn, {
|
|
9
9
|
get(target, prop, receiver) {
|
|
10
10
|
if (prop === "url") {
|
|
11
|
-
return `${baseURL}/_server
|
|
11
|
+
return `${baseURL}/_server/?id=${encodeURIComponent(id)}&name=${encodeURIComponent(name)}`;
|
|
12
12
|
}
|
|
13
13
|
if (prop === "GET") return receiver;
|
|
14
14
|
},
|
package/config/server-handler.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/// <reference types="vinxi/types/server" />
|
|
2
2
|
import { crossSerializeStream, fromJSON, getCrossReferenceHeader } from "seroval";
|
|
3
|
+
// @ts-ignore
|
|
3
4
|
import {
|
|
4
5
|
CustomEventPlugin,
|
|
5
6
|
DOMExceptionPlugin,
|
|
@@ -13,10 +14,15 @@ import {
|
|
|
13
14
|
URLSearchParamsPlugin
|
|
14
15
|
} from "seroval-plugins/web";
|
|
15
16
|
import { sharedConfig } from "solid-js";
|
|
17
|
+
import { renderToStringAsync } from "solid-js/web";
|
|
16
18
|
import { provideRequestEvent } from "solid-js/web/storage";
|
|
17
|
-
import { eventHandler, setHeader } from "vinxi/http";
|
|
19
|
+
import { eventHandler, setHeader, setResponseStatus } from "vinxi/http";
|
|
18
20
|
import invariant from "vinxi/lib/invariant";
|
|
19
|
-
import { getFetchEvent, mergeResponseHeaders } from "../server/fetchEvent";
|
|
21
|
+
import { cloneEvent, getFetchEvent, mergeResponseHeaders } from "../server/fetchEvent";
|
|
22
|
+
import { createPageEvent } from "../server/pageEvent";
|
|
23
|
+
// @ts-ignore
|
|
24
|
+
import App from "#start/app";
|
|
25
|
+
import { FetchEvent, PageEvent } from "../server";
|
|
20
26
|
|
|
21
27
|
function createChunk(data) {
|
|
22
28
|
const bytes = data.length;
|
|
@@ -62,8 +68,9 @@ async function handleServerFunction(h3Event) {
|
|
|
62
68
|
const event = getFetchEvent(h3Event);
|
|
63
69
|
const request = event.request;
|
|
64
70
|
|
|
65
|
-
const serverReference = request.headers.get("
|
|
66
|
-
const instance = request.headers.get("
|
|
71
|
+
const serverReference = request.headers.get("X-Server-Id");
|
|
72
|
+
const instance = request.headers.get("X-Server-Instance");
|
|
73
|
+
const singleFlight = request.headers.has("X-Single-Flight");
|
|
67
74
|
const url = new URL(request.url);
|
|
68
75
|
let filepath, name;
|
|
69
76
|
if (serverReference) {
|
|
@@ -75,7 +82,7 @@ async function handleServerFunction(h3Event) {
|
|
|
75
82
|
if (!filepath || !name) throw new Error("Invalid request");
|
|
76
83
|
}
|
|
77
84
|
|
|
78
|
-
const
|
|
85
|
+
const serverFunction = (
|
|
79
86
|
await import.meta.env.MANIFEST[import.meta.env.ROUTER_NAME].chunks[filepath].import()
|
|
80
87
|
)[name];
|
|
81
88
|
let parsed = [];
|
|
@@ -93,13 +100,15 @@ async function handleServerFunction(h3Event) {
|
|
|
93
100
|
) {
|
|
94
101
|
// workaround for https://github.com/unjs/nitro/issues/1721
|
|
95
102
|
// (issue only in edge runtimes)
|
|
96
|
-
parsed.push(
|
|
97
|
-
|
|
103
|
+
parsed.push(
|
|
104
|
+
await new Request(request, { ...request, body: h3Event.node.req.body }).formData()
|
|
105
|
+
);
|
|
106
|
+
// what should work when #1721 is fixed
|
|
98
107
|
// parsed.push(await request.formData);
|
|
99
108
|
} else {
|
|
100
109
|
// workaround for https://github.com/unjs/nitro/issues/1721
|
|
101
110
|
// (issue only in edge runtimes)
|
|
102
|
-
const tmpReq = new Request(request, { ...request, body: h3Event.node.req.body })
|
|
111
|
+
const tmpReq = new Request(request, { ...request, body: h3Event.node.req.body });
|
|
103
112
|
// what should work when #1721 is fixed
|
|
104
113
|
// just use request.json() here
|
|
105
114
|
parsed = fromJSON(await tmpReq.json(), {
|
|
@@ -119,27 +128,23 @@ async function handleServerFunction(h3Event) {
|
|
|
119
128
|
}
|
|
120
129
|
}
|
|
121
130
|
try {
|
|
122
|
-
let result = await provideRequestEvent(event, () => {
|
|
131
|
+
let result = await provideRequestEvent(event, async () => {
|
|
123
132
|
/* @ts-ignore */
|
|
124
133
|
sharedConfig.context = { event };
|
|
125
|
-
return
|
|
134
|
+
return serverFunction(...parsed);
|
|
126
135
|
});
|
|
127
136
|
|
|
137
|
+
if (singleFlight && instance) {
|
|
138
|
+
result = await handleSingleFlight(event, result);
|
|
139
|
+
}
|
|
140
|
+
|
|
128
141
|
// handle responses
|
|
129
|
-
if (result instanceof Response) {
|
|
130
|
-
if (result.status === 302) {
|
|
131
|
-
return new Response(null, {
|
|
132
|
-
status: instance ? 204 : 302,
|
|
133
|
-
headers: {
|
|
134
|
-
Location: result.headers.get("Location")
|
|
135
|
-
}
|
|
136
|
-
});
|
|
137
|
-
}
|
|
142
|
+
if (result instanceof Response && instance) {
|
|
138
143
|
// forward headers
|
|
139
144
|
if (result.headers) mergeResponseHeaders(h3Event, result.headers);
|
|
140
145
|
if ((result as any).customBody) {
|
|
141
146
|
result = await (result as any).customBody();
|
|
142
|
-
} else if (result.body == undefined) result =
|
|
147
|
+
} else if (result.body == undefined) result = null;
|
|
143
148
|
}
|
|
144
149
|
|
|
145
150
|
// handle no JS success case
|
|
@@ -163,27 +168,59 @@ async function handleServerFunction(h3Event) {
|
|
|
163
168
|
}
|
|
164
169
|
});
|
|
165
170
|
}
|
|
166
|
-
if (typeof result === "string") return
|
|
171
|
+
if (!result || typeof result === "string") return result || null;
|
|
167
172
|
setHeader(h3Event, "content-type", "text/javascript");
|
|
168
173
|
return serializeToStream(instance, result);
|
|
169
174
|
} catch (x) {
|
|
170
175
|
if (x instanceof Response) {
|
|
171
|
-
if (x.status === 302)
|
|
172
|
-
return new Response(null, {
|
|
173
|
-
status: instance ? 204 : 302,
|
|
174
|
-
headers: {
|
|
175
|
-
Location: x.headers.get("Location")
|
|
176
|
-
}
|
|
177
|
-
});
|
|
178
|
-
}
|
|
176
|
+
if (x.status === 302 && !instance) setResponseStatus(h3Event, 302);
|
|
179
177
|
// forward headers
|
|
180
178
|
if (x.headers) mergeResponseHeaders(h3Event, x.headers);
|
|
181
179
|
if ((x as any).customBody) {
|
|
182
180
|
x = await (x as any).customBody();
|
|
183
|
-
} else if (x.body == undefined) x =
|
|
181
|
+
} else if (x.body == undefined) x = null;
|
|
184
182
|
}
|
|
185
183
|
return x;
|
|
186
184
|
}
|
|
187
185
|
}
|
|
188
186
|
|
|
187
|
+
async function handleSingleFlight(sourceEvent: FetchEvent, result: any) {
|
|
188
|
+
let revalidate;
|
|
189
|
+
let url = new URL(sourceEvent.request.headers.get("referer")).toString();
|
|
190
|
+
if (result instanceof Response) {
|
|
191
|
+
if (result.headers.has("X-Revalidate"))
|
|
192
|
+
revalidate = result.headers.get("X-Revalidate").split(",");
|
|
193
|
+
if (result.headers.has("Location")) url = result.headers.get("Location");
|
|
194
|
+
}
|
|
195
|
+
const event = cloneEvent(sourceEvent) as PageEvent;
|
|
196
|
+
event.request = new Request(url);
|
|
197
|
+
return await provideRequestEvent(event, async () => {
|
|
198
|
+
await createPageEvent(event);
|
|
199
|
+
/* @ts-ignore */
|
|
200
|
+
sharedConfig.context = { event };
|
|
201
|
+
/* @ts-ignore */
|
|
202
|
+
event.router.dataOnly = revalidate || true;
|
|
203
|
+
/* @ts-ignore */
|
|
204
|
+
event.router.previousUrl = sourceEvent.request.headers.get("referer");
|
|
205
|
+
renderToStringAsync(App);
|
|
206
|
+
/* @ts-ignore */
|
|
207
|
+
const body = event.router.data;
|
|
208
|
+
if (body) {
|
|
209
|
+
let containsKey = false;
|
|
210
|
+
for (const key in body) {
|
|
211
|
+
if (body[key] === undefined) delete body[key];
|
|
212
|
+
else containsKey = true;
|
|
213
|
+
}
|
|
214
|
+
if (!containsKey) return result;
|
|
215
|
+
if (!(result instanceof Response)) {
|
|
216
|
+
body["_$value"] = result;
|
|
217
|
+
result = new Response(null, { status: 200 });
|
|
218
|
+
}
|
|
219
|
+
result.customBody = () => body;
|
|
220
|
+
result.headers.set("X-Single-Flight", "true");
|
|
221
|
+
return result;
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
189
226
|
export default eventHandler(handleServerFunction);
|
package/config/server-runtime.ts
CHANGED
|
@@ -109,33 +109,32 @@ async function deserializeStream(id, response) {
|
|
|
109
109
|
|
|
110
110
|
let INSTANCE = 0;
|
|
111
111
|
|
|
112
|
-
function createRequest(base: string, id: string, instance: string,
|
|
112
|
+
function createRequest(base: string, id: string, instance: string, options: RequestInit) {
|
|
113
113
|
return fetch(base, {
|
|
114
114
|
method: "POST",
|
|
115
|
+
...options,
|
|
115
116
|
headers: {
|
|
116
|
-
|
|
117
|
-
"
|
|
118
|
-
|
|
119
|
-
}
|
|
120
|
-
body
|
|
117
|
+
...options.headers,
|
|
118
|
+
"X-Server-Id": id,
|
|
119
|
+
"X-Server-Instance": instance
|
|
120
|
+
}
|
|
121
121
|
});
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
-
async function fetchServerFunction(
|
|
124
|
+
async function fetchServerFunction(
|
|
125
|
+
base: string,
|
|
126
|
+
id: string,
|
|
127
|
+
options: Omit<RequestInit, "body">,
|
|
128
|
+
args: any[]
|
|
129
|
+
) {
|
|
125
130
|
const instance = `server-fn:${INSTANCE++}`;
|
|
126
|
-
const response = await (
|
|
127
|
-
?
|
|
128
|
-
headers: {
|
|
129
|
-
"x-server-instance": instance
|
|
130
|
-
}
|
|
131
|
-
})
|
|
131
|
+
const response = await (args.length === 0
|
|
132
|
+
? createRequest(base, id, instance, options)
|
|
132
133
|
: args.length === 1 && args[0] instanceof FormData
|
|
133
|
-
? createRequest(base, id, instance, args[0])
|
|
134
|
-
: createRequest(
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
instance,
|
|
138
|
-
JSON.stringify(
|
|
134
|
+
? createRequest(base, id, instance, { ...options, body: args[0] })
|
|
135
|
+
: createRequest(base, id, instance, {
|
|
136
|
+
...options,
|
|
137
|
+
body: JSON.stringify(
|
|
139
138
|
await Promise.resolve(
|
|
140
139
|
toJSONAsync(args, {
|
|
141
140
|
plugins: [
|
|
@@ -153,17 +152,23 @@ async function fetchServerFunction(base, id, method, args) {
|
|
|
153
152
|
})
|
|
154
153
|
)
|
|
155
154
|
),
|
|
156
|
-
"application/json"
|
|
157
|
-
));
|
|
158
|
-
|
|
159
|
-
if (
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
response.
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
155
|
+
headers: { ...options.headers, "Content-Type": "application/json" }
|
|
156
|
+
}));
|
|
157
|
+
|
|
158
|
+
if (
|
|
159
|
+
response.headers.get("Location") ||
|
|
160
|
+
response.headers.get("X-Revalidate") ||
|
|
161
|
+
response.headers.get("X-Single-Flight")
|
|
162
|
+
) {
|
|
163
|
+
if (response.body) {
|
|
164
|
+
/* @ts-ignore-next-line */
|
|
165
|
+
response.customBody = () => {
|
|
166
|
+
return deserializeStream(instance, response);
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
return response;
|
|
166
170
|
}
|
|
171
|
+
|
|
167
172
|
const contentType = response.headers.get("Content-Type");
|
|
168
173
|
let result;
|
|
169
174
|
if (contentType && contentType.startsWith("text/plain")) {
|
|
@@ -184,14 +189,31 @@ export function createServerReference(fn, id, name) {
|
|
|
184
189
|
return new Proxy(fn, {
|
|
185
190
|
get(target, prop, receiver) {
|
|
186
191
|
if (prop === "url") {
|
|
187
|
-
return `${baseURL}/_server
|
|
192
|
+
return `${baseURL}/_server/?id=${encodeURIComponent(id)}&name=${encodeURIComponent(name)}`;
|
|
188
193
|
}
|
|
189
194
|
if (prop === "GET") {
|
|
190
|
-
return (
|
|
195
|
+
return receiver.withOptions({ method: "GET" });
|
|
196
|
+
}
|
|
197
|
+
if (prop === "withOptions") {
|
|
198
|
+
return (options: RequestInit) => {
|
|
199
|
+
const fn = (...args) => {
|
|
200
|
+
const encodeArgs = options.method && options.method.toUpperCase() === "GET";
|
|
201
|
+
return fetchServerFunction(
|
|
202
|
+
encodeArgs
|
|
203
|
+
? receiver.url + (args.length ? `&args=${JSON.stringify(args)}` : "")
|
|
204
|
+
: `${baseURL}/_server`,
|
|
205
|
+
`${id}#${name}`,
|
|
206
|
+
options,
|
|
207
|
+
encodeArgs ? [] : args
|
|
208
|
+
);
|
|
209
|
+
};
|
|
210
|
+
fn.url = receiver.url;
|
|
211
|
+
return fn;
|
|
212
|
+
};
|
|
191
213
|
}
|
|
192
214
|
},
|
|
193
215
|
apply(target, thisArg, args) {
|
|
194
|
-
return fetchServerFunction(`${baseURL}/_server`, `${id}#${name}`,
|
|
216
|
+
return fetchServerFunction(`${baseURL}/_server`, `${id}#${name}`, {}, args);
|
|
195
217
|
}
|
|
196
218
|
});
|
|
197
219
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solidjs/start",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"author": "Ryan Carniato",
|
|
6
6
|
"publishConfig": {
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
}
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
|
-
"solid-js": "^1.8.
|
|
45
|
+
"solid-js": "^1.8.14",
|
|
46
46
|
"vinxi": "^0.2.1"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
package/server/fetchEvent.ts
CHANGED
|
@@ -74,21 +74,21 @@ class HeaderProxy {
|
|
|
74
74
|
return Array.isArray(cookies) ? cookies : [cookies];
|
|
75
75
|
}
|
|
76
76
|
forEach(fn: (value: string, key: string, object: Headers) => void) {
|
|
77
|
-
return
|
|
77
|
+
return Object.entries<string | string[]>(getResponseHeaders(this.event)).forEach(([key, value]) => fn(Array.isArray(value) ? value.join(", ") : value, key, this));
|
|
78
78
|
}
|
|
79
79
|
entries() {
|
|
80
|
-
return getResponseHeaders(this.event).map(([key, value]) => [
|
|
80
|
+
return Object.entries<string | string[]>(getResponseHeaders(this.event)).map(([key, value]) => [
|
|
81
81
|
key,
|
|
82
82
|
Array.isArray(value) ? value.join(", ") : value
|
|
83
|
-
]);
|
|
83
|
+
] as [string, string])[Symbol.iterator]();
|
|
84
84
|
}
|
|
85
85
|
keys() {
|
|
86
|
-
return getResponseHeaders(this.event).
|
|
86
|
+
return Object.keys(getResponseHeaders(this.event))[Symbol.iterator]();
|
|
87
87
|
}
|
|
88
88
|
values() {
|
|
89
|
-
return getResponseHeaders(this.event).map((
|
|
89
|
+
return Object.values<string | string[]>(getResponseHeaders(this.event)).map((value) =>
|
|
90
90
|
Array.isArray(value) ? value.join(", ") : value
|
|
91
|
-
);
|
|
91
|
+
)[Symbol.iterator]();
|
|
92
92
|
}
|
|
93
93
|
[Symbol.iterator]() {
|
|
94
94
|
return this.entries()[Symbol.iterator]();
|
package/shared/ErrorBoundary.tsx
CHANGED
|
@@ -1,24 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ErrorBoundary as DefaultErrorBoundary,
|
|
3
|
-
ParentProps,
|
|
4
|
-
} from "solid-js";
|
|
1
|
+
import { ErrorBoundary as DefaultErrorBoundary, type ParentProps } from "solid-js";
|
|
5
2
|
import { HttpStatusCode } from "./HttpStatusCode";
|
|
6
3
|
import { DevOverlay } from "./dev-overlay";
|
|
7
4
|
|
|
8
5
|
export function ErrorBoundary(props: ParentProps) {
|
|
9
6
|
if (import.meta.env.DEV) {
|
|
10
|
-
return
|
|
11
|
-
<DevOverlay>
|
|
12
|
-
{props.children}
|
|
13
|
-
</DevOverlay>
|
|
14
|
-
);
|
|
7
|
+
return <DevOverlay>{props.children}</DevOverlay>;
|
|
15
8
|
}
|
|
16
9
|
return (
|
|
17
|
-
<DefaultErrorBoundary
|
|
18
|
-
fallback={(
|
|
19
|
-
<HttpStatusCode code={500} />
|
|
20
|
-
)}
|
|
21
|
-
>
|
|
10
|
+
<DefaultErrorBoundary fallback={<HttpStatusCode code={500} />}>
|
|
22
11
|
{props.children}
|
|
23
12
|
</DefaultErrorBoundary>
|
|
24
13
|
);
|