htmx-router 1.0.17 → 2.0.1

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/index.d.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import type { ParameterShaper } from "./util/parameters.js";
2
2
  import type { RouteContext } from "./router.js";
3
- import { createRequestHandler } from "./internal/request/index.js";
4
3
  export type RenderFunction<T extends ParameterShaper = {}> = (ctx: RouteContext<T>) => Promise<Response | JSX.Element | null>;
5
4
  export type CatchFunction<T extends ParameterShaper = {}> = (ctx: RouteContext<T>, err: unknown) => Promise<Response | JSX.Element>;
6
5
  export type RouteModule<T extends ParameterShaper> = {
@@ -16,4 +15,4 @@ export type ClientIslandManifest<T> = {
16
15
  type ClientIsland<T> = T extends (props: infer P) => JSX.Element ? (props: P & {
17
16
  children?: JSX.Element;
18
17
  }) => JSX.Element : T;
19
- export { createRequestHandler, RouteContext };
18
+ export { RouteContext };
package/index.js CHANGED
@@ -1,2 +1 @@
1
- import { createRequestHandler } from "./internal/request/index.js";
2
- export { createRequestHandler };
1
+ export {};
@@ -0,0 +1,3 @@
1
+ import type { IncomingMessage, ServerResponse } from "node:http";
2
+ import type { HtmxRouterServer } from "../server.js";
3
+ export declare function NodeAdaptor(server: HtmxRouterServer, resolve404: boolean): (req: IncomingMessage, res: ServerResponse) => Promise<void>;
@@ -0,0 +1,54 @@
1
+ export function NodeAdaptor(server, resolve404) {
2
+ return async (req, res) => {
3
+ const request = CreateRequest(req);
4
+ const response = await server.resolve(request, resolve404);
5
+ if (response === null)
6
+ return;
7
+ ConsumeResponse(res, response);
8
+ };
9
+ }
10
+ function CreateRequest(req) {
11
+ const ctrl = new AbortController();
12
+ const headers = new Headers(req.headers);
13
+ const url = new URL(`http://${headers.get('host')}${req.originalUrl || req.url}`);
14
+ req.once('aborted', () => ctrl.abort());
15
+ const bodied = req.method !== "GET" && req.method !== "HEAD";
16
+ const request = new Request(url, {
17
+ headers,
18
+ method: req.method,
19
+ body: bodied ? req : undefined,
20
+ signal: ctrl.signal,
21
+ referrer: headers.get("referrer") || undefined,
22
+ // @ts-ignore
23
+ duplex: bodied ? 'half' : undefined
24
+ });
25
+ if (!request.headers.has("X-Real-IP")) {
26
+ const info = req.socket.address();
27
+ if ("address" in info)
28
+ request.headers.set("X-Real-IP", info.address);
29
+ }
30
+ return request;
31
+ }
32
+ async function ConsumeResponse(into, response) {
33
+ const headers = Object.fromEntries(response.headers);
34
+ { // handle multi-cookie setting
35
+ const cookies = response.headers.getSetCookie();
36
+ if (cookies.length > 0)
37
+ headers["set-cookie"] = cookies;
38
+ }
39
+ into.writeHead(response.status, headers);
40
+ if (response.body instanceof ReadableStream) {
41
+ const reader = response.body.getReader();
42
+ while (true) {
43
+ const { done, value } = await reader.read();
44
+ if (done)
45
+ break;
46
+ into.write(value);
47
+ }
48
+ into.end();
49
+ }
50
+ else {
51
+ const rendered = await response.text();
52
+ into.end(rendered);
53
+ }
54
+ }
@@ -0,0 +1,13 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ export { connectToWeb };
3
+ import { GenericContext } from '../../../router.js';
4
+ type NextFunction = (err?: unknown) => void;
5
+ type ConnectMiddleware<PlatformRequest extends IncomingMessage = IncomingMessage, PlatformResponse extends ServerResponse = ServerResponse> = (req: PlatformRequest, res: PlatformResponse, next: NextFunction) => void;
6
+ type WebHandler = (ctx: GenericContext) => Promise<Response | null>;
7
+ /**
8
+ * Converts a Connect-style middleware to a web-compatible request handler.
9
+ *
10
+ * @param {ConnectMiddleware} handler - The Connect-style middleware function to be converted.
11
+ * @returns {WebHandler} A function that handles web requests and returns a Response or undefined.
12
+ */
13
+ declare function connectToWeb(handler: ConnectMiddleware): WebHandler;
@@ -0,0 +1,59 @@
1
+ import { Readable } from 'node:stream';
2
+ export { connectToWeb };
3
+ import { createServerResponse } from './createServerResponse.js';
4
+ import { flattenHeaders } from './header-utils.js';
5
+ const statusCodesWithoutBody = [
6
+ 100, // Continue
7
+ 101, // Switching Protocols
8
+ 102, // Processing (WebDAV)
9
+ 103, // Early Hints
10
+ 204, // No Content
11
+ 205, // Reset Content
12
+ 304, // Not Modified
13
+ ];
14
+ /**
15
+ * Converts a Connect-style middleware to a web-compatible request handler.
16
+ *
17
+ * @param {ConnectMiddleware} handler - The Connect-style middleware function to be converted.
18
+ * @returns {WebHandler} A function that handles web requests and returns a Response or undefined.
19
+ */
20
+ function connectToWeb(handler) {
21
+ return async (ctx) => {
22
+ const req = createIncomingMessage(ctx.request);
23
+ const { res, onReadable } = createServerResponse(req);
24
+ return new Promise((resolve, reject) => {
25
+ onReadable(({ readable, headers, statusCode }) => {
26
+ const responseBody = statusCodesWithoutBody.includes(statusCode)
27
+ ? null
28
+ : Readable.toWeb(readable);
29
+ resolve(new Response(responseBody, {
30
+ status: statusCode,
31
+ headers: flattenHeaders(headers)
32
+ }));
33
+ });
34
+ const next = (error) => {
35
+ if (error)
36
+ reject(error instanceof Error ? error : new Error(String(error)));
37
+ else
38
+ resolve(null);
39
+ };
40
+ Promise.resolve(handler(req, res, next)).catch(next);
41
+ });
42
+ };
43
+ }
44
+ /**
45
+ * Creates an IncomingMessage object from a web Request.
46
+ *
47
+ * @param {Request} request - The web Request object.
48
+ * @returns {IncomingMessage} An IncomingMessage-like object compatible with Node.js HTTP module.
49
+ */
50
+ function createIncomingMessage(request) {
51
+ const parsedUrl = new URL(request.url, 'http://localhost');
52
+ const pathnameAndQuery = (parsedUrl.pathname || '') + (parsedUrl.search || '');
53
+ const body = request.body ? Readable.fromWeb(request.body) : Readable.from([]);
54
+ return Object.assign(body, {
55
+ url: pathnameAndQuery,
56
+ method: request.method,
57
+ headers: Object.fromEntries(request.headers)
58
+ });
59
+ }
@@ -0,0 +1,24 @@
1
+ export { createServerResponse };
2
+ import { ServerResponse, type IncomingMessage, type OutgoingHttpHeaders } from 'node:http';
3
+ import { Readable } from 'node:stream';
4
+ /**
5
+ * Creates a custom ServerResponse object that allows for intercepting and streaming the response.
6
+ *
7
+ * @param {IncomingMessage} incomingMessage - The incoming HTTP request message.
8
+ * @returns {{
9
+ * res: ServerResponse;
10
+ * onReadable: (cb: (result: { readable: Readable; headers: OutgoingHttpHeaders; statusCode: number }) => void) => void
11
+ * }}
12
+ * An object containing:
13
+ * - res: The custom ServerResponse object.
14
+ * - onReadable: A function that takes a callback. The callback is invoked when the response is readable,
15
+ * providing an object with the readable stream, headers, and status code.
16
+ */
17
+ declare function createServerResponse(incomingMessage: IncomingMessage): {
18
+ res: ServerResponse<IncomingMessage>;
19
+ onReadable: (cb: (result: {
20
+ readable: Readable;
21
+ headers: OutgoingHttpHeaders;
22
+ statusCode: number;
23
+ }) => void) => void;
24
+ };
@@ -0,0 +1,63 @@
1
+ // Taken From: https://github.com/vikejs/vike-server/blob/7bcb7805fa39ef945adc73f4faf5c89a81f3f7ec/packages/vike-node/src/runtime/adapters/createServerResponse.ts
2
+ export { createServerResponse };
3
+ import { ServerResponse } from 'node:http';
4
+ import { PassThrough, Readable } from 'node:stream';
5
+ /**
6
+ * Creates a custom ServerResponse object that allows for intercepting and streaming the response.
7
+ *
8
+ * @param {IncomingMessage} incomingMessage - The incoming HTTP request message.
9
+ * @returns {{
10
+ * res: ServerResponse;
11
+ * onReadable: (cb: (result: { readable: Readable; headers: OutgoingHttpHeaders; statusCode: number }) => void) => void
12
+ * }}
13
+ * An object containing:
14
+ * - res: The custom ServerResponse object.
15
+ * - onReadable: A function that takes a callback. The callback is invoked when the response is readable,
16
+ * providing an object with the readable stream, headers, and status code.
17
+ */
18
+ function createServerResponse(incomingMessage) {
19
+ const res = new ServerResponse(incomingMessage);
20
+ const passThrough = new PassThrough();
21
+ let handled = false;
22
+ const onReadable = (cb) => {
23
+ const handleReadable = () => {
24
+ if (handled)
25
+ return;
26
+ handled = true;
27
+ cb({ readable: Readable.from(passThrough), headers: res.getHeaders(), statusCode: res.statusCode });
28
+ };
29
+ passThrough.once('readable', handleReadable);
30
+ passThrough.once('end', handleReadable);
31
+ };
32
+ passThrough.once('finish', () => {
33
+ res.emit('finish');
34
+ });
35
+ passThrough.once('close', () => {
36
+ res.destroy();
37
+ res.emit('close');
38
+ });
39
+ passThrough.on('drain', () => {
40
+ res.emit('drain');
41
+ });
42
+ res.write = passThrough.write.bind(passThrough);
43
+ res.end = passThrough.end.bind(passThrough);
44
+ res.writeHead = function writeHead(statusCode, statusMessage, headers) {
45
+ res.statusCode = statusCode;
46
+ if (typeof statusMessage === 'object') {
47
+ headers = statusMessage;
48
+ statusMessage = undefined;
49
+ }
50
+ if (headers) {
51
+ Object.entries(headers).forEach(([key, value]) => {
52
+ if (value !== undefined) {
53
+ res.setHeader(key, value);
54
+ }
55
+ });
56
+ }
57
+ return res;
58
+ };
59
+ return {
60
+ res,
61
+ onReadable
62
+ };
63
+ }
@@ -0,0 +1,6 @@
1
+ export { flattenHeaders, groupHeaders, parseHeaders };
2
+ import type { OutgoingHttpHeaders } from 'node:http';
3
+ type HeadersProvided = Record<string, string | string[] | undefined> | Headers;
4
+ declare function groupHeaders(headers: [string, string][]): [string, string | string[]][];
5
+ declare function flattenHeaders(headers: OutgoingHttpHeaders): [string, string][];
6
+ declare function parseHeaders(headers: HeadersProvided): [string, string][];
@@ -0,0 +1,70 @@
1
+ // Taken From: https://github.com/vikejs/vike-server/blob/7bcb7805fa39ef945adc73f4faf5c89a81f3f7ec/packages/vike-node/src/runtime/utils/header-utils.ts
2
+ export { flattenHeaders, groupHeaders, parseHeaders };
3
+ function groupHeaders(headers) {
4
+ const grouped = {};
5
+ headers.forEach(([key, value]) => {
6
+ if (grouped[key]) {
7
+ // If the key already exists, append the new value
8
+ if (Array.isArray(grouped[key])) {
9
+ ;
10
+ grouped[key].push(value);
11
+ }
12
+ else {
13
+ grouped[key] = [grouped[key], value];
14
+ }
15
+ }
16
+ else {
17
+ // If the key doesn't exist, add it to the object
18
+ grouped[key] = value;
19
+ }
20
+ });
21
+ // Convert the object back to an array
22
+ return Object.entries(grouped);
23
+ }
24
+ function flattenHeaders(headers) {
25
+ const flatHeaders = [];
26
+ for (const [key, value] of Object.entries(headers)) {
27
+ if (value === undefined || value === null) {
28
+ continue;
29
+ }
30
+ if (Array.isArray(value)) {
31
+ value.forEach((v) => {
32
+ if (v != null) {
33
+ flatHeaders.push([key, String(v)]);
34
+ }
35
+ });
36
+ }
37
+ else {
38
+ flatHeaders.push([key, String(value)]);
39
+ }
40
+ }
41
+ return flatHeaders;
42
+ }
43
+ function parseHeaders(headers) {
44
+ const result = [];
45
+ if (typeof headers.forEach === 'function') {
46
+ headers.forEach((value, key) => {
47
+ if (Array.isArray(value)) {
48
+ value.forEach((value_) => {
49
+ result.push([key, value_]);
50
+ });
51
+ }
52
+ else {
53
+ result.push([key, value]);
54
+ }
55
+ });
56
+ }
57
+ else {
58
+ for (const [key, value] of Object.entries(headers)) {
59
+ if (Array.isArray(value)) {
60
+ value.forEach((value_) => {
61
+ result.push([key, value_]);
62
+ });
63
+ }
64
+ else {
65
+ result.push([key, value]);
66
+ }
67
+ }
68
+ }
69
+ return result;
70
+ }
@@ -1,17 +1,4 @@
1
- import type { ViteDevServer } from "vite";
2
- import type { GenericContext } from "../router.js";
3
1
  import type { RouteTree } from '../../router.js';
4
- import * as native from "./native.js";
5
- import * as http from "./http.js";
6
- export type Config = {
7
- build: Promise<any> | (() => Promise<Record<string, any>>);
8
- viteDevServer: ViteDevServer | null;
9
- render: GenericContext["render"];
10
- };
11
2
  export type RouterModule = {
12
3
  tree: RouteTree;
13
4
  };
14
- export declare const createRequestHandler: {
15
- http: typeof http.createRequestHandler;
16
- native: typeof native.createRequestHandler;
17
- };
@@ -1,8 +1,2 @@
1
1
  import { ServerOnlyWarning } from "../util.js";
2
2
  ServerOnlyWarning("request");
3
- import * as native from "./native.js";
4
- import * as http from "./http.js";
5
- export const createRequestHandler = {
6
- http: http.createRequestHandler,
7
- native: native.createRequestHandler
8
- };
@@ -0,0 +1,33 @@
1
+ import type { ViteDevServer } from "vite";
2
+ import { GenericContext } from "../router.js";
3
+ import { RouterModule } from "./index.js";
4
+ export type Config = {
5
+ build: () => Promise<RouterModule> | Promise<RouterModule>;
6
+ render: GenericContext["render"];
7
+ viteDevServer: ViteDevServer | null;
8
+ };
9
+ type ServerBindType = "pre" | "post";
10
+ type ServerBind = (ctx: GenericContext) => Promise<Response | null> | Response | null;
11
+ export declare class HtmxRouterServer {
12
+ #private;
13
+ readonly vite: Config["viteDevServer"];
14
+ readonly render: Config["render"];
15
+ readonly build: Config["build"];
16
+ constructor(config: Config);
17
+ /**
18
+ * Add a middleware to resolve requests before/after the route tree attempts to resolve
19
+ */
20
+ use(type: ServerBindType, binding: ServerBind): void;
21
+ resolve<T extends boolean>(request: Request, resolve404?: T): Promise<T extends true ? Response : (Response | null)>;
22
+ /**
23
+ * Use the top level error handler in the route tree to render an error
24
+ * @param ctx The context the error came from
25
+ * @param e The error that caused it (if undefined, will be a 404 response)
26
+ */
27
+ error(ctx: Request | GenericContext, e: unknown | undefined): Promise<Response>;
28
+ /**
29
+ * Create a closure for use with the classic express.js like servers
30
+ */
31
+ nodeAdaptor(resolve404?: boolean): (req: import("http").IncomingMessage, res: import("http").ServerResponse) => Promise<void>;
32
+ }
33
+ export {};
@@ -0,0 +1,141 @@
1
+ import { ServerOnlyWarning } from "../util.js";
2
+ ServerOnlyWarning("request/server");
3
+ import { connectToWeb } from "./compatibility/vite/connectToWeb.js";
4
+ import { GenericContext } from "../router.js";
5
+ import { NodeAdaptor } from "./compatibility/node.js";
6
+ import { MakeStatus } from "../../status.js";
7
+ function UrlCleaner({ url }) {
8
+ const i = url.pathname.lastIndexOf("/");
9
+ if (i === 0)
10
+ return null;
11
+ if (i !== url.pathname.length - 1)
12
+ return null;
13
+ url.pathname = url.pathname.slice(0, -1);
14
+ return new Response("", MakeStatus("Permanent Redirect", {
15
+ headers: { location: url.toString() }
16
+ }));
17
+ }
18
+ export class HtmxRouterServer {
19
+ vite;
20
+ render;
21
+ build;
22
+ #binding;
23
+ constructor(config) {
24
+ this.vite = config.viteDevServer;
25
+ this.render = config.render;
26
+ this.build = config.build;
27
+ this.#binding = {
28
+ pre: [UrlCleaner],
29
+ post: []
30
+ };
31
+ if (this.vite) {
32
+ const handler = connectToWeb(this.vite.middlewares);
33
+ this.#binding.post.push(handler);
34
+ }
35
+ }
36
+ /**
37
+ * Add a middleware to resolve requests before/after the route tree attempts to resolve
38
+ */
39
+ use(type, binding) {
40
+ if (type === "pre") {
41
+ this.#binding.pre.push(binding);
42
+ return;
43
+ }
44
+ if (type === "post") {
45
+ this.#binding.post.push(binding);
46
+ return;
47
+ }
48
+ throw new Error(`Unknown binding type "${type}"`);
49
+ }
50
+ async resolve(request, resolve404 = true) {
51
+ const url = new URL(request.url);
52
+ const ctx = new GenericContext(request, url, this.render);
53
+ { // pre-binding
54
+ const res = await this.#applyBindings("pre", ctx);
55
+ if (res)
56
+ return res;
57
+ }
58
+ const tree = await this.#getTree();
59
+ { // route
60
+ const res = await this.#resolveRoute(ctx, tree);
61
+ if (res)
62
+ return res;
63
+ }
64
+ { // post-binding
65
+ const res = await this.#applyBindings("post", ctx);
66
+ if (res)
67
+ return res;
68
+ }
69
+ if (resolve404)
70
+ return await this.error(ctx, undefined);
71
+ return null;
72
+ }
73
+ /**
74
+ * Use the top level error handler in the route tree to render an error
75
+ * @param ctx The context the error came from
76
+ * @param e The error that caused it (if undefined, will be a 404 response)
77
+ */
78
+ async error(ctx, e) {
79
+ if (e === undefined)
80
+ e = new Response("No Route", MakeStatus("Not Found"));
81
+ if (ctx instanceof Request)
82
+ ctx = new GenericContext(ctx, new URL(ctx.url), this.render);
83
+ const tree = await this.#getTree();
84
+ return await tree.unwrap(ctx, e);
85
+ }
86
+ /**
87
+ * Create a closure for use with the classic express.js like servers
88
+ */
89
+ nodeAdaptor(resolve404 = true) {
90
+ return NodeAdaptor(this, resolve404);
91
+ }
92
+ async #getTree() {
93
+ const mod = typeof this.build === "function" ? await this.build() : await this.build;
94
+ return mod.tree;
95
+ }
96
+ async #resolveRoute(ctx, tree) {
97
+ let response;
98
+ try {
99
+ const x = ctx.url.pathname.slice(1);
100
+ const fragments = x === "" ? [] : x.split("/");
101
+ const res = await tree.resolve(fragments, ctx);
102
+ if (res === null)
103
+ return null;
104
+ response = res;
105
+ }
106
+ catch (e) {
107
+ if (e instanceof Error)
108
+ this.vite?.ssrFixStacktrace(e);
109
+ console.error(e);
110
+ response = await tree.unwrap(ctx, e);
111
+ }
112
+ // context merge headers if divergent
113
+ if (response.headers !== ctx.headers) {
114
+ for (const [key, value] of ctx.headers) {
115
+ if (key === "content-type")
116
+ continue;
117
+ response.headers.set(key, value);
118
+ }
119
+ }
120
+ // merge in cookie changes
121
+ const cookies = ctx.cookie.export();
122
+ for (const cookie of cookies)
123
+ response.headers.append("Set-Cookie", cookie);
124
+ return response;
125
+ }
126
+ async #applyBindings(type, ctx) {
127
+ const set = this.#binding[type];
128
+ if (!set)
129
+ throw new Error(`Unknown binding type "${type}"`);
130
+ try {
131
+ for (const bind of set) {
132
+ const res = bind(ctx);
133
+ if (res)
134
+ return res;
135
+ }
136
+ }
137
+ catch (e) {
138
+ return this.error(ctx.request, e);
139
+ }
140
+ }
141
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "htmx-router",
3
- "version": "1.0.17",
3
+ "version": "2.0.1",
4
4
  "description": "A lightweight SSR framework with server+client islands",
