Skip to content

v0.5.0 - Async Local Storage ...Everywhere

Compare
Choose a tag to compare
@ryansolid ryansolid released this 02 Feb 22:54
· 195 commits to main since this release

This release is a bunch of organizational shifts from our learnings after running Solid Start Beta 2 for the first month (see: #1279). These are all minor changes but we hope they will smooth out the dev experience significantly. This release is largely possible due to the heroic efforts of @nksaraf and the multitudes of updates/fixes @lxsmnsyc has been making behind the scenes.

RequestEvent improvements

Main update is we've changed how we look at the main event object. Before we were mashing everything on top of H3 which wasn't always the best. We couldn't just adopt H3's Event because we have conventions in the Solid ecosystem and it was a bit awkward trying to figure out how to best interact with both systems in place.

Main change here is that instead of merging, the underlying H3Event is now available at event.nativeEvent. But having to pump that through helpers all the time would be a bit clunky so we have a couple new mechanism to make it easier to use.

Stubbing out the Response

If you are doing simple operations like updating the headers or status of the response you can do that now directly off our event the same way you can with request:

import { getRequestEvent } from "solid-js/web";

const event = getRequestEvent();
console.log(event.request.url);

event.response.headers.set("x-foo", "bar");
console.log(event.response.status);
event.response.status = 201;

This response object proxies the H3 methods which has the nice benefit of being able to use it easier in isomorphic settings without worrying about imports and bundling. Components like <HttpStatus> and <HttpHeader> are built using this primitive.

Async Local Storage Everywhere

HTTP helpers are now available at vinxi/http. Previously we were re-exporting these but we don't want to own this piece. Currently there is a light wrapper around H3 done here. But it means that the provided helpers are usable now directly without passing the H3Event in.

import { useSession } from "vinxi/http";

async function someServerFunction() {
  "use server";
  const session = await useSession({ password: process.env.SESSION_SECRET});
  doSomethingWith(session.data.userId);
}

You can still pass in the H3Event if you want as the first argument, and these Vinxi wrappers also support passing in Solid's RequestEvent as well but between these 2 APIs you probably won't be interfacing with the H3Event much directly in application code.

Typing Locals

SolidStart uses event.locals to pass around local context to be used as you see fit. Before this was just a record but now you can add specific types to it as well:

declare module "@solidjs/start/server" {
  interface RequestEventLocals {
    myNumber: number;
    someString: string;
  }
}

Server Function Improvements

HTTP-Cacheable Server Functions

Technically this came out in 0.4.11 but will announce it here. Solid Start now supports GET server functions. This is important because it means they can be cached with HTTP cache. And that the arguments to the function will be serialized into the URL.

// example with streaming promise and a 60 second cache life

import { json } from "@solidjs/router";
import { GET } from "@solidjs/start";

const hello = GET(async (name: string) => {
  "use server";
  return json(
    { hello: new Promise<string>(r => setTimeout(() => r(name), 1000)) },
    { headers: { "cache-control": "max-age=60" } }
  );
});

Note that cache from @solidjs/router will automatically opt into GET.

Return Responses as well as throw them

There were several comments about ergonomic issues of only supporting throwing of Response objects in server functions. Mostly it was about TypeScript. However, now that we need to support json helper for adding headers for GET instead of the typical redirect or reload cases of Actions we also support returning Responses.

@solidjs/router's redirect, reload, and json helpers will all return responses but from a type perspective will not impact your server function. For instance json above will see that hello function returns a Promise<{ hello: Promise<string> }>. And redirect and reload return never meaning getUser below can only ever return Promise<User>.

export async function getUser() {
  const session = await getSession();
  const userId = session.data.userId;
  if (userId === undefined) return redirect("/login");

  try {
    const user: User = await db.user.findUnique({ where: { id: userId } });
    
    // throwing here could have been a bit awkward.
    if (!user) return redirect("/login");
    return user;
  } catch {
    // do stuff
    throw redirect("/login");
  }
}

API Routes Refined

A decent amount of feedback in Beta 2 has been around the FS routing system with API routes. One of the consequences of being Router agnostic is that the API routes needed to be processed separately. In our case it means API routes are always matched before Page routes. With the desire to be able to break them out in the future to be deployable in different places this decision makes a lot of sense. But we had a lot of duplication between router logic and API routes, that didn't really optimize for them.

In 0.5.0 API route logic has been pulled out and they are now being matched and processed by unjs radix3. While ultimately we decided to still have them in the same folder structure for maximum flexibility and visibility, we'd recommend keeping them separate on paths to make it more clear. While you can technically have a GET method and a default export for a Page component at the same URL if you really want to, you would have to use Accept headers to early return from the API route without a value. And know it will go through the API route first and then Page route.

Other updates

Moved createHandler back into @solidjs/start/server and middleware to @solidjs/start/middleware

While it was only a recent change to move createHandler out it was not the right decision. Circular bundling issues are better resolved by moving the server utilities to a different package (vinxi/http) and having middleware in its own entry. So you entry-server.tsx returns to:

import { createHandler, StartServer } from "@solidjs/start/server";

export default createHandler(() => (
  <StartServer
    document={({ assets, children, scripts }) => (
      <html lang="en">
        <head>
          <meta charset="utf-8" />
          <meta name="viewport" content="width=device-width, initial-scale=1" />
          <link rel="icon" href="/favicon.ico" />
          {assets}
        </head>
        <body>
          <div id="app">{children}</div>
          {scripts}
        </body>
      </html>
    )}
  />
));

And your middleware.ts might look like:

import { createMiddleware } from "@solidjs/start/middleware";

export default createMiddleware({
  onRequest: [
    event => {
      console.log("REQUEST", event.request.url);
    }
  ]
});

Replacing Solid Start's CLI

create-solid in this repo hasn't built properly for almost 8 months now and was slowly falling apart. I was manually editing the bin file on my one computer with a working build. I have removed it from the repo. You can continue to use the currnet version until the new version is published under the same npm init solid command. We will be replacing it with https://github.com/solidjs-community/solid-cli built by our community in Rust. It's faster, better organized, and more featured thanks to the amazing work of @Tommypop2 and @dev-rb. So it will be where CLI work will continue to improve instead of in this repo.

Support for Solid's renderToString

Since the release of Beta 2 we have been focusing on streaming as the default. But you could always opt into wait for all data loading with the ssr: "async". With 0.5.x we have also added back support for ssr: "sync" which uses renderToString. This mode synchronously renders on the server until it hits a Suspense boundary and then fetches and renders the rest in the browser. While generally less performant than streaming, it may be a good option for those just moving to SSR.

Continuing towards 1.0

We think these quality of life improvements will made development a lot more streamlined and help you all better build the applications you are looking to make. I'm excited to work through the next couple features as approach RC of 1.0.

@ryansolid