htmx-router 0.1.3 → 1.0.0-alpha.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.
Files changed (55) hide show
  1. package/bin/cli/config.d.ts +10 -0
  2. package/bin/cli/config.js +4 -0
  3. package/bin/cli/index.js +54 -10
  4. package/bin/client/index.d.ts +4 -0
  5. package/bin/client/index.js +100 -0
  6. package/bin/client/mount.d.ts +2 -0
  7. package/bin/client/mount.js +75 -0
  8. package/bin/client/watch.d.ts +1 -0
  9. package/bin/client/watch.js +19 -0
  10. package/bin/helper.d.ts +1 -2
  11. package/bin/helper.js +25 -14
  12. package/bin/index.d.ts +8 -6
  13. package/bin/index.js +7 -16
  14. package/bin/request/http.d.ts +10 -0
  15. package/bin/request/http.js +46 -0
  16. package/bin/request/index.d.ts +16 -0
  17. package/bin/request/index.js +6 -0
  18. package/bin/request/native.d.ts +9 -0
  19. package/bin/request/native.js +56 -0
  20. package/bin/router.d.ts +41 -16
  21. package/bin/router.js +176 -236
  22. package/bin/types.d.ts +10 -0
  23. package/bin/types.js +1 -0
  24. package/bin/util/cookies.d.ts +22 -0
  25. package/bin/util/cookies.js +57 -0
  26. package/bin/util/css.d.ts +9 -0
  27. package/bin/util/css.js +47 -0
  28. package/bin/util/dynamic.d.ts +5 -0
  29. package/bin/util/dynamic.js +26 -0
  30. package/bin/util/endpoint.d.ts +9 -0
  31. package/bin/util/endpoint.js +28 -0
  32. package/bin/util/event-source.d.ts +16 -0
  33. package/bin/util/event-source.js +85 -0
  34. package/bin/util/hash.d.ts +1 -0
  35. package/bin/util/hash.js +7 -0
  36. package/bin/util/index.d.ts +1 -0
  37. package/bin/util/index.js +7 -0
  38. package/bin/util/parameters.d.ts +7 -0
  39. package/bin/util/parameters.js +14 -0
  40. package/bin/util/shell.d.ts +32 -0
  41. package/bin/util/shell.js +1 -0
  42. package/package.json +9 -7
  43. package/readme.md +149 -211
  44. package/bin/404-route.d.ts +0 -2
  45. package/bin/404-route.js +0 -8
  46. package/bin/cli/dynamic.d.ts +0 -2
  47. package/bin/cli/dynamic.js +0 -47
  48. package/bin/cli/static.d.ts +0 -2
  49. package/bin/cli/static.js +0 -49
  50. package/bin/components.d.ts +0 -6
  51. package/bin/components.js +0 -31
  52. package/bin/render-args.d.ts +0 -35
  53. package/bin/render-args.js +0 -140
  54. package/bin/shared.d.ts +0 -28
  55. package/bin/shared.js +0 -28