5
5
  "keywords": [
6
6
  "htmx",
package/router.d.ts CHANGED
@@ -30,7 +30,7 @@ export declare class RouteTree {
30
30
  private resolveNext;
31
31
  private resolveWild;
32
32
  private resolveSlug;
33
- private unwrap;
33
+ unwrap(ctx: GenericContext, res: unknown): Promise<Response>;
34
34
  }
35
35
  declare class RouteLeaf {
36
36
  private module;
package/server.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ import { HtmxRouterServer, Config } from "./internal/request/server.js";
2
+ export declare function createHtmxServer(config: Config): HtmxRouterServer;
package/server.js ADDED
@@ -0,0 +1,4 @@
1
+ import { HtmxRouterServer } from "./internal/request/server.js";
2
+ export function createHtmxServer(config) {
3
+ return new HtmxRouterServer(config);
4
+ }
package/status.d.ts CHANGED
@@ -67,8 +67,4 @@ type Definitions = typeof dictionary;
67
67
  export type StatusCode = keyof Definitions;
68
68
  export type StatusText = Definitions[StatusCode];
69
69
  export declare function MakeStatus(lookup: StatusCode | StatusText, init?: ResponseInit | Headers): ResponseInit;
70
- /**
71
- * @deprecated
72
- */
73
- export type Status = StatusCode;
74
70
  export {};
package/status.js CHANGED
@@ -97,3 +97,4 @@ function SetStatus(into = {}, status, statusText) {
97
97
  into.status = status;
98
98
  return into;
99
99
  }
100
+ ;
@@ -1,10 +0,0 @@
1
- import type { IncomingMessage, ServerResponse } from "http";
2
- import type { ViteDevServer } from "vite";
3
- import { GenericContext } from "../router.js";
4
- type Config = {
5
- build: Promise<any> | (() => Promise<Record<string, any>>);
6
- viteDevServer: ViteDevServer | null;
7
- render: GenericContext["render"];
8
- };
9
- export declare function createRequestHandler(config: Config): (req: IncomingMessage, res: ServerResponse) => Promise<void>;
10
- export {};
@@ -1,61 +0,0 @@
1
- import { ServerOnlyWarning } from "../util.js";
2
- ServerOnlyWarning("http-request");
3
- import { Resolve } from "./native.js";
4
- export function createRequestHandler(config) {
5
- return async (req, res) => {
6
- try {
7
- const mod = typeof config.build === "function" ? await config.build() : await config.build;
8
- const request = NativeRequest(req);
9
- let { response, headers } = await Resolve(request, mod.tree, config);
10
- res.writeHead(response.status, headers);
11
- if (response.body instanceof ReadableStream) {
12
- const reader = response.body.getReader();
13
- while (true) {
14
- const { done, value } = await reader.read();
15
- if (done)
16
- break;
17
- res.write(value); // `value` is a Uint8Array.
18
- }
19
- res.end();
20
- }
21
- else {
22
- const rendered = await response.text();
23
- res.end(rendered);
24
- }
25
- }
26
- catch (e) {
27
- res.statusCode = 500;
28
- if (e instanceof Error) {
29
- console.error(e.stack);
30
- config.viteDevServer?.ssrFixStacktrace(e);
31
- res.end(e.stack);
32
- }
33
- else {
34
- console.error(e);
35
- res.end(String(e));
36
- }
37
- }
38
- };
39
- }
40
- function NativeRequest(req) {
41
- const ctrl = new AbortController();
42
- const headers = new Headers(req.headers);
43
- const url = new URL(`http://${headers.get('host')}${req.originalUrl || req.url}`);
44
- req.once('aborted', () => ctrl.abort());
45
- const bodied = req.method !== "GET" && req.method !== "HEAD";
46
- const request = new Request(url, {
47
- headers,
48
- method: req.method,
49
- body: bodied ? req : undefined,
50
- signal: ctrl.signal,
51
- referrer: headers.get("referrer") || undefined,
52
- // @ts-ignore
53
- duplex: bodied ? 'half' : undefined
54
- });
55
- if (!request.headers.has("X-Real-IP")) {
56
- const info = req.socket.address();
57
- if ("address" in info)
58
- request.headers.set("X-Real-IP", info.address);
59
- }
60
- return request;
61
- }
@@ -1,14 +0,0 @@
1
- import type { Config } from './index.js';
2
- import type { RouteTree } from '../../router.js';
3
- export declare function createRequestHandler(config: Config): (req: Request) => Promise<{
4
- response: Response;
5
- headers: {
6
- [key: string]: string | string[];
7
- };
8
- }>;
9
- export declare function Resolve(request: Request, tree: RouteTree, config: Config): Promise<{
10
- response: Response;
11
- headers: {
12
- [key: string]: string | string[];
13
- };
14
- }>;
@@ -1,55 +0,0 @@
1
- import { ServerOnlyWarning } from "../util.js";
2
- ServerOnlyWarning("native-request");
3
- import { GenericContext } from "../router.js";
4
- import { MakeStatus } from "../../status.js";
5
- export function createRequestHandler(config) {
6
- return async (req) => {
7
- const mod = typeof config.build === "function" ? await config.build() : await config.build;
8
- return await Resolve(req, mod.tree, config);
9
- };
10
- }
11
- export async function Resolve(request, tree, config) {
12
- const ctx = new GenericContext(request, new URL(request.url), config.render);
13
- let response;
14
- try {
15
- const x = ctx.url.pathname.slice(1);
16
- if (x.endsWith("/")) {
17
- ctx.headers.set("location", ctx.url.pathname.slice(0, -1) + ctx.url.search + ctx.url.hash);
18
- response = new Response("", MakeStatus("Permanent Redirect", { headers: ctx.headers }));
19
- }
20
- else {
21
- const fragments = x === "" ? [] : x.split("/");
22
- const res = await tree.resolve(fragments, ctx);
23
- response = res === null
24
- ? new Response("No Route Found", MakeStatus("Not Found", ctx.headers))
25
- : res;
26
- }
27
- // Override with context headers
28
- if (response.headers !== ctx.headers) {
29
- for (const [key, value] of ctx.headers) {
30
- if (key === "content-type")
31
- continue;
32
- response.headers.set(key, value);
33
- }
34
- }
35
- }
36
- catch (e) {
37
- if (e instanceof Error) {
38
- console.error(e.stack);
39
- config.viteDevServer?.ssrFixStacktrace(e);
40
- response = new Response(e.message + "\n" + e.stack, { status: 500, statusText: "Internal Server Error" });
41
- }
42
- else {
43
- console.error(e);
44
- response = new Response(String(e), { status: 500, statusText: "Internal Server Error" });
45
- }
46
- }
47
- // Merge cookie changes
48
- const headers = Object.fromEntries(response.headers);
49
- const cookies = ctx.cookie.export();
50
- if (cookies.length > 0) {
51
- headers['set-cookie'] = cookies;
52
- response.headers.set("Set-Cookie", cookies[0]); // Response object doesn't support multi-header..[]
53
- }
54
- return { response, headers };
55
- }