vector-framework 1.2.1 → 1.2.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/dist/cli.js +97 -11
- package/dist/core/server.d.ts +1 -0
- package/dist/core/server.d.ts.map +1 -1
- package/dist/core/server.js +13 -2
- package/dist/core/server.js.map +1 -1
- package/dist/errors/index.cjs +2 -0
- package/dist/index.cjs +1434 -0
- package/dist/index.js +12 -1433
- package/dist/index.mjs +20 -19
- package/dist/openapi/generator.d.ts.map +1 -1
- package/dist/openapi/generator.js +92 -9
- package/dist/openapi/generator.js.map +1 -1
- package/package.json +8 -14
- package/src/core/server.ts +15 -2
- package/src/openapi/generator.ts +115 -7
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1434 @@
|
|
|
1
|
+
// @bun @bun-cjs
|
|
2
|
+
(function(exports, require, module, __filename, __dirname) {var{defineProperty:P,getOwnPropertyNames:Re,getOwnPropertyDescriptor:Ue}=Object,$e=Object.prototype.hasOwnProperty;var ie=new WeakMap,Pe=(e)=>{var t=ie.get(e),d;if(t)return t;if(t=P({},"__esModule",{value:!0}),e&&typeof e==="object"||typeof e==="function")Re(e).map((n)=>!$e.call(t,n)&&P(t,n,{get:()=>e[n],enumerable:!(d=Ue(e,n))||d.enumerable}));return ie.set(e,t),t};var _e=(e,t)=>{for(var d in t)P(e,d,{get:t[d],enumerable:!0,configurable:!0,set:(n)=>t[d]=()=>n})};var wt={};_e(wt,{startVector:()=>He,route:()=>Me,createResponse:()=>I,APIError:()=>B});module.exports=Pe(wt);var v={OK:200,CREATED:201,ACCEPTED:202,NON_AUTHORITATIVE_INFORMATION:203,NO_CONTENT:204,RESET_CONTENT:205,PARTIAL_CONTENT:206,MULTI_STATUS:207,ALREADY_REPORTED:208,IM_USED:226,MULTIPLE_CHOICES:300,MOVED_PERMANENTLY:301,FOUND:302,SEE_OTHER:303,NOT_MODIFIED:304,USE_PROXY:305,TEMPORARY_REDIRECT:307,PERMANENT_REDIRECT:308,BAD_REQUEST:400,UNAUTHORIZED:401,PAYMENT_REQUIRED:402,FORBIDDEN:403,NOT_FOUND:404,METHOD_NOT_ALLOWED:405,NOT_ACCEPTABLE:406,PROXY_AUTHENTICATION_REQUIRED:407,REQUEST_TIMEOUT:408,CONFLICT:409,GONE:410,LENGTH_REQUIRED:411,PRECONDITION_FAILED:412,PAYLOAD_TOO_LARGE:413,URI_TOO_LONG:414,UNSUPPORTED_MEDIA_TYPE:415,RANGE_NOT_SATISFIABLE:416,EXPECTATION_FAILED:417,IM_A_TEAPOT:418,MISDIRECTED_REQUEST:421,UNPROCESSABLE_ENTITY:422,LOCKED:423,FAILED_DEPENDENCY:424,TOO_EARLY:425,UPGRADE_REQUIRED:426,PRECONDITION_REQUIRED:428,TOO_MANY_REQUESTS:429,REQUEST_HEADER_FIELDS_TOO_LARGE:431,UNAVAILABLE_FOR_LEGAL_REASONS:451,INTERNAL_SERVER_ERROR:500,NOT_IMPLEMENTED:501,BAD_GATEWAY:502,SERVICE_UNAVAILABLE:503,GATEWAY_TIMEOUT:504,HTTP_VERSION_NOT_SUPPORTED:505,VARIANT_ALSO_NEGOTIATES:506,INSUFFICIENT_STORAGE:507,LOOP_DETECTED:508,NOT_EXTENDED:510,NETWORK_AUTHENTICATION_REQUIRED:511},N={PORT:3000,HOSTNAME:"localhost",ROUTES_DIR:"./routes",CACHE_TTL:0,CORS_MAX_AGE:86400},_={JSON:"application/json",TEXT:"text/plain",HTML:"text/html",FORM_URLENCODED:"application/x-www-form-urlencoded",MULTIPART:"multipart/form-data"};var H={NOT_FOUND:new Response(JSON.stringify({error:!0,message:"Not Found",statusCode:404}),{status:404,headers:{"content-type":"application/json"}})};class J{protectedHandler=null;setProtectedHandler(e){this.protectedHandler=e}clearProtectedHandler(){this.protectedHandler=null}async authenticate(e){if(!this.protectedHandler)throw new Error("Protected handler not configured. Use vector.protected() to set authentication handler.");try{let t=await this.protectedHandler(e);return e.authUser=t,t}catch(t){throw new Error(`Authentication failed: ${t instanceof Error?t.message:String(t)}`)}}isAuthenticated(e){return!!e.authUser}getUser(e){return e.authUser||null}}class S{cacheHandler=null;memoryCache=new Map;cleanupInterval=null;inflight=new Map;setCacheHandler(e){this.cacheHandler=e}clearCacheHandler(){this.cacheHandler=null}async get(e,t,d=N.CACHE_TTL){if(d<=0)return t();if(this.cacheHandler)return this.cacheHandler(e,t,d);return this.getFromMemoryCache(e,t,d)}async getFromMemoryCache(e,t,d){let n=Date.now(),r=this.memoryCache.get(e);if(this.isCacheValid(r,n))return r.value;if(this.inflight.has(e))return await this.inflight.get(e);let o=(async()=>{let i=await t();return this.setInMemoryCache(e,i,d),i})();this.inflight.set(e,o);try{return await o}finally{this.inflight.delete(e)}}isCacheValid(e,t){return e!==void 0&&e.expires>t}setInMemoryCache(e,t,d){let n=Date.now()+d*1000;this.memoryCache.set(e,{value:t,expires:n}),this.scheduleCleanup()}scheduleCleanup(){if(this.cleanupInterval)return;this.cleanupInterval=setInterval(()=>{this.cleanupExpired()},60000)}cleanupExpired(){let e=Date.now();for(let[t,d]of this.memoryCache.entries())if(d.expires<=e)this.memoryCache.delete(t);if(this.memoryCache.size===0&&this.cleanupInterval)clearInterval(this.cleanupInterval),this.cleanupInterval=null}clear(){if(this.memoryCache.clear(),this.cleanupInterval)clearInterval(this.cleanupInterval),this.cleanupInterval=null}async set(e,t,d=N.CACHE_TTL){if(d<=0)return;if(this.cacheHandler){await this.cacheHandler(e,async()=>t,d);return}this.setInMemoryCache(e,t,d)}delete(e){return this.memoryCache.delete(e)}has(e){let t=this.memoryCache.get(e);if(!t)return!1;if(t.expires<=Date.now())return this.memoryCache.delete(e),!1;return!0}generateKey(e,t){let d=e._parsedUrl??new URL(e.url),n=t?.authUser?.id!=null?String(t.authUser.id):"anonymous";return`${e.method}:${d.pathname}:${d.search}:${n}`}}var F=require("fs"),A=require("path");class z{outputPath;constructor(e="./.vector/routes.generated.ts"){this.outputPath=e}async generate(e){let t=A.dirname(this.outputPath);await F.promises.mkdir(t,{recursive:!0});let d=[],n=new Map;for(let a of e){if(!n.has(a.path))n.set(a.path,[]);n.get(a.path).push(a)}let r=0,o=[];for(let[a,s]of n){let c=A.relative(A.dirname(this.outputPath),a).replace(/\\/g,"/").replace(/\.(ts|js)$/,""),m=`route_${r++}`,b=s.filter((p)=>p.name!=="default").map((p)=>p.name);if(s.some((p)=>p.name==="default"))if(b.length>0)d.push(`import ${m}, { ${b.join(", ")} } from '${c}';`);else d.push(`import ${m} from '${c}';`);else if(b.length>0)d.push(`import { ${b.join(", ")} } from '${c}';`);for(let p of s){let x=p.name==="default"?m:p.name;o.push(` ${x},`)}}let i=`// This file is auto-generated. Do not edit manually.
|
|
3
|
+
// Generated at: ${new Date().toISOString()}
|
|
4
|
+
|
|
5
|
+
${d.join(`
|
|
6
|
+
`)}
|
|
7
|
+
|
|
8
|
+
export const routes = [
|
|
9
|
+
${o.join(`
|
|
10
|
+
`)}
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
export default routes;
|
|
14
|
+
`;await F.promises.writeFile(this.outputPath,i,"utf-8")}async generateDynamic(e){let t=[];for(let d of e){let n=JSON.stringify({method:d.method,path:d.options.path,options:d.options});t.push(` await import('${d.path}').then(m => ({
|
|
15
|
+
...${n},
|
|
16
|
+
handler: m.${d.name==="default"?"default":d.name}
|
|
17
|
+
}))`)}return`export const loadRoutes = async () => {
|
|
18
|
+
return Promise.all([
|
|
19
|
+
${t.join(`,
|
|
20
|
+
`)}
|
|
21
|
+
]);
|
|
22
|
+
};`}}var j=require("fs"),k=require("path");class O{routesDir;excludePatterns;static DEFAULT_EXCLUDE_PATTERNS=["*.test.ts","*.test.js","*.test.tsx","*.test.jsx","*.spec.ts","*.spec.js","*.spec.tsx","*.spec.jsx","*.tests.ts","*.tests.js","**/__tests__/**","*.interface.ts","*.type.ts","*.d.ts"];constructor(e="./routes",t){this.routesDir=k.resolve(process.cwd(),e),this.excludePatterns=t||O.DEFAULT_EXCLUDE_PATTERNS}async scan(){let e=[];if(!j.existsSync(this.routesDir))return[];try{await this.scanDirectory(this.routesDir,e)}catch(t){if(t.code==="ENOENT")return console.warn(` \u2717 Routes directory not accessible: ${this.routesDir}`),[];throw t}return e}isExcluded(e){let t=k.relative(this.routesDir,e);for(let d of this.excludePatterns){let n=d.replace(/\./g,"\\.").replace(/\*\*/g,"__GLOBSTAR__").replace(/\*/g,"[^/]*").replace(/__GLOBSTAR__/g,".*").replace(/\?/g,"."),r=new RegExp(`^${n}$`),o=t.split(k.sep).pop()||"";if(r.test(t)||r.test(o))return!0}return!1}async scanDirectory(e,t,d=""){let n=await j.promises.readdir(e);for(let r of n){let o=k.join(e,r);if((await j.promises.stat(o)).isDirectory()){let a=d?`${d}/${r}`:r;await this.scanDirectory(o,t,a)}else if(r.endsWith(".ts")||r.endsWith(".js")){if(this.isExcluded(o))continue;let a=k.relative(this.routesDir,o).replace(/\.(ts|js)$/,"").split(k.sep).join("/");try{let c=await import(process.platform==="win32"?`file:///${o.replace(/\\/g,"/")}`:o);if(c.default&&typeof c.default==="function")t.push({name:"default",path:o,method:"GET",options:{method:"GET",path:`/${a}`,expose:!0}});for(let[m,b]of Object.entries(c)){if(m==="default")continue;if(b&&typeof b==="object"&&"entry"in b&&"options"in b&&"handler"in b){let p=b;t.push({name:m,path:o,method:p.options.method,options:p.options})}else if(Array.isArray(b)&&b.length>=4){let[p,,,x]=b;t.push({name:m,path:o,method:p,options:{method:p,path:x,expose:!0}})}}}catch(s){console.error(`Failed to load route from ${o}:`,s)}}}}enableWatch(e){if(typeof Bun!=="undefined"&&Bun.env.NODE_ENV==="development")console.log(`Watching for route changes in ${this.routesDir}`),setInterval(async()=>{await e()},1000)}}class T{beforeHandlers=[];finallyHandlers=[];addBefore(...e){this.beforeHandlers.push(...e)}addFinally(...e){this.finallyHandlers.push(...e)}async executeBefore(e){if(this.beforeHandlers.length===0)return e;let t=e;for(let d of this.beforeHandlers){let n=await d(t);if(n instanceof Response)return n;t=n}return t}async executeFinally(e,t){if(this.finallyHandlers.length===0)return e;let d=e;for(let n of this.finallyHandlers)try{d=await n(d,t)}catch(r){console.error("After middleware error:",r)}return d}clone(){let e=new T;return e.beforeHandlers=[...this.beforeHandlers],e.finallyHandlers=[...this.finallyHandlers],e}clear(){this.beforeHandlers=[],this.finallyHandlers=[]}}function R(e){return process.platform==="win32"?`file:///${e.replace(/\\/g,"/")}`:e}function G(e){return RegExp(`^${e.replace(/\/+(\/|$)/g,"$1").replace(/(\/?\.?):(\w+)\+/g,"($1(?<$2>[\\s\\S]+))").replace(/(\/?\.?):(\w+)/g,"($1(?<$2>[^$1/]+?))").replace(/\./g,"\\.").replace(/(\/?)\*/g,"($1.*)?")}/*$`)}function se(e){let t=e?.["~standard"];return!!t&&typeof t==="object"&&typeof t.validate==="function"&&t.version===1}async function le(e,t){let d=await e["~standard"].validate(t),n=d?.issues;if(Array.isArray(n)&&n.length>0)return{success:!1,issues:n};return{success:!0,value:d?.value}}function ce(e){if(Array.isArray(e))return e;if(e&&typeof e==="object"&&Array.isArray(e.issues))return e.issues;if(e&&typeof e==="object"&&e.cause&&Array.isArray(e.cause.issues))return e.cause.issues;return null}function Je(e){if(!Array.isArray(e))return[];let t=[];for(let d=0;d<e.length;d++){let n=e[d],r=n;if(n&&typeof n==="object"&&"key"in n)r=n.key;if(typeof r==="string"||typeof r==="number")t.push(r);else if(typeof r==="symbol")t.push(String(r));else if(r!==void 0&&r!==null)t.push(String(r))}return t}function V(e,t){let d=[];for(let n=0;n<e.length;n++){let r=e[n],o=r,i={message:typeof o?.message==="string"&&o.message.length>0?o.message:"Invalid value",path:Je(o?.path)};if(typeof o?.code==="string")i.code=o.code;if(t)i.raw=r;d.push(i)}return d}function Q(e,t){return{error:!0,message:"Validation failed",statusCode:422,source:"validation",target:e,issues:t,timestamp:new Date().toISOString()}}class M{middlewareManager;authManager;cacheManager;routeBooleanDefaults={};developmentMode=void 0;routeDefinitions=[];routeTable=Object.create(null);routeMatchers=[];corsHeadersEntries=null;corsHandler=null;constructor(e,t,d){this.middlewareManager=e,this.authManager=t,this.cacheManager=d}setCorsHeaders(e){this.corsHeadersEntries=e}setCorsHandler(e){this.corsHandler=e}setRouteBooleanDefaults(e){this.routeBooleanDefaults={...e}}setDevelopmentMode(e){this.developmentMode=e}applyRouteBooleanDefaults(e){let t={...e},d=this.routeBooleanDefaults,n=["auth","expose","rawRequest","validate","rawResponse"];for(let r of n)if(t[r]===void 0&&d[r]!==void 0)t[r]=d[r];return t}route(e,t){let d=this.applyRouteBooleanDefaults(e),n=d.method.toUpperCase(),r=d.path,o=this.wrapHandler(d,t),i=this.getOrCreateMethodMap(r);i[n]=o,this.routeDefinitions.push({method:n,path:r,options:d})}addRoute(e){let[t,,d,n]=e;if(!n)return;let r=this.getOrCreateMethodMap(n);r[t.toUpperCase()]=d[0];let o=t.toUpperCase();this.routeDefinitions.push({method:o,path:n,options:{method:o,path:n,expose:!0}})}bulkAddRoutes(e){for(let t of e)this.addRoute(t)}addStaticRoute(e,t){let d=this.routeTable[e];if(d&&!(d instanceof Response))throw new Error(`Cannot register static route for path "${e}" because method routes already exist.`);this.routeTable[e]=t,this.removeRouteMatcher(e)}getRouteTable(){return this.routeTable}getRoutes(){let e=[];for(let t of this.routeMatchers){let d=this.routeTable[t.path];if(!d||d instanceof Response)continue;for(let[n,r]of Object.entries(d))e.push([n,t.regex,[r],t.path])}return e}getRouteDefinitions(){return[...this.routeDefinitions]}clearRoutes(){this.routeTable=Object.create(null),this.routeMatchers=[],this.routeDefinitions=[]}sortRoutes(){}async handle(e){let t;try{t=new URL(e.url)}catch{return B.badRequest("Malformed request URL")}e._parsedUrl=t;let d=t.pathname;for(let n of this.routeMatchers){let r=n.path,o=this.routeTable[r];if(!o)continue;if(o instanceof Response)continue;let i=o;if(e.method==="OPTIONS"||e.method in i){let a=d.match(n.regex);if(a){try{e.params=a.groups??{}}catch{}let s=i[e.method]??i.GET;if(s){let c=await s(e);if(c)return c}}}}return H.NOT_FOUND.clone()}prepareRequest(e,t){if(!e.context)e.context={};let d=!!e.params&&typeof e.params==="object"&&!Array.isArray(e.params)&&Object.keys(e.params).length===0;if(t?.params!==void 0&&(e.params===void 0||d))try{e.params=t.params}catch{}if(t?.route!==void 0)e.route=t.route;if(t?.metadata!==void 0)e.metadata=t.metadata;if(e.query==null&&e.url)try{Object.defineProperty(e,"query",{get(){let n=this._parsedUrl??new URL(this.url),r=M.parseQuery(n);return Object.defineProperty(this,"query",{value:r,writable:!0,configurable:!0,enumerable:!0}),r},set(n){Object.defineProperty(this,"query",{value:n,writable:!0,configurable:!0,enumerable:!0})},configurable:!0,enumerable:!0})}catch{let n=e._parsedUrl??new URL(e.url);try{e.query=M.parseQuery(n)}catch{}}if(!Object.getOwnPropertyDescriptor(e,"cookies"))Object.defineProperty(e,"cookies",{get(){let n=this.headers.get("cookie")??"",r={};if(n)for(let o of n.split(";")){let i=o.indexOf("=");if(i>0)r[o.slice(0,i).trim()]=o.slice(i+1).trim()}return Object.defineProperty(this,"cookies",{value:r,writable:!0,configurable:!0,enumerable:!0}),r},configurable:!0,enumerable:!0})}resolveFallbackParams(e,t){if(!t)return;let d=e.params;if(d&&typeof d==="object"&&!Array.isArray(d)&&Object.keys(d).length>0)return;let n;try{n=(e._parsedUrl??new URL(e.url)).pathname}catch{return}let r=n.match(t);if(!r?.groups)return;return r.groups}wrapHandler(e,t){let d=e.path,n=d.includes(":")?G(d):null;return async(r)=>{let o=r,i=this.resolveFallbackParams(r,n);this.prepareRequest(o,{params:i,route:d,metadata:e.metadata});try{if(e.expose===!1)return B.forbidden("Forbidden");let a=await this.middlewareManager.executeBefore(o);if(a instanceof Response)return a;let s=a;if(e.auth)try{await this.authManager.authenticate(s)}catch(u){return B.unauthorized(u instanceof Error?u.message:"Authentication failed",e.responseContentType)}if(!e.rawRequest&&s.method!=="GET"&&s.method!=="HEAD"){let u=null;try{let f=s.headers.get("content-type");if(f?.startsWith("application/json"))u=await s.json();else if(f?.startsWith("application/x-www-form-urlencoded"))u=Object.fromEntries(await s.formData());else if(f?.startsWith("multipart/form-data"))u=await s.formData();else u=await s.text()}catch{u=null}this.setContentAndBodyAlias(s,u)}let c=await this.validateInputSchema(s,e);if(c)return c;let m,b=e.cache;if(b&&typeof b==="number"&&b>0){let u=this.cacheManager.generateKey(s,{authUser:s.authUser});m=await this.cacheManager.get(u,async()=>{let f=await t(s);if(f instanceof Response)return{_isResponse:!0,body:await f.text(),status:f.status,headers:Object.fromEntries(f.headers.entries())};return f},b)}else if(b&&typeof b==="object"&&b.ttl){let u=b.key||this.cacheManager.generateKey(s,{authUser:s.authUser});m=await this.cacheManager.get(u,async()=>{let f=await t(s);if(f instanceof Response)return{_isResponse:!0,body:await f.text(),status:f.status,headers:Object.fromEntries(f.headers.entries())};return f},b.ttl)}else m=await t(s);if(m&&typeof m==="object"&&m._isResponse===!0)m=new Response(m.body,{status:m.status,headers:m.headers});let p;if(e.rawResponse||m instanceof Response)p=m instanceof Response?m:new Response(m);else p=I(200,m,e.responseContentType);p=await this.middlewareManager.executeFinally(p,s);let x=this.corsHeadersEntries;if(x)for(let[u,f]of x)p.headers.set(u,f);else{let u=this.corsHandler;if(u)p=u(p,s)}return p}catch(a){if(a instanceof Response)return a;return console.error("Route handler error:",a),B.internalServerError(a instanceof Error?a.message:String(a),e.responseContentType)}}}isDevelopmentMode(){if(this.developmentMode!==void 0)return this.developmentMode;return(typeof Bun!=="undefined"?Bun.env.NODE_ENV:"development")!=="production"}async buildInputValidationPayload(e,t){let d=e.content;if(t.rawRequest&&e.method!=="GET"&&e.method!=="HEAD")try{d=await e.clone().text()}catch{d=null}return{params:e.params??{},query:e.query??{},headers:Object.fromEntries(e.headers.entries()),cookies:e.cookies??{},body:d}}applyValidatedInput(e,t){if(e.validatedInput=t,!t||typeof t!=="object")return;let d=t;if("params"in d)try{e.params=d.params}catch{}if("query"in d)try{e.query=d.query}catch{}if("cookies"in d)try{e.cookies=d.cookies}catch{}if("body"in d)this.setContentAndBodyAlias(e,d.body)}setContentAndBodyAlias(e,t){try{e.content=t}catch{return}this.setBodyAlias(e,t)}setBodyAlias(e,t){try{e.body=t}catch{}}async validateInputSchema(e,t){let d=t.schema?.input;if(!d)return null;if(t.validate===!1)return null;if(!se(d))return B.internalServerError("Invalid route schema configuration",t.responseContentType);let n=this.isDevelopmentMode(),r=await this.buildInputValidationPayload(e,t);try{let o=await le(d,r);if(o.success===!1){let i=V(o.issues,n);return I(422,Q("input",i),t.responseContentType)}return this.applyValidatedInput(e,o.value),null}catch(o){let i=ce(o);if(i){let a=V(i,n);return I(422,Q("input",a),t.responseContentType)}return B.internalServerError(o instanceof Error?o.message:"Validation failed",t.responseContentType)}}getOrCreateMethodMap(e){let t=this.routeTable[e];if(t instanceof Response)throw new Error(`Cannot register method route for path "${e}" because a static route already exists.`);if(t)return t;let d=Object.create(null);return this.routeTable[e]=d,this.addRouteMatcher(e),d}addRouteMatcher(e){if(this.routeMatchers.some((t)=>t.path===e))return;this.routeMatchers.push({path:e,regex:G(e),specificity:this.routeSpecificityScore(e)}),this.routeMatchers.sort((t,d)=>{if(t.specificity!==d.specificity)return d.specificity-t.specificity;return t.path.localeCompare(d.path)})}removeRouteMatcher(e){this.routeMatchers=this.routeMatchers.filter((t)=>t.path!==e)}static parseQuery(e){let t={};for(let[d,n]of e.searchParams)if(d in t){let r=t[d];if(Array.isArray(r))r.push(n);else t[d]=[r,n]}else t[d]=n;return t}routeSpecificityScore(e){let o=e.split("/").filter(Boolean),i=0;for(let a of o)if(a.includes("*"))i+=1;else if(a.startsWith(":"))i+=10;else i+=1000;if(i+=e.length,!e.includes(":")&&!e.includes("*"))i+=1e4;return i}}var ne=require("fs"),Le=require("path");function be(e,t){if(!e){if(typeof t.origin==="string"){if(t.origin==="*"&&t.credentials)return null;return t.origin}return null}if(typeof t.origin==="string"){if(t.origin==="*")return t.credentials?e:"*";return t.origin===e?e:null}if(Array.isArray(t.origin))return t.origin.includes(e)?e:null;if(typeof t.origin==="function")return t.origin(e)?e:null;return null}function me(e){return typeof e.origin==="string"&&e.origin==="*"&&e.credentials||Array.isArray(e.origin)||typeof e.origin==="function"}function pe(e,t,d){let n={};if(e){if(n["access-control-allow-origin"]=e,n["access-control-allow-methods"]=t.allowMethods,n["access-control-allow-headers"]=t.allowHeaders,n["access-control-expose-headers"]=t.exposeHeaders,n["access-control-max-age"]=String(t.maxAge),t.credentials)n["access-control-allow-credentials"]="true";if(d)n.vary="Origin"}return n}function Se(e,t){if(!e)return t;let d=e.split(",").map((r)=>r.trim()).filter(Boolean);if(!d.map((r)=>r.toLowerCase()).includes(t.toLowerCase()))d.push(t);return d.join(", ")}function ue(e){return{preflight(t){let d=t.headers.get("origin")??void 0,n=be(d,e),r=Boolean(d&&n&&me(e));return new Response(null,{status:204,headers:pe(n,e,r)})},corsify(t,d){let n=d.headers.get("origin")??void 0,r=be(n,e);if(!r)return t;let o=Boolean(n&&me(e)),i=pe(r,e,o);for(let[a,s]of Object.entries(i)){if(a==="vary"){t.headers.set("vary",Se(t.headers.get("vary"),s));continue}t.headers.set(a,s)}return t}}}function fe(e,t,d,n,r,o,i,a,s){let c=JSON.stringify(e).replace(/<\/script/gi,"<\\/script"),m=JSON.stringify(t),b=JSON.stringify(d),p=JSON.stringify(n),x=JSON.stringify(r),u=JSON.stringify(o),f=JSON.stringify(i),Oe=JSON.stringify(a),Te=JSON.stringify(s);return`<!DOCTYPE html>
|
|
23
|
+
<html lang="en">
|
|
24
|
+
<head>
|
|
25
|
+
<meta charset="UTF-8">
|
|
26
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
27
|
+
<title>Vector API Documentation</title>
|
|
28
|
+
<link rel="apple-touch-icon" sizes="180x180" href=${u}>
|
|
29
|
+
<link rel="icon" type="image/png" sizes="32x32" href=${f}>
|
|
30
|
+
<link rel="icon" type="image/png" sizes="16x16" href=${Oe}>
|
|
31
|
+
<link rel="manifest" href=${Te}>
|
|
32
|
+
<script>
|
|
33
|
+
if (localStorage.getItem('theme') === 'dark' || (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
|
34
|
+
document.documentElement.classList.add('dark');
|
|
35
|
+
} else {
|
|
36
|
+
document.documentElement.classList.remove('dark');
|
|
37
|
+
}
|
|
38
|
+
</script>
|
|
39
|
+
<script src=${b}></script>
|
|
40
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
|
41
|
+
<script>
|
|
42
|
+
tailwind.config = {
|
|
43
|
+
darkMode: 'class',
|
|
44
|
+
theme: {
|
|
45
|
+
extend: {
|
|
46
|
+
colors: {
|
|
47
|
+
brand: {
|
|
48
|
+
DEFAULT: '#00A1FF',
|
|
49
|
+
mint: '#00FF8F',
|
|
50
|
+
soft: '#E4F5FF',
|
|
51
|
+
deep: '#007BC5',
|
|
52
|
+
},
|
|
53
|
+
dark: { bg: '#0A0A0A', surface: '#111111', border: '#1F1F1F', text: '#EDEDED' },
|
|
54
|
+
light: { bg: '#FFFFFF', surface: '#F9F9F9', border: '#E5E5E5', text: '#111111' }
|
|
55
|
+
},
|
|
56
|
+
fontFamily: {
|
|
57
|
+
sans: ['Inter', 'sans-serif'],
|
|
58
|
+
mono: ['JetBrains Mono', 'monospace'],
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
</script>
|
|
64
|
+
<style>
|
|
65
|
+
:root {
|
|
66
|
+
--motion-fast: 180ms;
|
|
67
|
+
--motion-base: 280ms;
|
|
68
|
+
--motion-slow: 420ms;
|
|
69
|
+
--motion-ease: cubic-bezier(0.22, 1, 0.36, 1);
|
|
70
|
+
}
|
|
71
|
+
::-webkit-scrollbar { width: 8px; height: 8px; }
|
|
72
|
+
::-webkit-scrollbar-track { background: transparent; }
|
|
73
|
+
::-webkit-scrollbar-thumb { background: #333; border-radius: 4px; }
|
|
74
|
+
body { transition: background-color 150ms ease, color 150ms ease; }
|
|
75
|
+
#sidebar-nav a,
|
|
76
|
+
#send-btn,
|
|
77
|
+
#copy-curl,
|
|
78
|
+
#add-header-btn,
|
|
79
|
+
#expand-body-btn,
|
|
80
|
+
#expand-response-btn,
|
|
81
|
+
#expand-close,
|
|
82
|
+
#expand-apply {
|
|
83
|
+
transition:
|
|
84
|
+
transform var(--motion-fast) var(--motion-ease),
|
|
85
|
+
opacity var(--motion-fast) var(--motion-ease),
|
|
86
|
+
border-color var(--motion-fast) var(--motion-ease),
|
|
87
|
+
background-color var(--motion-fast) var(--motion-ease),
|
|
88
|
+
color var(--motion-fast) var(--motion-ease);
|
|
89
|
+
will-change: transform, opacity;
|
|
90
|
+
}
|
|
91
|
+
#sidebar-nav a:hover,
|
|
92
|
+
#send-btn:hover,
|
|
93
|
+
#add-header-btn:hover,
|
|
94
|
+
#expand-body-btn:hover,
|
|
95
|
+
#expand-response-btn:hover {
|
|
96
|
+
transform: translateY(-1px);
|
|
97
|
+
}
|
|
98
|
+
#endpoint-card {
|
|
99
|
+
transition:
|
|
100
|
+
box-shadow var(--motion-base) var(--motion-ease),
|
|
101
|
+
transform var(--motion-base) var(--motion-ease),
|
|
102
|
+
opacity var(--motion-base) var(--motion-ease);
|
|
103
|
+
}
|
|
104
|
+
.enter-fade-up {
|
|
105
|
+
animation: enterFadeUp var(--motion-base) var(--motion-ease) both;
|
|
106
|
+
}
|
|
107
|
+
.enter-stagger {
|
|
108
|
+
animation: enterStagger var(--motion-base) var(--motion-ease) both;
|
|
109
|
+
animation-delay: var(--stagger-delay, 0ms);
|
|
110
|
+
}
|
|
111
|
+
@keyframes enterFadeUp {
|
|
112
|
+
from { opacity: 0; transform: translateY(8px); }
|
|
113
|
+
to { opacity: 1; transform: translateY(0); }
|
|
114
|
+
}
|
|
115
|
+
@keyframes enterStagger {
|
|
116
|
+
from { opacity: 0; transform: translateX(-6px); }
|
|
117
|
+
to { opacity: 1; transform: translateX(0); }
|
|
118
|
+
}
|
|
119
|
+
@keyframes spin {
|
|
120
|
+
to { transform: rotate(360deg); }
|
|
121
|
+
}
|
|
122
|
+
.button-spinner {
|
|
123
|
+
display: inline-block;
|
|
124
|
+
width: 0.875rem;
|
|
125
|
+
height: 0.875rem;
|
|
126
|
+
border: 2px solid currentColor;
|
|
127
|
+
border-right-color: transparent;
|
|
128
|
+
border-radius: 9999px;
|
|
129
|
+
animation: spin 700ms linear infinite;
|
|
130
|
+
}
|
|
131
|
+
@media (prefers-reduced-motion: reduce) {
|
|
132
|
+
*, *::before, *::after {
|
|
133
|
+
animation: none !important;
|
|
134
|
+
transition: none !important;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
.json-key { color: #007bc5; }
|
|
138
|
+
.json-string { color: #334155; }
|
|
139
|
+
.json-number { color: #00a1ff; }
|
|
140
|
+
.json-boolean { color: #475569; }
|
|
141
|
+
.json-null { color: #64748b; }
|
|
142
|
+
.dark .json-key { color: #7dc9ff; }
|
|
143
|
+
.dark .json-string { color: #d1d9e6; }
|
|
144
|
+
.dark .json-number { color: #7dc9ff; }
|
|
145
|
+
.dark .json-boolean { color: #93a4bf; }
|
|
146
|
+
.dark .json-null { color: #7c8ba3; }
|
|
147
|
+
</style>
|
|
148
|
+
</head>
|
|
149
|
+
<body class="bg-light-bg text-light-text dark:bg-dark-bg dark:text-dark-text font-sans antialiased flex h-screen overflow-hidden">
|
|
150
|
+
<div id="mobile-backdrop" class="fixed inset-0 z-30 bg-black/40 opacity-0 pointer-events-none transition-opacity duration-300 md:hidden"></div>
|
|
151
|
+
<aside id="docs-sidebar" class="fixed inset-y-0 left-0 z-40 w-72 md:w-64 border-r border-light-border dark:border-dark-border bg-light-surface dark:bg-dark-surface flex flex-col flex-shrink-0 transition-transform duration-300 ease-out -translate-x-full md:translate-x-0 md:static md:z-auto transition-colors duration-150">
|
|
152
|
+
<div class="h-14 flex items-center px-5 border-b border-light-border dark:border-dark-border">
|
|
153
|
+
<div class="flex items-center">
|
|
154
|
+
<img src=${p} alt="Vector" class="h-6 w-auto block dark:hidden" />
|
|
155
|
+
<img src=${x} alt="Vector" class="h-6 w-auto hidden dark:block" />
|
|
156
|
+
</div>
|
|
157
|
+
<button id="sidebar-close" class="ml-auto p-1.5 rounded-full border border-light-border dark:border-dark-border bg-light-bg/90 dark:bg-dark-bg/90 opacity-90 hover:opacity-100 transition md:hidden" aria-label="Close Menu" title="Close Menu">
|
|
158
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
159
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
|
160
|
+
</svg>
|
|
161
|
+
</button>
|
|
162
|
+
</div>
|
|
163
|
+
<div class="p-4">
|
|
164
|
+
<div class="relative">
|
|
165
|
+
<svg class="w-4 h-4 absolute left-3 top-1/2 -translate-y-1/2 opacity-60" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
166
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
|
|
167
|
+
</svg>
|
|
168
|
+
<input
|
|
169
|
+
id="sidebar-search"
|
|
170
|
+
type="text"
|
|
171
|
+
placeholder="Search routes..."
|
|
172
|
+
class="w-full pl-9 pr-3 py-2 text-sm rounded-md border border-light-border dark:border-dark-border bg-light-bg dark:bg-dark-bg focus:outline-none focus:border-brand dark:focus:border-brand transition-colors"
|
|
173
|
+
/>
|
|
174
|
+
</div>
|
|
175
|
+
</div>
|
|
176
|
+
<nav class="flex-1 overflow-y-auto px-3 py-2 space-y-6 text-sm" id="sidebar-nav"></nav>
|
|
177
|
+
</aside>
|
|
178
|
+
|
|
179
|
+
<main class="flex-1 flex flex-col min-w-0 relative">
|
|
180
|
+
<header class="h-14 flex items-center justify-between px-6 border-b border-light-border dark:border-dark-border lg:border-none lg:bg-transparent absolute top-0 w-full z-10 bg-light-bg/80 dark:bg-dark-bg/80 backdrop-blur-sm transition-colors duration-150">
|
|
181
|
+
<div class="md:hidden flex items-center gap-2">
|
|
182
|
+
<button id="sidebar-open" class="p-1.5 rounded-full border border-light-border dark:border-dark-border bg-light-bg/90 dark:bg-dark-bg/90 opacity-90 hover:opacity-100 transition" aria-label="Open Menu" title="Open Menu">
|
|
183
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
184
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>
|
|
185
|
+
</svg>
|
|
186
|
+
</button>
|
|
187
|
+
<img src=${p} alt="Vector" class="h-5 w-auto block dark:hidden" />
|
|
188
|
+
<img src=${x} alt="Vector" class="h-5 w-auto hidden dark:block" />
|
|
189
|
+
</div>
|
|
190
|
+
<div class="flex-1"></div>
|
|
191
|
+
<button id="theme-toggle" class="p-2 rounded-md hover:bg-black/5 dark:hover:bg-white/5 transition-colors" aria-label="Toggle Dark Mode">
|
|
192
|
+
<svg class="w-5 h-5 hidden dark:block text-dark-text" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"></path></svg>
|
|
193
|
+
<svg class="w-5 h-5 block dark:hidden text-light-text" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"></path></svg>
|
|
194
|
+
</button>
|
|
195
|
+
</header>
|
|
196
|
+
|
|
197
|
+
<div class="flex-1 overflow-y-auto pt-14 pb-24">
|
|
198
|
+
<div class="max-w-[860px] mx-auto px-6 py-12 lg:py-16">
|
|
199
|
+
<div class="mb-12">
|
|
200
|
+
<h1 class="text-4xl font-bold tracking-tight mb-4" id="tag-title">API</h1>
|
|
201
|
+
<p class="text-lg opacity-80 max-w-2xl leading-relaxed" id="tag-description">Interactive API documentation.</p>
|
|
202
|
+
</div>
|
|
203
|
+
<hr class="border-t border-light-border dark:border-dark-border mb-12">
|
|
204
|
+
<div class="mb-20" id="endpoint-card">
|
|
205
|
+
<div class="flex items-center gap-3 mb-4">
|
|
206
|
+
<span id="endpoint-method" class="px-2.5 py-0.5 rounded-full text-xs font-mono font-medium"></span>
|
|
207
|
+
<h2 class="text-xl font-semibold tracking-tight" id="endpoint-title">Operation</h2>
|
|
208
|
+
</div>
|
|
209
|
+
<p class="text-sm opacity-80 mb-8 font-mono" id="endpoint-path">/</p>
|
|
210
|
+
<div class="grid grid-cols-1 lg:grid-cols-12 gap-10">
|
|
211
|
+
<div class="lg:col-span-5 space-y-8" id="params-column"></div>
|
|
212
|
+
<div class="lg:col-span-7">
|
|
213
|
+
<div class="rounded-lg border border-light-border dark:border-dark-border bg-light-bg dark:bg-dark-bg overflow-hidden group">
|
|
214
|
+
<div class="flex items-center justify-between px-4 py-2 border-b border-light-border dark:border-dark-border bg-light-surface dark:bg-dark-surface">
|
|
215
|
+
<span class="text-xs font-mono text-light-text/70 dark:text-dark-text/70">cURL</span>
|
|
216
|
+
<button class="text-xs text-light-text/50 hover:text-light-text dark:text-dark-text/50 dark:hover:text-dark-text transition-colors" id="copy-curl">Copy</button>
|
|
217
|
+
</div>
|
|
218
|
+
<pre class="p-4 text-sm font-mono text-light-text dark:text-dark-text overflow-x-auto leading-relaxed"><code id="curl-code"></code></pre>
|
|
219
|
+
</div>
|
|
220
|
+
<div class="mt-4 p-4 rounded-lg border border-light-border dark:border-dark-border bg-light-surface dark:bg-dark-surface">
|
|
221
|
+
<div class="flex items-center justify-between mb-3">
|
|
222
|
+
<h4 class="text-sm font-medium">Try it out</h4>
|
|
223
|
+
<button id="send-btn" class="px-4 py-1.5 bg-brand text-white text-sm font-semibold rounded hover:bg-brand-deep transition-colors">
|
|
224
|
+
<span class="inline-flex items-center gap-2">
|
|
225
|
+
<span id="send-btn-spinner" class="button-spinner hidden" aria-hidden="true"></span>
|
|
226
|
+
<span id="send-btn-label">Submit</span>
|
|
227
|
+
</span>
|
|
228
|
+
</button>
|
|
229
|
+
</div>
|
|
230
|
+
<div class="space-y-4">
|
|
231
|
+
<div>
|
|
232
|
+
<div id="request-param-inputs" class="space-y-3"></div>
|
|
233
|
+
</div>
|
|
234
|
+
|
|
235
|
+
<div>
|
|
236
|
+
<div class="flex items-center justify-between mb-2">
|
|
237
|
+
<p class="text-xs font-semibold uppercase tracking-wider opacity-60">Headers</p>
|
|
238
|
+
<button id="add-header-btn" class="p-1.5 rounded-full border border-light-border dark:border-dark-border bg-light-bg/90 dark:bg-dark-bg/90 opacity-90 hover:opacity-100 hover:border-brand/60 transition-colors" aria-label="Add Header" title="Add Header">
|
|
239
|
+
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
240
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 5v14m-7-7h14"></path>
|
|
241
|
+
</svg>
|
|
242
|
+
</button>
|
|
243
|
+
</div>
|
|
244
|
+
<div id="header-inputs" class="space-y-2"></div>
|
|
245
|
+
</div>
|
|
246
|
+
|
|
247
|
+
<div id="request-body-section">
|
|
248
|
+
<div class="flex items-center justify-between mb-2">
|
|
249
|
+
<p class="text-xs font-semibold uppercase tracking-wider opacity-60">Request Body</p>
|
|
250
|
+
</div>
|
|
251
|
+
<div class="relative h-40 rounded border border-light-border dark:border-dark-border bg-light-bg dark:bg-dark-bg overflow-hidden">
|
|
252
|
+
<pre id="body-highlight" class="absolute inset-0 m-0 p-3 pr-11 text-xs font-mono leading-5 overflow-auto whitespace-pre-wrap break-words pointer-events-none"></pre>
|
|
253
|
+
<textarea id="body-input" class="absolute inset-0 w-full h-full p-3 pr-11 text-xs font-mono leading-5 bg-transparent text-transparent caret-black dark:caret-white resize-none focus:outline-none overflow-auto placeholder:text-light-text/50 dark:placeholder:text-dark-text/40" placeholder='{"key":"value"}' spellcheck="false" autocapitalize="off" autocorrect="off"></textarea>
|
|
254
|
+
<button id="expand-body-btn" class="absolute bottom-2 right-2 p-1.5 rounded-full border border-light-border dark:border-dark-border bg-light-surface/95 dark:bg-dark-surface/95 opacity-90 hover:opacity-100 hover:border-brand/60 transition-colors" aria-label="Expand Request Body" title="Expand Request Body">
|
|
255
|
+
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
256
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 9V4h5M20 15v5h-5M15 4h5v5M9 20H4v-5"></path>
|
|
257
|
+
</svg>
|
|
258
|
+
</button>
|
|
259
|
+
</div>
|
|
260
|
+
</div>
|
|
261
|
+
|
|
262
|
+
<div id="response-section">
|
|
263
|
+
<div class="flex items-center justify-between mb-2">
|
|
264
|
+
<p class="text-xs font-semibold uppercase tracking-wider opacity-60">Response</p>
|
|
265
|
+
</div>
|
|
266
|
+
<div class="relative">
|
|
267
|
+
<pre id="result" class="p-3 pr-11 text-xs font-mono rounded border border-light-border dark:border-dark-border overflow-x-auto min-h-[140px]"></pre>
|
|
268
|
+
<button id="expand-response-btn" class="absolute bottom-2 right-2 p-1.5 rounded-full border border-light-border dark:border-dark-border bg-light-surface/95 dark:bg-dark-surface/95 opacity-90 hover:opacity-100 hover:border-brand/60 transition-colors" aria-label="Expand Response" title="Expand Response">
|
|
269
|
+
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
270
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 9V4h5M20 15v5h-5M15 4h5v5M9 20H4v-5"></path>
|
|
271
|
+
</svg>
|
|
272
|
+
</button>
|
|
273
|
+
</div>
|
|
274
|
+
</div>
|
|
275
|
+
</div>
|
|
276
|
+
</div>
|
|
277
|
+
</div>
|
|
278
|
+
</div>
|
|
279
|
+
</div>
|
|
280
|
+
</div>
|
|
281
|
+
</div>
|
|
282
|
+
</main>
|
|
283
|
+
|
|
284
|
+
<div id="expand-modal" class="fixed inset-0 z-50 hidden items-center justify-center bg-black/60 p-4">
|
|
285
|
+
<div class="w-full max-w-5xl rounded-lg border border-light-border dark:border-dark-border bg-light-surface dark:bg-dark-surface p-4">
|
|
286
|
+
<div class="flex items-center justify-between mb-3">
|
|
287
|
+
<h3 id="expand-modal-title" class="text-sm font-semibold">Expanded View</h3>
|
|
288
|
+
<div class="flex items-center gap-2">
|
|
289
|
+
<button id="expand-apply" class="hidden text-sm px-3 py-1.5 rounded bg-brand text-white font-semibold hover:bg-brand-deep transition-colors">Apply</button>
|
|
290
|
+
<button id="expand-close" class="p-1.5 rounded-full border border-light-border dark:border-dark-border bg-light-bg/90 dark:bg-dark-bg/90 opacity-90 hover:opacity-100 hover:border-brand/60 transition-colors" aria-label="Close Modal" title="Close Modal">
|
|
291
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
292
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
|
293
|
+
</svg>
|
|
294
|
+
</button>
|
|
295
|
+
</div>
|
|
296
|
+
</div>
|
|
297
|
+
<div id="expand-editor-shell" class="hidden relative w-full h-[70vh] rounded border border-light-border dark:border-dark-border bg-light-bg dark:bg-dark-bg overflow-hidden">
|
|
298
|
+
<pre id="expand-editor-highlight" class="absolute inset-0 m-0 p-3 text-sm font-mono leading-6 overflow-auto whitespace-pre-wrap break-words pointer-events-none"></pre>
|
|
299
|
+
<textarea id="expand-editor" class="absolute inset-0 w-full h-full p-3 text-sm font-mono leading-6 bg-transparent text-transparent caret-black dark:caret-white resize-none focus:outline-none overflow-auto" spellcheck="false" autocapitalize="off" autocorrect="off"></textarea>
|
|
300
|
+
</div>
|
|
301
|
+
<pre id="expand-viewer" class="hidden w-full h-[70vh] text-sm p-3 rounded border border-light-border dark:border-dark-border bg-light-bg dark:bg-dark-bg overflow-auto font-mono"></pre>
|
|
302
|
+
</div>
|
|
303
|
+
</div>
|
|
304
|
+
|
|
305
|
+
<script>
|
|
306
|
+
const spec = ${c};
|
|
307
|
+
const openapiPath = ${m};
|
|
308
|
+
const methodBadgeDefault = "bg-black/5 text-light-text/80 dark:bg-white/10 dark:text-dark-text/80";
|
|
309
|
+
const methodBadge = {
|
|
310
|
+
GET: "bg-brand-soft text-brand-deep dark:bg-brand/20 dark:text-brand",
|
|
311
|
+
POST: "bg-brand-soft text-brand-deep dark:bg-brand/20 dark:text-brand",
|
|
312
|
+
PUT: "bg-brand-soft text-brand-deep dark:bg-brand/20 dark:text-brand",
|
|
313
|
+
PATCH: "bg-brand-soft text-brand-deep dark:bg-brand/20 dark:text-brand",
|
|
314
|
+
DELETE: "bg-brand-soft text-brand-deep dark:bg-brand/20 dark:text-brand",
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
function getOperations() {
|
|
318
|
+
const httpMethods = new Set([
|
|
319
|
+
"get",
|
|
320
|
+
"post",
|
|
321
|
+
"put",
|
|
322
|
+
"patch",
|
|
323
|
+
"delete",
|
|
324
|
+
"head",
|
|
325
|
+
"options",
|
|
326
|
+
]);
|
|
327
|
+
|
|
328
|
+
const humanizePath = (path) =>
|
|
329
|
+
path
|
|
330
|
+
.replace(/^\\/+/, "")
|
|
331
|
+
.replace(/[{}]/g, "")
|
|
332
|
+
.replace(/[\\/_]+/g, " ")
|
|
333
|
+
.trim() || "root";
|
|
334
|
+
|
|
335
|
+
const toTitleCase = (value) =>
|
|
336
|
+
value.replace(/\\w\\S*/g, (word) => word.charAt(0).toUpperCase() + word.slice(1));
|
|
337
|
+
|
|
338
|
+
const getDisplayName = (op, method, path) => {
|
|
339
|
+
if (typeof op.summary === "string" && op.summary.trim()) {
|
|
340
|
+
return op.summary.trim();
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (typeof op.operationId === "string" && op.operationId.trim()) {
|
|
344
|
+
const withoutPrefix = op.operationId.replace(
|
|
345
|
+
new RegExp("^" + method + "_+", "i"),
|
|
346
|
+
"",
|
|
347
|
+
);
|
|
348
|
+
const readable = withoutPrefix.replace(/_+/g, " ").trim();
|
|
349
|
+
if (readable) return toTitleCase(readable);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return toTitleCase(humanizePath(path));
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
const ops = [];
|
|
356
|
+
const paths = spec.paths || {};
|
|
357
|
+
for (const path of Object.keys(paths)) {
|
|
358
|
+
const methods = paths[path] || {};
|
|
359
|
+
for (const method of Object.keys(methods)) {
|
|
360
|
+
if (!httpMethods.has(method)) continue;
|
|
361
|
+
const op = methods[method];
|
|
362
|
+
ops.push({
|
|
363
|
+
path,
|
|
364
|
+
method: method.toUpperCase(),
|
|
365
|
+
operation: op,
|
|
366
|
+
tag: (op.tags && op.tags[0]) || "default",
|
|
367
|
+
name: getDisplayName(op, method, path),
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
return ops;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const operations = getOperations();
|
|
375
|
+
let selected = operations[0] || null;
|
|
376
|
+
const operationParamValues = new Map();
|
|
377
|
+
const operationBodyDrafts = new Map();
|
|
378
|
+
const requestHeaders = [{ key: "Authorization", value: "" }];
|
|
379
|
+
let expandModalMode = null;
|
|
380
|
+
let isMobileSidebarOpen = false;
|
|
381
|
+
let sidebarSearchQuery = "";
|
|
382
|
+
|
|
383
|
+
function setMobileSidebarOpen(open) {
|
|
384
|
+
const sidebar = document.getElementById("docs-sidebar");
|
|
385
|
+
const backdrop = document.getElementById("mobile-backdrop");
|
|
386
|
+
const openBtn = document.getElementById("sidebar-open");
|
|
387
|
+
if (!sidebar || !backdrop || !openBtn) return;
|
|
388
|
+
|
|
389
|
+
isMobileSidebarOpen = open;
|
|
390
|
+
sidebar.classList.toggle("-translate-x-full", !open);
|
|
391
|
+
backdrop.classList.toggle("opacity-0", !open);
|
|
392
|
+
backdrop.classList.toggle("pointer-events-none", !open);
|
|
393
|
+
openBtn.setAttribute("aria-expanded", open ? "true" : "false");
|
|
394
|
+
document.body.classList.toggle("overflow-hidden", open);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function getOperationKey(op) {
|
|
398
|
+
return op.method + " " + op.path;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function getOperationParameterGroups(op) {
|
|
402
|
+
const params =
|
|
403
|
+
op &&
|
|
404
|
+
op.operation &&
|
|
405
|
+
Array.isArray(op.operation.parameters)
|
|
406
|
+
? op.operation.parameters
|
|
407
|
+
: [];
|
|
408
|
+
|
|
409
|
+
return {
|
|
410
|
+
all: params,
|
|
411
|
+
path: params.filter((p) => p.in === "path"),
|
|
412
|
+
query: params.filter((p) => p.in === "query"),
|
|
413
|
+
headers: params.filter((p) => p.in === "header"),
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function getParameterValues(op) {
|
|
418
|
+
const key = getOperationKey(op);
|
|
419
|
+
if (!operationParamValues.has(key)) {
|
|
420
|
+
operationParamValues.set(key, {});
|
|
421
|
+
}
|
|
422
|
+
return operationParamValues.get(key);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function getBodyDraft(op) {
|
|
426
|
+
const key = getOperationKey(op);
|
|
427
|
+
return operationBodyDrafts.get(key);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function setBodyDraft(op, bodyValue) {
|
|
431
|
+
const key = getOperationKey(op);
|
|
432
|
+
operationBodyDrafts.set(key, bodyValue);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function resolvePath(pathTemplate, pathParams, values) {
|
|
436
|
+
let resolved = pathTemplate;
|
|
437
|
+
for (const param of pathParams) {
|
|
438
|
+
const rawValue = values[param.name];
|
|
439
|
+
if (rawValue === undefined || rawValue === null || rawValue === "") {
|
|
440
|
+
continue;
|
|
441
|
+
}
|
|
442
|
+
const placeholder = "{" + param.name + "}";
|
|
443
|
+
resolved = resolved
|
|
444
|
+
.split(placeholder)
|
|
445
|
+
.join(encodeURIComponent(String(rawValue)));
|
|
446
|
+
}
|
|
447
|
+
return resolved;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function buildRequestPath(op, pathParams, queryParams, values) {
|
|
451
|
+
const resolvedPath = resolvePath(op.path, pathParams, values);
|
|
452
|
+
const query = new URLSearchParams();
|
|
453
|
+
|
|
454
|
+
for (const param of queryParams) {
|
|
455
|
+
const rawValue = values[param.name];
|
|
456
|
+
if (rawValue === undefined || rawValue === null || rawValue === "") {
|
|
457
|
+
continue;
|
|
458
|
+
}
|
|
459
|
+
query.append(param.name, String(rawValue));
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
const queryString = query.toString();
|
|
463
|
+
return queryString ? resolvedPath + "?" + queryString : resolvedPath;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
function schemaDefaultValue(schema) {
|
|
467
|
+
if (!schema || typeof schema !== "object") return null;
|
|
468
|
+
if (schema.default !== undefined) return schema.default;
|
|
469
|
+
if (schema.example !== undefined) return schema.example;
|
|
470
|
+
if (Array.isArray(schema.enum) && schema.enum.length > 0) return schema.enum[0];
|
|
471
|
+
if (Array.isArray(schema.oneOf) && schema.oneOf.length > 0) {
|
|
472
|
+
return schemaDefaultValue(schema.oneOf[0]);
|
|
473
|
+
}
|
|
474
|
+
if (Array.isArray(schema.anyOf) && schema.anyOf.length > 0) {
|
|
475
|
+
return schemaDefaultValue(schema.anyOf[0]);
|
|
476
|
+
}
|
|
477
|
+
if (Array.isArray(schema.allOf) && schema.allOf.length > 0) {
|
|
478
|
+
return schemaDefaultValue(schema.allOf[0]);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
switch (schema.type) {
|
|
482
|
+
case "string":
|
|
483
|
+
return "";
|
|
484
|
+
case "number":
|
|
485
|
+
case "integer":
|
|
486
|
+
return 0;
|
|
487
|
+
case "boolean":
|
|
488
|
+
return false;
|
|
489
|
+
case "array":
|
|
490
|
+
return [];
|
|
491
|
+
case "object": {
|
|
492
|
+
const required = Array.isArray(schema.required) ? schema.required : [];
|
|
493
|
+
const properties = schema.properties && typeof schema.properties === "object"
|
|
494
|
+
? schema.properties
|
|
495
|
+
: {};
|
|
496
|
+
const obj = {};
|
|
497
|
+
for (const fieldName of required) {
|
|
498
|
+
obj[fieldName] = schemaDefaultValue(properties[fieldName]);
|
|
499
|
+
}
|
|
500
|
+
return obj;
|
|
501
|
+
}
|
|
502
|
+
default:
|
|
503
|
+
return null;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
function buildRequiredBodyPrefill(schema) {
|
|
508
|
+
if (!schema || typeof schema !== "object") return "";
|
|
509
|
+
const prefillValue = schemaDefaultValue(schema);
|
|
510
|
+
if (
|
|
511
|
+
prefillValue &&
|
|
512
|
+
typeof prefillValue === "object" &&
|
|
513
|
+
!Array.isArray(prefillValue) &&
|
|
514
|
+
Object.keys(prefillValue).length === 0
|
|
515
|
+
) {
|
|
516
|
+
return "";
|
|
517
|
+
}
|
|
518
|
+
try {
|
|
519
|
+
return JSON.stringify(prefillValue, null, 2);
|
|
520
|
+
} catch {
|
|
521
|
+
return "";
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
function hasMeaningfulRequestBodySchema(schema) {
|
|
526
|
+
if (!schema || typeof schema !== "object") return false;
|
|
527
|
+
if (Array.isArray(schema.oneOf) && schema.oneOf.length > 0) return true;
|
|
528
|
+
if (Array.isArray(schema.anyOf) && schema.anyOf.length > 0) return true;
|
|
529
|
+
if (Array.isArray(schema.allOf) && schema.allOf.length > 0) return true;
|
|
530
|
+
if (schema.type && schema.type !== "object") return true;
|
|
531
|
+
if (schema.additionalProperties !== undefined) return true;
|
|
532
|
+
if (Array.isArray(schema.required) && schema.required.length > 0) return true;
|
|
533
|
+
if (schema.properties && typeof schema.properties === "object") {
|
|
534
|
+
return Object.keys(schema.properties).length > 0;
|
|
535
|
+
}
|
|
536
|
+
return false;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
function renderSidebar() {
|
|
540
|
+
const nav = document.getElementById("sidebar-nav");
|
|
541
|
+
const groups = new Map();
|
|
542
|
+
const query = sidebarSearchQuery.trim().toLowerCase();
|
|
543
|
+
const visibleOps = query
|
|
544
|
+
? operations.filter((op) => {
|
|
545
|
+
const haystack = [
|
|
546
|
+
op.name,
|
|
547
|
+
op.path,
|
|
548
|
+
op.method,
|
|
549
|
+
op.tag,
|
|
550
|
+
]
|
|
551
|
+
.join(" ")
|
|
552
|
+
.toLowerCase();
|
|
553
|
+
return haystack.includes(query);
|
|
554
|
+
})
|
|
555
|
+
: operations;
|
|
556
|
+
|
|
557
|
+
for (const op of visibleOps) {
|
|
558
|
+
if (!groups.has(op.tag)) groups.set(op.tag, []);
|
|
559
|
+
groups.get(op.tag).push(op);
|
|
560
|
+
}
|
|
561
|
+
nav.innerHTML = "";
|
|
562
|
+
if (visibleOps.length === 0) {
|
|
563
|
+
nav.innerHTML =
|
|
564
|
+
'<p class="px-2 text-xs opacity-60">No routes match your search.</p>';
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
for (const [tag, ops] of groups.entries()) {
|
|
568
|
+
ops.sort((a, b) => {
|
|
569
|
+
const byName = a.name.localeCompare(b.name, undefined, { sensitivity: "base" });
|
|
570
|
+
if (byName !== 0) return byName;
|
|
571
|
+
|
|
572
|
+
const byPath = a.path.localeCompare(b.path, undefined, { sensitivity: "base" });
|
|
573
|
+
if (byPath !== 0) return byPath;
|
|
574
|
+
|
|
575
|
+
return a.method.localeCompare(b.method, undefined, { sensitivity: "base" });
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
const block = document.createElement("div");
|
|
579
|
+
block.innerHTML = '<h3 class="px-2 mb-2 font-semibold text-xs uppercase tracking-wider opacity-50"></h3><ul class="space-y-0.5"></ul>';
|
|
580
|
+
block.querySelector("h3").textContent = tag;
|
|
581
|
+
const list = block.querySelector("ul");
|
|
582
|
+
for (const op of ops) {
|
|
583
|
+
const li = document.createElement("li");
|
|
584
|
+
li.className = "enter-stagger";
|
|
585
|
+
li.style.setProperty("--stagger-delay", String(Math.min(list.children.length * 22, 180)) + "ms");
|
|
586
|
+
const a = document.createElement("a");
|
|
587
|
+
a.href = "#";
|
|
588
|
+
a.className = op === selected
|
|
589
|
+
? "block px-2 py-1.5 rounded-md bg-brand-soft/70 dark:bg-brand/20 text-brand-deep dark:text-brand font-medium transition-colors"
|
|
590
|
+
: "block px-2 py-1.5 rounded-md hover:bg-black/5 dark:hover:bg-white/5 transition-colors";
|
|
591
|
+
|
|
592
|
+
const row = document.createElement("span");
|
|
593
|
+
row.className = "flex items-center gap-2";
|
|
594
|
+
|
|
595
|
+
const method = document.createElement("span");
|
|
596
|
+
method.className = "px-1.5 py-0.5 rounded text-[10px] font-mono font-semibold " + (methodBadge[op.method] || methodBadgeDefault);
|
|
597
|
+
method.textContent = op.method;
|
|
598
|
+
|
|
599
|
+
const name = document.createElement("span");
|
|
600
|
+
name.textContent = op.name;
|
|
601
|
+
|
|
602
|
+
row.appendChild(method);
|
|
603
|
+
row.appendChild(name);
|
|
604
|
+
a.appendChild(row);
|
|
605
|
+
|
|
606
|
+
a.onclick = (e) => {
|
|
607
|
+
e.preventDefault();
|
|
608
|
+
selected = op;
|
|
609
|
+
renderSidebar();
|
|
610
|
+
renderEndpoint();
|
|
611
|
+
if (window.innerWidth < 768) {
|
|
612
|
+
setMobileSidebarOpen(false);
|
|
613
|
+
}
|
|
614
|
+
};
|
|
615
|
+
li.appendChild(a);
|
|
616
|
+
list.appendChild(li);
|
|
617
|
+
}
|
|
618
|
+
nav.appendChild(block);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
function renderParamSection(title, params) {
|
|
623
|
+
if (!params.length) return "";
|
|
624
|
+
let rows = "";
|
|
625
|
+
for (const p of params) {
|
|
626
|
+
const type = escapeHtml((p.schema && p.schema.type) || "unknown");
|
|
627
|
+
const name = escapeHtml(p.name || "");
|
|
628
|
+
rows += '<div class="py-2 flex justify-between border-b border-light-border/50 dark:border-dark-border/50"><div><code class="text-sm font-mono">' + name + '</code><span class="text-xs text-brand ml-2">' + (p.required ? "required" : "optional") + '</span></div><span class="text-xs font-mono opacity-60">' + type + '</span></div>';
|
|
629
|
+
}
|
|
630
|
+
return '<div><h3 class="text-sm font-semibold mb-3 flex items-center border-b border-light-border dark:border-dark-border pb-2">' + escapeHtml(title) + "</h3>" + rows + "</div>";
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
function getSchemaTypeLabel(schema) {
|
|
634
|
+
if (!schema || typeof schema !== "object") return "unknown";
|
|
635
|
+
if (Array.isArray(schema.type)) return schema.type.join(" | ");
|
|
636
|
+
if (schema.type) return String(schema.type);
|
|
637
|
+
if (schema.properties) return "object";
|
|
638
|
+
if (schema.items) return "array";
|
|
639
|
+
if (Array.isArray(schema.oneOf)) return "oneOf";
|
|
640
|
+
if (Array.isArray(schema.anyOf)) return "anyOf";
|
|
641
|
+
if (Array.isArray(schema.allOf)) return "allOf";
|
|
642
|
+
return "unknown";
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
function buildSchemaChildren(schema) {
|
|
646
|
+
if (!schema || typeof schema !== "object") return [];
|
|
647
|
+
|
|
648
|
+
const children = [];
|
|
649
|
+
|
|
650
|
+
if (schema.properties && typeof schema.properties === "object") {
|
|
651
|
+
const requiredSet = new Set(
|
|
652
|
+
Array.isArray(schema.required) ? schema.required : [],
|
|
653
|
+
);
|
|
654
|
+
for (const [name, childSchema] of Object.entries(schema.properties)) {
|
|
655
|
+
children.push({
|
|
656
|
+
name,
|
|
657
|
+
schema: childSchema || {},
|
|
658
|
+
required: requiredSet.has(name),
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
if (schema.items) {
|
|
664
|
+
children.push({
|
|
665
|
+
name: "items[]",
|
|
666
|
+
schema: schema.items,
|
|
667
|
+
required: true,
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
return children;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
function renderSchemaFieldNode(field, depth) {
|
|
675
|
+
const schema = field.schema || {};
|
|
676
|
+
const name = escapeHtml(field.name || "field");
|
|
677
|
+
const requiredLabel = field.required ? "required" : "optional";
|
|
678
|
+
const type = escapeHtml(getSchemaTypeLabel(schema));
|
|
679
|
+
const children = buildSchemaChildren(schema);
|
|
680
|
+
const padding = depth * 14;
|
|
681
|
+
|
|
682
|
+
if (!children.length) {
|
|
683
|
+
return (
|
|
684
|
+
'<div class="py-2 border-b border-light-border/50 dark:border-dark-border/50" style="padding-left:' +
|
|
685
|
+
padding +
|
|
686
|
+
'px"><div class="flex justify-between"><div><code class="text-sm font-mono">' +
|
|
687
|
+
name +
|
|
688
|
+
'</code><span class="text-xs text-brand ml-2">' +
|
|
689
|
+
requiredLabel +
|
|
690
|
+
'</span></div><span class="text-xs font-mono opacity-60">' +
|
|
691
|
+
type +
|
|
692
|
+
"</span></div></div>"
|
|
693
|
+
);
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
let nested = "";
|
|
697
|
+
for (const child of children) {
|
|
698
|
+
nested += renderSchemaFieldNode(child, depth + 1);
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
return (
|
|
702
|
+
'<details class="border-b border-light-border/50 dark:border-dark-border/50" open>' +
|
|
703
|
+
'<summary class="list-none cursor-pointer py-2 flex justify-between items-center" style="padding-left:' +
|
|
704
|
+
padding +
|
|
705
|
+
'px"><div class="flex items-center gap-2"><span class="text-xs opacity-70">\u25BE</span><code class="text-sm font-mono">' +
|
|
706
|
+
name +
|
|
707
|
+
'</code><span class="text-xs text-brand">' +
|
|
708
|
+
requiredLabel +
|
|
709
|
+
'</span></div><span class="text-xs font-mono opacity-60">' +
|
|
710
|
+
type +
|
|
711
|
+
"</span></summary>" +
|
|
712
|
+
'<div class="pb-1">' +
|
|
713
|
+
nested +
|
|
714
|
+
"</div></details>"
|
|
715
|
+
);
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
function renderRequestBodySchemaSection(schema) {
|
|
719
|
+
if (!schema || typeof schema !== "object") return "";
|
|
720
|
+
const rootChildren = buildSchemaChildren(schema);
|
|
721
|
+
if (!rootChildren.length) return "";
|
|
722
|
+
|
|
723
|
+
let rows = "";
|
|
724
|
+
for (const child of rootChildren) {
|
|
725
|
+
rows += renderSchemaFieldNode(child, 0);
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
return (
|
|
729
|
+
'<div><h3 class="text-sm font-semibold mb-3 flex items-center border-b border-light-border dark:border-dark-border pb-2">Request Body</h3>' +
|
|
730
|
+
rows +
|
|
731
|
+
"</div>"
|
|
732
|
+
);
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
function renderResponseSchemasSection(responses) {
|
|
736
|
+
if (!responses || typeof responses !== "object") return "";
|
|
737
|
+
|
|
738
|
+
const statusCodes = Object.keys(responses).sort((a, b) => {
|
|
739
|
+
const aNum = Number(a);
|
|
740
|
+
const bNum = Number(b);
|
|
741
|
+
if (Number.isInteger(aNum) && Number.isInteger(bNum)) return aNum - bNum;
|
|
742
|
+
if (Number.isInteger(aNum)) return -1;
|
|
743
|
+
if (Number.isInteger(bNum)) return 1;
|
|
744
|
+
return a.localeCompare(b);
|
|
745
|
+
});
|
|
746
|
+
|
|
747
|
+
let sections = "";
|
|
748
|
+
for (const statusCode of statusCodes) {
|
|
749
|
+
const responseDef = responses[statusCode];
|
|
750
|
+
if (!responseDef || typeof responseDef !== "object") continue;
|
|
751
|
+
|
|
752
|
+
const jsonSchema =
|
|
753
|
+
responseDef.content &&
|
|
754
|
+
responseDef.content["application/json"] &&
|
|
755
|
+
responseDef.content["application/json"].schema;
|
|
756
|
+
|
|
757
|
+
if (!jsonSchema || typeof jsonSchema !== "object") continue;
|
|
758
|
+
|
|
759
|
+
const rootChildren = buildSchemaChildren(jsonSchema);
|
|
760
|
+
if (!rootChildren.length) continue;
|
|
761
|
+
|
|
762
|
+
let rows = "";
|
|
763
|
+
for (const child of rootChildren) {
|
|
764
|
+
rows += renderSchemaFieldNode(child, 0);
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
sections +=
|
|
768
|
+
'<div class="mb-4"><h4 class="text-xs font-mono uppercase tracking-wider opacity-70 mb-2">Status ' +
|
|
769
|
+
escapeHtml(statusCode) +
|
|
770
|
+
"</h4>" +
|
|
771
|
+
rows +
|
|
772
|
+
"</div>";
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
if (!sections) return "";
|
|
776
|
+
|
|
777
|
+
return (
|
|
778
|
+
'<div><h3 class="text-sm font-semibold mb-3 flex items-center border-b border-light-border dark:border-dark-border pb-2">Response Schemas</h3>' +
|
|
779
|
+
sections +
|
|
780
|
+
"</div>"
|
|
781
|
+
);
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
function renderTryItParameterInputs(pathParams, queryParams) {
|
|
785
|
+
const container = document.getElementById("request-param-inputs");
|
|
786
|
+
if (!container || !selected) return;
|
|
787
|
+
|
|
788
|
+
const values = getParameterValues(selected);
|
|
789
|
+
container.innerHTML = "";
|
|
790
|
+
|
|
791
|
+
const sections = [
|
|
792
|
+
{ title: "Path Values", params: pathParams },
|
|
793
|
+
{ title: "Query Values", params: queryParams },
|
|
794
|
+
];
|
|
795
|
+
|
|
796
|
+
for (const section of sections) {
|
|
797
|
+
if (!section.params.length) continue;
|
|
798
|
+
|
|
799
|
+
const group = document.createElement("div");
|
|
800
|
+
group.className = "space-y-2";
|
|
801
|
+
|
|
802
|
+
const title = document.createElement("p");
|
|
803
|
+
title.className = "text-xs font-semibold uppercase tracking-wider opacity-60";
|
|
804
|
+
title.textContent = section.title;
|
|
805
|
+
group.appendChild(title);
|
|
806
|
+
|
|
807
|
+
for (const param of section.params) {
|
|
808
|
+
const field = document.createElement("div");
|
|
809
|
+
field.className = "space-y-1";
|
|
810
|
+
|
|
811
|
+
const label = document.createElement("label");
|
|
812
|
+
label.className = "text-xs opacity-80 flex items-center gap-2";
|
|
813
|
+
|
|
814
|
+
const labelName = document.createElement("span");
|
|
815
|
+
labelName.className = "font-mono";
|
|
816
|
+
labelName.textContent = param.name;
|
|
817
|
+
|
|
818
|
+
const required = document.createElement("span");
|
|
819
|
+
required.className = "text-[10px] text-brand";
|
|
820
|
+
required.textContent = param.required ? "required" : "optional";
|
|
821
|
+
|
|
822
|
+
label.appendChild(labelName);
|
|
823
|
+
label.appendChild(required);
|
|
824
|
+
|
|
825
|
+
const input = document.createElement("input");
|
|
826
|
+
input.type = "text";
|
|
827
|
+
input.value = values[param.name] || "";
|
|
828
|
+
input.placeholder =
|
|
829
|
+
section.title === "Path Values" ? param.name : "optional";
|
|
830
|
+
input.className =
|
|
831
|
+
"w-full text-sm px-3 py-2 rounded border border-light-border dark:border-dark-border bg-light-bg dark:bg-dark-bg focus:outline-none focus:border-brand dark:focus:border-brand transition-colors font-mono";
|
|
832
|
+
|
|
833
|
+
input.addEventListener("input", () => {
|
|
834
|
+
values[param.name] = input.value;
|
|
835
|
+
updateRequestPreview();
|
|
836
|
+
});
|
|
837
|
+
|
|
838
|
+
field.appendChild(label);
|
|
839
|
+
field.appendChild(input);
|
|
840
|
+
group.appendChild(field);
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
container.appendChild(group);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
function renderHeaderInputs() {
|
|
848
|
+
const container = document.getElementById("header-inputs");
|
|
849
|
+
if (!container) return;
|
|
850
|
+
|
|
851
|
+
container.innerHTML = "";
|
|
852
|
+
requestHeaders.forEach((entry, index) => {
|
|
853
|
+
const row = document.createElement("div");
|
|
854
|
+
row.className = "grid grid-cols-[1fr_1fr_auto] gap-2";
|
|
855
|
+
|
|
856
|
+
const keyInput = document.createElement("input");
|
|
857
|
+
keyInput.type = "text";
|
|
858
|
+
keyInput.value = entry.key || "";
|
|
859
|
+
keyInput.placeholder = "Header";
|
|
860
|
+
keyInput.className =
|
|
861
|
+
"w-full text-xs px-2.5 py-2 rounded border border-light-border dark:border-dark-border bg-light-bg dark:bg-dark-bg focus:outline-none focus:border-brand dark:focus:border-brand transition-colors font-mono";
|
|
862
|
+
keyInput.addEventListener("input", () => {
|
|
863
|
+
entry.key = keyInput.value;
|
|
864
|
+
updateRequestPreview();
|
|
865
|
+
});
|
|
866
|
+
|
|
867
|
+
const valueInput = document.createElement("input");
|
|
868
|
+
valueInput.type = "text";
|
|
869
|
+
valueInput.value = entry.value || "";
|
|
870
|
+
valueInput.placeholder =
|
|
871
|
+
String(entry.key || "").toLowerCase() === "authorization"
|
|
872
|
+
? "Bearer token"
|
|
873
|
+
: "Value";
|
|
874
|
+
valueInput.className =
|
|
875
|
+
"w-full text-xs px-2.5 py-2 rounded border border-light-border dark:border-dark-border bg-light-bg dark:bg-dark-bg focus:outline-none focus:border-brand dark:focus:border-brand transition-colors font-mono";
|
|
876
|
+
valueInput.addEventListener("input", () => {
|
|
877
|
+
entry.value = valueInput.value;
|
|
878
|
+
updateRequestPreview();
|
|
879
|
+
});
|
|
880
|
+
|
|
881
|
+
const removeButton = document.createElement("button");
|
|
882
|
+
removeButton.type = "button";
|
|
883
|
+
removeButton.className =
|
|
884
|
+
"p-1.5 rounded-full border border-light-border dark:border-dark-border bg-light-bg/90 dark:bg-dark-bg/90 opacity-90 hover:opacity-100 hover:border-brand/60 transition-colors";
|
|
885
|
+
removeButton.setAttribute("aria-label", "Remove Header");
|
|
886
|
+
removeButton.setAttribute("title", "Remove Header");
|
|
887
|
+
removeButton.innerHTML =
|
|
888
|
+
'<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 12h12"></path></svg>';
|
|
889
|
+
removeButton.addEventListener("click", () => {
|
|
890
|
+
requestHeaders.splice(index, 1);
|
|
891
|
+
renderHeaderInputs();
|
|
892
|
+
updateRequestPreview();
|
|
893
|
+
});
|
|
894
|
+
|
|
895
|
+
row.appendChild(keyInput);
|
|
896
|
+
row.appendChild(valueInput);
|
|
897
|
+
row.appendChild(removeButton);
|
|
898
|
+
container.appendChild(row);
|
|
899
|
+
});
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
function hasHeaderName(headers, expectedName) {
|
|
903
|
+
const target = expectedName.toLowerCase();
|
|
904
|
+
return Object.keys(headers).some((key) => key.toLowerCase() === target);
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
function getRequestHeadersObject() {
|
|
908
|
+
const headers = {};
|
|
909
|
+
for (const entry of requestHeaders) {
|
|
910
|
+
const key = String(entry.key || "").trim();
|
|
911
|
+
const value = String(entry.value || "").trim();
|
|
912
|
+
if (!key || !value) continue;
|
|
913
|
+
headers[key] = value;
|
|
914
|
+
}
|
|
915
|
+
return headers;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
function buildCurl(op, headers, body, requestPath) {
|
|
919
|
+
const url = window.location.origin + requestPath;
|
|
920
|
+
const lines = ['curl -X ' + op.method + ' "' + url + '"'];
|
|
921
|
+
|
|
922
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
923
|
+
const safeName = String(name).replace(/"/g, '\\"');
|
|
924
|
+
const safeValue = String(value).replace(/"/g, '\\"');
|
|
925
|
+
lines.push(' -H "' + safeName + ": " + safeValue + '"');
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
if (body) {
|
|
929
|
+
lines.push(" -d '" + body.replace(/'/g, "'\\\\''") + "'");
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
return lines.join(" \\\\\\n");
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
function formatBodyJsonInput() {
|
|
936
|
+
const bodyInput = document.getElementById("body-input");
|
|
937
|
+
if (!bodyInput) return;
|
|
938
|
+
const current = bodyInput.value.trim();
|
|
939
|
+
if (!current) return;
|
|
940
|
+
try {
|
|
941
|
+
bodyInput.value = JSON.stringify(JSON.parse(current), null, 2);
|
|
942
|
+
} catch {}
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
function escapeHtml(value) {
|
|
946
|
+
return String(value)
|
|
947
|
+
.replace(/&/g, "&")
|
|
948
|
+
.replace(/</g, "<")
|
|
949
|
+
.replace(/>/g, ">");
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
function toPrettyJson(value) {
|
|
953
|
+
const trimmed = (value || "").trim();
|
|
954
|
+
if (!trimmed) return null;
|
|
955
|
+
try {
|
|
956
|
+
return JSON.stringify(JSON.parse(trimmed), null, 2);
|
|
957
|
+
} catch {
|
|
958
|
+
return null;
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
function highlightJson(jsonText) {
|
|
963
|
+
const escaped = escapeHtml(jsonText);
|
|
964
|
+
return escaped.replace(
|
|
965
|
+
/("(\\\\u[a-zA-Z0-9]{4}|\\\\[^u]|[^\\\\"])*"(\\s*:)?|\\btrue\\b|\\bfalse\\b|\\bnull\\b|-?\\d+(?:\\.\\d+)?(?:[eE][+\\-]?\\d+)?)/g,
|
|
966
|
+
(match) => {
|
|
967
|
+
let cls = "json-number";
|
|
968
|
+
if (match.startsWith('"')) {
|
|
969
|
+
cls = match.endsWith(":") ? "json-key" : "json-string";
|
|
970
|
+
} else if (match === "true" || match === "false") {
|
|
971
|
+
cls = "json-boolean";
|
|
972
|
+
} else if (match === "null") {
|
|
973
|
+
cls = "json-null";
|
|
974
|
+
}
|
|
975
|
+
return '<span class="' + cls + '">' + match + "</span>";
|
|
976
|
+
},
|
|
977
|
+
);
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
function updateBodyJsonPresentation() {
|
|
981
|
+
const bodyInput = document.getElementById("body-input");
|
|
982
|
+
const highlight = document.getElementById("body-highlight");
|
|
983
|
+
const bodySection = document.getElementById("request-body-section");
|
|
984
|
+
|
|
985
|
+
if (!bodyInput || !highlight || !bodySection) return;
|
|
986
|
+
if (bodySection.classList.contains("hidden")) {
|
|
987
|
+
highlight.innerHTML = "";
|
|
988
|
+
return;
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
const raw = bodyInput.value || "";
|
|
992
|
+
if (!raw.trim()) {
|
|
993
|
+
const placeholder = bodyInput.getAttribute("placeholder") || "";
|
|
994
|
+
highlight.innerHTML = '<span class="opacity-40">' + escapeHtml(placeholder) + "</span>";
|
|
995
|
+
return;
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
const prettyJson = toPrettyJson(raw);
|
|
999
|
+
if (!prettyJson) {
|
|
1000
|
+
highlight.innerHTML = escapeHtml(raw);
|
|
1001
|
+
return;
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
highlight.innerHTML = highlightJson(raw);
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
function syncBodyEditorScroll() {
|
|
1008
|
+
const bodyInput = document.getElementById("body-input");
|
|
1009
|
+
const highlight = document.getElementById("body-highlight");
|
|
1010
|
+
if (!bodyInput || !highlight) return;
|
|
1011
|
+
highlight.scrollTop = bodyInput.scrollTop;
|
|
1012
|
+
highlight.scrollLeft = bodyInput.scrollLeft;
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
function updateExpandEditorPresentation() {
|
|
1016
|
+
const editor = document.getElementById("expand-editor");
|
|
1017
|
+
const highlight = document.getElementById("expand-editor-highlight");
|
|
1018
|
+
if (!editor || !highlight) return;
|
|
1019
|
+
const raw = editor.value || "";
|
|
1020
|
+
if (!raw.trim()) {
|
|
1021
|
+
highlight.innerHTML = "";
|
|
1022
|
+
return;
|
|
1023
|
+
}
|
|
1024
|
+
const prettyJson = toPrettyJson(raw);
|
|
1025
|
+
highlight.innerHTML = prettyJson ? highlightJson(raw) : escapeHtml(raw);
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
function syncExpandEditorScroll() {
|
|
1029
|
+
const editor = document.getElementById("expand-editor");
|
|
1030
|
+
const highlight = document.getElementById("expand-editor-highlight");
|
|
1031
|
+
if (!editor || !highlight) return;
|
|
1032
|
+
highlight.scrollTop = editor.scrollTop;
|
|
1033
|
+
highlight.scrollLeft = editor.scrollLeft;
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
function formatResponseText(responseText) {
|
|
1037
|
+
const trimmed = (responseText || "").trim();
|
|
1038
|
+
if (!trimmed) return { text: "(empty)", isJson: false };
|
|
1039
|
+
try {
|
|
1040
|
+
return {
|
|
1041
|
+
text: JSON.stringify(JSON.parse(trimmed), null, 2),
|
|
1042
|
+
isJson: true,
|
|
1043
|
+
};
|
|
1044
|
+
} catch {
|
|
1045
|
+
return {
|
|
1046
|
+
text: responseText,
|
|
1047
|
+
isJson: false,
|
|
1048
|
+
};
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
function setResponseContent(headerText, bodyText, isJson) {
|
|
1053
|
+
const result = document.getElementById("result");
|
|
1054
|
+
if (!result) return;
|
|
1055
|
+
const fullText = String(headerText || "") + String(bodyText || "");
|
|
1056
|
+
result.dataset.raw = fullText;
|
|
1057
|
+
result.dataset.header = String(headerText || "");
|
|
1058
|
+
result.dataset.body = String(bodyText || "");
|
|
1059
|
+
result.dataset.isJson = isJson ? "true" : "false";
|
|
1060
|
+
if (isJson) {
|
|
1061
|
+
result.innerHTML = escapeHtml(String(headerText || "")) + highlightJson(String(bodyText || ""));
|
|
1062
|
+
} else {
|
|
1063
|
+
result.textContent = fullText;
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
function setSubmitLoading(isLoading) {
|
|
1068
|
+
const sendButton = document.getElementById("send-btn");
|
|
1069
|
+
const spinner = document.getElementById("send-btn-spinner");
|
|
1070
|
+
const label = document.getElementById("send-btn-label");
|
|
1071
|
+
if (!sendButton) return;
|
|
1072
|
+
|
|
1073
|
+
sendButton.disabled = isLoading;
|
|
1074
|
+
sendButton.classList.toggle("opacity-80", isLoading);
|
|
1075
|
+
sendButton.classList.toggle("cursor-wait", isLoading);
|
|
1076
|
+
if (spinner) spinner.classList.toggle("hidden", !isLoading);
|
|
1077
|
+
if (label) label.textContent = isLoading ? "Sending..." : "Submit";
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
function updateRequestPreview() {
|
|
1081
|
+
if (!selected) return;
|
|
1082
|
+
|
|
1083
|
+
const { path, query } = getOperationParameterGroups(selected);
|
|
1084
|
+
const values = getParameterValues(selected);
|
|
1085
|
+
const requestPath = buildRequestPath(selected, path, query, values);
|
|
1086
|
+
const bodyInput = document.getElementById("body-input");
|
|
1087
|
+
const body = bodyInput ? bodyInput.value.trim() : "";
|
|
1088
|
+
const headers = getRequestHeadersObject();
|
|
1089
|
+
if (body && !hasHeaderName(headers, "Content-Type")) {
|
|
1090
|
+
headers["Content-Type"] = "application/json";
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
document.getElementById("endpoint-path").textContent = requestPath;
|
|
1094
|
+
document.getElementById("curl-code").textContent = buildCurl(
|
|
1095
|
+
selected,
|
|
1096
|
+
headers,
|
|
1097
|
+
body,
|
|
1098
|
+
requestPath,
|
|
1099
|
+
);
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
function renderEndpoint() {
|
|
1103
|
+
if (!selected) return;
|
|
1104
|
+
const endpointCard = document.getElementById("endpoint-card");
|
|
1105
|
+
if (endpointCard) {
|
|
1106
|
+
endpointCard.classList.remove("enter-fade-up");
|
|
1107
|
+
// Restart CSS animation for each operation switch
|
|
1108
|
+
void endpointCard.offsetWidth;
|
|
1109
|
+
endpointCard.classList.add("enter-fade-up");
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
const op = selected.operation || {};
|
|
1113
|
+
const reqSchema = op.requestBody && op.requestBody.content && op.requestBody.content["application/json"] && op.requestBody.content["application/json"].schema;
|
|
1114
|
+
const requestBodySection = document.getElementById("request-body-section");
|
|
1115
|
+
const bodyInput = document.getElementById("body-input");
|
|
1116
|
+
const expandBodyBtn = document.getElementById("expand-body-btn");
|
|
1117
|
+
const supportsBody = hasMeaningfulRequestBodySchema(reqSchema);
|
|
1118
|
+
|
|
1119
|
+
if (requestBodySection) {
|
|
1120
|
+
requestBodySection.classList.toggle("hidden", !supportsBody);
|
|
1121
|
+
}
|
|
1122
|
+
if (supportsBody && bodyInput) {
|
|
1123
|
+
const existingDraft = getBodyDraft(selected);
|
|
1124
|
+
if (typeof existingDraft === "string") {
|
|
1125
|
+
bodyInput.value = existingDraft;
|
|
1126
|
+
} else {
|
|
1127
|
+
const prefill = buildRequiredBodyPrefill(reqSchema);
|
|
1128
|
+
bodyInput.value = prefill;
|
|
1129
|
+
setBodyDraft(selected, prefill);
|
|
1130
|
+
}
|
|
1131
|
+
} else if (!supportsBody && bodyInput) {
|
|
1132
|
+
bodyInput.value = "";
|
|
1133
|
+
}
|
|
1134
|
+
if (expandBodyBtn) {
|
|
1135
|
+
expandBodyBtn.disabled = !supportsBody;
|
|
1136
|
+
}
|
|
1137
|
+
setResponseContent("", "", false);
|
|
1138
|
+
|
|
1139
|
+
document.getElementById("tag-title").textContent = selected.tag;
|
|
1140
|
+
document.getElementById("tag-description").textContent = op.description || "Interactive API documentation.";
|
|
1141
|
+
const methodNode = document.getElementById("endpoint-method");
|
|
1142
|
+
methodNode.textContent = selected.method;
|
|
1143
|
+
methodNode.className = "px-2.5 py-0.5 rounded-full text-xs font-mono font-medium " + (methodBadge[selected.method] || methodBadgeDefault);
|
|
1144
|
+
document.getElementById("endpoint-title").textContent = selected.name;
|
|
1145
|
+
document.getElementById("endpoint-path").textContent = selected.path;
|
|
1146
|
+
|
|
1147
|
+
const { all: params, query, path, headers } =
|
|
1148
|
+
getOperationParameterGroups(selected);
|
|
1149
|
+
|
|
1150
|
+
let html = "";
|
|
1151
|
+
html += renderParamSection("Path Parameters", path);
|
|
1152
|
+
html += renderParamSection("Query Parameters", query);
|
|
1153
|
+
html += renderParamSection("Header Parameters", headers);
|
|
1154
|
+
|
|
1155
|
+
html += renderRequestBodySchemaSection(reqSchema);
|
|
1156
|
+
html += renderResponseSchemasSection(op.responses);
|
|
1157
|
+
document.getElementById("params-column").innerHTML = html || '<div class="text-sm opacity-70">No parameters</div>';
|
|
1158
|
+
renderTryItParameterInputs(path, query);
|
|
1159
|
+
renderHeaderInputs();
|
|
1160
|
+
updateRequestPreview();
|
|
1161
|
+
updateBodyJsonPresentation();
|
|
1162
|
+
syncBodyEditorScroll();
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
document.getElementById("copy-curl").addEventListener("click", async () => {
|
|
1166
|
+
try { await navigator.clipboard.writeText(document.getElementById("curl-code").textContent || ""); } catch {}
|
|
1167
|
+
});
|
|
1168
|
+
document.getElementById("sidebar-search").addEventListener("input", (event) => {
|
|
1169
|
+
sidebarSearchQuery = event.currentTarget.value || "";
|
|
1170
|
+
renderSidebar();
|
|
1171
|
+
});
|
|
1172
|
+
|
|
1173
|
+
document.getElementById("send-btn").addEventListener("click", async () => {
|
|
1174
|
+
if (!selected) return;
|
|
1175
|
+
const { path, query } = getOperationParameterGroups(selected);
|
|
1176
|
+
const values = getParameterValues(selected);
|
|
1177
|
+
const missingPathParams = path.filter((param) => {
|
|
1178
|
+
if (param.required === false) return false;
|
|
1179
|
+
const value = values[param.name];
|
|
1180
|
+
return value === undefined || value === null || String(value).trim() === "";
|
|
1181
|
+
});
|
|
1182
|
+
|
|
1183
|
+
if (missingPathParams.length > 0) {
|
|
1184
|
+
setResponseContent(
|
|
1185
|
+
"",
|
|
1186
|
+
"Missing required path parameter(s): " +
|
|
1187
|
+
missingPathParams.map((param) => param.name).join(", "),
|
|
1188
|
+
false,
|
|
1189
|
+
);
|
|
1190
|
+
return;
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
const requestPath = buildRequestPath(selected, path, query, values);
|
|
1194
|
+
formatBodyJsonInput();
|
|
1195
|
+
updateBodyJsonPresentation();
|
|
1196
|
+
const op = selected.operation || {};
|
|
1197
|
+
const reqSchema = op.requestBody && op.requestBody.content && op.requestBody.content["application/json"] && op.requestBody.content["application/json"].schema;
|
|
1198
|
+
const supportsBody = hasMeaningfulRequestBodySchema(reqSchema);
|
|
1199
|
+
const bodyInput = document.getElementById("body-input");
|
|
1200
|
+
const body =
|
|
1201
|
+
supportsBody && bodyInput ? bodyInput.value.trim() : "";
|
|
1202
|
+
const headers = getRequestHeadersObject();
|
|
1203
|
+
if (body && !hasHeaderName(headers, "Content-Type")) {
|
|
1204
|
+
headers["Content-Type"] = "application/json";
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
setSubmitLoading(true);
|
|
1208
|
+
try {
|
|
1209
|
+
const requestStart = performance.now();
|
|
1210
|
+
const response = await fetch(requestPath, { method: selected.method, headers, body: body || undefined });
|
|
1211
|
+
const text = await response.text();
|
|
1212
|
+
const responseTimeMs = Math.round(performance.now() - requestStart);
|
|
1213
|
+
const contentType = response.headers.get("content-type") || "unknown";
|
|
1214
|
+
const formattedResponse = formatResponseText(text);
|
|
1215
|
+
const headerText =
|
|
1216
|
+
"Status: " + response.status + " " + response.statusText + "\\n" +
|
|
1217
|
+
"Content-Type: " + contentType + "\\n" +
|
|
1218
|
+
"Response Time: " + responseTimeMs + " ms\\n\\n";
|
|
1219
|
+
setResponseContent(
|
|
1220
|
+
headerText,
|
|
1221
|
+
formattedResponse.text,
|
|
1222
|
+
formattedResponse.isJson,
|
|
1223
|
+
);
|
|
1224
|
+
} catch (error) {
|
|
1225
|
+
setResponseContent("", "Request failed: " + String(error), false);
|
|
1226
|
+
} finally {
|
|
1227
|
+
setSubmitLoading(false);
|
|
1228
|
+
}
|
|
1229
|
+
});
|
|
1230
|
+
|
|
1231
|
+
function openExpandModal(mode) {
|
|
1232
|
+
const modal = document.getElementById("expand-modal");
|
|
1233
|
+
const title = document.getElementById("expand-modal-title");
|
|
1234
|
+
const editorShell = document.getElementById("expand-editor-shell");
|
|
1235
|
+
const editor = document.getElementById("expand-editor");
|
|
1236
|
+
const viewer = document.getElementById("expand-viewer");
|
|
1237
|
+
const apply = document.getElementById("expand-apply");
|
|
1238
|
+
const bodyInput = document.getElementById("body-input");
|
|
1239
|
+
const result = document.getElementById("result");
|
|
1240
|
+
if (!modal || !title || !editorShell || !editor || !viewer || !apply) return;
|
|
1241
|
+
|
|
1242
|
+
expandModalMode = mode;
|
|
1243
|
+
modal.classList.remove("hidden");
|
|
1244
|
+
modal.classList.add("flex");
|
|
1245
|
+
|
|
1246
|
+
if (mode === "body") {
|
|
1247
|
+
title.textContent = "Request Body";
|
|
1248
|
+
editorShell.classList.remove("hidden");
|
|
1249
|
+
viewer.classList.add("hidden");
|
|
1250
|
+
apply.classList.remove("hidden");
|
|
1251
|
+
editor.value = bodyInput ? bodyInput.value : "";
|
|
1252
|
+
updateExpandEditorPresentation();
|
|
1253
|
+
syncExpandEditorScroll();
|
|
1254
|
+
} else {
|
|
1255
|
+
title.textContent = "Response";
|
|
1256
|
+
viewer.classList.remove("hidden");
|
|
1257
|
+
editorShell.classList.add("hidden");
|
|
1258
|
+
apply.classList.add("hidden");
|
|
1259
|
+
const hasResponse = Boolean(result && result.dataset && result.dataset.raw);
|
|
1260
|
+
if (!hasResponse) {
|
|
1261
|
+
viewer.textContent = "(empty response yet)";
|
|
1262
|
+
return;
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
const header = result.dataset.header || "";
|
|
1266
|
+
const body = result.dataset.body || "";
|
|
1267
|
+
const isJson = result.dataset.isJson === "true";
|
|
1268
|
+
if (isJson) {
|
|
1269
|
+
viewer.innerHTML = escapeHtml(header) + highlightJson(body);
|
|
1270
|
+
} else {
|
|
1271
|
+
viewer.textContent = result.dataset.raw || "(empty response yet)";
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
function closeExpandModal() {
|
|
1277
|
+
const modal = document.getElementById("expand-modal");
|
|
1278
|
+
if (!modal) return;
|
|
1279
|
+
modal.classList.add("hidden");
|
|
1280
|
+
modal.classList.remove("flex");
|
|
1281
|
+
expandModalMode = null;
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
document.getElementById("add-header-btn").addEventListener("click", () => {
|
|
1285
|
+
requestHeaders.push({ key: "", value: "" });
|
|
1286
|
+
renderHeaderInputs();
|
|
1287
|
+
updateRequestPreview();
|
|
1288
|
+
});
|
|
1289
|
+
document.getElementById("body-input").addEventListener("input", () => {
|
|
1290
|
+
if (selected) {
|
|
1291
|
+
setBodyDraft(selected, document.getElementById("body-input").value);
|
|
1292
|
+
}
|
|
1293
|
+
updateRequestPreview();
|
|
1294
|
+
updateBodyJsonPresentation();
|
|
1295
|
+
syncBodyEditorScroll();
|
|
1296
|
+
});
|
|
1297
|
+
document.getElementById("body-input").addEventListener("scroll", () => {
|
|
1298
|
+
syncBodyEditorScroll();
|
|
1299
|
+
});
|
|
1300
|
+
document.getElementById("body-input").addEventListener("keydown", (event) => {
|
|
1301
|
+
if (event.key !== "Tab") return;
|
|
1302
|
+
event.preventDefault();
|
|
1303
|
+
const input = event.currentTarget;
|
|
1304
|
+
const start = input.selectionStart;
|
|
1305
|
+
const end = input.selectionEnd;
|
|
1306
|
+
const value = input.value;
|
|
1307
|
+
const tab = " ";
|
|
1308
|
+
input.value = value.slice(0, start) + tab + value.slice(end);
|
|
1309
|
+
input.selectionStart = input.selectionEnd = start + tab.length;
|
|
1310
|
+
if (selected) {
|
|
1311
|
+
setBodyDraft(selected, input.value);
|
|
1312
|
+
}
|
|
1313
|
+
updateRequestPreview();
|
|
1314
|
+
updateBodyJsonPresentation();
|
|
1315
|
+
syncBodyEditorScroll();
|
|
1316
|
+
});
|
|
1317
|
+
document.getElementById("body-input").addEventListener("blur", () => {
|
|
1318
|
+
formatBodyJsonInput();
|
|
1319
|
+
if (selected) {
|
|
1320
|
+
setBodyDraft(selected, document.getElementById("body-input").value);
|
|
1321
|
+
}
|
|
1322
|
+
updateRequestPreview();
|
|
1323
|
+
updateBodyJsonPresentation();
|
|
1324
|
+
syncBodyEditorScroll();
|
|
1325
|
+
});
|
|
1326
|
+
document.getElementById("expand-editor").addEventListener("input", () => {
|
|
1327
|
+
updateExpandEditorPresentation();
|
|
1328
|
+
syncExpandEditorScroll();
|
|
1329
|
+
});
|
|
1330
|
+
document.getElementById("expand-editor").addEventListener("scroll", () => {
|
|
1331
|
+
syncExpandEditorScroll();
|
|
1332
|
+
});
|
|
1333
|
+
document.getElementById("expand-editor").addEventListener("keydown", (event) => {
|
|
1334
|
+
if (event.key !== "Tab") return;
|
|
1335
|
+
event.preventDefault();
|
|
1336
|
+
const editor = event.currentTarget;
|
|
1337
|
+
const start = editor.selectionStart;
|
|
1338
|
+
const end = editor.selectionEnd;
|
|
1339
|
+
const value = editor.value;
|
|
1340
|
+
const tab = " ";
|
|
1341
|
+
editor.value = value.slice(0, start) + tab + value.slice(end);
|
|
1342
|
+
editor.selectionStart = editor.selectionEnd = start + tab.length;
|
|
1343
|
+
updateExpandEditorPresentation();
|
|
1344
|
+
syncExpandEditorScroll();
|
|
1345
|
+
});
|
|
1346
|
+
document.getElementById("expand-editor").addEventListener("blur", () => {
|
|
1347
|
+
const editor = document.getElementById("expand-editor");
|
|
1348
|
+
const current = editor.value.trim();
|
|
1349
|
+
if (current) {
|
|
1350
|
+
try {
|
|
1351
|
+
editor.value = JSON.stringify(JSON.parse(current), null, 2);
|
|
1352
|
+
} catch {}
|
|
1353
|
+
}
|
|
1354
|
+
updateExpandEditorPresentation();
|
|
1355
|
+
syncExpandEditorScroll();
|
|
1356
|
+
});
|
|
1357
|
+
document.getElementById("expand-body-btn").addEventListener("click", () => {
|
|
1358
|
+
openExpandModal("body");
|
|
1359
|
+
});
|
|
1360
|
+
document.getElementById("expand-response-btn").addEventListener("click", () => {
|
|
1361
|
+
openExpandModal("response");
|
|
1362
|
+
});
|
|
1363
|
+
document.getElementById("expand-close").addEventListener("click", closeExpandModal);
|
|
1364
|
+
document.getElementById("expand-apply").addEventListener("click", () => {
|
|
1365
|
+
if (expandModalMode !== "body") {
|
|
1366
|
+
closeExpandModal();
|
|
1367
|
+
return;
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
const editor = document.getElementById("expand-editor");
|
|
1371
|
+
const bodyInput = document.getElementById("body-input");
|
|
1372
|
+
if (editor && bodyInput) {
|
|
1373
|
+
bodyInput.value = editor.value;
|
|
1374
|
+
formatBodyJsonInput();
|
|
1375
|
+
if (selected) {
|
|
1376
|
+
setBodyDraft(selected, bodyInput.value);
|
|
1377
|
+
}
|
|
1378
|
+
updateRequestPreview();
|
|
1379
|
+
updateBodyJsonPresentation();
|
|
1380
|
+
syncBodyEditorScroll();
|
|
1381
|
+
}
|
|
1382
|
+
closeExpandModal();
|
|
1383
|
+
});
|
|
1384
|
+
document.getElementById("expand-modal").addEventListener("click", (event) => {
|
|
1385
|
+
if (event.target === event.currentTarget) {
|
|
1386
|
+
closeExpandModal();
|
|
1387
|
+
}
|
|
1388
|
+
});
|
|
1389
|
+
document.getElementById("sidebar-open").addEventListener("click", () => {
|
|
1390
|
+
setMobileSidebarOpen(true);
|
|
1391
|
+
});
|
|
1392
|
+
document.getElementById("sidebar-close").addEventListener("click", () => {
|
|
1393
|
+
setMobileSidebarOpen(false);
|
|
1394
|
+
});
|
|
1395
|
+
document.getElementById("mobile-backdrop").addEventListener("click", () => {
|
|
1396
|
+
setMobileSidebarOpen(false);
|
|
1397
|
+
});
|
|
1398
|
+
window.addEventListener("resize", () => {
|
|
1399
|
+
if (window.innerWidth >= 768 && isMobileSidebarOpen) {
|
|
1400
|
+
setMobileSidebarOpen(false);
|
|
1401
|
+
}
|
|
1402
|
+
});
|
|
1403
|
+
document.addEventListener("keydown", (event) => {
|
|
1404
|
+
if (event.key === "Escape") {
|
|
1405
|
+
if (isMobileSidebarOpen) {
|
|
1406
|
+
setMobileSidebarOpen(false);
|
|
1407
|
+
}
|
|
1408
|
+
closeExpandModal();
|
|
1409
|
+
}
|
|
1410
|
+
});
|
|
1411
|
+
|
|
1412
|
+
const themeToggleBtn = document.getElementById('theme-toggle');
|
|
1413
|
+
const htmlElement = document.documentElement;
|
|
1414
|
+
themeToggleBtn.addEventListener('click', () => {
|
|
1415
|
+
htmlElement.classList.toggle('dark');
|
|
1416
|
+
if (htmlElement.classList.contains('dark')) {
|
|
1417
|
+
localStorage.setItem('theme', 'dark');
|
|
1418
|
+
} else {
|
|
1419
|
+
localStorage.setItem('theme', 'light');
|
|
1420
|
+
}
|
|
1421
|
+
});
|
|
1422
|
+
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
|
|
1423
|
+
if (!('theme' in localStorage)) {
|
|
1424
|
+
if (e.matches) htmlElement.classList.add('dark');
|
|
1425
|
+
else htmlElement.classList.remove('dark');
|
|
1426
|
+
}
|
|
1427
|
+
});
|
|
1428
|
+
|
|
1429
|
+
setMobileSidebarOpen(false);
|
|
1430
|
+
renderSidebar();
|
|
1431
|
+
renderEndpoint();
|
|
1432
|
+
</script>
|
|
1433
|
+
</body>
|
|
1434
|
+
</html>`}function W(e){let t=e?.["~standard"],d=t?.jsonSchema;return!!t&&typeof t==="object"&&t.version===1&&!!d&&typeof d.input==="function"&&typeof d.output==="function"}function xe(e){let t=0,d=[];return{openapiPath:e.split("/").map((r)=>{let o=/^:([A-Za-z0-9_]+)\+$/.exec(r);if(o?.[1])return d.push(o[1]),`{${o[1]}}`;let i=/^:([A-Za-z0-9_]+)$/.exec(r);if(i?.[1])return d.push(i[1]),`{${i[1]}}`;if(r==="*"){t+=1;let a=t===1?"wildcard":`wildcard${t}`;return d.push(a),`{${a}}`}return r}).join("/"),pathParamNames:d}}function Fe(e){return xe(e).openapiPath}function ze(e,t){return`${e.toLowerCase()}_${t}`.replace(/[:{}]/g,"").replace(/[^A-Za-z0-9_]+/g,"_").replace(/^_+|_+$/g,"")||`${e.toLowerCase()}_operation`}function Ge(e){let t=e.split("/").filter(Boolean);for(let d of t)if(!d.startsWith(":")&&d!=="*")return d.toLowerCase();return"default"}function Ve(e){return xe(e).pathParamNames}function Qe(e,t){let d=new Set((e.parameters||[]).filter((n)=>n.in==="path").map((n)=>String(n.name)));for(let n of Ve(t)){if(d.has(n))continue;(e.parameters||=[]).push({name:n,in:"path",required:!0,schema:{type:"string"}})}}function Ye(e){let t=Number(e);if(!Number.isInteger(t))return!1;return t>=100&&t<200||t===204||t===205||t===304}function Xe(e){if(e==="204")return"No Content";if(e==="205")return"Reset Content";if(e==="304")return"Not Modified";let t=Number(e);if(Number.isInteger(t)&&t>=100&&t<200)return"Informational";return"OK"}function We(e,t,d,n){if(!W(t)){let r=U(t);return Y(r)?null:r}try{return t["~standard"].jsonSchema.input({target:d})}catch(r){let o=ge(t,"input",d,r,e,void 0,n);if(o)return o;n.push(`[OpenAPI] Failed input schema conversion for ${e}: ${r instanceof Error?r.message:String(r)}. Falling back to a permissive JSON Schema.`);let i=U(t);return Y(i)?null:i}}function he(e,t,d,n,r){if(!W(d)){let o=U(d);return Y(o)?null:o}try{return d["~standard"].jsonSchema.output({target:n})}catch(o){let i=ge(d,"output",n,o,e,t,r);if(i)return i;return r.push(`[OpenAPI] Failed output schema conversion for ${e} (${t}): ${o instanceof Error?o.message:String(o)}. Falling back to a permissive JSON Schema.`),U(d)}}function y(e){return!!e&&typeof e==="object"&&!Array.isArray(e)}function Y(e){return y(e)&&Object.keys(e).length===0}function ge(e,t,d,n,r,o,i){if(!W(e))return null;let a=n instanceof Error?n.message:String(n);if(!(d==="openapi-3.0"&&a.includes("target 'openapi-3.0' is not supported")&&a.includes("draft-2020-12")&&a.includes("draft-07")))return null;try{let c=e["~standard"].jsonSchema[t]({target:"draft-07"});return i.push(t==="input"?`[OpenAPI] ${r} converter does not support openapi-3.0 target; using draft-07 conversion output.`:`[OpenAPI] ${r} (${o}) converter does not support openapi-3.0 target; using draft-07 conversion output.`),c}catch{return null}}function K(e){if(!e||typeof e!=="object")return null;let t=e;if(y(t._def))return t._def;if(y(t._zod)&&y(t._zod.def))return t._zod.def;if(t.kind==="schema"&&typeof t.type==="string")return t;return null}function ke(e){if(!e)return null;let t=e.typeName;if(typeof t==="string")return t;let d=e.type;if(typeof d==="string")return d;return null}function X(e){let t=["innerType","schema","type","out","in","left","right","wrapped","element"];for(let d of t)if(d in e)return e[d];return}function Ke(e,t){for(let d of t){let n=e[d];if(n&&typeof n==="object"&&!Array.isArray(n))return n}return}function Ze(e){if(!e)return!1;let t=e.toLowerCase();return t.includes("optional")||t.includes("default")||t.includes("catch")}function qe(e){let t=e,d=!1,n=0;while(n<8){n+=1;let r=K(t),o=ke(r);if(!r||!Ze(o))break;d=!0;let i=X(r);if(!i)break;t=i}return{schema:t,optional:d}}function et(e){let t=e.entries;if(y(t))return t;let d=e.shape;if(typeof d==="function")try{let n=d();return y(n)?n:{}}catch{return{}}return y(d)?d:{}}function ye(e){let t=e.values;if(Array.isArray(t))return t;if(t&&typeof t==="object")return Object.values(t);let d=e.entries;if(d&&typeof d==="object")return Object.values(d);let n=e.enum;if(n&&typeof n==="object")return Object.values(n);let r=e.options;if(Array.isArray(r))return r.map((o)=>{if(o&&typeof o==="object"&&"unit"in o)return o.unit;return o}).filter((o)=>o!==void 0);return[]}function tt(e){let t=e.toLowerCase();if(t.includes("string"))return{type:"string"};if(t.includes("number"))return{type:"number"};if(t.includes("boolean"))return{type:"boolean"};if(t.includes("bigint"))return{type:"string"};if(t==="null"||t.includes("zodnull"))return{type:"null"};if(t.includes("any")||t.includes("unknown")||t.includes("never"))return{};if(t.includes("date"))return{type:"string",format:"date-time"};if(t.includes("custom"))return{type:"object",additionalProperties:!0};return null}function g(e,t=new WeakSet){if(!e||typeof e!=="object")return{};if(t.has(e))return{};t.add(e);let d=K(e),n=ke(d);if(!d||!n)return{};let r=tt(n);if(r)return r;let o=n.toLowerCase();if(o.includes("object")){let a=et(d),s={},c=[];for(let[b,p]of Object.entries(a)){let x=qe(p);if(s[b]=g(x.schema,t),!x.optional)c.push(b)}let m={type:"object",properties:s,additionalProperties:!0};if(c.length>0)m.required=c;return m}if(o.includes("array")){let a=Ke(d,["element","items","innerType","type"])??{};return{type:"array",items:g(a,t)}}if(o.includes("record")){let a=d.valueType??d.valueSchema;return{type:"object",additionalProperties:a?g(a,t):!0}}if(o.includes("tuple")){let s=(Array.isArray(d.items)?d.items:[]).map((c)=>g(c,t));return{type:"array",prefixItems:s,minItems:s.length,maxItems:s.length}}if(o.includes("union")){let a=d.options??d.schemas??[];if(!Array.isArray(a)||a.length===0)return{};return{anyOf:a.map((s)=>g(s,t))}}if(o.includes("intersection")){let{left:a,right:s}=d;if(!a||!s)return{};return{allOf:[g(a,t),g(s,t)]}}if(o.includes("enum")){let a=ye(d);if(a.length>0){let s=a.every((b)=>typeof b==="string"),c=a.every((b)=>typeof b==="number"),m=a.every((b)=>typeof b==="boolean");if(s)return{type:"string",enum:a};if(c)return{type:"number",enum:a};if(m)return{type:"boolean",enum:a};return{enum:a}}return{}}if(o.includes("picklist")){let a=ye(d);if(a.length>0){if(a.every((c)=>typeof c==="string"))return{type:"string",enum:a};return{enum:a}}return{}}if(o.includes("literal")){let a=d.value;if(a===void 0)return{};let s=a===null?"null":typeof a;if(s==="string"||s==="number"||s==="boolean"||s==="null")return{type:s,const:a};return{const:a}}if(o.includes("nullable")){let a=X(d);if(!a)return{};return{anyOf:[g(a,t),{type:"null"}]}}if(o.includes("lazy")){let a=d.getter;if(typeof a!=="function")return{};try{return g(a(),t)}catch{return{}}}let i=X(d);if(i)return g(i,t);return{}}function U(e){if(!K(e))return{};return g(e)}function dt(e,t){if(!y(t))return;if(t.type!=="object"||!y(t.properties)){e.requestBody={required:!0,content:{"application/json":{schema:t}}};return}let d=new Set(Array.isArray(t.required)?t.required:[]),n=t.properties,r=Array.isArray(e.parameters)?e.parameters:[],o=[{key:"params",in:"path"},{key:"query",in:"query"},{key:"headers",in:"header"},{key:"cookies",in:"cookie"}];for(let a of o){let s=n[a.key];if(!y(s))continue;if(s.type!=="object"||!y(s.properties))continue;let c=new Set(Array.isArray(s.required)?s.required:[]);for(let[m,b]of Object.entries(s.properties))r.push({name:m,in:a.in,required:a.in==="path"?!0:c.has(m),schema:y(b)?b:{}})}if(r.length>0){let a=new Map;for(let s of r)a.set(`${s.in}:${s.name}`,s);e.parameters=[...a.values()]}let i=n.body;if(i)e.requestBody={required:d.has("body"),content:{"application/json":{schema:y(i)?i:{}}}}}function nt(e,t,d,n,r){let o=d.output;if(!o){e.responses={200:{description:"OK"}};return}let i={};if(typeof o==="object"&&o!==null&&"~standard"in o){let a=he(t,"200",o,n,r);if(a)i["200"]={description:"OK",content:{"application/json":{schema:a}}};else i["200"]={description:"OK"}}else for(let[a,s]of Object.entries(o)){let c=String(a),m=he(t,c,s,n,r),b=Xe(c);if(m&&!Ye(c))i[c]={description:b,content:{"application/json":{schema:m}}};else i[c]={description:b}}if(Object.keys(i).length===0)i["200"]={description:"OK"};e.responses=i}function Ee(e,t){let d=[],n={};for(let i of e){if(i.options.expose===!1)continue;if(!i.method||!i.path)continue;let a=i.method.toLowerCase();if(a==="options")continue;let s=Fe(i.path),c={operationId:ze(a,s),tags:[i.options.schema?.tag||Ge(i.path)]},m=We(i.path,i.options.schema?.input,t.target,d);if(m)dt(c,m);Qe(c,i.path),nt(c,i.path,i.options.schema||{},t.target,d),n[s]||={},n[s][a]=c}return{document:{openapi:t.target==="openapi-3.0"?"3.0.3":"3.1.0",info:{title:t.info?.title||"Vector API",version:t.info?.version||"1.0.0",...t.info?.description?{description:t.info.description}:{}},paths:n},warnings:d}}var Z="/_vector/openapi/tailwindcdn.js",q="/_vector/openapi/logo_dark.svg",ee="/_vector/openapi/logo_white.svg",De="/_vector/openapi/favicon/apple-touch-icon.png",Ne="/_vector/openapi/favicon/favicon-32x32.png",Ae="/_vector/openapi/favicon/favicon-16x16.png",rt="/_vector/openapi/favicon/favicon.ico",je="/_vector/openapi/favicon/site.webmanifest",ot="/_vector/openapi/favicon/android-chrome-192x192.png",at="/_vector/openapi/favicon/android-chrome-512x512.png",it=["../openapi/assets/tailwindcdn.js","../src/openapi/assets/tailwindcdn.js","../../src/openapi/assets/tailwindcdn.js"],st=["../openapi/assets/logo_dark.svg","../src/openapi/assets/logo_dark.svg","../../src/openapi/assets/logo_dark.svg"],lt=["../openapi/assets/logo_white.svg","../src/openapi/assets/logo_white.svg","../../src/openapi/assets/logo_white.svg"],ct=["src/openapi/assets/tailwindcdn.js","openapi/assets/tailwindcdn.js","dist/openapi/assets/tailwindcdn.js"],bt=["src/openapi/assets/logo_dark.svg","openapi/assets/logo_dark.svg","dist/openapi/assets/logo_dark.svg"],mt=["src/openapi/assets/logo_white.svg","openapi/assets/logo_white.svg","dist/openapi/assets/logo_white.svg"],w=["../openapi/assets/favicon","../src/openapi/assets/favicon","../../src/openapi/assets/favicon"],L=["src/openapi/assets/favicon","openapi/assets/favicon","dist/openapi/assets/favicon"],pt="/* OpenAPI docs runtime asset missing: tailwind disabled */";function h(e,t){return e.map((d)=>`${d}/${t}`)}function E(e,t){for(let n of e)try{let r=new URL(n,import.meta.url);if(ne.existsSync(r))return Bun.file(r)}catch{}let d=process.cwd();for(let n of t){let r=Le.join(d,n);if(ne.existsSync(r))return Bun.file(r)}return null}var ve=E(it,ct),Be=E(st,bt),Ie=E(lt,mt),ut=E(h(w,"apple-touch-icon.png"),h(L,"apple-touch-icon.png")),ft=E(h(w,"favicon-32x32.png"),h(L,"favicon-32x32.png")),ht=E(h(w,"favicon-16x16.png"),h(L,"favicon-16x16.png")),yt=E(h(w,"favicon.ico"),h(L,"favicon.ico")),xt=E(h(w,"site.webmanifest"),h(L,"site.webmanifest")),gt=E(h(w,"android-chrome-192x192.png"),h(L,"android-chrome-192x192.png")),kt=E(h(w,"android-chrome-512x512.png"),h(L,"android-chrome-512x512.png")),we=[{path:De,file:ut,contentType:"image/png",filename:"apple-touch-icon.png"},{path:Ne,file:ft,contentType:"image/png",filename:"favicon-32x32.png"},{path:Ae,file:ht,contentType:"image/png",filename:"favicon-16x16.png"},{path:rt,file:yt,contentType:"image/x-icon",filename:"favicon.ico"},{path:je,file:xt,contentType:"application/manifest+json; charset=utf-8",filename:"site.webmanifest"},{path:ot,file:gt,contentType:"image/png",filename:"android-chrome-192x192.png"},{path:at,file:kt,contentType:"image/png",filename:"android-chrome-512x512.png"}],te="public, max-age=0, must-revalidate",C="public, max-age=31536000, immutable",de="no-store";function Et(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function vt(e){let t="^";for(let d of e){if(d==="*"){t+=".*";continue}t+=Et(d)}return t+="$",new RegExp(t)}function Bt(e,t){if(!t.includes("*"))return e===t;return vt(t).test(e)}class re{server=null;router;config;openapiConfig;openapiDocCache=null;openapiDocsHtmlCache=null;openapiWarningsLogged=!1;openapiTailwindMissingLogged=!1;openapiLogoDarkMissingLogged=!1;openapiLogoWhiteMissingLogged=!1;corsHandler=null;corsHeadersEntries=null;constructor(e,t){if(this.router=e,this.config=t,this.openapiConfig=this.normalizeOpenAPIConfig(t.openapi,t.development),t.cors){let d=this.normalizeCorsOptions(t.cors),{preflight:n,corsify:r}=ue(d);if(this.corsHandler={preflight:n,corsify:r},typeof d.origin==="string"&&(d.origin!=="*"||!d.credentials)){let i={"access-control-allow-origin":d.origin,"access-control-allow-methods":d.allowMethods,"access-control-allow-headers":d.allowHeaders,"access-control-expose-headers":d.exposeHeaders,"access-control-max-age":String(d.maxAge)};if(d.credentials)i["access-control-allow-credentials"]="true";this.corsHeadersEntries=Object.entries(i)}this.router.setCorsHeaders(this.corsHeadersEntries),this.router.setCorsHandler(this.corsHeadersEntries?null:this.corsHandler.corsify)}}normalizeOpenAPIConfig(e,t){let n=t!==!1&&!0;if(e===!1)return{enabled:!1,path:"/openapi.json",target:"openapi-3.0",docs:{enabled:!1,path:"/docs"}};if(e===!0)return{enabled:!0,path:"/openapi.json",target:"openapi-3.0",docs:{enabled:!1,path:"/docs"}};let r=e||{},o=r.docs,i=typeof o==="boolean"?{enabled:o,path:"/docs",exposePaths:void 0}:{enabled:o?.enabled===!0,path:o?.path||"/docs",exposePaths:Array.isArray(o?.exposePaths)?o.exposePaths.map((a)=>typeof a==="string"?a.trim():"").filter((a)=>a.length>0):void 0};return{enabled:r.enabled??n,path:r.path||"/openapi.json",target:r.target||"openapi-3.0",docs:i,info:r.info}}isDocsReservedPath(e){return e===this.openapiConfig.path||this.openapiConfig.docs.enabled&&e===this.openapiConfig.docs.path}shouldLogOpenAPIConversionWarnings(){if(this.config.development===!1)return!1;let d=process.env.LOG_LEVEL;return typeof d==="string"&&d.toLowerCase()==="debug"}getOpenAPIDocument(){if(this.openapiDocCache)return this.openapiDocCache;let e=this.router.getRouteDefinitions().filter((d)=>!this.isDocsReservedPath(d.path)),t=Ee(e,{target:this.openapiConfig.target,info:this.openapiConfig.info});if(!this.openapiWarningsLogged&&t.warnings.length>0){if(this.shouldLogOpenAPIConversionWarnings())for(let d of t.warnings)console.warn(d);this.openapiWarningsLogged=!0}return this.openapiDocCache=t.document,this.openapiDocCache}getOpenAPIDocumentForDocs(){let e=this.openapiConfig.docs.exposePaths,t=this.getOpenAPIDocument();if(!Array.isArray(e)||e.length===0)return t;let d=t.paths&&typeof t.paths==="object"&&!Array.isArray(t.paths)?t.paths:{},n={};for(let[r,o]of Object.entries(d))if(e.some((i)=>Bt(r,i)))n[r]=o;return{...t,paths:n}}getOpenAPIDocsHtmlCacheEntry(){if(this.openapiDocsHtmlCache)return this.openapiDocsHtmlCache;let e=fe(this.getOpenAPIDocumentForDocs(),this.openapiConfig.path,Z,q,ee,De,Ne,Ae,je),t=Bun.gzipSync(e),d=`"${Bun.hash(e).toString(16)}"`;return this.openapiDocsHtmlCache={html:e,gzip:t,etag:d},this.openapiDocsHtmlCache}requestAcceptsGzip(e){let t=e.headers.get("accept-encoding");return Boolean(t&&/\bgzip\b/i.test(t))}validateReservedOpenAPIPaths(){if(!this.openapiConfig.enabled)return;let e=new Set([this.openapiConfig.path]);if(this.openapiConfig.docs.enabled){e.add(this.openapiConfig.docs.path),e.add(Z),e.add(q),e.add(ee);for(let r of we)e.add(r.path)}let t=this.router.getRouteDefinitions().filter((r)=>e.has(r.path)).map((r)=>`${r.method} ${r.path}`),d=Object.entries(this.router.getRouteTable()).filter(([r,o])=>e.has(r)&&o instanceof Response).map(([r])=>`STATIC ${r}`),n=[...t,...d];if(n.length>0)throw new Error(`OpenAPI reserved path conflict: ${n.join(", ")}. Change your route path(s) or reconfigure openapi.path/docs.path.`)}tryHandleOpenAPIRequest(e){if(!this.openapiConfig.enabled||e.method!=="GET")return null;let t=new URL(e.url).pathname;if(t===this.openapiConfig.path)return Response.json(this.getOpenAPIDocument());if(this.openapiConfig.docs.enabled&&t===this.openapiConfig.docs.path){let{html:d,gzip:n,etag:r}=this.getOpenAPIDocsHtmlCacheEntry();if(e.headers.get("if-none-match")===r)return new Response(null,{status:304,headers:{etag:r,"cache-control":te,vary:"accept-encoding"}});if(this.requestAcceptsGzip(e))return new Response(n,{status:200,headers:{"content-type":"text/html; charset=utf-8","content-encoding":"gzip",etag:r,"cache-control":te,vary:"accept-encoding"}});return new Response(d,{status:200,headers:{"content-type":"text/html; charset=utf-8",etag:r,"cache-control":te,vary:"accept-encoding"}})}if(this.openapiConfig.docs.enabled&&t===Z){if(!ve){if(!this.openapiTailwindMissingLogged)this.openapiTailwindMissingLogged=!0,console.warn('[OpenAPI] Missing docs runtime asset "tailwindcdn.js". Serving inline fallback script instead.');return new Response(pt,{status:200,headers:{"content-type":"application/javascript; charset=utf-8","cache-control":C}})}return new Response(ve,{status:200,headers:{"content-type":"application/javascript; charset=utf-8","cache-control":C}})}if(this.openapiConfig.docs.enabled&&t===q){if(!Be){if(!this.openapiLogoDarkMissingLogged)this.openapiLogoDarkMissingLogged=!0,console.warn('[OpenAPI] Missing docs runtime asset "logo_dark.svg".');return new Response("OpenAPI docs runtime asset missing: logo_dark.svg",{status:404,headers:{"content-type":"text/plain; charset=utf-8","cache-control":de}})}return new Response(Be,{status:200,headers:{"content-type":"image/svg+xml; charset=utf-8","cache-control":C}})}if(this.openapiConfig.docs.enabled&&t===ee){if(!Ie){if(!this.openapiLogoWhiteMissingLogged)this.openapiLogoWhiteMissingLogged=!0,console.warn('[OpenAPI] Missing docs runtime asset "logo_white.svg".');return new Response("OpenAPI docs runtime asset missing: logo_white.svg",{status:404,headers:{"content-type":"text/plain; charset=utf-8","cache-control":de}})}return new Response(Ie,{status:200,headers:{"content-type":"image/svg+xml; charset=utf-8","cache-control":C}})}if(this.openapiConfig.docs.enabled){let d=we.find((n)=>n.path===t);if(d){if(!d.file)return new Response(`OpenAPI docs runtime asset missing: ${d.filename}`,{status:404,headers:{"content-type":"text/plain; charset=utf-8","cache-control":de}});return new Response(d.file,{status:200,headers:{"content-type":d.contentType,"cache-control":C}})}}return null}normalizeCorsOptions(e){return{origin:e.origin||"*",credentials:e.credentials!==!1,allowHeaders:Array.isArray(e.allowHeaders)?e.allowHeaders.join(", "):e.allowHeaders||"Content-Type, Authorization",allowMethods:Array.isArray(e.allowMethods)?e.allowMethods.join(", "):e.allowMethods||"GET, POST, PUT, PATCH, DELETE, OPTIONS",exposeHeaders:Array.isArray(e.exposeHeaders)?e.exposeHeaders.join(", "):e.exposeHeaders||"Authorization",maxAge:e.maxAge||86400}}applyCors(e,t){if(this.corsHeadersEntries){for(let[d,n]of this.corsHeadersEntries)e.headers.set(d,n);return e}if(this.corsHandler&&t)return this.corsHandler.corsify(e,t);return e}async start(){let e=this.config.port??3000,t=this.config.hostname||"localhost";this.validateReservedOpenAPIPaths();let d=async(n)=>{try{if(this.corsHandler&&n.method==="OPTIONS")return this.corsHandler.preflight(n);let r=this.tryHandleOpenAPIRequest(n);if(r)return this.applyCors(r,n);return this.applyCors(H.NOT_FOUND.clone(),n)}catch(r){return console.error("Server error:",r),this.applyCors(new Response("Internal Server Error",{status:500}),n)}};try{if(this.server=Bun.serve({port:e,hostname:t,reusePort:this.config.reusePort!==!1,routes:this.router.getRouteTable(),fetch:d,idleTimeout:this.config.idleTimeout??60,error:(n,r)=>{return console.error("[ERROR] Server error:",n),this.applyCors(new Response("Internal Server Error",{status:500}),r)}}),!this.server||!this.server.port)throw new Error(`Failed to start server on ${t}:${e} - server object is invalid`);return this.server}catch(n){if(n.code==="EADDRINUSE"||n.message?.includes("address already in use"))n.message=`Port ${e} is already in use`,n.port=e;else if(n.code==="EACCES"||n.message?.includes("permission denied"))n.message=`Permission denied to bind to port ${e}`,n.port=e;else if(n.message?.includes("EADDRNOTAVAIL"))n.message=`Cannot bind to hostname ${t}`,n.hostname=t;throw n}}stop(){if(this.server)this.server.stop(),this.server=null,this.openapiDocCache=null,this.openapiDocsHtmlCache=null,this.openapiWarningsLogged=!1,console.log("Server stopped")}getServer(){return this.server}getPort(){return this.server?.port??this.config.port??3000}getHostname(){return this.server?.hostname||this.config.hostname||"localhost"}getUrl(){let e=this.getPort();return`http://${this.getHostname()}:${e}`}}class D{static instance;router;server=null;middlewareManager;authManager;cacheManager;config={};routeScanner=null;routeGenerator=null;_protectedHandler=null;_cacheHandler=null;shutdownPromise=null;constructor(){this.middlewareManager=new T,this.authManager=new J,this.cacheManager=new S,this.router=new M(this.middlewareManager,this.authManager,this.cacheManager)}static getInstance(){if(!D.instance)D.instance=new D;return D.instance}setProtectedHandler(e){if(this._protectedHandler=e,e){this.authManager.setProtectedHandler(e);return}this.authManager.clearProtectedHandler()}getProtectedHandler(){return this._protectedHandler}setCacheHandler(e){if(this._cacheHandler=e,e){this.cacheManager.setCacheHandler(e);return}this.cacheManager.clearCacheHandler()}getCacheHandler(){return this._cacheHandler}addRoute(e,t){this.router.route(e,t)}async startServer(e){this.config={...this.config,...e};let t={...this.config.defaults?.route};if(this.router.setRouteBooleanDefaults(t),this.router.setDevelopmentMode(this.config.development),this.middlewareManager.clear(),this.config.autoDiscover!==!1)this.router.clearRoutes();if(e?.before)this.middlewareManager.addBefore(...e.before);if(e?.finally)this.middlewareManager.addFinally(...e.finally);if(typeof this.config.startup==="function")await this.config.startup();if(this.config.autoDiscover!==!1)await this.discoverRoutes();return this.server=new re(this.router,this.config),await this.server.start()}async discoverRoutes(){let e=this.config.routesDir||"./routes",t=this.config.routeExcludePatterns;if(this.routeScanner=new O(e,t),!this.routeGenerator)this.routeGenerator=new z;try{let d=await this.routeScanner.scan();if(d.length>0){if(this.config.development)await this.routeGenerator.generate(d);for(let n of d)try{let o=await import(R(n.path)),i=n.name==="default"?o.default:o[n.name];if(i){if(this.isRouteDefinition(i))this.router.route(i.options,i.handler),this.logRouteLoaded(i.options);else if(this.isRouteEntry(i))this.router.addRoute(i),this.logRouteLoaded(i);else if(typeof i==="function")this.router.route(n.options,i),this.logRouteLoaded(n.options)}}catch(r){console.error(`Failed to load route ${n.name} from ${n.path}:`,r)}}}catch(d){if(d.code!=="ENOENT"&&d.code!=="ENOTDIR")console.error("Failed to discover routes:",d)}}async loadRoute(e){if(typeof e==="function"){let t=e();if(this.isRouteEntry(t))this.router.addRoute(t)}else if(e&&typeof e==="object"){for(let[,t]of Object.entries(e))if(typeof t==="function"){let d=t();if(this.isRouteEntry(d))this.router.addRoute(d)}}}isRouteEntry(e){if(!Array.isArray(e)||e.length<3)return!1;let[t,d,n,r]=e;return typeof t==="string"&&d instanceof RegExp&&Array.isArray(n)&&n.length>0&&n.every((o)=>typeof o==="function")&&(r===void 0||typeof r==="string")}isRouteDefinition(e){return e!==null&&typeof e==="object"&&"entry"in e&&"options"in e&&"handler"in e&&typeof e.handler==="function"}logRouteLoaded(e){}stop(){if(this.server)this.server.stop(),this.server=null}async shutdown(){if(this.shutdownPromise)return this.shutdownPromise;this.shutdownPromise=(async()=>{if(this.stop(),typeof this.config.shutdown==="function")await this.config.shutdown()})();try{await this.shutdownPromise}finally{this.shutdownPromise=null}}getServer(){return this.server}getRouter(){return this.router}getCacheManager(){return this.cacheManager}getAuthManager(){return this.authManager}static resetInstance(){D.instance=null}}var oe=D.getInstance;function Me(e,t){return{entry:{method:e.method.toUpperCase(),path:e.path},options:e,handler:t}}function It(e){let t=e??null;try{return JSON.stringify(t)}catch(d){if(d instanceof TypeError&&/\bbigint\b/i.test(d.message))return JSON.stringify(t,(n,r)=>typeof r==="bigint"?r.toString():r);throw d}}function l(e,t,d){let n={error:!0,message:t,statusCode:e,timestamp:new Date().toISOString()};return I(e,n,d)}var B={badRequest:(e="Bad Request",t)=>l(v.BAD_REQUEST,e,t),unauthorized:(e="Unauthorized",t)=>l(v.UNAUTHORIZED,e,t),paymentRequired:(e="Payment Required",t)=>l(402,e,t),forbidden:(e="Forbidden",t)=>l(v.FORBIDDEN,e,t),notFound:(e="Not Found",t)=>l(v.NOT_FOUND,e,t),methodNotAllowed:(e="Method Not Allowed",t)=>l(405,e,t),notAcceptable:(e="Not Acceptable",t)=>l(406,e,t),requestTimeout:(e="Request Timeout",t)=>l(408,e,t),conflict:(e="Conflict",t)=>l(v.CONFLICT,e,t),gone:(e="Gone",t)=>l(410,e,t),lengthRequired:(e="Length Required",t)=>l(411,e,t),preconditionFailed:(e="Precondition Failed",t)=>l(412,e,t),payloadTooLarge:(e="Payload Too Large",t)=>l(413,e,t),uriTooLong:(e="URI Too Long",t)=>l(414,e,t),unsupportedMediaType:(e="Unsupported Media Type",t)=>l(415,e,t),rangeNotSatisfiable:(e="Range Not Satisfiable",t)=>l(416,e,t),expectationFailed:(e="Expectation Failed",t)=>l(417,e,t),imATeapot:(e="I'm a teapot",t)=>l(418,e,t),misdirectedRequest:(e="Misdirected Request",t)=>l(421,e,t),unprocessableEntity:(e="Unprocessable Entity",t)=>l(v.UNPROCESSABLE_ENTITY,e,t),locked:(e="Locked",t)=>l(423,e,t),failedDependency:(e="Failed Dependency",t)=>l(424,e,t),tooEarly:(e="Too Early",t)=>l(425,e,t),upgradeRequired:(e="Upgrade Required",t)=>l(426,e,t),preconditionRequired:(e="Precondition Required",t)=>l(428,e,t),tooManyRequests:(e="Too Many Requests",t)=>l(429,e,t),requestHeaderFieldsTooLarge:(e="Request Header Fields Too Large",t)=>l(431,e,t),unavailableForLegalReasons:(e="Unavailable For Legal Reasons",t)=>l(451,e,t),internalServerError:(e="Internal Server Error",t)=>l(v.INTERNAL_SERVER_ERROR,e,t),notImplemented:(e="Not Implemented",t)=>l(501,e,t),badGateway:(e="Bad Gateway",t)=>l(502,e,t),serviceUnavailable:(e="Service Unavailable",t)=>l(503,e,t),gatewayTimeout:(e="Gateway Timeout",t)=>l(504,e,t),httpVersionNotSupported:(e="HTTP Version Not Supported",t)=>l(505,e,t),variantAlsoNegotiates:(e="Variant Also Negotiates",t)=>l(506,e,t),insufficientStorage:(e="Insufficient Storage",t)=>l(507,e,t),loopDetected:(e="Loop Detected",t)=>l(508,e,t),notExtended:(e="Not Extended",t)=>l(510,e,t),networkAuthenticationRequired:(e="Network Authentication Required",t)=>l(511,e,t),invalidArgument:(e="Invalid Argument",t)=>l(v.UNPROCESSABLE_ENTITY,e,t),rateLimitExceeded:(e="Rate Limit Exceeded",t)=>l(429,e,t),maintenance:(e="Service Under Maintenance",t)=>l(503,e,t),custom:(e,t,d)=>l(e,t,d)};function I(e,t,d=_.JSON){let n=d===_.JSON?It(t):t;return new Response(n,{status:e,headers:{"content-type":d}})}var Ce=require("fs"),$=require("path");class ae{configPath;config=null;configSource="default";constructor(e){let t=e||"vector.config.ts";this.configPath=$.isAbsolute(t)?t:$.resolve(process.cwd(),t)}async load(){if(Ce.existsSync(this.configPath))try{let t=await import(R(this.configPath));this.config=t.default||t,this.configSource="user"}catch(e){let t=e instanceof Error?e.message:String(e);console.error(`[vector] Failed to load config from ${this.configPath}: ${t}`),console.error("[vector] Server is using default configuration. Fix your config file and restart."),this.config={}}else this.config={};return await this.buildLegacyConfig()}getConfigSource(){return this.configSource}async buildLegacyConfig(){let e={};if(this.config)e.port=this.config.port,e.hostname=this.config.hostname,e.reusePort=this.config.reusePort,e.development=this.config.development,e.routesDir=this.config.routesDir||"./routes",e.routeExcludePatterns=this.config.routeExcludePatterns,e.idleTimeout=this.config.idleTimeout,e.defaults=this.config.defaults,e.openapi=this.config.openapi,e.startup=this.config.startup,e.shutdown=this.config.shutdown;if(e.autoDiscover=!0,this.config?.cors)if(typeof this.config.cors==="boolean")e.cors=this.config.cors?{origin:"*",credentials:!0,allowHeaders:"Content-Type, Authorization",allowMethods:"GET, POST, PUT, PATCH, DELETE, OPTIONS",exposeHeaders:"Authorization",maxAge:86400}:void 0;else e.cors=this.config.cors;if(this.config?.before)e.before=this.config.before;if(this.config?.after)e.finally=this.config.after;return e}async loadAuthHandler(){return this.config?.auth||null}async loadCacheHandler(){return this.config?.cache||null}getConfig(){return this.config}}async function He(e={}){let t=new ae(e.configPath),d=await t.load(),n=t.getConfigSource(),r={...d};if(e.mutateConfig)r=await e.mutateConfig(r,{configSource:n});if(e.config)r={...r,...e.config};if(e.autoDiscover!==void 0)r.autoDiscover=e.autoDiscover;let o=oe(),i=e.protectedHandler!==void 0?e.protectedHandler:await t.loadAuthHandler(),a=e.cacheHandler!==void 0?e.cacheHandler:await t.loadCacheHandler();o.setProtectedHandler(i??null),o.setCacheHandler(a??null);let s=await o.startServer(r),c={...r,port:s.port??r.port??N.PORT,hostname:s.hostname||r.hostname||N.HOSTNAME,reusePort:r.reusePort!==!1,idleTimeout:r.idleTimeout??60};return{server:s,config:c,stop:()=>o.stop(),shutdown:()=>o.shutdown()}}})
|