package/bin/router.js CHANGED
@@ -1,281 +1,221 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
1
+ import * as endpoint from './util/endpoint.js';
2
+ import * as dynamic from './util/dynamic.js';
3
+ import * as mount from './client/mount.js';
4
+ import * as css from './util/css.js';
5
+ import { Parameterize } from './util/parameters.js';
6
+ import { Cookies } from './util/cookies.js';
7
+ export class GenericContext {
8
+ request;
9
+ headers; // response headers
10
+ cookie;
11
+ params;
12
+ url;
13
+ render;
14
+ constructor(request, url, renderer) {
15
+ this.cookie = new Cookies(request.headers);
16
+ this.headers = new Headers();
17
+ this.request = request;
18
+ this.params = {};
19
+ this.url = url;
20
+ this.render = renderer;
21
+ this.headers.set("x-powered-by", "htmx-router");
22
+ }
23
+ shape(shape) {
24
+ return new RouteContext(this, shape);
7
25
  }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || function (mod) {
19
- if (mod && mod.__esModule) return mod;
20
- var result = {};
21
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
- __setModuleDefault(result, mod);
23
- return result;
24
- };
25
- Object.defineProperty(exports, "__esModule", { value: true });
26
- exports.RouteTree = exports.RouteLeaf = exports.IsAllowedExt = void 0;
27
- const shared_1 = require("./shared");
28
- const render_args_1 = require("./render-args");
29
- const BlankRoute = __importStar(require("./404-route"));
30
- function IsAllowedExt(ext) {
31
- if (ext[0] !== ".")
32
- return false;
33
- // js, jsx, tsx, ts
34
- if (ext[2] !== "s")
35
- return false;
36
- if (ext[1] !== "j" && ext[1] !== "t")
37
- return false;
38
- if (ext.length == 3)
39
- return true;
40
- if (ext.length != 4)
41
- return false;
42
- if (ext[3] !== "x")
43
- return false;
44
- return true;
45
26
  }
46
- exports.IsAllowedExt = IsAllowedExt;
47
- class RouteLeaf {
48
- constructor(module, mask) {
27
+ export class RouteContext {
28
+ request;
29
+ headers; // response headers
30
+ cookie;
31
+ params;
32
+ url;
33
+ render;
34
+ constructor(base, shape) {
35
+ this.params = Parameterize(base.params, shape);
36
+ this.cookie = base.cookie;
37
+ this.headers = base.headers;
38
+ this.request = base.request;
39
+ this.render = base.render;
40
+ this.url = base.url;
41
+ }
42
+ }
43
+ export class RouteLeaf {
44
+ module;
45
+ constructor(module) {
49
46
  this.module = module;
50
- this.mask = mask;
51
47
  }
52
- async render(args, mask, routeName) {
48
+ async resolve(ctx) {
49
+ const res = await this.renderWrapper(ctx);
50
+ if (res === null)
51
+ return null;
52
+ if (res instanceof Response)
53
+ return res;
54
+ return ctx.render(res);
55
+ }
56
+ async error(ctx, e) {
57
+ if (!this.module.error)
58
+ return null;
59
+ const res = await this.module.error(ctx, e);
60
+ if (res === null)
61
+ return null;
62
+ if (res instanceof Response)
63
+ return res;
64
+ return ctx.render(res);
65
+ }
66
+ async renderWrapper(ctx) {
53
67
  try {
54
- // Always check auth
55
- // If auth error this function will throw
56
- if (this.module.Auth)
57
- await this.module.Auth(args);
58
- if (mask === render_args_1.MaskType.show) {
59
- if (this.module.Render)
60
- return await this.module.Render(routeName, args);
61
- }
62
- else {
63
- return await args.Outlet();
68
+ if (!this.module.loader && !this.module.action)
69
+ return null;
70
+ const context = ctx.shape(this.module.parameters || {});
71
+ if (ctx.request.method === "HEAD" || ctx.request.method === "GET") {
72
+ if (this.module.loader)
73
+ return await this.module.loader(context);
74
+ else
75
+ return null;
64
76
  }
77
+ if (this.module.action)
78
+ return await this.module.action(context);
79
+ throw new Response("Method not Allowed", { status: 405, statusText: "Method not Allowed", headers: ctx.headers });
65
80
  }
66
81
  catch (e) {
67
- if (e instanceof shared_1.Redirect || e instanceof shared_1.Override)
82
+ if (this.module.error)
83
+ return await this.module.error(ctx, e);
84
+ else
68
85
  throw e;
69
- const err = (e instanceof shared_1.ErrorResponse) ? e :
70
- new shared_1.ErrorResponse(500, "Runtime Error", e);
71
- if (this.module.CatchError)
72
- return await this.module.CatchError(routeName, args, err);
73
- throw err;
74
86
  }
75
- return "";
87
+ return null;
76
88
  }
77
89
  }
78
- exports.RouteLeaf = RouteLeaf;
79
- const blankLeaf = new RouteLeaf(BlankRoute, []);
80
- class RouteTree {
81
- constructor() {
90
+ export class RouteTree {
91
+ root;
92
+ nested;
93
+ // Leaf nodes
94
+ index; // about._index
95
+ // Wild card route
96
+ slug; // $
97
+ wild; // e.g. $userID
98
+ wildCard;
99
+ constructor(root = true) {
100
+ this.root = root;
82
101
  this.nested = new Map();
83
102
  this.wildCard = "";
103
+ this.slug = null;
84
104
  this.wild = null;
85
- this.default = null;
86
- this.route = null;
105
+ this.index = null;
87
106
  }
88
- assignRoot(module) {
89
- if (!module.Render)
90
- throw new Error(`Root route is missing Render()`);
91
- if (!module.CatchError)
92
- throw new Error(`Root route is missing CatchError()`);
93
- this.route = new RouteLeaf(module, []);
94
- }
95
- ingest(path, module, override) {
96
- if (!Array.isArray(path)) {
97
- path = path.split(/[\./\\]/g);
98
- }
99
- if (path.length === 0) {
100
- override.push(false);
101
- this.route = new RouteLeaf(module, override);
107
+ ingest(path, module) {
108
+ if (!Array.isArray(path))
109
+ path = path.split("/");
110
+ if (path.length === 0 || (path.length == 1 && path[0] === "_index")) {
111
+ this.index = new RouteLeaf(module);
102
112
  return;
103
113
  }
104
- if (path.length === 1 && path[0] === "_index") {
105
- override.push(false);
106
- this.default = new RouteLeaf(module, override);
114
+ if (path[0] === "$") {
115
+ this.slug = new RouteLeaf(module);
107
116
  return;
108
117
  }
109
- if (path[0].endsWith("_")) {
110
- path[0] = path[0].slice(0, -1);
111
- override.push(true);
112
- }
113
- else {
114
- override.push(false);
115
- }
116
118
  if (path[0][0] === "$") {
117
119
  const wildCard = path[0].slice(1);
118
120
  // Check wildcard isn't being changed
119
121
  if (!this.wild) {
120
122
  this.wildCard = wildCard;
121
- this.wild = new RouteTree();
123
+ this.wild = new RouteTree(false);
122
124
  }
123
125
  else if (wildCard !== this.wildCard) {
124
126
  throw new Error(`Redefinition of wild card ${this.wildCard} to ${wildCard}`);
125
127
  }
126
128
  path.splice(0, 1);
127
- this.wild.ingest(path, module, override);
129
+ this.wild.ingest(path, module);
128
130
  return;
129
131
  }
130
132
  let next = this.nested.get(path[0]);
131
133
  if (!next) {
132
- next = new RouteTree();
134
+ next = new RouteTree(false);
133
135
  this.nested.set(path[0], next);
134
136
  }
135
137
  path.splice(0, 1);
136
- next.ingest(path, module, override);
138
+ next.ingest(path, module);
137
139
  }
138
- calculateDepth(from, to) {
139
- let depth = 0;
140
- if (from.length == 0 || to.length == 0) {
141
- depth = 1;
142
- }
143
- else {
144
- const segmentA = from.splice(0, 1)[0];
145
- const segmentB = to.splice(0, 1)[0];
146
- const subRoute = this.nested.get(segmentA);
147
- if (subRoute && segmentA === segmentB) {
148
- depth = subRoute.calculateDepth(from, to);
149
- }
150
- else if (this.wild) {
151
- depth = this.wild.calculateDepth(from, to);
152
- }
153
- else {
154
- return 1;
155
- }
156
- }
157
- depth++;
158
- return depth;
140
+ async resolve(fragments, ctx) {
141
+ let res = await this.resolveNative(fragments, ctx)
142
+ || await this.resolveIndex(fragments, ctx)
143
+ || await this.resolveNext(fragments, ctx)
144
+ || await this.resolveWild(fragments, ctx)
145
+ || await this.resolveSlug(fragments, ctx);
146
+ return this.unwrap(ctx, res);
159
147
  }
160
- async render(req, res, url) {
161
- var _a;
162
- if (url.pathname.length != 1 && url.pathname.endsWith("/")) {
163
- return new shared_1.Redirect(url.pathname.slice(0, -1) + url.search);
164
- }
165
- const args = new render_args_1.RenderArgs(req, res, url);
166
- res.setHeader('Vary', "hx-current-url");
167
- const from = req.headers['hx-current-url'] ?
168
- new URL(((_a = req.headers['hx-current-url']) === null || _a === void 0 ? void 0 : _a.toString()) || "/").pathname :
169
- "";
170
- try {
171
- const depth = BuildOutlet(this, args, from);
172
- if (from) {
173
- res.setHeader('HX-Push-Url', req.url || "/");
174
- if (depth > 0) {
175
- res.setHeader('HX-Retarget', `#hx-route-${depth.toString(16)}`);
176
- }
177
- res.setHeader('HX-Reswap', "outerHTML");
178
- }
179
- const out = await args.Outlet();
180
- if (args.title) {
181
- const trigger = res.getHeader('HX-Trigger');
182
- const entry = `{"setTitle":"${encodeURIComponent(args.title)}"}`;
183
- if (Array.isArray(trigger)) {
184
- res.setHeader('HX-Trigger', [...trigger, entry]);
185
- }
186
- else if (trigger) {
187
- res.setHeader('HX-Trigger', [trigger.toString(), entry]);
188
- }
189
- else {
190
- res.setHeader('HX-Trigger', [entry]);
191
- }
192
- }
193
- return out;
194
- }
195
- catch (e) {
196
- if (e instanceof shared_1.Redirect)
197
- return e;
198
- if (e instanceof shared_1.Override)
199
- return e;
200
- console.error(e);
201
- throw new Error(`Unhandled boil up type ${typeof (e)}: ${e}`);
202
- }
203
- ;
148
+ async resolveIndex(fragments, ctx) {
149
+ if (fragments.length > 0)
150
+ return null;
151
+ if (!this.index)
152
+ return null;
153
+ return await this.index.resolve(ctx);
204
154
  }
205
- }
206
- exports.RouteTree = RouteTree;
207
- function BuildOutlet(start, args, fromPath) {
208
- const frags = args.url.pathname.split('/').slice(1);
209
- if (frags.length === 1 && frags[0] === "") {
210
- frags.splice(0, 1);
155
+ async resolveNext(fragments, ctx) {
156
+ if (fragments.length < 1)
157
+ return null;
158
+ const next = this.nested.get(fragments[0]);
159
+ if (!next)
160
+ return null;
161
+ return await next.resolve(fragments.slice(1), ctx);
211
162
  }
212
- const from = fromPath.split('/').slice(1);
213
- if (from.length === 1 && from[0] === "") {
214
- from.splice(0, 1);
163
+ async resolveWild(fragments, ctx) {
164
+ if (!this.wild)
165
+ return null;
166
+ if (fragments.length < 1)
167
+ return null;
168
+ ctx.params[this.wildCard] = fragments[0];
169
+ return this.wild.resolve(fragments.slice(1), ctx);
215
170
  }
216
- let matching = fromPath.length > 0;
217
- let depth = -1;
218
- const stack = [start];
219
- let mask = null;
220
- while (stack.length > 0) {
221
- const cursor = stack.pop();
222
- if (!mask) {
223
- stack.push(cursor);
224
- if (frags.length === 0) {
225
- if (matching && from.length !== 0) {
226
- depth = args._outletChain.length + stack.length;
227
- matching = false;
228
- }
229
- ;
230
- if (cursor.default) {
231
- args._addOutlet(cursor.default);
232
- mask = cursor.default.mask;
233
- }
234
- else {
235
- args._addOutlet(blankLeaf);
236
- mask = [];
237
- }
238
- }
239
- else {
240
- if (matching && from.length === 0) {
241
- depth = args._outletChain.length + stack.length;
242
- matching = false;
243
- }
244
- const segment = frags.splice(0, 1)[0];
245
- const other = from.splice(0, 1)[0];
246
- const subRoute = cursor.nested.get(segment);
247
- if (subRoute) {
248
- if (matching && segment !== other) {
249
- depth = args._outletChain.length + stack.length;
250
- matching = false;
251
- }
252
- ;
253
- stack.push(subRoute);
254
- }
255
- else if (cursor.wild) {
256
- if (matching && cursor.nested.has(other)) {
257
- depth = args._outletChain.length + stack.length;
258
- matching = false;
259
- }
260
- ;
261
- args.params[cursor.wildCard] = segment;
262
- stack.push(cursor.wild);
263
- }
264
- else {
265
- args._addOutlet(blankLeaf);
266
- mask = [];
267
- }
268
- }
269
- }
270
- else {
271
- if (cursor.route) {
272
- args._addOutlet(cursor.route);
273
- }
274
- }
171
+ async resolveSlug(fragments, ctx) {
172
+ if (!this.slug)
173
+ return null;
174
+ ctx.params["$"] = fragments.join("/");
175
+ const res = this.slug.resolve
176
+ ? await this.slug.resolve(ctx)
177
+ : null;
178
+ return res;
179
+ }
180
+ async resolveNative(fragments, ctx) {
181
+ if (!this.root)
182
+ return null;
183
+ if (fragments.length < 2)
184
+ return null;
185
+ if (fragments[0] != "_")
186
+ return null;
187
+ return await ResolveNatively(fragments, ctx);
275
188
  }
276
- if (matching) {
277
- depth = args._outletChain.length - 1;
189
+ async unwrap(ctx, res) {
190
+ if (!BadResponse(res))
191
+ return res;
192
+ 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;
198
+ const caught = await this.slug.error(ctx, res);
199
+ if (!caught)
200
+ return res;
201
+ caught.headers.set("X-Caught", "true");
202
+ return caught;
203
+ }
204
+ }
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
+ async function ResolveNatively(fragments, ctx) {
214
+ switch (fragments[1]) {
215
+ case "dynamic": return dynamic._resolve(fragments, ctx);
216
+ case "endpoint": return endpoint._resolve(fragments, ctx);
217
+ case "mount": return mount._resolve(fragments);
218
+ case "style": return css._resolve(fragments);
278
219
  }
279
- args._applyMask(mask, depth);
280
- return depth;
220
+ return null;
281
221
  }
package/bin/types.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ import { ParameterShaper } from "./util/parameters.js";
2
+ import { RouteContext } from "./router.js";
3
+ export type CatchFunction<T> = (args: T, err: unknown) => Promise<Response | JSX.Element | null>;
4
+ export type RenderFunction<T> = (args: T) => Promise<Response | JSX.Element | null>;
5
+ export type RouteModule<T extends ParameterShaper> = {
6
+ parameters?: T;
7
+ loader?: RenderFunction<RouteContext<T>>;
8
+ action?: RenderFunction<RouteContext<T>>;
9
+ error?: CatchFunction<RouteContext<T>>;
10
+ };
package/bin/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,22 @@
1
+ export interface CookieOptions {
2
+ domain?: string | undefined;
3
+ expires?: Date;
4
+ httpOnly?: boolean;
5
+ maxAge?: number;
6
+ partitioned?: boolean;
7
+ path?: string;
8
+ priority?: "low" | "medium" | "high";
9
+ sameSite?: "lax" | "strict" | "none";
10
+ secure?: boolean;
11
+ }
12
+ export declare class Cookies {
13
+ private map;
14
+ private config;
15
+ constructor(headers: Headers);
16
+ get(name: string): string | null;
17
+ has(name: string): boolean;
18
+ set(name: string, value: string, options?: CookieOptions): void;
19
+ flash(name: string, value: string): void;
20
+ unset(name: string): void;
21
+ export(): string[];
22
+ }
@@ -0,0 +1,57 @@
1
+ export class Cookies {
2
+ map;
3
+ config;
4
+ constructor(headers) {
5
+ this.config = {};
6
+ this.map = {};
7
+ const cookie = headers.get("Cookie");
8
+ if (!cookie)
9
+ return;
10
+ for (const line of cookie.split("; ")) {
11
+ const [name, value] = line.split("=");
12
+ this.map[name] = value;
13
+ }
14
+ }
15
+ get(name) {
16
+ return this.map[name] || null;
17
+ }
18
+ has(name) {
19
+ return name in this.map;
20
+ }
21
+ set(name, value, options = {}) {
22
+ if (!options['path'])
23
+ options['path'] = "/";
24
+ this.config[name] = options;
25
+ this.map[name] = value;
26
+ }
27
+ flash(name, value) {
28
+ return this.set(name, value, { maxAge: 0 });
29
+ }
30
+ unset(name) {
31
+ return this.set(name, "", { maxAge: 0 });
32
+ }
33
+ export() {
34
+ const headers = new Array();
35
+ for (const name in this.config) {
36
+ let config = "";
37
+ for (const opt in this.config[name]) {
38
+ const prop = opt === "maxAge"
39
+ ? "Max-Age"
40
+ : opt[0].toUpperCase() + opt.slice(1);
41
+ const raw = this.config[name][opt];
42
+ if (raw === true) {
43
+ config += `; ${prop}`;
44
+ continue;
45
+ }
46
+ if (raw === false)
47
+ continue;
48
+ let value = String(raw);
49
+ value = value[0].toUpperCase() + value.slice(1);
50
+ config += `; ${prop}=${value}`;
51
+ }
52
+ const cookie = name + "=" + this.map[name] + config + ";";
53
+ headers.push(cookie);
54
+ }
55
+ return headers;
56
+ }
57
+ }
@@ -0,0 +1,9 @@
1
+ export declare class StyleClass {
2
+ readonly name: string;
3
+ readonly style: string;
4
+ readonly hash: string;
5
+ constructor(name: string, style: string);
6
+ toString(): string;
7
+ }
8
+ export declare function GetSheetUrl(): string;
9
+ export declare function _resolve(fragments: string[]): Response | null;
@@ -0,0 +1,47 @@
1
+ import { QuickHash } from "../util/hash.js";
2
+ const classNamePattern = /^[a-zA-Z_][a-zA-Z0-9_-]*$/;
3
+ const registry = new Array();
4
+ let cache = null;
5
+ export class StyleClass {
6
+ name;
7
+ style;
8
+ hash;
9
+ constructor(name, style) {
10
+ if (!name.match(classNamePattern))
11
+ throw new Error("Cannot use given name for CSS class");
12
+ this.hash = QuickHash(style);
13
+ this.name = `${name}-${this.hash}`;
14
+ style = style.replaceAll(".this", "." + this.name);
15
+ this.style = style;
16
+ registry.push(this);
17
+ cache = null;
18
+ }
19
+ toString() {
20
+ return this.name;
21
+ }
22
+ }
23
+ function GetSheet() {
24
+ return cache || BuildSheet();
25
+ }
26
+ export function GetSheetUrl() {
27
+ const sheet = GetSheet();
28
+ return `/_/style/${sheet.hash}.css`;
29
+ }
30
+ export function _resolve(fragments) {
31
+ if (!fragments[2])
32
+ return null;
33
+ const build = GetSheet();
34
+ if (!fragments[2].startsWith(build.hash))
35
+ return null;
36
+ const headers = new Headers();
37
+ headers.set("Content-Type", "text/css");
38
+ headers.set("Cache-Control", "public, max-age=604800");
39
+ return new Response(build.sheet, { headers });
40
+ }
41
+ function BuildSheet() {
42
+ const key = registry.map(x => x.hash).join("");
43
+ const hash = QuickHash(key);
44
+ const sheet = registry.map(x => x.style).join("");
45
+ cache = { hash, sheet };
46
+ return cache;
47
+ }
@@ -0,0 +1,5 @@
1
+ import { GenericContext } from "../router.js";
2
+ export declare function RegisterDynamic<T>(load: Loader<T>): string;
3
+ type Loader<T> = (params: T, ctx: GenericContext) => Promise<JSX.Element>;
4
+ export declare function _resolve(fragments: string[], ctx: GenericContext): Promise<Response | null>;
5
+ export {};
@@ -0,0 +1,26 @@
1
+ import { QuickHash } from "../util/hash.js";
2
+ const registry = new Map();
3
+ const index = new Map();
4
+ export function RegisterDynamic(load) {
5
+ const existing = index.get(load);
6
+ if (existing)
7
+ return existing;
8
+ const hash = QuickHash(String(load));
9
+ const name = `${encodeURIComponent(load.name)}-${hash}`;
10
+ registry.set(name, load);
11
+ const url = `/_/dynamic/${name}?`;
12
+ index.set(load, url);
13
+ return url;
14
+ }
15
+ export async function _resolve(fragments, ctx) {
16
+ if (!fragments[2])
17
+ return null;
18
+ const endpoint = registry.get(fragments[2]);
19
+ if (!endpoint)
20
+ return null;
21
+ const props = {};
22
+ for (const [key, value] of ctx.url.searchParams)
23
+ props[key] = value;
24
+ ctx.headers.set("X-Partial", "true");
25
+ return ctx.render(await endpoint(props, ctx));
26
+ }
@@ -0,0 +1,9 @@
1
+ import { RenderFunction } from "../types.js";
2
+ import { GenericContext } from "../router.js";
3
+ export declare class Endpoint {
4
+ readonly render: RenderFunction<GenericContext>;
5
+ readonly name: string;
6
+ readonly url: string;
7
+ constructor(render: RenderFunction<GenericContext>, name?: string);
8
+ }
9
+ export declare function _resolve(fragments: string[], ctx: GenericContext): Promise<Response | null>;