@solidjs/start 0.5.1 → 0.5.3

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 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
- // routes: solidStartServerFsRouter({ dir: `${start.appRoot}/routes`, extensions }),
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?id=${encodeURIComponent(id)}&name=${encodeURIComponent(name)}`;
11
+ return `${baseURL}/_server/?id=${encodeURIComponent(id)}&name=${encodeURIComponent(name)}`;
12
12
  }
13
13
  if (prop === "GET") return receiver;
14
14
  },
@@ -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("x-server-id");
66
- const instance = request.headers.get("x-server-instance");
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 action = (
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(await new Request(request, { ...request, body: h3Event.node.req.body }).formData());
97
- // what should work when #1721 is fixed
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 action(...parsed);
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 = undefined;
147
+ } else if (result.body == undefined) result = null;
143
148
  }
144
149
 
145
150
  // handle no JS success case
@@ -163,27 +168,63 @@ async function handleServerFunction(h3Event) {
163
168
  }
164
169
  });
165
170
  }
166
- if (typeof result === "string") return new Response(result);
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 = undefined;
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"))
194
+ url = new URL(
195
+ result.headers.get("Location"),
196
+ new URL(sourceEvent.request.url).origin + import.meta.env.SERVER_BASE_URL
197
+ ).toString();
198
+ }
199
+ const event = cloneEvent(sourceEvent) as PageEvent;
200
+ event.request = new Request(url);
201
+ return await provideRequestEvent(event, async () => {
202
+ await createPageEvent(event);
203
+ /* @ts-ignore */
204
+ sharedConfig.context = { event };
205
+ /* @ts-ignore */
206
+ event.router.dataOnly = revalidate || true;
207
+ /* @ts-ignore */
208
+ event.router.previousUrl = sourceEvent.request.headers.get("referer");
209
+ renderToStringAsync(App);
210
+ /* @ts-ignore */
211
+ const body = event.router.data;
212
+ if (body) {
213
+ let containsKey = false;
214
+ for (const key in body) {
215
+ if (body[key] === undefined) delete body[key];
216
+ else containsKey = true;
217
+ }
218
+ if (!containsKey) return result;
219
+ if (!(result instanceof Response)) {
220
+ body["_$value"] = result;
221
+ result = new Response(null, { status: 200 });
222
+ }
223
+ result.customBody = () => body;
224
+ result.headers.set("X-Single-Flight", "true");
225
+ return result;
226
+ }
227
+ });
228
+ }
229
+
189
230
  export default eventHandler(handleServerFunction);
@@ -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, body: any, contentType?: 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
- "x-server-id": id,
117
- "x-server-instance": instance,
118
- ...(contentType ? { "Content-Type": contentType } : {})
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(base, id, method, args) {
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 (method === "GET"
127
- ? fetch(base + (args.length ? `&args=${JSON.stringify(args)}` : ""), {
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
- base,
136
- id,
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 (response.headers.get("Location")) throw response;
160
- if (response.headers.get("X-Revalidate")) {
161
- /* @ts-ignore-next-line */
162
- response.customBody = () => {
163
- return deserializeStream(instance, response);
164
- };
165
- throw response;
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?id=${encodeURIComponent(id)}&name=${encodeURIComponent(name)}`;
192
+ return `${baseURL}/_server/?id=${encodeURIComponent(id)}&name=${encodeURIComponent(name)}`;
188
193
  }
189
194
  if (prop === "GET") {
190
- return (...args) => fetchServerFunction(`${baseURL}/_server/?id=${encodeURIComponent(id)}&name=${encodeURIComponent(name)}`, `${id}#${name}`, "GET", args);
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}`, "POST", args);
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.1",
3
+ "version": "0.5.3",
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.13",
45
+ "solid-js": "^1.8.14",
46
46
  "vinxi": "^0.2.1"
47
47
  },
48
48
  "dependencies": {
@@ -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 this.entries().forEach(([key, value]) => fn(value, key, this));
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).map(([key]) => key);
86
+ return Object.keys(getResponseHeaders(this.event))[Symbol.iterator]();
87
87
  }
88
88
  values() {
89
- return getResponseHeaders(this.event).map(([key, value]) =>
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]();
@@ -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
  );