@fusedio/widget-sdk 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -58,9 +58,10 @@ 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
- | `useDuckDbSqlQuery` | Run a DuckDB-WASM query against UDF parquet outputs in the browser. |
64
+ | `useDuckDbSqlQuery` | Run a DuckDB-WASM query against UDF parquet outputs in the browser; optional `refreshInterval` (ms) declares host-driven auto-refresh. |
64
65
  | `useUrlSigning` / `useMediaSrc` | Sign `s3://`, `gs://`, `fd://` URLs and resolve media sources. |
65
66
  | `useUploadAccessCheck` | Pre-flight an upload destination for write access. |
66
67
  | `useAllowedSources` | Which UDFs are allowed to broadcast to this node? |
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. */
@@ -96,6 +128,15 @@ export interface SqlQueryOptions {
96
128
  * backward-compatible.
97
129
  */
98
130
  queryId?: string;
131
+ /**
132
+ * Author-declared refresh cadence for this query, in **milliseconds**. When
133
+ * set, a server-resolved host (dev-serve / `widget open` / parley / deployed)
134
+ * re-runs this query on its own timer to drive a live dashboard. The
135
+ * workbench / static bridge **ignores** this field (no timer), so it is fully
136
+ * backward-compatible. The SDK only carries the value — the timer, visibility
137
+ * pausing, and backoff live in the host runtime.
138
+ */
139
+ refreshInterval?: number;
99
140
  }
