@fusedio/widget-sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/README.md +169 -0
  2. package/dist/bridge.d.ts +247 -0
  3. package/dist/bridge.js +61 -0
  4. package/dist/bundle.js +2 -0
  5. package/dist/define-catalog.d.ts +53 -0
  6. package/dist/define-catalog.js +3 -0
  7. package/dist/define-component.d.ts +93 -0
  8. package/dist/define-component.js +43 -0
  9. package/dist/form.d.ts +49 -0
  10. package/dist/form.js +136 -0
  11. package/dist/hooks/use-allowed-sources.d.ts +20 -0
  12. package/dist/hooks/use-allowed-sources.js +49 -0
  13. package/dist/hooks/use-allowed-udf-names.d.ts +15 -0
  14. package/dist/hooks/use-allowed-udf-names.js +42 -0
  15. package/dist/hooks/use-canvas-params.d.ts +13 -0
  16. package/dist/hooks/use-canvas-params.js +58 -0
  17. package/dist/hooks/use-duckdb-sql.d.ts +61 -0
  18. package/dist/hooks/use-duckdb-sql.js +558 -0
  19. package/dist/hooks/use-fused-param.d.ts +40 -0
  20. package/dist/hooks/use-fused-param.js +283 -0
  21. package/dist/hooks/use-json-ui-edge-animation.d.ts +22 -0
  22. package/dist/hooks/use-json-ui-edge-animation.js +26 -0
  23. package/dist/hooks/use-json-ui-log.d.ts +33 -0
  24. package/dist/hooks/use-json-ui-log.js +74 -0
  25. package/dist/hooks/use-json-ui-udf-info.d.ts +24 -0
  26. package/dist/hooks/use-json-ui-udf-info.js +23 -0
  27. package/dist/hooks/use-param-substitution.d.ts +22 -0
  28. package/dist/hooks/use-param-substitution.js +207 -0
  29. package/dist/hooks/use-udf-output.d.ts +85 -0
  30. package/dist/hooks/use-udf-output.js +202 -0
  31. package/dist/hooks/use-upload-access-check.d.ts +19 -0
  32. package/dist/hooks/use-upload-access-check.js +39 -0
  33. package/dist/hooks/use-url-signing.d.ts +42 -0
  34. package/dist/hooks/use-url-signing.js +101 -0
  35. package/dist/index.d.ts +35 -0
  36. package/dist/index.js +40 -0
  37. package/dist/protocol.d.ts +39 -0
  38. package/dist/protocol.js +32 -0
  39. package/dist/types.d.ts +84 -0
  40. package/dist/types.js +1 -0
  41. package/dist/utils/sql-placeholders.d.ts +80 -0
  42. package/dist/utils/sql-placeholders.js +204 -0
  43. package/package.json +36 -0
