htmx-router 1.0.0-alpha.3 → 1.0.0-alpha.4

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/bin/cli/index.js CHANGED
@@ -11,6 +11,7 @@ await writeFile(config.router.output, `/*---------------------------------------
11
11
  * Generated by htmx-router *
12
12
  * Warn: Any changes will be overwritten *
13
13
  -------------------------------------------*/
14
+ /* eslint-disable @typescript-eslint/no-explicit-any */
14
15
 
15
16
  import { GenericContext, RouteTree } from "htmx-router/bin/router";
16
17
  import { RegisterDynamic } from "htmx-router/bin/util/dynamic";
@@ -83,6 +83,7 @@ function BuildClientManifest(type, imports) {
83
83
  + " * Generated by htmx-router *\n"
84
84
  + " * Warn: Any changes will be overwritten *\n"
85
85
  + "-------------------------------------------*/\n\n"
86
+ + "/* eslint-disable @typescript-eslint/no-explicit-any */\n"
86
87
  + "const client = {\n";
87
88
  const render = renderer[type];
88
89
  if (!render) {
package/bin/index.d.ts CHANGED
@@ -5,4 +5,5 @@ import { Cookies, CookieOptions } from "./util/cookies.js";
5
5
  import { EventSourceConnection } from "./util/event-source.js";
6
6
  import { StyleClass } from './util/css.js';
7
7
  import { Endpoint } from './util/endpoint.js';
8
- export { CatchFunction, CookieOptions, Cookies, createRequestHandler, Endpoint, EventSourceConnection, GenericContext, RenderFunction, RouteContext, RouteModule, StyleClass, };
8
+ import { redirect, text, json, refresh } from './response.js';
9
+ export { CatchFunction, CookieOptions, Cookies, createRequestHandler, Endpoint, EventSourceConnection, GenericContext, RenderFunction, RouteContext, RouteModule, StyleClass, redirect, text, json, refresh };
package/bin/index.js CHANGED
@@ -4,4 +4,5 @@ import { Cookies } from "./util/cookies.js";
4
4
  import { EventSourceConnection } from "./util/event-source.js";
5
5
  import { StyleClass } from './util/css.js';
6
6
  import { Endpoint } from './util/endpoint.js';
7
- export { Cookies, createRequestHandler, Endpoint, EventSourceConnection, GenericContext, RouteContext, StyleClass, };
7
+ import { redirect, text, json, refresh } from './response.js';
8
+ export { Cookies, createRequestHandler, Endpoint, EventSourceConnection, GenericContext, RouteContext, StyleClass, redirect, text, json, refresh };
@@ -26,15 +26,7 @@ export async function Resolve(request, tree, config) {
26
26
  const fragments = x.split("/").slice(1);
27
27
  let response = await tree.resolve(fragments, ctx);
28
28
  if (response === null)
29
- response = new Response("Not Found", { status: 404, statusText: "Not Found", headers: ctx.headers });
30
- // Merge context headers
31
- if (response.headers !== ctx.headers) {
32
- for (const [key, value] of ctx.headers) {
33
- if (response.headers.has(key))
34
- continue;
35
- response.headers.set(key, value);
36
- }
37
- }
29
+ response = new Response("No Route Found", { status: 404, statusText: "Not Found", headers: ctx.headers });
38
30
  // Merge cookie changes
39
31
  const headers = Object.fromEntries(ctx.headers);
40
32
  const cookies = ctx.cookie.export();
@@ -42,5 +34,17 @@ export async function Resolve(request, tree, config) {
42
34
  headers['set-cookie'] = cookies;
43
35
  response.headers.set("Set-Cookie", cookies[0]); // Response object doesn't support multi-header..[]
44
36
  }
37
+ // Merge context headers
38
+ if (response.headers !== ctx.headers) {
39
+ for (const [key, value] of response.headers) {
40
+ if (!headers[key]) {
41
+ headers[key] = value;
42
+ continue;
43
+ }
44
+ if (!Array.isArray(headers[key]))
45
+ headers[key] = [headers[key]];
46
+ headers[key].push(value);
47
+ }
48
+ }
45
49
  return { response, headers };
46
50
  }
@@ -0,0 +1,4 @@
1
+ export declare function redirect(url: string, init?: ResponseInit): Response;
2
+ export declare function text(text: string, init?: ResponseInit): Response;
3
+ export declare function json(data: unknown, init?: ResponseInit): Response;
4
+ export declare function refresh(init?: ResponseInit): Response;
@@ -0,0 +1,33 @@
1
+ export function redirect(url, init) {
2
+ init ||= {};
3
+ init.statusText ||= "Temporary Redirect";
4
+ init.status = 307;
5
+ const res = new Response("", init);
6
+ res.headers.set("X-Caught", "true");
7
+ res.headers.set("Location", url);
8
+ return res;
9
+ }
10
+ export function text(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/plain");
16
+ return res;
17
+ }
18
+ export function json(data, init) {
19
+ init ||= {};
20
+ init.statusText ||= "ok";
21
+ init.status = 200;
22
+ const res = new Response(JSON.stringify(data), init);
23
+ res.headers.set("Content-Type", "application/json");
24
+ return res;
25
+ }
26
+ export function refresh(init) {
27
+ init ||= {};
28
+ init.statusText ||= "ok";
29
+ init.status = 200;
30
+ const res = new Response("", init);
31
+ res.headers.set("HX-Refresh", "true");
32
+ return res;
33
+ }
package/bin/router.d.ts CHANGED
@@ -26,7 +26,7 @@ export declare class RouteLeaf {
26
26
  module: RouteModule<any>;
27
27
  constructor(module: RouteModule<any>);
28
28
  resolve(ctx: GenericContext): Promise<Response | null>;
29
- error(ctx: GenericContext, e: unknown): Promise<Response | null>;
29
+ error(ctx: GenericContext, e: unknown): Promise<Response>;
30
30
  private renderWrapper;
31
31
  }
32
32
  export declare class RouteTree {
@@ -39,6 +39,7 @@ export declare class RouteTree {
39
39
  constructor(root?: boolean);
40
40
  ingest(path: string | string[], module: RouteModule<any>): void;
41
41
  resolve(fragments: string[], ctx: GenericContext): Promise<Response | null>;
42
+ private _resolve;
42
43
  private resolveIndex;
43
44
  private resolveNext;
44
45
  private resolveWild;
package/bin/router.js CHANGED
@@ -55,10 +55,8 @@ export class RouteLeaf {
55
55
  }
56
56
  async error(ctx, e) {
57
57
  if (!this.module.error)
58
- return null;
58
+ throw e;
59
59
  const res = await this.module.error(ctx, e);
60
- if (res === null)
61
- return null;
62
60
  if (res instanceof Response)
63
61
  return res;
64
62
  return ctx.render(res);
@@ -79,10 +77,7 @@ export class RouteLeaf {
79
77
  throw new Response("Method not Allowed", { status: 405, statusText: "Method not Allowed", headers: ctx.headers });
80
78
  }
81
79
  catch (e) {
82
- if (this.module.error)
83
- return await this.module.error(ctx, e);
84
- else
85
- throw e;
80
+ return await this.error(ctx, e);
86
81
  }
87
82
  return null;
88
83
  }
@@ -138,12 +133,29 @@ export class RouteTree {
138
133
  next.ingest(path, module);
139
134
  }
140
135
  async resolve(fragments, ctx) {
136
+ if (!this.slug)
137
+ return await this._resolve(fragments, ctx);
138
+ try {
139
+ return await this._resolve(fragments, ctx);
140
+ }
141
+ catch (e) {
142
+ return this.unwrap(ctx, e);
143
+ }
144
+ }
145
+ async _resolve(fragments, ctx) {
141
146
  let res = await this.resolveNative(fragments, ctx)
142
147
  || await this.resolveIndex(fragments, ctx)
143
148
  || await this.resolveNext(fragments, ctx)
144
149
  || await this.resolveWild(fragments, ctx)
145
150
  || await this.resolveSlug(fragments, ctx);
146
- return this.unwrap(ctx, res);
151
+ if (res instanceof Response) {
152
+ if (100 <= res.status && res.status <= 399)
153
+ return res;
154
+ if (res.headers.has("X-Caught"))
155
+ return res;
156
+ this.unwrap(ctx, res);
157
+ }
158
+ return res;
147
159
  }
148
160
  async resolveIndex(fragments, ctx) {
149
161
  if (fragments.length > 0)
@@ -187,29 +199,13 @@ export class RouteTree {
187
199
  return await ResolveNatively(fragments, ctx);
188
200
  }
189
201
  async unwrap(ctx, res) {
190
- if (!BadResponse(res))
191
- return res;
192
202
  if (!this.slug)
193
- return res;
194
- if (res === null)
195
- res = new Response("Not Found", { status: 404, statusText: "Not Found", headers: ctx.headers });
196
- if (res.headers.has("X-Caught"))
197
- return res;
203
+ throw res;
198
204
  const caught = await this.slug.error(ctx, res);
199
- if (!caught)
200
- return res;
201
205
  caught.headers.set("X-Caught", "true");
202
206
  return caught;
203
207
  }
204
208
  }
205
- function BadResponse(res) {
206
- if (res === null)
207
- return true;
208
- if (res.status < 200)
209
- return true;
210
- if (res.status > 299)
211
- return true;
212
- }
213
209
  async function ResolveNatively(fragments, ctx) {
214
210
  switch (fragments[1]) {
215
211
  case "dynamic": return dynamic._resolve(fragments, ctx);
package/bin/types.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { ParameterShaper } from "./util/parameters.js";
2
2
  import { RouteContext } from "./router.js";
3
- export type CatchFunction<T> = (args: T, err: unknown) => Promise<Response | JSX.Element | null>;
3
+ export type CatchFunction<T> = (args: T, err: unknown) => Promise<Response | JSX.Element>;
4
4
  export type RenderFunction<T> = (args: T) => Promise<Response | JSX.Element | null>;
5
5
  export type RouteModule<T extends ParameterShaper> = {
6
6
  parameters?: T;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "htmx-router",
3
- "version": "1.0.0-alpha.3",
3
+ "version": "1.0.0-alpha.4",
4
4
  "description": "A simple SSR framework with dynamic+client islands",
5
5
  "keywords": ["htmx", "router", ""],
6
6
  "main": "./bin/index.js",