100
141
  export interface SqlQueryResult {
101
142
  rows: ReadonlyArray<Record<string, unknown>>;
package/dist/bundle.js CHANGED
@@ -1,2 +1,2 @@
1
- var xr="parameter-updates",ce=(o=>(o.PARAM="param",o.RANGE="range",o.VIEWPORT="viewport",o.CLEAR="clear",o))(ce||{});function br(e){if(typeof e!="object"||e===null)return!1;let n=e;return typeof n.type=="string"&&Object.values(ce).includes(n.type)&&typeof n.parameter=="string"&&"values"in n}import{createContext as ke,useContext as Ce}from"react";var Pe=ke(null);Pe.displayName="FusedWidgetBridgeContext";function v(){let e=Ce(Pe);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 Ee=ke(null);Ee.displayName="JsonUiNodeOverrideContext";function j(){let e=v(),n=Ce(Ee);return n?{udfUniqueId:n.udfUniqueId??e.node.udfUniqueId,udfName:n.udfName??e.node.udfName,configHash:n.configHash??e.node.configHash}:{udfUniqueId:e.node.udfUniqueId,udfName:e.node.udfName,configHash:e.node.configHash}}import{createContext as dn,useCallback as Ae,useContext as fn,useRef as Le,useSyncExternalStore as gn}from"react";function Cr(){let e=new Map,n=new Set,r=t=>{n.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),r(t))},removeField(t){e.has(t)&&(e.delete(t),r(t))},subscribe(t,o){let s={names:new Set(t),cb:o};return n.add(s),()=>{n.delete(s)}}}}var Fe=dn({store:null,isInForm:!1});Fe.displayName="JsonUiFormContext";function de(){return fn(Fe)}var Oe=Object.freeze({});function W(e){let{store:n,isInForm:r}=de(),t=mn(e),o=Ae(c=>n?n.subscribe(t,c):()=>{},[n,t]),s=Le(Oe),u=Ae(()=>{if(!n)return Oe;let c=n.getSnapshot(t),l=s.current;return pn(l,c)?l:(s.current=c,c)},[n,t]),i=gn(o,u,u);return{inForm:r,values:i}}function pn(e,n){if(e===n)return!0;let r=Object.keys(e),t=Object.keys(n);if(r.length!==t.length)return!1;for(let o of r)if(!Object.is(e[o],n[o]))return!1;return!0}function mn(e){let n=Le(e),r=n.current;return r!==e&&(r.length!==e.length||r.some((t,o)=>t!==e[o]))&&(n.current=e),n.current}function Sn(e){return e}function yn(e){return e}import{useCallback as ee,useEffect as z,useMemo as ne,useRef as J,useState as Rn}from"react";function fe({param:e,debounceMs:n=300,readOnly:r=!1,defaultValue:t,broadcastDefaultValue:o=!0,validate:s,preprocess:u}){let i=v(),{configHash:c}=j(),l=!!e,x=(d,f)=>{if(!e)return;if(d==="Cleared"){i.log.log(`Cleared param "${e}"`,"info",c);return}let a=JSON.stringify(f),g=a&&a.length>100?a.slice(0,100)+"\u2026":a;i.log.log(`${d} param "${e}" = ${g}`,"info",c)},b=ne(()=>u??xn(t),[u]),w=ne(()=>s??vn(t),[s]),S=ne(()=>e?[e]:[],[e]),{inForm:p,values:h}=W(S),E=e?h[e]:void 0,q=ne(()=>{if(!(!l||!e))return i.params.getSnapshot(e)},[i,l,e]),k=()=>{let d=p&&E!==void 0?E:q;if(d==null)return t;let f=b(d);return w(f)?f:t},[N,O]=Rn(k),U=J(N);U.current=N;let y=J(null),C=J(!1),D=J(!1),T=J({enabled:l,param:e});T.current={enabled:l,param:e};let M=J(i);M.current=i;let L=J(e);z(()=>{let d=L.current;L.current=e,d&&d!==e&&i.params.clear(d)},[i,e]),z(()=>{if(!l||!e||C.current)return;let d=p&&E!==void 0?E:i.params.getSnapshot(e);if(d==null)return;let f=b(d);f!==U.current&&w(f)&&(O(f),x("Received",d))},[i,e,l,p,E,b,w]),z(()=>!l||!e?void 0:i.params.subscribe(e,()=>{if(C.current)return;let f=i.params.getSnapshot(e);if(f==null)return;let a=b(f);a!==U.current&&w(a)&&(O(a),x("Received",f))}),[i,e,l,b,w]);let A=ee(d=>{!l||!e||(i.params.set(e,d,"param"),i.edges.stopLoading(),x("Broadcast",d))},[i,l,e]),I=ee(()=>{!l||r||(y.current&&(clearTimeout(y.current),y.current=null),i.edges.startLoading(),A(U.current),C.current=!1)},[i,A,l,r]),$=ee(d=>{y.current&&(clearTimeout(y.current),y.current=null),O(d),U.current=d,C.current=!1,!(!l||!e||r)&&(i.params.clear(e),i.edges.stopLoading(),x("Cleared",null))},[i,l,e,r]);z(()=>{D.current=!1},[e,l]),z(()=>{if(!l||!e||r||!o||D.current)return;let d=i.params.getSnapshot(e);if(d!=null){D.current=!0;return}if(U.current===""){D.current=!0;return}A(U.current),D.current=!0},[i,l,e,r,o,A]);let Q=ee(d=>{O(d),!(!l||r)&&(C.current=!0,i.edges.startLoading(),y.current&&clearTimeout(y.current),y.current=setTimeout(()=>{A(d),C.current=!1},n))},[i,A,n,l,r]);return z(()=>()=>{y.current&&clearTimeout(y.current);let{enabled:d,param:f}=T.current;!d||!f||M.current.params.clear(f)},[]),{value:N,setValue:Q,broadcastNow:I,clearValue:$}}function vn(e){return typeof e=="string"?(n=>typeof n=="string"):typeof e=="number"?(n=>typeof n=="number"):typeof e=="boolean"?(n=>typeof n=="boolean"):Array.isArray(e)?(n=>Array.isArray(n)):e!==null&&typeof e=="object"?(n=>n!==null&&typeof n=="object"&&!Array.isArray(n)):(n=>!0)}function xn(e){return typeof e=="string"?n=>typeof n=="string"?n:Array.isArray(n)?n.join(","):n!==null&&typeof n=="object"?JSON.stringify(n):String(n):n=>n}import{useEffect as Te}from"react";function bn(e){let n=de(),r=e.param,t=!!(n.isInForm&&r),{value:o,setValue:s,broadcastNow:u,clearValue:i}=fe({...e,param:t?void 0:e.param}),c=n.store;return Te(()=>{!t||!r||!c||c.setField(r,o)},[o,t,r,c]),Te(()=>{if(!(!t||!r||!c))return()=>{c.removeField(r)}},[t,r,c]),{value:o,setValue:s,broadcastNow:u,clearValue:i,isInForm:n.isInForm}}import{useCallback as qe,useRef as De,useSyncExternalStore as hn}from"react";var Ne=Object.freeze({});function Y(e){let n=v(),r=wn(e),t=qe(u=>r.length===0?()=>{}:n.params.subscribeMany(r,u),[n,r]),o=De(Ne),s=qe(()=>{if(r.length===0)return Ne;let u=n.params.getSnapshotMany(r),i=o.current;return Un(i,u)?i:(o.current=u,u)},[n,r]);return hn(t,s,s)}function Un(e,n){if(e===n)return!0;let r=Object.keys(e),t=Object.keys(n);if(r.length!==t.length)return!1;for(let o of r)if(!Object.is(e[o],n[o]))return!1;return!0}function wn(e){let n=De(e),r=n.current;return r!==e&&(r.length!==e.length||r.some((t,o)=>t!==e[o]))&&(n.current=e),n.current}import{useCallback as ge,useRef as kn,useSyncExternalStore as Cn}from"react";function Pn(){let e=v(),n=ge(u=>e.routing.subscribeAllowedSources(u),[e]),r=kn(null),t=ge(()=>{let u=e.routing.getAllowedSources(),i=r.current;return En(i,u)?i:(r.current=u,u)},[e]),o=Cn(n,t,t),s=ge((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 En(e,n){return e===n?!0:e===null||n===null||e.length!==n.length?!1:e.every((r,t)=>r.udfUniqueId===n[t].udfUniqueId&&r.udfName===n[t].udfName)}import{useCallback as Be,useRef as An,useSyncExternalStore as On}from"react";function Ln(){let e=v(),n=Be(o=>e.routing.subscribeAllowedSources(o),[e]),r=An(null),t=Be(()=>{let o=e.routing.getAllowedUdfNames(),s=r.current;return Fn(s,o)?s:(r.current=o,o)},[e]);return On(n,t,t)}function Fn(e,n){if(e===n)return!0;if(e===null||n===null||e.size!==n.size)return!1;for(let r of e)if(!n.has(r))return!1;return!0}import{useEffect as Tn,useMemo as V,useRef as qn,useState as Me,useSyncExternalStore as Nn,useCallback as Ie}from"react";var Qe=/\$([a-zA-Z_][a-zA-Z0-9_]*)/g,Dn=/\{\{[\s\S]*?\}\}/,_e=/\{\{\s*([A-Za-z_][A-Za-z0-9_]*)\b/g;function pe(e,n={}){let r=e??"",t=n.preserveMissingParams??!1,o=v(),s=V(()=>Bn(r),[r]),u=Y(s),{inForm:i,values:c}=W(s),l=V(()=>i?{...u,...c}:u,[u,c,i]),x=V(()=>Dn.test(r),[r]),b=V(()=>x?"":Mn(r,l,t),[r,l,t,x]),w=V(()=>{if(!x)return[];let O=new Set,U=[];_e.lastIndex=0;let y;for(;(y=_e.exec(r))!==null;)O.has(y[1])||(O.add(y[1]),U.push(y[1]));return U},[r,x]),S=_n(o,w),[p,h]=Me(()=>({key:"",value:""})),[E,q]=Me(!1),k=V(()=>JSON.stringify({template:r,paramValues:l,preserveMissingParams:t,tick:S}),[r,l,t,S]);return Tn(()=>{if(!x){q(!1);return}let O=!1,U=new AbortController;return q(!0),o.template.render(r,l,{preserveMissingParams:t,signal:U.signal}).then(y=>{O||(h(C=>C.key===k&&C.value===y.value?C:{key:k,value:y.value}),q(y.loading))},y=>{O||y?.name!=="AbortError"&&q(!1)}),()=>{O=!0,U.abort()}},[o,x,r,l,t,k]),{value:V(()=>{if(!x)return b;if(p.key===k)return p.value;try{return o.template.renderLoading(r,l,{preserveMissingParams:t})}catch{return r}},[o,x,b,p,k,r,l,t]),loading:x?E:!1}}function Bn(e){let n=[],r=new Set;for(let t of e.matchAll(Qe)){let o=t[1];r.has(o)||(r.add(o),n.push(o))}return n}function Mn(e,n,r){return e.replace(Qe,(t,o)=>{let s=n[o];return s==null?r?t:"":In(s)})}function In(e){return e==null?"":typeof e=="string"?e:typeof e=="number"||typeof e=="boolean"?String(e):JSON.stringify(e)??""}function _n(e,n){let r=qn(0),t=n.slice().sort().join("|"),o=Ie(u=>{let i=()=>{r.current+=1,u()},c=[e.template.subscribe(i)];for(let l of n)c.push(e.udfs.subscribeOutput(l,i));return()=>c.forEach(l=>l())},[e,t]),s=Ie(()=>r.current,[]);return Nn(o,s,s)}import{useCallback as re,useEffect as Qn,useMemo as te,useRef as Jn,useState as Je,useSyncExternalStore as Vn}from"react";function Ve(e){let n=v(),r=re(s=>e?n.udfs.subscribeOutput(e,s):()=>{},[n,e]),t=Jn(void 0),o=re(()=>{if(!e)return;let s=n.udfs.getOutputSnapshot(e),u=t.current;return $n(u,s)?u:(t.current=s,s)},[n,e]);return Vn(r,o,o)}function $e(){let e=v();return re(n=>e.udfs.requestReexecute(n),[e])}function $n(e,n){return e===n?!0:!e||!n?!1:e.data===n.data&&e.isExecutionInProgress===n.isExecutionInProgress&&e.error===n.error&&e.vfsFilename===n.vfsFilename}var je=/^\{\{(\w+)\.(\w+)(?:\[(\d+)\])?\}\}$/;function oe(e){return!e||typeof e!="string"?!1:je.test(e)}function me(e){if(!oe(e))return null;let n=e.match(je);if(!n)return null;let[,r,t,o]=n,s=o!==void 0?parseInt(o,10):void 0;return{udfName:r,columnName:t,index:s}}function jn(e){return!!e&&typeof e=="object"&&typeof e.getRows=="function"}function Se({udfName:e,sampleSize:n=200}){let r=Ve(e),t=$e(),[o,s]=Je([]),[u,i]=Je([]);Qn(()=>{let l=!1,x=r?.data;if(!x||!jn(x)){s([]),i([]);return}return(async()=>{try{let b=await x.getRows(0,Math.max(0,n));if(l)return;let w=b.map(p=>{let h=p;return h&&typeof h=="object"&&h.properties&&typeof h.properties=="object"?h.properties:p});s(w);let S=Array.from(new Set(w.flatMap(p=>Object.keys(p??{}))));i(S)}catch{if(l)return;s([]),i([])}})(),()=>{l=!0}},[r?.data,n]);let c=re(()=>{e&&t(e)},[t,e]);return{loading:r?.isExecutionInProgress??!1,errorMessage:r?.error??null,isError:!!r?.error,columns:u,rows:o,requestReexecute:c}}function Wn(e,n=200){let r=oe(e),t=te(()=>r?me(e):null,[r,e]),{rows:o,loading:s}=Se({udfName:t?.udfName,sampleSize:n});return{values:te(()=>!t||!t.columnName?[]:o.map(i=>i?.[t.columnName]).filter(i=>i!=null),[o,t]),loading:r?s:!1}}function zn(e,n=200){let r=oe(e),t=te(()=>r?me(e):null,[r,e]),{rows:o,loading:s}=Se({udfName:t?.udfName,sampleSize:n});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:r?s:!1}}import{useCallback as on,useEffect as K,useMemo as F,useRef as ur,useState as B}from"react";import{createContext as Hn,useContext as Kn}from"react";var ye=Hn({});ye.displayName="JsonUiBindingContext";function Re(){return Kn(ye)}import{useCallback as se,useMemo as Zn,useRef as ve,useSyncExternalStore as Gn}from"react";var We=Object.freeze([]);function ie(){let e=v(),{configHash:n}=j(),r=ve(e);r.current=e;let t=ve(n);t.current=n;let o=se((s,u="info")=>{r.current.log.log(s,u,t.current)},[]);return Zn(()=>({log:o}),[o])}function Yn(e){let n=v(),r=se(s=>e?n.log.subscribeLogs(e,s):()=>{},[n,e]),t=ve(We),o=se(()=>{if(!e)return We;let s=n.log.getLogsSnapshot(e);return s===t.current?t.current:(t.current=s,s)},[n,e]);return Gn(r,o,o)}function Xn(e){let n=v();return se(()=>{e&&n.log.clearLogs(e)},[n,e])}function ue(){let e=v();return{startLoading:e.edges.startLoading,stopLoading:e.edges.stopLoading}}import{createContext as er,useContext as nr}from"react";var rr=Object.freeze({}),xe=er(rr);xe.displayName="SqlSourceOverrideContext";function be(){return nr(xe)}var he=/\$([a-zA-Z_][a-zA-Z0-9_]*)/g,ze=/\{\{(\w+)(?:\?([^}]*))?\}\}/g,Ue=/'((?:s3|gs|fd):\/\/[^'\n]+)'/g;function tr(e){return e==null?"''":typeof e=="number"&&!Number.isNaN(e)?String(e):typeof e=="boolean"?e?"TRUE":"FALSE":`'${String(e).replace(/'/g,"''")}'`}function or(e,n){let r=!1;for(let t=0;t<n;t++)if(e[t]==="'"){if(r&&e[t+1]==="'"){t++;continue}r=!r}return r}function He(e,n){return e.replace(he,(r,t,o)=>{let s=n[t],u=s==null?"":String(s);return or(e,o)?u.replace(/'/g,"''"):tr(s)})}function Ke(e){let n=new Set,r=[];he.lastIndex=0;for(let t of e.matchAll(he)){let o=t[1];n.has(o)||(n.add(o),r.push(o))}return r}function sr(e){if(!e)return null;let n={},r=!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&&(n[i]=c,r=!0)}return r?n:null}function Ze(e){let n=[],r;for(ze.lastIndex=0;(r=ze.exec(e))!==null;){let[t,o,s]=r;n.push({match:t,name:o,overrides:sr(s),start:r.index,end:r.index+t.length})}return n}function Ge(e){if(!e)return[];let n=new Set,r=[];Ue.lastIndex=0;let t;for(;(t=Ue.exec(e))!==null;){let o=t[1];n.has(o)||(n.add(o),r.push(o))}return r}function Ye(e,n){return e&&e.replace(Ue,(r,t)=>{let o=n[t];return o?`'${o}'`:r})}var Xe=/^\$([a-zA-Z_][a-zA-Z0-9_]*)$/;function en(e){let n=e.trim(),r=Xe.exec(n);return r?r[1]:null}function nn(e,n){let r=Xe.exec(e);if(!r)return{value:e,unresolved:!1};let t=r[1];if(!(t in n))return{value:e,unresolved:!0};let o=n[t];return o==null?{value:"",unresolved:!1}:{value:String(o),unresolved:!1}}function ir(e){return Object.keys(e).sort().map(n=>`${n}=${e[n]}`).join("&")}function rn(e,n){return n?`${e}#${ir(n)}`:e}var sn=500,H=Object.freeze([]),X=Object.freeze([]);function tn(e,n){if(/\bLIMIT\b/i.test(e))return e;let r=e.trimEnd();return`${r.endsWith(";")?r.slice(0,-1):r} LIMIT ${n}`}function ar(e){return`"${e.replace(/"/g,'""')}"`}function lr(e,n,r,t,o,s){let u=e.match(/^\$([a-zA-Z_][a-zA-Z0-9_]*)$/);if(u){let c=o[u[1]];return c==null?"":tn(String(c),s)}let i=e;for(let c=n.length-1;c>=0;c--){let{raw:l,key:x,resolvedOverrides:b}=n[c],S=b===null&&t?t[l.name]:void 0,p;S?p=ar(S.relationName):p=`'${r.get(x)??`${l.name}.parquet`}'`,i=i.slice(0,l.start)+p+i.slice(l.end)}return i=tn(i,s),He(i,o)}function un({sql:e,enabled:n=!0,maxRows:r=sn,sourceOverrides:t}){let o=v(),s=be(),u=F(()=>{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(),[x,b]=B(""),[w,S]=B(!1),[p,h]=B(null),[E,q]=B(0),k=F(()=>e?Ze(e):[],[e]),N=F(()=>u?k.filter(a=>a.overrides!==null||!u[a.name]):k,[k,u]),O=F(()=>{let a=new Set;for(let g of N)if(g.overrides)for(let R of Object.values(g.overrides)){let _=en(R);_&&a.add(_)}return Array.from(a)},[N]),U=F(()=>e?Ke(e):[],[e]),y=F(()=>{let a=new Set,g=[];for(let R of U)a.has(R)||(a.add(R),g.push(R));for(let R of O)a.has(R)||(a.add(R),g.push(R));return g},[U,O]),C=Y(y),{inForm:D,values:T}=W(y),M=F(()=>D?{...C,...T}:C,[D,C,T]),L=F(()=>N.map(a=>{if(!a.overrides)return{raw:a,key:a.name,resolvedOverrides:null,unresolved:!1};let g={},R=!1;for(let[_,Z]of Object.entries(a.overrides)){let m=nn(Z,M);m.unresolved&&(R=!0),g[_]=m.value}return{raw:a,key:rn(a.name,g),resolvedOverrides:g,unresolved:R}}),[N,M]),A=F(()=>{let a=new Set,g=[];for(let R of L)R.unresolved||a.has(R.key)||(a.add(R.key),g.push({name:R.raw.name,key:R.key,overrides:R.resolvedOverrides??void 0}));return g},[L]),I=F(()=>A.map(a=>`${a.key}|${a.name}|${a.overrides?Object.entries(a.overrides).sort(([g],[R])=>g.localeCompare(R)).map(([g,R])=>`${g}=${R}`).join(","):""}`).join(`
2
- `),[A]),$=on(()=>{q(a=>a+1)},[]);K(()=>{if(!n||A.length===0)return;let a=A.map(g=>o.udfs.subscribeOutput(g.name,()=>{q(R=>R+1)}));return()=>{a.forEach(g=>g())}},[o,n,A]),K(()=>{w?i():c()},[w,i,c]);let Q=F(()=>{if(!u)return null;for(let a of k){if(a.overrides!==null)continue;let g=u[a.name];if(g?.error)return g.error}return null},[k,u]),d=F(()=>u?k.some(a=>a.overrides===null&&u[a.name]?.loading):!1,[k,u]),f=L.some(a=>a.unresolved);return K(()=>{if(!n||!e){b(""),S(!1),h(null);return}if(Q){b(""),h(Q),S(!1),l(`SQL preprocessing: ${Q}`,"error");return}if(d||f){b(""),h(null),S(!0);return}let a=!1;return S(!0),h(null),(async()=>{let g=new Map,R;if(A.length>0)try{let m=await o.sql.resolveVfsFilenames(A);if(a)return;if(m instanceof Map)for(let P of L){if(P.resolvedOverrides)continue;let G=m.get(P.raw.name);G&&g.set(P.key,G)}else g=m.filenames,R=m.errors}catch(m){if(a)return;let P=m instanceof Error?m.message:typeof m=="string"?m:"VFS registration failed";b(""),h(P),S(!1),l(`SQL preprocessing: ${P}`,"error");return}if(R)for(let m of L){let P=R.get(m.key);if(P){if(a)return;b(""),h(P),S(!1),l(`SQL preprocessing: ${P}`,"error");return}}for(let m of L)if(!(m.unresolved||m.resolvedOverrides===null&&u?.[m.raw.name]!==void 0)&&!g.has(m.key)){if(a)return;b(""),h(null),S(!0);return}let _;try{_=lr(e,L,g,u,M,r)}catch(m){if(a)return;let P=m instanceof Error?m.message:typeof m=="string"?m:"SQL preprocessing failed";b(""),h(P),S(!1),l(`SQL preprocessing failed: ${P}`,"error");return}let Z=Ge(_);if(Z.length===0){if(a)return;b(_),h(null),S(!1),l("SQL preprocessing completed");return}try{let m={},P=await Promise.all(Z.map(le=>o.signUrl(le)));if(a)return;Z.forEach((le,cn)=>{m[le]=P[cn].signed});let G=Ye(_,m);b(G),h(null),S(!1),l("SQL preprocessing completed")}catch(m){if(a)return;let P=m instanceof Error?m.message:typeof m=="string"?m:"URL signing failed";b(""),h(P),S(!1),l(`SQL preprocessing failed: ${P}`,"error")}})(),()=>{a=!0}},[o,n,e,I,L,M,u,Q,d,f,r,E,l,A]),{processedSql:x,loading:w,error:p,refetch:$}}function cr({sql:e,enabled:n=!0,maxRows:r=sn,sourceOverrides:t,queryId:o}){let s=v(),{startLoading:u,stopLoading:i}=ue(),{log:c}=ie(),{queryId:l}=Re(),x=o??l,[b,w]=B(H),[S,p]=B(X),[h,E]=B(!1),[q,k]=B(null),[N,O]=B(0),{processedSql:U,loading:y,error:C,refetch:D}=un({sql:e,enabled:n,maxRows:r,sourceOverrides:t}),T=ur(""),M=n&&!!e&&!!U&&!y&&!C&&U!==T.current,L=y||h||M;K(()=>{L?u():i()},[L,u,i]);let A=on(()=>{T.current="",D(),O(I=>I+1)},[D]);return K(()=>{if(!n||!e){T.current="",w(H),p(X),E(!1),k(null);return}if(C){T.current="",k(C),w(H),p(X),E(!1);return}if(y||!U){T.current="",E(!1);return}let I=!1,$=new AbortController;T.current=U,E(!0),k(null);let Q=U.length>120?U.slice(0,120)+"\u2026":U;c(`SQL query started: ${Q}`);let d=performance.now();return s.sql.query(U,{signal:$.signal,queryId:x}).then(f=>{if(I)return;let a=Math.round(performance.now()-d);if(f.error){w(H),p(X),k(f.error),E(!1),c(`SQL failed (${a}ms): ${f.error}`,"error");return}w(f.rows.length===0?H:f.rows),p(f.columns),k(null),E(!1),c(`SQL completed: ${f.rows.length} row${f.rows.length!==1?"s":""} in ${a}ms`)},f=>{if(I||f?.name==="AbortError")return;let a=Math.round(performance.now()-d),g=f instanceof Error?f.message:typeof f=="string"?f:"SQL query failed";k(g),w(H),p(X),E(!1),c(`SQL failed (${a}ms): ${g}`,"error")}),()=>{I=!0,$.abort()}},[s,n,e,U,y,C,N,c,x]),{rows:b,columns:S,loading:L,error:q,refetch:A}}function dr(e,n=!0){let r=v(),[t,o]=B({filenames:new Map,loading:!1}),s=F(()=>e.slice().sort().join("|"),[e]);return K(()=>{if(!n||e.length===0){o({filenames:new Map,loading:!1});return}let u=!1;return o(i=>({...i,loading:!0,error:void 0})),r.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}},[r,s,n]),t}import{useCallback as an,useEffect as fr,useState as ae}from"react";var ln=["s3://","gs://","fd://"];function we(e){return ln.some(n=>e.startsWith(n))}function gr(){let e=v();return{signUrl:an(r=>e.signUrl(r),[e])}}function pr(e){let n=v(),{value:r,loading:t}=pe(e),[o,s]=ae(null),[u,i]=ae(null),[c,l]=ae(!1),[x,b]=ae(0);fr(()=>{if(t)return;if(!r){s(null),i(null),l(!1);return}if(!we(r)){s(r),i(null),l(!1);return}let S=!1;return l(!0),i(null),n.signUrl(r).then(({signed:p})=>{S||s(p??r)}).catch(p=>{S||(i(p instanceof Error?p.message:"Failed to load media"),s(null))}).finally(()=>{S||l(!1)}),()=>{S=!0}},[n,t,r,x]);let w=an(async()=>{if(!r||!we(r))return r??null;let{signed:S}=await n.signUrl(r),p=S??r;return s(p),i(null),b(h=>h+1),p},[n,r]);return{src:o,loading:t||c,error:u,refreshSignedUrl:w,resolvedSrc:r,needsSigning:!!(r&&we(r))}}import{useEffect as mr,useState as Sr}from"react";function yr(e,n){let r=v(),[t,o]=Sr({status:"idle"});return mr(()=>{if(!n||!e?.trim()){o({status:"idle"});return}let s=!1;return o({status:"checking"}),r.uploads.checkAccess(e).then(u=>{s||(u.ok?o({status:"allowed"}):o({status:"denied",message:u.message??"Upload access denied."}))}),()=>{s=!0}},[r,e,n]),t}function Rr(){return j()}function vr(e){if(!e)return{};let n={};for(let r of e.split(";")){let t=r.indexOf(":");if(t===-1)continue;let o=r.slice(0,t).trim(),s=r.slice(t+1).trim();if(!o||!s)continue;let u=o.replace(/-([a-z])/g,(i,c)=>c.toUpperCase());n[u]=s}return n}export{Fe as FormContext,Pe as FusedWidgetBridgeContext,ye as JsonUiBindingContext,Ee as JsonUiNodeOverrideContext,xr as PARAMETER_BROADCAST_CHANNEL,ce as ParameterMessageType,Ue as SIGNABLE_URL_LITERAL_REGEX,ln as SIGNED_URL_SCHEMES,he as SQL_PARAM_REGEX,ze as SQL_SOURCE_PLACEHOLDER_REGEX,xe as SqlSourceOverrideContext,ir as canonicalOverrideKey,rn as computePlaceholderKey,Cr as createFormParamsStore,yn as defineCatalog,Sn as defineComponent,tr as escapeSqlValue,Ge as extractSignableUrls,Ke as extractSqlParams,en as getDollarRefName,br as isStandardMessage,oe as isUdfQuery,sr as parseOverridesString,Ze as parseSqlUdfPlaceholders,vr as parseStyle,me as parseUdfColumnQuery,nn as resolveOverrideValue,Ye as rewriteSignedUrls,He as substituteSqlParams,Pn as useAllowedSources,Ln as useAllowedUdfNames,Y as useCanvasParams,cr as useDuckDbSqlQuery,un as useDuckDbSqlQueryPreprocessing,de as useFormContext,W as useFormParams,fe as useFusedParam,bn as useFusedParamWithForm,v as useFusedWidgetBridge,Re as useJsonUiBinding,ue as useJsonUiEdgeAnimation,ie as useJsonUiLog,Xn as useJsonUiLogClear,Yn as useJsonUiLogs,j as useJsonUiNode,Rr as useJsonUiUdfInfo,pr as useMediaSrc,pe as useParamSubstitution,$e as useRequestUdfReexecute,be as useSqlSourceOverrides,zn as useUdfColumnValue,Wn as useUdfColumnValues,Se as useUdfDataFrameSample,Ve as useUdfOutputByName,yr as useUploadAccessCheck,gr as useUrlSigning,dr as useVfsRegistration};
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 x(){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 H(){let e=x(),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 $(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),a=s.current;return vr(a,c)?a:(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 K,useMemo as re,useRef as j,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=x(),{configHash:c}=H(),a=!!e,b=(f,P)=>{if(!e)return;if(f==="Cleared"){i.log.log(`Cleared param "${e}"`,"info",c);return}let l=JSON.stringify(P),S=l&&l.length>100?l.slice(0,100)+"\u2026":l;i.log.log(`${f} param "${e}" = ${S}`,"info",c)},R=re(()=>u??Er(t),[u]),k=re(()=>s??kr(t),[s]),g=re(()=>e?[e]:[],[e]),{inForm:v,values:p}=$(g),M=e?p[e]:void 0,E=re(()=>{if(!(!a||!e))return i.params.getSnapshot(e)},[i,a,e]),O=()=>{let f=v&&M!==void 0?M:E;if(f==null)return t;let P=R(f);return k(P)?P:t},[L,A]=wr(O),h=j(L);h.current=L;let m=j(null),U=j(!1),C=j(!1),T=j({enabled:a,param:e});T.current={enabled:a,param:e};let q=j(i);q.current=i;let N=j(e);K(()=>{let f=N.current;N.current=e,f&&f!==e&&i.params.clear(f)},[i,e]),K(()=>{if(!a||!e||U.current)return;let f=v&&M!==void 0?M:i.params.getSnapshot(e);if(f==null)return;let P=R(f);P!==h.current&&k(P)&&(A(P),b("Received",f))},[i,e,a,v,M,R,k]),K(()=>!a||!e?void 0:i.params.subscribe(e,()=>{if(U.current)return;let P=i.params.getSnapshot(e);if(P==null)return;let l=R(P);l!==h.current&&k(l)&&(A(l),b("Received",P))}),[i,e,a,R,k]);let w=ee(f=>{!a||!e||(i.params.set(e,f,"param"),i.edges.stopLoading(),b("Broadcast",f))},[i,a,e]),Q=ee(()=>{!a||n||(m.current&&(clearTimeout(m.current),m.current=null),i.edges.startLoading(),w(h.current),U.current=!1)},[i,w,a,n]),D=ee(f=>{m.current&&(clearTimeout(m.current),m.current=null),A(f),h.current=f,U.current=!1,!(!a||!e||n)&&(i.params.clear(e),i.edges.stopLoading(),b("Cleared",null))},[i,a,e,n]);K(()=>{C.current=!1},[e,a]),K(()=>{if(!a||!e||n||!o||C.current)return;let f=i.params.getSnapshot(e);if(f!=null){C.current=!0;return}if(h.current===""){C.current=!0;return}w(h.current),C.current=!0},[i,a,e,n,o,w]);let I=ee(f=>{A(f),!(!a||n)&&(U.current=!0,i.edges.startLoading(),m.current&&clearTimeout(m.current),m.current=setTimeout(()=>{w(f),U.current=!1},r))},[i,w,r,a,n]);return K(()=>()=>{m.current&&clearTimeout(m.current);let{enabled:f,param:P}=T.current;!f||!P||q.current.params.clear(P)},[]),{value:L,setValue:I,broadcastNow:Q,clearValue:D}}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 W(e){let r=x(),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=x(),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=x(),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 z,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=x(),s=z(()=>Qr(n),[n]),u=W(s),{inForm:i,values:c}=$(s),a=z(()=>i?{...u,...c}:u,[u,c,i]),b=z(()=>Vr.test(n),[n]),R=z(()=>b?"":Jr(n,a,t),[n,a,t,b]),k=z(()=>{if(!b)return[];let A=new Set,h=[];He.lastIndex=0;let m;for(;(m=He.exec(n))!==null;)A.has(m[1])||(A.add(m[1]),h.push(m[1]));return h},[n,b]),g=jr(o,k),[v,p]=We(()=>({key:"",value:""})),[M,E]=We(!1),O=z(()=>JSON.stringify({template:n,paramValues:a,preserveMissingParams:t,tick:g}),[n,a,t,g]);return Mr(()=>{if(!b){E(!1);return}let A=!1,h=new AbortController;return E(!0),o.template.render(n,a,{preserveMissingParams:t,signal:h.signal}).then(m=>{A||(p(U=>U.key===O&&U.value===m.value?U:{key:O,value:m.value}),E(m.loading))},m=>{A||m?.name!=="AbortError"&&E(!1)}),()=>{A=!0,h.abort()}},[o,b,n,a,t,O]),{value:z(()=>{if(!b)return R;if(v.key===O)return v.value;try{return o.template.renderLoading(n,a,{preserveMissingParams:t})}catch{return n}},[o,b,R,v,O,n,a,t]),loading:b?M:!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 a of r)c.push(e.udfs.subscribeOutput(a,i));return()=>c.forEach(a=>a())},[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=x(),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=x();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 a=!1,b=n?.data;if(!b||!Zr(b)){s([]),i([]);return}return(async()=>{try{let R=await b.getRows(0,Math.max(0,r));if(a)return;let k=R.map(v=>{let p=v;return p&&typeof p=="object"&&p.properties&&typeof p.properties=="object"?p.properties:v});s(k);let g=Array.from(new Set(k.flatMap(v=>Object.keys(v??{}))));i(g)}catch{if(a)return;s([]),i([])}})(),()=>{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 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 G,useMemo as _,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=x(),{configHash:r}=H(),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=x(),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=x();return se(()=>{e&&r.log.clearLogs(e)},[r,e])}function ue(){let e=x();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,Z=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:a,key:b,resolvedOverrides:R}=r[c],g=R===null&&t?t[a.name]:void 0,v;g?v=gn(g.relationName):v=`'${n.get(b)??`${a.name}.parquet`}'`,i=i.slice(0,a.start)+v+i.slice(a.end)}return i=lr(i,s),nr(i,o)}function fr({sql:e,enabled:r=!0,maxRows:n=dr,sourceOverrides:t}){let o=x(),s=we(),u=_(()=>{let l=Object.keys(s).length>0;return t?l?{...s,...t}:t:l?s:void 0},[s,t]),{startLoading:i,stopLoading:c}=ue(),{log:a}=ie(),[b,R]=V(""),[k,g]=V(!1),[v,p]=V(null),[M,E]=V(0),O=_(()=>e?or(e):[],[e]),L=_(()=>u?O.filter(l=>l.overrides!==null||!u[l.name]):O,[O,u]),A=_(()=>{let l=new Set;for(let S of L)if(S.overrides)for(let d of Object.values(S.overrides)){let B=ae(d);B&&l.add(B)}return Array.from(l)},[L]),h=_(()=>e?tr(e):[],[e]),m=_(()=>{let l=new Set,S=[];for(let d of h)l.has(d)||(l.add(d),S.push(d));for(let d of A)l.has(d)||(l.add(d),S.push(d));return S},[h,A]),U=W(m),{inForm:C,values:T}=$(m),q=_(()=>C?{...U,...T}:U,[C,U,T]),N=_(()=>L.map(l=>{if(!l.overrides)return{raw:l,key:l.name,resolvedOverrides:null,unresolved:!1};let S={},d=!1;for(let[B,J]of Object.entries(l.overrides)){let y=le(J,q);y.unresolved&&(d=!0),S[B]=y.value}return{raw:l,key:ar(l.name,S),resolvedOverrides:S,unresolved:d}}),[L,q]),w=_(()=>{let l=new Set,S=[];for(let d of N)d.unresolved||l.has(d.key)||(l.add(d.key),S.push({name:d.raw.name,key:d.key,overrides:d.resolvedOverrides??void 0}));return S},[N]),Q=_(()=>w.map(l=>`${l.key}|${l.name}|${l.overrides?Object.entries(l.overrides).sort(([S],[d])=>S.localeCompare(d)).map(([S,d])=>`${S}=${d}`).join(","):""}`).join(`
2
+ `),[w]),D=cr(()=>{E(l=>l+1)},[]);G(()=>{if(!r||w.length===0)return;let l=w.map(S=>o.udfs.subscribeOutput(S.name,()=>{E(d=>d+1)}));return()=>{l.forEach(S=>S())}},[o,r,w]),G(()=>{k?i():c()},[k,i,c]);let I=_(()=>{if(!u)return null;for(let l of O){if(l.overrides!==null)continue;let S=u[l.name];if(S?.error)return S.error}return null},[O,u]),f=_(()=>u?O.some(l=>l.overrides===null&&u[l.name]?.loading):!1,[O,u]),P=N.some(l=>l.unresolved);return G(()=>{if(!r||!e){R(""),g(!1),p(null);return}if(I){R(""),p(I),g(!1),a(`SQL preprocessing: ${I}`,"error");return}if(f||P){R(""),p(null),g(!0);return}let l=!1;return g(!0),p(null),(async()=>{let S=new Map,d;if(w.length>0)try{let y=await o.sql.resolveVfsFilenames(w);if(l)return;if(y instanceof Map)for(let F of N){if(F.resolvedOverrides)continue;let X=y.get(F.raw.name);X&&S.set(F.key,X)}else S=y.filenames,d=y.errors}catch(y){if(l)return;let F=y instanceof Error?y.message:typeof y=="string"?y:"VFS registration failed";R(""),p(F),g(!1),a(`SQL preprocessing: ${F}`,"error");return}if(d)for(let y of N){let F=d.get(y.key);if(F){if(l)return;R(""),p(F),g(!1),a(`SQL preprocessing: ${F}`,"error");return}}for(let y of N)if(!(y.unresolved||y.resolvedOverrides===null&&u?.[y.raw.name]!==void 0)&&!S.has(y.key)){if(l)return;R(""),p(null),g(!0);return}let B;try{B=pn(e,N,S,u,q,n)}catch(y){if(l)return;let F=y instanceof Error?y.message:typeof y=="string"?y:"SQL preprocessing failed";R(""),p(F),g(!1),a(`SQL preprocessing failed: ${F}`,"error");return}let J=sr(B);if(J.length===0){if(l)return;R(B),p(null),g(!1),a("SQL preprocessing completed");return}try{let y={},F=await Promise.all(J.map(de=>o.signUrl(de)));if(l)return;J.forEach((de,Sr)=>{y[de]=F[Sr].signed});let X=ir(B,y);R(X),p(null),g(!1),a("SQL preprocessing completed")}catch(y){if(l)return;let F=y instanceof Error?y.message:typeof y=="string"?y:"URL signing failed";R(""),p(F),g(!1),a(`SQL preprocessing failed: ${F}`,"error")}})(),()=>{l=!0}},[o,r,e,Q,N,q,u,I,f,P,n,M,a,w]),{processedSql:b,loading:k,error:v,refetch:D}}function mn({sql:e,enabled:r=!0,maxRows:n=dr,sourceOverrides:t,queryId:o,refreshInterval:s}){let u=x(),{startLoading:i,stopLoading:c}=ue(),{log:a}=ie(),{queryId:b,refreshInterval:R}=be(),k=o??b,g=s??R,[v,p]=V(Z),[M,E]=V(Y),[O,L]=V(!1),[A,h]=V(null),[m,U]=V(0),{processedSql:C,loading:T,error:q,refetch:N}=fr({sql:e,enabled:r,maxRows:n,sourceOverrides:t}),w=fn(""),Q=r&&!!e&&!!C&&!T&&!q&&C!==w.current,D=T||O||Q;G(()=>{D?i():c()},[D,i,c]);let I=cr(()=>{w.current="",N(),U(f=>f+1)},[N]);return G(()=>{if(!r||!e){w.current="",p(Z),E(Y),L(!1),h(null);return}if(q){w.current="",h(q),p(Z),E(Y),L(!1);return}if(T||!C){w.current="",L(!1);return}let f=!1,P=new AbortController;w.current=C,L(!0),h(null);let l=C.length>120?C.slice(0,120)+"\u2026":C;a(`SQL query started: ${l}`);let S=performance.now();return u.sql.query(C,{signal:P.signal,queryId:k,refreshInterval:g}).then(d=>{if(f)return;let B=Math.round(performance.now()-S);if(d.error){p(Z),E(Y),h(d.error),L(!1),a(`SQL failed (${B}ms): ${d.error}`,"error");return}p(d.rows.length===0?Z:d.rows),E(d.columns),h(null),L(!1),a(`SQL completed: ${d.rows.length} row${d.rows.length!==1?"s":""} in ${B}ms`)},d=>{if(f||d?.name==="AbortError")return;let B=Math.round(performance.now()-S),J=d instanceof Error?d.message:typeof d=="string"?d:"SQL query failed";h(J),p(Z),E(Y),L(!1),a(`SQL failed (${B}ms): ${J}`,"error")}),()=>{f=!0,P.abort()}},[u,r,e,C,T,q,m,a,k,g]),{rows:v,columns:M,loading:D,error:A,refetch:I}}function Sn(e,r=!0){let n=x(),[t,o]=V({filenames:new Map,loading:!1}),s=_(()=>e.slice().sort().join("|"),[e]);return G(()=>{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=x();return{signUrl:gr(n=>e.signUrl(n),[e])}}function Rn(e){let r=x(),{value:n,loading:t}=ye(e),[o,s]=ce(null),[u,i]=ce(null),[c,a]=ce(!1),[b,R]=ce(0);yn(()=>{if(t)return;if(!n){s(null),i(null),a(!1);return}if(!Pe(n)){s(n),i(null),a(!1);return}let g=!1;return a(!0),i(null),r.signUrl(n).then(({signed:v})=>{g||s(v??n)}).catch(v=>{g||(i(v instanceof Error?v.message:"Failed to load media"),s(null))}).finally(()=>{g||a(!1)}),()=>{g=!0}},[r,t,n,b]);let k=gr(async()=>{if(!n||!Pe(n))return n??null;let{signed:g}=await r.signUrl(n),v=g??n;return s(v),i(null),R(p=>p+1),v},[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=x(),[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 H()}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=x(),{format:t}=r,o=Le(()=>Oe(e),[e]),s=Le(()=>{if(!o)return[];let m=new Set,U=[];for(let C of Object.values(o.overrides)){let T=ae(C);T&&!m.has(T)&&(m.add(T),U.push(T))}return U},[o]),u=W(s),{inForm:i,values:c}=$(s),a=Le(()=>i?{...u,...c}:u,[u,c,i]),b=Se(),R=mr({parsed:o,paramValues:a,allowedUdfNames:b,format:t});R.current={parsed:o,paramValues:a,allowedUdfNames:b,format:t};let[k,g]=Fe("idle"),[v,p]=Fe(null),[M,E]=Fe(null),O=mr(0),L=Ae(()=>{O.current+=1,g("idle"),p(null),E(null)},[]),A=Ae(m=>(g("error"),E(m),p(null),{data:null,error:m}),[]);return{fire:Ae(async m=>{let{parsed:U,paramValues:C,allowedUdfNames:T,format:q}=R.current;if(!U)return A("No UDF to run (empty or invalid executor)."),null;if(T&&!T.has(U.name))return A(`UDF "${U.name}" is not reachable from this node \u2014 connect it with an edge.`);let N={};for(let[D,I]of Object.entries(U.overrides))N[D]=le(I,C).value;m&&Object.assign(N,m);let w=++O.current,Q=()=>w===O.current;g("running"),E(null),n.edges.startLoading();try{let D=await n.udfs.execute(U.name,N,{format:q});return Q()&&(D.error?(g("error"),E(D.error),p(null)):(g("success"),p(D.data),E(null))),D}catch(D){let I=D instanceof Error?D.message:"UDF execution failed.";return Q()&&(g("error"),E(I),p(null)),{data:null,error:I}}finally{n.edges.stopLoading()}},[n,A]),status:k,isRunning:k==="running",data:v,error:M,canFire:o!==null,reset:L}}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,W as useCanvasParams,mn as useDuckDbSqlQuery,fr as useDuckDbSqlQueryPreprocessing,ge as useFormContext,$ as useFormParams,pe as useFusedParam,Cr as useFusedParamWithForm,x as useFusedWidgetBridge,be as useJsonUiBinding,ue as useJsonUiEdgeAnimation,ie as useJsonUiLog,on as useJsonUiLogClear,tn as useJsonUiLogs,H 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};
@@ -1,6 +1,8 @@
1
1
  export interface JsonUiBinding {
2
2
  /** Resolver-stamped query id for this node (e.g. `"q0"`); undefined elsewhere. */
3
3
  queryId?: string;
4
+ /** Resolver-stamped refresh interval (ms) for this node; undefined in non-server-resolved hosts. */
5
+ refreshInterval?: number;
4
6
  }
5
7
  export declare const JsonUiBindingContext: import("react").Context<JsonUiBinding>;
6
8
  /** Read the current node's data-binding identity. `{}` when no provider is present. */
@@ -11,7 +11,9 @@ exports.useJsonUiBinding = useJsonUiBinding;
11
11
  * SDK data hooks (currently `useDuckDbSqlQuery`) read the id via
12
12
  * `useJsonUiBinding()` and thread it into `bridge.sql.query(sql, { queryId })`,
13
13
  * letting the static MCP bridge look up the pre-resolved rows by id instead of
14
- * running DuckDB in the sandbox.
14
+ * running DuckDB in the sandbox. The binding also carries an optional
15
+ * `refreshInterval` (ms) the host stamps alongside `_queryId`, parallel to the
16
+ * query id, so a server-resolved host knows this node's auto-refresh cadence.
15
17
  *
16
18
  * The default value is `{}` (no binding). In every other host (workbench, test
17
19
  * harness, mobile) the provider is absent, `queryId` is `undefined`, and the
@@ -30,6 +30,14 @@ export interface UseDuckDbSqlQueryOptions {
30
30
  * `bridge.sql.query(sql, { queryId })`; the workbench bridge ignores it.
31
31
  */
32
32
  queryId?: string;
33
+ /**
34
+ * Author-declared auto-refresh cadence for this query, in **milliseconds**.
35
+ * The refresh timer is provided by the host runtime, **not** this SDK — in
36
+ * hosts without refresh support (e.g. the workbench) the field is inert and
37
+ * behavior is unchanged. An explicit option wins over the binding-provided
38
+ * value. Threaded into `bridge.sql.query(sql, { refreshInterval })`.
39
+ */
40
+ refreshInterval?: number;
33
41
  }
34
42
  export interface UseDuckDbSqlQueryResult {
35
43
  rows: ReadonlyArray<Record<string, unknown>>;
@@ -55,7 +63,7 @@ export declare function useDuckDbSqlQueryPreprocessing({ sql, enabled, maxRows,
55
63
  * `useDuckDbSqlQueryPreprocessing` to prepare the SQL string, then runs
56
64
  * it via the bridge.
57
65
  */
58
- export declare function useDuckDbSqlQuery({ sql, enabled, maxRows, sourceOverrides, queryId: queryIdOption, }: UseDuckDbSqlQueryOptions): UseDuckDbSqlQueryResult;
66
+ export declare function useDuckDbSqlQuery({ sql, enabled, maxRows, sourceOverrides, queryId: queryIdOption, refreshInterval: refreshIntervalOption, }: UseDuckDbSqlQueryOptions): UseDuckDbSqlQueryResult;
59
67
  /**
60
68
  * Resolve UDF names to VFS filenames, registering them in DuckDB if needed.
61
69
  * Exposed for advanced use cases (e.g. building your own query string).
@@ -423,15 +423,20 @@ function useDuckDbSqlQueryPreprocessing({ sql, enabled = true, maxRows = DEFAULT
423
423
  * `useDuckDbSqlQueryPreprocessing` to prepare the SQL string, then runs
424
424
  * it via the bridge.
425
425
  */
426
- function useDuckDbSqlQuery({ sql, enabled = true, maxRows = DEFAULT_MAX_ROWS, sourceOverrides, queryId: queryIdOption, }) {
426
+ function useDuckDbSqlQuery({ sql, enabled = true, maxRows = DEFAULT_MAX_ROWS, sourceOverrides, queryId: queryIdOption, refreshInterval: refreshIntervalOption, }) {
427
427
  const bridge = (0, bridge_1.useFusedWidgetBridge)();
428
428
  const { startLoading: startEdgeLoading, stopLoading: stopEdgeLoading } = (0, use_json_ui_edge_animation_1.useJsonUiEdgeAnimation)();
429
429
  const { log } = (0, use_json_ui_log_1.useJsonUiLog)();
430
430
  // Server-resolved data seam: explicit option wins, else fall back to the
431
431
  // per-node binding context. `undefined` in every non-MCP host (no behavior
432
432
  // change for the workbench).
433
- const { queryId: queryIdBinding } = (0, json_ui_binding_1.useJsonUiBinding)();
433
+ const { queryId: queryIdBinding, refreshInterval: refreshIntervalBinding } = (0, json_ui_binding_1.useJsonUiBinding)();
434
434
  const queryId = queryIdOption ?? queryIdBinding;
435
+ // Passthrough metadata for the host's refresh timer (Layer 2). The workbench
436
+ // bridge ignores it; a server-resolved host reads it off the query call.
437
+ // Kept in the query effect's deps (like queryId) so the host never receives a
438
+ // stale interval — it's stable in practice, so this triggers no extra fetches.
439
+ const refreshInterval = refreshIntervalOption ?? refreshIntervalBinding;
435
440
  const [rows, setRows] = (0, react_1.useState)(EMPTY_ROWS);
436
441
  const [columns, setColumns] = (0, react_1.useState)(EMPTY_COLUMNS);
437
442
  const [queryLoading, setQueryLoading] = (0, react_1.useState)(false);
@@ -496,7 +501,9 @@ function useDuckDbSqlQuery({ sql, enabled = true, maxRows = DEFAULT_MAX_ROWS, so
496
501
  : processedSql;
497
502
  log(`SQL query started: ${truncatedSql}`);
498
503
  const t0 = performance.now();
499
- bridge.sql.query(processedSql, { signal: controller.signal, queryId }).then((result) => {
504
+ bridge.sql
505
+ .query(processedSql, { signal: controller.signal, queryId, refreshInterval })
506
+ .then((result) => {
500
507
  if (cancelled)
501
508
  return;
502
509
  const elapsed = Math.round(performance.now() - t0);
@@ -544,6 +551,7 @@ function useDuckDbSqlQuery({ sql, enabled = true, maxRows = DEFAULT_MAX_ROWS, so
544
551
  fetchKey,
545
552
  log,
546
553
  queryId,
554
+ refreshInterval,
547
555
  ]);
548
556
  return { rows, columns, loading, error, refetch };
549
557
  }
@@ -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,6 +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";
37
38
  export { JsonUiBindingContext, useJsonUiBinding, type JsonUiBinding, } from "./hooks/json-ui-binding";
38
39
  export * from "./utils/sql-placeholders";
39
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.useJsonUiBinding = exports.JsonUiBindingContext = 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,6 +84,8 @@ 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; } });
87
89
  var json_ui_binding_1 = require("./hooks/json-ui-binding");
88
90
  Object.defineProperty(exports, "JsonUiBindingContext", { enumerable: true, get: function () { return json_ui_binding_1.JsonUiBindingContext; } });
89
91
  Object.defineProperty(exports, "useJsonUiBinding", { enumerable: true, get: function () { return json_ui_binding_1.useJsonUiBinding; } });
@@ -91,3 +93,5 @@ Object.defineProperty(exports, "useJsonUiBinding", { enumerable: true, get: func
91
93
  __exportStar(require("./utils/sql-placeholders"), exports);
92
94
  var parse_style_1 = require("./utils/parse-style");
93
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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fusedio/widget-sdk",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "SDK for building custom json-ui components for the Fused workbench",
5
5
  "license": "Apache-2.0",
6
6
  "main": "dist/index.js",