@databrainhq/mcp-server 0.3.0-rc.12 → 0.3.0-rc.14

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 (46) hide show
  1. package/.mcp.json +6 -1
  2. package/dist/apps/server.d.ts +102 -389
  3. package/dist/apps/server.js +5 -5
  4. package/dist/apps/stdio.d.ts +1 -2
  5. package/dist/apps/stdio.js +6 -6
  6. package/dist/{bundle-TDQXWIPM.js → bundle-QJNCUMNY.js} +2 -2
  7. package/dist/{chunk-Q5EYDKSA.js → chunk-2F7V6RZR.js} +511 -107
  8. package/dist/chunk-2F7V6RZR.js.map +1 -0
  9. package/dist/{chunk-347U6GXR.js → chunk-AWICHHDU.js} +153 -61
  10. package/dist/chunk-AWICHHDU.js.map +1 -0
  11. package/dist/{chunk-Z3ZHN4UW.js → chunk-CEGHG5U3.js} +59 -18
  12. package/dist/chunk-CEGHG5U3.js.map +1 -0
  13. package/dist/{chunk-ZTHGF27R.js → chunk-NIC5MAGC.js} +317 -147
  14. package/dist/chunk-NIC5MAGC.js.map +1 -0
  15. package/dist/{chunk-V3UW3B6U.js → chunk-S65KA3Z7.js} +26 -11
  16. package/dist/chunk-S65KA3Z7.js.map +1 -0
  17. package/dist/{chunk-OLOC2MTA.js → chunk-UYFFFAYV.js} +11722 -10560
  18. package/dist/chunk-UYFFFAYV.js.map +1 -0
  19. package/dist/{chunk-LACJHQYH.js → chunk-VQ5G5AZV.js} +10 -7
  20. package/dist/chunk-VQ5G5AZV.js.map +1 -0
  21. package/dist/cli.d.ts +16 -15
  22. package/dist/cli.js +134 -36
  23. package/dist/cli.js.map +1 -1
  24. package/dist/custom-chart/custom-runtime.js +6 -6
  25. package/dist/custom-chart/template/chart.ts +116 -28
  26. package/dist/custom-chart/template/icon.svg +1 -1
  27. package/dist/custom-chart/template/manifest.json +66 -12
  28. package/dist/custom-chart/template/sample-data.json +21 -3
  29. package/dist/index.d.ts +1 -2
  30. package/dist/index.js +20 -10
  31. package/dist/index.js.map +1 -1
  32. package/dist/{main-IUV24DMP.js → main-QCBVJSSR.js} +363 -155
  33. package/dist/main-QCBVJSSR.js.map +1 -0
  34. package/dist/{test-only-K7QTSVO4.js → test-only-6474NMTR.js} +12 -8
  35. package/dist/test-only-6474NMTR.js.map +1 -0
  36. package/package.json +20 -9
  37. package/dist/chunk-347U6GXR.js.map +0 -1
  38. package/dist/chunk-LACJHQYH.js.map +0 -1
  39. package/dist/chunk-OLOC2MTA.js.map +0 -1
  40. package/dist/chunk-Q5EYDKSA.js.map +0 -1
  41. package/dist/chunk-V3UW3B6U.js.map +0 -1
  42. package/dist/chunk-Z3ZHN4UW.js.map +0 -1
  43. package/dist/chunk-ZTHGF27R.js.map +0 -1
  44. package/dist/main-IUV24DMP.js.map +0 -1
  45. package/dist/test-only-K7QTSVO4.js.map +0 -1
  46. /package/dist/{bundle-TDQXWIPM.js.map → bundle-QJNCUMNY.js.map} +0 -0
