htmx-router 1.0.16 → 2.0.0

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 {};
@@ -74,7 +74,7 @@ function BuildServerManifest(type, imported) {
74
74
  + "type FirstArg<T> = T extends (arg: infer U, ...args: any[]) => any ? U : never;\n"
75
75
  + "function mount(name: string, json: string, ssr?: JSX.Element) {\n"
76
76
  + "\treturn (<div className={island}>\n"
77
- + `\t\t{ssr}\n`
77
+ + `\t\t<div className={island}>{ssr}</div>\n`
78
78
  + `\t\t${SafeScript(type, "`Router._mount('${name}', ${json})`")}\n`
79
79
  + "\t</div>);\n"
80
80
  + "}\n"
@@ -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
+ }
@@ -6,5 +6,8 @@ type Config = {
6
6
  viteDevServer: ViteDevServer | null;
7
7
  render: GenericContext["render"];
8
8
  };
9
+ /**
10
+ * @deprecated
11
+ */
9
12
  export declare function createRequestHandler(config: Config): (req: IncomingMessage, res: ServerResponse) => Promise<void>;
10
13
  export {};
@@ -1,11 +1,15 @@
1
1
  import { ServerOnlyWarning } from "../util.js";
2
2
  ServerOnlyWarning("http-request");
3
+ import { CreateRequest } from "./compatibility/node.js";
3
4
  import { Resolve } from "./native.js";
