jetpath
Version:
A performance-first cross-runtime API framework without the boilerplate
177 lines (176 loc) • 38.9 kB
JavaScript
import { _jet_middleware, _JetPath_paths, _JetPath_paths_trie, assignMiddleware, codeGen, compileAPI, compileUI, corsMiddleware, fs, getHandlers, getHandlersEdge, getLocalIP, server, } from "./primitives/functions.js";
import { JetPlugin, LOG } from "./primitives/classes.js";
export class Jetpath {
server = { listen: () => { }, edge: false };
listening = false;
/**
* an object you can set values to per request
*/
plugins = [];
options = {
port: 8080,
apiDoc: { display: "UI" },
cors: false,
strictMode: "OFF",
source: ".",
};
plugs = [];
constructor(options = {}) {
// ? setting up app configs
if (this.options.cors === true) {
corsMiddleware({
exposeHeaders: [],
allowMethods: ["DELETE", "GET", "HEAD", "PATCH", "POST", "PUT"],
origin: ["*"],
allowHeaders: ["*"],
maxAge: "86400",
keepHeadersOnError: true,
...(typeof options?.cors === "object" ? options.cors : {}),
});
}
//? setting up default values
Object.assign(this.options, options);
//?
if (!this.options.port)
this.options.port = 8080;
}
derivePlugins(...plugins) {
if (this.listening) {
throw new Error("Your app is listening new plugins can't be added.");
}
plugins.forEach((plugin) => {
if (typeof plugin.executor === "function" || typeof plugin.name === "string") {
// ? add plugin to the server
this.plugs.push(new JetPlugin(plugin));
}
else {
throw new Error("Plugin executor and name is required");
}
});
return this;
}
async listen() {
// ? {-view-} here is replaced at build time to html
let UI = `<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>{NAME} API</title><link rel="shortcut icon" href="https://raw.githubusercontent.com/codedynasty-dev/jetpath/main/icon.png" type="image/png" /><link rel="stylesheet"href="https://fonts.googleapis.com/css2?family=Google+Sans:wght@400;500;700&family=Roboto:wght@300;400;500&display=swap"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/pretty-print-json@3.0/dist/css/pretty-print-json.css"><script src="https://cdn.jsdelivr.net/npm/pretty-print-json@3.0/dist/pretty-print-json.min.js"></script><link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" /><script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" defer></script><script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" defer></script><script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" defer></script><script src="https://cdn.jsdelivr.net/gh/adamvleggett/drawdown/drawdown.js"></script><style>:root {--primary-color: JETPATHCOLOR;--primary-light: JETPATHCOLOR36;--primary-lighter: JETPATHCOLOR2e;--primary-dark: JETPATHCOLORb6;--primary-border: JETPATHCOLOR5e;--text-primary: #202124;--text-secondary: #5f6368;--surface-1: #ffffff;--surface-2: #f8f9fa;--surface-3: #f1f3f4;--border-color: #dadce0;--error-color: #d93025;--success-color: #1e8e3e;--shadow-1: 0 1px 2px 0 rgba(60, 64, 67, 0.3), 0 1px 3px 1px rgba(60, 64, 67, 0.15);--radius-sm: 4px;--radius-md: 8px;--font-family: 'Google Sans', 'Roboto', -apple-system, BlinkMacSystemFont, sans-serif;}body {font-family: var(--font-family);color: var(--text-primary);background-color: var(--surface-2);line-height: 1.5;font-size: 14px;-webkit-font-smoothing: antialiased;}::-webkit-scrollbar {width: 8px;height: 8px;}::-webkit-scrollbar-thumb {background-color: #c1c1c1;border-radius: 8px;}::-webkit-scrollbar-thumb:hover {background-color: #a8a8a8;}.container-fluid {padding-left: 25px;padding-right: 25px;}header {border-bottom: 1px solid var(--border-color);position: sticky;top: 0;z-index: 1020;box-shadow: var(--shadow-1);background-color: var(--primary-color) ;color: #fff ;margin-bottom: 20px;display: flex;align-items: center;padding: 0.8rem 1.5rem;}.header-content {display: flex;align-items: center;gap: 16px;width: 100%;}.header-content h1 {font-size: 20px;line-height: 1.2;color: #fff;margin-bottom: 0;}.project-info {background-color: var(--surface-1);border-radius: var(--radius-md);padding: 15px 20px;margin-bottom: 20px;box-shadow: var(--shadow-1);border: 1px solid var(--border-color);}.project-info span:first-child {font-weight: 500;color: var(--text-secondary);display: block;margin-bottom: 5px;}.section {max-width: 1460px;margin: 0 auto;margin-bottom: 2rem;}.section-title h2 {font-size: 1.25rem;margin-bottom: 1rem;color: var(--text-primary);border-bottom: 2px solid var(--primary-color);padding-bottom: 0.5rem;}.card {background-color: var(--surface-1);border-radius: var(--radius-md);margin-bottom: 10px;border: 1px solid var(--border-color);box-shadow: none;}.card:hover {box-shadow: var(--shadow-1);}.card-header {background-color: var(--surface-1);padding: 0;border-bottom: 1px solid var(--border-color);}.card-header .btn-link {padding: 12px 18px;width: 100%;text-align: left;border: none;background: none;cursor: pointer;display: flex;align-items: center;gap: 10px;color: var(--text-primary);font-weight: 500;text-decoration: none;font-size: 0.95rem;}.card-header .btn-link:hover {background-color: var(--surface-3);}.card-header .btn-link .api-path {word-break: break-all;}.card-header .btn-link::after {content: '\\25BC';font-size: 0.8em;margin-left: auto;transition: transform 0.2s ease;}/* Down arrow */.card-header .btn-link[aria-expanded="true"]::after {transform: rotate(-180deg);}/* Up arrow */.card-body {padding: 18px;background-color: var(--surface-2);border-top: 1px solid #e0e0e0;}.input-group-text {background-color: var(--surface-3);border-right: 0;font-weight: 500;font-size: 0.85rem;}input[type="text"],input[type="number"],input[type="file"],select.form-control {border-radius: var(--radius-sm);font-size: 0.9rem;}input[type="text"]:focus,input[type="number"]:focus,input[type="file"]:focus,select.form-control:focus {border-color: var(--primary-color);box-shadow: 0 0 0 2px var(--primary-lighter);}.url-input {font-family: monospace;font-size: 0.9rem;}.btn-sm {padding: .25rem .5rem;font-size: .8rem;}.btn-primary {background-color: var(--primary-color);border-color: var(--primary-color);}.btn-primary:hover {background-color: var(--primary-dark);border-color: var(--primary-dark);}.btn-secondary {color: var(--primary-color);border-color: var(--primary-color);background-color: transparent;}.btn-secondary:hover {background-color: var(--primary-lighter);color: var(--primary-dark);border-color: var(--primary-dark);}span.method {display: inline-flex;align-items: center;justify-content: center;min-width: 60px;height: 22px;border-radius: var(--radius-sm);color: white;font-weight: 500;font-size: 0.75rem;text-transform: uppercase;padding: 0 8px;}span.GET {background-color: #0d6efd;}span.POST {background-color: #198754;}span.PUT {background-color: #ffc107;color: var(--text-primary);}span.DELETE {background-color: #dc3545;}.nav-tabs .nav-link {font-size: 0.9rem;color: var(--text-secondary);border-bottom-width: 2px;}.nav-tabs .nav-link.active {color: var(--primary-color);border-color: var(--primary-color) var(--primary-color) var(--surface-1);font-weight: 500;}.tab-content {padding: 15px;background-color: var(--surface-1);border: 1px solid var(--border-color);border-top: 0;border-radius: 0 0 var(--radius-md) var(--radius-md);}.response-meta {font-size: 0.85rem;color: var(--text-secondary);margin-bottom: 10px;}.response-meta span {margin-right: 15px;}.response-meta .status-success {color: var(--success-color);font-weight: bold;}.response-meta .status-error {color: var(--error-color);font-weight: bold;}.code-container {border-radius: var(--radius-sm);overflow: auto;margin-top: 8px;padding: 10px;color: #f0f0f0;max-height: 400px;}.code-container pre {margin: 0;font-size: 0.85rem;}.assertion-result {margin-bottom: 5px;padding: 8px;border-radius: var(--radius-sm);font-size: 0.85rem;}.assertion-pass {background-color: #d1e7dd;color: #0f5132;border-left: 3px solid var(--success-color);}.assertion-fail {background-color: #f8d7da;color: #842029;border-left: 3px solid var(--error-color);}.toast-container {position: fixed;top: 20px;right: 20px;z-index: 1050;}.toast-message {background-color: var(--primary-color);color: white;padding: 10px 15px;border-radius: var(--radius-sm);box-shadow: var(--shadow-1);font-size: 0.9rem;}#api-search-input {margin-bottom: 15px;}#api-history-container .list-group-item {font-size: 0.85rem;}.action-buttons button {margin-right: 10px;}.headers-table {width: 100%;font-size: 0.85rem;}.headers-table th,.headers-table td {padding: 5px;border-bottom: 1px solid var(--surface-3);text-align: left;}.headers-table th {font-weight: 500;background-color: var(--surface-2);}.payload-section,.global-auth-section {font-size: 0.9rem;padding: 10px;background-color: var(--surface-3);border-radius: var(--radius-sm);}.payload-section strong,.global-auth-section strong {display: block;margin-bottom: 8px;}.curl-output pre {color: #f0f0f0;background-color: #1e1e1e;padding: 10px;border-radius: var(--radius-sm);white-space: pre-wrap;word-break: break-all;}</style></head><body><header><div class="header-content"><img src="{LOGO}" alt="{NAME} Logo" style="width: 36px; height: 36px;" /><h1>{NAME} API Documentation</h1></div></header><div class="container-fluid"><div class="project-info section"><h2>Project Information</h2><p id="project-info-text">{INFO}</p><h2>Project Global Headers</h2><div id="keys" style="margin-top: 10px;"></div><div id="env-switcher-container" style="margin-top: 10px;"></div></div><div class="section"><div class="section-title d-flex justify-content-between align-items-center"><h2>API Endpoints</h2></div><input type="text" id="api-search-input" class="form-control"placeholder="Search endpoints (e.g., GET /users)..."><div class="accordion" id="api-endpoint-container"></div></div><div id="api-history-container" class="section"></div></div><footer><div class="container text-center py-3"><small class="text-muted">© {CURRENT_YEAR} {NAME}. All rights reserved. Powered by Jetpath.</small></div></footer><div class="toast-container" id="toast-container"></div><script>var v=document.getElementById("project-info-text");if(v)v.innerHTML=markdown(v.innerText);var XJ=15,E=JSON.parse(localStorage.getItem("jetpathApiHistory"))||[],GJ="{ JETPATH }",n="{ JETPATHGH }",YJ=new Date().getFullYear();if(document.querySelector("footer small"))document.querySelector("footer small").textContent=document.querySelector("footer small").textContent.replace("{CURRENT_YEAR}",String(YJ));var JJ={"Default (Current Host)":typeof window!=="undefined"?window.location.origin:""},y=JJ["Default (Current Host)"];function i(J){let z=new DocumentFragment;for(let W of J)if(Array.isArray(W))z.appendChild(i(W));else{if(typeof W==="function"){if(W=W(),typeof W==="function")W=W()}if(W instanceof HTMLElement||W instanceof DocumentFragment){z.appendChild(W);continue}if(typeof W==="string")z.appendChild(document.createTextNode(W))}return z}var $J=(J,z)=>{let W={},X=void 0;if(z.length!==0)for(let Q=0;Q<z.length;Q++){let Z=z[Q];if(typeof Z==="function")Z=Z();if(Z instanceof HTMLElement||Z instanceof DocumentFragment){J.appendChild(Z);continue}if(Array.isArray(Z)){J.appendChild(i(Z));continue}if(typeof Z==="string"){X=Z;continue}if(typeof Z==="object"&&Z!==null){Object.assign(W,Z);continue}}else return J;if(typeof W==="object"&&J)for(let[Q,Z]of Object.entries(W)){if(Q==="style"&&typeof Z==="object"){Object.assign(J.style,Z);continue}if(Q.startsWith("on")&&typeof Z==="function"){J.addEventListener(Q.substring(2).toLowerCase(),Z);continue}if(Q.includes("data-")||Q.includes("aria-")){J.setAttribute(Q,Z);continue}J[Q]=Z}if(X!==void 0)J.appendChild(document.createTextNode(X));return J},_=(J)=>(...z)=>$J(document.createElement(J),z);function o(J,...z){if(J)return i(z.flat());return document.createDocumentFragment()}var D=_("button"),Y=_("div"),KJ=_("h2"),FJ=_("h3"),MJ=_("h4"),I=_("h5"),k=_("input"),L=_("span"),h=_("strong"),UJ=_("pre"),b=_("option"),QJ=_("select"),u=_("ul"),g=_("li"),VJ=_("table"),_J=_("tbody"),wJ=_("thead"),s=_("tr"),d=_("th"),r=_("td"),x=_("a"),S=_("label"),m=_("small"),OJ=_("p"),jJ=()=>{let J=document.createElement("span");return J.innerHTML='<div class="spinner-border spinner-border-sm text-primary" role="status"><span class="sr-only">Loading...</span></div>',J};function LJ(J){if(typeof J==="string")try{J=JSON.parse(J)}catch(z){return\`<pre>\${c(J)}</pre>\`}if(typeof prettyPrintJson==="undefined")return\`<pre>\${c(JSON.stringify(J,null,2))}</pre>\`;return prettyPrintJson.toHtml(J,{indent:2,lineNumbers:!1,linkUrls:!0,linksNewTab:!0,quoteKeys:!0,trailingCommas:!1})}function c(J){return J.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}function C(J){let z=document.getElementById("toast-container");if(!z)return;let W=Y({className:"toast-message"},J);z.appendChild(W),setTimeout(()=>{W.remove()},3000)}function WJ(J,z){navigator.clipboard.writeText(J).then(()=>C(\`\${z} copied to clipboard!\`)).catch((W)=>{console.error("Failed to copy: ",W),C(\`Failed to copy \${z}.\`)})}function RJ(){let J=document.getElementById("env-switcher-container");if(!J)return;let z=FJ({style:{marginRight:"8px",fontWeight:"500"}},"Environment:"),W=QJ({id:"env-selector",className:"form-control form-control-sm d-inline-block",style:{width:"auto",minWidth:"200px"},onchange:(X)=>{y=X.target.value,l()}});Object.entries(JJ).forEach(([X,Q])=>{W.appendChild(b({value:Q},X))}),W.value=y,J.appendChild(z),J.appendChild(W)}function BJ(J,z,W,X,Q){let Z=new Date().toISOString();if(E.unshift({method:J,url:z,status:W,timestamp:Z,payloadSummary:X,time:Q}),E.length>XJ)E.pop();localStorage.setItem("jetpathApiHistory",JSON.stringify(E)),ZJ()}function ZJ(){let J=document.getElementById("api-history-container");if(!J)return;if(J.innerHTML="",J.appendChild(KJ({className:"section-title"},"Request History")),E.length===0){J.appendChild(L("No requests in history yet."));return}let z=u({className:"list-group"});E.forEach((W)=>{let X=g({className:"list-group-item list-group-item-action flex-column align-items-start",style:{cursor:"pointer"},title:"Click to re-populate (basic)",onclick:()=>{NJ(W)}},Y({className:"d-flex w-100 justify-content-between"},I({className:"mb-1"},\`\${W.method} \${new URL(W.url).pathname}\`),m(\`\${W.time?W.time+"ms - ":""}\${new Date(W.timestamp).toLocaleTimeString()}\`)),OJ({className:"mb-1",style:{fontSize:"0.8rem",wordBreak:"break-all"}},W.url),m({className:\`status-\${W.status<400?"success":"error"}\`},\`Status: \${W.status}\`),W.payloadSummary?m({className:"d-block text-muted",style:{whiteSpace:"nowrap",overflow:"hidden",textOverflow:"ellipsis"}},\`Payload: \${W.payloadSummary}\`):"");z.appendChild(X)}),J.appendChild(z)}function NJ(J){let z=Array.from(document.querySelectorAll(".card .btn-link")).find((W)=>{let X=W.querySelector(".api-path")?.textContent;try{return X&&X.trim()===new URL(J.url).pathname}catch{return!1}});if(z){let W=z.closest(".card").id,X=z.getAttribute("data-target");if(X)$(X).collapse("show"),setTimeout(()=>{let Q=document.getElementById(\`url-\${W}\`);if(Q)Q.value=J.url;if(J.payloadSummary&&J.payloadSummary.startsWith("{"))try{let Z=JSON.parse(J.payloadSummary),G=document.getElementById(\`request-tab-content-\${W}\`);if(G){let F=G.querySelector(".body-pack");if(F)Object.entries(Z).forEach(([K,U])=>{let O=F.querySelector(\`input[placeholder="Enter \${K}"]\`);if(O)O.value=typeof U==="object"?JSON.stringify(U):U})}}catch(Z){console.warn("Could not parse payload from history for re-population",Z)}z.scrollIntoView({behavior:"smooth",block:"center"}),C("Form populated from history.")},300)}else C("Could not find matching API card to re-populate.")}function t(J){if(!J)return;let z={},W=!1;if(Array.from(J.children).forEach((Q)=>{let Z=Q.querySelector("input");if(!Z)return;W=!0;let G=Q.querySelector("span")?.textContent?.replace(":","");if(!G)return;if(Z.type==="file"){z[G]=Z.files[0];return}z[G.trim()]=Z.value}),!W)return;return z}function a(J){if(!J)return;if(J.type==="file")return J.files&&J.files.length>0?J.files[0]:void 0;if(J.type==="number")return J.value===""?void 0:Number(J.value);if(J.type==="checkbox")return J.checked;let z=J.value;if(z==="true")return!0;if(z==="false")return!1;return z}function e(J){if(!J)return{};function z(X){if(X.matches(".form-group.data-input")){let Q=X.querySelector('input:not([type="button"]), select, textarea');return a(Q)}else if(X.dataset?.obj==="true"&&X.matches(".data-input")){let Q={};return Array.from(X.children).forEach((Z)=>{if(Z.matches(".form-group.data-input, .data-input")){let G=Z.querySelector("span[data-key-name]")?.dataset?.keyName||Z.dataset?.holder;if(G)Q[G]=z(Z)}}),Q}else if(X.dataset?.arr==="true"&&X.matches(".data-input")){let Q=[],Z=X.querySelector(".array-cont");if(Z)Array.from(Z.children).filter((G)=>G.matches(".array-item")).forEach((G)=>{let F={};Array.from(G.children).forEach((K)=>{if(K.matches(".form-group.data-input, .data-input")){let U=K.querySelector("span[data-key-name]")?.dataset?.keyName||K.dataset?.holder;if(U)F[U]=z(K)}}),Q.push(F)});else{let G=X.querySelector('input[type="text"][placeholder*="comma-separated"]');if(G){let F=a(G);if(F&&typeof F==="string"&&F.trim()!=="")Q.push(...F.split(",").map((K)=>K.trim()).filter((K)=>K));else if(F!==void 0&&F!==""&&F!==null)Q.push(F)}}return Q}return}let W={};return Array.from(J.children).forEach((X)=>{if(X.matches(".form-group.data-input, .data-input")){let Q=X.querySelector("span[data-key-name]")?.dataset?.keyName||X.dataset?.holder;if(Q)W[Q]=z(X)}}),W}function zJ(J,z,W=!0){if(!J)return;let X=Y({className:"body-pack"});function Q(Z,G,F,K,U=""){let O=\`\${z}-field-\${(U+Z).replace(/[^a-zA-Z0-9]/g,"_")}\`;if(typeof G==="object"&&!Array.isArray(G)&&G!==null){let M=Y({className:"data-input","data-obj":"true","data-holder":Z,style:{marginLeft:"15px",borderLeft:"2px solid var(--surface-3)",paddingLeft:"10px",marginBottom:"10px"}},I({style:{fontSize:"0.9em",color:"var(--text-secondary)"}},Z+": {object}"));return Object.entries(G??{}).forEach(([R,w])=>{M.appendChild(Q(R,w,"obj",Z,U+Z+"."))}),M}else if(Array.isArray(G)){let M=Y({className:"data-input","data-arr":"true","data-holder":Z,style:{marginLeft:"15px",borderLeft:"2px solid var(--surface-3)",paddingLeft:"10px",marginBottom:"10px"}},I({style:{fontSize:"0.9em",color:"var(--text-secondary)"}},Z+": [array]"));if(G.length>0&&typeof G[0]==="object"&&G[0]!==null){let R=Y({className:"array-cont"});M.appendChild(R);let w=0,V=()=>{let B=Y({className:"array-item","data-pos":w,style:{border:"1px dashed var(--border-color)",padding:"10px",marginBottom:"5px"}});Object.entries(G).forEach(([f,H])=>{B.appendChild(Q(f,H,"arr-obj",Z,\`\${U}\${Z}[\${w}].\`))}),R.appendChild(B),w+=1};V(),M.appendChild(D("+ Add Item",{onclick:V,className:"btn btn-sm btn-outline-secondary",style:{fontSize:"0.8rem",marginTop:"5px"}}))}else M.appendChild(L({"data-key-name":Z,style:{marginRight:"5px"}},Z+": ")),M.appendChild(k({type:G[0].split(":")[0],id:O,placeholder:"Enter "+Z+" (comma-separated values)",value:G[0].split(":")[1],className:"form-control form-control-sm"}));return M}else{let[M,R]=typeof G==="string"?G.split(":"):[G,null];if(!W)R=G;return Y({className:"form-group row data-input",style:{marginBottom:"0.5rem"}},L({htmlFor:O,className:"col-sm-4 col-form-label col-form-label-sm","data-key-name":Z,style:{fontWeight:"normal"}},Z+": "),Y({className:"col-sm-8"},k({type:/(file|number|string|date|datetime|time|email|url|tel|password|checkbox|radio|select|textarea|hidden|button|submit|reset|image|color|month|week|range)/.test(M)?M:"text",id:O,value:M!=="file"&&R!==null?R:null,placeholder:"Enter "+Z,className:"form-control form-control-sm"})))}}if(J&&typeof J==="object")Object.entries(J).forEach(([Z,G])=>{X.appendChild(Q(Z,G,"root",""))});else if(J)X.appendChild(L("Payload schema is not a valid object. Raw: "+c(String(J))));else X.appendChild(L("No payload schema defined for this request."));return X}function AJ(J){return J.split("### break ###").map((W)=>W.trim()).filter((W)=>W!=="").map(HJ)}function HJ(J){let z=J.split("\\n").map((M)=>M.trim()),W=z[0].split(" "),X=W[0],Q=W[1],Z=W[2],G={};for(let M=1;M<z.length;M++){if(z[M]==="")break;let[R,w]=z[M].split(":").map((V)=>V.trim());G[R.toLowerCase()]=w}let F=z.indexOf("")+1,K=F!==0?z.slice(F).join("\\n"):null,U="",O="";if(K?.includes("#")){if(K.includes("#-JET-TITLE"))U=K.slice(K.indexOf("#-JET-TITLE")+12,K.lastIndexOf("#-JET-TITLE"));if(K.includes("#-JET-DESCRIPTION"))O=K.slice(K.indexOf("#-JET-DESCRIPTION")+18,K.lastIndexOf("#-JET-DESCRIPTION"));K=K.split(\`
\`).filter((M)=>!M.startsWith("#")).join(\`
\`)}return{method:X,url:Q,httpVersion:Z,headers:G,payload:K,title:U,description:O}}var gJ=(J)=>{let z={},W=[];for(let X=0;X<J.length;X++){let Q=J[X];try{let Z=new URL(Q?.url).pathname.split("/")[1]||"/";if(z[Z])z[Z].push(Q);else z[Z]=[Q];z[Z].sort((G,F)=>G.url.localeCompare(F.url))}catch(Z){console.warn("Skipping API with invalid URL:",Q);continue}}for(let X in z)W.push({title:X,isGroup:!0}),W.push(...z[X]);return W};function xJ(J,z,W,X,Q){let G=(performance.now()-Q).toFixed(0),F=J.body?new TextEncoder().encode(J.body).length:0,K=document.getElementById(\`response-body-tab-\${z}\`),U=document.getElementById(\`response-headers-tab-\${z}\`),O=document.getElementById(\`test-results-tab-\${z}\`),M=document.getElementById(\`response-meta-\${z}\`);if(!K||!U||!O||!M)return;if(M.innerHTML="",M.appendChild(L({className:\`status-\${J.status<400&&!J.error?"success":"error"}\`},\`Status: \${J.status||(J.error?"Client Error":"N/A")}\`)),M.appendChild(L(\`Time: \${G} ms\`)),M.appendChild(L(\`Size: \${(F/1024).toFixed(2)} KB\`)),K.innerHTML="",J.body)K.appendChild(D({className:"btn btn-sm btn-outline-secondary float-right mb-2",onclick:()=>WJ(J.body,"Response Body")},"Copy Body")),K.appendChild(Y({className:"code-container",style:{overflowX:"auto"},innerHTML:LJ(J.body)}));else K.appendChild(L(J.error||"No response body."));if(U.innerHTML="",J.headers&&typeof J.headers.forEach==="function"){let V=VJ({className:"table table-sm headers-table"}),B=wJ(s(d("Header Name"),d("Header Value"))),f=_J();J.headers.forEach((H,j)=>{f.appendChild(s(r(j),r(H)))}),V.appendChild(B),V.appendChild(f),U.appendChild(V)}else U.appendChild(L("No headers in response."));O.innerHTML="";let R=!0;if(W){let V=J.status||(J.error?0:404),B=Number(W)===V;if(!B)R=!1;O.appendChild(Y({className:\`assertion-result \${B?"assertion-pass":"assertion-fail"}\`},\`Status Code: Expected \${W}, Got \${V}. (\${B?"PASS":"FAIL"})\`))}if(X&&J.body){let V=J.body.includes(X);if(!V)R=!1;O.appendChild(Y({className:\`assertion-result \${V?"assertion-pass":"assertion-fail"}\`},\`Body Contains "\${X}": \${V?"Found (PASS)":"Not Found (FAIL)"}\`))}if(!W&&!X)O.appendChild(L("No assertions defined for this request."));document.getElementById(\`response-container-tabs-\${z}\`).style.display="block";let w=W||X?\`test-results-nav-\${z}\`:\`response-body-nav-\${z}\`;$(\`#\${w}\`).tab("show")}function DJ(J,z){let W=JSON.parse(J.payload?.includes("{")?J.payload:"null"),X=J.method.toLowerCase()+J.url.replace(/[^a-zA-Z0-9]/g,"")+z,Q=\`card-\${X}\`,Z=\`collapse-\${X}\`,G=u({className:"nav nav-tabs",role:"tablist"},g({className:"nav-item"},x({className:"nav-link active",id:\`request-nav-\${Q}\`,"data-toggle":"tab",href:\`#request-tab-content-\${Q}\`,role:"tab"},"Request")),g({className:"nav-item"},x({className:"nav-link",id:\`auth-nav-\${Q}\`,"data-toggle":"tab",href:\`#auth-tab-content-\${Q}\`,role:"tab"},"Global Auth")),g({className:"nav-item"},x({className:"nav-link",id:\`assertions-nav-\${Q}\`,"data-toggle":"tab",href:\`#assertions-tab-content-\${Q}\`,role:"tab"},"Assertions")),g({className:"nav-item"},x({className:"nav-link",id:\`curl-nav-\${Q}\`,"data-toggle":"tab",href:\`#curl-tab-content-\${Q}\`,role:"tab"},"cURL"))),F=Y({className:"tab-content"},Y({className:"tab-pane fade show active",id:\`request-tab-content-\${Q}\`,role:"tabpanel"},Y({className:"form-group mt-3"},S({htmlFor:\`url-\${Q}\`},"Request URL"),k({className:"form-control url-input",id:\`url-\${Q}\`,value:J.url})),Y({className:"form-group"},S({htmlFor:\`content-type-dropdown-\${Q}\`},"Content-Type"),QJ({id:\`content-type-dropdown-\${Q}\`,className:"form-control form-control-sm"},b({value:"application/json"},"JSON"),b({value:"multipart/form-data"},"Form Data"),b({value:"application/x-www-form-urlencoded"},"Form URL Encoded"))),o(W,()=>Y({className:"payload-section"},h("Payload:"),zJ(W,Q)))),Y({className:"tab-pane fade",id:\`auth-tab-content-\${Q}\`,role:"tabpanel"},Y({className:"global-auth-section mt-3",id:\`global-auth-preview-\${Q}\`},h("Global Authentication Headers (Read-only):"))),Y({className:"tab-pane fade",id:\`assertions-tab-content-\${Q}\`,role:"tabpanel"},Y({className:"form-group mt-3"},S({htmlFor:\`expected-status-\${Q}\`},"Expected Status Code"),k({type:"number",id:\`expected-status-\${Q}\`,placeholder:"e.g., 200",className:"form-control form-control-sm"})),Y({className:"form-group"},S({htmlFor:\`expected-body-\${Q}\`},"Response Body Contains (Text)"),k({type:"text",id:\`expected-body-\${Q}\`,placeholder:'e.g., "success": true',className:"form-control form-control-sm"}))),Y({className:"tab-pane fade",id:\`curl-tab-content-\${Q}\`,role:"tabpanel"},Y({className:"mt-3 curl-output",id:\`curl-output-\${Q}\`},UJ('Click "Generate cURL" after configuring request.')))),K=u({className:"nav nav-tabs mt-3",role:"tablist"},g({className:"nav-item"},x({className:"nav-link active",id:\`response-body-nav-\${Q}\`,"data-toggle":"tab",href:\`#response-body-tab-\${Q}\`,role:"tab"},"Body")),g({className:"nav-item"},x({className:"nav-link",id:\`response-headers-nav-\${Q}\`,"data-toggle":"tab",href:\`#response-headers-tab-\${Q}\`,role:"tab"},"Headers")),g({className:"nav-item"},x({className:"nav-link",id:\`test-results-nav-\${Q}\`,"data-toggle":"tab",href:\`#test-results-tab-\${Q}\`,role:"tab"},"Test Results"))),U=Y({className:"tab-content"},Y({className:"tab-pane fade show active",id:\`response-body-tab-\${Q}\`,role:"tabpanel",style:{padding:"10px",flexDirection:"column"}}),Y({className:"tab-pane fade",id:\`response-headers-tab-\${Q}\`,role:"tabpanel",style:{padding:"10px"}}),Y({className:"tab-pane fade",id:\`test-results-tab-\${Q}\`,role:"tabpanel",style:{padding:"10px"}})),O=()=>{let w=document.getElementById(\`request-tab-content-\${Q}\`);if(w)w.querySelectorAll('input[type="text"], input[type="number"], input[type="file"], textarea').forEach((V)=>V.value=""),w.querySelectorAll('input[type="checkbox"], input[type="radio"]').forEach((V)=>V.checked=!1);document.getElementById(\`expected-status-\${Q}\`).value="",document.getElementById(\`expected-body-\${Q}\`).value="",document.getElementById(\`response-meta-\${Q}\`).innerHTML="",document.getElementById(\`response-body-tab-\${Q}\`).innerHTML="",document.getElementById(\`response-headers-tab-\${Q}\`).innerHTML="",document.getElementById(\`test-results-tab-\${Q}\`).innerHTML="",document.getElementById(\`curl-output-\${Q}\`).querySelector("pre").textContent='Click "Generate cURL" after configuring request.',C("Form cleared.")},M=()=>{let w=J.method.toUpperCase(),V=document.getElementById(\`url-\${Q}\`)?.value?.trim(),B=t(document.getElementById("keys"))||{},H={...J.headers,...B},j=\`curl --location --request \${w} '\${V}' \\
\`;for(let N in H)if(Object.hasOwnProperty.call(H,N))j+=\`--header '\${N}: \${H[N]}' \\
\`;let A=e(document.getElementById(\`request-tab-content-\${Q}\`).querySelector(".body-pack")),P=document.getElementById(\`content-type-dropdown-\${Q}\`)?.value||"application/json";if(A&&Object.keys(A).length>0){if(j+=\`--header 'Content-Type: \${P}' \\
\`,P==="application/json")j+=\`--data-raw '\${JSON.stringify(A)}'\`;else if(P==="multipart/form-data"){for(let N in A)j+=\`--form '\${N}=\${A[N]instanceof File?A[N].name:JSON.stringify(A[N])}' \\
\`;j=j.slice(0,-4)}else if(P==="application/x-www-form-urlencoded")j+=\`--data-raw '\${new URLSearchParams(A).toString()}'\`}else if(j.endsWith(" \\\n"))j=j;let q=document.getElementById(\`curl-output-\${Q}\`).querySelector("pre");q.textContent=j;let T=document.getElementById(\`copy-curl-\${Q}\`);if(!T&&q.parentNode)T=D({id:\`copy-curl-\${Q}\`,className:"btn btn-sm btn-outline-secondary mt-2",onclick:()=>WJ(j,"cURL command")},"Copy cURL"),q.parentNode.appendChild(T);$(\`#curl-nav-\${Q}\`).tab("show")},R=async()=>{let w=performance.now(),V=document.getElementById(\`url-\${Q}\`)?.value?.trim(),B=t(document.getElementById("keys"))||{},H={...J.headers,...B},j=e(document.getElementById(\`request-tab-content-\${Q}\`).querySelector(".body-pack")),A=document.getElementById(\`expected-status-\${Q}\`)?.value,P=document.getElementById(\`expected-body-\${Q}\`)?.value,q=document.getElementById(\`content-type-dropdown-\${Q}\`)?.value||"application/json",T=document.getElementById(\`response-container-tabs-\${Q}\`);T.style.display="block",document.getElementById(\`response-meta-\${Q}\`).innerHTML="",document.getElementById(\`response-body-tab-\${Q}\`).innerHTML="",document.getElementById(\`response-headers-tab-\${Q}\`).innerHTML="",document.getElementById(\`test-results-tab-\${Q}\`).innerHTML="",document.getElementById(\`response-body-tab-\${Q}\`).appendChild(jJ());let N=await fJ(J.method,V,H,j,q);if(xJ(N,Q,Number(A),P,w),!N.error){let p="";if(j&&Object.keys(j).length>0)p=JSON.stringify(j).substring(0,70)+(JSON.stringify(j).length>70?"...":"");BJ(J.method,V,N.status,p,(performance.now()-w).toFixed(0))}};return Y({className:"card",id:Q},Y({className:"card-header",id:\`header-\${Q}\`},I({className:"mb-0"},D({className:"btn btn-link collapsed",type:"button","data-toggle":"collapse","data-target":\`#\${Z}\`,"aria-expanded":"false","aria-controls":Z,onclick:()=>{let w=document.getElementById(\`global-auth-preview-\${Q}\`);if(w&&w.children.length<=1)if(Object.keys(J.headers).length>0)Object.entries(J.headers).forEach(([V,B])=>{w.appendChild(Y({style:{fontSize:"0.85rem"}},h(V+": "),L(B)))});else w.appendChild(L({style:{fontSize:"0.85rem"}},"No global authentication headers configured or found."))}},L(J.method,{className:"method "+J.method}),L({className:"api-path"},J.url?new URL(J.url).pathname:"Invalid URL"),L({className:"text-muted small ml-2",style:{fontWeight:"normal"}},J.title||"")))),Y({id:Z,className:"collapse","data-parent":"#api-endpoint-container"},Y({className:"card-body"},G,o(J.description||J.title,Y({className:"tab-content",innerHTML:markdown((J.description||"#"+J.title)?.slice(1)?.replaceAll(\`
#\`,\`
\`))})),F,Y({className:"action-buttons mt-3 mb-3"},D("Send Request",{className:"btn btn-primary",onclick:R}),D("Generate cURL",{className:"btn btn-secondary",onclick:M}),D("Clear Form",{className:"btn btn-outline-danger btn-sm",onclick:O})),Y({id:\`response-meta-\${Q}\`,className:"response-meta"}),Y({id:\`response-container-tabs-\${Q}\`,style:{display:"none"}},K,U))))}function l(){let J=document.getElementById("api-endpoint-container"),z=document.getElementById("api-search-input");if(!J||!z)return;let W=z.value.toLowerCase();J.innerHTML="";let X=GJ.replaceAll("[--host--]",y),Q=AJ(X),Z=Q;if(W)Z=Q.filter((F)=>{return\`\${F.method} \${F.url} \${F.title||F.description||""}\`.toLowerCase().includes(W)});let G=gJ(Z);if(G.length===0&&W){J.appendChild(Y({className:"alert alert-warning"},"No endpoints match your search."));return}else if(G.length===0){J.appendChild(Y({className:"alert alert-info"},"No API endpoints defined or an error occurred parsing them."));return}G.forEach((F,K)=>{if(F.isGroup){let U=MJ({className:"mt-3 mb-2 text-muted px-2",style:{fontSize:"1rem",borderBottom:"1px solid var(--surface-3)",paddingBottom:"5px"}},F.title.toUpperCase());J.appendChild(U)}else J.appendChild(DJ(F,K))})}async function fJ(J,z,W={},X,Q="application/json"){if(Q!=="multipart/form-data"&&Q!=="application/octet-stream")W["Content-Type"]=Q;else if(Q==="multipart/form-data")delete W["Content-Type"];let Z;try{let G={method:J,headers:W,signal:AbortSignal.timeout(30000),body:X};if(J!=="GET"&&J!=="HEAD"&&X!==void 0)if(Q==="application/json")G.body=JSON.stringify(X);else if(Q==="multipart/form-data"){let U=new FormData;if(X)for(let O in X)U.append(O,X[O]);G.body=U}else if(Q==="application/x-www-form-urlencoded")G.body=new URLSearchParams(X).toString();else G.body=X;else delete G.body;Z=await fetch(z,G);let F=await Z.text(),K={};for(let[U,O]of Z.headers.entries())K[U]=O;return{status:Z.status,headers:Z.headers,body:F}}catch(G){return console.error("API Test Error:",G),{error:G.message,status:0,body:G.message,headers:new Headers}}}document.addEventListener("DOMContentLoaded",()=>{RJ();try{document.getElementById("keys")?.appendChild(zJ(JSON.parse(n),"global-headers",!1))}catch(z){console.error("Failed to parse global headers JSON:",n,z)}l(),ZJ();let J=document.getElementById("api-search-input");if(J)J.addEventListener("keyup",()=>{clearTimeout(J.searchTimeout),J.searchTimeout=setTimeout(l,300)})});
</script></body></html>`;
// ? check if server is already listening
this.server = server(this.plugs, this.options);
// ? add plugins to the server
if (this.server.edge && typeof this.options.edgeGrabber?.length === "number") {
await getHandlersEdge(this.options.edgeGrabber);
if (this.options?.apiDoc?.display === "UI") {
this.api_UI_req(UI);
}
this.server.listen();
LOG.log("Jetpath: Edge is enabled", "success");
return;
}
else if (this.server.edge && !this.options.edgeGrabber?.length) {
// ? edge is enabled but no edgeGrabber provided
throw new Error("Jetpath: the runtime is Edge is enabled but no edgeGrabber provided. Please provide edgeGrabber in options.");
}
if (!this.options.source) {
LOG.log("Jetpath: Provide a source directory to avoid scanning the root directory", "warn");
}
LOG.log("Compiling...", "info");
const startTime = performance.now();
// ? Load all jetpath functions described in user code
const errorsCount = await getHandlers(this.options?.source, true);
const endTime = performance.now();
// LOG.log("Compiled!");
//? compile API
const [handlersCount, compiledAPI] = compileAPI(this.options);
// ? render API in UI
if (this.options?.apiDoc?.display === "UI") {
this.api_UI_req(UI);
LOG.log(`Compiled ${handlersCount} Functions\nTime: ${Math.round(endTime - startTime)}ms`, "info");
//? generate types
if (/(ON|WARN)/.test(this.options?.strictMode || "OFF")) {
await codeGen(this.options.source || ".", this.options.strictMode, this.options.generatedRoutesFilePath);
}
LOG.log(`APIs: Viewable at http://localhost:${this.options.port}${this.options?.apiDoc?.path || "/api-doc"}`, "info");
}
else if (this.options?.apiDoc?.display === "HTTP") {
//? generate types
await codeGen(this.options.source || ".", this.options?.strictMode, this.options.generatedRoutesFilePath);
// ? render API in a .HTTP file
await fs().writeFile("api-doc.http", compiledAPI);
LOG.log(`Compiled ${handlersCount} Functions\nTime: ${Math.round(endTime - startTime)}ms`, "info");
LOG.log(`APIs: written to ${fs().sep}api-doc.http`, "info");
}
if (errorsCount) {
for (let i = 0; i < errorsCount.length; i++) {
LOG.log(`\nReport: ${errorsCount[i].file} file was not loaded due to \n "${errorsCount[i].error}" error; \n please resolve!`, "warn");
}
}
//
assignMiddleware(_JetPath_paths, _jet_middleware);
// ? start server
this.listening = true;
this.server.listen(this.options.port);
LOG.log(`Open http://localhost:${this.options.port}`, "info");
// ? show external IP
const localIP = getLocalIP();
if (localIP) {
LOG.log(`External: http://${localIP}:${this.options.port}`, "info");
}
}
api_UI_req(UI) {
const [_, compiledAPI] = compileAPI(this.options);
const name = this.options?.apiDoc?.path || "/api-doc";
_JetPath_paths_trie["GET"].insert(name, (ctx) => {
UI = compileUI(UI, this.options, compiledAPI);
if (this.options.apiDoc?.username && this.options.apiDoc?.password) {
const authHeader = ctx.get("authorization");
if (authHeader && authHeader.startsWith("Basic ")) {
const [authType, encodedToken] = authHeader.trim().split(" ");
if (authType !== "Basic" || !encodedToken) {
ctx.set("WWW-Authenticate", `Basic realm=Jetpath API Doc`);
ctx.send(`<h1>Unauthorized</h1>`, 401, "text/html");
return;
}
let username, password;
try {
const decodedToken = new TextDecoder().decode(Uint8Array.from(atob(encodedToken), (c) => c.charCodeAt(0)));
[username, password] = decodedToken.split(":");
}
catch (error) {
ctx.set("WWW-Authenticate", `Basic realm=Jetpath API Doc`);
ctx.send(`<h1>Unauthorized</h1>`, 401, "text/html");
return;
}
if (password === this.options?.apiDoc?.password &&
username === this.options?.apiDoc?.username) {
ctx.send(UI, 200, "text/html");
return;
}
else {
ctx.set("WWW-Authenticate", `Basic realm=Jetpath API Doc`);
ctx.send(`<h1>Unauthorized</h1>`, 401, "text/html");
return;
}
}
else {
ctx.set("WWW-Authenticate", `Basic realm=Jetpath API Doc`);
ctx.send(`<h1>Unauthorized</h1>`, 401, "text/html");
return;
}
}
else {
ctx.send(UI, 200, "text/html");
return;
}
});
}
}
export { JetServer } from "./primitives/classes.js";
export { use } from "./primitives/functions.js";
export { mime } from "./extracts/mimejs-extract.js";