@@ -1,4 +1,4 @@
1
- "use strict";(()=>{var ie=Object.defineProperty;var ae=(e,t,n)=>t in e?ie(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n;var V=(e,t,n)=>ae(e,typeof t!="symbol"?t+"":t,n);var f=e=>e!==null&&typeof e=="object"&&Object.getPrototypeOf(e)===Object.prototype,O=(e,t)=>Object.keys(e).length===t.length&&t.every(n=>Object.hasOwn(e,n)),S=e=>typeof e=="string"&&/^[a-z][a-z0-9_-]{0,63}$/.test(e)&&!["constructor","prototype","__proto__"].includes(e),T=(e,t=256)=>typeof e=="string"&&e.length>0&&e.length<=t,_=e=>e===null||typeof e=="boolean"||typeof e=="number"&&Number.isFinite(e)||typeof e=="string"&&e.length<=4096,A=e=>new TextEncoder().encode(JSON.stringify(e)).byteLength;var de=32*1024,X=16,K=32,ce=/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$/,q=/^#[0-9a-fA-F]{6}$/,le=/^[a-z]{2}(-[A-Z]{2})?$/,fe=["string","number","boolean"],M=["schemaVersion","id","name","slots","options"],me=[...M,"version","description","translations"],L=["id","label","role","types","required"],ue=[...L,"description"],ge=["id","label","type","default"],Y={boolean:[],number:["min","max"],string:["maxLength"],select:["choices"],color:[]},pe={boolean:["group"],number:["group","step"],string:["group"],select:["group"],color:["group"]},j=e=>typeof e=="number"&&Number.isFinite(e),I=e=>typeof e=="string"&&e.length<=256,D=class{constructor(){V(this,"list",[])}add(t,n,o){this.list.push({code:t,path:n,message:o})}},C=(e,t,n,o,s,i,d=r=>`${o}.${r}`)=>{let r=new Set([...s,...i]),c=!0;for(let a of Object.keys(t))r.has(a)||(e.add(`${o}.unknownKey`,`${n}/${a}`,`Remove "${a}". It is not a known key here.`),c=!1);for(let a of s)Object.hasOwn(t,a)||(e.add(d(a),`${n}/${a}`,`Add the required key "${a}".`),c=!1);return c},be=(e,t,n,o)=>{if(!f(t)){e.add("manifest.slot.invalid",n,"A slot must be an object.");return}if(!C(e,t,n,"manifest.slot",L,o?ue.slice(L.length):[]))return;S(t.id)||e.add("manifest.slot.id",`${n}/id`,'Use a slot id that starts with a letter and has only lowercase letters, digits, "_" or "-" (max 64).'),T(t.label)||e.add("manifest.slot.label",`${n}/label`,"Give the slot a label of 1 to 256 characters.");let s=t.role;s!=="dimension"&&s!=="measure"&&e.add("manifest.slot.role",`${n}/role`,'Set role to "dimension" or "measure".'),typeof t.required!="boolean"&&e.add("manifest.slot.required",`${n}/required`,"Set required to true or false.");let i=t.types;Array.isArray(i)&&i.length>0&&i.length<=3&&new Set(i).size===i.length&&i.every(r=>fe.includes(r))?s==="measure"&&(i.length!==1||i[0]!=="number")&&e.add("manifest.slot.types",`${n}/types`,'A measure slot accepts only ["number"].'):e.add("manifest.slot.types",`${n}/types`,'List 1 to 3 unique types from "string", "number" and "boolean".'),Object.hasOwn(t,"description")&&!T(t.description,256)&&e.add("manifest.slot.description",`${n}/description`,"A slot description is 1 to 256 characters.")},ye=(e,t,n,o)=>{if(!f(t)){e.add("manifest.option.invalid",n,"An option must be an object.");return}let s=t.id;if(typeof s=="string"&&(s==="theme"||s.startsWith("theme."))){e.add("manifest.option.reserved",`${n}/id`,'The option id "theme" is reserved for the host theme. Choose another id.');return}let i=typeof t.type=="string"?t.type:"",d=Object.hasOwn(Y,i)?Y[i]:void 0;if(!d||i==="color"&&!o){e.add("manifest.option.type",`${n}/type`,o?'Set type to "boolean", "number", "string", "select" or "color".':'Set type to "boolean", "number", "string" or "select". "color" needs schemaVersion 2.');return}if(C(e,t,n,"manifest.option",[...ge,...d],o?pe[i]:[]))switch(S(s)||e.add("manifest.option.id",`${n}/id`,'Use an option id that starts with a letter and has only lowercase letters, digits, "_" or "-" (max 64).'),T(t.label)||e.add("manifest.option.label",`${n}/label`,"Give the option a label of 1 to 256 characters."),Object.hasOwn(t,"group")&&!T(t.group,64)&&e.add("manifest.option.group",`${n}/group`,"A group label is 1 to 64 characters."),i){case"boolean":typeof t.default!="boolean"&&e.add("manifest.option.default",`${n}/default`,"A boolean option needs a true or false default.");break;case"number":{if(!(j(t.min)&&j(t.max)&&t.min<=t.max)){e.add("manifest.option.range",`${n}/min`,"Set finite min and max with min <= max.");break}let c=t.min,a=t.max;(!j(t.default)||t.default<c||t.default>a)&&e.add("manifest.option.default",`${n}/default`,"The default must be a finite number between min and max."),Object.hasOwn(t,"step")&&(!j(t.step)||t.step<=0||t.step>a-c)&&e.add("manifest.option.step",`${n}/step`,"A step must be a positive number no larger than max - min.");break}case"string":{let r=t.maxLength;if(!j(r)||!Number.isInteger(r)||r<1||r>1024){e.add("manifest.option.maxLength",`${n}/maxLength`,"Set maxLength to an integer from 1 to 1024.");break}(typeof t.default!="string"||t.default.length>r)&&e.add("manifest.option.default",`${n}/default`,"The default must be a string no longer than maxLength.");break}case"select":{let r=t.choices;if(!(Array.isArray(r)&&r.length>0&&r.length<=32&&r.every(a=>f(a)&&Object.keys(a).length===2&&S(a.value)&&T(a.label))&&new Set(r.map(a=>a.value)).size===r.length)){e.add("manifest.option.choices",`${n}/choices`,"List 1 to 32 choices, each { value, label } with unique ids as values.");break}r.some(a=>a.value===t.default)||e.add("manifest.option.default",`${n}/default`,"The default must be the value of one of the choices.");break}case"color":(typeof t.default!="string"||!q.test(t.default))&&e.add("manifest.option.color",`${n}/default`,'A color default is a six-digit hex string such as "#3366cc".');break}},he=(e,t,n,o)=>{let s="/translations";if(!f(t)){e.add("manifest.translations.invalid",s,"translations must be an object keyed by language code.");return}for(let[i,d]of Object.entries(t)){let r=`${s}/${i}`;if(!le.test(i)){e.add("manifest.translations.language",r,'Use a language code such as "fr" or "pt-BR".');continue}if(!f(d)){e.add("manifest.translations.invalid",r,"A translation must be an object.");continue}if(C(e,d,r,"manifest.translations",[],["name","description","slots","options"])){for(let c of["name","description"])Object.hasOwn(d,c)&&!I(d[c])&&e.add("manifest.translations.text",`${r}/${c}`,"A translated string is at most 256 characters.");if(Object.hasOwn(d,"slots")){let c=d.slots;if(!f(c))e.add("manifest.translations.invalid",`${r}/slots`,"slots must be an object keyed by slot id.");else for(let[a,l]of Object.entries(c)){let g=`${r}/slots/${a}`;if(!n.some(m=>m.id===a)){e.add("manifest.translations.slot",g,`There is no slot "${a}" to translate.`);continue}if(!f(l)||!C(e,l,g,"manifest.translations",[],["label","description"])){f(l)||e.add("manifest.translations.invalid",g,"A slot translation must be an object.");continue}for(let m of["label","description"])Object.hasOwn(l,m)&&!I(l[m])&&e.add("manifest.translations.text",`${g}/${m}`,"A translated string is at most 256 characters.")}}if(Object.hasOwn(d,"options")){let c=d.options;if(!f(c))e.add("manifest.translations.invalid",`${r}/options`,"options must be an object keyed by option id.");else for(let[a,l]of Object.entries(c)){let g=`${r}/options/${a}`,m=o.find(p=>p.id===a);if(!m){e.add("manifest.translations.option",g,`There is no option "${a}" to translate.`);continue}if(!f(l)||!C(e,l,g,"manifest.translations",[],["label","group","choices"])){f(l)||e.add("manifest.translations.invalid",g,"An option translation must be an object.");continue}for(let p of["label","group"])Object.hasOwn(l,p)&&!I(l[p])&&e.add("manifest.translations.text",`${g}/${p}`,"A translated string is at most 256 characters.");if(Object.hasOwn(l,"choices")){let p=l.choices;if(!f(p)){e.add("manifest.translations.invalid",`${g}/choices`,"choices must be an object keyed by choice value.");continue}for(let[x,u]of Object.entries(p)){let y=`${g}/choices/${x}`;m.type!=="select"||!m.choices.some(b=>b.value===x)?e.add("manifest.translations.choice",y,`There is no choice "${x}" on option "${a}".`):I(u)||e.add("manifest.translations.text",y,"A translated string is at most 256 characters.")}}}}}}},xe=e=>{let t=new D;if(!f(e))return t.add("manifest.invalid","","The manifest must be a JSON object."),t.list;let n=e.schemaVersion;if(n!==1&&n!==2)return t.add("manifest.schemaVersion.unsupported","/schemaVersion","Set schemaVersion to 1 or 2."),t.list;let o=n===2;if(!C(t,e,"","manifest",M,o?me.slice(M.length):[],d=>`manifest.${d}.required`))return t.list;S(e.id)||t.add("manifest.id.invalid","/id",'Use an id that starts with a letter and has only lowercase letters, digits, "_" or "-" (max 64). The chart type becomes "custom:<id>".'),T(e.name)||t.add("manifest.name.invalid","/name","Give the chart a name of 1 to 256 characters."),Object.hasOwn(e,"version")&&(typeof e.version!="string"||!ce.test(e.version))&&t.add("manifest.version.invalid","/version",'version must be a semver string such as "1.2.0".'),Object.hasOwn(e,"description")&&!T(e.description,500)&&t.add("manifest.description.invalid","/description","description is 1 to 500 characters.");let s=e.slots;if(!Array.isArray(s))t.add("manifest.slots.invalid","/slots","slots must be an array.");else if(s.length===0)t.add("manifest.slots.empty","/slots","Declare at least one slot.");else if(s.length>X)t.add("manifest.slots.tooMany","/slots",`Declare at most ${X} slots.`);else{for(let[r,c]of s.entries())be(t,c,`/slots/${r}`,o);let d=new Set;s.forEach((r,c)=>{!f(r)||typeof r.id!="string"||(d.has(r.id)&&t.add("manifest.slot.duplicate",`/slots/${c}/id`,`Slot id "${r.id}" is used more than once.`),d.add(r.id))})}let i=e.options;if(!Array.isArray(i))t.add("manifest.options.invalid","/options","options must be an array (it may be empty).");else if(i.length>K)t.add("manifest.options.tooMany","/options",`Declare at most ${K} options.`);else{for(let[r,c]of i.entries())ye(t,c,`/options/${r}`,o);let d=new Set;i.forEach((r,c)=>{!f(r)||typeof r.id!="string"||(d.has(r.id)&&t.add("manifest.option.duplicate",`/options/${c}/id`,`Option id "${r.id}" is used more than once.`),d.add(r.id))})}return t.list.length===0&&Object.hasOwn(e,"translations")&&he(t,e.translations,s,i),t.list.length===0&&A(e)>de&&t.add("manifest.size","","The manifest must be 32 KiB or smaller as JSON."),t.list},N=e=>xe(e).length===0?e:null;var R=(e,t)=>{if(!N(e)||!f(t)||Object.keys(t).some(o=>!e.options.some(s=>s.id===o)))return null;let n={};for(let o of e.options){let s=Object.hasOwn(t,o.id)?t[o.id]:o.default,i=!1;switch(o.type){case"boolean":i=typeof s=="boolean";break;case"number":i=typeof s=="number"&&Number.isFinite(s)&&s>=o.min&&s<=o.max;break;case"string":i=typeof s=="string"&&s.length<=o.maxLength;break;case"select":i=o.choices.some(d=>d.value===s);break;case"color":i=typeof s=="string"&&q.test(s);break}if(!i)return null;n[o.id]=s}return n};var J=2*1024*1024,G=256*1024,B="allow-scripts",P="camera 'none'; microphone 'none'; geolocation 'none'; payment 'none'; usb 'none'; clipboard-read 'none'; clipboard-write 'none'; display-capture 'none'; fullscreen 'none'",Se=e=>{if(!N(e.manifest))throw new Error("Invalid chart manifest. Check schema version 1.");if(typeof e.javascript!="string"||e.javascript.trim().length===0||e.javascript.length>J||new TextEncoder().encode(e.javascript).byteLength>J)throw new Error("Chart bundle must contain JavaScript and be no larger than 2 MiB.");if(e.css!==void 0&&(typeof e.css!="string"||e.css.length>G||new TextEncoder().encode(e.css).byteLength>G))throw new Error("Chart stylesheet must be text no larger than 256 KiB.")},we=e=>`<style>${e.replace(/<\/(style)/gi,"<\\/$1")}</style>`,Oe=e=>["default-src 'none'","script-src 'unsafe-inline' data:","script-src-attr 'none'","style-src 'unsafe-inline'","connect-src 'none'","img-src data:","font-src 'none'","media-src 'none'","object-src 'none'","worker-src 'none'","manifest-src 'none'","base-uri 'none'","form-action 'none'",`frame-src ${e?"blob:":"'none'"}`].join("; "),Te=e=>{let t=new TextEncoder().encode(e),n="";for(let o of t)n+=String.fromCharCode(o);return btoa(n)},ke=e=>e.replace(/<\/(script)/gi,"<\\/$1").replace(/<!--/g,"<\\!--"),H=(e,t)=>`<!doctype html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="Content-Security-Policy" content="${Oe(t)}"><meta name="referrer" content="no-referrer"><meta http-equiv="x-dns-prefetch-control" content="off"><title>Custom chart</title><style>html,body{margin:0;width:100%;height:100%;overflow:hidden}body{font:14px system-ui;color:CanvasText;background:Canvas}iframe{border:0;width:100%;height:100%}</style></head><body>${e}</body></html>`,Ae=e=>`(() => {
1
+ "use strict";(()=>{var ie=Object.defineProperty;var ae=(e,t,n)=>t in e?ie(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n;var V=(e,t,n)=>ae(e,typeof t!="symbol"?t+"":t,n);var f=e=>e!==null&&typeof e=="object"&&Object.getPrototypeOf(e)===Object.prototype,O=(e,t)=>Object.keys(e).length===t.length&&t.every(n=>Object.hasOwn(e,n)),S=e=>typeof e=="string"&&/^[a-z][a-z0-9_-]{0,63}$/.test(e)&&!["constructor","prototype","__proto__"].includes(e),T=(e,t=256)=>typeof e=="string"&&e.length>0&&e.length<=t,_=e=>e===null||typeof e=="boolean"||typeof e=="number"&&Number.isFinite(e)||typeof e=="string"&&e.length<=4096,A=e=>new TextEncoder().encode(JSON.stringify(e)).byteLength;var de=32*1024,X=16,K=32,ce=/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$/,q=/^#[0-9a-fA-F]{6}$/,le=/^[a-z]{2}(-[A-Z]{2})?$/,fe=["string","number","boolean"],M=["schemaVersion","id","name","slots","options"],me=[...M,"version","description","translations"],L=["id","label","role","types","required"],ue=[...L,"description"],ge=["id","label","type","default"],Y={boolean:[],number:["min","max"],string:["maxLength"],select:["choices"],color:[]},pe={boolean:["group"],number:["group","step"],string:["group"],select:["group"],color:["group"]},j=e=>typeof e=="number"&&Number.isFinite(e),I=e=>typeof e=="string"&&e.length<=256,D=class{constructor(){V(this,"list",[])}add(t,n,o){this.list.push({code:t,path:n,message:o})}},C=(e,t,n,o,s,i,d=r=>`${o}.${r}`)=>{let r=new Set([...s,...i]),c=!0;for(let a of Object.keys(t))r.has(a)||(e.add(`${o}.unknownKey`,`${n}/${a}`,`Remove "${a}". It is not a known key here.`),c=!1);for(let a of s)Object.hasOwn(t,a)||(e.add(d(a),`${n}/${a}`,`Add the required key "${a}".`),c=!1);return c},be=(e,t,n,o)=>{if(!f(t)){e.add("manifest.slot.invalid",n,"A slot must be an object.");return}if(!C(e,t,n,"manifest.slot",L,o?ue.slice(L.length):[]))return;S(t.id)||e.add("manifest.slot.id",`${n}/id`,'Use a slot id that starts with a letter and has only lowercase letters, digits, "_" or "-" (max 64).'),T(t.label)||e.add("manifest.slot.label",`${n}/label`,"Give the slot a label of 1 to 256 characters.");let s=t.role;s!=="dimension"&&s!=="measure"&&e.add("manifest.slot.role",`${n}/role`,'Set role to "dimension" or "measure".'),typeof t.required!="boolean"&&e.add("manifest.slot.required",`${n}/required`,"Set required to true or false.");let i=t.types;Array.isArray(i)&&i.length>0&&i.length<=3&&new Set(i).size===i.length&&i.every(r=>fe.includes(r))?s==="measure"&&(i.length!==1||i[0]!=="number")&&e.add("manifest.slot.types",`${n}/types`,'A measure slot accepts only ["number"].'):e.add("manifest.slot.types",`${n}/types`,'List 1 to 3 unique types from "string", "number" and "boolean".'),Object.hasOwn(t,"description")&&!T(t.description,256)&&e.add("manifest.slot.description",`${n}/description`,"A slot description is 1 to 256 characters.")},ye=(e,t,n,o)=>{if(!f(t)){e.add("manifest.option.invalid",n,"An option must be an object.");return}let s=t.id;if(typeof s=="string"&&(s==="theme"||s.startsWith("theme."))){e.add("manifest.option.reserved",`${n}/id`,'The option id "theme" is reserved for the host theme. Choose another id.');return}let i=typeof t.type=="string"?t.type:"",d=Object.hasOwn(Y,i)?Y[i]:void 0;if(!d||i==="color"&&!o){e.add("manifest.option.type",`${n}/type`,o?'Set type to "boolean", "number", "string", "select" or "color".':'Set type to "boolean", "number", "string" or "select". "color" needs schemaVersion 2.');return}if(C(e,t,n,"manifest.option",[...ge,...d],o?pe[i]:[]))switch(S(s)||e.add("manifest.option.id",`${n}/id`,'Use an option id that starts with a letter and has only lowercase letters, digits, "_" or "-" (max 64).'),T(t.label)||e.add("manifest.option.label",`${n}/label`,"Give the option a label of 1 to 256 characters."),Object.hasOwn(t,"group")&&!T(t.group,64)&&e.add("manifest.option.group",`${n}/group`,"A group label is 1 to 64 characters."),i){case"boolean":typeof t.default!="boolean"&&e.add("manifest.option.default",`${n}/default`,"A boolean option needs a true or false default.");break;case"number":{if(!(j(t.min)&&j(t.max)&&t.min<=t.max)){e.add("manifest.option.range",`${n}/min`,"Set finite min and max with min <= max.");break}let c=t.min,a=t.max;(!j(t.default)||t.default<c||t.default>a)&&e.add("manifest.option.default",`${n}/default`,"The default must be a finite number between min and max."),Object.hasOwn(t,"step")&&(!j(t.step)||t.step<=0||t.step>a-c)&&e.add("manifest.option.step",`${n}/step`,"A step must be a positive number no larger than max - min.");break}case"string":{let r=t.maxLength;if(!j(r)||!Number.isInteger(r)||r<1||r>1024){e.add("manifest.option.maxLength",`${n}/maxLength`,"Set maxLength to an integer from 1 to 1024.");break}(typeof t.default!="string"||t.default.length>r)&&e.add("manifest.option.default",`${n}/default`,"The default must be a string no longer than maxLength.");break}case"select":{let r=t.choices;if(!(Array.isArray(r)&&r.length>0&&r.length<=32&&r.every(a=>f(a)&&Object.keys(a).length===2&&S(a.value)&&T(a.label))&&new Set(r.map(a=>a.value)).size===r.length)){e.add("manifest.option.choices",`${n}/choices`,"List 1 to 32 choices, each { value, label } with unique ids as values.");break}r.some(a=>a.value===t.default)||e.add("manifest.option.default",`${n}/default`,"The default must be the value of one of the choices.");break}case"color":(typeof t.default!="string"||!q.test(t.default))&&e.add("manifest.option.color",`${n}/default`,'A color default is a six-digit hex string such as "#3366cc".');break}},he=(e,t,n,o)=>{let s="/translations";if(!f(t)){e.add("manifest.translations.invalid",s,"translations must be an object keyed by language code.");return}for(let[i,d]of Object.entries(t)){let r=`${s}/${i}`;if(!le.test(i)){e.add("manifest.translations.language",r,'Use a language code such as "fr" or "pt-BR".');continue}if(!f(d)){e.add("manifest.translations.invalid",r,"A translation must be an object.");continue}if(C(e,d,r,"manifest.translations",[],["name","description","slots","options"])){for(let c of["name","description"])Object.hasOwn(d,c)&&!I(d[c])&&e.add("manifest.translations.text",`${r}/${c}`,"A translated string is at most 256 characters.");if(Object.hasOwn(d,"slots")){let c=d.slots;if(!f(c))e.add("manifest.translations.invalid",`${r}/slots`,"slots must be an object keyed by slot id.");else for(let[a,l]of Object.entries(c)){let g=`${r}/slots/${a}`;if(!n.some(m=>m.id===a)){e.add("manifest.translations.slot",g,`There is no slot "${a}" to translate.`);continue}if(!f(l)||!C(e,l,g,"manifest.translations",[],["label","description"])){f(l)||e.add("manifest.translations.invalid",g,"A slot translation must be an object.");continue}for(let m of["label","description"])Object.hasOwn(l,m)&&!I(l[m])&&e.add("manifest.translations.text",`${g}/${m}`,"A translated string is at most 256 characters.")}}if(Object.hasOwn(d,"options")){let c=d.options;if(!f(c))e.add("manifest.translations.invalid",`${r}/options`,"options must be an object keyed by option id.");else for(let[a,l]of Object.entries(c)){let g=`${r}/options/${a}`,m=o.find(p=>p.id===a);if(!m){e.add("manifest.translations.option",g,`There is no option "${a}" to translate.`);continue}if(!f(l)||!C(e,l,g,"manifest.translations",[],["label","group","choices"])){f(l)||e.add("manifest.translations.invalid",g,"An option translation must be an object.");continue}for(let p of["label","group"])Object.hasOwn(l,p)&&!I(l[p])&&e.add("manifest.translations.text",`${g}/${p}`,"A translated string is at most 256 characters.");if(Object.hasOwn(l,"choices")){let p=l.choices;if(!f(p)){e.add("manifest.translations.invalid",`${g}/choices`,"choices must be an object keyed by choice value.");continue}for(let[x,u]of Object.entries(p)){let y=`${g}/choices/${x}`;m.type!=="select"||!m.choices.some(b=>b.value===x)?e.add("manifest.translations.choice",y,`There is no choice "${x}" on option "${a}".`):I(u)||e.add("manifest.translations.text",y,"A translated string is at most 256 characters.")}}}}}}},xe=e=>{let t=new D;if(!f(e))return t.add("manifest.invalid","","The manifest must be a JSON object."),t.list;let n=e.schemaVersion;if(n!==1&&n!==2)return t.add("manifest.schemaVersion.unsupported","/schemaVersion","Set schemaVersion to 1 or 2."),t.list;let o=n===2;if(!C(t,e,"","manifest",M,o?me.slice(M.length):[],d=>`manifest.${d}.required`))return t.list;S(e.id)||t.add("manifest.id.invalid","/id",'Use an id that starts with a letter and has only lowercase letters, digits, "_" or "-" (max 64). The chart type becomes "custom:<id>".'),T(e.name)||t.add("manifest.name.invalid","/name","Give the chart a name of 1 to 256 characters."),Object.hasOwn(e,"version")&&(typeof e.version!="string"||!ce.test(e.version))&&t.add("manifest.version.invalid","/version",'version must be a semver string such as "1.2.0".'),Object.hasOwn(e,"description")&&!T(e.description,500)&&t.add("manifest.description.invalid","/description","description is 1 to 500 characters.");let s=e.slots;if(!Array.isArray(s))t.add("manifest.slots.invalid","/slots","slots must be an array.");else if(s.length===0)t.add("manifest.slots.empty","/slots","Declare at least one slot.");else if(s.length>X)t.add("manifest.slots.tooMany","/slots",`Declare at most ${X} slots.`);else{for(let[r,c]of s.entries())be(t,c,`/slots/${r}`,o);let d=new Set;s.forEach((r,c)=>{!f(r)||typeof r.id!="string"||(d.has(r.id)&&t.add("manifest.slot.duplicate",`/slots/${c}/id`,`Slot id "${r.id}" is used more than once.`),d.add(r.id))})}let i=e.options;if(!Array.isArray(i))t.add("manifest.options.invalid","/options","options must be an array (it may be empty).");else if(i.length>K)t.add("manifest.options.tooMany","/options",`Declare at most ${K} options.`);else{for(let[r,c]of i.entries())ye(t,c,`/options/${r}`,o);let d=new Set;i.forEach((r,c)=>{!f(r)||typeof r.id!="string"||(d.has(r.id)&&t.add("manifest.option.duplicate",`/options/${c}/id`,`Option id "${r.id}" is used more than once.`),d.add(r.id))})}return t.list.length===0&&Object.hasOwn(e,"translations")&&he(t,e.translations,s,i),t.list.length===0&&A(e)>de&&t.add("manifest.size","","The manifest must be 32 KiB or smaller as JSON."),t.list},N=e=>xe(e).length===0?e:null;var R=(e,t)=>{if(!N(e)||!f(t)||Object.keys(t).some(o=>!e.options.some(s=>s.id===o)))return null;let n={};for(let o of e.options){let s=Object.hasOwn(t,o.id)?t[o.id]:o.default,i=!1;switch(o.type){case"boolean":i=typeof s=="boolean";break;case"number":i=typeof s=="number"&&Number.isFinite(s)&&s>=o.min&&s<=o.max;break;case"string":i=typeof s=="string"&&s.length<=o.maxLength;break;case"select":i=o.choices.some(d=>d.value===s);break;case"color":i=typeof s=="string"&&q.test(s);break}if(!i)return null;n[o.id]=s}return n};var H="dbn-chart-sandbox",Se=1,B=1024*1024,P=5e3,we=32,J=["fontFamily","textColor","textSecondaryColor","background","surfaceColor","borderColor","primaryColor"],G=["fontSize","borderRadius"],Oe=/^[a-z]{2}(-[A-Z]{2})?$/,Te=e=>!f(e)||!O(e,[...J,...G,"palette"])?!1:J.every(t=>typeof e[t]=="string"&&e[t].length<=256)&&G.every(t=>typeof e[t]=="number"&&Number.isFinite(e[t]))&&Array.isArray(e.palette)&&e.palette.length<=we&&e.palette.every(t=>typeof t=="string"&&t.length<=256),ke=["theme","language"],Ae=e=>{let t=Object.keys(e),n=ke.filter(o=>Object.hasOwn(e,o)).length;return t.length===3+n&&["columns","rows","options"].every(o=>Object.hasOwn(e,o))},v=e=>{if(!f(e)||!Ae(e)||Object.hasOwn(e,"theme")&&!Te(e.theme)||Object.hasOwn(e,"language")&&!(typeof e.language=="string"&&Oe.test(e.language))||!Array.isArray(e.columns)||e.columns.length<1||e.columns.length>64||!Array.isArray(e.rows)||e.rows.length>P||!f(e.options)||Object.keys(e.options).length>32)return!1;let t=e.columns;return!t.every(n=>f(n)&&(O(n,["id","label","type","filterable"])||O(n,["id","label","type","filterable","slotId"])&&S(n.slotId))&&S(n.id)&&T(n.label)&&["string","number","boolean"].includes(n.type)&&typeof n.filterable=="boolean")||new Set(t.map(({id:n})=>n)).size!==t.length||!e.rows.every(n=>Array.isArray(n)&&n.length===t.length&&n.every((o,s)=>_(o)&&(o===null||typeof o===t[s].type)))||!Object.entries(e.options).every(([n,o])=>S(n)&&o!==null&&_(o))?!1:A(e)<=B},W=(e,t)=>{if(!t.source||e.source!==t.source||e.origin!=="null")return null;let n=e.data;if(!f(n)||n.protocol!==H||n.version!==1||n.channel!==t.channel)return null;let o=["protocol","version","channel","type"];if(n.type==="ready")return O(n,o)?n:null;if(n.type==="error")return O(n,[...o,"code"])&&(n.code==="navigation"||n.code==="runtime")?n:null;if(n.type==="resize")return O(n,[...o,"height"])&&typeof n.height=="number"&&Number.isInteger(n.height)&&n.height>=80&&n.height<=1200?n:null;if(n.revision!==t.revision)return null;let s=t.data.columns.findIndex(({id:i})=>i===n.columnId);return s<0?null:n.type==="click"?O(n,[...o,"revision","rowIndex","columnId"])&&typeof n.rowIndex=="number"&&Number.isInteger(n.rowIndex)&&n.rowIndex>=0&&n.rowIndex<t.data.rows.length?n:null:n.type==="filter"&&O(n,[...o,"revision","columnId","values"])&&t.data.columns[s].filterable&&Array.isArray(n.values)&&n.values.length<=100&&n.values.every(i=>_(i)&&t.data.rows.some(d=>d[s]===i))&&A(n)<=32*1024?n:null},Z=(e,t,n)=>({protocol:H,version:Se,channel:e,type:"data",revision:t,data:n});var Q=4096,$e=(e,t)=>{let n=e.toLowerCase().replace(/[^a-z0-9_-]+/g,"_").replace(/^[^a-z]+|_+$/g,"").slice(0,48);return n?`${n}_${t}`:`col_${t}`},ee=e=>{if(typeof e=="number")return Number.isFinite(e)?e:null;if(typeof e=="boolean")return e?1:0;if(typeof e=="string"&&e.trim()!==""){let t=Number(e);return Number.isFinite(t)?t:null}return null},Ee=e=>{if(e==null)return null;if(e instanceof Date)return Number.isNaN(e.getTime())?null:e.toISOString();let t=typeof e=="string"?e:String(e);return t.length>Q?t.slice(0,Q):t},Ce=e=>typeof e=="boolean"?e:e==="true"?!0:e==="false"?!1:e===1||e===0?e===1:null,je=(e,t)=>{if(e.types.length===1)return e.types[0];let n=t.filter(o=>o!=null);return e.types.includes("boolean")&&n.length>0&&n.every(o=>typeof o=="boolean")?"boolean":e.types.includes("number")&&n.length>0&&n.every(o=>ee(o)!==null)?"number":e.types.includes("string")?"string":e.types[0]},_e=(e,t)=>{switch(e){case"number":return ee(t);case"boolean":return Ce(t);default:return Ee(t)}},Ie=(e,t)=>{let n=R(e,t);if(n)return n;let o={};for(let[s,i]of Object.entries(t))R(e,{[s]:i})&&(o[s]=i);return R(e,o)??{}},te=({manifest:e,config:t,rows:n,columns:o,theme:s,language:i})=>{let d=new Map;if(o)for(let u of o)d.set(u.key,u.label);if(d.size===0&&n[0])for(let u of Object.keys(n[0]))d.set(u,u);let r=new Set,c={},a=[],l=[];for(let u of e.slots){let y=(t.bindings[u.id]??[]).filter(b=>d.has(b));y.length===0&&u.required&&l.push(u);for(let b of y){let h=$e(b,a.length);for(;r.has(h)||!S(h);)h=`col_${a.length}_${r.size}`;r.add(h),c[h]=b;let $=n.slice(0,200).map(E=>E[b]);a.push({id:h,label:(d.get(b)||b).slice(0,256)||h,type:je(u,$),filterable:u.role==="dimension",slotId:u.id,source:b})}}let g=n.length,m=Math.min(g,P),p=u=>({columns:a.map(({id:y,label:b,type:h,filterable:$,slotId:E})=>({id:y,label:b,type:h,filterable:$,slotId:E})),rows:n.slice(0,u).map(y=>a.map(b=>_e(b.type,y[b.source]))),options:Ie(e,t.options),...s?{theme:s}:{},...i?{language:i}:{}}),x=p(m);for(;m>0&&A(x)>B;)m=Math.floor(m/2),x=p(m);return a.length===0?{data:x,truncated:!1,totalRows:g,sources:c,missingSlots:l}:(v(x)||(x={...p(0)},m=0),{data:x,truncated:m<g,totalRows:g,sources:c,missingSlots:l})};var ne=2*1024*1024,oe=256*1024,z="allow-scripts",F="camera 'none'; microphone 'none'; geolocation 'none'; payment 'none'; usb 'none'; clipboard-read 'none'; clipboard-write 'none'; display-capture 'none'; fullscreen 'none'",Re=e=>{if(!N(e.manifest))throw new Error("Invalid chart manifest. Check schema version 1.");if(typeof e.javascript!="string"||e.javascript.trim().length===0||e.javascript.length>ne||new TextEncoder().encode(e.javascript).byteLength>ne)throw new Error("Chart bundle must contain JavaScript and be no larger than 2 MiB.");if(e.css!==void 0&&(typeof e.css!="string"||e.css.length>oe||new TextEncoder().encode(e.css).byteLength>oe))throw new Error("Chart stylesheet must be text no larger than 256 KiB.")},ve=e=>`<style>${e.replace(/<\/(style)/gi,"<\\/$1")}</style>`,Me=e=>["default-src 'none'","script-src 'unsafe-inline' data:","script-src-attr 'none'","style-src 'unsafe-inline'","connect-src 'none'","img-src data:","font-src 'none'","media-src 'none'","object-src 'none'","worker-src 'none'","manifest-src 'none'","base-uri 'none'","form-action 'none'",`frame-src ${e?"blob:":"'none'"}`].join("; "),Le=e=>{let t=new TextEncoder().encode(e),n="";for(let o of t)n+=String.fromCharCode(o);return btoa(n)},De=e=>e.replace(/<\/(script)/gi,"<\\/$1").replace(/<!--/g,"<\\!--"),se=(e,t)=>`<!doctype html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="Content-Security-Policy" content="${Me(t)}"><meta name="referrer" content="no-referrer"><meta http-equiv="x-dns-prefetch-control" content="off"><title>Custom chart</title><style>html,body{margin:0;width:100%;height:100%;overflow:hidden}body{font:14px system-ui;color:CanvasText;background:Canvas}iframe{border:0;width:100%;height:100%}</style></head><body>${e}</body></html>`,Ne=e=>`(() => {
2
2
  'use strict';
3
3
  const channel = ${JSON.stringify(e)};
4
4
  const send = (payload) => parent.postMessage({protocol:'dbn-chart-sandbox',version:1,channel,...payload}, '*');
@@ -21,15 +21,15 @@
21
21
  revision = m.revision; latest = m.data;
22
22
  if (listener) listener(latest);
23
23
  });
24
- })();`,W=(e,t,n)=>{if(Se(e),!/^[a-zA-Z0-9-]{16,80}$/.test(t))throw new Error("Invalid sandbox channel.");let o=n==="null"?null:new URL(n);if(o&&(!["http:","https:"].includes(o.protocol)||o.origin!==n))throw new Error("Sandbox host must have an HTTP or opaque origin.");let s=H(`${e.css?we(e.css):""}<div id="chart" role="img" aria-label="Custom chart"></div><script>${Ae(t)}<\/script><script>${ke(e.javascript)}<\/script>`,!1),i=`(() => {
24
+ })();`,re=(e,t,n)=>{if(Re(e),!/^[a-zA-Z0-9-]{16,80}$/.test(t))throw new Error("Invalid sandbox channel.");let o=n==="null"?null:new URL(n);if(o&&(!["http:","https:"].includes(o.protocol)||o.origin!==n))throw new Error("Sandbox host must have an HTTP or opaque origin.");let s=se(`${e.css?ve(e.css):""}<div id="chart" role="img" aria-label="Custom chart"></div><script>${Ne(t)}<\/script><script>${De(e.javascript)}<\/script>`,!1),i=`(() => {
25
25
  'use strict';
26
26
  const channel = ${JSON.stringify(t)};
27
27
  const hostOrigin = ${JSON.stringify(n)};
28
28
  const hostTarget = hostOrigin === 'null' ? '*' : hostOrigin;
29
- const childHtml = new TextDecoder().decode(Uint8Array.from(atob('${Te(s)}'), c => c.charCodeAt(0)));
29
+ const childHtml = new TextDecoder().decode(Uint8Array.from(atob('${Le(s)}'), c => c.charCodeAt(0)));
30
30
  const frame = document.createElement('iframe');
31
- frame.setAttribute('sandbox', '${B}');
32
- frame.setAttribute('allow', ${JSON.stringify(P)});
31
+ frame.setAttribute('sandbox', '${z}');
32
+ frame.setAttribute('allow', ${JSON.stringify(F)});
33
33
  frame.referrerPolicy = 'no-referrer';
34
34
  frame.title = 'Custom chart content';
35
35
  const url = URL.createObjectURL(new Blob([childHtml], {type:'text/html'}));
@@ -60,4 +60,4 @@
60
60
  });
61
61
  frame.src = url;
62
62
  document.body.append(frame);
63
- })();`;return H(`<script>${i}<\/script>`,!0)};var ee="dbn-chart-sandbox",$e=1,z=1024*1024,F=5e3,Ee=32,Z=["fontFamily","textColor","textSecondaryColor","background","surfaceColor","borderColor","primaryColor"],Q=["fontSize","borderRadius"],Ce=/^[a-z]{2}(-[A-Z]{2})?$/,je=e=>!f(e)||!O(e,[...Z,...Q,"palette"])?!1:Z.every(t=>typeof e[t]=="string"&&e[t].length<=256)&&Q.every(t=>typeof e[t]=="number"&&Number.isFinite(e[t]))&&Array.isArray(e.palette)&&e.palette.length<=Ee&&e.palette.every(t=>typeof t=="string"&&t.length<=256),_e=["theme","language"],Ie=e=>{let t=Object.keys(e),n=_e.filter(o=>Object.hasOwn(e,o)).length;return t.length===3+n&&["columns","rows","options"].every(o=>Object.hasOwn(e,o))},v=e=>{if(!f(e)||!Ie(e)||Object.hasOwn(e,"theme")&&!je(e.theme)||Object.hasOwn(e,"language")&&!(typeof e.language=="string"&&Ce.test(e.language))||!Array.isArray(e.columns)||e.columns.length<1||e.columns.length>64||!Array.isArray(e.rows)||e.rows.length>F||!f(e.options)||Object.keys(e.options).length>32)return!1;let t=e.columns;return!t.every(n=>f(n)&&(O(n,["id","label","type","filterable"])||O(n,["id","label","type","filterable","slotId"])&&S(n.slotId))&&S(n.id)&&T(n.label)&&["string","number","boolean"].includes(n.type)&&typeof n.filterable=="boolean")||new Set(t.map(({id:n})=>n)).size!==t.length||!e.rows.every(n=>Array.isArray(n)&&n.length===t.length&&n.every((o,s)=>_(o)&&(o===null||typeof o===t[s].type)))||!Object.entries(e.options).every(([n,o])=>S(n)&&o!==null&&_(o))?!1:A(e)<=z},te=(e,t)=>{if(!t.source||e.source!==t.source||e.origin!=="null")return null;let n=e.data;if(!f(n)||n.protocol!==ee||n.version!==1||n.channel!==t.channel)return null;let o=["protocol","version","channel","type"];if(n.type==="ready")return O(n,o)?n:null;if(n.type==="error")return O(n,[...o,"code"])&&(n.code==="navigation"||n.code==="runtime")?n:null;if(n.type==="resize")return O(n,[...o,"height"])&&typeof n.height=="number"&&Number.isInteger(n.height)&&n.height>=80&&n.height<=1200?n:null;if(n.revision!==t.revision)return null;let s=t.data.columns.findIndex(({id:i})=>i===n.columnId);return s<0?null:n.type==="click"?O(n,[...o,"revision","rowIndex","columnId"])&&typeof n.rowIndex=="number"&&Number.isInteger(n.rowIndex)&&n.rowIndex>=0&&n.rowIndex<t.data.rows.length?n:null:n.type==="filter"&&O(n,[...o,"revision","columnId","values"])&&t.data.columns[s].filterable&&Array.isArray(n.values)&&n.values.length<=100&&n.values.every(i=>_(i)&&t.data.rows.some(d=>d[s]===i))&&A(n)<=32*1024?n:null},ne=(e,t,n)=>({protocol:ee,version:$e,channel:e,type:"data",revision:t,data:n});var oe=4096,Re=(e,t)=>{let n=e.toLowerCase().replace(/[^a-z0-9_-]+/g,"_").replace(/^[^a-z]+|_+$/g,"").slice(0,48);return n?`${n}_${t}`:`col_${t}`},se=e=>{if(typeof e=="number")return Number.isFinite(e)?e:null;if(typeof e=="boolean")return e?1:0;if(typeof e=="string"&&e.trim()!==""){let t=Number(e);return Number.isFinite(t)?t:null}return null},ve=e=>{if(e==null)return null;if(e instanceof Date)return Number.isNaN(e.getTime())?null:e.toISOString();let t=typeof e=="string"?e:String(e);return t.length>oe?t.slice(0,oe):t},Me=e=>typeof e=="boolean"?e:e==="true"?!0:e==="false"?!1:e===1||e===0?e===1:null,Le=(e,t)=>{if(e.types.length===1)return e.types[0];let n=t.filter(o=>o!=null);return e.types.includes("boolean")&&n.length>0&&n.every(o=>typeof o=="boolean")?"boolean":e.types.includes("number")&&n.length>0&&n.every(o=>se(o)!==null)?"number":e.types.includes("string")?"string":e.types[0]},De=(e,t)=>{switch(e){case"number":return se(t);case"boolean":return Me(t);default:return ve(t)}},Ne=(e,t)=>{let n=R(e,t);if(n)return n;let o={};for(let[s,i]of Object.entries(t))R(e,{[s]:i})&&(o[s]=i);return R(e,o)??{}},re=({manifest:e,config:t,rows:n,columns:o,theme:s,language:i})=>{let d=new Map;if(o)for(let u of o)d.set(u.key,u.label);if(d.size===0&&n[0])for(let u of Object.keys(n[0]))d.set(u,u);let r=new Set,c={},a=[],l=[];for(let u of e.slots){let y=(t.bindings[u.id]??[]).filter(b=>d.has(b));y.length===0&&u.required&&l.push(u);for(let b of y){let h=Re(b,a.length);for(;r.has(h)||!S(h);)h=`col_${a.length}_${r.size}`;r.add(h),c[h]=b;let $=n.slice(0,200).map(E=>E[b]);a.push({id:h,label:(d.get(b)||b).slice(0,256)||h,type:Le(u,$),filterable:u.role==="dimension",slotId:u.id,source:b})}}let g=n.length,m=Math.min(g,F),p=u=>({columns:a.map(({id:y,label:b,type:h,filterable:$,slotId:E})=>({id:y,label:b,type:h,filterable:$,slotId:E})),rows:n.slice(0,u).map(y=>a.map(b=>De(b.type,y[b.source]))),options:Ne(e,t.options),...s?{theme:s}:{},...i?{language:i}:{}}),x=p(m);for(;m>0&&A(x)>z;)m=Math.floor(m/2),x=p(m);return a.length===0?{data:x,truncated:!1,totalRows:g,sources:c,missingSlots:l}:(v(x)||(x={...p(0)},m=0),{data:x,truncated:m<g,totalRows:g,sources:c,missingSlots:l})};function Be(e,t,n,o,s){let i=!0,d=!1,r=0,c=0,a=crypto.randomUUID(),l=document.createElement("iframe");l.title=t.manifest.name,l.setAttribute("sandbox",B),l.setAttribute("allow",P),l.referrerPolicy="no-referrer",l.style.cssText="width:100%;height:320px;border:0;display:block";let g=()=>{let w=getComputedStyle(document.body),k=getComputedStyle(document.documentElement).colorScheme==="dark";return{fontFamily:w.fontFamily,fontSize:parseFloat(w.fontSize)||14,textColor:w.color,textSecondaryColor:w.color,background:w.backgroundColor,surfaceColor:w.backgroundColor,borderColor:k?"#454652":"#dedee6",borderRadius:8,primaryColor:"#7c6cf0",palette:["#7c6cf0","#35b9a7","#e6a548","#df7197","#539bdd","#a985db"]}},m=()=>re({manifest:t.manifest,config:n,rows:o,theme:g()}),p=m();if(p.missingSlots.length||!v(p.data))throw Error("Bind each required custom chart slot to a result column. The rows are below.");let x=W({manifest:t.manifest,javascript:t.bundleJs,css:t.bundleCss},a,globalThis.origin),u=URL.createObjectURL(new Blob([x],{type:"text/html"})),y=()=>{!i||!d||(p=m(),l.contentWindow?.postMessage(ne(a,++r,p.data),"*"))},b=()=>{i=!1,clearTimeout(E),U.disconnect(),removeEventListener("message",$),URL.revokeObjectURL(u),l.remove()},h=w=>{b(),s(w)},$=w=>{if(!i)return;let k=te(w,{source:l.contentWindow,channel:a,revision:r,data:p.data});if(k)if(k.type==="ready"){if(d)return;d=!0,clearTimeout(E),s(""),y()}else k.type==="error"?h("The custom chart could not run safely. The rows are below."):k.type==="resize"&&(l.style.height=`${k.height}px`)},E=setTimeout(()=>h("This host could not open the custom chart sandbox. The rows are below."),8e3),U=new ResizeObserver(y);return l.addEventListener("load",()=>{URL.revokeObjectURL(u),++c>1&&h("The custom chart navigated away. The rows are below.")}),addEventListener("message",$),l.src=u,e.append(l),U.observe(e),{dispose:b,update:y}}Object.assign(globalThis,{DbnCustomChartRuntime:{mount:Be}});})();
63
+ })();`;return se(`<script>${i}<\/script>`,!0)};function Be(e,t,n,o,s){let i=!0,d=!1,r=0,c=0,a=crypto.randomUUID(),l=document.createElement("iframe");l.title=t.manifest.name,l.setAttribute("sandbox",z),l.setAttribute("allow",F),l.referrerPolicy="no-referrer",l.style.cssText="width:100%;height:320px;border:0;display:block";let g=()=>{let w=getComputedStyle(document.body),k=getComputedStyle(document.documentElement).colorScheme==="dark";return{fontFamily:w.fontFamily,fontSize:parseFloat(w.fontSize)||14,textColor:w.color,textSecondaryColor:w.color,background:w.backgroundColor,surfaceColor:w.backgroundColor,borderColor:k?"#454652":"#dedee6",borderRadius:8,primaryColor:"#7c6cf0",palette:["#7c6cf0","#35b9a7","#e6a548","#df7197","#539bdd","#a985db"]}},m=()=>te({manifest:t.manifest,config:n,rows:o,theme:g()}),p=m();if(p.missingSlots.length||!v(p.data))throw Error("Bind each required custom chart slot to a result column. The rows are below.");let x=re({manifest:t.manifest,javascript:t.bundleJs,css:t.bundleCss},a,globalThis.origin),u=URL.createObjectURL(new Blob([x],{type:"text/html"})),y=()=>{!i||!d||(p=m(),l.contentWindow?.postMessage(Z(a,++r,p.data),"*"))},b=()=>{i=!1,clearTimeout(E),U.disconnect(),removeEventListener("message",$),URL.revokeObjectURL(u),l.remove()},h=w=>{b(),s(w)},$=w=>{if(!i)return;let k=W(w,{source:l.contentWindow,channel:a,revision:r,data:p.data});if(k)if(k.type==="ready"){if(d)return;d=!0,clearTimeout(E),s(""),y()}else k.type==="error"?h("The custom chart could not run safely. The rows are below."):k.type==="resize"&&(l.style.height=`${k.height}px`)},E=setTimeout(()=>h("This host could not open the custom chart sandbox. The rows are below."),8e3),U=new ResizeObserver(y);return l.addEventListener("load",()=>{URL.revokeObjectURL(u),++c>1&&h("The custom chart navigated away. The rows are below.")}),addEventListener("message",$),l.src=u,e.append(l),U.observe(e),{dispose:b,update:y}}Object.assign(globalThis,{DbnCustomChartRuntime:{mount:Be}});})();
@@ -1,4 +1,7 @@
1
- import type { CustomChartContext, CustomChartValue } from '@databrainhq/custom-chart-types';
1
+ import type {
2
+ CustomChartContext,
3
+ CustomChartValue,
4
+ } from '@databrainhq/custom-chart-types';
2
5
 
