@databrainhq/mcp-server 0.3.0-rc.13 → 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.
- package/.mcp.json +6 -1
- package/dist/apps/server.d.ts +102 -389
- package/dist/apps/server.js +5 -5
- package/dist/apps/stdio.d.ts +1 -2
- package/dist/apps/stdio.js +6 -6
- package/dist/{bundle-TDQXWIPM.js → bundle-QJNCUMNY.js} +2 -2
- package/dist/{chunk-Q5EYDKSA.js → chunk-2F7V6RZR.js} +511 -107
- package/dist/chunk-2F7V6RZR.js.map +1 -0
- package/dist/{chunk-AEQTEE5C.js → chunk-AWICHHDU.js} +153 -61
- package/dist/chunk-AWICHHDU.js.map +1 -0
- package/dist/{chunk-X44C5GW2.js → chunk-CEGHG5U3.js} +59 -18
- package/dist/chunk-CEGHG5U3.js.map +1 -0
- package/dist/{chunk-233VGFHY.js → chunk-NIC5MAGC.js} +317 -147
- package/dist/chunk-NIC5MAGC.js.map +1 -0
- package/dist/{chunk-DTFYHLYN.js → chunk-S65KA3Z7.js} +26 -11
- package/dist/chunk-S65KA3Z7.js.map +1 -0
- package/dist/{chunk-PSJVYD4W.js → chunk-UYFFFAYV.js} +11722 -10560
- package/dist/chunk-UYFFFAYV.js.map +1 -0
- package/dist/{chunk-HCYZN7I3.js → chunk-VQ5G5AZV.js} +10 -7
- package/dist/chunk-VQ5G5AZV.js.map +1 -0
- package/dist/cli.d.ts +16 -15
- package/dist/cli.js +134 -36
- package/dist/cli.js.map +1 -1
- package/dist/custom-chart/custom-runtime.js +6 -6
- package/dist/custom-chart/template/chart.ts +116 -28
- package/dist/custom-chart/template/icon.svg +1 -1
- package/dist/custom-chart/template/manifest.json +66 -12
- package/dist/custom-chart/template/sample-data.json +21 -3
- package/dist/index.d.ts +1 -2
- package/dist/index.js +20 -10
- package/dist/index.js.map +1 -1
- package/dist/{main-VEKNKWL5.js → main-QCBVJSSR.js} +348 -155
- package/dist/main-QCBVJSSR.js.map +1 -0
- package/dist/{test-only-LJGOY2QA.js → test-only-6474NMTR.js} +12 -8
- package/dist/test-only-6474NMTR.js.map +1 -0
- package/package.json +20 -9
- package/dist/chunk-233VGFHY.js.map +0 -1
- package/dist/chunk-AEQTEE5C.js.map +0 -1
- package/dist/chunk-DTFYHLYN.js.map +0 -1
- package/dist/chunk-HCYZN7I3.js.map +0 -1
- package/dist/chunk-PSJVYD4W.js.map +0 -1
- package/dist/chunk-Q5EYDKSA.js.map +0 -1
- package/dist/chunk-X44C5GW2.js.map +0 -1
- package/dist/main-VEKNKWL5.js.map +0 -1
- package/dist/test-only-LJGOY2QA.js.map +0 -1
- /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
|
-
})();`,
|
|
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('${
|
|
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', '${
|
|
32
|
-
frame.setAttribute('allow', ${JSON.stringify(
|
|
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
|
|
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 {
|
|
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>(
|
|
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))
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
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 {
|
|
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 {
|
|
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 =
|
|
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 =
|
|
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')
|
|
103
|
-
|
|
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 =
|
|
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', {
|
|
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', {
|
|
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,
|
|
133
|
-
|
|
134
|
-
|
|
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', {
|
|
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', () =>
|
|
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,
|
|
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(
|
|
166
|
-
|
|
167
|
-
|
|
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 =
|
|
258
|
+
const targetY =
|
|
259
|
+
padding.top + plotHeight - (targets[position] / max) * plotHeight;
|
|
177
260
|
const marker = svg('line', {
|
|
178
|
-
x1: x - 2,
|
|
179
|
-
|
|
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
|
-
{
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
{
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
{
|
|
4
|
-
|
|
5
|
-
|
|
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-
|
|
6
|
+
} from "./chunk-S65KA3Z7.js";
|
|
7
7
|
import {
|
|
8
8
|
registerAllPrompts,
|
|
9
9
|
registerAllResources,
|
|
10
10
|
registerAllTools
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-UYFFFAYV.js";
|
|
12
12
|
import {
|
|
13
13
|
DatabrainClient,
|
|
14
14
|
MCP_SERVER_VERSION,
|
|
15
15
|
logger
|
|
16
|
-
} from "./chunk-
|
|
17
|
-
import "./chunk-
|
|
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(
|
|
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-
|
|
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(
|
|
137
|
-
|
|
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(
|
|
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
|
}
|