@databehave/server 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/LICENSE +21 -0
  3. package/README.md +87 -0
  4. package/dist/admin/admin-routes.d.ts +42 -0
  5. package/dist/admin/admin-routes.d.ts.map +1 -0
  6. package/dist/admin/admin-routes.js +472 -0
  7. package/dist/admin/admin-routes.js.map +1 -0
  8. package/dist/admin/admin-types.d.ts +170 -0
  9. package/dist/admin/admin-types.d.ts.map +1 -0
  10. package/dist/admin/admin-types.js +30 -0
  11. package/dist/admin/admin-types.js.map +1 -0
  12. package/dist/admin/banner.d.ts +11 -0
  13. package/dist/admin/banner.d.ts.map +1 -0
  14. package/dist/admin/banner.js +5 -0
  15. package/dist/admin/banner.js.map +1 -0
  16. package/dist/admin/header-parser.d.ts +14 -0
  17. package/dist/admin/header-parser.d.ts.map +1 -0
  18. package/dist/admin/header-parser.js +171 -0
  19. package/dist/admin/header-parser.js.map +1 -0
  20. package/dist/admin/inject.d.ts +48 -0
  21. package/dist/admin/inject.d.ts.map +1 -0
  22. package/dist/admin/inject.js +167 -0
  23. package/dist/admin/inject.js.map +1 -0
  24. package/dist/admin/overrides-store.d.ts +18 -0
  25. package/dist/admin/overrides-store.d.ts.map +1 -0
  26. package/dist/admin/overrides-store.js +100 -0
  27. package/dist/admin/overrides-store.js.map +1 -0
  28. package/dist/admin/resolve-cors.d.ts +22 -0
  29. package/dist/admin/resolve-cors.d.ts.map +1 -0
  30. package/dist/admin/resolve-cors.js +28 -0
  31. package/dist/admin/resolve-cors.js.map +1 -0
  32. package/dist/admin/scenarios-store.d.ts +15 -0
  33. package/dist/admin/scenarios-store.d.ts.map +1 -0
  34. package/dist/admin/scenarios-store.js +109 -0
  35. package/dist/admin/scenarios-store.js.map +1 -0
  36. package/dist/admin/ui.css +2 -0
  37. package/dist/admin/ui.html +13 -0
  38. package/dist/admin/ui.js +240 -0
  39. package/dist/bin-helpers.d.ts +46 -0
  40. package/dist/bin-helpers.d.ts.map +1 -0
  41. package/dist/bin-helpers.js +61 -0
  42. package/dist/bin-helpers.js.map +1 -0
  43. package/dist/bin.d.ts +3 -0
  44. package/dist/bin.d.ts.map +1 -0
  45. package/dist/bin.js +57 -0
  46. package/dist/bin.js.map +1 -0
  47. package/dist/cors.d.ts +57 -0
  48. package/dist/cors.d.ts.map +1 -0
  49. package/dist/cors.js +167 -0
  50. package/dist/cors.js.map +1 -0
  51. package/dist/define.d.ts +26 -0
  52. package/dist/define.d.ts.map +1 -0
  53. package/dist/define.js +55 -0
  54. package/dist/define.js.map +1 -0
  55. package/dist/index.d.ts +21 -0
  56. package/dist/index.d.ts.map +1 -0
  57. package/dist/index.js +19 -0
  58. package/dist/index.js.map +1 -0
  59. package/dist/json-config.d.ts +170 -0
  60. package/dist/json-config.d.ts.map +1 -0
  61. package/dist/json-config.js +684 -0
  62. package/dist/json-config.js.map +1 -0
  63. package/dist/jsonc.d.ts +24 -0
  64. package/dist/jsonc.d.ts.map +1 -0
  65. package/dist/jsonc.js +125 -0
  66. package/dist/jsonc.js.map +1 -0
  67. package/dist/log.d.ts +57 -0
  68. package/dist/log.d.ts.map +1 -0
  69. package/dist/log.js +103 -0
  70. package/dist/log.js.map +1 -0
  71. package/dist/middleware/request-validation.d.ts +58 -0
  72. package/dist/middleware/request-validation.d.ts.map +1 -0
  73. package/dist/middleware/request-validation.js +328 -0
  74. package/dist/middleware/request-validation.js.map +1 -0
  75. package/dist/mock-mode.d.ts +95 -0
  76. package/dist/mock-mode.d.ts.map +1 -0
  77. package/dist/mock-mode.js +95 -0
  78. package/dist/mock-mode.js.map +1 -0
  79. package/dist/openapi/auto-schema.d.ts +83 -0
  80. package/dist/openapi/auto-schema.d.ts.map +1 -0
  81. package/dist/openapi/auto-schema.js +232 -0
  82. package/dist/openapi/auto-schema.js.map +1 -0
  83. package/dist/openapi/generate.d.ts +20 -0
  84. package/dist/openapi/generate.d.ts.map +1 -0
  85. package/dist/openapi/generate.js +121 -0
  86. package/dist/openapi/generate.js.map +1 -0
  87. package/dist/openapi/loader.d.ts +18 -0
  88. package/dist/openapi/loader.d.ts.map +1 -0
  89. package/dist/openapi/loader.js +25 -0
  90. package/dist/openapi/loader.js.map +1 -0
  91. package/dist/openapi/register.d.ts +58 -0
  92. package/dist/openapi/register.d.ts.map +1 -0
  93. package/dist/openapi/register.js +166 -0
  94. package/dist/openapi/register.js.map +1 -0
  95. package/dist/openapi/seed.d.ts +16 -0
  96. package/dist/openapi/seed.d.ts.map +1 -0
  97. package/dist/openapi/seed.js +58 -0
  98. package/dist/openapi/seed.js.map +1 -0
  99. package/dist/openapi/translate.d.ts +55 -0
  100. package/dist/openapi/translate.d.ts.map +1 -0
  101. package/dist/openapi/translate.js +319 -0
  102. package/dist/openapi/translate.js.map +1 -0
  103. package/dist/openapi/walker.d.ts +44 -0
  104. package/dist/openapi/walker.d.ts.map +1 -0
  105. package/dist/openapi/walker.js +15 -0
  106. package/dist/openapi/walker.js.map +1 -0
  107. package/dist/request.d.ts +19 -0
  108. package/dist/request.d.ts.map +1 -0
  109. package/dist/request.js +66 -0
  110. package/dist/request.js.map +1 -0
  111. package/dist/response.d.ts +13 -0
  112. package/dist/response.d.ts.map +1 -0
  113. package/dist/response.js +43 -0
  114. package/dist/response.js.map +1 -0
  115. package/dist/route-key.d.ts +60 -0
  116. package/dist/route-key.d.ts.map +1 -0
  117. package/dist/route-key.js +105 -0
  118. package/dist/route-key.js.map +1 -0
  119. package/dist/run.d.ts +27 -0
  120. package/dist/run.d.ts.map +1 -0
  121. package/dist/run.js +87 -0
  122. package/dist/run.js.map +1 -0
  123. package/dist/server.d.ts +60 -0
  124. package/dist/server.d.ts.map +1 -0
  125. package/dist/server.js +497 -0
  126. package/dist/server.js.map +1 -0
  127. package/dist/types.d.ts +274 -0
  128. package/dist/types.d.ts.map +1 -0
  129. package/dist/types.js +9 -0
  130. package/dist/types.js.map +1 -0
  131. package/dist/validation/validate.d.ts +38 -0
  132. package/dist/validation/validate.d.ts.map +1 -0
  133. package/dist/validation/validate.js +372 -0
  134. package/dist/validation/validate.js.map +1 -0
  135. package/docs/admin/error-mode.md +14 -0
  136. package/docs/admin/headers.md +63 -0
  137. package/docs/admin/index.md +111 -0
  138. package/docs/admin/overrides.md +23 -0
  139. package/docs/admin/rest-api.md +90 -0
  140. package/docs/admin/scenarios.md +28 -0
  141. package/docs/cli.md +76 -0
  142. package/docs/config/admin.md +62 -0
  143. package/docs/config/cors.md +72 -0
  144. package/docs/config/endpoints.md +162 -0
  145. package/docs/config/index.md +107 -0
  146. package/docs/config/log.md +43 -0
  147. package/docs/config/mock-mode.md +84 -0
  148. package/docs/config/openapi.md +8 -0
  149. package/docs/config/schema.md +34 -0
  150. package/docs/config/server.md +32 -0
  151. package/docs/config/validation.md +161 -0
  152. package/docs/design.md +517 -0
  153. package/docs/errors.md +192 -0
  154. package/docs/getting-started.md +146 -0
  155. package/docs/index.md +23 -0
  156. package/docs/openapi/auto-schema.md +146 -0
  157. package/docs/openapi/fallback.md +242 -0
  158. package/docs/openapi/keywords.md +78 -0
  159. package/docs/recipes.md +258 -0
  160. package/docs/stability.md +118 -0
  161. package/docs/troubleshooting.md +191 -0
  162. package/package.json +80 -0