3
6
  /*
4
7
  * A bar chart in plain DOM and SVG. No dependency. It reads:
@@ -10,13 +13,21 @@ import type { CustomChartContext, CustomChartValue } from '@databrainhq/custom-c
10
13
 
11
14
  const SVG_NS = 'http://www.w3.org/2000/svg';
12
15
 
13
- function svg<K extends keyof SVGElementTagNameMap>(tag: K, attrs: Record<string, string | number>): SVGElementTagNameMap[K] {
16
+ function svg<K extends keyof SVGElementTagNameMap>(
17
+ tag: K,
18
+ attrs: Record<string, string | number>
19
+ ): SVGElementTagNameMap[K] {
14
20
  const el = document.createElementNS(SVG_NS, tag);
15
- for (const [key, value] of Object.entries(attrs)) el.setAttribute(key, String(value));
21
+ for (const [key, value] of Object.entries(attrs))
22
+ el.setAttribute(key, String(value));
16
23
  return el;
17
24
  }
18
25
 
19
- function firstIndex(ctx: CustomChartContext, slotId: string, type: 'string' | 'number'): number {
26
+ function firstIndex(
27
+ ctx: CustomChartContext,
28
+ slotId: string,
29
+ type: 'string' | 'number'
30
+ ): number {
20
31
  const bound = ctx.slots[slotId];
21
32
  if (bound && bound.length > 0) return bound[0];
22
33
  return ctx.data.columns.findIndex((column) => column.type === type);
@@ -32,7 +43,9 @@ function asText(value: CustomChartValue): string {
32
43
 
33
44
  function formatNumber(value: number, language: string): string {
34
45
  try {
35
- return new Intl.NumberFormat(language, { maximumFractionDigits: 2 }).format(value);
46
+ return new Intl.NumberFormat(language, { maximumFractionDigits: 2 }).format(
47
+ value
48
+ );
36
49
  } catch {
37
50
  return String(value);
38
51
  }
@@ -56,16 +69,31 @@ function truncate(text: string, maxWidth: number, fontSize: number): string {
56
69
  * label fits its bar slot; rotated (and truncated to MAX_LABEL_HEIGHT) when
57
70
  * the slots are too narrow. Returns the bottom padding the plot must keep.
58
71
  */
