@fusedio/widget-sdk 0.2.0 → 0.3.1
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/README.md +1 -0
- package/dist/bridge.d.ts +41 -0
- package/dist/bundle.js +2 -2
- package/dist/hooks/json-ui-binding.d.ts +7 -0
- package/dist/hooks/json-ui-binding.js +27 -0
- package/dist/hooks/use-duckdb-sql.d.ts +7 -1
- package/dist/hooks/use-duckdb-sql.js +9 -2
- package/dist/hooks/use-udf-executor.d.ts +32 -0
- package/dist/hooks/use-udf-executor.js +147 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +8 -1
- package/dist/utils/executor.d.ts +20 -0
- package/dist/utils/executor.js +45 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -58,6 +58,7 @@ any connected UDF re-runs with the updated parameter.
|
|
|
58
58
|
| `useCanvasParams` | Read multiple canvas parameter values at once (edge-filtered). |
|
|
59
59
|
| `useParamSubstitution` | Resolve `$param` and `{{udf}}` placeholders inside a template. |
|
|
60
60
|
| `useUdfOutputByName` | Subscribe to a UDF's output, status, error. |
|
|
61
|
+
| `useUdfExecutor` | Run a UDF on an event (`udf?param=1`); resolves `$param` at fire time. |
|
|
61
62
|
| `useUdfColumnValue` / `Values` | Pull values out of `{{udf.col}}` / `{{udf.col[idx]}}` queries. |
|
|
62
63
|
| `useUdfDataFrameSample` | Sample rows from a UDF's DataFrame output. |
|
|
63
64
|
| `useDuckDbSqlQuery` | Run a DuckDB-WASM query against UDF parquet outputs in the browser. |
|
package/dist/bridge.d.ts
CHANGED
|
@@ -46,6 +46,38 @@ export interface UdfBridge {
|
|
|
46
46
|
getOutputSnapshot(udfName: string): UdfOutputSnapshot | undefined;
|
|
47
47
|
/** Request the workbench to re-execute a UDF. */
|
|
48
48
|
requestReexecute(udfName: string): void;
|
|
49
|
+
/**
|
|
50
|
+
* Imperatively run a named UDF with already-resolved query-param overrides
|
|
51
|
+
* and return its decoded output. One-shot request/response: unlike
|
|
52
|
+
* `requestReexecute`, this does NOT mutate canvas param state or the node's
|
|
53
|
+
* own results. It is the primitive behind event-driven execution — e.g.
|
|
54
|
+
* `useUdfExecutor` (a button click runs `udf?param=1`).
|
|
55
|
+
*
|
|
56
|
+
* `overrides` are fully resolved by the SDK before this is called — no
|
|
57
|
+
* `$param` references remain. Hosts that cannot execute UDFs (e.g. a minimal
|
|
58
|
+
* test harness) should reject the returned promise or resolve with `error`;
|
|
59
|
+
* `useUdfExecutor` surfaces either as an error state.
|
|
60
|
+
*/
|
|
61
|
+
execute(udfName: string, overrides: Record<string, string>, options?: UdfExecuteOptions): Promise<UdfExecuteResult>;
|
|
62
|
+
}
|
|
63
|
+
export interface UdfExecuteOptions {
|
|
64
|
+
/**
|
|
65
|
+
* Desired output format. The host decodes the response accordingly:
|
|
66
|
+
* `"json"` → parsed JSON value, `"html"`/`"text"` → string, others are
|
|
67
|
+
* host-defined. When omitted the host picks its default (typically `"json"`).
|
|
68
|
+
*/
|
|
69
|
+
format?: string;
|
|
70
|
+
/** Optional cancellation signal forwarded to the host's fetch. */
|
|
71
|
+
signal?: AbortSignal;
|
|
72
|
+
}
|
|
73
|
+
export interface UdfExecuteResult {
|
|
74
|
+
/**
|
|
75
|
+
* Decoded UDF output. Shape depends on the requested `format` (parsed JSON
|
|
76
|
+
* value, HTML/text string, …). `null` when `error` is set.
|
|
77
|
+
*/
|
|
78
|
+
data: unknown;
|
|
79
|
+
/** Human-readable error message when execution failed; omitted on success. */
|
|
80
|
+
error?: string;
|
|
49
81
|
}
|
|
50
82
|
export interface UdfOutputSnapshot {
|
|
51
83
|
/** The UDF result data — TableDataSource, HTML blob, array, etc. */
|
|
@@ -87,6 +119,15 @@ export interface RoutingBridge {
|
|
|
87
119
|
export interface SqlQueryOptions {
|
|
88
120
|
defaultLimit?: number;
|
|
89
121
|
signal?: AbortSignal;
|
|
122
|
+
/**
|
|
123
|
+
* Optional binding identity (chunk-2 MCP-host seam). When the host resolves
|
|
124
|
+
* data server-side (the MCP Apps renderer), it stamps a `_queryId` into each
|
|
125
|
+
* data-bound node's props and the SDK threads it here so the static bridge can
|
|
126
|
+
* look up the pre-resolved rows by id. The workbench bridge ignores this field
|
|
127
|
+
* (it runs the query against DuckDB regardless), so it is fully
|
|
128
|
+
* backward-compatible.
|
|
129
|
+
*/
|
|
130
|
+
queryId?: string;
|
|
90
131
|
}
|
|
91
132
|
export interface SqlQueryResult {
|
|
92
133
|
rows: ReadonlyArray<Record<string, unknown>>;
|
package/dist/bundle.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var Sn="parameter-updates",ce=(o=>(o.PARAM="param",o.RANGE="range",o.VIEWPORT="viewport",o.CLEAR="clear",o))(ce||{});function yn(e){if(typeof e!="object"||e===null)return!1;let r=e;return typeof r.type=="string"&&Object.values(ce).includes(r.type)&&typeof r.parameter=="string"&&"values"in r}import{createContext as we,useContext as Ue}from"react";var ke=we(null);ke.displayName="FusedWidgetBridgeContext";function v(){let e=Ue(ke);if(!e)throw new Error("useFusedWidgetBridge: no FusedWidgetBridgeContext provider in the tree. Wrap your components with the workbench's <JsonUiProvider> or the test harness's <FusedWidgetBridgeContext.Provider value={createTestBridge()}>.");return e}var Ce=we(null);Ce.displayName="JsonUiNodeOverrideContext";function W(){let e=v(),r=Ue(Ce);return r?{udfUniqueId:r.udfUniqueId??e.node.udfUniqueId,udfName:r.udfName??e.node.udfName,configHash:r.configHash??e.node.configHash}:{udfUniqueId:e.node.udfUniqueId,udfName:e.node.udfName,configHash:e.node.configHash}}import{createContext as ar,useCallback as Pe,useContext as lr,useRef as Ae,useSyncExternalStore as cr}from"react";function hn(){let e=new Map,r=new Set,n=t=>{r.forEach(o=>{o.names.has(t)&&o.cb()})};return{get(t){return e.get(t)},getSnapshot(t){let o={};for(let i of t)e.has(i)&&(o[i]=e.get(i));return o},getAll(){let t={};return e.forEach((o,i)=>{t[i]=o}),t},setField(t,o){e.has(t)&&Object.is(e.get(t),o)||(e.set(t,o),n(t))},removeField(t){e.has(t)&&(e.delete(t),n(t))},subscribe(t,o){let i={names:new Set(t),cb:o};return r.add(i),()=>{r.delete(i)}}}}var Oe=ar({store:null,isInForm:!1});Oe.displayName="JsonUiFormContext";function de(){return lr(Oe)}var Ee=Object.freeze({});function J(e){let{store:r,isInForm:n}=de(),t=fr(e),o=Pe(c=>r?r.subscribe(t,c):()=>{},[r,t]),i=Ae(Ee),u=Pe(()=>{if(!r)return Ee;let c=r.getSnapshot(t),a=i.current;return dr(a,c)?a:(i.current=c,c)},[r,t]),s=cr(o,u,u);return{inForm:n,values:s}}function dr(e,r){if(e===r)return!0;let n=Object.keys(e),t=Object.keys(r);if(n.length!==t.length)return!1;for(let o of n)if(!Object.is(e[o],r[o]))return!1;return!0}function fr(e){let r=Ae(e),n=r.current;return n!==e&&(n.length!==e.length||n.some((t,o)=>t!==e[o]))&&(r.current=e),r.current}function gr(e){return e}function pr(e){return e}import{useCallback as ee,useEffect as z,useMemo as re,useRef as V,useState as mr}from"react";function fe({param:e,debounceMs:r=300,readOnly:n=!1,defaultValue:t,broadcastDefaultValue:o=!0,validate:i,preprocess:u}){let s=v(),{configHash:c}=W(),a=!!e,x=(f,C)=>{if(!e)return;if(f==="Cleared"){s.log.log(`Cleared param "${e}"`,"info",c);return}let l=JSON.stringify(C),y=l&&l.length>100?l.slice(0,100)+"\u2026":l;s.log.log(`${f} param "${e}" = ${y}`,"info",c)},m=re(()=>u??yr(t),[u]),w=re(()=>i??Sr(t),[i]),d=re(()=>e?[e]:[],[e]),{inForm:b,values:S}=J(d),q=e?S[e]:void 0,M=re(()=>{if(!(!a||!e))return s.params.getSnapshot(e)},[s,a,e]),h=()=>{let f=b&&q!==void 0?q:M;if(f==null)return t;let C=m(f);return w(C)?C:t},[L,U]=mr(h),k=V(L);k.current=L;let g=V(null),O=V(!1),N=V(!1),Q=V({enabled:a,param:e});Q.current={enabled:a,param:e};let T=V(s);T.current=s;let F=V(e);z(()=>{let f=F.current;F.current=e,f&&f!==e&&s.params.clear(f)},[s,e]),z(()=>{if(!a||!e||O.current)return;let f=b&&q!==void 0?q:s.params.getSnapshot(e);if(f==null)return;let C=m(f);C!==k.current&&w(C)&&(U(C),x("Received",f))},[s,e,a,b,q,m,w]),z(()=>!a||!e?void 0:s.params.subscribe(e,()=>{if(O.current)return;let C=s.params.getSnapshot(e);if(C==null)return;let l=m(C);l!==k.current&&w(l)&&(U(l),x("Received",C))}),[s,e,a,m,w]);let A=ee(f=>{!a||!e||(s.params.set(e,f,"param"),s.edges.stopLoading(),x("Broadcast",f))},[s,a,e]),j=ee(()=>{!a||n||(g.current&&(clearTimeout(g.current),g.current=null),s.edges.startLoading(),A(k.current),O.current=!1)},[s,A,a,n]),E=ee(f=>{g.current&&(clearTimeout(g.current),g.current=null),U(f),k.current=f,O.current=!1,!(!a||!e||n)&&(s.params.clear(e),s.edges.stopLoading(),x("Cleared",null))},[s,a,e,n]);z(()=>{N.current=!1},[e,a]),z(()=>{if(!a||!e||n||!o||N.current)return;let f=s.params.getSnapshot(e);if(f!=null){N.current=!0;return}if(k.current===""){N.current=!0;return}A(k.current),N.current=!0},[s,a,e,n,o,A]);let B=ee(f=>{U(f),!(!a||n)&&(O.current=!0,s.edges.startLoading(),g.current&&clearTimeout(g.current),g.current=setTimeout(()=>{A(f),O.current=!1},r))},[s,A,r,a,n]);return z(()=>()=>{g.current&&clearTimeout(g.current);let{enabled:f,param:C}=Q.current;!f||!C||T.current.params.clear(C)},[]),{value:L,setValue:B,broadcastNow:j,clearValue:E}}function Sr(e){return typeof e=="string"?(r=>typeof r=="string"):typeof e=="number"?(r=>typeof r=="number"):typeof e=="boolean"?(r=>typeof r=="boolean"):Array.isArray(e)?(r=>Array.isArray(r)):e!==null&&typeof e=="object"?(r=>r!==null&&typeof r=="object"&&!Array.isArray(r)):(r=>!0)}function yr(e){return typeof e=="string"?r=>typeof r=="string"?r:Array.isArray(r)?r.join(","):r!==null&&typeof r=="object"?JSON.stringify(r):String(r):r=>r}import{useEffect as Le}from"react";function Rr(e){let r=de(),n=e.param,t=!!(r.isInForm&&n),{value:o,setValue:i,broadcastNow:u,clearValue:s}=fe({...e,param:t?void 0:e.param}),c=r.store;return Le(()=>{!t||!n||!c||c.setField(n,o)},[o,t,n,c]),Le(()=>{if(!(!t||!n||!c))return()=>{c.removeField(n)}},[t,n,c]),{value:o,setValue:i,broadcastNow:u,clearValue:s,isInForm:r.isInForm}}import{useCallback as Fe,useRef as De,useSyncExternalStore as vr}from"react";var Te=Object.freeze({});function Y(e){let r=v(),n=xr(e),t=Fe(u=>n.length===0?()=>{}:r.params.subscribeMany(n,u),[r,n]),o=De(Te),i=Fe(()=>{if(n.length===0)return Te;let u=r.params.getSnapshotMany(n),s=o.current;return br(s,u)?s:(o.current=u,u)},[r,n]);return vr(t,i,i)}function br(e,r){if(e===r)return!0;let n=Object.keys(e),t=Object.keys(r);if(n.length!==t.length)return!1;for(let o of n)if(!Object.is(e[o],r[o]))return!1;return!0}function xr(e){let r=De(e),n=r.current;return n!==e&&(n.length!==e.length||n.some((t,o)=>t!==e[o]))&&(r.current=e),r.current}import{useCallback as ge,useRef as hr,useSyncExternalStore as wr}from"react";function Ur(){let e=v(),r=ge(u=>e.routing.subscribeAllowedSources(u),[e]),n=hr(null),t=ge(()=>{let u=e.routing.getAllowedSources(),s=n.current;return kr(s,u)?s:(n.current=u,u)},[e]),o=wr(r,t,t),i=ge((u,s)=>!o||o.length===0||!u&&!s?!0:o.some(c=>c.udfUniqueId&&c.udfUniqueId===u||c.udfName&&c.udfName===s),[o]);return{allowedSources:o,isAllowedSource:i}}function kr(e,r){return e===r?!0:e===null||r===null||e.length!==r.length?!1:e.every((n,t)=>n.udfUniqueId===r[t].udfUniqueId&&n.udfName===r[t].udfName)}import{useCallback as Ne,useRef as Cr,useSyncExternalStore as Pr}from"react";function Er(){let e=v(),r=Ne(o=>e.routing.subscribeAllowedSources(o),[e]),n=Cr(null),t=Ne(()=>{let o=e.routing.getAllowedUdfNames(),i=n.current;return Ar(i,o)?i:(n.current=o,o)},[e]);return Pr(r,t,t)}function Ar(e,r){if(e===r)return!0;if(e===null||r===null||e.size!==r.size)return!1;for(let n of e)if(!r.has(n))return!1;return!0}import{useEffect as Or,useMemo as $,useRef as Lr,useState as qe,useSyncExternalStore as Fr,useCallback as Me}from"react";var Ie=/\$([a-zA-Z_][a-zA-Z0-9_]*)/g,Tr=/\{\{[\s\S]*?\}\}/,Be=/\{\{\s*([A-Za-z_][A-Za-z0-9_]*)\b/g;function pe(e,r={}){let n=e??"",t=r.preserveMissingParams??!1,o=v(),i=$(()=>Dr(n),[n]),u=Y(i),{inForm:s,values:c}=J(i),a=$(()=>s?{...u,...c}:u,[u,c,s]),x=$(()=>Tr.test(n),[n]),m=$(()=>x?"":Nr(n,a,t),[n,a,t,x]),w=$(()=>{if(!x)return[];let U=new Set,k=[];Be.lastIndex=0;let g;for(;(g=Be.exec(n))!==null;)U.has(g[1])||(U.add(g[1]),k.push(g[1]));return k},[n,x]),d=Mr(o,w),[b,S]=qe(()=>({key:"",value:""})),[q,M]=qe(!1),h=$(()=>JSON.stringify({template:n,paramValues:a,preserveMissingParams:t,tick:d}),[n,a,t,d]);return Or(()=>{if(!x){M(!1);return}let U=!1,k=new AbortController;return M(!0),o.template.render(n,a,{preserveMissingParams:t,signal:k.signal}).then(g=>{U||(S(O=>O.key===h&&O.value===g.value?O:{key:h,value:g.value}),M(g.loading))},g=>{U||g?.name!=="AbortError"&&M(!1)}),()=>{U=!0,k.abort()}},[o,x,n,a,t,h]),{value:$(()=>{if(!x)return m;if(b.key===h)return b.value;try{return o.template.renderLoading(n,a,{preserveMissingParams:t})}catch{return n}},[o,x,m,b,h,n,a,t]),loading:x?q:!1}}function Dr(e){let r=[],n=new Set;for(let t of e.matchAll(Ie)){let o=t[1];n.has(o)||(n.add(o),r.push(o))}return r}function Nr(e,r,n){return e.replace(Ie,(t,o)=>{let i=r[o];return i==null?n?t:"":qr(i)})}function qr(e){return e==null?"":typeof e=="string"?e:typeof e=="number"||typeof e=="boolean"?String(e):JSON.stringify(e)??""}function Mr(e,r){let n=Lr(0),t=r.slice().sort().join("|"),o=Me(u=>{let s=()=>{n.current+=1,u()},c=[e.template.subscribe(s)];for(let a of r)c.push(e.udfs.subscribeOutput(a,s));return()=>c.forEach(a=>a())},[e,t]),i=Me(()=>n.current,[]);return Fr(o,i,i)}import{useCallback as ne,useEffect as Br,useMemo as te,useRef as Ir,useState as _e,useSyncExternalStore as _r}from"react";function Qe(e){let r=v(),n=ne(i=>e?r.udfs.subscribeOutput(e,i):()=>{},[r,e]),t=Ir(void 0),o=ne(()=>{if(!e)return;let i=r.udfs.getOutputSnapshot(e),u=t.current;return Qr(u,i)?u:(t.current=i,i)},[r,e]);return _r(n,o,o)}function Ve(){let e=v();return ne(r=>e.udfs.requestReexecute(r),[e])}function Qr(e,r){return e===r?!0:!e||!r?!1:e.data===r.data&&e.isExecutionInProgress===r.isExecutionInProgress&&e.error===r.error&&e.vfsFilename===r.vfsFilename}var $e=/^\{\{(\w+)\.(\w+)(?:\[(\d+)\])?\}\}$/;function oe(e){return!e||typeof e!="string"?!1:$e.test(e)}function me(e){if(!oe(e))return null;let r=e.match($e);if(!r)return null;let[,n,t,o]=r,i=o!==void 0?parseInt(o,10):void 0;return{udfName:n,columnName:t,index:i}}function Vr(e){return!!e&&typeof e=="object"&&typeof e.getRows=="function"}function Se({udfName:e,sampleSize:r=200}){let n=Qe(e),t=Ve(),[o,i]=_e([]),[u,s]=_e([]);Br(()=>{let a=!1,x=n?.data;if(!x||!Vr(x)){i([]),s([]);return}return(async()=>{try{let m=await x.getRows(0,Math.max(0,r));if(a)return;let w=m.map(b=>{let S=b;return S&&typeof S=="object"&&S.properties&&typeof S.properties=="object"?S.properties:b});i(w);let d=Array.from(new Set(w.flatMap(b=>Object.keys(b??{}))));s(d)}catch{if(a)return;i([]),s([])}})(),()=>{a=!0}},[n?.data,r]);let c=ne(()=>{e&&t(e)},[t,e]);return{loading:n?.isExecutionInProgress??!1,errorMessage:n?.error??null,isError:!!n?.error,columns:u,rows:o,requestReexecute:c}}function $r(e,r=200){let n=oe(e),t=te(()=>n?me(e):null,[n,e]),{rows:o,loading:i}=Se({udfName:t?.udfName,sampleSize:r});return{values:te(()=>!t||!t.columnName?[]:o.map(s=>s?.[t.columnName]).filter(s=>s!=null),[o,t]),loading:n?i:!1}}function jr(e,r=200){let n=oe(e),t=te(()=>n?me(e):null,[n,e]),{rows:o,loading:i}=Se({udfName:t?.udfName,sampleSize:r});return{value:te(()=>{if(!t||!t.columnName||t.index===void 0)return null;let s=o[t.index];if(!s)return null;let c=s[t.columnName];return c!==void 0?c:null},[o,t]),loading:n?i:!1}}import{useCallback as nr,useEffect as K,useMemo as D,useRef as nn,useState as I}from"react";import{useCallback as se,useMemo as Wr,useRef as ye,useSyncExternalStore as Jr}from"react";var je=Object.freeze([]);function ie(){let e=v(),{configHash:r}=W(),n=ye(e);n.current=e;let t=ye(r);t.current=r;let o=se((i,u="info")=>{n.current.log.log(i,u,t.current)},[]);return Wr(()=>({log:o}),[o])}function zr(e){let r=v(),n=se(i=>e?r.log.subscribeLogs(e,i):()=>{},[r,e]),t=ye(je),o=se(()=>{if(!e)return je;let i=r.log.getLogsSnapshot(e);return i===t.current?t.current:(t.current=i,i)},[r,e]);return Jr(n,o,o)}function Hr(e){let r=v();return se(()=>{e&&r.log.clearLogs(e)},[r,e])}function ue(){let e=v();return{startLoading:e.edges.startLoading,stopLoading:e.edges.stopLoading}}import{createContext as Kr,useContext as Zr}from"react";var Gr=Object.freeze({}),Re=Kr(Gr);Re.displayName="SqlSourceOverrideContext";function ve(){return Zr(Re)}var be=/\$([a-zA-Z_][a-zA-Z0-9_]*)/g,We=/\{\{(\w+)(?:\?([^}]*))?\}\}/g,xe=/'((?:s3|gs|fd):\/\/[^'\n]+)'/g;function Yr(e){return e==null?"''":typeof e=="number"&&!Number.isNaN(e)?String(e):typeof e=="boolean"?e?"TRUE":"FALSE":`'${String(e).replace(/'/g,"''")}'`}function Xr(e,r){let n=!1;for(let t=0;t<r;t++)if(e[t]==="'"){if(n&&e[t+1]==="'"){t++;continue}n=!n}return n}function Je(e,r){return e.replace(be,(n,t,o)=>{let i=r[t],u=i==null?"":String(i);return Xr(e,o)?u.replace(/'/g,"''"):Yr(i)})}function ze(e){let r=new Set,n=[];be.lastIndex=0;for(let t of e.matchAll(be)){let o=t[1];r.has(o)||(r.add(o),n.push(o))}return n}function en(e){if(!e)return null;let r={},n=!1;for(let t of e.split(/[&,]/)){if(!t)continue;let o=t.indexOf("=");if(o===-1)continue;let i=t.slice(0,o),u=t.slice(o+1),s,c;try{s=decodeURIComponent(i),c=decodeURIComponent(u)}catch{s=i,c=u}s&&(r[s]=c,n=!0)}return n?r:null}function He(e){let r=[],n;for(We.lastIndex=0;(n=We.exec(e))!==null;){let[t,o,i]=n;r.push({match:t,name:o,overrides:en(i),start:n.index,end:n.index+t.length})}return r}function Ke(e){if(!e)return[];let r=new Set,n=[];xe.lastIndex=0;let t;for(;(t=xe.exec(e))!==null;){let o=t[1];r.has(o)||(r.add(o),n.push(o))}return n}function Ze(e,r){return e&&e.replace(xe,(n,t)=>{let o=r[t];return o?`'${o}'`:n})}var Ge=/^\$([a-zA-Z_][a-zA-Z0-9_]*)$/;function Ye(e){let r=e.trim(),n=Ge.exec(r);return n?n[1]:null}function Xe(e,r){let n=Ge.exec(e);if(!n)return{value:e,unresolved:!1};let t=n[1];if(!(t in r))return{value:e,unresolved:!0};let o=r[t];return o==null?{value:"",unresolved:!1}:{value:String(o),unresolved:!1}}function rn(e){return Object.keys(e).sort().map(r=>`${r}=${e[r]}`).join("&")}function er(e,r){return r?`${e}#${rn(r)}`:e}var tr=500,H=Object.freeze([]),X=Object.freeze([]);function rr(e,r){if(/\bLIMIT\b/i.test(e))return e;let n=e.trimEnd();return`${n.endsWith(";")?n.slice(0,-1):n} LIMIT ${r}`}function tn(e){return`"${e.replace(/"/g,'""')}"`}function on(e,r,n,t,o,i){let u=e.match(/^\$([a-zA-Z_][a-zA-Z0-9_]*)$/);if(u){let c=o[u[1]];return c==null?"":rr(String(c),i)}let s=e;for(let c=r.length-1;c>=0;c--){let{raw:a,key:x,resolvedOverrides:m}=r[c],d=m===null&&t?t[a.name]:void 0,b;d?b=tn(d.relationName):b=`'${n.get(x)??`${a.name}.parquet`}'`,s=s.slice(0,a.start)+b+s.slice(a.end)}return s=rr(s,i),Je(s,o)}function or({sql:e,enabled:r=!0,maxRows:n=tr,sourceOverrides:t}){let o=v(),i=ve(),u=D(()=>{let l=Object.keys(i).length>0;return t?l?{...i,...t}:t:l?i:void 0},[i,t]),{startLoading:s,stopLoading:c}=ue(),{log:a}=ie(),[x,m]=I(""),[w,d]=I(!1),[b,S]=I(null),[q,M]=I(0),h=D(()=>e?He(e):[],[e]),L=D(()=>u?h.filter(l=>l.overrides!==null||!u[l.name]):h,[h,u]),U=D(()=>{let l=new Set;for(let y of L)if(y.overrides)for(let R of Object.values(y.overrides)){let _=Ye(R);_&&l.add(_)}return Array.from(l)},[L]),k=D(()=>e?ze(e):[],[e]),g=D(()=>{let l=new Set,y=[];for(let R of k)l.has(R)||(l.add(R),y.push(R));for(let R of U)l.has(R)||(l.add(R),y.push(R));return y},[k,U]),O=Y(g),{inForm:N,values:Q}=J(g),T=D(()=>N?{...O,...Q}:O,[N,O,Q]),F=D(()=>L.map(l=>{if(!l.overrides)return{raw:l,key:l.name,resolvedOverrides:null,unresolved:!1};let y={},R=!1;for(let[_,Z]of Object.entries(l.overrides)){let p=Xe(Z,T);p.unresolved&&(R=!0),y[_]=p.value}return{raw:l,key:er(l.name,y),resolvedOverrides:y,unresolved:R}}),[L,T]),A=D(()=>{let l=new Set,y=[];for(let R of F)R.unresolved||l.has(R.key)||(l.add(R.key),y.push({name:R.raw.name,key:R.key,overrides:R.resolvedOverrides??void 0}));return y},[F]),j=D(()=>A.map(l=>`${l.key}|${l.name}|${l.overrides?Object.entries(l.overrides).sort(([y],[R])=>y.localeCompare(R)).map(([y,R])=>`${y}=${R}`).join(","):""}`).join(`
|
|
2
|
-
`),[
|
|
1
|
+
var Cn="parameter-updates",fe=(o=>(o.PARAM="param",o.RANGE="range",o.VIEWPORT="viewport",o.CLEAR="clear",o))(fe||{});function Pn(e){if(typeof e!="object"||e===null)return!1;let r=e;return typeof r.type=="string"&&Object.values(fe).includes(r.type)&&typeof r.parameter=="string"&&"values"in r}import{createContext as Te,useContext as Ne}from"react";var De=Te(null);De.displayName="FusedWidgetBridgeContext";function R(){let e=Ne(De);if(!e)throw new Error("useFusedWidgetBridge: no FusedWidgetBridgeContext provider in the tree. Wrap your components with the workbench's <JsonUiProvider> or the test harness's <FusedWidgetBridgeContext.Provider value={createTestBridge()}>.");return e}var qe=Te(null);qe.displayName="JsonUiNodeOverrideContext";function z(){let e=R(),r=Ne(qe);return r?{udfUniqueId:r.udfUniqueId??e.node.udfUniqueId,udfName:r.udfName??e.node.udfName,configHash:r.configHash??e.node.configHash}:{udfUniqueId:e.node.udfUniqueId,udfName:e.node.udfName,configHash:e.node.configHash}}import{createContext as yr,useCallback as Be,useContext as xr,useRef as Ie,useSyncExternalStore as Rr}from"react";function Tn(){let e=new Map,r=new Set,n=t=>{r.forEach(o=>{o.names.has(t)&&o.cb()})};return{get(t){return e.get(t)},getSnapshot(t){let o={};for(let s of t)e.has(s)&&(o[s]=e.get(s));return o},getAll(){let t={};return e.forEach((o,s)=>{t[s]=o}),t},setField(t,o){e.has(t)&&Object.is(e.get(t),o)||(e.set(t,o),n(t))},removeField(t){e.has(t)&&(e.delete(t),n(t))},subscribe(t,o){let s={names:new Set(t),cb:o};return r.add(s),()=>{r.delete(s)}}}}var _e=yr({store:null,isInForm:!1});_e.displayName="JsonUiFormContext";function ge(){return xr(_e)}var Me=Object.freeze({});function J(e){let{store:r,isInForm:n}=ge(),t=br(e),o=Be(c=>r?r.subscribe(t,c):()=>{},[r,t]),s=Ie(Me),u=Be(()=>{if(!r)return Me;let c=r.getSnapshot(t),l=s.current;return vr(l,c)?l:(s.current=c,c)},[r,t]),i=Rr(o,u,u);return{inForm:n,values:i}}function vr(e,r){if(e===r)return!0;let n=Object.keys(e),t=Object.keys(r);if(n.length!==t.length)return!1;for(let o of n)if(!Object.is(e[o],r[o]))return!1;return!0}function br(e){let r=Ie(e),n=r.current;return n!==e&&(n.length!==e.length||n.some((t,o)=>t!==e[o]))&&(r.current=e),r.current}function Ur(e){return e}function hr(e){return e}import{useCallback as ee,useEffect as H,useMemo as re,useRef as $,useState as wr}from"react";function pe({param:e,debounceMs:r=300,readOnly:n=!1,defaultValue:t,broadcastDefaultValue:o=!0,validate:s,preprocess:u}){let i=R(),{configHash:c}=z(),l=!!e,v=(g,m)=>{if(!e)return;if(g==="Cleared"){i.log.log(`Cleared param "${e}"`,"info",c);return}let a=JSON.stringify(m),S=a&&a.length>100?a.slice(0,100)+"\u2026":a;i.log.log(`${g} param "${e}" = ${S}`,"info",c)},b=re(()=>u??Er(t),[u]),k=re(()=>s??kr(t),[s]),f=re(()=>e?[e]:[],[e]),{inForm:p,values:y}=J(f),O=e?y[e]:void 0,T=re(()=>{if(!(!l||!e))return i.params.getSnapshot(e)},[i,l,e]),E=()=>{let g=p&&O!==void 0?O:T;if(g==null)return t;let m=b(g);return k(m)?m:t},[q,C]=wr(E),w=$(q);w.current=q;let d=$(null),h=$(!1),D=$(!1),P=$({enabled:l,param:e});P.current={enabled:l,param:e};let _=$(i);_.current=i;let A=$(e);H(()=>{let g=A.current;A.current=e,g&&g!==e&&i.params.clear(g)},[i,e]),H(()=>{if(!l||!e||h.current)return;let g=p&&O!==void 0?O:i.params.getSnapshot(e);if(g==null)return;let m=b(g);m!==w.current&&k(m)&&(C(m),v("Received",g))},[i,e,l,p,O,b,k]),H(()=>!l||!e?void 0:i.params.subscribe(e,()=>{if(h.current)return;let m=i.params.getSnapshot(e);if(m==null)return;let a=b(m);a!==w.current&&k(a)&&(C(a),v("Received",m))}),[i,e,l,b,k]);let L=ee(g=>{!l||!e||(i.params.set(e,g,"param"),i.edges.stopLoading(),v("Broadcast",g))},[i,l,e]),I=ee(()=>{!l||n||(d.current&&(clearTimeout(d.current),d.current=null),i.edges.startLoading(),L(w.current),h.current=!1)},[i,L,l,n]),N=ee(g=>{d.current&&(clearTimeout(d.current),d.current=null),C(g),w.current=g,h.current=!1,!(!l||!e||n)&&(i.params.clear(e),i.edges.stopLoading(),v("Cleared",null))},[i,l,e,n]);H(()=>{D.current=!1},[e,l]),H(()=>{if(!l||!e||n||!o||D.current)return;let g=i.params.getSnapshot(e);if(g!=null){D.current=!0;return}if(w.current===""){D.current=!0;return}L(w.current),D.current=!0},[i,l,e,n,o,L]);let B=ee(g=>{C(g),!(!l||n)&&(h.current=!0,i.edges.startLoading(),d.current&&clearTimeout(d.current),d.current=setTimeout(()=>{L(g),h.current=!1},r))},[i,L,r,l,n]);return H(()=>()=>{d.current&&clearTimeout(d.current);let{enabled:g,param:m}=P.current;!g||!m||_.current.params.clear(m)},[]),{value:q,setValue:B,broadcastNow:I,clearValue:N}}function kr(e){return typeof e=="string"?(r=>typeof r=="string"):typeof e=="number"?(r=>typeof r=="number"):typeof e=="boolean"?(r=>typeof r=="boolean"):Array.isArray(e)?(r=>Array.isArray(r)):e!==null&&typeof e=="object"?(r=>r!==null&&typeof r=="object"&&!Array.isArray(r)):(r=>!0)}function Er(e){return typeof e=="string"?r=>typeof r=="string"?r:Array.isArray(r)?r.join(","):r!==null&&typeof r=="object"?JSON.stringify(r):String(r):r=>r}import{useEffect as Ve}from"react";function Cr(e){let r=ge(),n=e.param,t=!!(r.isInForm&&n),{value:o,setValue:s,broadcastNow:u,clearValue:i}=pe({...e,param:t?void 0:e.param}),c=r.store;return Ve(()=>{!t||!n||!c||c.setField(n,o)},[o,t,n,c]),Ve(()=>{if(!(!t||!n||!c))return()=>{c.removeField(n)}},[t,n,c]),{value:o,setValue:s,broadcastNow:u,clearValue:i,isInForm:r.isInForm}}import{useCallback as Qe,useRef as $e,useSyncExternalStore as Pr}from"react";var Je=Object.freeze({});function j(e){let r=R(),n=Ar(e),t=Qe(u=>n.length===0?()=>{}:r.params.subscribeMany(n,u),[r,n]),o=$e(Je),s=Qe(()=>{if(n.length===0)return Je;let u=r.params.getSnapshotMany(n),i=o.current;return Or(i,u)?i:(o.current=u,u)},[r,n]);return Pr(t,s,s)}function Or(e,r){if(e===r)return!0;let n=Object.keys(e),t=Object.keys(r);if(n.length!==t.length)return!1;for(let o of n)if(!Object.is(e[o],r[o]))return!1;return!0}function Ar(e){let r=$e(e),n=r.current;return n!==e&&(n.length!==e.length||n.some((t,o)=>t!==e[o]))&&(r.current=e),r.current}import{useCallback as me,useRef as Lr,useSyncExternalStore as Fr}from"react";function Tr(){let e=R(),r=me(u=>e.routing.subscribeAllowedSources(u),[e]),n=Lr(null),t=me(()=>{let u=e.routing.getAllowedSources(),i=n.current;return Nr(i,u)?i:(n.current=u,u)},[e]),o=Fr(r,t,t),s=me((u,i)=>!o||o.length===0||!u&&!i?!0:o.some(c=>c.udfUniqueId&&c.udfUniqueId===u||c.udfName&&c.udfName===i),[o]);return{allowedSources:o,isAllowedSource:s}}function Nr(e,r){return e===r?!0:e===null||r===null||e.length!==r.length?!1:e.every((n,t)=>n.udfUniqueId===r[t].udfUniqueId&&n.udfName===r[t].udfName)}import{useCallback as je,useRef as Dr,useSyncExternalStore as qr}from"react";function Se(){let e=R(),r=je(o=>e.routing.subscribeAllowedSources(o),[e]),n=Dr(null),t=je(()=>{let o=e.routing.getAllowedUdfNames(),s=n.current;return Br(s,o)?s:(n.current=o,o)},[e]);return qr(r,t,t)}function Br(e,r){if(e===r)return!0;if(e===null||r===null||e.size!==r.size)return!1;for(let n of e)if(!r.has(n))return!1;return!0}import{useEffect as Mr,useMemo as W,useRef as Ir,useState as We,useSyncExternalStore as _r,useCallback as ze}from"react";var Ke=/\$([a-zA-Z_][a-zA-Z0-9_]*)/g,Vr=/\{\{[\s\S]*?\}\}/,He=/\{\{\s*([A-Za-z_][A-Za-z0-9_]*)\b/g;function ye(e,r={}){let n=e??"",t=r.preserveMissingParams??!1,o=R(),s=W(()=>Qr(n),[n]),u=j(s),{inForm:i,values:c}=J(s),l=W(()=>i?{...u,...c}:u,[u,c,i]),v=W(()=>Vr.test(n),[n]),b=W(()=>v?"":Jr(n,l,t),[n,l,t,v]),k=W(()=>{if(!v)return[];let C=new Set,w=[];He.lastIndex=0;let d;for(;(d=He.exec(n))!==null;)C.has(d[1])||(C.add(d[1]),w.push(d[1]));return w},[n,v]),f=jr(o,k),[p,y]=We(()=>({key:"",value:""})),[O,T]=We(!1),E=W(()=>JSON.stringify({template:n,paramValues:l,preserveMissingParams:t,tick:f}),[n,l,t,f]);return Mr(()=>{if(!v){T(!1);return}let C=!1,w=new AbortController;return T(!0),o.template.render(n,l,{preserveMissingParams:t,signal:w.signal}).then(d=>{C||(y(h=>h.key===E&&h.value===d.value?h:{key:E,value:d.value}),T(d.loading))},d=>{C||d?.name!=="AbortError"&&T(!1)}),()=>{C=!0,w.abort()}},[o,v,n,l,t,E]),{value:W(()=>{if(!v)return b;if(p.key===E)return p.value;try{return o.template.renderLoading(n,l,{preserveMissingParams:t})}catch{return n}},[o,v,b,p,E,n,l,t]),loading:v?O:!1}}function Qr(e){let r=[],n=new Set;for(let t of e.matchAll(Ke)){let o=t[1];n.has(o)||(n.add(o),r.push(o))}return r}function Jr(e,r,n){return e.replace(Ke,(t,o)=>{let s=r[o];return s==null?n?t:"":$r(s)})}function $r(e){return e==null?"":typeof e=="string"?e:typeof e=="number"||typeof e=="boolean"?String(e):JSON.stringify(e)??""}function jr(e,r){let n=Ir(0),t=r.slice().sort().join("|"),o=ze(u=>{let i=()=>{n.current+=1,u()},c=[e.template.subscribe(i)];for(let l of r)c.push(e.udfs.subscribeOutput(l,i));return()=>c.forEach(l=>l())},[e,t]),s=ze(()=>n.current,[]);return _r(o,s,s)}import{useCallback as ne,useEffect as Wr,useMemo as te,useRef as zr,useState as Ze,useSyncExternalStore as Hr}from"react";function Ge(e){let r=R(),n=ne(s=>e?r.udfs.subscribeOutput(e,s):()=>{},[r,e]),t=zr(void 0),o=ne(()=>{if(!e)return;let s=r.udfs.getOutputSnapshot(e),u=t.current;return Kr(u,s)?u:(t.current=s,s)},[r,e]);return Hr(n,o,o)}function Xe(){let e=R();return ne(r=>e.udfs.requestReexecute(r),[e])}function Kr(e,r){return e===r?!0:!e||!r?!1:e.data===r.data&&e.isExecutionInProgress===r.isExecutionInProgress&&e.error===r.error&&e.vfsFilename===r.vfsFilename}var Ye=/^\{\{(\w+)\.(\w+)(?:\[(\d+)\])?\}\}$/;function oe(e){return!e||typeof e!="string"?!1:Ye.test(e)}function xe(e){if(!oe(e))return null;let r=e.match(Ye);if(!r)return null;let[,n,t,o]=r,s=o!==void 0?parseInt(o,10):void 0;return{udfName:n,columnName:t,index:s}}function Zr(e){return!!e&&typeof e=="object"&&typeof e.getRows=="function"}function Re({udfName:e,sampleSize:r=200}){let n=Ge(e),t=Xe(),[o,s]=Ze([]),[u,i]=Ze([]);Wr(()=>{let l=!1,v=n?.data;if(!v||!Zr(v)){s([]),i([]);return}return(async()=>{try{let b=await v.getRows(0,Math.max(0,r));if(l)return;let k=b.map(p=>{let y=p;return y&&typeof y=="object"&&y.properties&&typeof y.properties=="object"?y.properties:p});s(k);let f=Array.from(new Set(k.flatMap(p=>Object.keys(p??{}))));i(f)}catch{if(l)return;s([]),i([])}})(),()=>{l=!0}},[n?.data,r]);let c=ne(()=>{e&&t(e)},[t,e]);return{loading:n?.isExecutionInProgress??!1,errorMessage:n?.error??null,isError:!!n?.error,columns:u,rows:o,requestReexecute:c}}function Gr(e,r=200){let n=oe(e),t=te(()=>n?xe(e):null,[n,e]),{rows:o,loading:s}=Re({udfName:t?.udfName,sampleSize:r});return{values:te(()=>!t||!t.columnName?[]:o.map(i=>i?.[t.columnName]).filter(i=>i!=null),[o,t]),loading:n?s:!1}}function Xr(e,r=200){let n=oe(e),t=te(()=>n?xe(e):null,[n,e]),{rows:o,loading:s}=Re({udfName:t?.udfName,sampleSize:r});return{value:te(()=>{if(!t||!t.columnName||t.index===void 0)return null;let i=o[t.index];if(!i)return null;let c=i[t.columnName];return c!==void 0?c:null},[o,t]),loading:n?s:!1}}import{useCallback as cr,useEffect as Z,useMemo as M,useRef as fn,useState as V}from"react";import{createContext as Yr,useContext as en}from"react";var ve=Yr({});ve.displayName="JsonUiBindingContext";function be(){return en(ve)}import{useCallback as se,useMemo as rn,useRef as Ue,useSyncExternalStore as nn}from"react";var er=Object.freeze([]);function ie(){let e=R(),{configHash:r}=z(),n=Ue(e);n.current=e;let t=Ue(r);t.current=r;let o=se((s,u="info")=>{n.current.log.log(s,u,t.current)},[]);return rn(()=>({log:o}),[o])}function tn(e){let r=R(),n=se(s=>e?r.log.subscribeLogs(e,s):()=>{},[r,e]),t=Ue(er),o=se(()=>{if(!e)return er;let s=r.log.getLogsSnapshot(e);return s===t.current?t.current:(t.current=s,s)},[r,e]);return nn(n,o,o)}function on(e){let r=R();return se(()=>{e&&r.log.clearLogs(e)},[r,e])}function ue(){let e=R();return{startLoading:e.edges.startLoading,stopLoading:e.edges.stopLoading}}import{createContext as sn,useContext as un}from"react";var an=Object.freeze({}),he=sn(an);he.displayName="SqlSourceOverrideContext";function we(){return un(he)}var ke=/\$([a-zA-Z_][a-zA-Z0-9_]*)/g,rr=/\{\{(\w+)(?:\?([^}]*))?\}\}/g,Ee=/'((?:s3|gs|fd):\/\/[^'\n]+)'/g;function ln(e){return e==null?"''":typeof e=="number"&&!Number.isNaN(e)?String(e):typeof e=="boolean"?e?"TRUE":"FALSE":`'${String(e).replace(/'/g,"''")}'`}function cn(e,r){let n=!1;for(let t=0;t<r;t++)if(e[t]==="'"){if(n&&e[t+1]==="'"){t++;continue}n=!n}return n}function nr(e,r){return e.replace(ke,(n,t,o)=>{let s=r[t],u=s==null?"":String(s);return cn(e,o)?u.replace(/'/g,"''"):ln(s)})}function tr(e){let r=new Set,n=[];ke.lastIndex=0;for(let t of e.matchAll(ke)){let o=t[1];r.has(o)||(r.add(o),n.push(o))}return n}function Ce(e){if(!e)return null;let r={},n=!1;for(let t of e.split(/[&,]/)){if(!t)continue;let o=t.indexOf("=");if(o===-1)continue;let s=t.slice(0,o),u=t.slice(o+1),i,c;try{i=decodeURIComponent(s),c=decodeURIComponent(u)}catch{i=s,c=u}i&&(r[i]=c,n=!0)}return n?r:null}function or(e){let r=[],n;for(rr.lastIndex=0;(n=rr.exec(e))!==null;){let[t,o,s]=n;r.push({match:t,name:o,overrides:Ce(s),start:n.index,end:n.index+t.length})}return r}function sr(e){if(!e)return[];let r=new Set,n=[];Ee.lastIndex=0;let t;for(;(t=Ee.exec(e))!==null;){let o=t[1];r.has(o)||(r.add(o),n.push(o))}return n}function ir(e,r){return e&&e.replace(Ee,(n,t)=>{let o=r[t];return o?`'${o}'`:n})}var ur=/^\$([a-zA-Z_][a-zA-Z0-9_]*)$/;function ae(e){let r=e.trim(),n=ur.exec(r);return n?n[1]:null}function le(e,r){let n=ur.exec(e);if(!n)return{value:e,unresolved:!1};let t=n[1];if(!(t in r))return{value:e,unresolved:!0};let o=r[t];return o==null?{value:"",unresolved:!1}:{value:String(o),unresolved:!1}}function dn(e){return Object.keys(e).sort().map(r=>`${r}=${e[r]}`).join("&")}function ar(e,r){return r?`${e}#${dn(r)}`:e}var dr=500,K=Object.freeze([]),Y=Object.freeze([]);function lr(e,r){if(/\bLIMIT\b/i.test(e))return e;let n=e.trimEnd();return`${n.endsWith(";")?n.slice(0,-1):n} LIMIT ${r}`}function gn(e){return`"${e.replace(/"/g,'""')}"`}function pn(e,r,n,t,o,s){let u=e.match(/^\$([a-zA-Z_][a-zA-Z0-9_]*)$/);if(u){let c=o[u[1]];return c==null?"":lr(String(c),s)}let i=e;for(let c=r.length-1;c>=0;c--){let{raw:l,key:v,resolvedOverrides:b}=r[c],f=b===null&&t?t[l.name]:void 0,p;f?p=gn(f.relationName):p=`'${n.get(v)??`${l.name}.parquet`}'`,i=i.slice(0,l.start)+p+i.slice(l.end)}return i=lr(i,s),nr(i,o)}function fr({sql:e,enabled:r=!0,maxRows:n=dr,sourceOverrides:t}){let o=R(),s=we(),u=M(()=>{let a=Object.keys(s).length>0;return t?a?{...s,...t}:t:a?s:void 0},[s,t]),{startLoading:i,stopLoading:c}=ue(),{log:l}=ie(),[v,b]=V(""),[k,f]=V(!1),[p,y]=V(null),[O,T]=V(0),E=M(()=>e?or(e):[],[e]),q=M(()=>u?E.filter(a=>a.overrides!==null||!u[a.name]):E,[E,u]),C=M(()=>{let a=new Set;for(let S of q)if(S.overrides)for(let U of Object.values(S.overrides)){let Q=ae(U);Q&&a.add(Q)}return Array.from(a)},[q]),w=M(()=>e?tr(e):[],[e]),d=M(()=>{let a=new Set,S=[];for(let U of w)a.has(U)||(a.add(U),S.push(U));for(let U of C)a.has(U)||(a.add(U),S.push(U));return S},[w,C]),h=j(d),{inForm:D,values:P}=J(d),_=M(()=>D?{...h,...P}:h,[D,h,P]),A=M(()=>q.map(a=>{if(!a.overrides)return{raw:a,key:a.name,resolvedOverrides:null,unresolved:!1};let S={},U=!1;for(let[Q,G]of Object.entries(a.overrides)){let x=le(G,_);x.unresolved&&(U=!0),S[Q]=x.value}return{raw:a,key:ar(a.name,S),resolvedOverrides:S,unresolved:U}}),[q,_]),L=M(()=>{let a=new Set,S=[];for(let U of A)U.unresolved||a.has(U.key)||(a.add(U.key),S.push({name:U.raw.name,key:U.key,overrides:U.resolvedOverrides??void 0}));return S},[A]),I=M(()=>L.map(a=>`${a.key}|${a.name}|${a.overrides?Object.entries(a.overrides).sort(([S],[U])=>S.localeCompare(U)).map(([S,U])=>`${S}=${U}`).join(","):""}`).join(`
|
|
2
|
+
`),[L]),N=cr(()=>{T(a=>a+1)},[]);Z(()=>{if(!r||L.length===0)return;let a=L.map(S=>o.udfs.subscribeOutput(S.name,()=>{T(U=>U+1)}));return()=>{a.forEach(S=>S())}},[o,r,L]),Z(()=>{k?i():c()},[k,i,c]);let B=M(()=>{if(!u)return null;for(let a of E){if(a.overrides!==null)continue;let S=u[a.name];if(S?.error)return S.error}return null},[E,u]),g=M(()=>u?E.some(a=>a.overrides===null&&u[a.name]?.loading):!1,[E,u]),m=A.some(a=>a.unresolved);return Z(()=>{if(!r||!e){b(""),f(!1),y(null);return}if(B){b(""),y(B),f(!1),l(`SQL preprocessing: ${B}`,"error");return}if(g||m){b(""),y(null),f(!0);return}let a=!1;return f(!0),y(null),(async()=>{let S=new Map,U;if(L.length>0)try{let x=await o.sql.resolveVfsFilenames(L);if(a)return;if(x instanceof Map)for(let F of A){if(F.resolvedOverrides)continue;let X=x.get(F.raw.name);X&&S.set(F.key,X)}else S=x.filenames,U=x.errors}catch(x){if(a)return;let F=x instanceof Error?x.message:typeof x=="string"?x:"VFS registration failed";b(""),y(F),f(!1),l(`SQL preprocessing: ${F}`,"error");return}if(U)for(let x of A){let F=U.get(x.key);if(F){if(a)return;b(""),y(F),f(!1),l(`SQL preprocessing: ${F}`,"error");return}}for(let x of A)if(!(x.unresolved||x.resolvedOverrides===null&&u?.[x.raw.name]!==void 0)&&!S.has(x.key)){if(a)return;b(""),y(null),f(!0);return}let Q;try{Q=pn(e,A,S,u,_,n)}catch(x){if(a)return;let F=x instanceof Error?x.message:typeof x=="string"?x:"SQL preprocessing failed";b(""),y(F),f(!1),l(`SQL preprocessing failed: ${F}`,"error");return}let G=sr(Q);if(G.length===0){if(a)return;b(Q),y(null),f(!1),l("SQL preprocessing completed");return}try{let x={},F=await Promise.all(G.map(de=>o.signUrl(de)));if(a)return;G.forEach((de,Sr)=>{x[de]=F[Sr].signed});let X=ir(Q,x);b(X),y(null),f(!1),l("SQL preprocessing completed")}catch(x){if(a)return;let F=x instanceof Error?x.message:typeof x=="string"?x:"URL signing failed";b(""),y(F),f(!1),l(`SQL preprocessing failed: ${F}`,"error")}})(),()=>{a=!0}},[o,r,e,I,A,_,u,B,g,m,n,O,l,L]),{processedSql:v,loading:k,error:p,refetch:N}}function mn({sql:e,enabled:r=!0,maxRows:n=dr,sourceOverrides:t,queryId:o}){let s=R(),{startLoading:u,stopLoading:i}=ue(),{log:c}=ie(),{queryId:l}=be(),v=o??l,[b,k]=V(K),[f,p]=V(Y),[y,O]=V(!1),[T,E]=V(null),[q,C]=V(0),{processedSql:w,loading:d,error:h,refetch:D}=fr({sql:e,enabled:r,maxRows:n,sourceOverrides:t}),P=fn(""),_=r&&!!e&&!!w&&!d&&!h&&w!==P.current,A=d||y||_;Z(()=>{A?u():i()},[A,u,i]);let L=cr(()=>{P.current="",D(),C(I=>I+1)},[D]);return Z(()=>{if(!r||!e){P.current="",k(K),p(Y),O(!1),E(null);return}if(h){P.current="",E(h),k(K),p(Y),O(!1);return}if(d||!w){P.current="",O(!1);return}let I=!1,N=new AbortController;P.current=w,O(!0),E(null);let B=w.length>120?w.slice(0,120)+"\u2026":w;c(`SQL query started: ${B}`);let g=performance.now();return s.sql.query(w,{signal:N.signal,queryId:v}).then(m=>{if(I)return;let a=Math.round(performance.now()-g);if(m.error){k(K),p(Y),E(m.error),O(!1),c(`SQL failed (${a}ms): ${m.error}`,"error");return}k(m.rows.length===0?K:m.rows),p(m.columns),E(null),O(!1),c(`SQL completed: ${m.rows.length} row${m.rows.length!==1?"s":""} in ${a}ms`)},m=>{if(I||m?.name==="AbortError")return;let a=Math.round(performance.now()-g),S=m instanceof Error?m.message:typeof m=="string"?m:"SQL query failed";E(S),k(K),p(Y),O(!1),c(`SQL failed (${a}ms): ${S}`,"error")}),()=>{I=!0,N.abort()}},[s,r,e,w,d,h,q,c,v]),{rows:b,columns:f,loading:A,error:T,refetch:L}}function Sn(e,r=!0){let n=R(),[t,o]=V({filenames:new Map,loading:!1}),s=M(()=>e.slice().sort().join("|"),[e]);return Z(()=>{if(!r||e.length===0){o({filenames:new Map,loading:!1});return}let u=!1;return o(i=>({...i,loading:!0,error:void 0})),n.sql.resolveVfsFilenames(e).then(i=>{if(u)return;let c=i instanceof Map?i:i.filenames;o({filenames:c,loading:!1})},i=>{u||o({filenames:new Map,loading:!1,error:i instanceof Error?i.message:String(i)})}),()=>{u=!0}},[n,s,r]),t}import{useCallback as gr,useEffect as yn,useState as ce}from"react";var pr=["s3://","gs://","fd://"];function Pe(e){return pr.some(r=>e.startsWith(r))}function xn(){let e=R();return{signUrl:gr(n=>e.signUrl(n),[e])}}function Rn(e){let r=R(),{value:n,loading:t}=ye(e),[o,s]=ce(null),[u,i]=ce(null),[c,l]=ce(!1),[v,b]=ce(0);yn(()=>{if(t)return;if(!n){s(null),i(null),l(!1);return}if(!Pe(n)){s(n),i(null),l(!1);return}let f=!1;return l(!0),i(null),r.signUrl(n).then(({signed:p})=>{f||s(p??n)}).catch(p=>{f||(i(p instanceof Error?p.message:"Failed to load media"),s(null))}).finally(()=>{f||l(!1)}),()=>{f=!0}},[r,t,n,v]);let k=gr(async()=>{if(!n||!Pe(n))return n??null;let{signed:f}=await r.signUrl(n),p=f??n;return s(p),i(null),b(y=>y+1),p},[r,n]);return{src:o,loading:t||c,error:u,refreshSignedUrl:k,resolvedSrc:n,needsSigning:!!(n&&Pe(n))}}import{useEffect as vn,useState as bn}from"react";function Un(e,r){let n=R(),[t,o]=bn({status:"idle"});return vn(()=>{if(!r||!e?.trim()){o({status:"idle"});return}let s=!1;return o({status:"checking"}),n.uploads.checkAccess(e).then(u=>{s||(u.ok?o({status:"allowed"}):o({status:"denied",message:u.message??"Upload access denied."}))}),()=>{s=!0}},[n,e,r]),t}function hn(){return z()}import{useCallback as Ae,useMemo as Le,useRef as mr,useState as Fe}from"react";var wn=/^[a-zA-Z_][a-zA-Z0-9_]*$/;function Oe(e){if(!e)return null;let r=e.trim();if(!r)return null;let n=r.indexOf("?"),t=(n===-1?r:r.slice(0,n)).trim();if(!wn.test(t))return null;let o=n===-1?void 0:r.slice(n+1);return{name:t,overrides:Ce(o)??{}}}function kn(e,r={}){let n=R(),{format:t}=r,o=Le(()=>Oe(e),[e]),s=Le(()=>{if(!o)return[];let d=new Set,h=[];for(let D of Object.values(o.overrides)){let P=ae(D);P&&!d.has(P)&&(d.add(P),h.push(P))}return h},[o]),u=j(s),{inForm:i,values:c}=J(s),l=Le(()=>i?{...u,...c}:u,[u,c,i]),v=Se(),b=mr({parsed:o,paramValues:l,allowedUdfNames:v,format:t});b.current={parsed:o,paramValues:l,allowedUdfNames:v,format:t};let[k,f]=Fe("idle"),[p,y]=Fe(null),[O,T]=Fe(null),E=mr(0),q=Ae(()=>{E.current+=1,f("idle"),y(null),T(null)},[]),C=Ae(d=>(f("error"),T(d),y(null),{data:null,error:d}),[]);return{fire:Ae(async d=>{let{parsed:h,paramValues:D,allowedUdfNames:P,format:_}=b.current;if(!h)return C("No UDF to run (empty or invalid executor)."),null;if(P&&!P.has(h.name))return C(`UDF "${h.name}" is not reachable from this node \u2014 connect it with an edge.`);let A={};for(let[N,B]of Object.entries(h.overrides))A[N]=le(B,D).value;d&&Object.assign(A,d);let L=++E.current,I=()=>L===E.current;f("running"),T(null),n.edges.startLoading();try{let N=await n.udfs.execute(h.name,A,{format:_});return I()&&(N.error?(f("error"),T(N.error),y(null)):(f("success"),y(N.data),T(null))),N}catch(N){let B=N instanceof Error?N.message:"UDF execution failed.";return I()&&(f("error"),T(B),y(null)),{data:null,error:B}}finally{n.edges.stopLoading()}},[n,C]),status:k,isRunning:k==="running",data:p,error:O,canFire:o!==null,reset:q}}function En(e){if(!e)return{};let r={};for(let n of e.split(";")){let t=n.indexOf(":");if(t===-1)continue;let o=n.slice(0,t).trim(),s=n.slice(t+1).trim();if(!o||!s)continue;let u=o.replace(/-([a-z])/g,(i,c)=>c.toUpperCase());r[u]=s}return r}export{_e as FormContext,De as FusedWidgetBridgeContext,ve as JsonUiBindingContext,qe as JsonUiNodeOverrideContext,Cn as PARAMETER_BROADCAST_CHANNEL,fe as ParameterMessageType,Ee as SIGNABLE_URL_LITERAL_REGEX,pr as SIGNED_URL_SCHEMES,ke as SQL_PARAM_REGEX,rr as SQL_SOURCE_PLACEHOLDER_REGEX,he as SqlSourceOverrideContext,dn as canonicalOverrideKey,ar as computePlaceholderKey,Tn as createFormParamsStore,hr as defineCatalog,Ur as defineComponent,ln as escapeSqlValue,sr as extractSignableUrls,tr as extractSqlParams,ae as getDollarRefName,Pn as isStandardMessage,oe as isUdfQuery,Oe as parseExecutor,Ce as parseOverridesString,or as parseSqlUdfPlaceholders,En as parseStyle,xe as parseUdfColumnQuery,le as resolveOverrideValue,ir as rewriteSignedUrls,nr as substituteSqlParams,Tr as useAllowedSources,Se as useAllowedUdfNames,j as useCanvasParams,mn as useDuckDbSqlQuery,fr as useDuckDbSqlQueryPreprocessing,ge as useFormContext,J as useFormParams,pe as useFusedParam,Cr as useFusedParamWithForm,R as useFusedWidgetBridge,be as useJsonUiBinding,ue as useJsonUiEdgeAnimation,ie as useJsonUiLog,on as useJsonUiLogClear,tn as useJsonUiLogs,z as useJsonUiNode,hn as useJsonUiUdfInfo,Rn as useMediaSrc,ye as useParamSubstitution,Xe as useRequestUdfReexecute,we as useSqlSourceOverrides,Xr as useUdfColumnValue,Gr as useUdfColumnValues,Re as useUdfDataFrameSample,kn as useUdfExecutor,Ge as useUdfOutputByName,Un as useUploadAccessCheck,xn as useUrlSigning,Sn as useVfsRegistration};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface JsonUiBinding {
|
|
2
|
+
/** Resolver-stamped query id for this node (e.g. `"q0"`); undefined elsewhere. */
|
|
3
|
+
queryId?: string;
|
|
4
|
+
}
|
|
5
|
+
export declare const JsonUiBindingContext: import("react").Context<JsonUiBinding>;
|
|
6
|
+
/** Read the current node's data-binding identity. `{}` when no provider is present. */
|
|
7
|
+
export declare function useJsonUiBinding(): JsonUiBinding;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.JsonUiBindingContext = void 0;
|
|
4
|
+
exports.useJsonUiBinding = useJsonUiBinding;
|
|
5
|
+
/**
|
|
6
|
+
* JsonUiBindingContext — per-node data-binding identity (chunk-2 MCP-host seam).
|
|
7
|
+
*
|
|
8
|
+
* When a host resolves a widget's data server-side (the MCP Apps renderer), it
|
|
9
|
+
* stamps a deterministic `_queryId` into each data-bound node's props and wraps
|
|
10
|
+
* that node's renderer in a `<JsonUiBindingContext.Provider value={{queryId}}>`.
|
|
11
|
+
* SDK data hooks (currently `useDuckDbSqlQuery`) read the id via
|
|
12
|
+
* `useJsonUiBinding()` and thread it into `bridge.sql.query(sql, { queryId })`,
|
|
13
|
+
* letting the static MCP bridge look up the pre-resolved rows by id instead of
|
|
14
|
+
* running DuckDB in the sandbox.
|
|
15
|
+
*
|
|
16
|
+
* The default value is `{}` (no binding). In every other host (workbench, test
|
|
17
|
+
* harness, mobile) the provider is absent, `queryId` is `undefined`, and the
|
|
18
|
+
* hooks behave exactly as before — so this seam is fully backward-compatible and
|
|
19
|
+
* data-bound components never need editing.
|
|
20
|
+
*/
|
|
21
|
+
const react_1 = require("react");
|
|
22
|
+
exports.JsonUiBindingContext = (0, react_1.createContext)({});
|
|
23
|
+
exports.JsonUiBindingContext.displayName = "JsonUiBindingContext";
|
|
24
|
+
/** Read the current node's data-binding identity. `{}` when no provider is present. */
|
|
25
|
+
function useJsonUiBinding() {
|
|
26
|
+
return (0, react_1.useContext)(exports.JsonUiBindingContext);
|
|
27
|
+
}
|
|
@@ -24,6 +24,12 @@ export interface UseDuckDbSqlQueryOptions {
|
|
|
24
24
|
}>;
|
|
25
25
|
/** @deprecated Use `maxRows` — kept temporarily for the SDK's existing surface. */
|
|
26
26
|
defaultLimit?: number;
|
|
27
|
+
/**
|
|
28
|
+
* Binding identity for server-resolved data (MCP-host seam). When omitted,
|
|
29
|
+
* the hook falls back to `useJsonUiBinding()`. Threaded into
|
|
30
|
+
* `bridge.sql.query(sql, { queryId })`; the workbench bridge ignores it.
|
|
31
|
+
*/
|
|
32
|
+
queryId?: string;
|
|
27
33
|
}
|
|
28
34
|
export interface UseDuckDbSqlQueryResult {
|
|
29
35
|
rows: ReadonlyArray<Record<string, unknown>>;
|
|
@@ -49,7 +55,7 @@ export declare function useDuckDbSqlQueryPreprocessing({ sql, enabled, maxRows,
|
|
|
49
55
|
* `useDuckDbSqlQueryPreprocessing` to prepare the SQL string, then runs
|
|
50
56
|
* it via the bridge.
|
|
51
57
|
*/
|
|
52
|
-
export declare function useDuckDbSqlQuery({ sql, enabled, maxRows, sourceOverrides, }: UseDuckDbSqlQueryOptions): UseDuckDbSqlQueryResult;
|
|
58
|
+
export declare function useDuckDbSqlQuery({ sql, enabled, maxRows, sourceOverrides, queryId: queryIdOption, }: UseDuckDbSqlQueryOptions): UseDuckDbSqlQueryResult;
|
|
53
59
|
/**
|
|
54
60
|
* Resolve UDF names to VFS filenames, registering them in DuckDB if needed.
|
|
55
61
|
* Exposed for advanced use cases (e.g. building your own query string).
|
|
@@ -21,6 +21,7 @@ exports.useVfsRegistration = useVfsRegistration;
|
|
|
21
21
|
const react_1 = require("react");
|
|
22
22
|
const bridge_1 = require("../bridge");
|
|
23
23
|
const form_1 = require("../form");
|
|
24
|
+
const json_ui_binding_1 = require("./json-ui-binding");
|
|
24
25
|
const use_json_ui_log_1 = require("./use-json-ui-log");
|
|
25
26
|
const use_json_ui_edge_animation_1 = require("./use-json-ui-edge-animation");
|
|
26
27
|
const use_canvas_params_1 = require("./use-canvas-params");
|
|
@@ -422,10 +423,15 @@ function useDuckDbSqlQueryPreprocessing({ sql, enabled = true, maxRows = DEFAULT
|
|
|
422
423
|
* `useDuckDbSqlQueryPreprocessing` to prepare the SQL string, then runs
|
|
423
424
|
* it via the bridge.
|
|
424
425
|
*/
|
|
425
|
-
function useDuckDbSqlQuery({ sql, enabled = true, maxRows = DEFAULT_MAX_ROWS, sourceOverrides, }) {
|
|
426
|
+
function useDuckDbSqlQuery({ sql, enabled = true, maxRows = DEFAULT_MAX_ROWS, sourceOverrides, queryId: queryIdOption, }) {
|
|
426
427
|
const bridge = (0, bridge_1.useFusedWidgetBridge)();
|
|
427
428
|
const { startLoading: startEdgeLoading, stopLoading: stopEdgeLoading } = (0, use_json_ui_edge_animation_1.useJsonUiEdgeAnimation)();
|
|
428
429
|
const { log } = (0, use_json_ui_log_1.useJsonUiLog)();
|
|
430
|
+
// Server-resolved data seam: explicit option wins, else fall back to the
|
|
431
|
+
// per-node binding context. `undefined` in every non-MCP host (no behavior
|
|
432
|
+
// change for the workbench).
|
|
433
|
+
const { queryId: queryIdBinding } = (0, json_ui_binding_1.useJsonUiBinding)();
|
|
434
|
+
const queryId = queryIdOption ?? queryIdBinding;
|
|
429
435
|
const [rows, setRows] = (0, react_1.useState)(EMPTY_ROWS);
|
|
430
436
|
const [columns, setColumns] = (0, react_1.useState)(EMPTY_COLUMNS);
|
|
431
437
|
const [queryLoading, setQueryLoading] = (0, react_1.useState)(false);
|
|
@@ -490,7 +496,7 @@ function useDuckDbSqlQuery({ sql, enabled = true, maxRows = DEFAULT_MAX_ROWS, so
|
|
|
490
496
|
: processedSql;
|
|
491
497
|
log(`SQL query started: ${truncatedSql}`);
|
|
492
498
|
const t0 = performance.now();
|
|
493
|
-
bridge.sql.query(processedSql, { signal: controller.signal }).then((result) => {
|
|
499
|
+
bridge.sql.query(processedSql, { signal: controller.signal, queryId }).then((result) => {
|
|
494
500
|
if (cancelled)
|
|
495
501
|
return;
|
|
496
502
|
const elapsed = Math.round(performance.now() - t0);
|
|
@@ -537,6 +543,7 @@ function useDuckDbSqlQuery({ sql, enabled = true, maxRows = DEFAULT_MAX_ROWS, so
|
|
|
537
543
|
preprocessingError,
|
|
538
544
|
fetchKey,
|
|
539
545
|
log,
|
|
546
|
+
queryId,
|
|
540
547
|
]);
|
|
541
548
|
return { rows, columns, loading, error, refetch };
|
|
542
549
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type UdfExecuteResult } from "../bridge";
|
|
2
|
+
export type UdfExecutorStatus = "idle" | "running" | "success" | "error";
|
|
3
|
+
export interface UseUdfExecutorOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Output format forwarded to `bridge.udfs.execute`. Omit to let the host
|
|
6
|
+
* pick its default (typically `"json"`).
|
|
7
|
+
*/
|
|
8
|
+
format?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface UseUdfExecutorResult {
|
|
11
|
+
/**
|
|
12
|
+
* Run the UDF. Optional `extraOverrides` are merged over the parsed
|
|
13
|
+
* overrides and win — use them for runtime values not known at authoring
|
|
14
|
+
* time (a clicked row id, a form's current field values). They are passed
|
|
15
|
+
* through verbatim (no `$param` resolution). Resolves with the execution
|
|
16
|
+
* result, or `null` when there is nothing to run.
|
|
17
|
+
*/
|
|
18
|
+
fire: (extraOverrides?: Record<string, string>) => Promise<UdfExecuteResult | null>;
|
|
19
|
+
/** Lifecycle of the most recent `fire()`. */
|
|
20
|
+
status: UdfExecutorStatus;
|
|
21
|
+
/** Convenience for `status === "running"`. */
|
|
22
|
+
isRunning: boolean;
|
|
23
|
+
/** Decoded output of the most recent successful run (`null` otherwise). */
|
|
24
|
+
data: unknown;
|
|
25
|
+
/** Error from the most recent run (invalid executor, edge gating, or execution). */
|
|
26
|
+
error: string | null;
|
|
27
|
+
/** Whether `executor` parsed to a runnable UDF reference. */
|
|
28
|
+
canFire: boolean;
|
|
29
|
+
/** Reset status / data / error back to idle and invalidate any in-flight run. */
|
|
30
|
+
reset: () => void;
|
|
31
|
+
}
|
|
32
|
+
export declare function useUdfExecutor(executor: string | null | undefined, options?: UseUdfExecutorOptions): UseUdfExecutorResult;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useUdfExecutor = useUdfExecutor;
|
|
4
|
+
/**
|
|
5
|
+
* Event-triggered UDF execution.
|
|
6
|
+
*
|
|
7
|
+
* `useUdfExecutor` is the imperative sibling of `useParamSubstitution`: it
|
|
8
|
+
* parses an `executor` string (`udf_name?key=value&key2=$param`), subscribes
|
|
9
|
+
* to the canvas/form params its overrides reference, and returns a stable
|
|
10
|
+
* `fire()` callback. Calling `fire()` resolves the overrides against the
|
|
11
|
+
* *current* param values and runs the UDF once via `bridge.udfs.execute` —
|
|
12
|
+
* nothing happens until the event fires.
|
|
13
|
+
*
|
|
14
|
+
* Any component can adopt it: wire `fire` to whatever event it owns (a button
|
|
15
|
+
* click, a form submit, a row selection) and reflect `isRunning` / `error` in
|
|
16
|
+
* its own UI.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* function ButtonRenderer({ element }: ComponentRenderProps<{ executor?: string }>) {
|
|
20
|
+
* const exec = useUdfExecutor(element.props.executor);
|
|
21
|
+
* return (
|
|
22
|
+
* <button onClick={() => exec.fire()} disabled={exec.isRunning}>
|
|
23
|
+
* {exec.isRunning ? "Running…" : "Run"}
|
|
24
|
+
* </button>
|
|
25
|
+
* );
|
|
26
|
+
* }
|
|
27
|
+
*/
|
|
28
|
+
const react_1 = require("react");
|
|
29
|
+
const bridge_1 = require("../bridge");
|
|
30
|
+
const form_1 = require("../form");
|
|
31
|
+
const executor_1 = require("../utils/executor");
|
|
32
|
+
const sql_placeholders_1 = require("../utils/sql-placeholders");
|
|
33
|
+
const use_allowed_udf_names_1 = require("./use-allowed-udf-names");
|
|
34
|
+
const use_canvas_params_1 = require("./use-canvas-params");
|
|
35
|
+
function useUdfExecutor(executor, options = {}) {
|
|
36
|
+
const bridge = (0, bridge_1.useFusedWidgetBridge)();
|
|
37
|
+
const { format } = options;
|
|
38
|
+
const parsed = (0, react_1.useMemo)(() => (0, executor_1.parseExecutor)(executor), [executor]);
|
|
39
|
+
// The `$param` references inside override values — these are the canvas/form
|
|
40
|
+
// params we subscribe to so `fire()` always resolves fresh values.
|
|
41
|
+
const paramNames = (0, react_1.useMemo)(() => {
|
|
42
|
+
if (!parsed)
|
|
43
|
+
return [];
|
|
44
|
+
const seen = new Set();
|
|
45
|
+
const out = [];
|
|
46
|
+
for (const rawValue of Object.values(parsed.overrides)) {
|
|
47
|
+
const name = (0, sql_placeholders_1.getDollarRefName)(rawValue);
|
|
48
|
+
if (name && !seen.has(name)) {
|
|
49
|
+
seen.add(name);
|
|
50
|
+
out.push(name);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return out;
|
|
54
|
+
}, [parsed]);
|
|
55
|
+
// Mirror useParamSubstitution: form-scoped values shadow canvas values when
|
|
56
|
+
// the component is rendered inside a form.
|
|
57
|
+
const canvasValues = (0, use_canvas_params_1.useCanvasParams)(paramNames);
|
|
58
|
+
const { inForm, values: formValues } = (0, form_1.useFormParams)(paramNames);
|
|
59
|
+
const paramValues = (0, react_1.useMemo)(() => (inForm ? { ...canvasValues, ...formValues } : canvasValues), [canvasValues, formValues, inForm]);
|
|
60
|
+
const allowedUdfNames = (0, use_allowed_udf_names_1.useAllowedUdfNames)();
|
|
61
|
+
// Keep resolution inputs in a ref so `fire` stays referentially stable and
|
|
62
|
+
// always reads the latest values — it is event-driven, not reactive.
|
|
63
|
+
const latest = (0, react_1.useRef)({ parsed, paramValues, allowedUdfNames, format });
|
|
64
|
+
latest.current = { parsed, paramValues, allowedUdfNames, format };
|
|
65
|
+
const [status, setStatus] = (0, react_1.useState)("idle");
|
|
66
|
+
const [data, setData] = (0, react_1.useState)(null);
|
|
67
|
+
const [error, setError] = (0, react_1.useState)(null);
|
|
68
|
+
// Monotonic run id: only the latest fire() may write state, so a slower
|
|
69
|
+
// earlier run can't clobber a newer one (latest-wins).
|
|
70
|
+
const runIdRef = (0, react_1.useRef)(0);
|
|
71
|
+
const reset = (0, react_1.useCallback)(() => {
|
|
72
|
+
runIdRef.current += 1;
|
|
73
|
+
setStatus("idle");
|
|
74
|
+
setData(null);
|
|
75
|
+
setError(null);
|
|
76
|
+
}, []);
|
|
77
|
+
const fail = (0, react_1.useCallback)((message) => {
|
|
78
|
+
setStatus("error");
|
|
79
|
+
setError(message);
|
|
80
|
+
setData(null);
|
|
81
|
+
return { data: null, error: message };
|
|
82
|
+
}, []);
|
|
83
|
+
const fire = (0, react_1.useCallback)(async (extraOverrides) => {
|
|
84
|
+
const { parsed: p, paramValues: pv, allowedUdfNames: allowed, format: fmt, } = latest.current;
|
|
85
|
+
if (!p) {
|
|
86
|
+
fail("No UDF to run (empty or invalid executor).");
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
// Edge-gating parity with inline {{udf}} / SQL references: the UDF must
|
|
90
|
+
// be reachable from this node. `null` means no filtering applies.
|
|
91
|
+
if (allowed && !allowed.has(p.name)) {
|
|
92
|
+
return fail(`UDF "${p.name}" is not reachable from this node — connect it with an edge.`);
|
|
93
|
+
}
|
|
94
|
+
// Resolve `$param` references against current params; runtime
|
|
95
|
+
// extraOverrides win over authored ones.
|
|
96
|
+
const overrides = {};
|
|
97
|
+
for (const [key, rawValue] of Object.entries(p.overrides)) {
|
|
98
|
+
overrides[key] = (0, sql_placeholders_1.resolveOverrideValue)(rawValue, pv).value;
|
|
99
|
+
}
|
|
100
|
+
if (extraOverrides)
|
|
101
|
+
Object.assign(overrides, extraOverrides);
|
|
102
|
+
const runId = ++runIdRef.current;
|
|
103
|
+
const isCurrent = () => runId === runIdRef.current;
|
|
104
|
+
setStatus("running");
|
|
105
|
+
setError(null);
|
|
106
|
+
bridge.edges.startLoading();
|
|
107
|
+
try {
|
|
108
|
+
const result = await bridge.udfs.execute(p.name, overrides, {
|
|
109
|
+
format: fmt,
|
|
110
|
+
});
|
|
111
|
+
if (isCurrent()) {
|
|
112
|
+
if (result.error) {
|
|
113
|
+
setStatus("error");
|
|
114
|
+
setError(result.error);
|
|
115
|
+
setData(null);
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
setStatus("success");
|
|
119
|
+
setData(result.data);
|
|
120
|
+
setError(null);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return result;
|
|
124
|
+
}
|
|
125
|
+
catch (err) {
|
|
126
|
+
const message = err instanceof Error ? err.message : "UDF execution failed.";
|
|
127
|
+
if (isCurrent()) {
|
|
128
|
+
setStatus("error");
|
|
129
|
+
setError(message);
|
|
130
|
+
setData(null);
|
|
131
|
+
}
|
|
132
|
+
return { data: null, error: message };
|
|
133
|
+
}
|
|
134
|
+
finally {
|
|
135
|
+
bridge.edges.stopLoading();
|
|
136
|
+
}
|
|
137
|
+
}, [bridge, fail]);
|
|
138
|
+
return {
|
|
139
|
+
fire,
|
|
140
|
+
status,
|
|
141
|
+
isRunning: status === "running",
|
|
142
|
+
data,
|
|
143
|
+
error,
|
|
144
|
+
canFire: parsed !== null,
|
|
145
|
+
reset,
|
|
146
|
+
};
|
|
147
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -34,5 +34,8 @@ export { useUploadAccessCheck, type UploadAccessState, } from "./hooks/use-uploa
|
|
|
34
34
|
export { useJsonUiLog, useJsonUiLogs, useJsonUiLogClear, } from "./hooks/use-json-ui-log";
|
|
35
35
|
export { useJsonUiUdfInfo } from "./hooks/use-json-ui-udf-info";
|
|
36
36
|
export { useJsonUiEdgeAnimation } from "./hooks/use-json-ui-edge-animation";
|
|
37
|
+
export { useUdfExecutor, type UseUdfExecutorOptions, type UseUdfExecutorResult, type UdfExecutorStatus, } from "./hooks/use-udf-executor";
|
|
38
|
+
export { JsonUiBindingContext, useJsonUiBinding, type JsonUiBinding, } from "./hooks/json-ui-binding";
|
|
37
39
|
export * from "./utils/sql-placeholders";
|
|
38
40
|
export { parseStyle } from "./utils/parse-style";
|
|
41
|
+
export { parseExecutor, type ParsedExecutor } from "./utils/executor";
|
package/dist/index.js
CHANGED
|
@@ -30,7 +30,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
30
30
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
31
31
|
};
|
|
32
32
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
33
|
-
exports.parseStyle = exports.useJsonUiEdgeAnimation = exports.useJsonUiUdfInfo = exports.useJsonUiLogClear = exports.useJsonUiLogs = exports.useJsonUiLog = exports.useUploadAccessCheck = exports.SIGNED_URL_SCHEMES = exports.useMediaSrc = exports.useUrlSigning = exports.useSqlSourceOverrides = exports.SqlSourceOverrideContext = exports.useVfsRegistration = exports.useDuckDbSqlQueryPreprocessing = exports.useDuckDbSqlQuery = exports.parseUdfColumnQuery = exports.isUdfQuery = exports.useUdfColumnValues = exports.useUdfColumnValue = exports.useUdfDataFrameSample = exports.useRequestUdfReexecute = exports.useUdfOutputByName = exports.useParamSubstitution = exports.useAllowedUdfNames = exports.useAllowedSources = exports.useCanvasParams = exports.useFusedParamWithForm = exports.useFusedParam = exports.defineCatalog = exports.defineComponent = void 0;
|
|
33
|
+
exports.parseExecutor = exports.parseStyle = exports.useJsonUiBinding = exports.JsonUiBindingContext = exports.useUdfExecutor = exports.useJsonUiEdgeAnimation = exports.useJsonUiUdfInfo = exports.useJsonUiLogClear = exports.useJsonUiLogs = exports.useJsonUiLog = exports.useUploadAccessCheck = exports.SIGNED_URL_SCHEMES = exports.useMediaSrc = exports.useUrlSigning = exports.useSqlSourceOverrides = exports.SqlSourceOverrideContext = exports.useVfsRegistration = exports.useDuckDbSqlQueryPreprocessing = exports.useDuckDbSqlQuery = exports.parseUdfColumnQuery = exports.isUdfQuery = exports.useUdfColumnValues = exports.useUdfColumnValue = exports.useUdfDataFrameSample = exports.useRequestUdfReexecute = exports.useUdfOutputByName = exports.useParamSubstitution = exports.useAllowedUdfNames = exports.useAllowedSources = exports.useCanvasParams = exports.useFusedParamWithForm = exports.useFusedParam = exports.defineCatalog = exports.defineComponent = void 0;
|
|
34
34
|
// ── Part 1: Provider contract ────────────────────────────────────────────────
|
|
35
35
|
__exportStar(require("./protocol"), exports);
|
|
36
36
|
__exportStar(require("./bridge"), exports);
|
|
@@ -84,7 +84,14 @@ var use_json_ui_udf_info_1 = require("./hooks/use-json-ui-udf-info");
|
|
|
84
84
|
Object.defineProperty(exports, "useJsonUiUdfInfo", { enumerable: true, get: function () { return use_json_ui_udf_info_1.useJsonUiUdfInfo; } });
|
|
85
85
|
var use_json_ui_edge_animation_1 = require("./hooks/use-json-ui-edge-animation");
|
|
86
86
|
Object.defineProperty(exports, "useJsonUiEdgeAnimation", { enumerable: true, get: function () { return use_json_ui_edge_animation_1.useJsonUiEdgeAnimation; } });
|
|
87
|
+
var use_udf_executor_1 = require("./hooks/use-udf-executor");
|
|
88
|
+
Object.defineProperty(exports, "useUdfExecutor", { enumerable: true, get: function () { return use_udf_executor_1.useUdfExecutor; } });
|
|
89
|
+
var json_ui_binding_1 = require("./hooks/json-ui-binding");
|
|
90
|
+
Object.defineProperty(exports, "JsonUiBindingContext", { enumerable: true, get: function () { return json_ui_binding_1.JsonUiBindingContext; } });
|
|
91
|
+
Object.defineProperty(exports, "useJsonUiBinding", { enumerable: true, get: function () { return json_ui_binding_1.useJsonUiBinding; } });
|
|
87
92
|
// ── Pure utilities (re-exported for advanced workbench paths) ────────────────
|
|
88
93
|
__exportStar(require("./utils/sql-placeholders"), exports);
|
|
89
94
|
var parse_style_1 = require("./utils/parse-style");
|
|
90
95
|
Object.defineProperty(exports, "parseStyle", { enumerable: true, get: function () { return parse_style_1.parseStyle; } });
|
|
96
|
+
var executor_1 = require("./utils/executor");
|
|
97
|
+
Object.defineProperty(exports, "parseExecutor", { enumerable: true, get: function () { return executor_1.parseExecutor; } });
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface ParsedExecutor {
|
|
2
|
+
/** UDF name to run. */
|
|
3
|
+
name: string;
|
|
4
|
+
/**
|
|
5
|
+
* Raw override params (URL-decoded). Values may still contain a `$param`
|
|
6
|
+
* reference that needs canvas/form resolution before execution.
|
|
7
|
+
*/
|
|
8
|
+
overrides: Record<string, string>;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Parse an executor string into `{ name, overrides }`. Returns `null` when the
|
|
12
|
+
* string is empty/blank or the UDF name is not a valid identifier — callers
|
|
13
|
+
* treat `null` as "nothing to run".
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* parseExecutor("send_email") // { name: "send_email", overrides: {} }
|
|
17
|
+
* parseExecutor("ingest?city=$city&dry=true") // { name: "ingest", overrides: { city: "$city", dry: "true" } }
|
|
18
|
+
* parseExecutor(" ") // null
|
|
19
|
+
*/
|
|
20
|
+
export declare function parseExecutor(executor: string | null | undefined): ParsedExecutor | null;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseExecutor = parseExecutor;
|
|
4
|
+
/**
|
|
5
|
+
* Pure parsing for "executor" strings — a declarative reference to a UDF that
|
|
6
|
+
* a component runs in response to an event (button click, form submit, …).
|
|
7
|
+
*
|
|
8
|
+
* The grammar matches the body of an inline `{{udf?...}}` placeholder, minus
|
|
9
|
+
* the column/row access path:
|
|
10
|
+
*
|
|
11
|
+
* "udf_name"
|
|
12
|
+
* "udf_name?key=value"
|
|
13
|
+
* "udf_name?city=$selected_city&limit=10"
|
|
14
|
+
*
|
|
15
|
+
* Override values may contain `$param` references; those are resolved against
|
|
16
|
+
* canvas/form params at fire time (see `resolveOverrideValue`) — exactly the
|
|
17
|
+
* same substitution the SQL widget, iframe, and inline templates use. The
|
|
18
|
+
* override suffix accepts `&` or `,` separators and URL-decoded keys/values
|
|
19
|
+
* (delegated to `parseOverridesString`).
|
|
20
|
+
*/
|
|
21
|
+
const sql_placeholders_1 = require("./sql-placeholders");
|
|
22
|
+
const EXECUTOR_NAME_RE = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
23
|
+
/**
|
|
24
|
+
* Parse an executor string into `{ name, overrides }`. Returns `null` when the
|
|
25
|
+
* string is empty/blank or the UDF name is not a valid identifier — callers
|
|
26
|
+
* treat `null` as "nothing to run".
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* parseExecutor("send_email") // { name: "send_email", overrides: {} }
|
|
30
|
+
* parseExecutor("ingest?city=$city&dry=true") // { name: "ingest", overrides: { city: "$city", dry: "true" } }
|
|
31
|
+
* parseExecutor(" ") // null
|
|
32
|
+
*/
|
|
33
|
+
function parseExecutor(executor) {
|
|
34
|
+
if (!executor)
|
|
35
|
+
return null;
|
|
36
|
+
const trimmed = executor.trim();
|
|
37
|
+
if (!trimmed)
|
|
38
|
+
return null;
|
|
39
|
+
const queryStart = trimmed.indexOf("?");
|
|
40
|
+
const name = (queryStart === -1 ? trimmed : trimmed.slice(0, queryStart)).trim();
|
|
41
|
+
if (!EXECUTOR_NAME_RE.test(name))
|
|
42
|
+
return null;
|
|
43
|
+
const rawOverrides = queryStart === -1 ? undefined : trimmed.slice(queryStart + 1);
|
|
44
|
+
return { name, overrides: (0, sql_placeholders_1.parseOverridesString)(rawOverrides) ?? {} };
|
|
45
|
+
}
|