@solidjs/start 2.0.0-rc.5 → 2.0.0-rc.7

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/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # @solidjs/start
2
2
 
3
+ ## 2.0.0-rc.7
4
+
5
+ ### Patch Changes
6
+
7
+ - 87d73e1: chore: bump seroval to v1.6.0
8
+
9
+ ## 2.0.0-rc.6
10
+
11
+ ### Minor Changes
12
+
13
+ - bac24b2: Add a `serverFunctions.onError` option naming a module that observes and replaces what a server function threw, before it is serialized into the response
14
+
15
+ ### Patch Changes
16
+
17
+ - 27c2877: Remove leftover debug `console.log` calls from the server functions inspector, which logged on every server function request in dev.
18
+ - f15724b: Declare `@solidjs/router` as an optional peer dependency constrained to `>=0.16.0 <2.0.0-0`. Router v2 is expected to target Solid v2, so installing it alongside `@solidjs/start` v2 now surfaces a peer warning instead of silently producing an incompatible pairing. The peer is marked optional, so apps that do not use the router are unaffected.
19
+ - 5c8612f: Apply cookies set on a returned or thrown response during single flight mutations. `redirect(to, { headers: { "Set-Cookie": ... } })` previously only reached the browser: the single flight re-render of the redirect target still ran with the old request cookies, so queries reading that cookie saw stale values. Those cookies are now merged into the request the re-render sees, matching what a browser round trip would have sent.
20
+ - 83122ed: Reject server function calls when the response is a 5xx without an X-Error header, instead of resolving with the parsed error body
21
+
3
22
  ## 2.0.0-rc.5
4
23
 
5
24
  ### Minor Changes