59
- function axisLayout(labels: string[], step: number, fontSize: number): { rotated: boolean; bottom: number; maxWidth: number } {
72
+ function axisLayout(
73
+ labels: string[],
74
+ step: number,
75
+ fontSize: number
76
+ ): { rotated: boolean; bottom: number; maxWidth: number } {
60
77
  const labelFont = fontSize * 0.9;
61
- const longest = labels.reduce((max, label) => Math.max(max, label.length), 0) * labelFont * CHAR_WIDTH;
78
+ const longest =
79
+ labels.reduce((max, label) => Math.max(max, label.length), 0) *
80
+ labelFont *
81
+ CHAR_WIDTH;
62
82
  const slot = Math.max(8, step - 4);
63
83
  if (longest <= slot) {
64
- return { rotated: false, bottom: Math.max(MIN_BOTTOM_PADDING, labelFont * 2), maxWidth: slot };
84
+ return {
85
+ rotated: false,
86
+ bottom: Math.max(MIN_BOTTOM_PADDING, labelFont * 2),
87
+ maxWidth: slot,
88
+ };
65
89
  }
66
90
  const maxWidth = Math.min(longest, MAX_LABEL_HEIGHT);
67
91
  // sin(45deg) of the label length is its height; a line of clearance on top.
68
- return { rotated: true, bottom: Math.max(MIN_BOTTOM_PADDING, maxWidth * 0.71 + labelFont), maxWidth };
92
+ return {
93
+ rotated: true,
94
+ bottom: Math.max(MIN_BOTTOM_PADDING, maxWidth * 0.71 + labelFont),
95
+ maxWidth,
96
+ };
69
97
  }
70
98
 
71
99
  export function render(ctx: CustomChartContext): void {
@@ -77,7 +105,8 @@ export function render(ctx: CustomChartContext): void {
77
105
 
78
106
  const categoryIndex = firstIndex(ctx, 'category', 'string');
79
107
  const valueIndex = firstIndex(ctx, 'value', 'number');
80
- const targetIndex = ctx.slots.target && ctx.slots.target.length > 0 ? ctx.slots.target[0] : -1;
108
+ const targetIndex =
109
+ ctx.slots.target && ctx.slots.target.length > 0 ? ctx.slots.target[0] : -1;
81
110
 
82
111
  if (categoryIndex < 0 || valueIndex < 0 || data.rows.length === 0) {
83
112
  const empty = document.createElement('div');
@@ -95,12 +124,21 @@ export function render(ctx: CustomChartContext): void {
95
124
  const subtitle = String(options.subtitle ?? '');
96
125
  const showLabels = options.show_labels !== false;
97
126
  const radius = Math.max(0, Number(options.bar_radius ?? 4));
98
- const barColor = typeof options.bar_color === 'string' ? options.bar_color : theme.palette[0] ?? theme.primaryColor;
127
+ const barColor =
128
+ typeof options.bar_color === 'string'
129
+ ? options.bar_color
130
+ : (theme.palette[0] ?? theme.primaryColor);
99
131
  const sort = String(options.sort ?? 'none');
100
132
 
101
133
  let rows = data.rows.map((row, rowIndex) => ({ row, rowIndex }));
102
- if (sort === 'asc') rows = [...rows].sort((a, b) => asNumber(a.row[valueIndex]) - asNumber(b.row[valueIndex]));
103
- if (sort === 'desc') rows = [...rows].sort((a, b) => asNumber(b.row[valueIndex]) - asNumber(a.row[valueIndex]));
134
+ if (sort === 'asc')
135
+ rows = [...rows].sort(
136
+ (a, b) => asNumber(a.row[valueIndex]) - asNumber(b.row[valueIndex])
137
+ );
138
+ if (sort === 'desc')
139
+ rows = [...rows].sort(
140
+ (a, b) => asNumber(b.row[valueIndex]) - asNumber(a.row[valueIndex])
141
+ );
104
142
 
105
143
  const labels = rows.map(({ row }) => asText(row[categoryIndex]));
106
144
  const sidePadding = fontSize;
@@ -115,23 +153,38 @@ export function render(ctx: CustomChartContext): void {
115
153
  };
116
154
  const plotHeight = Math.max(8, height - padding.top - padding.bottom);
117
155
  const values = rows.map(({ row }) => asNumber(row[valueIndex]));
118
- const targets = targetIndex >= 0 ? rows.map(({ row }) => asNumber(row[targetIndex])) : [];
156
+ const targets =
157
+ targetIndex >= 0 ? rows.map(({ row }) => asNumber(row[targetIndex])) : [];
119
158
  const max = Math.max(1, ...values, ...targets);
120
159
  const barWidth = Math.max(2, step * 0.7);
121
160
 
122
- const root = svg('svg', { width, height, viewBox: `0 0 ${width} ${height}`, role: 'img', 'aria-label': 'Bar chart' });
161
+ const root = svg('svg', {
162
+ width,
163
+ height,
164
+ viewBox: `0 0 ${width} ${height}`,
165
+ role: 'img',
166
+ 'aria-label': 'Bar chart',
167
+ });
123
168
  root.classList.add('dbn-chart-svg');
124
169
 
125
170
  if (subtitle) {
126
- const text = svg('text', { x: padding.left, y: fontSize * 1.4, 'font-size': fontSize, fill: theme.textSecondaryColor });
171
+ const text = svg('text', {
172
+ x: padding.left,
173
+ y: fontSize * 1.4,
174
+ 'font-size': fontSize,
175
+ fill: theme.textSecondaryColor,
176
+ });
127
177
  text.textContent = subtitle;
128
178
  root.append(text);
129
179
  }
130
180
 
131
181
  const baseline = svg('line', {
132
- x1: padding.left, x2: width - padding.right,
133
- y1: padding.top + plotHeight, y2: padding.top + plotHeight,
134
- stroke: theme.borderColor, 'stroke-width': 1,
182
+ x1: padding.left,
183
+ x2: width - padding.right,
184
+ y1: padding.top + plotHeight,
185
+ y2: padding.top + plotHeight,
186
+ stroke: theme.borderColor,
187
+ 'stroke-width': 1,
135
188
  });
136
189
  root.append(baseline);
137
190
 
@@ -141,9 +194,18 @@ export function render(ctx: CustomChartContext): void {
141
194
  const barHeight = (value / max) * plotHeight;
142
195
  const x = padding.left + position * step + (step - barWidth) / 2;
143
196
  const y = padding.top + plotHeight - barHeight;
144
- const bar = svg('rect', { x, y, width: barWidth, height: barHeight, rx: Math.min(radius, barWidth / 2), fill: barColor });
197
+ const bar = svg('rect', {
198
+ x,
199
+ y,
200
+ width: barWidth,
201
+ height: barHeight,
202
+ rx: Math.min(radius, barWidth / 2),
203
+ fill: barColor,
204
+ });
145
205
  bar.classList.add('dbn-chart-bar');
146
- bar.addEventListener('click', () => dbnChart.click(rowIndex, categoryColumnId));
206
+ bar.addEventListener('click', () =>
207
+ dbnChart.click(rowIndex, categoryColumnId)
208
+ );
147
209
  const title = svg('title', {});
148
210
  title.textContent = `${asText(row[categoryIndex])}: ${formatNumber(value, language)}`;
149
211
  bar.append(title);
@@ -151,7 +213,11 @@ export function render(ctx: CustomChartContext): void {
151
213
 
152
214
  if (showLabels) {
153
215
  const label = svg('text', {
154
- x: x + barWidth / 2, y: y - 4, 'text-anchor': 'middle', 'font-size': fontSize * 0.9, fill: theme.textColor,
216
+ x: x + barWidth / 2,
217
+ y: y - 4,
218
+ 'text-anchor': 'middle',
219
+ 'font-size': fontSize * 0.9,
220
+ fill: theme.textColor,
155
221
  });
156
222
  label.textContent = formatNumber(value, language);
157
223
  root.append(label);
@@ -162,9 +228,25 @@ export function render(ctx: CustomChartContext): void {
162
228
  const labelFont = fontSize * 0.9;
163
229
  const labelX = x + barWidth / 2;
164
230
  const labelY = padding.top + plotHeight + labelFont * 1.2;
165
- const label = svg('text', axis.rotated
166
- ? { x: labelX, y: labelY, 'text-anchor': 'end', 'font-size': labelFont, fill: theme.textSecondaryColor, transform: `rotate(-45 ${labelX} ${labelY})` }
167
- : { x: labelX, y: labelY, 'text-anchor': 'middle', 'font-size': labelFont, fill: theme.textSecondaryColor });
231
+ const label = svg(
232
+ 'text',
233
+ axis.rotated
234
+ ? {
235
+ x: labelX,
236
+ y: labelY,
237
+ 'text-anchor': 'end',
238
+ 'font-size': labelFont,
239
+ fill: theme.textSecondaryColor,
240
+ transform: `rotate(-45 ${labelX} ${labelY})`,
241
+ }
242
+ : {
243
+ x: labelX,
244
+ y: labelY,
245
+ 'text-anchor': 'middle',
246
+ 'font-size': labelFont,
247
+ fill: theme.textSecondaryColor,
248
+ }
249
+ );
168
250
  label.classList.add('dbn-chart-axis-label');
169
251
  label.textContent = truncate(labels[position], axis.maxWidth, labelFont);
170
252
  const full = svg('title', {});
@@ -173,10 +255,16 @@ export function render(ctx: CustomChartContext): void {
173
255
  root.append(label);
174
256
 
175
257
  if (targetIndex >= 0) {
176
- const targetY = padding.top + plotHeight - (targets[position] / max) * plotHeight;
258
+ const targetY =
259
+ padding.top + plotHeight - (targets[position] / max) * plotHeight;
177
260
  const marker = svg('line', {
178
- x1: x - 2, x2: x + barWidth + 2, y1: targetY, y2: targetY,
179
- stroke: theme.palette[1] ?? theme.textColor, 'stroke-width': 2, 'stroke-dasharray': '4 2',
261
+ x1: x - 2,
262
+ x2: x + barWidth + 2,
263
+ y1: targetY,
264
+ y2: targetY,
265
+ stroke: theme.palette[1] ?? theme.textColor,
266
+ 'stroke-width': 2,
267
+ 'stroke-dasharray': '4 2',
180
268
  });
181
269
  marker.classList.add('dbn-chart-target');
182
270
  root.append(marker);
@@ -1 +1 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="12" width="4" height="8" rx="1"/><rect x="10" y="6" width="4" height="14" rx="1"/><rect x="17" y="9" width="4" height="11" rx="1"/></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><title>Sample chart</title><rect x="3" y="12" width="4" height="8" rx="1"/><rect x="10" y="6" width="4" height="14" rx="1"/><rect x="17" y="9" width="4" height="11" rx="1"/></svg>
@@ -5,19 +5,73 @@
5
5
  "version": "1.0.0",
6
6
  "description": "Bars with an optional target line.",
7
7
  "slots": [
8
- { "id": "category", "label": "Category", "role": "dimension", "types": ["string"], "required": true, "description": "One bar per value." },
9
- { "id": "value", "label": "Value", "role": "measure", "types": ["number"], "required": true },
10
- { "id": "target", "label": "Target", "role": "measure", "types": ["number"], "required": false }
8
+ {
9
+ "id": "category",
10
+ "label": "Category",
11
+ "role": "dimension",
12
+ "types": ["string"],
13
+ "required": true,
14
+ "description": "One bar per value."
15
+ },
16
+ {
17
+ "id": "value",
18
+ "label": "Value",
19
+ "role": "measure",
20
+ "types": ["number"],
21
+ "required": true
22
+ },
23
+ {
24
+ "id": "target",
25
+ "label": "Target",
26
+ "role": "measure",
27
+ "types": ["number"],
28
+ "required": false
29
+ }
11
30
  ],
12
31
  "options": [
13
- { "id": "show_labels", "label": "Show labels", "type": "boolean", "default": true, "group": "Labels" },
14
- { "id": "subtitle", "label": "Subtitle", "type": "string", "default": "", "maxLength": 120, "group": "Labels" },
15
- { "id": "bar_radius", "label": "Corner radius", "type": "number", "default": 4, "min": 0, "max": 20, "step": 1, "group": "Bars" },
16
- { "id": "bar_color", "label": "Bar colour", "type": "color", "default": "#3366cc", "group": "Bars" },
17
- { "id": "sort", "label": "Sort", "type": "select", "default": "none", "choices": [
18
- { "value": "none", "label": "Data order" },
19
- { "value": "asc", "label": "Smallest first" },
20
- { "value": "desc", "label": "Largest first" }
21
- ], "group": "Bars" }
32
+ {
33
+ "id": "show_labels",
34
+ "label": "Show labels",
35
+ "type": "boolean",
36
+ "default": true,
37
+ "group": "Labels"
38
+ },
39
+ {
40
+ "id": "subtitle",
41
+ "label": "Subtitle",
42
+ "type": "string",
43
+ "default": "",
44
+ "maxLength": 120,
45
+ "group": "Labels"
46
+ },
47
+ {
48
+ "id": "bar_radius",
49
+ "label": "Corner radius",
50
+ "type": "number",
51
+ "default": 4,
52
+ "min": 0,
53
+ "max": 20,
54
+ "step": 1,
55
+ "group": "Bars"
56
+ },
57
+ {
58
+ "id": "bar_color",
59
+ "label": "Bar colour",
60
+ "type": "color",
61
+ "default": "#3366cc",
62
+ "group": "Bars"
63
+ },
64
+ {
65
+ "id": "sort",
66
+ "label": "Sort",
67
+ "type": "select",
68
+ "default": "none",
69
+ "choices": [
70
+ { "value": "none", "label": "Data order" },
71
+ { "value": "asc", "label": "Smallest first" },
72
+ { "value": "desc", "label": "Largest first" }
73
+ ],
74
+ "group": "Bars"
75
+ }
22
76
  ]
23
77
  }
@@ -1,8 +1,26 @@
1
1
  {
2
2
  "columns": [
3
- { "id": "country", "label": "Country", "type": "string", "filterable": true, "slotId": "category" },
4
- { "id": "revenue", "label": "Revenue", "type": "number", "filterable": false, "slotId": "value" },
5
- { "id": "target", "label": "Target", "type": "number", "filterable": false, "slotId": "target" }
3
+ {
4
+ "id": "country",
5
+ "label": "Country",
6
+ "type": "string",
7
+ "filterable": true,
8
+ "slotId": "category"
9
+ },
10
+ {
11
+ "id": "revenue",
12
+ "label": "Revenue",
13
+ "type": "number",
14
+ "filterable": false,
15
+ "slotId": "value"
16
+ },
17
+ {
18
+ "id": "target",
19
+ "label": "Target",
20
+ "type": "number",
21
+ "filterable": false,
22
+ "slotId": "target"
23
+ }
6
24
  ],
7
25
  "rows": [
8
26
  ["India", 1240, 1100],
package/dist/index.d.ts CHANGED
@@ -1,2 +1 @@
1
-
2
- export { }
1
+ export { }
package/dist/index.js CHANGED
@@ -3,18 +3,18 @@ import {
3
3
  DEFAULT_POSTHOG_API_KEY,
4
4
  DEFAULT_POSTHOG_HOST,
5
5
  loadConfig
6
- } from "./chunk-V3UW3B6U.js";
6
+ } from "./chunk-S65KA3Z7.js";
7
7
  import {
8
8
  registerAllPrompts,
9
9
  registerAllResources,
10
10
  registerAllTools
11
- } from "./chunk-OLOC2MTA.js";
11
+ } from "./chunk-UYFFFAYV.js";
12
12
  import {
13
13
  DatabrainClient,
14
14
  MCP_SERVER_VERSION,
15
15
  logger
16
- } from "./chunk-347U6GXR.js";
17
- import "./chunk-Q5EYDKSA.js";
16
+ } from "./chunk-AWICHHDU.js";
17
+ import "./chunk-2F7V6RZR.js";
18
18
 
19
19
  // src/index.ts
20
20
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
@@ -89,14 +89,16 @@ Trigger keywords: embed, dashboard, analytics, Databrain, guest token, theme, fi
89
89
 
90
90
  // src/utils/analytics.ts
91
91
  import { createHash } from "crypto";
92
- import { PostHog } from "posthog-node";
93
92
  import { instrument } from "@posthog/mcp";
93
+ import { PostHog } from "posthog-node";
94
94
  function distinctIdFromToken(token) {
95
95
  if (!token) return null;
96
96
  return createHash("sha256").update(token).digest("hex").slice(0, 32);
97
97
  }
98
98
  function setupAnalytics(server, config) {
99
- const distinctId = distinctIdFromToken(config.serviceToken || config.apiToken);
99
+ const distinctId = distinctIdFromToken(
100
+ config.serviceToken || config.apiToken
101
+ );
100
102
  if (!distinctId) {
101
103
  return null;
102
104
  }
@@ -118,7 +120,7 @@ function banner(msg) {
118
120
  async function main() {
119
121
  try {
120
122
  if (process.argv.includes("--http")) {
121
- const { startHosted } = await import("./main-IUV24DMP.js");
123
+ const { startHosted } = await import("./main-QCBVJSSR.js");
122
124
  await startHosted();
123
125
  return;
124
126
  }
@@ -133,12 +135,20 @@ async function main() {
133
135
  await posthog.shutdown();
134
136
  };
135
137
  process.on("beforeExit", () => void flushAnalytics());
136
- process.on("SIGINT", () => void flushAnalytics().then(() => process.exit(0)));
137
- process.on("SIGTERM", () => void flushAnalytics().then(() => process.exit(0)));
138
+ process.on(
139
+ "SIGINT",
140
+ () => void flushAnalytics().then(() => process.exit(0))
141
+ );
142
+ process.on(
143
+ "SIGTERM",
144
+ () => void flushAnalytics().then(() => process.exit(0))
145
+ );
138
146
  await server.connect(transport);
139
147
  banner(`v${MCP_SERVER_VERSION} ready`);
140
148
  } catch (error) {
141
- logger.error(`Failed to start server: ${error instanceof Error ? error.message : String(error)}`);
149
+ logger.error(
150
+ `Failed to start server: ${error instanceof Error ? error.message : String(error)}`
151
+ );
142
152
  process.exit(1);
143
153
  }
144
154
  }