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

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,18 @@
1
1
  # @solidjs/start
2
2
 
3
+ ## 2.0.0-rc.6
4
+
5
+ ### Minor Changes
6
+
7
+ - 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
8
+
9
+ ### Patch Changes
10
+
11
+ - 27c2877: Remove leftover debug `console.log` calls from the server functions inspector, which logged on every server function request in dev.
12
+ - 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.
13
+ - 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.
14
+ - 83122ed: Reject server function calls when the response is a 5xx without an X-Error header, instead of resolving with the parsed error body
15
+
3
16
  ## 2.0.0-rc.5
4
17
 
5
18
  ### 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",
@@ -118,6 +118,23 @@ export interface SolidStartOptions {
118
118
  * Options controlling which files are processed as server functions
119
119
  * (inclusion / exclusion filters for the `"use server"` transform).
120
120
  */
121
- serverFunctions?: Pick<ServerFunctionsOptions, "filter">;
121
+ serverFunctions?: Pick<ServerFunctionsOptions, "filter"> & {
122
+ /**
123
+ * Path to a module whose default export is called with whatever a server
124
+ * function threw, before it is serialized into the response. Return a
125
+ * value to send it in place of what was thrown, or `undefined` to send the
126
+ * original.
127
+ *
128
+ * Naming the module here rather than registering a handler at runtime
129
+ * keeps the app in sole control of it: no dependency can reach into the
130
+ * running server and take over reporting.
131
+ *
132
+ * The module is bundled into the server only, so it may import server-only
133
+ * code such as a monitoring SDK.
134
+ *
135
+ * @example "src/server-fn-error.ts"
136
+ */
137
+ onError?: string;
138
+ };
122
139
  }
123
140
  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,3 @@
1
+ export type ServerFunctionErrorHandler = (thrown: unknown) => unknown;
2
+ /** @internal */
3
+ export declare function applyServerFunctionErrorHandler(thrown: unknown): unknown;
@@ -0,0 +1,5 @@
1
+ import onServerFunctionError from "solid-start:server-fn-error-handler";
2
+ /** @internal */
3
+ export function applyServerFunctionErrorHandler(thrown) {
4
+ return onServerFunctionError?.(thrown) ?? thrown;
5
+ }
@@ -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.6",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/solidjs/solid-start.git",
@@ -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
  },