htmx-router 1.0.1 → 1.0.2

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/defer.d.ts CHANGED
@@ -10,5 +10,5 @@ export declare const path = "_/defer/$";
10
10
  export declare const parameters: {
11
11
  $: StringConstructor;
12
12
  };
13
- export declare function loader(ctx: RouteContext<typeof parameters>): Promise<Response | null>;
13
+ export declare function loader(ctx: RouteContext<typeof parameters>): Promise<(Response | BodyInit) | null>;
14
14
  export declare const action: typeof loader;
package/endpoint.d.ts CHANGED
@@ -16,5 +16,5 @@ export declare const path = "_/endpoint/$";
16
16
  export declare const parameters: {
17
17
  $: StringConstructor;
18
18
  };
19
- export declare function loader(ctx: RouteContext<typeof parameters>): Promise<Response | null>;
19
+ export declare function loader(ctx: RouteContext<typeof parameters>): Promise<(Response | BodyInit) | null>;
20
20
  export declare const action: typeof loader;
@@ -1,6 +1,7 @@
1
1
  import { ParameterShaper } from '../util/parameters.js';
2
2
  import { RouteContext } from "../router.js";
3
3
  import { Cookies } from '../cookies.js';
4
+ type Rendered = Response | BodyInit;
4
5
  export declare class GenericContext {
5
6
  request: Request;
6
7
  headers: Headers;
@@ -9,7 +10,8 @@ export declare class GenericContext {
9
10
  [key: string]: string;
10
11
  };
11
12
  url: URL;
12
- render: (res: JSX.Element, headers: Headers) => Promise<Response> | Response;
13
+ render: (res: JSX.Element, headers: Headers) => Promise<Rendered> | Rendered;
13
14
  constructor(request: GenericContext["request"], url: GenericContext["url"], renderer: GenericContext["render"]);
14
15
  shape<T extends ParameterShaper>(shape: T): RouteContext<T>;
15
16
  }
17
+ export {};
package/navigate.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- export declare function navigate(href: string, pushUrl?: boolean): Promise<void>;
1
+ export declare function navigate(href: string, pushHistory?: boolean): Promise<void>;
2
2
  export declare function revalidate(): Promise<void>;
3
3
  export declare function htmxAppend(href: string, verb?: string): Promise<void>;
package/navigate.js CHANGED
@@ -4,20 +4,30 @@ function htmx() {
4
4
  throw new Error("Missing htmx");
5
5
  return htmx;
6
6
  }