@@ -0,0 +1,240 @@
1
+ (function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),e.crossOrigin===`use-credentials`?t.credentials=`include`:e.crossOrigin===`anonymous`?t.credentials=`omit`:t.credentials=`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var e=()=>({search:``,methods:new Set([`GET`,`POST`,`PUT`,`DELETE`,`PATCH`]),activeOnly:!1,rightPanelOpen:!1,helpOpen:!1,globalOpen:!1,expandedKey:null,focusedKey:null,routes:[],doc:null,overrides:[],scenarios:[],loading:!0}),t=(e,t)=>`${e.toUpperCase()} ${t}`,n=(...t)=>{let n=e(),r=new Set;return{get:()=>n,set:e=>{n={...n,...e};for(let e of r)e(n)},subscribe:e=>(r.add(e),()=>{r.delete(e)})}},r=(e,t)=>{let n=[];for(let r of e){let e=t?.paths?.[r.path]?.[r.method.toLowerCase()]??{},i=e.tags?.[0],a=r.path.split(`/`).filter(Boolean)[0]??`other`,o=i??a;n.push({method:r.method.toUpperCase(),path:r.path,op:{...e,...r.summary&&!e.summary?{summary:r.summary}:{}},groupKey:o.toLowerCase(),groupLabel:o,source:r.source??`openapi`})}return n},i=(e,t)=>{let n=new Map;for(let t of e){let e=n.get(t.groupKey)??{key:t.groupKey,label:t.groupLabel,ops:[]};e.ops.push(t),n.set(t.groupKey,e)}for(let e of n.values())e.ops.sort((e,t)=>e.path===t.path?e.method.localeCompare(t.method):e.path.localeCompare(t.path));return[...n.values()].sort((e,t)=>e.label.localeCompare(t.label))},a=e=>{let n=new Map;for(let r of e){if(r.matcher.kind!==`exact`)continue;let e=t(r.matcher.method,r.matcher.path);n.set(e,(n.get(e)??0)+1)}return n},o=typeof window<`u`&&window.__DATABEHAVE_BASE__||``,s=void 0,c=async e=>{let t=await e.text(),n=t?JSON.parse(t):void 0;if(!e.ok){let t=typeof n==`object`&&n?n.error:void 0;throw Error(t??`HTTP ${e.status}`)}return n},l={async listOverrides(){return(await c(await fetch(o+`/overrides`))).overrides??[]},async addOverride(e){return(await c(await fetch(o+`/overrides`,{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify(e)}))).override},async removeOverride(e){await c(await fetch(o+`/overrides/`+encodeURIComponent(e),{method:`DELETE`}))},async clearOverrides(){await c(await fetch(o+`/overrides`,{method:`DELETE`}))},async listRoutes(){return(await c(await fetch(o+`/openapi-routes`))).routes??[]},async fetchOpenApi(){if(s!==void 0)return s;try{let e=await fetch(o+`/openapi.json`);return e.ok?(s=await e.json(),s):(s=null,null)}catch{return s=null,null}},async listScenarios(){let e=await fetch(o+`/scenarios`);return e.status===404?[]:(await c(e)).scenarios??[]},async saveScenario(e){await c(await fetch(o+`/scenarios`,{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify({name:e})}))},async loadScenario(e){await c(await fetch(o+`/scenarios/`+encodeURIComponent(e)+`/load`,{method:`POST`}))},async deleteScenario(e){await c(await fetch(o+`/scenarios/`+encodeURIComponent(e),{method:`DELETE`}))}},u=n(),d=0,f=new Set,p=[],m=e=>(f.add(e),()=>{f.delete(e)}),h=()=>{for(let e of f)e(p)},g=(e,t)=>{let n=++d;p=[...p,{id:n,kind:e,message:t}],h(),setTimeout(()=>{p=p.filter(e=>e.id!==n),h()},3e3)},ee=e=>{p=p.filter(t=>t.id!==e),h()},te=()=>p,_=async()=>{try{u.set({overrides:await l.listOverrides()})}catch{}},v=async()=>{try{u.set({scenarios:await l.listScenarios()})}catch{}},y=async(e,t,n)=>{let r=await l.addOverride({matcher:e,mode:t,description:n});return g(`success`,`Override applied`),await _(),r},b=async e=>{await l.removeOverride(e),g(`success`,`Override removed`),await _()},x=async()=>{if(window.confirm(`Clear ALL sticky overrides?`))try{await l.clearOverrides(),g(`success`,`Cleared`),await _()}catch(e){g(`error`,e instanceof Error?e.message:String(e))}},S=async e=>{try{await l.saveScenario(e),g(`success`,`Saved: `+e),await v()}catch(e){g(`error`,e instanceof Error?e.message:String(e))}},C=async e=>{try{await l.loadScenario(e),g(`success`,`Loaded: `+e),await _()}catch(e){g(`error`,e instanceof Error?e.message:String(e))}},w=async e=>{try{await l.deleteScenario(e),g(`success`,`Deleted: `+e),await v()}catch(e){g(`error`,e instanceof Error?e.message:String(e))}},T=()=>u.set({globalOpen:!0,rightPanelOpen:!1}),ne={"&":`&amp;`,"<":`&lt;`,">":`&gt;`,'"':`&quot;`,"'":`&#39;`},E=e=>e==null?``:String(e).replace(/[&<>"']/g,e=>ne[e]??e);function D(e){"@babel/helpers - typeof";return D=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},D(e)}function re(e,t){if(D(e)!=`object`||!e)return e;var n=e[Symbol.toPrimitive];if(n!==void 0){var r=n.call(e,t||`default`);if(D(r)!=`object`)return r;throw TypeError(`@@toPrimitive must return a primitive value.`)}return(t===`string`?String:Number)(e)}function ie(e){var t=re(e,`string`);return D(t)==`symbol`?t:t+``}function O(e,t,n){return(t=ie(t))in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}var ae=class extends HTMLElement{constructor(...e){super(...e),O(this,`_unsub`,void 0),O(this,`_open`,!1)}connectedCallback(){this._render(),this._unsub=u.subscribe(()=>this._render())}disconnectedCallback(){this._unsub?.()}_render(){let e=u.get().scenarios;this.innerHTML=`
2
+ <div style="position:relative">
3
+ <button class="ghost" type="button" data-toggle>Scenarios (${e.length})</button>
4
+ ${this._open?`
5
+ <div style="position:absolute;top:110%;right:0;background:#fff;color:#1f2328;border:1px solid #d0d7de;border-radius:6px;padding:8px;min-width:240px;z-index:50;box-shadow:0 2px 8px rgba(0,0,0,0.1)">
6
+ <button class="btn" style="width:100%;margin-bottom:8px" type="button" data-save>Save current as…</button>
7
+ ${e.map(e=>`
8
+ <div style="display:flex;gap:4px;align-items:center;padding:4px 0;border-top:1px solid #eaeef2">
9
+ <span style="flex:1;font-size:12px">${E(e.name)} <small>(${e.count})</small></span>
10
+ <button class="btn" type="button" data-load="${E(e.name)}">Load</button>
11
+ <button class="btn danger" type="button" data-delete="${E(e.name)}">Delete</button>
12
+ </div>`).join(``)}
13
+ </div>`:``}
14
+ </div>`,this.querySelector(`[data-toggle]`)?.addEventListener(`click`,()=>{this._open=!this._open,this._render()}),this.querySelector(`[data-save]`)?.addEventListener(`click`,()=>{let e=window.prompt(`Scenario name (a-z, 0-9, _ -):`);e&&S(e),this._open=!1,this._render()});for(let e of this.querySelectorAll(`[data-load]`))e.addEventListener(`click`,()=>{C(e.dataset.load??``),this._open=!1,this._render()});for(let e of this.querySelectorAll(`[data-delete]`))e.addEventListener(`click`,()=>{w(e.dataset.delete??``)})}};customElements.define(`dbh-scenarios-menu`,ae);var k=class extends HTMLElement{constructor(...e){super(...e),O(this,`_unsub`,void 0)}connectedCallback(){this._render(),this._unsub=u.subscribe(()=>this._render())}disconnectedCallback(){this._unsub?.()}_render(){let e=u.get(),t=e.overrides.length,n=e.overrides.filter(e=>e.matcher.kind===`global`).length,r=typeof window<`u`?window.location.origin:``;this.innerHTML=`
15
+ <div class="top-bar">
16
+ <h1>Mock Injection</h1>
17
+ <span class="url">${r}</span>
18
+ <span class="badge">${t}</span>
19
+ <button class="ghost" type="button" data-clear>Clear all</button>
20
+ <button class="ghost global-override-btn" type="button" data-global title="Global override">
21
+ Global override${n>0?`<span class="badge global-badge">${n}</span>`:``}
22
+ </button>
23
+ <dbh-scenarios-menu></dbh-scenarios-menu>
24
+ <button class="ghost" type="button" data-help title="?">?</button>
25
+ <button class="ghost" type="button" data-panel>Active overrides</button>
26
+ </div>`,this.querySelector(`[data-clear]`)?.addEventListener(`click`,()=>{x()}),this.querySelector(`[data-global]`)?.addEventListener(`click`,()=>T()),this.querySelector(`[data-help]`)?.addEventListener(`click`,()=>u.set({helpOpen:!0})),this.querySelector(`[data-panel]`)?.addEventListener(`click`,()=>u.set({rightPanelOpen:!u.get().rightPanelOpen}))}};customElements.define(`dbh-top-bar`,k);var A=[`GET`,`POST`,`PUT`,`DELETE`,`PATCH`],j=class extends HTMLElement{constructor(...e){super(...e),O(this,`_unsub`,void 0),O(this,`_rendered`,!1),O(this,`searchInput`,void 0)}connectedCallback(){this._render(),this._unsub=u.subscribe(()=>this._sync())}disconnectedCallback(){this._unsub?.()}_render(){let e=u.get();this.innerHTML=`<div class="filter-bar"><input type="text" placeholder="Search path or summary…" value="${M(e.search)}" data-search />`+A.map(t=>`<label><input type="checkbox" data-method="${t}"${e.methods.has(t)?` checked`:``}/> ${t}</label>`).join(``)+`<label><input type="checkbox" data-active${e.activeOnly?` checked`:``}/> Active overrides only</label></div>`,this.searchInput=this.querySelector(`[data-search]`)??void 0,this.searchInput?.addEventListener(`input`,e=>{let t=e.target.value;u.set({search:t})});for(let e of this.querySelectorAll(`[data-method]`))e.addEventListener(`change`,()=>{let t=new Set(u.get().methods),n=e.dataset.method??``;e.checked?t.add(n):t.delete(n),u.set({methods:t})});this.querySelector(`[data-active]`)?.addEventListener(`change`,e=>{u.set({activeOnly:e.target.checked})}),this._rendered=!0}_sync(){if(!this._rendered)return;let e=u.get();this.searchInput&&this.searchInput.value!==e.search&&(this.searchInput.value=e.search);for(let t of this.querySelectorAll(`[data-method]`))t.checked=e.methods.has(t.dataset.method??``);let t=this.querySelector(`[data-active]`);t&&(t.checked=e.activeOnly)}},M=e=>e.replace(/&/g,`&amp;`).replace(/"/g,`&quot;`).replace(/</g,`&lt;`);customElements.define(`dbh-filter-bar`,j);var N={GET:`#1f883d`,POST:`#0969da`,PUT:`#bf8700`,DELETE:`#cf222e`,PATCH:`#1f6feb`},P=e=>e===`http-status`||e===`business-failure`||e===`destroy`?`#cf222e`:e===`delay`||e===`hang`?`#bf8700`:`#0969da`,F=class extends HTMLElement{static get observedAttributes(){return[`method`]}connectedCallback(){this._render()}attributeChangedCallback(){this.isConnected&&this._render()}_render(){let e=(this.getAttribute(`method`)??``).toUpperCase(),t=N[e]??`#57606a`;this.innerHTML=`<span class="method-badge" style="background:${t}">${e}</span>`}};customElements.define(`dbh-method-badge`,F);var I=class extends HTMLElement{setRow(e){let{op:t,override:n,expanded:r,focused:i}=e,a=n?P(n.mode.kind):`transparent`,o=t.source===`handler`;this.innerHTML=`
27
+ <div class="op-row${i?` focused`:``}${r?` expanded`:``}" data-key="${E(t.method+` `+t.path)}">
28
+ <dbh-method-badge method="${E(t.method)}"></dbh-method-badge>
29
+ <span class="path">${E(t.path)}${o?` <span class="no-oas-pill" title="No OpenAPI spec for this route">(no OAS)</span>`:``}</span>
30
+ <span class="summary">${E(t.op.summary??``)}</span>
31
+ <span class="dot${n?` active`:``}" style="${n?`background:${a}`:``}" title="${E(n?.mode.kind??``)}"></span>
32
+ </div>`,this.querySelector(`.op-row`)?.addEventListener(`click`,()=>{let e=t.method+` `+t.path,n=u.get().expandedKey;u.set({expandedKey:n===e?null:e})})}};customElements.define(`dbh-operation-row`,I);var L=e=>e.replace(/&/g,`&amp;`).replace(/</g,`&lt;`).replace(/>/g,`&gt;`).replace(/"/g,`&quot;`),R=e=>e?e.split(/\n{2,}/).map(e=>`<p>${L(e.trim()).replace(/`([^`]+)`/g,(e,t)=>`<code>${t}</code>`).replace(/\n/g,`<br/>`)}</p>`).join(``):``,z={string:`string`,integer:0,number:0,boolean:!0,null:null},B=(e,t,n=0,r=new Set)=>{if(!e||n>8)return null;if(e.example!==void 0)return e.example;if(e.default!==void 0)return e.default;if(e.enum&&e.enum.length>0)return e.enum[0];if(e.$ref){if(r.has(e.$ref))return null;let i=new Set(r);i.add(e.$ref);let a=e.$ref.replace(/^#\/components\/schemas\//,``),o=t?.components?.schemas?.[a];return B(o,t,n+1,i)}if(e.oneOf?.[0])return B(e.oneOf[0],t,n+1,r);if(e.anyOf?.[0])return B(e.anyOf[0],t,n+1,r);if(e.allOf&&e.allOf.length>0){let i={};for(let a of e.allOf){let e=B(a,t,n+1,r);e&&typeof e==`object`&&!Array.isArray(e)&&Object.assign(i,e)}return i}if(e.type===`object`||e.properties){let i={},a=e.properties??{};for(let[e,o]of Object.entries(a))i[e]=B(o,t,n+1,r);return i}return e.type===`array`?[B(e.items,t,n+1,r)]:typeof e.type==`string`&&e.type in z?z[e.type]:null},V=(e,t)=>{let n=e.replace(/^#\/components\/schemas\//,``);return t?.components?.schemas?.[n]},H=e=>e?e.$ref?e.$ref.split(`/`).pop()??`ref`:e.type===`array`?`${H(e.items)}[]`:e.type??(e.properties?`object`:`any`):`any`,U=(e,t,n=0,r,i)=>{if(!e)return`<span class="type">any</span>`;if(n>6)return`<span class="type">…</span>`;let a=e.$ref?V(e.$ref,t)??e:e,o=r?`<span class="key">${E(r)}${i?`<span class="req">*</span>`:``}: </span>`:``,s=`<span class="type">${E(H(a))}</span>`,c=n<2?` open`:``;return a.type===`object`||a.properties?`<details${c}><summary>${o}${s}</summary>`+Object.entries(a.properties??{}).map(([e,r])=>U(r,t,n+1,e,a.required?.includes(e))).join(``)+`</details>`:a.type===`array`?`<details${c}><summary>${o}${s}</summary>`+U(a.items,t,n+1)+`</details>`:`<div style="margin-left:${n?14:0}px">${o}${s}</div>`},W=class extends HTMLElement{constructor(...e){super(...e),O(this,`_op`,null),O(this,`_doc`,null)}setOp(e,t){this._op=e,this._doc=t,this._render()}_render(){let e=this._op;if(!e){this.innerHTML=``;return}if(e.source===`handler`){this.innerHTML=`
33
+ <div class="spec-zone">
34
+ <h4>Spec</h4>
35
+ <div class="desc" style="color:#57606a;font-style:italic">
36
+ No OpenAPI spec for this route. Inject controls and Try-it-out still
37
+ work; request/response schemas are unavailable.
38
+ </div>
39
+ </div>`;return}let t=this._doc,n=e.op.requestBody?.content?.[`application/json`],r=n?.schema,i=n?n.example??B(r,t):null,a=i==null?``:JSON.stringify(i,null,2),o=e.op.parameters??[],s=e.op.responses??{};this.innerHTML=`
40
+ <div class="spec-zone">
41
+ <h4>Spec</h4>
42
+ ${e.op.description?`<div class="desc">${R(e.op.description)}</div>`:``}
43
+ ${o.length>0?`
44
+ <div style="margin-top:8px">
45
+ <h4>Parameters</h4>
46
+ <table>
47
+ <thead><tr><th>name</th><th>in</th><th>type</th><th>required</th><th>description</th></tr></thead>
48
+ <tbody>
49
+ ${o.map(e=>`
50
+ <tr>
51
+ <td><code>${E(e.name)}</code></td>
52
+ <td>${E(e.in)}</td>
53
+ <td>${E(e.schema?.type??`any`)}</td>
54
+ <td>${e.required?`yes`:``}</td>
55
+ <td>${E(e.description??``)}</td>
56
+ </tr>`).join(``)}
57
+ </tbody>
58
+ </table>
59
+ </div>`:``}
60
+ ${r?`
61
+ <div style="margin-top:8px">
62
+ <h4>Request body</h4>
63
+ <div class="schema-tree">${U(r,t)}</div>
64
+ ${a?`<button class="btn ghost" style="margin-top:6px" type="button" data-copy>Copy as JSON example</button>`:``}
65
+ </div>`:``}
66
+ ${Object.keys(s).length>0?`
67
+ <div style="margin-top:8px">
68
+ <h4>Responses</h4>
69
+ ${Object.entries(s).map(([e,n])=>{let r=n.content?.[`application/json`]?.schema;return`
70
+ <details style="margin-top:4px">
71
+ <summary><strong>${E(e)}</strong> — ${E(n.description??``)}</summary>
72
+ ${r?`<div class="schema-tree">${U(r,t)}</div>`:``}
73
+ </details>`}).join(``)}
74
+ </div>`:``}
75
+ </div>`,this.querySelector(`[data-copy]`)?.addEventListener(`click`,()=>{navigator.clipboard?.writeText(a),g(`info`,`Copied`)})}};customElements.define(`dbh-spec-zone`,W);var G=e=>{let t=new TextEncoder().encode(e),n=``;for(let e of t)n+=String.fromCharCode(e);return btoa(n)},K=e=>{for(let t=0;t<e.length;t++)if(e.charCodeAt(t)>127)return!1;return!0},q=[`http-status`,`business-failure`,`custom-body`,`empty-body`,`malformed-json`,`delay`,`hang`,`destroy`],J=class extends HTMLElement{constructor(...e){super(...e),O(this,`_op`,null),O(this,`_existing`,void 0),O(this,`_globalActive`,!1),O(this,`headerOverride`,null),O(this,`_scope`,`exact`),O(this,`_kind`,`http-status`),O(this,`_status`,500),O(this,`_message`,``),O(this,`_body`,`{}`),O(this,`_contentType`,`application/json`),O(this,`_ms`,1e3),O(this,`_description`,``)}setOp(e){let t=this._existing?.id===e.existing?.id&&this._op?.path===e.op.path&&this._op?.method===e.op.method;this._op=e.op,this._existing=e.existing,this._globalActive=e.globalActive,t||this._syncFromExisting(),this._render()}_syncFromExisting(){let e=this._existing;if(!e){this._scope=`exact`,this._kind=`http-status`,this._status=500,this._message=``,this._body=`{}`,this._contentType=`application/json`,this._ms=1e3,this._description=``;return}(e.matcher.kind===`exact`||e.matcher.kind===`path`)&&(this._scope=e.matcher.kind),this._kind=e.mode.kind,this._description=e.description??``;let t=e.mode;t.kind===`http-status`?this._status=t.status:t.kind===`business-failure`?this._message=t.message:t.kind===`custom-body`?(this._status=t.status??200,this._body=JSON.stringify(t.body,null,2),this._contentType=t.contentType??`application/json`):t.kind===`empty-body`?this._status=t.status??204:t.kind===`delay`&&(this._ms=t.ms)}_renderFieldsHtml(){let e=this._kind;return e===`http-status`||e===`empty-body`||e===`malformed-json`?`<div class="row"><label>Status</label><input type="number" data-field="status" value="${this._status}" /></div>`:e===`business-failure`?`<div class="row"><label>Message</label><input type="text" data-field="message" value="${E(this._message)}" placeholder="Optional message returned with the failure" /></div>`:e===`custom-body`?`
76
+ <div class="row"><label>Status</label><input type="number" data-field="status" value="${this._status}" /></div>
77
+ <div class="row"><label>Content-Type</label><input type="text" data-field="contentType" value="${E(this._contentType)}" /></div>
78
+ <div class="row"><label>Body</label><textarea data-field="body">${E(this._body)}</textarea></div>`:e===`delay`?`<div class="row"><label>Delay (ms)</label><input type="number" data-field="ms" value="${this._ms}" /></div>`:``}_render(){if(!this._op){this.innerHTML=``;return}let e=this._existing;this.innerHTML=`
79
+ <div class="mock-zone">
80
+ <h4>Inject error</h4>
81
+ ${this._globalActive?`
82
+ <div class="global-active-hint" role="note">
83
+ <span>A global override is active. Manage it from the top bar.</span>
84
+ <button type="button" class="btn ghost" data-open-global>Open</button>
85
+ </div>`:``}
86
+ <div class="row">
87
+ <label>Scope</label>
88
+ <div class="radio-group">
89
+ <label><input type="radio" name="scope" data-scope="exact"${this._scope===`exact`?` checked`:``}/> Exact (METHOD + this path)</label>
90
+ <label><input type="radio" name="scope" data-scope="path"${this._scope===`path`?` checked`:``}/> Path (any method on this path)</label>
91
+ </div>
92
+ </div>
93
+ <div class="row">
94
+ <label>Mode</label>
95
+ <select data-mode>${q.map(e=>`<option value="${e}"${e===this._kind?` selected`:``}>${e}</option>`).join(``)}</select>
96
+ </div>
97
+ <div data-fields>${this._renderFieldsHtml()}</div>
98
+ <div class="row">
99
+ <label>Description</label>
100
+ <input type="text" data-field="description" value="${E(this._description)}" />
101
+ </div>
102
+ <div class="actions">
103
+ <button class="btn primary" type="button" data-submit>${e?`Update`:`Apply`}</button>
104
+ ${e?`<button class="btn danger" type="button" data-remove="${E(e.id)}">Remove</button>`:``}
105
+ </div>
106
+ </div>`,this._wire()}_wire(){this.querySelector(`[data-open-global]`)?.addEventListener(`click`,()=>T());for(let e of this.querySelectorAll(`[data-scope]`))e.addEventListener(`change`,()=>{e.checked&&(this._scope=e.dataset.scope)});let e=this.querySelector(`[data-mode]`);e?.addEventListener(`change`,()=>{this._kind=e.value;let t=this.querySelector(`[data-fields]`);t&&(t.innerHTML=this._renderFieldsHtml(),this._wireFields())}),this._wireFields(),this.querySelector(`[data-submit]`)?.addEventListener(`click`,()=>this._submit());let t=this.querySelector(`[data-remove]`);t?.addEventListener(`click`,()=>{b(t.dataset.remove??``)})}_wireFields(){let e=this.querySelector(`[data-field="description"]`);e?.addEventListener(`input`,()=>{this._description=e.value});for(let e of this.querySelectorAll(`[data-fields] [data-field]`))e.addEventListener(`input`,()=>{let t=e.dataset.field;t===`status`?this._status=Number(e.value):t===`ms`?this._ms=Number(e.value):t===`message`?this._message=e.value:t===`body`?this._body=e.value:t===`contentType`&&(this._contentType=e.value)})}_submit(){let e=this._op;if(!e)return;let t=this._scope===`exact`?{kind:`exact`,method:e.method,path:e.path}:{kind:`path`,path:e.path},n,r=null,i=this._kind;switch(i){case`http-status`:n={kind:i,status:this._status};break;case`business-failure`:n={kind:i,message:this._message},K(this._message)||(r={name:`x-mock-business-failure-b64`,value:G(this._message)});break;case`custom-body`:{let e;try{e=JSON.parse(this._body)}catch{e=this._body}n={kind:i,status:this._status,body:e,contentType:this._contentType};break}case`empty-body`:n={kind:i,status:this._status};break;case`malformed-json`:n={kind:i,status:this._status};break;case`delay`:n={kind:i,ms:this._ms};break;case`hang`:n={kind:i};break;case`destroy`:n={kind:i};break}this.headerOverride=r,this.dispatchEvent(new CustomEvent(`dbh-header-override`,{detail:r,bubbles:!0})),(async()=>{let e=this._existing;if(e&&t.kind===e.matcher.kind)try{await b(e.id)}catch{}try{await y(t,n,this._description||void 0)}catch(e){g(`error`,e instanceof Error?e.message:String(e))}})()}};customElements.define(`dbh-inject-error-panel`,J);var oe=(e,t)=>{let n=e.op.requestBody?.content?.[`application/json`]?.schema;return n?JSON.stringify(B(n,t),null,2):``},se=class extends HTMLElement{constructor(...e){super(...e),O(this,`_op`,null),O(this,`_doc`,null),O(this,`extraHeader`,null),O(this,`_pvals`,{}),O(this,`_freeQuery`,[]),O(this,`_body`,``),O(this,`_busy`,!1)}setOp(e,t){this._op=e,this._doc=t,this._pvals={};for(let t of e.op.parameters??[])this._pvals[t.name]=String(t.schema?.default??``);this._body=oe(e,t),this._freeQuery=e.source===`handler`?[{key:``,value:``}]:[],this._render()}_render(){let e=this._op;if(!e){this.innerHTML=``;return}let t=e.op.parameters??[],n=e.method!==`GET`&&e.method!==`HEAD`,r=e.source===`handler`;this.innerHTML=`
107
+ <details class="try-it">
108
+ <summary>Try it out</summary>
109
+ ${t.map(e=>`
110
+ <div class="field">
111
+ <label>${E(e.name)} <small>(${E(e.in)}${e.required?`, required`:``})</small></label>
112
+ <input type="text" data-pname="${E(e.name)}" value="${E(this._pvals[e.name]??``)}" />
113
+ </div>`).join(``)}
114
+ ${r?`
115
+ <div class="field">
116
+ <label>query params <small>(free-form)</small></label>
117
+ <div data-free-query>
118
+ ${this._freeQuery.map((e,t)=>`
119
+ <div class="free-q-row" style="display:flex;gap:6px;margin-bottom:4px">
120
+ <input type="text" data-fq-key="${t}" placeholder="name" value="${E(e.key)}" style="flex:1" />
121
+ <input type="text" data-fq-val="${t}" placeholder="value" value="${E(e.value)}" style="flex:2" />
122
+ <button class="btn ghost" type="button" data-fq-rm="${t}" title="Remove">\u2212</button>
123
+ </div>`).join(``)}
124
+ <button class="btn ghost" type="button" data-fq-add>+ Add query param</button>
125
+ </div>
126
+ </div>`:``}
127
+ ${n?`
128
+ <div class="field">
129
+ <label>body${r?` <small>(free-form)</small>`:``}</label>
130
+ <textarea data-body style="width:100%;min-height:100px;font-family:ui-monospace,Menlo,monospace">${E(this._body)}</textarea>
131
+ </div>`:``}
132
+ <button class="btn primary" type="button" data-send${this._busy?` disabled`:``}>${this._busy?`…`:`Send`}</button>
133
+ <dbh-response-viewer></dbh-response-viewer>
134
+ </details>`;for(let e of this.querySelectorAll(`[data-pname]`))e.addEventListener(`input`,()=>{this._pvals[e.dataset.pname??``]=e.value});for(let e of this.querySelectorAll(`[data-fq-key]`))e.addEventListener(`input`,()=>{let t=Number(e.dataset.fqKey);this._freeQuery[t]&&(this._freeQuery[t].key=e.value)});for(let e of this.querySelectorAll(`[data-fq-val]`))e.addEventListener(`input`,()=>{let t=Number(e.dataset.fqVal);this._freeQuery[t]&&(this._freeQuery[t].value=e.value)});for(let e of this.querySelectorAll(`[data-fq-rm]`))e.addEventListener(`click`,()=>{let t=Number(e.dataset.fqRm);this._freeQuery.splice(t,1),this._freeQuery.length===0&&this._freeQuery.push({key:``,value:``}),this._render()});this.querySelector(`[data-fq-add]`)?.addEventListener(`click`,()=>{this._freeQuery.push({key:``,value:``}),this._render()});let i=this.querySelector(`[data-body]`);i?.addEventListener(`input`,()=>{this._body=i.value}),this.querySelector(`[data-send]`)?.addEventListener(`click`,()=>{this._send()})}_buildUrl(){let e=this._op;if(!e)return``;let t=e.op.parameters??[],n=e.path,r=new URLSearchParams;for(let e of t){let t=this._pvals[e.name];t&&(e.in===`path`?n=n.replace(`{${e.name}}`,encodeURIComponent(t)):e.in===`query`&&r.set(e.name,t))}for(let e of this._freeQuery)e.key&&r.append(e.key,e.value);let i=r.toString();return i?`${n}?${i}`:n}async _send(){let e=this._op;if(!e)return;this._busy=!0;let t=this.querySelector(`dbh-response-viewer`);t?.setResult(null);let n=this.querySelector(`[data-send]`);n&&(n.disabled=!0,n.textContent=`…`);let r=performance.now();try{let n={};for(let t of e.op.parameters??[])t.in===`header`&&this._pvals[t.name]&&(n[t.name]=this._pvals[t.name]);this.extraHeader&&(n[this.extraHeader.name]=this.extraHeader.value);let i={method:e.method,headers:n};e.method!==`GET`&&e.method!==`HEAD`&&this._body&&(n[`content-type`]=n[`content-type`]??`application/json`,i.body=this._body);let a=await fetch(this._buildUrl(),i),o=await a.text(),s={status:a.status,headers:{},body:o,elapsedMs:Math.round(performance.now()-r)};a.headers.forEach((e,t)=>{s.headers[t]=e}),t?.setResult(s)}catch(e){t?.setResult({error:e instanceof Error?e.message:String(e)})}finally{this._busy=!1,n&&(n.disabled=!1,n.textContent=`Send`)}}};customElements.define(`dbh-try-it-out`,se);var Y=(e,t)=>{if(!e.methods||e.methods.length===0)return!0;let n=t.toUpperCase();return e.methods.some(e=>e.toUpperCase()===n)},ce=class extends HTMLElement{constructor(...e){super(...e),O(this,`_spec`,void 0),O(this,`_inject`,void 0),O(this,`_try`,void 0)}connectedCallback(){this.childElementCount===0&&(this.innerHTML=`
135
+ <div class="op-card">
136
+ <dbh-spec-zone></dbh-spec-zone>
137
+ <dbh-inject-error-panel></dbh-inject-error-panel>
138
+ <dbh-try-it-out></dbh-try-it-out>
139
+ </div>`,this._spec=this.querySelector(`dbh-spec-zone`),this._inject=this.querySelector(`dbh-inject-error-panel`),this._try=this.querySelector(`dbh-try-it-out`),this._inject.addEventListener(`dbh-header-override`,e=>{this._try.extraHeader=e.detail}))}setData(e){this._spec||this.connectedCallback();let{op:t,doc:n,overrides:r}=e,i=r.find(e=>e.matcher.kind===`exact`&&e.matcher.method===t.method&&e.matcher.path===t.path),a=r.some(e=>e.matcher.kind===`global`&&Y(e.matcher,t.method));this._spec.setOp(t,n),this._inject.setOp({op:t,existing:i,globalActive:a}),this._try.setOp(t,n)}};customElements.define(`dbh-operation-card`,ce);var le=class extends HTMLElement{constructor(...e){super(...e),O(this,`_label`,``),O(this,`_ops`,[]),O(this,`_doc`,null),O(this,`_overrides`,[]),O(this,`_expandedKey`,null),O(this,`_focusedKey`,null),O(this,`_open`,!0)}setData(e){this._label=e.label,this._ops=e.ops,this._doc=e.doc,this._overrides=e.overrides,this._expandedKey=e.expandedKey,this._focusedKey=e.focusedKey,this._render()}_render(){let e=this._ops.reduce((e,t)=>e+ +!!this._overrides.some(e=>e.matcher.kind===`exact`&&e.matcher.method===t.method&&e.matcher.path===t.path),0);this.innerHTML=`
140
+ <div class="group${this._open?` open`:``}">
141
+ <div class="group-header" data-header>
142
+ <h3>${E(this._label)}</h3>
143
+ <span class="count">${this._ops.length} ops${e?` · ${e} active`:``}</span>
144
+ <span>${this._open?`−`:`+`}</span>
145
+ </div>
146
+ <div class="group-body" data-body></div>
147
+ </div>`,this.querySelector(`[data-header]`)?.addEventListener(`click`,()=>{this._open=!this._open,this._render()});let t=this.querySelector(`[data-body]`);if(t)for(let e of this._ops){let n=`${e.method} ${e.path}`,r=this._overrides.find(t=>t.matcher.kind===`exact`&&t.matcher.method===e.method&&t.matcher.path===e.path),i=document.createElement(`dbh-operation-row`);if(i.setRow({op:e,override:r,expanded:this._expandedKey===n,focused:this._focusedKey===n}),t.appendChild(i),this._expandedKey===n){let n=document.createElement(`dbh-operation-card`);t.appendChild(n),n.setData({op:e,doc:this._doc,overrides:this._overrides})}}}};customElements.define(`dbh-tag-group`,le);var ue=e=>{if(e.matcher.kind===`exact`)return`${e.matcher.method} ${e.matcher.path}`;if(e.matcher.kind===`path`)return`* ${e.matcher.path}`;let t=e.matcher.methods;return t&&t.length>0?`${t.join(`|`)} (global)`:`* (global)`},X=e=>e.matcher.kind===`global`?0:e.matcher.kind===`path`?1:2,de=class extends HTMLElement{constructor(...e){super(...e),O(this,`_unsub`,void 0)}connectedCallback(){this._render(),this._unsub=u.subscribe(()=>this._render())}disconnectedCallback(){this._unsub?.()}_render(){let e=u.get(),n=[...e.overrides].sort((e,t)=>X(e)-X(t));this.innerHTML=`
148
+ <aside class="right-panel${e.rightPanelOpen?` open`:``}">
149
+ <header>
150
+ <h2>Active overrides (${e.overrides.length})</h2>
151
+ <button class="btn ghost" type="button" data-close>×</button>
152
+ </header>
153
+ <div class="list">
154
+ ${e.overrides.length===0?`<p style="padding:12px;color:#57606a">No active overrides</p>`:n.map(e=>`
155
+ <div class="override-item${e.matcher.kind===`global`?` is-global`:``}" data-jump="${E(e.id)}">
156
+ <span class="dot" style="background:${P(e.mode.kind)}"></span>
157
+ ${e.matcher.kind===`global`?`<span class="scope-badge scope-global">GLOBAL</span>`:``}
158
+ <div class="info">
159
+ <div class="route">${E(ue(e))}</div>
160
+ <small>${E(e.mode.kind)}${e.description?` — ${E(e.description)}`:``}</small>
161
+ </div>
162
+ <button class="btn danger" type="button" data-remove="${E(e.id)}">Remove</button>
163
+ </div>`).join(``)}
164
+ </div>
165
+ </aside>`,this.querySelector(`[data-close]`)?.addEventListener(`click`,()=>u.set({rightPanelOpen:!1}));for(let e of this.querySelectorAll(`[data-jump]`))e.addEventListener(`click`,()=>{let n=e.dataset.jump??``,r=u.get().overrides.find(e=>e.id===n);if(!r)return;if(r.matcher.kind===`global`){T();return}if(r.matcher.kind!==`exact`){u.set({rightPanelOpen:!1});return}let i=t(r.matcher.method,r.matcher.path);u.set({expandedKey:i,focusedKey:i,rightPanelOpen:!1}),setTimeout(()=>{let e=document.querySelector(`[data-key="${i.replace(/"/g,`\\"`)}"]`);e&&`scrollIntoView`in e&&e.scrollIntoView({behavior:`smooth`,block:`center`})},50)});for(let e of this.querySelectorAll(`[data-remove]`))e.addEventListener(`click`,t=>{t.stopPropagation(),b(e.dataset.remove??``)})}};customElements.define(`dbh-active-overrides-panel`,de);var fe=class extends HTMLElement{constructor(...e){super(...e),O(this,`_unsub`,void 0)}connectedCallback(){this._render(),this._unsub=u.subscribe(()=>this._render())}disconnectedCallback(){this._unsub?.()}_render(){let e=u.get().overrides.length;if(e===0){this.innerHTML=``;return}this.innerHTML=`<button class="pill-float" type="button">${e} Active overrides</button>`,this.querySelector(`button`)?.addEventListener(`click`,()=>u.set({rightPanelOpen:!0}))}};customElements.define(`dbh-floating-pill`,fe);var pe=class extends HTMLElement{constructor(...e){super(...e),O(this,`_unsub`,void 0)}connectedCallback(){this._render(te()),this._unsub=m(e=>this._render(e))}disconnectedCallback(){this._unsub?.()}_render(e){this.innerHTML=`<div class="toast-stack">`+e.map(e=>`<div class="toast ${E(e.kind)}" data-id="${e.id}">${E(e.message)}</div>`).join(``)+`</div>`;for(let e of this.querySelectorAll(`.toast`))e.addEventListener(`click`,()=>{let t=Number(e.dataset.id);Number.isFinite(t)&&ee(t)})}};customElements.define(`dbh-toasts`,pe);var me=class extends HTMLElement{constructor(...e){super(...e),O(this,`_unsub`,void 0)}connectedCallback(){this._render(),this._unsub=u.subscribe(()=>this._render())}disconnectedCallback(){this._unsub?.()}_render(){if(!u.get().helpOpen){this.innerHTML=``;return}this.innerHTML=`
166
+ <div class="modal-backdrop">
167
+ <div class="modal">
168
+ <h2>Keyboard shortcuts</h2>
169
+ <table>
170
+ <tbody>
171
+ <tr><td><kbd>/</kbd> or <kbd>Ctrl/⌘ + K</kbd></td><td>Focus search</td></tr>
172
+ <tr><td><kbd>Esc</kbd></td><td>Close panel / blur search</td></tr>
173
+ <tr><td><kbd>?</kbd></td><td>Open this help</td></tr>
174
+ <tr><td><kbd>↑</kbd> / <kbd>↓</kbd></td><td>Navigate operation list</td></tr>
175
+ <tr><td><kbd>Enter</kbd></td><td>Expand / collapse selected</td></tr>
176
+ </tbody>
177
+ </table>
178
+ <div style="margin-top:12px;text-align:right">
179
+ <button class="btn" type="button" data-ok>OK</button>
180
+ </div>
181
+ </div>
182
+ </div>`;let e=()=>u.set({helpOpen:!1});this.querySelector(`.modal-backdrop`)?.addEventListener(`click`,e),this.querySelector(`.modal`)?.addEventListener(`click`,e=>e.stopPropagation()),this.querySelector(`[data-ok]`)?.addEventListener(`click`,e)}};customElements.define(`dbh-help-modal`,me);var he=[`http-status`,`business-failure`,`custom-body`,`empty-body`,`malformed-json`,`delay`,`hang`,`destroy`],Z=[`GET`,`POST`,`PUT`,`PATCH`,`DELETE`,`HEAD`,`OPTIONS`],ge=class extends HTMLElement{constructor(...e){super(...e),O(this,`_unsub`,void 0),O(this,`_kind`,`http-status`),O(this,`_status`,503),O(this,`_message`,``),O(this,`_body`,`{}`),O(this,`_contentType`,`application/json`),O(this,`_ms`,1e3),O(this,`_description`,``),O(this,`_methods`,new Set(Z)),O(this,`_lastExistingId`,void 0)}connectedCallback(){this._render(),this._unsub=u.subscribe(()=>this._render())}disconnectedCallback(){this._unsub?.()}_globals(){return u.get().overrides.filter(e=>e.matcher.kind===`global`)}_syncFromExisting(e,t){if(t&&!(e&&e.id===this._lastExistingId))if(this._lastExistingId=e?.id,e){this._kind=e.mode.kind,this._description=e.description??``;let t=e.matcher.kind===`global`?e.matcher.methods:void 0;this._methods=new Set(t&&t.length>0?t.map(e=>e.toUpperCase()):Z);let n=e.mode;n.kind===`http-status`?this._status=n.status:n.kind===`business-failure`?this._message=n.message:n.kind===`custom-body`?(this._status=n.status??200,this._body=JSON.stringify(n.body,null,2),this._contentType=n.contentType??`application/json`):n.kind===`empty-body`?this._status=n.status??204:n.kind===`delay`&&(this._ms=n.ms)}else this._kind=`http-status`,this._status=503,this._message=``,this._body=`{}`,this._contentType=`application/json`,this._ms=1e3,this._description=``,this._methods=new Set(Z)}_renderFieldsHtml(){let e=this._kind;return e===`http-status`||e===`empty-body`||e===`malformed-json`?`<div class="row"><label>Status</label><input type="number" data-field="status" value="${this._status}" /></div>`:e===`business-failure`?`<div class="row"><label>Message</label><input type="text" data-field="message" value="${E(this._message)}" /></div>`:e===`custom-body`?`
183
+ <div class="row"><label>Status</label><input type="number" data-field="status" value="${this._status}" /></div>
184
+ <div class="row"><label>Content-Type</label><input type="text" data-field="contentType" value="${E(this._contentType)}" /></div>
185
+ <div class="row"><label>Body</label><textarea data-field="body">${E(this._body)}</textarea></div>`:e===`delay`?`<div class="row"><label>Delay (ms)</label><input type="number" data-field="ms" value="${this._ms}" /></div>`:``}_render(){let e=u.get().globalOpen;if(!e){this.innerHTML=``,this._lastExistingId=void 0;return}let t=this._globals(),n=t[0];this._syncFromExisting(n,e),this.innerHTML=`
186
+ <div class="modal-backdrop">
187
+ <div class="modal global-override-modal">
188
+ <header class="global-override-header">
189
+ <h2>Global override</h2>
190
+ <button type="button" class="btn ghost close-x" data-close aria-label="Close">×</button>
191
+ </header>
192
+ <p class="global-override-desc">Applies to every route on this server. Use to simulate the backend being down, slow, etc.</p>
193
+ <div class="row">
194
+ <label>Mode</label>
195
+ <select data-mode>${he.map(e=>`<option value="${e}"${e===this._kind?` selected`:``}>${e}</option>`).join(``)}</select>
196
+ </div>
197
+ <div class="row global-override-methods">
198
+ <label>Methods</label>
199
+ <div class="method-checkboxes">
200
+ ${Z.map(e=>`
201
+ <label class="method-checkbox">
202
+ <input type="checkbox" data-method="${e}"${this._methods.has(e)?` checked`:``} />
203
+ <span>${e}</span>
204
+ </label>`).join(``)}
205
+ </div>
206
+ </div>
207
+ <div data-fields>${this._renderFieldsHtml()}</div>
208
+ <div class="row">
209
+ <label>Description</label>
210
+ <input type="text" data-field="description" value="${E(this._description)}" />
211
+ </div>
212
+ <div class="actions">
213
+ <button type="button" class="btn primary" data-submit>${n?`Update`:`Apply`}</button>
214
+ ${n?`<button type="button" class="btn danger" data-remove="${E(n.id)}">Remove</button>`:``}
215
+ </div>
216
+ ${t.length>0?`
217
+ <div class="global-override-list">
218
+ <h3>Active global overrides (${t.length})</h3>
219
+ ${t.map(e=>{let t=e.matcher.kind===`global`?e.matcher.methods:void 0,n=t&&t.length>0?`${t.join(`|`)} (global)`:`* (global)`;return`
220
+ <div class="override-item">
221
+ <span class="dot" style="background:${P(e.mode.kind)}"></span>
222
+ <div class="info">
223
+ <div class="route">${E(n)}</div>
224
+ <small>${E(e.mode.kind)}${e.description?` — ${E(e.description)}`:``}</small>
225
+ </div>
226
+ <button type="button" class="btn danger" data-remove="${E(e.id)}">Remove</button>
227
+ </div>`}).join(``)}
228
+ </div>`:``}
229
+ </div>
230
+ </div>`,this._wire()}_wire(){let e=()=>u.set({globalOpen:!1});this.querySelector(`.modal-backdrop`)?.addEventListener(`click`,e),this.querySelector(`.modal`)?.addEventListener(`click`,e=>e.stopPropagation()),this.querySelector(`[data-close]`)?.addEventListener(`click`,e);let t=this.querySelector(`[data-mode]`);t?.addEventListener(`change`,()=>{this._kind=t.value;let e=this.querySelector(`[data-fields]`);e&&(e.innerHTML=this._renderFieldsHtml(),this._wireFields())}),this._wireFields();for(let e of this.querySelectorAll(`[data-method]`))e.addEventListener(`change`,()=>{let t=e.dataset.method??``;e.checked?this._methods.add(t):this._methods.delete(t)});this.querySelector(`[data-submit]`)?.addEventListener(`click`,()=>this._submit());for(let e of this.querySelectorAll(`[data-remove]`))e.addEventListener(`click`,()=>{b(e.dataset.remove??``)})}_wireFields(){let e=this.querySelector(`[data-field="description"]`);e?.addEventListener(`input`,()=>{this._description=e.value});for(let e of this.querySelectorAll(`[data-fields] [data-field]`))e.addEventListener(`input`,()=>{let t=e.dataset.field;t===`status`?this._status=Number(e.value):t===`ms`?this._ms=Number(e.value):t===`message`?this._message=e.value:t===`body`?this._body=e.value:t===`contentType`&&(this._contentType=e.value)})}_submit(){let e,t=this._kind;switch(t){case`http-status`:e={kind:t,status:this._status};break;case`business-failure`:e={kind:t,message:this._message},K(this._message)||G(this._message);break;case`custom-body`:{let n;try{n=JSON.parse(this._body)}catch{n=this._body}e={kind:t,status:this._status,body:n,contentType:this._contentType};break}case`empty-body`:e={kind:t,status:this._status};break;case`malformed-json`:e={kind:t,status:this._status};break;case`delay`:e={kind:t,ms:this._ms};break;case`hang`:e={kind:t};break;case`destroy`:e={kind:t};break}let n=Z.length,r=Z.filter(e=>this._methods.has(e));if(r.length===0){g(`error`,`Select at least one HTTP method.`);return}let i=r.length===n?{kind:`global`}:{kind:`global`,methods:r};(async()=>{let t=this._globals()[0];if(t)try{await b(t.id)}catch{}try{await y(i,e,this._description||void 0)}catch(e){g(`error`,e instanceof Error?e.message:String(e))}})()}};customElements.define(`dbh-global-override-panel`,ge);var _e=[`GET`,`POST`,`PUT`,`DELETE`,`PATCH`],Q=class extends HTMLElement{constructor(...e){super(...e),O(this,`_filter`,void 0),O(this,`_main`,void 0),O(this,`_unsub`,void 0),O(this,`_debounce`,void 0),O(this,`_debouncedSearch`,``),O(this,`_onKeyDown`,e=>this._handleKey(e))}connectedCallback(){this.innerHTML=`
231
+ <div class="app">
232
+ <dbh-top-bar></dbh-top-bar>
233
+ <dbh-filter-bar></dbh-filter-bar>
234
+ <main class="main" data-main></main>
235
+ <dbh-active-overrides-panel></dbh-active-overrides-panel>
236
+ <dbh-floating-pill></dbh-floating-pill>
237
+ <dbh-toasts></dbh-toasts>
238
+ <dbh-help-modal></dbh-help-modal>
239
+ <dbh-global-override-panel></dbh-global-override-panel>
240
+ </div>`,this._filter=this.querySelector(`dbh-filter-bar`),this._main=this.querySelector(`[data-main]`),this._unsub=u.subscribe(()=>this._onStateChange()),window.addEventListener(`keydown`,this._onKeyDown),(async()=>{let[e,t]=await Promise.all([l.listRoutes(),l.fetchOpenApi()]);u.set({routes:e,doc:t??null,loading:!1}),await _(),await v(),this._renderList()})()}disconnectedCallback(){this._unsub?.(),window.removeEventListener(`keydown`,this._onKeyDown),this._debounce&&clearTimeout(this._debounce)}_onStateChange(){u.get().search===this._debouncedSearch?this._renderList():(this._debounce&&clearTimeout(this._debounce),this._debounce=setTimeout(()=>{this._debouncedSearch=u.get().search,this._renderList()},150))}_filtered(){let e=u.get(),t=r(e.routes,e.doc),n=this._debouncedSearch.trim().toLowerCase(),i=e.methods.size>0?e.methods:new Set(_e);return t.filter(t=>!(!i.has(t.method)||n&&!t.path.toLowerCase().includes(n)&&!t.op.summary?.toLowerCase().includes(n)||e.activeOnly&&!e.overrides.some(e=>e.matcher.kind===`exact`&&e.matcher.method===t.method&&e.matcher.path===t.path||e.matcher.kind===`path`&&e.matcher.path===t.path||e.matcher.kind===`global`&&Y(e.matcher,t.method))))}_renderList(){let e=u.get(),t=i(this._filtered(),a(e.overrides));if(this._main.innerHTML=``,t.length===0){let e=document.createElement(`p`);e.style.color=`#57606a`,e.textContent=`No operations match these filters.`,this._main.appendChild(e);return}for(let n of t){let t=document.createElement(`dbh-tag-group`);this._main.appendChild(t),t.setData({label:n.label,ops:n.ops,doc:e.doc,overrides:e.overrides,expandedKey:e.expandedKey,focusedKey:e.focusedKey})}}_handleKey(e){let n=e.target,r=!!n&&(n.tagName===`INPUT`||n.tagName===`TEXTAREA`||n.tagName===`SELECT`),i=this._filter?.searchInput,a=u.get();if(e.key===`/`&&!r)e.preventDefault(),i?.focus();else if((e.ctrlKey||e.metaKey)&&e.key.toLowerCase()===`k`)e.preventDefault(),i?.focus();else if(e.key===`?`&&!r)e.preventDefault(),u.set({helpOpen:!0});else if(e.key===`Escape`)a.helpOpen?u.set({helpOpen:!1}):a.globalOpen?u.set({globalOpen:!1}):a.rightPanelOpen?u.set({rightPanelOpen:!1}):a.expandedKey?u.set({expandedKey:null}):i?.blur();else if((e.key===`ArrowDown`||e.key===`ArrowUp`)&&document.activeElement===i){e.preventDefault();let n=this._filtered().map(e=>t(e.method,e.path));if(n.length===0)return;let r=a.focusedKey?n.indexOf(a.focusedKey):-1,i=e.key===`ArrowDown`?Math.min(n.length-1,r+1):Math.max(0,r-1);u.set({focusedKey:n[i]??null})}else e.key===`Enter`&&document.activeElement===i&&a.focusedKey&&(e.preventDefault(),u.set({expandedKey:a.expandedKey===a.focusedKey?null:a.focusedKey}))}};customElements.define(`dbh-app`,Q);var $=document.getElementById(`root`);if($){let e=document.createElement(`dbh-app`);$.appendChild(e)}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Pure helpers for the `@databehave/server` CLI.
3
+ *
4
+ * Everything in this module is side-effect-free (or accepts injected
5
+ * side effects) so it can be unit-tested without spawning child
6
+ * processes or opening sockets. The thin executable entry point
7
+ * (`bin.ts`) wires these together.
8
+ */
9
+ /** Parsed CLI invocation. `configPath` is the first positional arg. */
10
+ export interface CliOptions {
11
+ open: boolean;
12
+ help: boolean;
13
+ configPath?: string;
14
+ unknown: string[];
15
+ }
16
+ export declare const HELP_TEXT = "Usage: databehave-server [options] <config>\n\nOptions:\n --open After the server is listening, open the admin UI in\n the default browser. No-op (with a log line) when\n admin is absent or admin.enabled !== true.\n -h, --help Show this help and exit.\n";
17
+ /**
18
+ * Parse argv (without `node` / script name). Unknown flags are
19
+ * collected into `unknown` for the entry point to reject — we never
20
+ * silently ignore an unrecognized option.
21
+ */
22
+ export declare const parseCliArgs: (argv: readonly string[]) => CliOptions;
23
+ /** Subset of the listen handle we need to build the admin URL. */
24
+ export interface ListenInfo {
25
+ readonly host: string;
26
+ readonly port: number;
27
+ }
28
+ /** Subset of the resolved admin config we care about for --open. */
29
+ export interface AdminUrlInfo {
30
+ readonly enabled?: boolean;
31
+ readonly path?: string;
32
+ }
33
+ /**
34
+ * Build the URL `--open` should hit. Returns `null` when the admin
35
+ * panel is not active (no `admin`, or `enabled !== true`) — the
36
+ * caller surfaces that as a skip-with-log message. Hosts bound to
37
+ * `0.0.0.0` / `::` are rewritten to the matching loopback so the
38
+ * browser doesn't try to dial the wildcard.
39
+ */
40
+ export declare const composeAdminUrl: (handle: ListenInfo, admin: AdminUrlInfo | undefined) => string | null;
41
+ /** Platform-specific browser opener. Pure — returns the spawn args. */
42
+ export declare const resolveOpener: (platform: NodeJS.Platform, url: string) => {
43
+ cmd: string;
44
+ args: string[];
45
+ };
46
+ //# sourceMappingURL=bin-helpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin-helpers.d.ts","sourceRoot":"","sources":["../src/bin-helpers.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,uEAAuE;AACvE,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,OAAO,CAAA;IACb,IAAI,EAAE,OAAO,CAAA;IACb,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,OAAO,EAAE,MAAM,EAAE,CAAA;CAClB;AAED,eAAO,MAAM,SAAS,wTAOrB,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,YAAY,GAAI,MAAM,SAAS,MAAM,EAAE,KAAG,UActD,CAAA;AAED,kEAAkE;AAClE,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB;AAED,oEAAoE;AACpE,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAA;IAC1B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CACvB;AAED;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,GAC1B,QAAQ,UAAU,EAClB,OAAO,YAAY,GAAG,SAAS,KAC9B,MAAM,GAAG,IAQX,CAAA;AAED,uEAAuE;AACvE,eAAO,MAAM,aAAa,GACxB,UAAU,MAAM,CAAC,QAAQ,EACzB,KAAK,MAAM,KACV;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAA;CAO/B,CAAA"}
@@ -0,0 +1,61 @@
1
+ export const HELP_TEXT = `Usage: databehave-server [options] <config>
2
+
3
+ Options:
4
+ --open After the server is listening, open the admin UI in
5
+ the default browser. No-op (with a log line) when
6
+ admin is absent or admin.enabled !== true.
7
+ -h, --help Show this help and exit.
8
+ `;
9
+ /**
10
+ * Parse argv (without `node` / script name). Unknown flags are
11
+ * collected into `unknown` for the entry point to reject — we never
12
+ * silently ignore an unrecognized option.
13
+ */
14
+ export const parseCliArgs = (argv) => {
15
+ const opts = {
16
+ open: false,
17
+ help: false,
18
+ unknown: [],
19
+ };
20
+ for (const a of argv) {
21
+ if (a === '--open')
22
+ opts.open = true;
23
+ else if (a === '-h' || a === '--help')
24
+ opts.help = true;
25
+ else if (a.startsWith('-'))
26
+ opts.unknown.push(a);
27
+ else if (opts.configPath === undefined)
28
+ opts.configPath = a;
29
+ else
30
+ opts.unknown.push(a);
31
+ }
32
+ return opts;
33
+ };
34
+ /**
35
+ * Build the URL `--open` should hit. Returns `null` when the admin
36
+ * panel is not active (no `admin`, or `enabled !== true`) — the
37
+ * caller surfaces that as a skip-with-log message. Hosts bound to
38
+ * `0.0.0.0` / `::` are rewritten to the matching loopback so the
39
+ * browser doesn't try to dial the wildcard.
40
+ */
41
+ export const composeAdminUrl = (handle, admin) => {
42
+ if (!admin || admin.enabled !== true)
43
+ return null;
44
+ const host = handle.host === '0.0.0.0' || handle.host === '::' || handle.host === ''
45
+ ? '127.0.0.1'
46
+ : handle.host;
47
+ const path = typeof admin.path === 'string' && admin.path !== '' ? admin.path : '/databehave';
48
+ return `http://${host}:${handle.port}${path}`;
49
+ };
50
+ /** Platform-specific browser opener. Pure — returns the spawn args. */
51
+ export const resolveOpener = (platform, url) => {
52
+ if (platform === 'darwin')
53
+ return { cmd: 'open', args: [url] };
54
+ // On Windows, `start` is a `cmd.exe` builtin. The empty string is
55
+ // the (required) window title — without it `start` treats the URL
56
+ // as the title and opens nothing.
57
+ if (platform === 'win32')
58
+ return { cmd: 'cmd', args: ['/c', 'start', '', url] };
59
+ return { cmd: 'xdg-open', args: [url] };
60
+ };
61
+ //# sourceMappingURL=bin-helpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin-helpers.js","sourceRoot":"","sources":["../src/bin-helpers.ts"],"names":[],"mappings":"AAgBA,MAAM,CAAC,MAAM,SAAS,GAAG;;;;;;;CAOxB,CAAA;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,IAAuB,EAAc,EAAE;IAClE,MAAM,IAAI,GAAe;QACvB,IAAI,EAAE,KAAK;QACX,IAAI,EAAE,KAAK;QACX,OAAO,EAAE,EAAE;KACZ,CAAA;IACD,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACrB,IAAI,CAAC,KAAK,QAAQ;YAAE,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;aAC/B,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,QAAQ;YAAE,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;aAClD,IAAI,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;aAC3C,IAAI,IAAI,CAAC,UAAU,KAAK,SAAS;YAAE,IAAI,CAAC,UAAU,GAAG,CAAC,CAAA;;YACtD,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IAC3B,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC,CAAA;AAcD;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAC7B,MAAkB,EAClB,KAA+B,EAChB,EAAE;IACjB,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,OAAO,KAAK,IAAI;QAAE,OAAO,IAAI,CAAA;IACjD,MAAM,IAAI,GACR,MAAM,CAAC,IAAI,KAAK,SAAS,IAAI,MAAM,CAAC,IAAI,KAAK,IAAI,IAAI,MAAM,CAAC,IAAI,KAAK,EAAE;QACrE,CAAC,CAAC,WAAW;QACb,CAAC,CAAC,MAAM,CAAC,IAAI,CAAA;IACjB,MAAM,IAAI,GAAG,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,aAAa,CAAA;IAC7F,OAAO,UAAU,IAAI,IAAI,MAAM,CAAC,IAAI,GAAG,IAAI,EAAE,CAAA;AAC/C,CAAC,CAAA;AAED,uEAAuE;AACvE,MAAM,CAAC,MAAM,aAAa,GAAG,CAC3B,QAAyB,EACzB,GAAW,EACsB,EAAE;IACnC,IAAI,QAAQ,KAAK,QAAQ;QAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,CAAA;IAC9D,kEAAkE;IAClE,kEAAkE;IAClE,kCAAkC;IAClC,IAAI,QAAQ,KAAK,OAAO;QAAE,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,CAAA;IAC/E,OAAO,EAAE,GAAG,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,CAAA;AACzC,CAAC,CAAA"}
package/dist/bin.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=bin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin.d.ts","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":""}
package/dist/bin.js ADDED
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * `@databehave/server` CLI — thin wrapper around the programmatic
4
+ * `run()` entry. Parses argv, delegates lifecycle to `run()`, and
5
+ * wires `SIGINT` / `SIGTERM` to the returned `RunHandle.close()`.
6
+ *
7
+ * All server / opener wiring lives in `./run.ts`, so
8
+ * `import { run } from '@databehave/server'` and `npx @databehave/server …`
9
+ * are observationally equivalent (same log lines, same error model).
10
+ *
11
+ * Flags:
12
+ *
13
+ * --open After `listening`, open the admin URL.
14
+ * -h, --help Print usage and exit 0.
15
+ *
16
+ * Exit codes: 0 on clean shutdown, 1 on bootstrap failure, 2 on bad
17
+ * usage (unknown flag or missing config path).
18
+ */
19
+ import { HELP_TEXT, parseCliArgs } from './bin-helpers.js';
20
+ import { run } from './run.js';
21
+ const main = async () => {
22
+ const opts = parseCliArgs(process.argv.slice(2));
23
+ if (opts.help) {
24
+ process.stdout.write(HELP_TEXT);
25
+ return;
26
+ }
27
+ if (opts.unknown.length > 0) {
28
+ console.error(`[@databehave/server] unknown option(s): ${opts.unknown.join(' ')}`);
29
+ process.stderr.write(HELP_TEXT);
30
+ process.exit(2);
31
+ }
32
+ if (!opts.configPath) {
33
+ process.stderr.write(HELP_TEXT);
34
+ process.exit(2);
35
+ }
36
+ const handle = await run({
37
+ config: opts.configPath,
38
+ open: opts.open,
39
+ });
40
+ const shutdown = (signal) => {
41
+ console.info(`[@databehave/server] shutting down (${signal})`);
42
+ handle
43
+ .close()
44
+ .then(() => process.exit(0))
45
+ .catch((err) => {
46
+ console.error('[@databehave/server] failed to close', err);
47
+ process.exit(1);
48
+ });
49
+ };
50
+ process.on('SIGINT', () => shutdown('SIGINT'));
51
+ process.on('SIGTERM', () => shutdown('SIGTERM'));
52
+ };
53
+ main().catch((err) => {
54
+ console.error('[@databehave/server] failed to start', err);
55
+ process.exit(1);
56
+ });
57
+ //# sourceMappingURL=bin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC1D,OAAO,EAAE,GAAG,EAAkB,MAAM,UAAU,CAAA;AAE9C,MAAM,IAAI,GAAG,KAAK,IAAmB,EAAE;IACrC,MAAM,IAAI,GAAG,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA;IAChD,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;QACd,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAA;QAC/B,OAAM;IACR,CAAC;IACD,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC5B,OAAO,CAAC,KAAK,CACX,2CAA2C,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CACpE,CAAA;QACD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAA;QAC/B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;IACD,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC;QACrB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAA;QAC/B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;IAED,MAAM,MAAM,GAAc,MAAM,GAAG,CAAC;QAClC,MAAM,EAAE,IAAI,CAAC,UAAU;QACvB,IAAI,EAAE,IAAI,CAAC,IAAI;KAChB,CAAC,CAAA;IAEF,MAAM,QAAQ,GAAG,CAAC,MAAc,EAAQ,EAAE;QACxC,OAAO,CAAC,IAAI,CAAC,uCAAuC,MAAM,GAAG,CAAC,CAAA;QAC9D,MAAM;aACH,KAAK,EAAE;aACP,IAAI,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;aAC3B,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;YACb,OAAO,CAAC,KAAK,CAAC,sCAAsC,EAAE,GAAG,CAAC,CAAA;YAC1D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QACjB,CAAC,CAAC,CAAA;IACN,CAAC,CAAA;IACD,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAA;IAC9C,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAA;AAClD,CAAC,CAAA;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,sCAAsC,EAAE,GAAG,CAAC,CAAA;IAC1D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;AACjB,CAAC,CAAC,CAAA"}
package/dist/cors.d.ts ADDED
@@ -0,0 +1,57 @@
1
+ /**
2
+ * CORS helpers — pure, framework-agnostic.
3
+ *
4
+ * Two concerns:
5
+ *
6
+ * 1. **Preflight (`OPTIONS`)** — answered up-front in the request
7
+ * pipeline so handlers never see it.
8
+ * 2. **Response decoration** — `Access-Control-*` headers are added
9
+ * to every other response in the pipeline.
10
+ *
11
+ * Defaults match the legacy mock server's Hono CORS config so the
12
+ * migration stays backwards-compatible:
13
+ * - origin: mirror request `Origin`, or `*` when absent
14
+ * - credentials: header omitted by default; set `credentials: true` in
15
+ * `Config.cors` to send `Access-Control-Allow-Credentials: true`
16
+ */
17
+ import type { CorsConfig, MockRequest, MockResponse } from './types.js';
18
+ /**
19
+ * Merge two `Vary` header values, deduplicating tokens
20
+ * case-insensitively while preserving the first occurrence's casing.
21
+ * `Vary: *` short-circuits to `*` (HTTP semantic: “varies on every
22
+ * possible header”, no further tokens add information).
23
+ *
24
+ * Exported so server-level decorators (and tests) can compose the
25
+ * CORS `Vary` onto whatever the handler already declared.
26
+ */
27
+ export declare const mergeVary: (existing: string | undefined, additional: string) => string;
28
+ /**
29
+ * Build the `Access-Control-*` header bag for a non-preflight
30
+ * response.
31
+ *
32
+ * - `Access-Control-Allow-Origin` is omitted when the resolved origin
33
+ * is an empty string (browsers reject empty header values, and an
34
+ * empty header is the de-facto “reject” signal anyway).
35
+ * - `Vary: Origin` is always added when CORS is enabled so reverse
36
+ * proxies and CDNs don't cache one origin's response and serve it
37
+ * to another. The server-level decorator merges this token with
38
+ * any handler-supplied `Vary` (see {@link mergeVary}).
39
+ */
40
+ export declare const buildCorsResponseHeaders: (cfg: CorsConfig, req: MockRequest) => Record<string, string>;
41
+ /**
42
+ * Build the preflight (`OPTIONS`) response for a given CORS config.
43
+ * The body is empty (204); headers cover origin, methods, headers,
44
+ * max-age, and `Vary: Origin`.
45
+ *
46
+ * `Access-Control-Allow-Headers` honours `cfg.allowHeaders` as a
47
+ * strict allowlist when it is set — anything off the list is dropped
48
+ * even if the browser requested it. When `cfg.allowHeaders` is unset
49
+ * the request value is echoed verbatim (legacy permissive default).
50
+ *
51
+ * The preflight response body itself does not vary on
52
+ * `Access-Control-Request-Method` / `…-Request-Headers` (we always
53
+ * derive the allow-headers value from the request), so those tokens
54
+ * are intentionally omitted to avoid fragmenting downstream caches.
55
+ */
56
+ export declare const buildPreflightResponse: (cfg: CorsConfig, req: MockRequest) => MockResponse;
57
+ //# sourceMappingURL=cors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cors.d.ts","sourceRoot":"","sources":["../src/cors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAUvE;;;;;;;;GAQG;AACH,eAAO,MAAM,SAAS,GAAI,UAAU,MAAM,GAAG,SAAS,EAAE,YAAY,MAAM,KAAG,MAa5E,CAAA;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,wBAAwB,GACnC,KAAK,UAAU,EACf,KAAK,WAAW,KACf,MAAM,CAAC,MAAM,EAAE,MAAM,CAavB,CAAA;AAmED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,sBAAsB,GACjC,KAAK,UAAU,EACf,KAAK,WAAW,KACf,YAqBF,CAAA"}