htmx-router
Advanced tools
Comparing version 0.0.5 to 0.0.6
@@ -42,3 +42,3 @@ #!/usr/bin/env node | ||
script += "\tconst url = relative(ctx, file.slice(0, file.lastIndexOf(\".\")).replace(/\\\\/g, \"/\"));\n"; | ||
script += `\timport(file).then((mod) => Router.ingest(url, mod, []));\n`; | ||
script += `\timport(file).then((mod) => Router.ingest(url, mod, [false]));\n`; | ||
script += "}\n"; | ||
@@ -45,0 +45,0 @@ (0, fs_1.writeFileSync)(`${cwd}/router.ts`, script); |
@@ -43,3 +43,3 @@ #!/usr/bin/env node | ||
const file = files[i]; | ||
script += `Router.ingest("${file.slice(DIR.length - 1)}", Route${i}, []);\n`; | ||
script += `Router.ingest("${file.slice(DIR.length - 1)}", Route${i}, [false]);\n`; | ||
} | ||
@@ -46,0 +46,0 @@ script += `Router.assignRoot(RootRoute);\n`; |
@@ -0,4 +1,6 @@ | ||
import { ErrorResponse, Redirect, Outlet, Override } from "./shared"; | ||
import { RouteTree, IsAllowedExt } from "./router"; | ||
import { ErrorResponse, Redirect, Outlet, Override, RenderArgs } from "./shared"; | ||
import { RenderArgs } from "./render-args"; | ||
import { Link } from "./components"; | ||
import { StyleCSS } from "./helper"; | ||
export { IsAllowedExt, RouteTree, ErrorResponse, Redirect, Override, RenderArgs, Outlet, StyleCSS }; | ||
export { IsAllowedExt, RouteTree, ErrorResponse, Redirect, Override, RenderArgs, Outlet, StyleCSS, Link }; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.StyleCSS = exports.RenderArgs = exports.Override = exports.Redirect = exports.ErrorResponse = exports.RouteTree = exports.IsAllowedExt = void 0; | ||
const router_1 = require("./router"); | ||
Object.defineProperty(exports, "RouteTree", { enumerable: true, get: function () { return router_1.RouteTree; } }); | ||
Object.defineProperty(exports, "IsAllowedExt", { enumerable: true, get: function () { return router_1.IsAllowedExt; } }); | ||
exports.Link = exports.StyleCSS = exports.RenderArgs = exports.Override = exports.Redirect = exports.ErrorResponse = exports.RouteTree = exports.IsAllowedExt = void 0; | ||
const shared_1 = require("./shared"); | ||
@@ -11,4 +8,10 @@ Object.defineProperty(exports, "ErrorResponse", { enumerable: true, get: function () { return shared_1.ErrorResponse; } }); | ||
Object.defineProperty(exports, "Override", { enumerable: true, get: function () { return shared_1.Override; } }); | ||
Object.defineProperty(exports, "RenderArgs", { enumerable: true, get: function () { return shared_1.RenderArgs; } }); | ||
const router_1 = require("./router"); | ||
Object.defineProperty(exports, "RouteTree", { enumerable: true, get: function () { return router_1.RouteTree; } }); | ||
Object.defineProperty(exports, "IsAllowedExt", { enumerable: true, get: function () { return router_1.IsAllowedExt; } }); | ||
const render_args_1 = require("./render-args"); | ||
Object.defineProperty(exports, "RenderArgs", { enumerable: true, get: function () { return render_args_1.RenderArgs; } }); | ||
const components_1 = require("./components"); | ||
Object.defineProperty(exports, "Link", { enumerable: true, get: function () { return components_1.Link; } }); | ||
const helper_1 = require("./helper"); | ||
Object.defineProperty(exports, "StyleCSS", { enumerable: true, get: function () { return helper_1.StyleCSS; } }); |
/// <reference types="node" /> | ||
import type http from "node:http"; | ||
import { Outlet, Override, Redirect, RenderArgs, RouteModule } from "./shared"; | ||
import { Override, Redirect, RouteModule } from "./shared"; | ||
import { MaskType, RenderArgs } from "./render-args"; | ||
export declare function IsAllowedExt(ext: string): boolean; | ||
declare class RouteLeaf { | ||
export declare class RouteLeaf { | ||
module: RouteModule; | ||
mask: boolean[]; | ||
constructor(module: RouteModule, mask: boolean[]); | ||
makeOutlet(args: RenderArgs, outlet: Outlet, depth: number): Outlet; | ||
render(args: RenderArgs, mask: MaskType, routeName: string): Promise<string>; | ||
} | ||
@@ -20,5 +21,4 @@ export declare class RouteTree { | ||
ingest(path: string | string[], module: RouteModule, override: boolean[]): void; | ||
calculateDepth(from: string[], to: string[]): number; | ||
render(req: http.IncomingMessage, res: http.ServerResponse, url: URL): Promise<string | Redirect | Override>; | ||
private _recursiveRender; | ||
} | ||
export {}; |
"use strict"; | ||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
var desc = Object.getOwnPropertyDescriptor(m, k); | ||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { | ||
desc = { enumerable: true, get: function() { return m[k]; } }; | ||
} | ||
Object.defineProperty(o, k2, desc); | ||
}) : (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
o[k2] = m[k]; | ||
})); | ||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { | ||
Object.defineProperty(o, "default", { enumerable: true, value: v }); | ||
}) : function(o, v) { | ||
o["default"] = v; | ||
}); | ||
var __importStar = (this && this.__importStar) || function (mod) { | ||
if (mod && mod.__esModule) return mod; | ||
var result = {}; | ||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); | ||
__setModuleDefault(result, mod); | ||
return result; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.RouteTree = exports.IsAllowedExt = void 0; | ||
exports.RouteTree = exports.RouteLeaf = exports.IsAllowedExt = void 0; | ||
const shared_1 = require("./shared"); | ||
const render_args_1 = require("./render-args"); | ||
const BlankRoute = __importStar(require("./404-route")); | ||
function IsAllowedExt(ext) { | ||
@@ -22,5 +47,2 @@ if (ext[0] !== ".") | ||
exports.IsAllowedExt = IsAllowedExt; | ||
async function blankOutlet() { | ||
return ""; | ||
} | ||
class RouteLeaf { | ||
@@ -31,24 +53,28 @@ constructor(module, mask) { | ||
} | ||
makeOutlet(args, outlet, depth) { | ||
const renderer = this.module.Render || blankOutlet; | ||
const catcher = this.module.CatchError; | ||
return async () => { | ||
try { | ||
args.depth = depth; | ||
return await renderer(args, outlet); | ||
async render(args, mask, routeName) { | ||
try { | ||
if (this.module.Auth) | ||
return await this.module.Auth(args); | ||
if (mask === render_args_1.MaskType.show) { | ||
if (this.module.Render) | ||
return await this.module.Render(routeName, args); | ||
} | ||
catch (e) { | ||
if (e instanceof shared_1.Redirect || e instanceof shared_1.Override) | ||
throw e; | ||
const err = (e instanceof shared_1.ErrorResponse) ? e : | ||
new shared_1.ErrorResponse(500, "Runtime Error", e); | ||
if (catcher) { | ||
args.depth = depth; | ||
return await catcher(args, err); | ||
} | ||
throw err; | ||
else { | ||
return await args.Outlet(); | ||
} | ||
}; | ||
} | ||
catch (e) { | ||
if (e instanceof shared_1.Redirect || e instanceof shared_1.Override) | ||
throw e; | ||
const err = (e instanceof shared_1.ErrorResponse) ? e : | ||
new shared_1.ErrorResponse(500, "Runtime Error", e); | ||
if (this.module.CatchError) | ||
return await this.module.CatchError(routeName, args, err); | ||
throw err; | ||
} | ||
return ""; | ||
} | ||
} | ||
exports.RouteLeaf = RouteLeaf; | ||
const blankLeaf = new RouteLeaf(BlankRoute, []); | ||
class RouteTree { | ||
@@ -110,14 +136,43 @@ constructor() { | ||
} | ||
calculateDepth(from, to) { | ||
let depth = 0; | ||
if (from.length == 0 || to.length == 0) { | ||
depth = 1; | ||
} | ||
else { | ||
const segmentA = from.splice(0, 1)[0]; | ||
const segmentB = to.splice(0, 1)[0]; | ||
const subRoute = this.nested.get(segmentA); | ||
if (subRoute && segmentA === segmentB) { | ||
depth = subRoute.calculateDepth(from, to); | ||
} | ||
else if (this.wild) { | ||
depth = this.wild.calculateDepth(from, to); | ||
} | ||
else { | ||
return 1; | ||
} | ||
} | ||
depth++; | ||
return depth; | ||
} | ||
async render(req, res, url) { | ||
const args = new shared_1.RenderArgs(req, res, url); | ||
if (!this.default || !this.default.module.Render) { | ||
return ""; | ||
var _a; | ||
if (url.pathname.length != 1 && url.pathname.endsWith("/")) { | ||
return new shared_1.Redirect(url.pathname.slice(0, -1) + url.search); | ||
} | ||
const frags = url.pathname.split('/').slice(1); | ||
if (frags.length === 1 && frags[0] === "") { | ||
frags.splice(0, 1); | ||
} | ||
const args = new render_args_1.RenderArgs(req, res, url); | ||
const from = req.headers['hx-headless'] ? | ||
new URL(((_a = req.headers['hx-current-url']) === null || _a === void 0 ? void 0 : _a.toString()) || "/").pathname : | ||
""; | ||
try { | ||
const out = await this._recursiveRender(args, frags).outlet(); | ||
return out; | ||
const depth = BuildOutlet(this, args, from); | ||
if (from) { | ||
res.setHeader('HX-Replace-Url', req.url || "/"); | ||
if (depth > 0) { | ||
res.setHeader('HX-Retarget', `#hx-route-${depth.toString(16)}`); | ||
} | ||
res.setHeader('HX-Reswap', "outerHTML"); | ||
} | ||
return await args.Outlet(); | ||
} | ||
@@ -129,2 +184,3 @@ catch (e) { | ||
return e; | ||
console.error(e); | ||
throw new Error(`Unhandled boil up type ${typeof (e)}: ${e}`); | ||
@@ -134,43 +190,78 @@ } | ||
} | ||
_recursiveRender(args, frags) { | ||
var _a; | ||
let out = { | ||
outlet: blankOutlet, | ||
mask: [], | ||
}; | ||
if (frags.length == 0) { | ||
if (!this.default) { | ||
out.outlet = () => { | ||
throw new shared_1.ErrorResponse(404, "Resource Not Found", `Unable to find ${args.url.pathname}`); | ||
}; | ||
} | ||
exports.RouteTree = RouteTree; | ||
function BuildOutlet(start, args, fromPath) { | ||
const frags = args.url.pathname.split('/').slice(1); | ||
if (frags.length === 1 && frags[0] === "") { | ||
frags.splice(0, 1); | ||
} | ||
const from = fromPath.split('/').slice(1); | ||
if (from.length === 1 && from[0] === "") { | ||
from.splice(0, 1); | ||
} | ||
let matching = fromPath.length > 0; | ||
let depth = -1; | ||
const stack = [start]; | ||
let mask = null; | ||
while (stack.length > 0) { | ||
const cursor = stack.pop(); | ||
if (!mask) { | ||
stack.push(cursor); | ||
if (frags.length === 0) { | ||
if (matching && from.length !== 0) { | ||
depth = args._outletChain.length + stack.length; | ||
matching = false; | ||
} | ||
; | ||
if (cursor.default) { | ||
args._addOutlet(cursor.default); | ||
mask = cursor.default.mask; | ||
} | ||
else { | ||
args._addOutlet(blankLeaf); | ||
mask = []; | ||
} | ||
} | ||
else if ((_a = this.default) === null || _a === void 0 ? void 0 : _a.module.Render) { | ||
out.mask = [...this.default.mask]; | ||
out.outlet = this.default.makeOutlet(args, out.outlet, out.mask.length); | ||
else { | ||
if (matching && from.length === 0) { | ||
depth = args._outletChain.length + stack.length; | ||
matching = false; | ||
} | ||
const segment = frags.splice(0, 1)[0]; | ||
const other = from.splice(0, 1)[0]; | ||
const subRoute = cursor.nested.get(segment); | ||
if (subRoute) { | ||
if (matching && segment !== other) { | ||
depth = args._outletChain.length + stack.length; | ||
matching = false; | ||
} | ||
; | ||
stack.push(subRoute); | ||
} | ||
else if (cursor.wild) { | ||
if (matching && cursor.nested.has(other)) { | ||
depth = args._outletChain.length + stack.length; | ||
matching = false; | ||
} | ||
; | ||
args.params[cursor.wildCard] = segment; | ||
stack.push(cursor.wild); | ||
} | ||
else { | ||
args._addOutlet(blankLeaf); | ||
mask = []; | ||
} | ||
} | ||
} | ||
else { | ||
const segment = frags.splice(0, 1)[0]; | ||
const subRoute = this.nested.get(segment); | ||
if (subRoute) { | ||
out = subRoute._recursiveRender(args, frags); | ||
if (cursor.route) { | ||
args._addOutlet(cursor.route); | ||
} | ||
else if (this.wild) { | ||
args.params[this.wildCard] = segment; | ||
out = this.wild._recursiveRender(args, frags); | ||
} | ||
else { | ||
out.outlet = () => { | ||
throw new shared_1.ErrorResponse(404, "Resource Not Found", `Unable to find ${args.url.pathname}`); | ||
}; | ||
} | ||
} | ||
// Is this route masked out? | ||
const ignored = out.mask.splice(0, 1)[0] === true; | ||
if (!ignored && this.route) { | ||
out.outlet = this.route.makeOutlet(args, out.outlet, out.mask.length); | ||
} | ||
return out; | ||
} | ||
if (matching) { | ||
depth = args._outletChain.length; | ||
} | ||
args._applyMask(mask, depth); | ||
return depth; | ||
} | ||
exports.RouteTree = RouteTree; |
/// <reference types="node" /> | ||
/// <reference types="node" /> | ||
import type http from "node:http"; | ||
import { RenderArgs } from "./render-args"; | ||
export type Outlet = () => Promise<string>; | ||
export type RenderFunction = (args: RenderArgs, Outlet: Outlet) => Promise<string>; | ||
export type CatchFunction = (args: RenderArgs, err: ErrorResponse) => Promise<string>; | ||
export type CatchFunction = (routeName: string, args: RenderArgs, err: ErrorResponse) => Promise<string>; | ||
export type RenderFunction = (routeName: string, args: RenderArgs) => Promise<string>; | ||
export type AuthFunction = (args: RenderArgs) => Promise<string>; | ||
export type RouteModule = { | ||
Render?: RenderFunction; | ||
CatchError?: CatchFunction; | ||
Auth?: AuthFunction; | ||
}; | ||
@@ -26,18 +29,1 @@ export declare class ErrorResponse { | ||
} | ||
type MetaHTML = { | ||
[key: string]: string; | ||
}; | ||
export declare class RenderArgs { | ||
req: http.IncomingMessage; | ||
res: http.ServerResponse; | ||
params: MetaHTML; | ||
depth: number; | ||
url: URL; | ||
links: MetaHTML[]; | ||
meta: MetaHTML[]; | ||
constructor(req: http.IncomingMessage, res: http.ServerResponse, url: URL); | ||
addLinks(links: MetaHTML[], override?: boolean): void; | ||
addMeta(links: MetaHTML[], override?: boolean): void; | ||
renderHeadHTML(): string; | ||
} | ||
export {}; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.RenderArgs = exports.Override = exports.Redirect = exports.ErrorResponse = void 0; | ||
exports.Override = exports.Redirect = exports.ErrorResponse = void 0; | ||
class ErrorResponse { | ||
@@ -29,66 +29,1 @@ constructor(statusCode, statusMessage, data) { | ||
exports.Override = Override; | ||
const attrRegex = /^[A-z][A-z\-0-9]+$/; | ||
function ValidateMetaHTML(val) { | ||
for (const key in val) { | ||
if (!attrRegex.test(key)) | ||
return false; | ||
} | ||
return true; | ||
} | ||
function ValidateMetaHTMLs(val) { | ||
for (const meta of val) { | ||
if (!ValidateMetaHTML(meta)) | ||
return false; | ||
} | ||
return true; | ||
} | ||
class RenderArgs { | ||
constructor(req, res, url) { | ||
this.req = req; | ||
this.res = res; | ||
this.url = url; | ||
this.params = {}; | ||
this.depth = -1; | ||
this.links = []; | ||
this.meta = []; | ||
} | ||
addLinks(links, override = false) { | ||
if (!ValidateMetaHTMLs(links)) | ||
throw new Error(`Provided links have invalid attribute`); | ||
if (override) { | ||
this.links = links; | ||
} | ||
else { | ||
this.links.push(...links); | ||
} | ||
} | ||
addMeta(links, override = false) { | ||
if (!ValidateMetaHTMLs(links)) | ||
throw new Error(`Provided links have invalid attribute`); | ||
if (override) { | ||
this.meta = links; | ||
} | ||
else { | ||
this.meta.push(...links); | ||
} | ||
} | ||
renderHeadHTML() { | ||
let out = ""; | ||
for (const elm of this.links) { | ||
out += "<link"; | ||
for (const attr in elm) { | ||
out += ` ${attr}="${elm[attr].replace(/"/g, "\\\"")}"`; | ||
} | ||
out += "></link>"; | ||
} | ||
for (const elm of this.meta) { | ||
out += "<meta"; | ||
for (const attr in elm) { | ||
out += ` ${attr}="${elm[attr].replace(/"/g, "\\\"")}"`; | ||
} | ||
out += "></meta>"; | ||
} | ||
return out; | ||
} | ||
} | ||
exports.RenderArgs = RenderArgs; |
{ | ||
"name": "htmx-router", | ||
"version": "0.0.5", | ||
"version": "0.0.6", | ||
"description": "A remix.js style file path router for htmX websites", | ||
@@ -28,4 +28,5 @@ "main": "./bin/index.js", | ||
"dependencies": { | ||
"csstype": "^3.1.2" | ||
"csstype": "^3.1.2", | ||
"typed-html": "^3.0.1" | ||
} | ||
} |
25205
22
678
2
+ Addedtyped-html@^3.0.1
+ Addedtyped-html@3.0.1(transitive)