@@ -7,6 +7,7 @@ export declare const VIRTUAL_MODULES: {
7
7
  readonly middleware: "solid-start:middleware";
8
8
  readonly serovalPlugins: "solid-start:seroval-plugins";
9
9
  readonly serverFnManifest: "solid-start:server-fn-manifest";
10
+ readonly serverFnErrorHandler: "solid-start:server-fn-error-handler";
10
11
  readonly clientEntry: "solid-start:client-entry";
11
12
  readonly serverEntry: "solid-start:server-entry";
12
13
  readonly app: "solid-start:app";
@@ -7,6 +7,7 @@ export const VIRTUAL_MODULES = {
7
7
  middleware: "solid-start:middleware",
8
8
  serovalPlugins: "solid-start:seroval-plugins",
9
9
  serverFnManifest: "solid-start:server-fn-manifest",
10
+ serverFnErrorHandler: "solid-start:server-fn-error-handler",
10
11
  clientEntry: "solid-start:client-entry",
11
12
  serverEntry: "solid-start:server-entry",
12
13
  app: "solid-start:app",
@@ -115,9 +115,49 @@ export interface SolidStartOptions {
115
115
  */
116
116
  env?: EnvPluginOptions;
117
117
  /**
118
- * Options controlling which files are processed as server functions
119
- * (inclusion / exclusion filters for the `"use server"` transform).
118
+ * Options for server functions: which files the `"use server"` transform
119
+ * processes (inclusion / exclusion filters), and what happens when a server
120
+ * function throws.
120
121
  */
121
- serverFunctions?: Pick<ServerFunctionsOptions, "filter">;
122
+ serverFunctions?: Pick<ServerFunctionsOptions, "filter"> & {
123
+ /**
124
+ * Path to a module whose default export handles whatever a server function
125
+ * throws, before it reaches the client. Use it to report failures to a
126
+ * monitoring service, or to replace an error carrying internal detail with
127
+ * one that is safe to send.
128
+ *
129
+ * Only server function calls made over the network run through it. A
130
+ * server function called during rendering runs in process and throws
131
+ * straight to its caller, and errors from API routes never reach it
132
+ * either.
133
+ *
134
+ * The export is called synchronously with the thrown value, and what it
135
+ * returns decides what the client sees:
136
+ *
137
+ * - `undefined` (or `null`) sends what was thrown, unchanged.
138
+ * - A `Response` is passed through unchanged, which keeps a thrown
139
+ * `redirect()` working.
140
+ * - Any other value is sent in place of what was thrown, and the client
141
+ * call rejects with it.
142
+ *
143
+ * Control flow reaches the export the same way errors do, so a handler
144
+ * that replaces everything it sees turns redirects into errors.
145
+ *
146
+ * Whatever is sent gets serialized to the client along with its own
147
+ * properties, so an error meant to be safe to expose must not carry
148
+ * internal detail.
149
+ *
150
+ * The return value is not awaited, so make the export a plain function
151
+ * rather than an `async` one, and report failures with calls that do not
152
+ * need awaiting.
153
+ *
154
+ * Type the export as `ServerFunctionErrorHandler` from
155
+ * `@solidjs/start/server`. The module is bundled into the server only, so
156
+ * it may import server-only code such as a monitoring SDK.
157
+ *
158
+ * @example "src/server-fn-error.ts"
159
+ */
160
+ onError?: string;
161
+ };
122
162
  }
123
163
  export declare function solidStart(options?: SolidStartOptions): Array<PluginOption>;
@@ -34,6 +34,12 @@ export function manifest(start) {
34
34
  return await this.resolve(plugins);
35
35
  return `\0${VIRTUAL_MODULES.serovalPlugins}`;
36
36
  }
37
+ if (id === VIRTUAL_MODULES.serverFnErrorHandler) {
38
+ const onError = start.serverFunctions?.onError;
39
+ if (onError)
40
+ return await this.resolve(onError);
41
+ return `\0${VIRTUAL_MODULES.serverFnErrorHandler}`;
42
+ }
37
43
  },
38
44
  async load(id) {
39
45
  if (id === `\0${VIRTUAL_MODULES.clientViteManifest}`) {
@@ -84,6 +90,8 @@ export function manifest(start) {
84
90
  return "export default {};";
85
91
  else if (id === `\0${VIRTUAL_MODULES.serovalPlugins}`)
86
92
  return "export default [];";
93
+ else if (id === `\0${VIRTUAL_MODULES.serverFnErrorHandler}`)
94
+ return "export default undefined;";
87
95
  else if (id.startsWith("/@manifest")) {
88
96
  if (this.environment.mode !== "dev")
89
97
  throw new Error("@manifest queries are only allowed in dev");
@@ -15,7 +15,6 @@ async function createRequest(base, id, instance, options) {
15
15
  },
16
16
  });
17
17
  if (import.meta.env.DEV) {
18
- console.log(pushRequest);
19
18
  pushRequest(id, instance, request.clone());
20
19
  }
21
20
  const response = await fetch(request);
@@ -73,8 +72,8 @@ async function fetchServerFunction(base, id, options, args) {
73
72
  return response;
74
73
  }
75
74
  const result = await extractBody(instance, true, response.clone());
76
- if (response.headers.has("X-Error")) {
77
- throw result;
75
+ if (response.headers.has("X-Error") || response.status >= 500) {
76
+ throw result ?? new Error(`Server function call failed with status ${response.status}`);
78
77
  }
79
78
  return result;
80
79
  }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Handles whatever a server function threw, before it reaches the client. Use
3
+ * it to type the default export of the module named by the
4
+ * `serverFunctions.onError` option in `vite.config.ts`.
5
+ *
6
+ * Called synchronously, and the value it returns is not awaited, so it cannot
7
+ * be an `async` function.
8
+ *
9
+ * @param thrown The value the server function threw. This is a `Response` when
10
+ * the server function threw control flow such as a `redirect()`.
11
+ * @returns `undefined` (or `null`) to send `thrown` unchanged, a `Response` to
12
+ * pass control flow through untouched, or any other value to send in place of
13
+ * `thrown`.
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * // src/server-fn-error.ts
18
+ * import type { ServerFunctionErrorHandler } from "@solidjs/start/server";
19
+ * import { captureException } from "./your-monitoring-client";
20
+ *
21
+ * const onServerFunctionError: ServerFunctionErrorHandler = thrown => {
22
+ * // redirect() throws a Response, so returning it preserves that control flow.
23
+ * if (thrown instanceof Response) return thrown;
24
+ *
25
+ * captureException(thrown); // or console.error, or any reporter
26
+ * return new Error("Something went wrong");
27
+ * };
28
+ *
29
+ * export default onServerFunctionError;
30
+ * ```
31
+ */
32
+ export type ServerFunctionErrorHandler = (thrown: unknown) => unknown;
33
+ export declare function applyServerFunctionErrorHandler(thrown: unknown): unknown;
@@ -0,0 +1,4 @@
1
+ import onServerFunctionError from "solid-start:server-fn-error-handler";
2
+ export function applyServerFunctionErrorHandler(thrown) {
3
+ return onServerFunctionError?.(thrown) ?? thrown;
4
+ }
@@ -2,4 +2,4 @@ import { type H3Event } from "h3";
2
2
  import "solid-start:server-fn-manifest";
3
3
  import type { FetchEvent } from "../server/types.ts";
4
4
  export declare function handleServerFunction(h3Event: H3Event): Promise<unknown>;
5
- export declare function createSingleFlightHeaders(sourceEvent: FetchEvent): Headers;
5
+ export declare function createSingleFlightHeaders(sourceEvent: FetchEvent, result?: unknown): Headers;
@@ -17,6 +17,7 @@ import { deserializeFromJSONString, serializeToJSONStream, serializeToJSStream,
17
17
  import { BODY_FORMAT_KEY, BodyFormat, extractBody, getHeadersAndBody } from "./shared.js";
18
18
  import "solid-start:server-fn-manifest";
19
19
  import { getServerFunction, hasServerFunction } from "./registration.js";
20
+ import { applyServerFunctionErrorHandler } from "./error-handler.js";
20
21
  import { getExpectedRedirectStatus } from "../server/util.js";
21
22
  export async function handleServerFunction(h3Event) {
22
23
  const event = getFetchEvent(h3Event);
@@ -124,6 +125,7 @@ export async function handleServerFunction(h3Event) {
124
125
  return serializeToJSONStream(result);
125
126
  }
126
127
  catch (x) {
128
+ x = applyServerFunctionErrorHandler(x);
127
129
  if (x instanceof Response) {
128
130
  if (singleFlight && instance) {
129
131
  x = await handleSingleFlight(event, x);
@@ -236,13 +238,19 @@ async function handleNoJS(result, request, parsed, thrown) {
236
238
  });
237
239
  }
238
240
  let App;
239
- export function createSingleFlightHeaders(sourceEvent) {
241
+ export function createSingleFlightHeaders(sourceEvent, result) {
240
242
  // cookie handling logic is pretty simplistic so this might be imperfect
241
243
  // unclear if h3 internals are available on all platforms but we need a way to
242
244
  // update request headers on the underlying H3 event.
243
245
  const headers = new Headers(sourceEvent.request.headers);
244
246
  const cookies = parseCookies(sourceEvent.nativeEvent);
245
247
  const SetCookies = sourceEvent.response.headers.getSetCookie();
248
+ // cookies attached to the returned/thrown response (eg. `redirect(to, { headers })`)
249
+ // haven't been merged onto the event response yet, but a browser round trip would
250
+ // have sent them back with the next request, so apply them here too. They come after
251
+ // the ones on the event response, so they win on conflict.
252
+ if (result instanceof Response)
253
+ SetCookies.push(...result.headers.getSetCookie());
246
254
  headers.delete("cookie");
247
255
  // let useH3Internals = false;
248
256
  // if (sourceEvent.nativeEvent.node?.req) {
@@ -286,7 +294,7 @@ async function handleSingleFlight(sourceEvent, result) {
286
294
  }
287
295
  const event = { ...sourceEvent };
288
296
  event.request = new Request(url, {
289
- headers: createSingleFlightHeaders(sourceEvent),
297
+ headers: createSingleFlightHeaders(sourceEvent, result),
290
298
  });
291
299
  return await provideRequestEvent(event, async () => {
292
300
  await createPageEvent(event);
@@ -3,6 +3,7 @@ export { getServerFunctionMeta } from "../shared/serverFunction.ts";
3
3
  export { StartServer } from "./StartServer.tsx";
4
4
  export { decorateHandler, decorateMiddleware } from "./fetchEvent.ts";
5
5
  export { createHandler } from "./handler.ts";
6
+ export type { ServerFunctionErrorHandler } from "../fns/error-handler.ts";
6
7
  export type { APIEvent, APIHandler, Asset, ContextMatches, DocumentComponentProps, FetchEvent, HandlerOptions, PageEvent, ResponseStub, ServerFunctionMeta, StartHandler, } from "./types.ts";
7
8
  /**
8
9
  * Checks if user has set a redirect status in the response.
@@ -121,7 +121,6 @@ export function DevToolbar(props) {
121
121
  });
122
122
  createEffect(() => {
123
123
  onCleanup(captureServerFunctionCall(call => {
124
- console.log(call);
125
124
  if (call.type === "request") {
126
125
  setStore("instances", call.instance, value => {
127
126
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solidjs/start",
3
- "version": "2.0.0-rc.5",
3
+ "version": "2.0.0-rc.7",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/solidjs/solid-start.git",
@@ -48,8 +48,8 @@
48
48
  "path-to-regexp": "^8.4.2",
49
49
  "pathe": "^2.0.3",
50
50
  "radix3": "^1.1.2",
51
- "seroval": "^1.5.6",
52
- "seroval-plugins": "^1.5.6",
51
+ "seroval": "^1.6.0",
52
+ "seroval-plugins": "^1.6.0",
53
53
  "shiki": "^4.3.1",
54
54
  "solid-js": "^1.9.14",
55
55
  "source-map-js": "^1.2.1",
@@ -64,8 +64,14 @@
64
64
  "vitest": "^4.1.10"
65
65
  },
66
66
  "peerDependencies": {
67
+ "@solidjs/router": ">=0.16.0 <2.0.0-0",
67
68
  "vite": "^8 || ^9"
68
69
  },
70
+ "peerDependenciesMeta": {
71
+ "@solidjs/router": {
72
+ "optional": true
73
+ }
74
+ },
69
75
  "engines": {
70
76
  "node": ">=24"
71
77
  },