package/README.md ADDED
@@ -0,0 +1,169 @@
1
+ # @fusedio/widget-sdk
2
+
3
+ React hooks and types for building custom **json-ui components** that run
4
+ inside the [Fused](https://fused.io) workbench canvas.
5
+
6
+ If you want to build a 3rd-party component catalog that the Fused workbench
7
+ can load — a custom chart, a custom input, a domain-specific widget — this
8
+ SDK is the contract your components depend on.
9
+
10
+ > **Status:** pre-1.0. The public hook surface is stabilising but minor
11
+ > breaking changes may still happen between `0.x` releases. The bridge
12
+ > interface (for host implementers) is more volatile and may grow.
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ npm install @fusedio/widget-sdk
18
+ # or
19
+ bun add @fusedio/widget-sdk
20
+ ```
21
+
22
+ React ≥18 is a peer dependency.
23
+
24
+ ## The shape of a component
25
+
26
+ Every component receives a single `element` prop. Read your props out of
27
+ `element.props`, return JSX.
28
+
29
+ ```tsx
30
+ import { useFusedParam, type ComponentRenderProps } from "@fusedio/widget-sdk";
31
+
32
+ interface CounterProps {
33
+ param: string;
34
+ label?: string;
35
+ step?: number;
36
+ }
37
+
38
+ export function Counter({ element }: ComponentRenderProps<CounterProps>) {
39
+ const { param, label = "Count", step = 1 } = element.props;
40
+ const { value, setValue } = useFusedParam({ param, defaultValue: 0 });
41
+
42
+ return (
43
+ <button onClick={() => setValue(value + step)}>
44
+ {label}: {value}
45
+ </button>
46
+ );
47
+ }
48
+ ```
49
+
50
+ When the user clicks, `setValue` broadcasts the new value to the canvas;
51
+ any connected UDF re-runs with the updated parameter.
52
+
53
+ ## What the hooks let you do
54
+
55
+ | Hook | What it does |
56
+ | ------------------------------ | ------------------------------------------------------------------- |
57
+ | `useFusedParam` | Two-way bind a component to a canvas parameter (debounced). |
58
+ | `useCanvasParams` | Read multiple canvas parameter values at once (edge-filtered). |
59
+ | `useParamSubstitution` | Resolve `$param` and `{{udf}}` placeholders inside a template. |
60
+ | `useUdfOutputByName` | Subscribe to a UDF's output, status, error. |
61
+ | `useUdfColumnValue` / `Values` | Pull values out of `{{udf.col}}` / `{{udf.col[idx]}}` queries. |
62
+ | `useUdfDataFrameSample` | Sample rows from a UDF's DataFrame output. |
63
+ | `useDuckDbSqlQuery` | Run a DuckDB-WASM query against UDF parquet outputs in the browser. |
64
+ | `useUrlSigning` / `useMediaSrc` | Sign `s3://`, `gs://`, `fd://` URLs and resolve media sources. |
65
+ | `useUploadAccessCheck` | Pre-flight an upload destination for write access. |
66
+ | `useAllowedSources` | Which UDFs are allowed to broadcast to this node? |
67
+ | `useAllowedUdfNames` | Set of UDF names this node may reference. |
68
+ | `useJsonUiEdgeAnimation` | Animate the canvas edge pellet around custom async work. |
69
+ | `useJsonUiLog` | Write entries to the runtime logs panel. |
70
+ | `useJsonUiUdfInfo` | Current node identity (`udfName`, `udfUniqueId`, `configHash`). |
71
+
72
+ Every hook ships with `@example` blocks in its TypeScript declarations —
73
+ hover any import in your editor for the full signature, defaults, and
74
+ usage notes.
75
+
76
+ ## Param flow at a glance
77
+
78
+ ```
79
+ Component A Component B
80
+ useFusedParam("city") useFusedParam("city")
81
+ │ ▲
82
+ ▼ │
83
+ setValue("NYC") │
84
+ │ │
85
+ ▼ │
86
+ Fused workbench │
87
+ ─ broadcasts on BroadcastChannel ─► edge-filtered routing
88
+
89
+
90
+ value = "NYC"
91
+ ```
92
+
93
+ - Values broadcast over a same-origin
94
+ `BroadcastChannel("parameter-updates")`.
95
+ - The workbench filters by canvas edges: a component only **receives**
96
+ values from upstream-connected nodes.
97
+ - `setValue` debounces (300ms default). Use `broadcastNow` to flush
98
+ immediately (e.g. `onMouseUp` of a slider). Use `clearValue` to reset
99
+ and notify the canvas.
100
+
101
+ ## Running against the workbench
102
+
103
+ In the deployed Fused workbench your component is loaded as part of a
104
+ **catalog bundle**: a single ESM file built with `esbuild` that the user
105
+ adds to the workbench via *Settings → Custom Catalogs*. A minimal build
106
+ looks like:
107
+
108
+ ```bash
109
+ esbuild src/index.ts --bundle --format=esm --outfile=dist/catalog.esm.js \
110
+ --external:react --external:react/jsx-runtime \
111
+ --external:@fusedio/widget-sdk --external:zod
112
+ ```
113
+
114
+ These four specifiers are marked `external` because the workbench injects
115
+ a runtime import map that resolves them to its own already-loaded React,
116
+ SDK, and Zod instances — guaranteeing one React instance, one SDK
117
+ instance, one Zod instance across host + every loaded catalog. Without
118
+ that, hook calls hit a different React instance ("invalid hook call"),
119
+ and Zod schemas fail `instanceof` checks inside the workbench's
120
+ `z.toJSONSchema(...)` pass.
121
+
122
+ A starter template (with a local sandbox, hot reload, and a GitHub Action
123
+ that publishes the built bundle) will be linked here once it's published.
124
+
125
+ ## Architecture
126
+
127
+ This SDK is a thin shell:
128
+
129
+ - **`FusedWidgetBridge`** is the interface the host implements (canvas
130
+ params, UDF outputs, SQL execution, URL signing, …).
131
+ - **Hooks** read the bridge from `FusedWidgetBridgeContext` and adapt it to
132
+ React via `useSyncExternalStore`.
133
+ - **Pure utilities** (`utils/sql-placeholders.ts`) parse SQL templates with
134
+ no host dependencies.
135
+
136
+ The SDK itself does **no I/O, no fetches, no auth, no storage**. Every
137
+ side effect goes through the bridge — which the host (the Fused workbench
138
+ or your test harness) provides. This is why the same component code runs
139
+ unchanged in the workbench, in a local sandbox, and in any future host.
140
+
141
+ ## Implementing a custom host (advanced)
142
+
143
+ Hosting json-ui components outside the Fused workbench:
144
+
145
+ ```tsx
146
+ import {
147
+ FusedWidgetBridgeContext,
148
+ type FusedWidgetBridge,
149
+ } from "@fusedio/widget-sdk";
150
+
151
+ const myBridge: FusedWidgetBridge = {
152
+ params: { /* … */ },
153
+ udfs: { /* … */ },
154
+ // … everything else
155
+ };
156
+
157
+ <FusedWidgetBridgeContext.Provider value={myBridge}>
158
+ {/* render json-ui components here */}
159
+ </FusedWidgetBridgeContext.Provider>
160
+ ```
161
+
162
+ You'll need to implement every sub-bridge (`params`, `udfs`, `routing`,
163
+ `sql`, `template`, `uploads`, `edges`, `log`, plus `signUrl` and `node`).
164
+ The exhaustive interface is exported as `FusedWidgetBridge` — hover it in
165
+ your editor for the full type.
166
+
167
+ ## License
168
+
169
+ Apache-2.0
@@ -0,0 +1,247 @@
1
+ import type { ParameterMessageType } from "./protocol";
2
+ export interface FusedWidgetBridge {
3
+ /** Canvas parameter state (edge-filtered, two-way). */
4
+ params: ParamBridge;
5
+ /** UDF output data and re-execution. */
6
+ udfs: UdfBridge;
7
+ /** Identity of the current canvas node. */
8
+ node: NodeIdentity;
9
+ /** Edge animation control. */
10
+ edges: EdgeAnimationBridge;
11
+ /** Edge-based routing capabilities. */
12
+ routing: RoutingBridge;
13
+ /** DuckDB SQL execution against UDF Parquet outputs. */
14
+ sql: SqlBridge;
15
+ /** Template rendering for `$param` + `{{udf}}` substitution. */
16
+ template: TemplateBridge;
17
+ /** File-upload access checks. */
18
+ uploads: UploadBridge;
19
+ /** Sign an S3/GCS/FD URL with the current user's access token. */
20
+ signUrl(url: string): Promise<SignUrlResult>;
21
+ /** Per-node logging (visible in the runtime logs panel). */
22
+ log: LogBridge;
23
+ }
24
+ export interface ParamBridge {
25
+ /**
26
+ * Subscribe to changes for a single canvas parameter.
27
+ * Designed for use with React.useSyncExternalStore — callback takes no args.
28
+ * Returns an unsubscribe function.
29
+ */
30
+ subscribe(param: string, cb: () => void): () => void;
31
+ /** Synchronously read the current edge-filtered value for a param. */
32
+ getSnapshot(param: string): unknown;
33
+ /** Subscribe to changes for *any* of a list of params. */
34
+ subscribeMany(params: readonly string[], cb: () => void): () => void;
35
+ /** Read snapshot for many params at once (edge-filtered). */
36
+ getSnapshotMany(params: readonly string[]): Record<string, unknown>;
37
+ /** Broadcast a parameter value to the canvas (typed). */
38
+ set(param: string, value: unknown, type?: ParameterMessageType): void;
39
+ /** Clear a parameter: send CLEAR for this source. */
40
+ clear(param: string): void;
41
+ }
42
+ export interface UdfBridge {
43
+ /** Subscribe to changes in a UDF's results (output data + execution status). */
44
+ subscribeOutput(udfName: string, cb: () => void): () => void;
45
+ /** Get the current snapshot of a UDF's results. */
46
+ getOutputSnapshot(udfName: string): UdfOutputSnapshot | undefined;
47
+ /** Request the workbench to re-execute a UDF. */
48
+ requestReexecute(udfName: string): void;
49
+ }
50
+ export interface UdfOutputSnapshot {
51
+ /** The UDF result data — TableDataSource, HTML blob, array, etc. */
52
+ data: unknown;
53
+ /** True while the UDF is currently executing. */
54
+ isExecutionInProgress: boolean;
55
+ /** Error message if the most recent execution failed. */
56
+ error?: string;
57
+ /** VFS filename for DuckDB queries (e.g. `"<udfName>.parquet"`). */
58
+ vfsFilename?: string;
59
+ }
60
+ export interface NodeIdentity {
61
+ /** Unique ID of the current canvas node (regenerated on page reload). */
62
+ udfUniqueId?: string;
63
+ /** Human-readable name of the current node (stable across reloads). */
64
+ udfName?: string;
65
+ /** Hash of the current widget JSON config. Changes when the JSON is edited. */
66
+ configHash?: string;
67
+ }
68
+ export interface EdgeAnimationBridge {
69
+ /** Start the edge-animating loading state for the current node. */
70
+ startLoading(): void;
71
+ /** End the loading state — fires the edge pellet on the true→false transition. */
72
+ stopLoading(): void;
73
+ }
74
+ /** Identity of a UDF allowed to broadcast params to the current node. */
75
+ export interface AllowedSource {
76
+ udfUniqueId?: string;
77
+ udfName?: string;
78
+ }
79
+ export interface RoutingBridge {
80
+ /** Subscribe to changes in allowed sources (canvas topology changes). */
81
+ subscribeAllowedSources(cb: () => void): () => void;
82
+ /** Get the set of UDF names this node may reference. `null` = no filtering. */
83
+ getAllowedUdfNames(): Set<string> | null;
84
+ /** Get the allowed source identities for this node. `null` = no filtering. */
85
+ getAllowedSources(): ReadonlyArray<AllowedSource> | null;
86
+ }
87
+ export interface SqlQueryOptions {
88
+ defaultLimit?: number;
89
+ signal?: AbortSignal;
90
+ }
91
+ export interface SqlQueryResult {
92
+ rows: ReadonlyArray<Record<string, unknown>>;
93
+ columns: readonly string[];
94
+ error?: string;
95
+ }
96
+ /**
97
+ * A reference to a `{{udf}}` or `{{udf?k=v}}` placeholder, used by
98
+ * `bridge.sql.resolveVfsFilenames` to register UDFs (including overrides).
99
+ *
100
+ * `key` is the canonical registry key that consumers should use to look up
101
+ * the resolved filename in the returned Map. Bare `{{udf}}` references use
102
+ * `key === name`; override references use `computePlaceholderKey(name, overrides)`.
103
+ */
104
+ export interface VfsResolveRef {
105
+ name: string;
106
+ key: string;
107
+ overrides?: Record<string, string>;
108
+ }
109
+ export interface VfsResolveResult {
110
+ /** Map of `ref.key` → resolved VFS filename (e.g. `"my_udf.parquet"`). */
111
+ filenames: Map<string, string>;
112
+ /** Per-key error messages for refs that failed to register. */
113
+ errors?: Map<string, string>;
114
+ }
115
+ export interface SqlBridge {
116
+ /** Execute a SQL query against UDF Parquet outputs via DuckDB. */
117
+ query(sql: string, options?: SqlQueryOptions): Promise<SqlQueryResult>;
118
+ /**
119
+ * Resolve UDF references to VFS filenames; registers any UDFs (and
120
+ * override variants) not yet in VFS. Returns a map keyed by `ref.key`.
121
+ *
122
+ * When called with bare string names, the legacy `Map<name, filename>`
123
+ * shape is preserved for backward compatibility with the previous bridge
124
+ * surface — but new callers should pass `VfsResolveRef[]`.
125
+ */
126
+ resolveVfsFilenames(refs: readonly VfsResolveRef[] | readonly string[]): Promise<VfsResolveResult | Map<string, string>>;
127
+ }
128
+ export interface TemplateRenderOptions {
129
+ /** When true, leave unresolved `$param` tokens intact rather than replacing with empty string. */
130
+ preserveMissingParams?: boolean;
131
+ /** Optional cancellation signal. */
132
+ signal?: AbortSignal;
133
+ }
134
+ export interface TemplateRenderResult {
135
+ /** The rendered string with `$param` and `{{udf}}` placeholders replaced. */
136
+ value: string;
137
+ /** True if any UDF placeholder is still loading (data not yet available). */
138
+ loading: boolean;
139
+ }
140
+ export interface TemplateBridge {
141
+ /**
142
+ * Asynchronously render a template containing `$param` and `{{udf}}`
143
+ * placeholders. Resolves UDF dependencies, fetches override variants if
144
+ * needed, and stringifies the result.
145
+ *
146
+ * The host owns the rendering machinery (UDF result access, allowed-UDF
147
+ * routing, HTML template node recursion, override fetching). The SDK
148
+ * orchestrates re-runs when params change.
149
+ */
150
+ render(template: string, paramValues: Record<string, unknown>, options?: TemplateRenderOptions): Promise<TemplateRenderResult>;
151
+ /**
152
+ * Synchronously render a loading placeholder for the template (used to
153
+ * keep the UI populated while async render is in flight). Should not
154
+ * touch any UDFs — only `$param` substitution and best-effort HTML
155
+ * template node substitution from already-available data.
156
+ */
157
+ renderLoading(template: string, paramValues: Record<string, unknown>, options?: TemplateRenderOptions): string;
158
+ /**
159
+ * Subscribe to events that should cause a re-render: UDF outputs changing,
160
+ * topology shifts, etc. The callback should be invoked any time
161
+ * `render()` could now produce a different result for the *same* inputs.
162
+ */
163
+ subscribe(cb: () => void): () => void;
164
+ }
165
+ export interface UploadAccessResult {
166
+ ok: boolean;
167
+ /** When ok=false, a human-readable message; otherwise omitted. */
168
+ message?: string;
169
+ }
170
+ export interface UploadBridge {
171
+ /**
172
+ * Check whether the current user has write access to a destination path
173
+ * (S3, GCS, etc.). Used by the `file-upload` widget to surface a clear
174
+ * error before the user attempts to upload.
175
+ */
176
+ checkAccess(destinationPath: string): Promise<UploadAccessResult>;
177
+ }
178
+ export interface SignUrlResult {
179
+ /** The signed URL (or the original URL if signing was not needed). */
180
+ signed: string;
181
+ /** True if the URL needed signing (false for non-S3/GCS/FD URLs). */
182
+ needsSigning: boolean;
183
+ }
184
+ export type JsonUiLogLevel = "info" | "warn" | "error";
185
+ export interface LogEntry {
186
+ /** Epoch millis of when this entry was created. */
187
+ timestamp: number;
188
+ level: JsonUiLogLevel;
189
+ message: string;
190
+ /** Hash of the widget config that produced this entry (for staleness detection). */
191
+ configHash?: string;
192
+ }
193
+ export interface LogBridge {
194
+ /**
195
+ * Append a log entry for the current node.
196
+ *
197
+ * `configHash` is optional — when provided (by the SDK's `useJsonUiLog`,
198
+ * which reads it from `JsonUiNodeOverrideContext`), the entry is tagged
199
+ * with that hash so nested `JsonUiConfigHashOverride` subtrees emit
200
+ * correctly-scoped entries without rebuilding the bridge. When omitted,
201
+ * the bridge falls back to its own node identity.
202
+ */
203
+ log(message: string, level?: JsonUiLogLevel, configHash?: string): void;
204
+ /** Subscribe to log changes for a node. */
205
+ subscribeLogs(nodeId: string, cb: () => void): () => void;
206
+ /** Get the log entries snapshot for a node. */
207
+ getLogsSnapshot(nodeId: string): readonly LogEntry[];
208
+ /** Clear all log entries for a node. */
209
+ clearLogs(nodeId: string): void;
210
+ }
211
+ /**
212
+ * Context that carries the FusedWidgetBridge instance.
213
+ * Provided by the workbench's `<JsonUiProvider>` and by the test harness.
214
+ * Catalog authors never interact with this directly — use the hooks.
215
+ */
216
+ export declare const FusedWidgetBridgeContext: import("react").Context<FusedWidgetBridge | null>;
217
+ /**
218
+ * Internal helper used by all SDK hooks. Throws if used outside a
219
+ * `<FusedWidgetBridgeContext.Provider>` so misconfiguration fails loudly.
220
+ */
221
+ export declare function useFusedWidgetBridge(): FusedWidgetBridge;
222
+ /**
223
+ * Per-subtree override of the bridge's node identity. When present, SDK hooks
224
+ * like `useJsonUiUdfInfo` and `useJsonUiLog` read these fields instead of
225
+ * `bridge.node`. Lets nested `JsonUiConfigHashOverride` providers tag log
226
+ * entries with their own `configHash` without rebuilding the entire bridge,
227
+ * which would otherwise cascade through every `useSyncExternalStore`
228
+ * subscription in the subtree.
229
+ *
230
+ * Hosts populate this from their JsonUiProvider props.
231
+ */
232
+ export interface JsonUiNodeOverride {
233
+ udfUniqueId?: string;
234
+ udfName?: string;
235
+ configHash?: string;
236
+ }
237
+ export declare const JsonUiNodeOverrideContext: import("react").Context<JsonUiNodeOverride | null>;
238
+ /**
239
+ * Internal helper that resolves the effective node identity for SDK hooks.
240
+ * Reads `JsonUiNodeOverrideContext` first (per-subtree override), falls back
241
+ * to `bridge.node` (per-provider identity).
242
+ */
243
+ export declare function useJsonUiNode(): {
244
+ udfUniqueId: string | undefined;
245
+ udfName: string | undefined;
246
+ configHash: string | undefined;
247
+ };
package/dist/bridge.js ADDED
@@ -0,0 +1,61 @@
1
+ /**
2
+ * FusedWidgetBridge — the dependency-injection interface between SDK hooks
3
+ * and the host environment (workbench, test harness, mobile app, etc).
4
+ *
5
+ * The SDK hooks (`useFusedParam`, `useParamSubstitution`, `useUdfOutputByName`,
6
+ * `useDuckDbSqlQuery`, …) read this bridge from `FusedWidgetBridgeContext`
7
+ * and delegate all state management to it. Catalog component authors only
8
+ * call hooks; they never touch the bridge directly.
9
+ *
10
+ * Hosts implement this interface to inject their own state stores:
11
+ * - The Fused workbench wires Jotai atoms, DuckDB, fetcher, log atom.
12
+ * - The catalog-template test harness uses in-memory Map storage.
13
+ * - Any future host (mobile, embedded) can implement their own.
14
+ */
15
+ import { createContext, useContext } from "react";
16
+ // ============================================================================
17
+ // React context
18
+ // ============================================================================
19
+ /**
20
+ * Context that carries the FusedWidgetBridge instance.
21
+ * Provided by the workbench's `<JsonUiProvider>` and by the test harness.
22
+ * Catalog authors never interact with this directly — use the hooks.
23
+ */
24
+ export const FusedWidgetBridgeContext = createContext(null);
25
+ FusedWidgetBridgeContext.displayName = "FusedWidgetBridgeContext";
26
+ /**
27
+ * Internal helper used by all SDK hooks. Throws if used outside a
28
+ * `<FusedWidgetBridgeContext.Provider>` so misconfiguration fails loudly.
29
+ */
30
+ export function useFusedWidgetBridge() {
31
+ const bridge = useContext(FusedWidgetBridgeContext);
32
+ if (!bridge) {
33
+ throw new Error("useFusedWidgetBridge: no FusedWidgetBridgeContext provider in the tree. " +
34
+ "Wrap your components with the workbench's <JsonUiProvider> or the " +
35
+ "test harness's <FusedWidgetBridgeContext.Provider value={createTestBridge()}>.");
36
+ }
37
+ return bridge;
38
+ }
39
+ export const JsonUiNodeOverrideContext = createContext(null);
40
+ JsonUiNodeOverrideContext.displayName = "JsonUiNodeOverrideContext";
41
+ /**
42
+ * Internal helper that resolves the effective node identity for SDK hooks.
43
+ * Reads `JsonUiNodeOverrideContext` first (per-subtree override), falls back
44
+ * to `bridge.node` (per-provider identity).
45
+ */
46
+ export function useJsonUiNode() {
47
+ const bridge = useFusedWidgetBridge();
48
+ const override = useContext(JsonUiNodeOverrideContext);
49
+ if (override) {
50
+ return {
51
+ udfUniqueId: override.udfUniqueId ?? bridge.node.udfUniqueId,
52
+ udfName: override.udfName ?? bridge.node.udfName,
53
+ configHash: override.configHash ?? bridge.node.configHash,
54
+ };
55
+ }
56
+ return {
57
+ udfUniqueId: bridge.node.udfUniqueId,
58
+ udfName: bridge.node.udfName,
59
+ configHash: bridge.node.configHash,
60
+ };
61
+ }
package/dist/bundle.js ADDED
@@ -0,0 +1,2 @@
1
+ var ir="parameter-updates",ae=(o=>(o.PARAM="param",o.RANGE="range",o.VIEWPORT="viewport",o.CLEAR="clear",o))(ae||{});function ur(e){if(typeof e!="object"||e===null)return!1;let n=e;return typeof n.type=="string"&&Object.values(ae).includes(n.type)&&typeof n.parameter=="string"&&"values"in n}import{createContext as ye,useContext as Re}from"react";var be=ye(null);be.displayName="FusedWidgetBridgeContext";function y(){let e=Re(be);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 he=ye(null);he.displayName="JsonUiNodeOverrideContext";function V(){let e=y(),n=Re(he);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 nn,useCallback as ve,useContext as rn,useRef as we,useSyncExternalStore as tn}from"react";function dr(){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 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),r(t))},removeField(t){e.has(t)&&(e.delete(t),r(t))},subscribe(t,o){let i={names:new Set(t),cb:o};return n.add(i),()=>{n.delete(i)}}}}var Ue=nn({store:null,isInForm:!1});Ue.displayName="JsonUiFormContext";function on(){return rn(Ue)}var xe=Object.freeze({});function j(e){let{store:n,isInForm:r}=on(),t=un(e),o=ve(f=>n?n.subscribe(t,f):()=>{},[n,t]),i=we(xe),l=ve(()=>{if(!n)return xe;let f=n.getSnapshot(t),a=i.current;return sn(a,f)?a:(i.current=f,f)},[n,t]),s=tn(o,l,l);return{inForm:r,values:s}}function sn(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 un(e){let n=we(e),r=n.current;return r!==e&&(r.length!==e.length||r.some((t,o)=>t!==e[o]))&&(n.current=e),n.current}function an(e){return e}function ln(e){return e}import{useCallback as X,useEffect as J,useMemo as Y,useRef as I,useState as cn}from"react";function fn({param:e,debounceMs:n=300,readOnly:r=!1,defaultValue:t,broadcastDefaultValue:o=!0,validate:i,preprocess:l}){let s=y(),{configHash:f}=V(),a=!!e,R=(u,c)=>{if(!e)return;if(u==="Cleared"){s.log.log(`Cleared param "${e}"`,"info",f);return}let d=JSON.stringify(c),N=d&&d.length>100?d.slice(0,100)+"\u2026":d;s.log.log(`${u} param "${e}" = ${N}`,"info",f)},p=Y(()=>l??gn(t),[l]),C=Y(()=>i??dn(t),[i]),g=Y(()=>e?[e]:[],[e]),{inForm:b,values:h}=j(g),P=e?h[e]:void 0,T=Y(()=>{if(!(!a||!e))return s.params.getSnapshot(e)},[s,a,e]),v=()=>{let u=b&&P!==void 0?P:T;if(u==null)return t;let c=p(u);return C(c)?c:t},[D,w]=cn(v),x=I(D);x.current=D;let m=I(null),A=I(!1),L=I(!1),F=I({enabled:a,param:e});F.current={enabled:a,param:e};let E=I(s);E.current=s;let Q=I(e);J(()=>{let u=Q.current;Q.current=e,u&&u!==e&&s.params.clear(u)},[s,e]),J(()=>{if(!a||!e||A.current)return;let u=b&&P!==void 0?P:s.params.getSnapshot(e);if(u==null)return;let c=p(u);c!==x.current&&C(c)&&(w(c),R("Received",u))},[s,e,a,b,P,p,C]),J(()=>!a||!e?void 0:s.params.subscribe(e,()=>{if(A.current)return;let c=s.params.getSnapshot(e);if(c==null)return;let d=p(c);d!==x.current&&C(d)&&(w(d),R("Received",c))}),[s,e,a,p,C]);let M=X(u=>{!a||!e||(s.params.set(e,u,"param"),s.edges.stopLoading(),R("Broadcast",u))},[s,a,e]),q=X(()=>{!a||r||(m.current&&(clearTimeout(m.current),m.current=null),s.edges.startLoading(),M(x.current),A.current=!1)},[s,M,a,r]),U=X(u=>{m.current&&(clearTimeout(m.current),m.current=null),w(u),x.current=u,A.current=!1,!(!a||!e||r)&&(s.params.clear(e),s.edges.stopLoading(),R("Cleared",null))},[s,a,e,r]);J(()=>{L.current=!1},[e,a]),J(()=>{if(!a||!e||r||!o||L.current)return;let u=s.params.getSnapshot(e);if(u!=null){L.current=!0;return}if(x.current===""){L.current=!0;return}M(x.current),L.current=!0},[s,a,e,r,o,M]);let _=X(u=>{w(u),!(!a||r)&&(A.current=!0,s.edges.startLoading(),m.current&&clearTimeout(m.current),m.current=setTimeout(()=>{M(u),A.current=!1},n))},[s,M,n,a,r]);return J(()=>()=>{m.current&&clearTimeout(m.current);let{enabled:u,param:c}=F.current;!u||!c||E.current.params.clear(c)},[]),{value:D,setValue:_,broadcastNow:q,clearValue:U}}function dn(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 gn(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{useCallback as ke,useRef as Pe,useSyncExternalStore as pn}from"react";var Ce=Object.freeze({});function Z(e){let n=y(),r=Sn(e),t=ke(l=>r.length===0?()=>{}:n.params.subscribeMany(r,l),[n,r]),o=Pe(Ce),i=ke(()=>{if(r.length===0)return Ce;let l=n.params.getSnapshotMany(r),s=o.current;return mn(s,l)?s:(o.current=l,l)},[n,r]);return pn(t,i,i)}function mn(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 Sn(e){let n=Pe(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 le,useRef as yn,useSyncExternalStore as Rn}from"react";function bn(){let e=y(),n=le(l=>e.routing.subscribeAllowedSources(l),[e]),r=yn(null),t=le(()=>{let l=e.routing.getAllowedSources(),s=r.current;return hn(s,l)?s:(r.current=l,l)},[e]),o=Rn(n,t,t),i=le((l,s)=>!o||o.length===0||!l&&!s?!0:o.some(f=>f.udfUniqueId&&f.udfUniqueId===l||f.udfName&&f.udfName===s),[o]);return{allowedSources:o,isAllowedSource:i}}function hn(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 Ee,useRef as vn,useSyncExternalStore as xn}from"react";function wn(){let e=y(),n=Ee(o=>e.routing.subscribeAllowedSources(o),[e]),r=vn(null),t=Ee(()=>{let o=e.routing.getAllowedUdfNames(),i=r.current;return Un(i,o)?i:(r.current=o,o)},[e]);return xn(n,t,t)}function Un(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 kn,useMemo as $,useRef as Cn,useState as Ae,useSyncExternalStore as Pn,useCallback as Le}from"react";var Fe=/\$([a-zA-Z_][a-zA-Z0-9_]*)/g,En=/\{\{[\s\S]*?\}\}/,Te=/\{\{\s*([A-Za-z_][A-Za-z0-9_]*)\b/g;function ce(e,n={}){let r=e??"",t=n.preserveMissingParams??!1,o=y(),i=$(()=>An(r),[r]),l=Z(i),{inForm:s,values:f}=j(i),a=$(()=>s?{...l,...f}:l,[l,f,s]),R=$(()=>En.test(r),[r]),p=$(()=>R?"":Ln(r,a,t),[r,a,t,R]),C=$(()=>{if(!R)return[];let w=new Set,x=[];Te.lastIndex=0;let m;for(;(m=Te.exec(r))!==null;)w.has(m[1])||(w.add(m[1]),x.push(m[1]));return x},[r,R]),g=Fn(o,C),[b,h]=Ae(()=>({key:"",value:""})),[P,T]=Ae(!1),v=$(()=>JSON.stringify({template:r,paramValues:a,preserveMissingParams:t,tick:g}),[r,a,t,g]);return kn(()=>{if(!R){T(!1);return}let w=!1,x=new AbortController;return T(!0),o.template.render(r,a,{preserveMissingParams:t,signal:x.signal}).then(m=>{w||(h(A=>A.key===v&&A.value===m.value?A:{key:v,value:m.value}),T(m.loading))},m=>{w||m?.name!=="AbortError"&&T(!1)}),()=>{w=!0,x.abort()}},[o,R,r,a,t,v]),{value:$(()=>{if(!R)return p;if(b.key===v)return b.value;try{return o.template.renderLoading(r,a,{preserveMissingParams:t})}catch{return r}},[o,R,p,b,v,r,a,t]),loading:R?P:!1}}function An(e){let n=[],r=new Set;for(let t of e.matchAll(Fe)){let o=t[1];r.has(o)||(r.add(o),n.push(o))}return n}function Ln(e,n,r){return e.replace(Fe,(t,o)=>{let i=n[o];return i==null?r?t:"":Tn(i)})}function Tn(e){return e==null?"":typeof e=="string"?e:typeof e=="number"||typeof e=="boolean"?String(e):JSON.stringify(e)??""}function Fn(e,n){let r=Cn(0),t=n.slice().sort().join("|"),o=Le(l=>{let s=()=>{r.current+=1,l()},f=[e.template.subscribe(s)];for(let a of n)f.push(e.udfs.subscribeOutput(a,s));return()=>f.forEach(a=>a())},[e,t]),i=Le(()=>r.current,[]);return Pn(o,i,i)}import{useCallback as ee,useEffect as Dn,useMemo as ne,useRef as On,useState as De,useSyncExternalStore as Nn}from"react";function Oe(e){let n=y(),r=ee(i=>e?n.udfs.subscribeOutput(e,i):()=>{},[n,e]),t=On(void 0),o=ee(()=>{if(!e)return;let i=n.udfs.getOutputSnapshot(e),l=t.current;return Mn(l,i)?l:(t.current=i,i)},[n,e]);return Nn(r,o,o)}function Ne(){let e=y();return ee(n=>e.udfs.requestReexecute(n),[e])}function Mn(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 Me=/^\{\{(\w+)\.(\w+)(?:\[(\d+)\])?\}\}$/;function re(e){return!e||typeof e!="string"?!1:Me.test(e)}function fe(e){if(!re(e))return null;let n=e.match(Me);if(!n)return null;let[,r,t,o]=n,i=o!==void 0?parseInt(o,10):void 0;return{udfName:r,columnName:t,index:i}}function Bn(e){return!!e&&typeof e=="object"&&typeof e.getRows=="function"}function de({udfName:e,sampleSize:n=200}){let r=Oe(e),t=Ne(),[o,i]=De([]),[l,s]=De([]);Dn(()=>{let a=!1,R=r?.data;if(!R||!Bn(R)){i([]),s([]);return}return(async()=>{try{let p=await R.getRows(0,Math.max(0,n));if(a)return;let C=p.map(b=>{let h=b;return h&&typeof h=="object"&&h.properties&&typeof h.properties=="object"?h.properties:b});i(C);let g=Array.from(new Set(C.flatMap(b=>Object.keys(b??{}))));s(g)}catch{if(a)return;i([]),s([])}})(),()=>{a=!0}},[r?.data,n]);let f=ee(()=>{e&&t(e)},[t,e]);return{loading:r?.isExecutionInProgress??!1,errorMessage:r?.error??null,isError:!!r?.error,columns:l,rows:o,requestReexecute:f}}function qn(e,n=200){let r=re(e),t=ne(()=>r?fe(e):null,[r,e]),{rows:o,loading:i}=de({udfName:t?.udfName,sampleSize:n});return{values:ne(()=>!t||!t.columnName?[]:o.map(s=>s?.[t.columnName]).filter(s=>s!=null),[o,t]),loading:r?i:!1}}function _n(e,n=200){let r=re(e),t=ne(()=>r?fe(e):null,[r,e]),{rows:o,loading:i}=de({udfName:t?.udfName,sampleSize:n});return{value:ne(()=>{if(!t||!t.columnName||t.index===void 0)return null;let s=o[t.index];if(!s)return null;let f=s[t.columnName];return f!==void 0?f:null},[o,t]),loading:r?i:!1}}import{useCallback as Ke,useEffect as z,useMemo as O,useRef as Hn,useState as B}from"react";import{useCallback as te,useMemo as Qn,useRef as ge,useSyncExternalStore as In}from"react";var Be=Object.freeze([]);function oe(){let e=y(),{configHash:n}=V(),r=ge(e);r.current=e;let t=ge(n);t.current=n;let o=te((i,l="info")=>{r.current.log.log(i,l,t.current)},[]);return Qn(()=>({log:o}),[o])}function $n(e){let n=y(),r=te(i=>e?n.log.subscribeLogs(e,i):()=>{},[n,e]),t=ge(Be),o=te(()=>{if(!e)return Be;let i=n.log.getLogsSnapshot(e);return i===t.current?t.current:(t.current=i,i)},[n,e]);return In(r,o,o)}function Vn(e){let n=y();return te(()=>{e&&n.log.clearLogs(e)},[n,e])}function se(){let e=y();return{startLoading:e.edges.startLoading,stopLoading:e.edges.stopLoading}}var pe=/\$([a-zA-Z_][a-zA-Z0-9_]*)/g,qe=/\{\{(\w+)(?:\?([^}]*))?\}\}/g,me=/'((?:s3|gs|fd):\/\/[^'\n]+)'/g;function jn(e){return e==null?"''":typeof e=="number"&&!Number.isNaN(e)?String(e):typeof e=="boolean"?e?"TRUE":"FALSE":`'${String(e).replace(/'/g,"''")}'`}function Jn(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 _e(e,n){return e.replace(pe,(r,t,o)=>{let i=n[t],l=i==null?"":String(i);return Jn(e,o)?l.replace(/'/g,"''"):jn(i)})}function Qe(e){let n=new Set,r=[];pe.lastIndex=0;for(let t of e.matchAll(pe)){let o=t[1];n.has(o)||(n.add(o),r.push(o))}return r}function Wn(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 i=t.slice(0,o),l=t.slice(o+1),s,f;try{s=decodeURIComponent(i),f=decodeURIComponent(l)}catch{s=i,f=l}s&&(n[s]=f,r=!0)}return r?n:null}function Ie(e){let n=[],r;for(qe.lastIndex=0;(r=qe.exec(e))!==null;){let[t,o,i]=r;n.push({match:t,name:o,overrides:Wn(i),start:r.index,end:r.index+t.length})}return n}function $e(e){if(!e)return[];let n=new Set,r=[];me.lastIndex=0;let t;for(;(t=me.exec(e))!==null;){let o=t[1];n.has(o)||(n.add(o),r.push(o))}return r}function Ve(e,n){return e&&e.replace(me,(r,t)=>{let o=n[t];return o?`'${o}'`:r})}var je=/^\$([a-zA-Z_][a-zA-Z0-9_]*)$/;function Je(e){let n=e.trim(),r=je.exec(n);return r?r[1]:null}function We(e,n){let r=je.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 zn(e){return Object.keys(e).sort().map(n=>`${n}=${e[n]}`).join("&")}function ze(e,n){return n?`${e}#${zn(n)}`:e}var Ze=500,W=Object.freeze([]),G=Object.freeze([]);function He(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 Kn(e){return`"${e.replace(/"/g,'""')}"`}function Zn(e,n,r,t,o,i){let l=e.match(/^\$([a-zA-Z_][a-zA-Z0-9_]*)$/);if(l){let f=o[l[1]];return f==null?"":He(String(f),i)}let s=e;for(let f=n.length-1;f>=0;f--){let{raw:a,key:R,resolvedOverrides:p}=n[f],g=p===null&&t?t[a.name]:void 0,b;g?b=Kn(g.relationName):b=`'${r.get(R)??`${a.name}.parquet`}'`,s=s.slice(0,a.start)+b+s.slice(a.end)}return s=He(s,i),_e(s,o)}function Ge({sql:e,enabled:n=!0,maxRows:r=Ze,sourceOverrides:t}){let o=y(),{startLoading:i,stopLoading:l}=se(),{log:s}=oe(),[f,a]=B(""),[R,p]=B(!1),[C,g]=B(null),[b,h]=B(0),P=O(()=>e?Ie(e):[],[e]),T=O(()=>t?P.filter(u=>u.overrides!==null||!t[u.name]):P,[P,t]),v=O(()=>{let u=new Set;for(let c of T)if(c.overrides)for(let d of Object.values(c.overrides)){let N=Je(d);N&&u.add(N)}return Array.from(u)},[T]),D=O(()=>e?Qe(e):[],[e]),w=O(()=>{let u=new Set,c=[];for(let d of D)u.has(d)||(u.add(d),c.push(d));for(let d of v)u.has(d)||(u.add(d),c.push(d));return c},[D,v]),x=Z(w),{inForm:m,values:A}=j(w),L=O(()=>m?{...x,...A}:x,[m,x,A]),F=O(()=>T.map(u=>{if(!u.overrides)return{raw:u,key:u.name,resolvedOverrides:null,unresolved:!1};let c={},d=!1;for(let[N,H]of Object.entries(u.overrides)){let S=We(H,L);S.unresolved&&(d=!0),c[N]=S.value}return{raw:u,key:ze(u.name,c),resolvedOverrides:c,unresolved:d}}),[T,L]),E=O(()=>{let u=new Set,c=[];for(let d of F)d.unresolved||u.has(d.key)||(u.add(d.key),c.push({name:d.raw.name,key:d.key,overrides:d.resolvedOverrides??void 0}));return c},[F]),Q=O(()=>E.map(u=>`${u.key}|${u.name}|${u.overrides?Object.entries(u.overrides).sort(([c],[d])=>c.localeCompare(d)).map(([c,d])=>`${c}=${d}`).join(","):""}`).join(`
2
+ `),[E]),M=Ke(()=>{h(u=>u+1)},[]);z(()=>{if(!n||E.length===0)return;let u=E.map(c=>o.udfs.subscribeOutput(c.name,()=>{h(d=>d+1)}));return()=>{u.forEach(c=>c())}},[o,n,E]),z(()=>{R?i():l()},[R,i,l]);let q=O(()=>{if(!t)return null;for(let u of P){if(u.overrides!==null)continue;let c=t[u.name];if(c?.error)return c.error}return null},[P,t]),U=O(()=>t?P.some(u=>u.overrides===null&&t[u.name]?.loading):!1,[P,t]),_=F.some(u=>u.unresolved);return z(()=>{if(!n||!e){a(""),p(!1),g(null);return}if(q){a(""),g(q),p(!1),s(`SQL preprocessing: ${q}`,"error");return}if(U||_){a(""),g(null),p(!0);return}let u=!1;return p(!0),g(null),(async()=>{let c=new Map,d;if(E.length>0)try{let S=await o.sql.resolveVfsFilenames(E);if(u)return;if(S instanceof Map)for(let k of F){if(k.resolvedOverrides)continue;let K=S.get(k.raw.name);K&&c.set(k.key,K)}else c=S.filenames,d=S.errors}catch(S){if(u)return;let k=S instanceof Error?S.message:typeof S=="string"?S:"VFS registration failed";a(""),g(k),p(!1),s(`SQL preprocessing: ${k}`,"error");return}if(d)for(let S of F){let k=d.get(S.key);if(k){if(u)return;a(""),g(k),p(!1),s(`SQL preprocessing: ${k}`,"error");return}}for(let S of F)if(!(S.unresolved||S.resolvedOverrides===null&&t?.[S.raw.name]!==void 0)&&!c.has(S.key)){if(u)return;a(""),g(null),p(!0);return}let N;try{N=Zn(e,F,c,t,L,r)}catch(S){if(u)return;let k=S instanceof Error?S.message:typeof S=="string"?S:"SQL preprocessing failed";a(""),g(k),p(!1),s(`SQL preprocessing failed: ${k}`,"error");return}let H=$e(N);if(H.length===0){if(u)return;a(N),g(null),p(!1),s("SQL preprocessing completed");return}try{let S={},k=await Promise.all(H.map(ue=>o.signUrl(ue)));if(u)return;H.forEach((ue,en)=>{S[ue]=k[en].signed});let K=Ve(N,S);a(K),g(null),p(!1),s("SQL preprocessing completed")}catch(S){if(u)return;let k=S instanceof Error?S.message:typeof S=="string"?S:"URL signing failed";a(""),g(k),p(!1),s(`SQL preprocessing failed: ${k}`,"error")}})(),()=>{u=!0}},[o,n,e,Q,F,L,t,q,U,_,r,b,s,E]),{processedSql:f,loading:R,error:C,refetch:M}}function Gn({sql:e,enabled:n=!0,maxRows:r=Ze,sourceOverrides:t}){let o=y(),{startLoading:i,stopLoading:l}=se(),{log:s}=oe(),[f,a]=B(W),[R,p]=B(G),[C,g]=B(!1),[b,h]=B(null),[P,T]=B(0),{processedSql:v,loading:D,error:w,refetch:x}=Ge({sql:e,enabled:n,maxRows:r,sourceOverrides:t}),m=Hn(""),A=n&&!!e&&!!v&&!D&&!w&&v!==m.current,L=D||C||A;z(()=>{L?i():l()},[L,i,l]);let F=Ke(()=>{m.current="",x(),T(E=>E+1)},[x]);return z(()=>{if(!n||!e){m.current="",a(W),p(G),g(!1),h(null);return}if(w){m.current="",h(w),a(W),p(G),g(!1);return}if(D||!v){m.current="",g(!1);return}let E=!1,Q=new AbortController;m.current=v,g(!0),h(null);let M=v.length>120?v.slice(0,120)+"\u2026":v;s(`SQL query started: ${M}`);let q=performance.now();return o.sql.query(v,{signal:Q.signal}).then(U=>{if(E)return;let _=Math.round(performance.now()-q);if(U.error){a(W),p(G),h(U.error),g(!1),s(`SQL failed (${_}ms): ${U.error}`,"error");return}a(U.rows.length===0?W:U.rows),p(U.columns),h(null),g(!1),s(`SQL completed: ${U.rows.length} row${U.rows.length!==1?"s":""} in ${_}ms`)},U=>{if(E||U?.name==="AbortError")return;let _=Math.round(performance.now()-q),u=U instanceof Error?U.message:typeof U=="string"?U:"SQL query failed";h(u),a(W),p(G),g(!1),s(`SQL failed (${_}ms): ${u}`,"error")}),()=>{E=!0,Q.abort()}},[o,n,e,v,D,w,P,s]),{rows:f,columns:R,loading:L,error:b,refetch:F}}function Xn(e,n=!0){let r=y(),[t,o]=B({filenames:new Map,loading:!1}),i=O(()=>e.slice().sort().join("|"),[e]);return z(()=>{if(!n||e.length===0){o({filenames:new Map,loading:!1});return}let l=!1;return o(s=>({...s,loading:!0,error:void 0})),r.sql.resolveVfsFilenames(e).then(s=>{if(l)return;let f=s instanceof Map?s:s.filenames;o({filenames:f,loading:!1})},s=>{l||o({filenames:new Map,loading:!1,error:s instanceof Error?s.message:String(s)})}),()=>{l=!0}},[r,i,n]),t}import{useCallback as Xe,useEffect as Yn,useState as ie}from"react";var Ye=["s3://","gs://","fd://"];function Se(e){return Ye.some(n=>e.startsWith(n))}function er(){let e=y();return{signUrl:Xe(r=>e.signUrl(r),[e])}}function nr(e){let n=y(),{value:r,loading:t}=ce(e),[o,i]=ie(null),[l,s]=ie(null),[f,a]=ie(!1),[R,p]=ie(0);Yn(()=>{if(t)return;if(!r){i(null),s(null),a(!1);return}if(!Se(r)){i(r),s(null),a(!1);return}let g=!1;return a(!0),s(null),n.signUrl(r).then(({signed:b})=>{g||i(b??r)}).catch(b=>{g||(s(b instanceof Error?b.message:"Failed to load media"),i(null))}).finally(()=>{g||a(!1)}),()=>{g=!0}},[n,t,r,R]);let C=Xe(async()=>{if(!r||!Se(r))return r??null;let{signed:g}=await n.signUrl(r),b=g??r;return i(b),s(null),p(h=>h+1),b},[n,r]);return{src:o,loading:t||f,error:l,refreshSignedUrl:C,resolvedSrc:r,needsSigning:!!(r&&Se(r))}}import{useEffect as rr,useState as tr}from"react";function or(e,n){let r=y(),[t,o]=tr({status:"idle"});return rr(()=>{if(!n||!e?.trim()){o({status:"idle"});return}let i=!1;return o({status:"checking"}),r.uploads.checkAccess(e).then(l=>{i||(l.ok?o({status:"allowed"}):o({status:"denied",message:l.message??"Upload access denied."}))}),()=>{i=!0}},[r,e,n]),t}function sr(){return V()}export{Ue as FormContext,be as FusedWidgetBridgeContext,he as JsonUiNodeOverrideContext,ir as PARAMETER_BROADCAST_CHANNEL,ae as ParameterMessageType,me as SIGNABLE_URL_LITERAL_REGEX,Ye as SIGNED_URL_SCHEMES,pe as SQL_PARAM_REGEX,qe as SQL_SOURCE_PLACEHOLDER_REGEX,zn as canonicalOverrideKey,ze as computePlaceholderKey,dr as createFormParamsStore,ln as defineCatalog,an as defineComponent,jn as escapeSqlValue,$e as extractSignableUrls,Qe as extractSqlParams,Je as getDollarRefName,ur as isStandardMessage,re as isUdfQuery,Wn as parseOverridesString,Ie as parseSqlUdfPlaceholders,fe as parseUdfColumnQuery,We as resolveOverrideValue,Ve as rewriteSignedUrls,_e as substituteSqlParams,bn as useAllowedSources,wn as useAllowedUdfNames,Z as useCanvasParams,Gn as useDuckDbSqlQuery,Ge as useDuckDbSqlQueryPreprocessing,on as useFormContext,j as useFormParams,fn as useFusedParam,y as useFusedWidgetBridge,se as useJsonUiEdgeAnimation,oe as useJsonUiLog,Vn as useJsonUiLogClear,$n as useJsonUiLogs,V as useJsonUiNode,sr as useJsonUiUdfInfo,nr as useMediaSrc,ce as useParamSubstitution,Ne as useRequestUdfReexecute,_n as useUdfColumnValue,qn as useUdfColumnValues,de as useUdfDataFrameSample,Oe as useUdfOutputByName,or as useUploadAccessCheck,er as useUrlSigning,Xn as useVfsRegistration};
@@ -0,0 +1,53 @@
1
+ import type { CatalogComponentDefinition } from "./define-component";
2
+ /**
3
+ * The single default export every catalog bundle ships:
4
+ *
5
+ * export default defineCatalog({
6
+ * components: { "kebab-key": defineComponent({...}), ... },
7
+ * skill, // string imported from "../SKILL.md" (esbuild text loader)
8
+ * summary, // ≤120-char headline; REQUIRED when skill is present
9
+ * });
10
+ *
11
+ * `skill` and `summary` are co-required at the **type level** via function
12
+ * overloads — TypeScript rejects `{components, skill}` and `{components,
13
+ * summary}` because neither overload matches. There is intentionally NO
14
+ * runtime validation here:
15
+ *
16
+ * - Bundle-time throws would block the entire catalog load over a UI
17
+ * concern (e.g. a too-long summary). The workbench loader instead
18
+ * validates the loaded module and surfaces structured errors in the
19
+ * Custom Catalogs UI so the canvas can keep working.
20
+ * - The `/build` slash-command in catalog-template owns the
21
+ * SKILL.md ↔ defineCatalog wiring symmetry check.
22
+ *
23
+ * `defineCatalog` is therefore a pure type-narrowing identity, matching the
24
+ * style of `defineComponent`.
25
+ */
26
+ export interface CatalogDefinitionBase {
27
+ components: Record<string, CatalogComponentDefinition<any>>;
28
+ }
29
+ export interface CatalogDefinitionWithSkill extends CatalogDefinitionBase {
30
+ /**
31
+ * Free-form markdown — author-supplied cross-component guidance the AI
32
+ * fetches lazily via `get_catalog_skill`. Do **not** restate per-component
33
+ * prop info here; that reaches the AI through each component's Zod schema
34
+ * and the existing `get_json_ui_component_schemas` tool, and any
35
+ * duplication will drift.
36
+ */
37
+ skill: string;
38
+ /**
39
+ * ≤120-char headline shown in the system prompt's
40
+ * `<available_catalog_skills>` block so the AI can decide whether the
41
+ * catalog is relevant before fetching the full skill.
42
+ */
43
+ summary: string;
44
+ }
45
+ export type CatalogDefinition = CatalogDefinitionBase | CatalogDefinitionWithSkill;
46
+ export declare function defineCatalog<C extends Record<string, CatalogComponentDefinition<any>>>(def: {
47
+ components: C;
48
+ skill: string;
49
+ summary: string;
50
+ }): CatalogDefinitionWithSkill;
51
+ export declare function defineCatalog<C extends Record<string, CatalogComponentDefinition<any>>>(def: {
52
+ components: C;
53
+ }): CatalogDefinitionBase;
@@ -0,0 +1,3 @@
1
+ export function defineCatalog(def) {
2
+ return def;
3
+ }