5
+ /**
6
+ * @deprecated
7
+ */
4
8
  export function createRequestHandler(config) {
5
9
  return async (req, res) => {
6
10
  try {
7
11
  const mod = typeof config.build === "function" ? await config.build() : await config.build;
8
- const request = NativeRequest(req);
12
+ const request = CreateRequest(req);
9
13
  let { response, headers } = await Resolve(request, mod.tree, config);
10
14
  res.writeHead(response.status, headers);
11
15
  if (response.body instanceof ReadableStream) {
@@ -37,25 +41,3 @@ export function createRequestHandler(config) {
37
41
  }
38
42
  };
39
43
  }
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,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
- };
@@ -1,11 +1,17 @@
1
1
  import type { Config } from './index.js';
2
2
  import type { RouteTree } from '../../router.js';
3
+ /**
4
+ * @deprecated
5
+ */
3
6
  export declare function createRequestHandler(config: Config): (req: Request) => Promise<{
4
7
  response: Response;
5
8
  headers: {
6
9
  [key: string]: string | string[];
7
10
  };
8
11
  }>;
12
+ /**
13
+ * @deprecated
14
+ */
9
15
  export declare function Resolve(request: Request, tree: RouteTree, config: Config): Promise<{
10
16
  response: Response;
11
17
  headers: {
@@ -2,12 +2,18 @@ import { ServerOnlyWarning } from "../util.js";
2
2
  ServerOnlyWarning("native-request");
3
3
  import { GenericContext } from "../router.js";
4
4
  import { MakeStatus } from "../../status.js";
5
+ /**
6
+ * @deprecated
7
+ */
5
8
  export function createRequestHandler(config) {
6
9
  return async (req) => {
7
10
  const mod = typeof config.build === "function" ? await config.build() : await config.build;
8
11
  return await Resolve(req, mod.tree, config);
9
12
  };
10
13
  }
14
+ /**
15
+ * @deprecated
16
+ */
11
17
  export async function Resolve(request, tree, config) {
12
18
  const ctx = new GenericContext(request, new URL(request.url), config.render);
13
19
  let response;
@@ -0,0 +1,37 @@
1
+ import type { ViteDevServer } from "vite";
2
+ import { connectToWeb } from "./compatibility/vite/connectToWeb.js";
3
+ import { GenericContext } from "../router.js";
4
+ import { RouterModule } from "./index.js";
5
+ export type Config = {
6
+ build: () => Promise<RouterModule> | Promise<RouterModule>;
7
+ render: GenericContext["render"];
8
+ viteDevServer: ViteDevServer | null;
9
+ };
10
+ type ServerBindType = "pre" | "post";
11
+ type ServerBind = (ctx: GenericContext) => Promise<Response | null> | Response | null;
12
+ export declare class HtmxRouterServer {
13
+ #private;
14
+ readonly vite: {
15
+ handler: ReturnType<typeof connectToWeb>;
16
+ server: Config["viteDevServer"];
17
+ } | null;
18
+ readonly render: Config["render"];
19
+ readonly build: Config["build"];
20
+ constructor(config: Config);
21
+ /**
22
+ * Add a middleware to resolve requests before/after the route tree attempts to resolve
23
+ */
24
+ use(type: ServerBindType, binding: ServerBind): void;
25
+ resolve<T extends boolean>(request: Request, resolve404?: T): Promise<T extends true ? Response : (Response | null)>;
26
+ /**
27
+ * Use the top level error handler in the route tree to render an error
28
+ * @param ctx The context the error came from
29
+ * @param e The error that caused it (if undefined, will be a 404 response)
30
+ */
31
+ error(ctx: Request | GenericContext, e: unknown | undefined): Promise<Response>;
32
+ /**
33
+ * Create a closure for use with the classic express.js like servers
34
+ */
35
+ nodeAdaptor(resolve404?: boolean): (req: import("http").IncomingMessage, res: import("http").ServerResponse) => Promise<void>;
36
+ }
37
+ export {};
@@ -0,0 +1,144 @@
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
+ handler: connectToWeb(config.viteDevServer.middlewares),
26
+ server: config.viteDevServer
27
+ } : null;
28
+ this.render = config.render;
29
+ this.build = config.build;
30
+ this.#binding = {
31
+ pre: [UrlCleaner],
32
+ post: []
33
+ };
34
+ if (config.viteDevServer) {
35
+ const handler = connectToWeb(config.viteDevServer.middlewares);
36
+ this.#binding.post.push(handler);
37
+ }
38
+ }
39
+ /**
40
+ * Add a middleware to resolve requests before/after the route tree attempts to resolve
41
+ */
42
+ use(type, binding) {
43
+ if (type === "pre") {
44
+ this.#binding.pre.push(binding);
45
+ return;
46
+ }
47
+ if (type === "post") {
48
+ this.#binding.post.push(binding);
49
+ return;
50
+ }
51
+ throw new Error(`Unknown binding type "${type}"`);
52
+ }
53
+ async resolve(request, resolve404 = true) {
54
+ const url = new URL(request.url);
55
+ const ctx = new GenericContext(request, url, this.render);
56
+ { // pre-binding
57
+ const res = await this.#applyBindings("pre", ctx);
58
+ if (res)
59
+ return res;
60
+ }
61
+ const tree = await this.#getTree();
62
+ { // route
63
+ const res = await this.#resolveRoute(ctx, tree);
64
+ if (res)
65
+ return res;
66
+ }
67
+ { // post-binding
68
+ const res = await this.#applyBindings("post", ctx);
69
+ if (res)
70
+ return res;
71
+ }
72
+ if (resolve404)
73
+ return await this.error(ctx, undefined);
74
+ return null;
75
+ }
76
+ /**
77
+ * Use the top level error handler in the route tree to render an error
78
+ * @param ctx The context the error came from
79
+ * @param e The error that caused it (if undefined, will be a 404 response)
80
+ */
81
+ async error(ctx, e) {
82
+ if (e === undefined)
83
+ e = new Response("No Route", MakeStatus("Not Found"));
84
+ if (ctx instanceof Request)
85
+ ctx = new GenericContext(ctx, new URL(ctx.url), this.render);
86
+ const tree = await this.#getTree();
87
+ return await tree.unwrap(ctx, e);
88
+ }
89
+ /**
90
+ * Create a closure for use with the classic express.js like servers
91
+ */
92
+ nodeAdaptor(resolve404 = true) {
93
+ return NodeAdaptor(this, resolve404);
94
+ }
95
+ async #getTree() {
96
+ const mod = typeof this.build === "function" ? await this.build() : await this.build;
97
+ return mod.tree;
98
+ }
99
+ async #resolveRoute(ctx, tree) {
100
+ let response;
101
+ try {
102
+ const x = ctx.url.pathname.slice(1);
103
+ const fragments = x === "" ? [] : x.split("/");
104
+ const res = await tree.resolve(fragments, ctx);
105
+ if (res === null)
106
+ return null;
107
+ response = res;
108
+ }
109
+ catch (e) {
110
+ if (e instanceof Error)
111
+ this.vite?.server?.ssrFixStacktrace(e);
112
+ console.error(e);
113
+ response = await tree.unwrap(ctx, e);
114
+ }
115
+ // context merge headers if divergent
116
+ if (response.headers !== ctx.headers) {
117
+ for (const [key, value] of ctx.headers) {
118
+ if (key === "content-type")
119
+ continue;
120
+ response.headers.set(key, value);
121
+ }
122
+ }
123
+ // merge in cookie changes
124
+ const cookies = ctx.cookie.export();
125
+ for (const cookie of cookies)
126
+ response.headers.append("Set-Cookie", cookie);
127
+ return response;
128
+ }
129
+ async #applyBindings(type, ctx) {
130
+ const set = this.#binding[type];
131
+ if (!set)
132
+ throw new Error(`Unknown binding type "${type}"`);
133
+ try {
134
+ for (const bind of set) {
135
+ const res = bind(ctx);
136
+ if (res)
137
+ return res;
138
+ }
139
+ }
140
+ catch (e) {
141
+ return this.error(ctx.request, e);
142
+ }
143
+ }
144
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "htmx-router",
3
- "version": "1.0.16",
3
+ "version": "2.0.0",
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
+ ;