Skip to content

Commit

Permalink
initial support for single flight mutations
Browse files Browse the repository at this point in the history
  • Loading branch information
ryansolid committed Feb 7, 2024
1 parent ae6dc62 commit 20697ed
Show file tree
Hide file tree
Showing 21 changed files with 179 additions and 110 deletions.
5 changes: 5 additions & 0 deletions .changeset/modern-gorillas-exercise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@solidjs/start": patch
---

initial support for single flight mutations
2 changes: 1 addition & 1 deletion examples/basic/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
},
"dependencies": {
"@solidjs/meta": "^0.29.2",
"@solidjs/router": "^0.11.2",
"@solidjs/router": "^0.11.4",
"@solidjs/start": "^0.5.1",
"solid-js": "^1.8.14",
"vinxi": "^0.2.1"
Expand Down
2 changes: 1 addition & 1 deletion examples/experiments/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
},
"dependencies": {
"@solidjs/meta": "^0.29.2",
"@solidjs/router": "^0.11.2",
"@solidjs/router": "^0.11.4",
"@solidjs/start": "^0.5.1",
"solid-js": "^1.8.14",
"vinxi": "^0.2.1"
Expand Down
2 changes: 1 addition & 1 deletion examples/hackernews/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"start": "vinxi start"
},
"dependencies": {
"@solidjs/router": "^0.11.2",
"@solidjs/router": "^0.11.4",
"@solidjs/start": "^0.5.1",
"solid-js": "^1.8.14",
"vinxi": "^0.2.1"
Expand Down
2 changes: 1 addition & 1 deletion examples/todomvc/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"start": "vinxi start"
},
"dependencies": {
"@solidjs/router": "^0.11.2",
"@solidjs/router": "^0.11.4",
"@solidjs/start": "^0.5.1",
"solid-js": "^1.8.14",
"unstorage": "1.10.1",
Expand Down
2 changes: 1 addition & 1 deletion examples/todomvc/src/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ body {
}

.todo-list li.pending label {
color: #998ef1;
color: #c23bc9;
}

.todo-list li .destroy {
Expand Down
8 changes: 7 additions & 1 deletion examples/todomvc/src/routes/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ const clearCompleted = action(clearCompletedFn, "clearCompleted");
const editTodo = action(editTodoFn, "editTodo");
const toggleTodo = action(toggleTodoFn, "toggleTodo");

export const route = {
load() {
getTodos();
}
}

export default function TodoApp(props: RouteSectionProps) {
const todos = createAsync(getTodos, { initialValue: [], deferStream: true });
const location = props.location;
Expand Down Expand Up @@ -103,7 +109,7 @@ export default function TodoApp(props: RouteSectionProps) {
togglingAll.pending
? !togglingAll.input[0]
: togglingTodo.pending
? !togglingTodo.input[0]
? !todo.completed
: todo.completed;
const removing = () => removingTodo.some(data => data.input[0] === todo.id);
return (
Expand Down
2 changes: 1 addition & 1 deletion examples/with-auth/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"@types/node": "^20.10.1"
},
"dependencies": {
"@solidjs/router": "^0.11.2",
"@solidjs/router": "^0.11.4",
"@solidjs/start": "^0.5.1",
"solid-js": "^1.8.14",
"unstorage": "1.10.1",
Expand Down
2 changes: 1 addition & 1 deletion examples/with-mdx/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
},
"dependencies": {
"@mdx-js/mdx": "^2.3.0",
"@solidjs/router": "^0.11.2",
"@solidjs/router": "^0.11.4",
"@solidjs/start": "^0.5.1",
"@vinxi/plugin-mdx": "^3.6.7",
"solid-js": "^1.8.14",
Expand Down
2 changes: 1 addition & 1 deletion examples/with-prisma/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
},
"dependencies": {
"@prisma/client": "^5.7.0",
"@solidjs/router": "^0.11.2",
"@solidjs/router": "^0.11.4",
"@solidjs/start": "^0.5.1",
"prisma": "^5.7.0",
"solid-js": "^1.8.14",
Expand Down
2 changes: 1 addition & 1 deletion examples/with-solid-styled/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
},
"dependencies": {
"@solidjs/meta": "^0.29.2",
"@solidjs/router": "^0.11.2",
"@solidjs/router": "^0.11.4",
"@solidjs/start": "^0.5.1",
"solid-js": "^1.8.14",
"solid-styled": "^0.8.2",
Expand Down
2 changes: 1 addition & 1 deletion examples/with-tailwindcss/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"start": "vinxi start"
},
"dependencies": {
"@solidjs/router": "^0.11.2",
"@solidjs/router": "^0.11.4",
"@solidjs/start": "^0.5.1",
"autoprefixer": "^10.4.14",
"postcss": "^8.4.26",
Expand Down
2 changes: 1 addition & 1 deletion examples/with-trpc/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"@trpc/client": "^10.44.1",
"@trpc/server": "^10.44.1",
"@solidjs/meta": "^0.29.2",
"@solidjs/router": "^0.11.2",
"@solidjs/router": "^0.11.4",
"@solidjs/start": "^0.5.1",
"solid-js": "^1.8.14",
"valibot": "^0.23.0",
Expand Down
2 changes: 1 addition & 1 deletion examples/with-unocss/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"start": "vinxi start"
},
"dependencies": {
"@solidjs/router": "^0.11.2",
"@solidjs/router": "^0.11.4",
"@solidjs/start": "^0.5.1",
"@unocss/reset": "^0.58.3",
"solid-js": "^1.8.14",
Expand Down
2 changes: 1 addition & 1 deletion examples/with-vitest/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"type": "module",
"devDependencies": {
"@solidjs/meta": "^0.29.3",
"@solidjs/router": "^0.11.2",
"@solidjs/router": "^0.11.4",
"@solidjs/start": "^0.5.1",
"@solidjs/testing-library": "^0.8.5",
"@testing-library/jest-dom": "^6.1.5",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"@cloudflare/kv-asset-handler": "^0.2.0",
"@decs/typeschema": "^0.12.1",
"@solidjs/meta": "^0.29.0",
"@solidjs/router": "^0.11.2",
"@solidjs/router": "^0.11.4",
"@solidjs/start": "workspace:*",
"@tailwindcss/typography": "^0.5.9",
"@trpc/client": "^9.27.4",
Expand Down
2 changes: 1 addition & 1 deletion packages/start/config/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ export function defineConfig(baseConfig = {}) {
serverFunctions.router({
handler: normalize(fileURLToPath(new URL("./server-handler.ts", import.meta.url))),
runtime: normalize(fileURLToPath(new URL("./server-fns-runtime.ts", import.meta.url))),
// routes: solidStartServerFsRouter({ dir: `${start.appRoot}/routes`, extensions }),
routes: solidStartServerFsRouter({ dir: `${start.appRoot}/routes`, extensions }),
plugins: async () => [
config("user", {
...userConfig,
Expand Down
2 changes: 1 addition & 1 deletion packages/start/config/server-fns-runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export function createServerReference(fn, id, name) {
return new Proxy(fn, {
get(target, prop, receiver) {
if (prop === "url") {
return `${baseURL}/_server?id=${encodeURIComponent(id)}&name=${encodeURIComponent(name)}`;
return `${baseURL}/_server/?id=${encodeURIComponent(id)}&name=${encodeURIComponent(name)}`;
}
if (prop === "GET") return receiver;
},
Expand Down
96 changes: 66 additions & 30 deletions packages/start/config/server-handler.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/// <reference types="vinxi/types/server" />
import { crossSerializeStream, fromJSON, getCrossReferenceHeader } from "seroval";
// @ts-ignore
import {
CustomEventPlugin,
DOMExceptionPlugin,
Expand All @@ -13,10 +14,15 @@ import {
URLSearchParamsPlugin
} from "seroval-plugins/web";
import { sharedConfig } from "solid-js";
import { renderToStringAsync } from "solid-js/web";
import { provideRequestEvent } from "solid-js/web/storage";
import { eventHandler, setHeader } from "vinxi/http";
import { eventHandler, setHeader, setResponseStatus } from "vinxi/http";
import invariant from "vinxi/lib/invariant";
import { getFetchEvent, mergeResponseHeaders } from "../server/fetchEvent";
import { cloneEvent, getFetchEvent, mergeResponseHeaders } from "../server/fetchEvent";
import { createPageEvent } from "../server/pageEvent";
// @ts-ignore
import App from "#start/app";
import { FetchEvent, PageEvent } from "../server";

function createChunk(data) {
const bytes = data.length;
Expand Down Expand Up @@ -62,8 +68,9 @@ async function handleServerFunction(h3Event) {
const event = getFetchEvent(h3Event);
const request = event.request;

const serverReference = request.headers.get("x-server-id");
const instance = request.headers.get("x-server-instance");
const serverReference = request.headers.get("X-Server-Id");
const instance = request.headers.get("X-Server-Instance");
const singleFlight = request.headers.has("X-Single-Flight");
const url = new URL(request.url);
let filepath, name;
if (serverReference) {
Expand All @@ -75,7 +82,7 @@ async function handleServerFunction(h3Event) {
if (!filepath || !name) throw new Error("Invalid request");
}

const action = (
const serverFunction = (
await import.meta.env.MANIFEST[import.meta.env.ROUTER_NAME].chunks[filepath].import()
)[name];
let parsed = [];
Expand All @@ -93,13 +100,15 @@ async function handleServerFunction(h3Event) {
) {
// workaround for https://github.com/unjs/nitro/issues/1721
// (issue only in edge runtimes)
parsed.push(await new Request(request, { ...request, body: h3Event.node.req.body }).formData());
// what should work when #1721 is fixed
parsed.push(
await new Request(request, { ...request, body: h3Event.node.req.body }).formData()
);
// what should work when #1721 is fixed
// parsed.push(await request.formData);
} else {
// workaround for https://github.com/unjs/nitro/issues/1721
// (issue only in edge runtimes)
const tmpReq = new Request(request, { ...request, body: h3Event.node.req.body })
const tmpReq = new Request(request, { ...request, body: h3Event.node.req.body });
// what should work when #1721 is fixed
// just use request.json() here
parsed = fromJSON(await tmpReq.json(), {
Expand All @@ -119,27 +128,23 @@ async function handleServerFunction(h3Event) {
}
}
try {
let result = await provideRequestEvent(event, () => {
let result = await provideRequestEvent(event, async () => {
/* @ts-ignore */
sharedConfig.context = { event };
return action(...parsed);
return serverFunction(...parsed);
});

if (singleFlight && instance) {
result = await handleSingleFlight(event, result);
}

// handle responses
if (result instanceof Response) {
if (result.status === 302) {
return new Response(null, {
status: instance ? 204 : 302,
headers: {
Location: result.headers.get("Location")
}
});
}
if (result instanceof Response && instance) {
// forward headers
if (result.headers) mergeResponseHeaders(h3Event, result.headers);
if ((result as any).customBody) {
result = await (result as any).customBody();
} else if (result.body == undefined) result = undefined;
} else if (result.body == undefined) result = null;
}

// handle no JS success case
Expand All @@ -163,27 +168,58 @@ async function handleServerFunction(h3Event) {
}
});
}
if (typeof result === "string") return new Response(result);
if (!result || typeof result === "string") return result || null;
setHeader(h3Event, "content-type", "text/javascript");
return serializeToStream(instance, result);
} catch (x) {
if (x instanceof Response) {
if (x.status === 302) {
return new Response(null, {
status: instance ? 204 : 302,
headers: {
Location: x.headers.get("Location")
}
});
}
if (x.status === 302 && !instance) setResponseStatus(h3Event, 302);
// forward headers
if (x.headers) mergeResponseHeaders(h3Event, x.headers);
if ((x as any).customBody) {
x = await (x as any).customBody();
} else if (x.body == undefined) x = undefined;
} else if (x.body == undefined) x = null;
}
return x;
}
}

async function handleSingleFlight(sourceEvent: FetchEvent, result: any) {
let revalidate;
let url = new URL(sourceEvent.request.headers.get("referer")).toString()
if (result instanceof Response) {
if (result.headers.has("X-Revalidate")) revalidate = result.headers.get("X-Revalidate").split(",");
if (result.headers.has("Location")) url = result.headers.get("Location");
}
const event = cloneEvent(sourceEvent) as PageEvent;
event.request = new Request(url);
return await provideRequestEvent(event, async () => {
await createPageEvent(event);
/* @ts-ignore */
sharedConfig.context = { event };
/* @ts-ignore */
event.router.dataOnly = revalidate || true;
/* @ts-ignore */
event.router.previousUrl = sourceEvent.request.headers.get("referer");
renderToStringAsync(App);
/* @ts-ignore */
const body = event.router.data;
if (body) {
let containsKey = false;
for (const key in body) {
if (body[key] === undefined) delete body[key];
else containsKey = true;
}
if (!containsKey) return result;
if (!(result instanceof Response)) {
body["_$value"] = result;
result = new Response(null, { status: 200 });
}
result.customBody = () => body;
result.headers.set("X-Single-Flight", "true");
return result;
}
});
}

export default eventHandler(handleServerFunction);
Loading

0 comments on commit 20697ed

Please sign in to comment.