7
- export async function navigate(href, pushUrl = true) {
7
+ const driver = (typeof document === "object" ? document.createElement("a") : null);
8
+ export async function navigate(href, pushHistory = true) {
8
9
  if (typeof window !== "object")
9
10
  return;
11
+ if (!driver)
12
+ return;
10
13
  const url = new URL(href, window.location.href);
11
14
  if (url.host !== window.location.host) {
12
15
  window.location.assign(href);
13
16
  return;
14
17
  }
15
- // Perform an HTMX GET request similar to hx-boost
16
- await htmx().ajax("GET", href, {
17
- target: 'body',
18
- swap: 'outerHTML',
19
- history: pushUrl
20
- });
18
+ driver.setAttribute("hx-boost", "true");
19
+ driver.setAttribute("href", url.href);
20
+ if (pushHistory) {
21
+ driver.setAttribute("hx-push-url", "true");
22
+ driver.removeAttribute("hx-replace-url");
23
+ }
24
+ else {
25
+ driver.setAttribute("hx-replace-url", "true");
26
+ driver.removeAttribute("hx-push-url");
27
+ }
28
+ document.body.appendChild(driver);
29
+ htmx().process(driver);
30
+ driver.click();
21
31
  }
22
32
  export function revalidate() {
23
33
  return navigate("", false);
@@ -25,7 +35,6 @@ export function revalidate() {
25
35
  export async function htmxAppend(href, verb = "GET") {
26
36
  await htmx().ajax(verb, href, {
27
37
  target: 'body',
28
- swap: 'beforeend',
29
- history: false
38
+ swap: 'beforeend'
30
39
  });
31
40
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "htmx-router",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "A lightweight SSR framework with server+client islands",
5
5
  "keywords": [
6
6
  "htmx",
package/response.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export declare function text(text: string, init?: ResponseInit): Response;
2
+ export declare function html(text: string, init?: ResponseInit): Response;
2
3
  export type TypedResponse<T> = Omit<Response, "json"> & {
3
4
  json(): Promise<T>;
4
5
  };
package/response.js CHANGED
@@ -7,6 +7,15 @@ export function text(text, init) {
7
7
  res.headers.set("X-Caught", "true");
8
8
  return res;
9
9
  }
10
+ export function html(text, init) {
11
+ init ||= {};
12
+ init.statusText ||= "ok";
13
+ init.status ||= 200;
14
+ const res = new Response(text, init);
15
+ res.headers.set("Content-Type", "text/html; charset=UTF-8");
16
+ res.headers.set("X-Caught", "true");
17
+ return res;
18
+ }
10
19
  export function json(data, init) {
11
20
  init ||= {};
12
21
  init.statusText ||= "ok";
package/router.js CHANGED
@@ -114,6 +114,8 @@ export class RouteTree {
114
114
  || await this.resolveWild(fragments, ctx)
115
115
  || await this.resolveSlug(fragments, ctx);
116
116
  if (res instanceof Response) {
117
+ if (res.ok)
118
+ return res;
117
119
  if (100 <= res.status && res.status <= 399)
118
120
  return res;
119
121
  if (res.headers.has("X-Caught"))
@@ -127,7 +129,10 @@ export class RouteTree {
127
129
  return null;
128
130
  if (!this.index)
129
131
  return null;
130
- return await this.index.resolve(ctx);
132
+ const res = await this.index.resolve(ctx);
133
+ if (res instanceof Response)
134
+ return res;
135
+ return new Response(res, { headers: ctx.headers });
131
136
  }
132
137
  async resolveNext(fragments, ctx) {
133
138
  if (fragments.length < 1)
@@ -152,14 +157,24 @@ export class RouteTree {
152
157
  const res = this.slug.resolve
153
158
  ? await this.slug.resolve(ctx)
154
159
  : null;
155
- return res;
160
+ if (res instanceof Response)
161
+ return res;
162
+ return new Response(res, { headers: ctx.headers });
156
163
  }
157
164
  async unwrap(ctx, res) {
158
165
  if (!this.slug)
159
166
  throw res;
160
- const caught = await this.slug.error(ctx, res);
161
- caught.headers.set("X-Caught", "true");
162
- return caught;
167
+ let caught = await this.slug.error(ctx, res);
168
+ if (caught instanceof Response) {
169
+ caught.headers.set("X-Caught", "true");
170
+ return caught;
171
+ }
172
+ ctx.headers.set("X-Caught", "true");
173
+ return new Response(caught, res instanceof Response ? res : {
174
+ status: 500,
175
+ statusText: "Internal Server Error",
176
+ headers: ctx.headers
177
+ });
163
178
  }
164
179
  }
165
180
  class RouteLeaf {
package/status.d.ts ADDED
@@ -0,0 +1,69 @@
1
+ declare const definitions: {
2
+ 100: "Continue";
3
+ 101: "Switching Protocols";
4
+ 102: "Processing";
5
+ 103: "Early Hints";
6
+ 200: "OK";
7
+ 201: "Created";
8
+ 202: "Accepted";
9
+ 203: "Non-Authoritative Information";
10
+ 204: "No Content";
11
+ 205: "Reset Content";
12
+ 206: "Partial Content";
13
+ 207: "Multi-Status";
14
+ 208: "Already Reported";
15
+ 226: "IM Used";
16
+ 300: "Multiple Choices";
17
+ 301: "Moved Permanently";
18
+ 302: "Found";
19
+ 303: "See Other";
20
+ 304: "Not Modified";
21
+ 305: "Use Proxy";
22
+ 306: "Switch Proxy";
23
+ 308: "Permanent Redirect";
24
+ 400: "Bad Request";
25
+ 401: "Unauthorized";
26
+ 402: "Payment Required";
27
+ 403: "Forbidden";
28
+ 405: "Method Not Allowed";
29
+ 406: "Not Acceptable";
30
+ 407: "Proxy Authentication Required";
31
+ 408: "Request Timeout";
32
+ 409: "Conflict";
33
+ 410: "Gone";
34
+ 411: "Length Required";
35
+ 412: "Precondition Failed";
36
+ 413: "Payload Too Large";
37
+ 414: "URI Too Long";
38
+ 415: "Unsupported Media Type";
39
+ 416: "Range Not Satisfiable";
40
+ 417: "Expectation Failed";
41
+ 418: "I'm a teapot";
42
+ 421: "Misdirected Request";
43
+ 422: "Unprocessable Content";
44
+ 423: "Locked";
45
+ 424: "Failed Dependency";
46
+ 425: "Too Early";
47
+ 426: "Upgrade Required";
48
+ 428: "Precondition Required";
49
+ 429: "Too Many Requests";
50
+ 431: "Request Header Fields Too Large";
51
+ 451: "Unavailable For Legal Reasons";
52
+ 500: "Internal Server Error";
53
+ 501: "Not Implemented";
54
+ 502: "Bad Gateway";
55
+ 503: "Service Unavailable";
56
+ 504: "Gateway Timeout";
57
+ 505: "HTTP Version Not Supported";
58
+ 506: "Variant Also Negotiates";
59
+ 507: "Insufficient Storage";
60
+ 508: "Loop Detected";
61
+ 510: "Not Extended";
62
+ 511: "Network Authentication Required";
63
+ };
64
+ export type StatusText = typeof definitions[keyof typeof definitions];
65
+ export declare function MakeStatus(lookup: number | StatusText | string): {
66
+ status: number;
67
+ statusText: string;
68
+ };
69
+ export {};
package/status.js ADDED
@@ -0,0 +1,91 @@
1
+ const definitions = {
2
+ 100: "Continue",
3
+ 101: "Switching Protocols",
4
+ 102: "Processing",
5
+ 103: "Early Hints",
6
+ 200: "OK",
7
+ 201: "Created",
8
+ 202: "Accepted",
9
+ 203: "Non-Authoritative Information",
10
+ 204: "No Content",
11
+ 205: "Reset Content",
12
+ 206: "Partial Content",
13
+ 207: "Multi-Status",
14
+ 208: "Already Reported",
15
+ 226: "IM Used",
16
+ 300: "Multiple Choices",
17
+ 301: "Moved Permanently",
18
+ 302: "Found",
19
+ 303: "See Other",
20
+ 304: "Not Modified",
21
+ 305: "Use Proxy",
22
+ 306: "Switch Proxy",
23
+ 308: "Permanent Redirect",
24
+ 400: "Bad Request",
25
+ 401: "Unauthorized",
26
+ 402: "Payment Required",
27
+ 403: "Forbidden",
28
+ 405: "Method Not Allowed",
29
+ 406: "Not Acceptable",
30
+ 407: "Proxy Authentication Required",
31
+ 408: "Request Timeout",
32
+ 409: "Conflict",
33
+ 410: "Gone",
34
+ 411: "Length Required",
35
+ 412: "Precondition Failed",
36
+ 413: "Payload Too Large",
37
+ 414: "URI Too Long",
38
+ 415: "Unsupported Media Type",
39
+ 416: "Range Not Satisfiable",
40
+ 417: "Expectation Failed",
41
+ 418: "I'm a teapot",
42
+ 421: "Misdirected Request",
43
+ 422: "Unprocessable Content",
44
+ 423: "Locked",
45
+ 424: "Failed Dependency",
46
+ 425: "Too Early",
47
+ 426: "Upgrade Required",
48
+ 428: "Precondition Required",
49
+ 429: "Too Many Requests",
50
+ 431: "Request Header Fields Too Large",
51
+ 451: "Unavailable For Legal Reasons",
52
+ 500: "Internal Server Error",
53
+ 501: "Not Implemented",
54
+ 502: "Bad Gateway",
55
+ 503: "Service Unavailable",
56
+ 504: "Gateway Timeout",
57
+ 505: "HTTP Version Not Supported",
58
+ 506: "Variant Also Negotiates",
59
+ 507: "Insufficient Storage",
60
+ 508: "Loop Detected",
61
+ 510: "Not Extended",
62
+ 511: "Network Authentication Required",
63
+ };
64
+ const lookup = new Array(500);
65
+ for (let i = 100; i < 600; i++) {
66
+ lookup[i - 100] = definitions[i]
67
+ || definitions[i % 100];
68
+ }
69
+ const index = new Map();
70
+ for (const key in definitions) {
71
+ const code = Number(key);
72
+ index.set(definitions[code].toLowerCase(), code);
73
+ }
74
+ export function MakeStatus(lookup) {
75
+ if (typeof lookup === "number")
76
+ return lookupCode(lookup);
77
+ return lookupStatus(lookup);
78
+ }
79
+ function lookupCode(status) {
80
+ if (status < 100)
81
+ throw new TypeError(`Status ${status}<100`);
82
+ if (status > 599)
83
+ throw new TypeError(`Status ${status}>599`);
84
+ return { status, statusText: lookup[status] };
85
+ }
86
+ function lookupStatus(statusText) {
87
+ const status = index.get(statusText.toLowerCase());
88
+ if (!status)
89
+ throw new TypeError(`statusText ${statusText} is not of type StatusText`);
90
+ return { status, statusText };
91
+ }