-
Notifications
You must be signed in to change notification settings - Fork 419
Expand file tree
/
Copy pathhandler.ts
More file actions
156 lines (145 loc) · 5.61 KB
/
handler.ts
File metadata and controls
156 lines (145 loc) · 5.61 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
import { sharedConfig } from "solid-js";
import { renderToStream, renderToString } from "solid-js/web";
import { provideRequestEvent } from "solid-js/web/storage";
import {
eventHandler,
sendRedirect,
setHeader,
setResponseStatus,
type HTTPEvent
} from "vinxi/http";
import { matchAPIRoute } from "../router/routes";
import { getFetchEvent } from "./fetchEvent";
import { createPageEvent } from "./pageEvent";
import type {
APIEvent,
FetchEvent,
HandlerOptions,
HttpHandlerOptions,
PageEvent,
ResponseStub
} from "./types";
// according to https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#redirection_messages
const validRedirectStatuses = new Set([301, 302, 303, 307, 308]);
/**
* Checks if user has set a redirect status in the response.
* If not, falls back to the 302 (temporary redirect)
*/
export function getExpectedRedirectStatus(response: ResponseStub): number {
if (response.status && validRedirectStatuses.has(response.status)) {
return response.status;
}
return 302;
}
async function defaultHandlerTransform(event: APIEvent, handler: (event: APIEvent) => unknown) {
return await handler(event);
}
export function createBaseHandler(
fn: (context: PageEvent) => unknown,
createPageEvent: (event: FetchEvent) => Promise<PageEvent>,
pageHandlerOptions:
| HandlerOptions
| ((context: PageEvent) => HandlerOptions | Promise<HandlerOptions>) = {},
httpHandlerOptions: HttpHandlerOptions = {}
) {
const transformHandler = httpHandlerOptions.transformHandler ?? defaultHandlerTransform;
return eventHandler({
handler: (e: HTTPEvent) => {
const event = getFetchEvent(e);
return provideRequestEvent(event, async () => {
// api
const match = matchAPIRoute(new URL(event.request.url).pathname, event.request.method);
if (match) {
const mod = await match.handler.import();
const fn =
event.request.method === "HEAD" ? mod["HEAD"] || mod["GET"] : mod[event.request.method];
(event as APIEvent).params = match.params || {};
// @ts-ignore
sharedConfig.context = { event };
const res = await transformHandler(event as APIEvent, fn);
if (res !== undefined) return res;
if (event.request.method !== "GET") {
throw new Error(
`API handler for ${event.request.method} "${event.request.url}" did not return a response.`
);
}
}
// render
const context = await createPageEvent(event);
const resolvedOptions =
typeof pageHandlerOptions == "function"
? await pageHandlerOptions(context)
: { ...pageHandlerOptions };
const mode = resolvedOptions.mode || "stream";
// @ts-ignore
if (resolvedOptions.nonce) context.nonce = resolvedOptions.nonce;
if (mode === "sync" || !import.meta.env.START_SSR) {
const html = renderToString(() => {
(sharedConfig.context as any).event = context;
return fn(context);
}, resolvedOptions);
context.complete = true;
if (context.response && context.response.headers.get("Location")) {
const status = getExpectedRedirectStatus(context.response);
return sendRedirect(e, context.response.headers.get("Location")!, status);
}
return html;
}
if (resolvedOptions.onCompleteAll) {
const og = resolvedOptions.onCompleteAll;
resolvedOptions.onCompleteAll = options => {
handleStreamCompleteRedirect(context)(options);
og(options);
};
} else resolvedOptions.onCompleteAll = handleStreamCompleteRedirect(context);
if (resolvedOptions.onCompleteShell) {
const og = resolvedOptions.onCompleteShell;
resolvedOptions.onCompleteShell = options => {
handleShellCompleteRedirect(context, e)();
og(options);
};
} else resolvedOptions.onCompleteShell = handleShellCompleteRedirect(context, e);
const stream = renderToStream(() => {
(sharedConfig.context as any).event = context;
return fn(context);
}, resolvedOptions);
if (context.response && context.response.headers.get("Location")) {
const status = getExpectedRedirectStatus(context.response);
return sendRedirect(e, context.response.headers.get("Location")!, status);
}
if (mode === "async") return stream;
// fix cloudflare streaming
const { writable, readable } = new TransformStream();
stream.pipeTo(writable);
return readable;
});
}
});
}
function handleShellCompleteRedirect(context: PageEvent, e: HTTPEvent) {
return () => {
if (context.response && context.response.headers.get("Location")) {
const status = getExpectedRedirectStatus(context.response);
setResponseStatus(e, status);
setHeader(e, "Location", context.response.headers.get("Location")!);
}
};
}
function handleStreamCompleteRedirect(context: PageEvent) {
return ({ write }: { write: (html: string) => void }) => {
context.complete = true;
const to = context.response && context.response.headers.get("Location");
to && write(`<script>window.location="${to}"</script>`);
};
}
/**
*
* Read more: https://docs.solidjs.com/solid-start/reference/server/create-handler
*/
export function createHandler(
fn: (context: PageEvent) => unknown,
options?: HandlerOptions | ((context: PageEvent) => HandlerOptions | Promise<HandlerOptions>),
httpOptions?: HttpHandlerOptions
) {
return createBaseHandler(fn, createPageEvent, options, httpOptions);
}