@visulima/ono 2.0.0-alpha.35 → 2.0.0-alpha.36

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/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ ## @visulima/ono [2.0.0-alpha.36](https://github.com/visulima/visulima/compare/@visulima/ono@2.0.0-alpha.35...@visulima/ono@2.0.0-alpha.36) (2026-06-13)
2
+
3
+ ### Bug Fixes
4
+
5
+ * **ono:** annotate renderJson for isolatedDeclarations ([bf8853f](https://github.com/visulima/visulima/commit/bf8853f1ffe39485fabbe3486eac4af1dc70d5d7))
6
+ * **ono:** fix runtime detection, request masking, add json renderer ([dcbd84b](https://github.com/visulima/visulima/commit/dcbd84b80a9153e262a645e0f6ffa2b5b0dd60c6))
7
+ * **ono:** guard circular request bodies and mask password-shaped body keys ([42a288c](https://github.com/visulima/visulima/commit/42a288cf3c0e2f3f00a02812b4879db95e718fda))
8
+
9
+
10
+ ### Dependencies
11
+
12
+ * **@visulima/error:** upgraded to 6.0.0-alpha.33
13
+ * **@visulima/path:** upgraded to 3.0.0-alpha.13
14
+
1
15
  ## @visulima/ono [2.0.0-alpha.35](https://github.com/visulima/visulima/compare/@visulima/ono@2.0.0-alpha.34...@visulima/ono@2.0.0-alpha.35) (2026-06-04)
2
16
 
3
17
  ### Bug Fixes
package/README.md CHANGED
@@ -263,6 +263,44 @@ Renders an error as ANSI terminal output.
263
263
 
264
264
  Returns an object with `errorAnsi` (the formatted error) and optional `solutionBox` (suggested solutions).
265
265
 
266
+ ##### `toJSON(error, options?)` => `Promise<OnoJson>`
267
+
268
+ Renders an error as a structured, JSON-serializable payload — the error name/message, the parsed
269
+ stack frames for the error and every entry in its `cause` chain, the detected runtime, and a
270
+ suggested solution (using the same solution-finder protocol as `toHTML`/`toANSI`). Useful for
271
+ SPA/REST error responses when the client sends `Accept: application/json`.
272
+
273
+ - **error**: `unknown` - The error to render
274
+ - **options**: `ToJsonOptions` (optional)
275
+ - `solutionFinders?: SolutionFinder[]` - Custom solution finders
276
+
277
+ ```ts
278
+ import { renderJson } from "@visulima/ono";
279
+
280
+ app.use((error, request, response, next) => {
281
+ if (request.accepts("json")) {
282
+ renderJson(error).then((payload) => response.status(500).json(payload));
283
+
284
+ return;
285
+ }
286
+
287
+ next(error);
288
+ });
289
+ ```
290
+
291
+ #### Standalone renderers
292
+
293
+ The same three renderers are also exported as standalone, tree-shakeable functions — prefer these
294
+ over instantiating `Ono`:
295
+
296
+ ```ts
297
+ import { renderAnsi, renderHtml, renderJson } from "@visulima/ono";
298
+
299
+ const html = await renderHtml(error);
300
+ const { errorAnsi } = await renderAnsi(error);
301
+ const json = await renderJson(error);
302
+ ```
303
+
266
304
  ### createRequestContextPage(request, options) => `Promise<ContentPage | undefined>`
267
305
 
268
306
  Creates a context page with detailed request debugging information.
@@ -323,10 +361,10 @@ try {
323
361
  Use `createRequestContextPage()` to create a "Context" tab with comprehensive debugging information:
324
362
 
325
363
  - **Request Overview** - cURL command with proper formatting and copy functionality
326
- - **Headers** - HTTP headers with smart masking for sensitive data
327
- - **Body** - Request body content with proper formatting
364
+ - **Headers** - HTTP headers with smart masking for sensitive data (authorization, cookies, API keys, tokens, secrets)
365
+ - **Body** - Request body content with sensitive-looking keys (password, token, secret, …) masked in both the rendered body and the copyable cURL
328
366
  - **Session** - Session data in organized key-value tables
329
- - **Cookies** - Cookie information in readable format
367
+ - **Cookies** - Cookie values are masked by default (the `cookie` header is treated as sensitive); pass an empty `maskValue` to disable
330
368
  - **Dynamic Context Sections** - Any additional context keys you provide are automatically rendered as sections with:
331
369
  - Proper titles (capitalized)
332
370
  - Copy buttons for JSON data
@@ -495,9 +533,9 @@ Try these routes:
495
533
 
496
534
  From `@visulima/ono/server/open-in-editor`:
497
535
 
498
- - `openInEditor(request, options)` — core function (uses `open-editor` under the hood)
499
- - `createNodeHttpHandler(options)` — returns `(req, res) => void` for Node http servers
500
- - `createExpressHandler(options)` — returns `(req, res) => void` for Express/Connect
536
+ - `createOpenInEditorMiddleware(options)` — universal handler factory (Node `http` and Connect/Express), backed by `launch-editor-middleware`
537
+ - `createNodeHttpHandler(options)` — returns `(req, res) => Promise<void>` for Node `http` servers
538
+ - `createExpressHandler(options)` — returns an Express/Connect-compatible handler
501
539
 
502
540
  Options:
503
541
 
@@ -1,4 +1,4 @@
1
- import { C as ContentPage } from "../../packem_shared/types.d-CDlBu_Vx.js";
1
+ import { C as ContentPage } from "../../packem_shared/types.d-D1UE67tt.js";
2
2
  import { IncomingMessage } from 'node:http';
3
3
  type NativeRequest = typeof globalThis.Request;
4
4
  type NativeHeaders = typeof globalThis.Headers;
@@ -1,60 +1,60 @@
1
- var ie=Object.defineProperty;var J=(e,r)=>ie(e,"name",{value:r,configurable:!0});import{a as G,s as P,n as X,e as W,d as de,g as le,c as ce}from"../../packem_shared/clipboard-DbMCKslu.js";const T="data:image/svg+xml;charset=utf-8,%3Csvg%20class%3D%22lucide%20lucide-braces%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22currentColor%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20%3E%20%3Cpath%20d%3D%22M8%203H7a2%202%200%200%200-2%202v5a2%202%200%200%201-2%202%202%202%200%200%201%202%202v5c0%201.1.9%202%202%202h1%22%20%2F%3E%20%3Cpath%20d%3D%22M16%2021h1a2%202%200%200%200%202-2v-5c0-1.1.9-2%202-2a2%202%200%200%201-2-2V5a2%202%200%200%200-2-2h-1%22%20%2F%3E%20%3C%2Fsvg%3E",q="data:image/svg+xml;charset=utf-8,%3Csvg%20class%3D%22lucide%20lucide-cookie%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22currentColor%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20%3E%20%3Cpath%20d%3D%22M12%202a10%2010%200%201%200%2010%2010%204%204%200%200%201-5-5%204%204%200%200%201-5-5%22%20%2F%3E%20%3Cpath%20d%3D%22M8.5%208.5v.01%22%20%2F%3E%20%3Cpath%20d%3D%22M16%2015.5v.01%22%20%2F%3E%20%3Cpath%20d%3D%22M12%2012v.01%22%20%2F%3E%20%3Cpath%20d%3D%22M11%2017v.01%22%20%2F%3E%20%3Cpath%20d%3D%22M7%2014v.01%22%20%2F%3E%20%3C%2Fsvg%3E",O="data:image/svg+xml;charset=utf-8,%3Csvg%20class%3D%22lucide%20lucide-file-text%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22currentColor%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20%3E%20%3Cpath%20d%3D%22M6%2022a2%202%200%200%201-2-2V4a2%202%200%200%201%202-2h8a2.4%202.4%200%200%201%201.704.706l3.588%203.588A2.4%202.4%200%200%201%2020%208v12a2%202%200%200%201-2%202z%22%20%2F%3E%20%3Cpath%20d%3D%22M14%202v5a1%201%200%200%200%201%201h5%22%20%2F%3E%20%3Cpath%20d%3D%22M10%209H8%22%20%2F%3E%20%3Cpath%20d%3D%22M16%2013H8%22%20%2F%3E%20%3Cpath%20d%3D%22M16%2017H8%22%20%2F%3E%20%3C%2Fsvg%3E",I="data:image/svg+xml;charset=utf-8,%3Csvg%20class%3D%22lucide%20lucide-globe%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22currentColor%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20%3E%20%3Ccircle%20cx%3D%2212%22%20cy%3D%2212%22%20r%3D%2210%22%20%2F%3E%20%3Cpath%20d%3D%22M12%202a14.5%2014.5%200%200%200%200%2020%2014.5%2014.5%200%200%200%200-20%22%20%2F%3E%20%3Cpath%20d%3D%22M2%2012h20%22%20%2F%3E%20%3C%2Fsvg%3E",V="data:image/svg+xml;charset=utf-8,%3Csvg%20class%3D%22lucide%20lucide-user%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22currentColor%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20%3E%20%3Cpath%20d%3D%22M19%2021v-2a4%204%200%200%200-4-4H9a4%204%200%200%200-4%204v2%22%20%2F%3E%20%3Ccircle%20cx%3D%2212%22%20cy%3D%227%22%20r%3D%224%22%20%2F%3E%20%3C%2Fsvg%3E";var ue=Object.defineProperty,ve=J((e,r)=>ue(e,"name",{value:r,configurable:!0}),"s$2");const S=ve(({label:e="Copy",successText:r="Copied!",targetId:o})=>{const c=G(o),m=G(e),x=P(e),l=P(r);return{html:`
1
+ import{s as q,a as G,l as _,b as K,c as de,d as le,e as ce}from"../../packem_shared/sanitize-D2LcyiFA.js";const N="data:image/svg+xml;charset=utf-8,%3Csvg%20class%3D%22lucide%20lucide-braces%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22currentColor%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20%3E%20%3Cpath%20d%3D%22M8%203H7a2%202%200%200%200-2%202v5a2%202%200%200%201-2%202%202%202%200%200%201%202%202v5c0%201.1.9%202%202%202h1%22%20%2F%3E%20%3Cpath%20d%3D%22M16%2021h1a2%202%200%200%200%202-2v-5c0-1.1.9-2%202-2a2%202%200%200%201-2-2V5a2%202%200%200%200-2-2h-1%22%20%2F%3E%20%3C%2Fsvg%3E",B="data:image/svg+xml;charset=utf-8,%3Csvg%20class%3D%22lucide%20lucide-cookie%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22currentColor%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20%3E%20%3Cpath%20d%3D%22M12%202a10%2010%200%201%200%2010%2010%204%204%200%200%201-5-5%204%204%200%200%201-5-5%22%20%2F%3E%20%3Cpath%20d%3D%22M8.5%208.5v.01%22%20%2F%3E%20%3Cpath%20d%3D%22M16%2015.5v.01%22%20%2F%3E%20%3Cpath%20d%3D%22M12%2012v.01%22%20%2F%3E%20%3Cpath%20d%3D%22M11%2017v.01%22%20%2F%3E%20%3Cpath%20d%3D%22M7%2014v.01%22%20%2F%3E%20%3C%2Fsvg%3E",j="data:image/svg+xml;charset=utf-8,%3Csvg%20class%3D%22lucide%20lucide-file-text%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22currentColor%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20%3E%20%3Cpath%20d%3D%22M6%2022a2%202%200%200%201-2-2V4a2%202%200%200%201%202-2h8a2.4%202.4%200%200%201%201.704.706l3.588%203.588A2.4%202.4%200%200%201%2020%208v12a2%202%200%200%201-2%202z%22%20%2F%3E%20%3Cpath%20d%3D%22M14%202v5a1%201%200%200%200%201%201h5%22%20%2F%3E%20%3Cpath%20d%3D%22M10%209H8%22%20%2F%3E%20%3Cpath%20d%3D%22M16%2013H8%22%20%2F%3E%20%3Cpath%20d%3D%22M16%2017H8%22%20%2F%3E%20%3C%2Fsvg%3E",L="data:image/svg+xml;charset=utf-8,%3Csvg%20class%3D%22lucide%20lucide-globe%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22currentColor%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20%3E%20%3Ccircle%20cx%3D%2212%22%20cy%3D%2212%22%20r%3D%2210%22%20%2F%3E%20%3Cpath%20d%3D%22M12%202a14.5%2014.5%200%200%200%200%2020%2014.5%2014.5%200%200%200%200-20%22%20%2F%3E%20%3Cpath%20d%3D%22M2%2012h20%22%20%2F%3E%20%3C%2Fsvg%3E",I="data:image/svg+xml;charset=utf-8,%3Csvg%20class%3D%22lucide%20lucide-user%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22currentColor%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20%3E%20%3Cpath%20d%3D%22M19%2021v-2a4%204%200%200%200-4-4H9a4%204%200%200%200-4%204v2%22%20%2F%3E%20%3Ccircle%20cx%3D%2212%22%20cy%3D%227%22%20r%3D%224%22%20%2F%3E%20%3C%2Fsvg%3E",E=({label:t="Copy",successText:o="Copied!",targetId:r})=>{const c=q(r),v=q(t),d=G(t),l=G(o);return{html:`
2
2
  <button
3
3
  type="button"
4
- aria-label="${m}"
5
- title="${x}"
4
+ aria-label="${v}"
5
+ title="${d}"
6
6
  class="ono-copy-btn relative inline-flex justify-center items-center size-8 rounded-[var(--ono-radius-md)] shadow-[var(--ono-elevation-1)] bg-[var(--ono-surface)] text-[var(--ono-text)] hover:bg-[var(--ono-hover-overlay)] focus:outline-hidden focus:bg-[var(--ono-hover-overlay)] disabled:opacity-50 disabled:pointer-events-none cursor-pointer"
7
7
  data-target="#${c}"
8
8
  data-success-text="${l}"
9
9
  >
10
- <span class="ono-copy-default dui size-4 transition" style="-webkit-mask-image: url('${X}'); mask-image: url('${X}')"></span>
11
- <span class="ono-copy-success dui hidden size-4 text-[var(--ono-red-orange)]" style="-webkit-mask-image: url('${W}'); mask-image: url('${W}')"></span>
12
- <span class="sr-only">${x}</span>
10
+ <span class="ono-copy-default dui size-4 transition" style="-webkit-mask-image: url('${_}'); mask-image: url('${_}')"></span>
11
+ <span class="ono-copy-success dui hidden size-4 text-[var(--ono-red-orange)]" style="-webkit-mask-image: url('${K}'); mask-image: url('${K}')"></span>
12
+ <span class="sr-only">${d}</span>
13
13
  </button>
14
- `}},"copyButton");var pe=Object.defineProperty,j=J((e,r)=>pe(e,"name",{value:r,configurable:!0}),"s$1");const me=typeof globalThis<"u"&&"Request"in globalThis,Z=j((e,r)=>r in e,"hasProperty"),R=j((e,r)=>Z(e,r)&&typeof e[r]=="function","hasMethod"),ee=j((e,r)=>Z(e,r)&&typeof e[r]=="string","hasStringProperty"),xe=j(e=>me&&R(e,"clone"),"isNativeRequest"),be=j(e=>R(e,"on")&&ee(e,"method"),"isIncomingMessage");j(e=>!xe(e)&&!be(e)&&ee(e,"method"),"isExpressRequest");const L=j(e=>typeof e=="object"&&e!==null&&R(e,"forEach")&&R(e,"get"),"isHeadersObject");var ge=Object.defineProperty,s=J((e,r)=>ge(e,"name",{value:r,configurable:!0}),"s");const he=[/authorization/i,/cookie/i,/set-cookie/i,/x-api-key/i,/api-key/i,/x-auth/i,/token/i,/secret/i],H=s((e,r)=>{if(typeof e=="object"&&e!==null&&r in e)return e[r]},"safeGetProperty"),p=s((e,r)=>{const o=H(e,r);return typeof o=="function"?o:void 0},"safeGetMethod"),A=s((e,r)=>{const o=H(e,r);return typeof o=="string"?o:void 0},"safeGetString"),fe=s((e,r)=>r?.some(o=>o.toLowerCase()===e.toLowerCase())?!0:he.some(o=>o.test(e)),"isSensitiveHeader"),ye=s(e=>{if(!e)return[];if(L(e)){const r=p(e,"entries");if(r){const o=r();return Array.from(o,([c,m])=>[c,m])}}return Object.entries(e)},"normalizeHeadersToEntries"),we=s((e,r)=>!r||r.length===0?!0:r.some(o=>o.toLowerCase()===e.toLowerCase()),"shouldIncludeHeader"),$e=s((e,r)=>!e||!r?e:Array.isArray(e)?e.map(()=>r):r,"maskHeaderValue"),ke=s((e,r,o,c="[masked]")=>{const m=ye(e);if(m.length===0)return;const x={};return m.forEach(([l,n])=>{if(!we(l,r)||n===void 0)return;const b=fe(l.toLowerCase(),o)?c:void 0;x[l]=$e(n,b)}),x},"filterHeaders"),Y=s(e=>{try{return JSON.stringify(e,null,2)}catch{return String(e)}},"safeJsonStringify"),De=s(e=>{if(!e)return{};const r={};return e.split(";").map(o=>o.trim()).filter(Boolean).forEach(o=>{const c=o.indexOf("=");if(c===-1)return;const m=o.slice(0,c).trim(),x=o.slice(c+1).trim();r[m]=x}),r},"parseCookieString"),Ce=s(async(e,r)=>{try{const o=(A(e,"method")??"GET").toUpperCase();if(o==="GET"||o==="HEAD")return;const c=H(e,"headers"),m=L(c)?p(c,"get")?.("content-type")??"":String(c["content-type"]??""),x=p(e,"clone"),l=x?x():e;if(p(l,"json")&&m.includes("application/json"))try{const n=p(l,"json");return n?await n():void 0}catch{try{const n=p(l,"text");return n?await n()??"":void 0}catch{return}}if(p(l,"text"))try{const n=p(l,"text");return n?await n():void 0}catch{return}return p(e,"on")?await new Promise(n=>{try{let b="",N=!1;const D=p(e,"setEncoding");D&&D("utf8");const C=s(w=>{if(N)return;const M=typeof w=="string"?w:String(w);if(b.length+M.length>r){const k=Math.max(0,r-b.length);b+=M.slice(0,k),N=!0,f(),n(`${b}
15
- … [truncated]`);return}b+=M},"onData"),E=s(()=>{f(),n(b||void 0)},"onEnd"),B=s(()=>{f(),n(void 0)},"onError"),f=s(()=>{const w=p(e,"off");w?.("data",C),w?.("end",E),w?.("error",B)},"cleanup"),F=p(e,"on");F?.("data",C),F?.("end",E),F?.("error",B)}catch{n(void 0)}}):void 0}catch{return}},"readRequestBody"),Fe=s(async(e,r)=>{const o=`${Date.now().toString(36)}-${Math.random().toString(36).slice(2,11)}`,{headerAllowlist:c,headerDenylist:m,maskValue:x="[masked]"}=r,l=ke(H(e,"headers"),c,m,x),n=s(t=>{if(t!==void 0)return Array.isArray(t)?t.join(", "):t},"toSingle"),b=s(t=>`'${t.replaceAll("'",String.raw`'\''`)}'`,"shellQuote"),N=s(t=>{if(!t)return;const a=[];return Object.entries(t).forEach(([i,d])=>{const g=n(d)??"";a.push(`${i}=${g}`)}),a.length>0?a.join("; "):void 0},"buildCookieHeader");let D;const C=H(e,"headers");if(L(C)){const t=p(C,"get")?.("cookie");D=typeof t=="string"?t:void 0}else if(C!==void 0){const t=C;if(Array.isArray(t.cookie))[D]=t.cookie;else{const a=t.cookie;D=typeof a=="string"?a:void 0}}const E=De(D),B=Math.max(0,r.previewBytes??64e3),f=await Ce(e,B),F=s(()=>{const t=(A(e,"method")??"GET").toUpperCase(),a=A(e,"url")??"",i=[],d={};l&&Object.entries(l).forEach(([h,$])=>{const Q=n($);Q!==void 0&&(d[h]=Q)});const g=N(E);g&&!d.cookie&&(d.Cookie=g),Object.entries(d).forEach(([h,$])=>{i.push(`-H ${b(`${h}: ${$}`)}`)});let v="";if(f!=null&&t!=="GET"){const h=typeof f=="string"?f:Y(f);v=`--data ${b(h)}`}const u=["curl","-X",t];return i.length>0&&u.push("\\",` ${i.join(` \\
16
- `)}`),v&&u.push("\\",` ${v}`),u.push("\\",` ${b(a)}`),u.join(" ")},"buildCurl")(),w=(await de(["bash"])).codeToHtml(F,{lang:"bash",themes:{dark:"min-dark",light:"min-light"}}),M=le(w),k=s(t=>G(t),"attributeEscape"),y=s(t=>P(t),"escapeHtml"),U=s(t=>!t||Object.keys(t).length===0?'<div class="px-4 pb-4 pt-2 text-xs text-[var(--ono-text-muted)]">(empty)</div>':`<div class="px-4 pb-4">${Object.entries(t).map(([a,i])=>{const d=Array.isArray(i)?i.join(", "):i;return`<div class="grid grid-cols-[200px_1fr] gap-3 items-start py-2 border-b border-[var(--ono-border)]">
17
- <div class="text-[11px] uppercase tracking-wide text-[var(--ono-text-muted)]">${y(a)}</div>
18
- <div class="text-sm break-words whitespace-pre-wrap text-[var(--ono-text)]">${y(d)}</div>
19
- </div>`}).join("")}</div>`,"renderKeyValueTable"),te=s(t=>`<span class="text-[var(--ono-red-orange)]">${typeof t=="string"?`"${y(t)}"`:String(t)}</span>`,"renderPrimitiveValue"),re=s(t=>`<span class="italic text-[var(--ono-text-muted)]">${t===null?"null":"undefined"}</span>`,"renderNullishValue"),oe=s((t,a)=>{if(Object.keys(t).length===0)return'<span class="italic text-[var(--ono-text-muted)]">(empty object)</span>';if(a>=3)return`<span class="italic text-[var(--ono-text-muted)]">{Object with ${String(Object.keys(t).length)} keys}</span>`;const i=Object.entries(t).slice(0,10).map(([g,v])=>{const u=z(v,a+1);return`
14
+ `}},ve=(t,o)=>o in t,P=(t,o)=>ve(t,o)&&typeof t[o]=="function",U=t=>typeof t=="object"&&t!==null&&P(t,"forEach")&&P(t,"get"),Y=[/authorization/i,/cookie/i,/set-cookie/i,/x-api-key/i,/api-key/i,/x-auth/i,/token/i,/secret/i],M=(t,o)=>{if(typeof t=="object"&&t!==null&&o in t)return t[o]},x=(t,o)=>{const r=M(t,o);return typeof r=="function"?r:void 0},F=(t,o)=>{const r=M(t,o);return typeof r=="string"?r:void 0},Z=(t,o)=>o?.some(r=>r.toLowerCase()===t.toLowerCase())?!0:Y.some(r=>r.test(t)),ue=[...Y,/passw(?:or)?d/i,/pwd/i,/credential/i,/passphrase/i],pe=(t,o)=>o?.some(r=>r.toLowerCase()===t.toLowerCase())?!0:ue.some(r=>r.test(t)),me=t=>{if(!t)return[];if(U(t)){const o=x(t,"entries");if(o){const r=o();return Array.from(r,([c,v])=>[c,v])}}return Object.entries(t)},xe=(t,o)=>!o||o.length===0?!0:o.some(r=>r.toLowerCase()===t.toLowerCase()),be=(t,o)=>!t||!o?t:Array.isArray(t)?t.map(()=>o):o,ge=(t,o,r,c="[masked]")=>{const v=me(t);if(v.length===0)return;const d={};return v.forEach(([l,n])=>{if(!xe(l,o)||n===void 0)return;const b=Z(l.toLowerCase(),r)?c:void 0;d[l]=be(n,b)}),d},Q=t=>{try{return JSON.stringify(t,null,2)}catch{return String(t)}},he=t=>{if(!t)return{};const o={};return t.split(";").map(r=>r.trim()).filter(Boolean).forEach(r=>{const c=r.indexOf("=");if(c===-1)return;const v=r.slice(0,c).trim(),d=r.slice(c+1).trim();o[v]=d}),o},fe=async(t,o)=>{try{const r=(F(t,"method")??"GET").toUpperCase();if(r==="GET"||r==="HEAD")return;const c=M(t,"headers"),v=U(c)?x(c,"get")?.("content-type")??"":String(c["content-type"]??""),d=x(t,"clone"),l=d?d():t;if(x(l,"json")&&v.includes("application/json"))try{const n=x(l,"json");return n?await n():void 0}catch{try{const n=x(l,"text");return n?await n()??"":void 0}catch{return}}if(x(l,"text"))try{const n=x(l,"text");return n?await n():void 0}catch{return}return x(t,"on")?await new Promise(n=>{try{let b="",H=!1;const $=x(t,"setEncoding");$&&$("utf8");const k=h=>{if(H)return;const f=typeof h=="string"?h:String(h);if(b.length+f.length>o){const J=Math.max(0,o-b.length);b+=f.slice(0,J),H=!0,A(),n(`${b}
15
+ … [truncated]`);return}b+=f},O=()=>{A(),n(b||void 0)},D=()=>{A(),n(void 0)},A=()=>{const h=x(t,"off");h?.("data",k),h?.("end",O),h?.("error",D)},S=x(t,"on");S?.("data",k),S?.("end",O),S?.("error",D)}catch{n(void 0)}}):void 0}catch{return}},we=async(t,o)=>{const r=`${Date.now().toString(36)}-${Math.random().toString(36).slice(2,11)}`,{headerAllowlist:c,headerDenylist:v,maskValue:d="[masked]"}=o,l=ge(M(t,"headers"),c,v,d),n=e=>{if(e!==void 0)return Array.isArray(e)?e.join(", "):e},b=e=>`'${e.replaceAll("'",String.raw`'\''`)}'`,H=e=>{if(!e)return;const s=[];return Object.entries(e).forEach(([a,i])=>{const p=n(i)??"";s.push(`${a}=${p}`)}),s.length>0?s.join("; "):void 0};let $;const k=M(t,"headers");if(U(k)){const e=x(k,"get")?.("cookie");$=typeof e=="string"?e:void 0}else if(k!==void 0){const e=k;if(Array.isArray(e.cookie))[$]=e.cookie;else{const s=e.cookie;$=typeof s=="string"?s:void 0}}const O=he($),D=d&&Z("cookie",v)?Object.fromEntries(Object.keys(O).map(e=>[e,d])):O,A=Math.max(0,o.previewBytes??64e3),S=await fe(t,A),h=(e,s)=>{if(!d||e===null||typeof e!="object")return e;if(s.has(e))return"[Circular]";s.add(e);let a;return Array.isArray(e)?a=e.map(i=>h(i,s)):a=Object.fromEntries(Object.entries(e).map(([i,p])=>[i,pe(i,v)?d:h(p,s)])),s.delete(e),a},f=h(S,new WeakSet),J=(()=>{const e=(F(t,"method")??"GET").toUpperCase(),s=F(t,"url")??"",a=[],i={};l&&Object.entries(l).forEach(([m,T])=>{const X=n(T);X!==void 0&&(i[m]=X)});const p=H(D),g=Object.keys(i).some(m=>m.toLowerCase()==="cookie");p&&!g&&(i.Cookie=p),Object.entries(i).forEach(([m,T])=>{a.push(`-H ${b(`${m}: ${T}`)}`)});let u="";if(f!=null&&e!=="GET"){const m=typeof f=="string"?f:Q(f);u=`--data ${b(m)}`}const w=["curl","-X",e];return a.length>0&&w.push("\\",` ${a.join(` \\
16
+ `)}`),u&&w.push("\\",` ${u}`),w.push("\\",` ${b(s)}`),w.join(" ")})(),ee=(await de(["bash"])).codeToHtml(J,{lang:"bash",themes:{dark:"min-dark",light:"min-light"}}),te=le(ee),C=e=>q(e),y=e=>G(e),R=e=>!e||Object.keys(e).length===0?'<div class="px-4 pb-4 pt-2 text-xs text-[var(--ono-text-muted)]">(empty)</div>':`<div class="px-4 pb-4">${Object.entries(e).map(([s,a])=>{const i=Array.isArray(a)?a.join(", "):a;return`<div class="grid grid-cols-[200px_1fr] gap-3 items-start py-2 border-b border-[var(--ono-border)]">
17
+ <div class="text-[11px] uppercase tracking-wide text-[var(--ono-text-muted)]">${y(s)}</div>
18
+ <div class="text-sm break-words whitespace-pre-wrap text-[var(--ono-text)]">${y(i)}</div>
19
+ </div>`}).join("")}</div>`,re=e=>`<span class="text-[var(--ono-red-orange)]">${typeof e=="string"?`"${y(e)}"`:String(e)}</span>`,oe=e=>`<span class="italic text-[var(--ono-text-muted)]">${e===null?"null":"undefined"}</span>`,se=(e,s)=>{if(Object.keys(e).length===0)return'<span class="italic text-[var(--ono-text-muted)]">(empty object)</span>';if(s>=3)return`<span class="italic text-[var(--ono-text-muted)]">{Object with ${String(Object.keys(e).length)} keys}</span>`;const a=Object.entries(e).slice(0,10).map(([p,g])=>{const u=z(g,s+1);return`
20
20
  <div class="ml-4 pl-3 py-1 border-l-2 border-[var(--ono-border)]">
21
- <span class="font-medium text-[var(--ono-text)]">${y(g)}:</span> ${u}
22
- </div>`}).join(""),d=Object.keys(t).length>10?`<div class="ml-4 italic text-[var(--ono-text-muted)]">... and ${String(Object.keys(t).length-10)} more keys</div>`:"";return`<div class="space-y-1">${i}${d}</div>`},"renderObjectValue"),z=s((t,a=0)=>{if(t==null)return re(t);if(typeof t=="string"||typeof t=="number"||typeof t=="boolean")return te(t);if(Array.isArray(t)){if(t.length===0)return'<span class="italic text-[var(--ono-text-muted)]">(empty array)</span>';if(a>=3)return`<span class="italic text-[var(--ono-text-muted)]">[Array with ${String(t.length)} items]</span>`;const d=t.slice(0,10).map((v,u)=>{const h=z(v,a+1);return`
21
+ <span class="font-medium text-[var(--ono-text)]">${y(p)}:</span> ${u}
22
+ </div>`}).join(""),i=Object.keys(e).length>10?`<div class="ml-4 italic text-[var(--ono-text-muted)]">... and ${String(Object.keys(e).length-10)} more keys</div>`:"";return`<div class="space-y-1">${a}${i}</div>`},z=(e,s=0)=>{if(e==null)return oe(e);if(typeof e=="string"||typeof e=="number"||typeof e=="boolean")return re(e);if(Array.isArray(e)){if(e.length===0)return'<span class="italic text-[var(--ono-text-muted)]">(empty array)</span>';if(s>=3)return`<span class="italic text-[var(--ono-text-muted)]">[Array with ${String(e.length)} items]</span>`;const i=e.slice(0,10).map((g,u)=>{const w=z(g,s+1);return`
23
23
  <div class="ml-4 pl-3 py-1 border-l-2 border-[var(--ono-border)]">
24
- <span class="text-xs text-[var(--ono-text-muted)]">[${String(u)}]:</span> ${h}
25
- </div>`}).join(""),g=t.length>10?`<div class="ml-4 text-sm italic text-[var(--ono-text-muted)]">... and ${String(t.length-10)} more items</div>`:"";return`<div class="space-y-1">${d}${g}</div>`}if(typeof t=="object")return oe(t,a);const i=typeof t=="symbol"||typeof t=="bigint"||typeof t=="function"?t.toString():JSON.stringify(t);return`<span class="text-[var(--ono-text)]">${y(i)}</span>`},"renderValue"),_=s(t=>!t||Object.keys(t).length===0?'<div class="px-4 pb-4 pt-2 text-xs text-[var(--ono-text-muted)]">(empty)</div>':`<div class="px-4 pb-4">${Object.entries(t).map(([a,i])=>{const d=z(i);return`<div class="grid grid-cols-[200px_1fr] gap-3 items-start py-2 border-b border-[var(--ono-border)]">
26
- <div class="text-[11px] uppercase tracking-wide text-[var(--ono-text-muted)]">${y(a)}</div>
27
- <div class="text-sm break-words text-[var(--ono-text)]">${d}</div>
28
- </div>`}).join("")}</div>`,"renderObjectTable"),se=s(t=>{if(t==null)return'<div class="px-4 pb-4 pt-2 text-xs text-[var(--ono-text-muted)]">(no body)</div>';if(typeof t=="string")return t.trim()===""?'<div class="px-4 pb-4 pt-2 text-xs text-[var(--ono-text-muted)]">(empty string)</div>':`<div class="px-4 pb-4">
29
- <div class="text-sm break-words whitespace-pre-wrap font-mono p-3 rounded border border-[var(--ono-border)] bg-[var(--ono-surface-muted)] text-[var(--ono-text)]">${y(t)}</div>
30
- </div>`;if(typeof t=="object")return Array.isArray(t)?t.length===0?'<div class="px-4 pb-4 pt-2 text-xs text-[var(--ono-text-muted)]">(empty array)</div>':`<div class="px-4 pb-4">
31
- <div class="text-sm break-words p-3 rounded border border-[var(--ono-border)] bg-[var(--ono-surface-muted)] text-[var(--ono-text)]">${z(t)}</div>
32
- </div>`:Object.keys(t).length===0?'<div class="px-4 pb-4 pt-2 text-xs text-[var(--ono-text-muted)]">(empty object)</div>':`<div class="px-4 pb-4">
33
- <div class="text-sm break-words p-3 rounded border border-[var(--ono-border)] bg-[var(--ono-surface-muted)] text-[var(--ono-text)]">${z(t)}</div>
34
- </div>`;const a=t.toString();return`<div class="px-4 pb-4">
35
- <div class="text-sm break-words whitespace-pre-wrap font-mono p-3 rounded border border-[var(--ono-border)] bg-[var(--ono-surface-muted)] text-[var(--ono-text)]">${y(a)}</div>
36
- </div>`},"renderBodyContent"),K=s(t=>{if(!t)return{content:"",sidebar:""};const a=new Set(["request"]),i=Object.keys(t).filter(v=>!a.has(v));if(i.length===0)return{content:"",sidebar:""};let d="",g="";return i.forEach(v=>{const u=t[v];if(u&&typeof u=="object"&&(Array.isArray(u)||Object.keys(u).length>0)){const h=v.charAt(0).toUpperCase()+v.slice(1),$=`context-${v}`;d+=`
24
+ <span class="text-xs text-[var(--ono-text-muted)]">[${String(u)}]:</span> ${w}
25
+ </div>`}).join(""),p=e.length>10?`<div class="ml-4 text-sm italic text-[var(--ono-text-muted)]">... and ${String(e.length-10)} more items</div>`:"";return`<div class="space-y-1">${i}${p}</div>`}if(typeof e=="object")return se(e,s);const a=typeof e=="symbol"||typeof e=="bigint"||typeof e=="function"?e.toString():JSON.stringify(e);return`<span class="text-[var(--ono-text)]">${y(a)}</span>`},V=e=>!e||Object.keys(e).length===0?'<div class="px-4 pb-4 pt-2 text-xs text-[var(--ono-text-muted)]">(empty)</div>':`<div class="px-4 pb-4">${Object.entries(e).map(([s,a])=>{const i=z(a);return`<div class="grid grid-cols-[200px_1fr] gap-3 items-start py-2 border-b border-[var(--ono-border)]">
26
+ <div class="text-[11px] uppercase tracking-wide text-[var(--ono-text-muted)]">${y(s)}</div>
27
+ <div class="text-sm break-words text-[var(--ono-text)]">${i}</div>
28
+ </div>`}).join("")}</div>`,ae=e=>{if(e==null)return'<div class="px-4 pb-4 pt-2 text-xs text-[var(--ono-text-muted)]">(no body)</div>';if(typeof e=="string")return e.trim()===""?'<div class="px-4 pb-4 pt-2 text-xs text-[var(--ono-text-muted)]">(empty string)</div>':`<div class="px-4 pb-4">
29
+ <div class="text-sm break-words whitespace-pre-wrap font-mono p-3 rounded border border-[var(--ono-border)] bg-[var(--ono-surface-muted)] text-[var(--ono-text)]">${y(e)}</div>
30
+ </div>`;if(typeof e=="object")return Array.isArray(e)?e.length===0?'<div class="px-4 pb-4 pt-2 text-xs text-[var(--ono-text-muted)]">(empty array)</div>':`<div class="px-4 pb-4">
31
+ <div class="text-sm break-words p-3 rounded border border-[var(--ono-border)] bg-[var(--ono-surface-muted)] text-[var(--ono-text)]">${z(e)}</div>
32
+ </div>`:Object.keys(e).length===0?'<div class="px-4 pb-4 pt-2 text-xs text-[var(--ono-text-muted)]">(empty object)</div>':`<div class="px-4 pb-4">
33
+ <div class="text-sm break-words p-3 rounded border border-[var(--ono-border)] bg-[var(--ono-surface-muted)] text-[var(--ono-text)]">${z(e)}</div>
34
+ </div>`;const s=e.toString();return`<div class="px-4 pb-4">
35
+ <div class="text-sm break-words whitespace-pre-wrap font-mono p-3 rounded border border-[var(--ono-border)] bg-[var(--ono-surface-muted)] text-[var(--ono-text)]">${y(s)}</div>
36
+ </div>`},W=(e=>{if(!e)return{content:"",sidebar:""};const s=new Set(["request"]),a=Object.keys(e).filter(g=>!s.has(g));if(a.length===0)return{content:"",sidebar:""};let i="",p="";return a.forEach(g=>{const u=e[g];if(u&&typeof u=="object"&&(Array.isArray(u)||Object.keys(u).length>0)){const w=g.charAt(0).toUpperCase()+g.slice(1),m=`context-${g}`;i+=`
37
37
  <div>
38
- <div class="text-[11px] uppercase tracking-wide mb-2 text-[var(--ono-text-muted)]">${h}</div>
38
+ <div class="text-[11px] uppercase tracking-wide mb-2 text-[var(--ono-text-muted)]">${w}</div>
39
39
  <ul class="space-y-1 text-sm">
40
40
  <li>
41
- <a href="#${$}" class="flex items-center gap-2 px-2 py-1 rounded text-[var(--ono-text)] hover:bg-[var(--ono-surface-muted)] ono-scrollspy-active:bg-[var(--ono-surface-muted)] ono-scrollspy-active:font-semibold">
42
- <span class="dui size-3" style="-webkit-mask-image:url('${O}'); mask-image:url('${O}')"></span>
43
- <span>${h}</span>
41
+ <a href="#${m}" class="flex items-center gap-2 px-2 py-1 rounded text-[var(--ono-text)] hover:bg-[var(--ono-surface-muted)] ono-scrollspy-active:bg-[var(--ono-surface-muted)] ono-scrollspy-active:font-semibold">
42
+ <span class="dui size-3" style="-webkit-mask-image:url('${j}'); mask-image:url('${j}')"></span>
43
+ <span>${w}</span>
44
44
  </a>
45
45
  </li>
46
46
  </ul>
47
- </div>`,g+=`
48
- <input type="hidden" id="clipboard-${$}-${o}" value="${k(JSON.stringify(u))}">
49
- <section id="${$}" class="mb-4 rounded-[var(--ono-radius-lg)] shadow-[var(--ono-elevation-1)] overflow-hidden bg-[var(--ono-surface)] border border-[var(--ono-border)]">
47
+ </div>`,p+=`
48
+ <input type="hidden" id="clipboard-${m}-${r}" value="${C(JSON.stringify(u))}">
49
+ <section id="${m}" class="mb-4 rounded-[var(--ono-radius-lg)] shadow-[var(--ono-elevation-1)] overflow-hidden bg-[var(--ono-surface)] border border-[var(--ono-border)]">
50
50
  <div class="px-4 py-2 flex items-center gap-2 bg-[var(--ono-surface-muted)] border-b border-[var(--ono-border)]">
51
- <h3 class="text-sm font-semibold text-[var(--ono-text)]">${h}</h3>
51
+ <h3 class="text-sm font-semibold text-[var(--ono-text)]">${w}</h3>
52
52
  <div class="grow"></div>
53
- <a href="#${$}" class="text-xs text-[var(--ono-text-muted)]" aria-label="Anchor">#</a>
54
- ${S({label:"Copy JSON",targetId:`clipboard-${$}-${o}`}).html}
53
+ <a href="#${m}" class="text-xs text-[var(--ono-text-muted)]" aria-label="Anchor">#</a>
54
+ ${E({label:"Copy JSON",targetId:`clipboard-${m}-${r}`}).html}
55
55
  </div>
56
- <div class="max-w-full overflow-auto mt-2">${_(u)}</div>
57
- </section>`}}),{content:g,sidebar:d}},"generateContextSections")(r.context),ae=`
56
+ <div class="max-w-full overflow-auto mt-2">${V(u)}</div>
57
+ </section>`}}),{content:p,sidebar:i}})(o.context),ie=`
58
58
  <aside class="shrink-0 w-64 sticky top-4 self-start">
59
59
  <nav aria-label="Context sections" class="p-3 rounded-[var(--ono-radius-md)] shadow-[var(--ono-elevation-1)] space-y-4 overflow-auto bg-[var(--ono-surface)] border border-[var(--ono-border)] max-h-[calc(100dvh-2rem)]">
60
60
  <div>
@@ -62,102 +62,102 @@ var ie=Object.defineProperty;var J=(e,r)=>ie(e,"name",{value:r,configurable:!0})
62
62
  <ul class="space-y-1 text-sm">
63
63
  <li>
64
64
  <a href="#context-request" class="flex items-center gap-2 px-2 py-1 rounded text-[var(--ono-text)] hover:bg-[var(--ono-surface-muted)]">
65
- <span class="dui size-3" style="-webkit-mask-image:url('${I}'); mask-image:url('${I}')"></span>
65
+ <span class="dui size-3" style="-webkit-mask-image:url('${L}'); mask-image:url('${L}')"></span>
66
66
  <span>Overview</span>
67
67
  </a>
68
68
  </li>
69
69
  <li>
70
70
  <a href="#context-headers" class="flex items-center gap-2 px-2 py-1 rounded text-[var(--ono-text)] hover:bg-[var(--ono-surface-muted)]">
71
- <span class="dui size-3" style="-webkit-mask-image:url('${O}'); mask-image:url('${O}')"></span>
71
+ <span class="dui size-3" style="-webkit-mask-image:url('${j}'); mask-image:url('${j}')"></span>
72
72
  <span>Headers</span>
73
73
  </a>
74
74
  </li>
75
75
  <li>
76
76
  <a href="#context-body" class="flex items-center gap-2 px-2 py-1 rounded text-[var(--ono-text)] hover:bg-[var(--ono-surface-muted)]">
77
- <span class="dui size-3" style="-webkit-mask-image:url('${T}'); mask-image:url('${T}')"></span>
77
+ <span class="dui size-3" style="-webkit-mask-image:url('${N}'); mask-image:url('${N}')"></span>
78
78
  <span>Body</span>
79
79
  </a>
80
80
  </li>
81
81
  <li>
82
82
  <a href="#context-session" class="flex items-center gap-2 px-2 py-1 rounded text-[var(--ono-text)] hover:bg-[var(--ono-surface-muted)]">
83
- <span class="dui size-3" style="-webkit-mask-image:url('${V}'); mask-image:url('${V}')"></span>
83
+ <span class="dui size-3" style="-webkit-mask-image:url('${I}'); mask-image:url('${I}')"></span>
84
84
  <span>Session</span>
85
85
  </a>
86
86
  </li>
87
87
  <li>
88
88
  <a href="#context-cookies" class="flex items-center gap-2 px-2 py-1 rounded text-[var(--ono-text)] hover:bg-[var(--ono-surface-muted)]">
89
- <span class="dui size-3" style="-webkit-mask-image:url('${q}'); mask-image:url('${q}')"></span>
89
+ <span class="dui size-3" style="-webkit-mask-image:url('${B}'); mask-image:url('${B}')"></span>
90
90
  <span>Cookies</span>
91
91
  </a>
92
92
  </li>
93
93
  </ul>
94
- </div>${K.sidebar}
94
+ </div>${W.sidebar}
95
95
  </nav>
96
96
  </aside>`,ne=`
97
97
  <div class="grow min-w-0">
98
- <input type="hidden" id="clipboard-curl-${o}" value="${k(F)}">
98
+ <input type="hidden" id="clipboard-curl-${r}" value="${C(J)}">
99
99
  <section id="context-request" class="mb-4 rounded-[var(--ono-radius-lg)] shadow-[var(--ono-elevation-1)] overflow-hidden bg-[var(--ono-surface)] border border-[var(--ono-border)]">
100
100
  <div class="px-4 py-2 flex items-center gap-3 min-w-0 bg-[var(--ono-surface-muted)] border-b border-[var(--ono-border)]">
101
- <span class="dui size-4" style="-webkit-mask-image:url('${I}'); mask-image:url('${I}')"></span>
101
+ <span class="dui size-4" style="-webkit-mask-image:url('${L}'); mask-image:url('${L}')"></span>
102
102
  <h2 class="text-sm font-semibold text-[var(--ono-text)]">Request</h2>
103
- <a class="text-sm truncate text-[var(--ono-red-orange)]" href="${ce(A(e,"url")??"#")}">${y(A(e,"url")??"")}</a>
104
- <span class="inline-block text-[10px] px-2 py-0.5 rounded-full bg-[var(--ono-chip-bg)] text-[var(--ono-chip-text)]">${y(A(e,"method")??"GET")}</span>
103
+ <a class="text-sm truncate text-[var(--ono-red-orange)]" href="${ce(F(t,"url")??"#")}">${y(F(t,"url")??"")}</a>
104
+ <span class="inline-block text-[10px] px-2 py-0.5 rounded-full bg-[var(--ono-chip-bg)] text-[var(--ono-chip-text)]">${y(F(t,"method")??"GET")}</span>
105
105
  <div class="grow"></div>
106
- ${S({label:"Copy cURL",targetId:`clipboard-curl-${o}`}).html}
106
+ ${E({label:"Copy cURL",targetId:`clipboard-curl-${r}`}).html}
107
107
  </div>
108
- <div class="px-4 pb-4 overflow-auto mt-2">${M}</div>
108
+ <div class="px-4 pb-4 overflow-auto mt-2">${te}</div>
109
109
  </section>
110
110
 
111
- <input type="hidden" id="clipboard-headers-${o}" value="${k(JSON.stringify(l??{}))}">
111
+ <input type="hidden" id="clipboard-headers-${r}" value="${C(JSON.stringify(l??{}))}">
112
112
  <section id="context-headers" class="mb-4 rounded-[var(--ono-radius-lg)] shadow-[var(--ono-elevation-1)] overflow-hidden bg-[var(--ono-surface)] border border-[var(--ono-border)]">
113
113
  <div class="px-4 py-2 flex items-center gap-2 bg-[var(--ono-surface-muted)] border-b border-[var(--ono-border)]">
114
- <span class="dui size-4" style="-webkit-mask-image:url('${O}'); mask-image:url('${O}')"></span>
114
+ <span class="dui size-4" style="-webkit-mask-image:url('${j}'); mask-image:url('${j}')"></span>
115
115
  <h3 class="text-sm font-semibold text-[var(--ono-text)]">Headers</h3>
116
116
  <div class="grow"></div>
117
117
  <a href="#context-headers" class="text-xs text-[var(--ono-text-muted)]" aria-label="Anchor">#</a>
118
- ${S({label:"Copy JSON",targetId:`clipboard-headers-${o}`}).html}
118
+ ${E({label:"Copy JSON",targetId:`clipboard-headers-${r}`}).html}
119
119
  </div>
120
- <div class="max-w-full overflow-auto mt-2">${U(l)}</div>
120
+ <div class="max-w-full overflow-auto mt-2">${R(l)}</div>
121
121
  </section>
122
122
 
123
- <input type="hidden" id="clipboard-body-${o}" value="${k(Y(f??{}))}">
123
+ <input type="hidden" id="clipboard-body-${r}" value="${C(Q(f??{}))}">
124
124
  <section id="context-body" class="mb-4 rounded-[var(--ono-radius-lg)] shadow-[var(--ono-elevation-1)] overflow-hidden bg-[var(--ono-surface)] border border-[var(--ono-border)]">
125
125
  <div class="px-4 py-2 flex items-center gap-2 bg-[var(--ono-surface-muted)] border-b border-[var(--ono-border)]">
126
- <span class="dui size-4" style="-webkit-mask-image:url('${T}'); mask-image:url('${T}')"></span>
126
+ <span class="dui size-4" style="-webkit-mask-image:url('${N}'); mask-image:url('${N}')"></span>
127
127
  <h3 class="text-sm font-semibold text-[var(--ono-text)]">Body</h3>
128
128
  <div class="grow"></div>
129
129
  <a href="#context-body" class="text-xs text-[var(--ono-text-muted)]" aria-label="Anchor">#</a>
130
- ${S({label:"Copy JSON",targetId:`clipboard-body-${o}`}).html}
130
+ ${E({label:"Copy JSON",targetId:`clipboard-body-${r}`}).html}
131
131
  </div>
132
- <div class="max-w-full overflow-auto mt-2">${se(f)}</div>
132
+ <div class="max-w-full overflow-auto mt-2">${ae(f)}</div>
133
133
  </section>
134
134
 
135
- <input type="hidden" id="clipboard-session-${o}" value="${k(JSON.stringify(e.session??{}))}">
135
+ <input type="hidden" id="clipboard-session-${r}" value="${C(JSON.stringify(t.session??{}))}">
136
136
  <section id="context-session" class="mb-4 rounded-[var(--ono-radius-lg)] shadow-[var(--ono-elevation-1)] overflow-hidden bg-[var(--ono-surface)] border border-[var(--ono-border)]">
137
137
  <div class="px-4 py-2 flex items-center gap-2 bg-[var(--ono-surface-muted)] border-b border-[var(--ono-border)]">
138
- <span class="dui size-4" style="-webkit-mask-image:url('${V}'); mask-image:url('${V}')"></span>
138
+ <span class="dui size-4" style="-webkit-mask-image:url('${I}'); mask-image:url('${I}')"></span>
139
139
  <h3 class="text-sm font-semibold text-[var(--ono-text)]">Session</h3>
140
140
  <div class="grow"></div>
141
141
  <a href="#context-session" class="text-xs text-[var(--ono-text-muted)]" aria-label="Anchor">#</a>
142
- ${S({label:"Copy JSON",targetId:`clipboard-session-${o}`}).html}
142
+ ${E({label:"Copy JSON",targetId:`clipboard-session-${r}`}).html}
143
143
  </div>
144
- <div class="max-w-full overflow-auto mt-2">${_(e.session)}</div>
144
+ <div class="max-w-full overflow-auto mt-2">${V(t.session)}</div>
145
145
  </section>
146
146
 
147
- <input type="hidden" id="clipboard-cookies-${o}" value="${k(JSON.stringify(E))}">
147
+ <input type="hidden" id="clipboard-cookies-${r}" value="${C(JSON.stringify(D))}">
148
148
  <section id="context-cookies" class="mb-4 rounded-[var(--ono-radius-lg)] shadow-[var(--ono-elevation-1)] overflow-hidden bg-[var(--ono-surface)] border border-[var(--ono-border)]">
149
149
  <div class="px-4 py-2 flex items-center gap-2 bg-[var(--ono-surface-muted)] border-b border-[var(--ono-border)]">
150
- <span class="dui size-4" style="-webkit-mask-image:url('${q}'); mask-image:url('${q}')"></span>
150
+ <span class="dui size-4" style="-webkit-mask-image:url('${B}'); mask-image:url('${B}')"></span>
151
151
  <h3 class="text-sm font-semibold text-[var(--ono-text)]">Cookies</h3>
152
152
  <div class="grow"></div>
153
153
  <a href="#context-cookies" class="text-xs text-[var(--ono-text-muted)]" aria-label="Anchor">#</a>
154
- ${S({label:"Copy JSON",targetId:`clipboard-cookies-${o}`}).html}
154
+ ${E({label:"Copy JSON",targetId:`clipboard-cookies-${r}`}).html}
155
155
  </div>
156
- <div class="max-w-full overflow-auto mt-2">${U(E)}
156
+ <div class="max-w-full overflow-auto mt-2">${R(D)}
157
157
  </div>
158
- </section>${K.content}
158
+ </section>${W.content}
159
159
  </div>`;return{code:{html:`
160
160
  <div class="w-full flex gap-4 relative">
161
- ${ae}
161
+ ${ie}
162
162
  ${ne}
163
- </div>`,script:""},id:"context",name:"Context"}},"createRequestContext");export{Fe as default};
163
+ </div>`,script:""},id:"context",name:"Context"}};export{we as default};
package/dist/index.d.ts CHANGED
@@ -1,6 +1,53 @@
1
1
  import { SolutionFinder } from '@visulima/error/solution';
2
2
  import { RenderErrorOptions } from '@visulima/error/error';
3
- import { T as TemplateOptions$1 } from "./packem_shared/types.d-CDlBu_Vx.js";
3
+ import { T as TemplateOptions$1 } from "./packem_shared/types.d-D1UE67tt.js";
4
+ /** A single parsed stack frame in the structured error payload. */
5
+ interface OnoJsonFrame {
6
+ column?: number;
7
+ file?: string;
8
+ line?: number;
9
+ methodName?: string;
10
+ raw: string;
11
+ type?: string;
12
+ }
13
+ /** A single error in the cause chain. */
14
+ interface OnoJsonError {
15
+ frames: OnoJsonFrame[];
16
+ message: string;
17
+ name: string;
18
+ }
19
+ /** Structured, JSON-serializable representation of a rendered error. */
20
+ interface OnoJson {
21
+ /** Detected runtime (node, bun, deno, workerd, …) or undefined when unknown. */
22
+ runtime?: string;
23
+ /** Suggested solution found by the configured solution finders, if any. */
24
+ solution?: {
25
+ body: string;
26
+ header: string;
27
+ };
28
+ /** The error and its full cause chain, outermost first. */
29
+ stack: OnoJsonError[];
30
+ }
31
+ type ToJsonOptions = {
32
+ solutionFinders?: SolutionFinder[];
33
+ };
34
+ /**
35
+ * Build a structured, JSON-serializable representation of an error — its name/message, the parsed
36
+ * stack frames for the error and every entry in its `cause` chain, the detected runtime, and a
37
+ * suggested solution (using the same solution-finder protocol as the HTML/ANSI renderers).
38
+ *
39
+ * Useful for API servers that want to return parsed error data when the client requests
40
+ * `Accept: application/json`, mirroring Youch's `toJSON()`.
41
+ * @param error the value thrown (any value is coerced to an `Error`).
42
+ * @param options optional custom solution finders.
43
+ * @returns a plain object safe to pass to `JSON.stringify`.
44
+ * @example
45
+ * ```typescript
46
+ * const payload = await toJSON(error);
47
+ * response.json(payload);
48
+ * ```
49
+ */
50
+ declare const toJSON: (error: unknown, options?: ToJsonOptions) => Promise<OnoJson>;
4
51
  type ColorizeMethod = (value: string) => string;
5
52
  type BaseCliOptions = {
6
53
  solutionTitle?: string;
@@ -20,6 +67,33 @@ type TemplateOptions = TemplateOptions$1 & {
20
67
  solutionFinders?: SolutionFinder[];
21
68
  };
22
69
  type CliOptions = BaseCliOptions;
70
+ /**
71
+ * Render an error to a Shiki-highlighted HTML error page.
72
+ * @param error the value thrown (any value is coerced to an `Error`).
73
+ * @param options template options, including custom `solutionFinders`.
74
+ */
75
+ declare const renderHtml: (error: unknown, options?: TemplateOptions) => Promise<string>;
76
+ /**
77
+ * Render an error to ANSI terminal output.
78
+ * @param error the value thrown (any value is coerced to an `Error`).
79
+ * @param options CLI options forwarded to the shared ANSI builder.
80
+ */
81
+ declare const renderAnsi: (error: unknown, options?: CliOptions) => Promise<{
82
+ errorAnsi: string;
83
+ solutionBox: undefined | string;
84
+ }>;
85
+ /**
86
+ * Render an error to a structured, JSON-serializable payload (parsed frames, cause chain, runtime,
87
+ * suggested solution). Useful for SPA/REST error responses (`Accept: application/json`).
88
+ * @param error the value thrown (any value is coerced to an `Error`).
89
+ * @param options optional custom `solutionFinders`.
90
+ */
91
+ declare const renderJson: typeof toJSON;
92
+ /**
93
+ * Convenience class wrapping the standalone {@link renderHtml}, {@link renderAnsi} and
94
+ * {@link renderJson} renderers. The standalone functions are preferred for tree-shaking; the class
95
+ * remains for backwards compatibility.
96
+ */
23
97
  declare class Ono {
24
98
  /**
25
99
  * Render error to HTML.
@@ -32,5 +106,9 @@ declare class Ono {
32
106
  errorAnsi: string;
33
107
  solutionBox: undefined | string;
34
108
  }>;
109
+ /**
110
+ * Render error to a structured JSON payload.
111
+ */
112
+ toJSON(error: unknown, options?: ToJsonOptions): ReturnType<typeof toJSON>;
35
113
  }
36
- export { CliOptions, Ono, TemplateOptions };
114
+ export { CliOptions, Ono, type OnoJson, type OnoJsonError, type OnoJsonFrame, TemplateOptions, type ToJsonOptions, renderAnsi, renderHtml, renderJson };