@altertable/data-app 0.59.1 → 0.62.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 (71) hide show
  1. package/AGENTS.md +5 -3
  2. package/CONTRIBUTING.md +16 -4
  3. package/README.md +20 -14
  4. package/dist/chunks/{contract-jksbmt5q.js → contract-3bnrf5pf.js} +27 -5
  5. package/dist/chunks/contract-3bnrf5pf.js.map +10 -0
  6. package/dist/chunks/{contract-tkkc28tg.js → contract-4vrw9zk9.js} +43 -13
  7. package/dist/chunks/contract-4vrw9zk9.js.map +12 -0
  8. package/dist/chunks/{contract-14vxdcrs.js → contract-farfe948.js} +17 -21
  9. package/dist/chunks/contract-farfe948.js.map +10 -0
  10. package/dist/chunks/contract-nt819swq.js.map +1 -1
  11. package/dist/chunks/{contract-tqrf3ykr.js → contract-rrm7s5zp.js} +61 -32
  12. package/dist/chunks/contract-rrm7s5zp.js.map +12 -0
  13. package/dist/chunks/{contract-mb5nfzwg.js → contract-xjv197ck.js} +178 -31
  14. package/dist/chunks/contract-xjv197ck.js.map +13 -0
  15. package/dist/client/index.js +6 -4
  16. package/dist/client/index.js.map +1 -1
  17. package/dist/core/appearance.js +1 -1
  18. package/dist/core/contract.js +5 -3
  19. package/dist/core/contract.js.map +1 -1
  20. package/dist/embed/index.js +42 -8
  21. package/dist/embed/index.js.map +5 -4
  22. package/dist/local.js +191 -79
  23. package/dist/local.js.map +8 -7
  24. package/dist/react/embed/index.js +84 -99
  25. package/dist/react/embed/index.js.map +4 -5
  26. package/dist/react/index.css +156 -6
  27. package/dist/react/index.js +313 -152
  28. package/dist/react/index.js.map +17 -13
  29. package/dist/server.js +172 -76
  30. package/dist/server.js.map +7 -6
  31. package/dist/types/client/iframe.d.ts +14 -0
  32. package/dist/types/client/index.d.ts +19 -13
  33. package/dist/types/core/appearance.d.ts +7 -6
  34. package/dist/types/core/contract.d.ts +3 -3
  35. package/dist/types/core/messages.d.ts +9 -3
  36. package/dist/types/core/operation.d.ts +27 -0
  37. package/dist/types/core/presentation.d.ts +7 -0
  38. package/dist/types/embed/bridge.d.ts +12 -0
  39. package/dist/types/embed/host.d.ts +10 -3
  40. package/dist/types/embed/index.d.ts +7 -4
  41. package/dist/types/embed/{shell.d.ts → source.d.ts} +5 -4
  42. package/dist/types/embed/sql.d.ts +4 -0
  43. package/dist/types/react/embed/bridge.d.ts +19 -10
  44. package/dist/types/react/embed/index.d.ts +0 -2
  45. package/dist/types/react/ui/DataAppSkeleton.d.ts +8 -0
  46. package/dist/types/react/ui/SearchInput.d.ts +1 -1
  47. package/dist/types/react/ui/index.d.ts +2 -0
  48. package/dist/types/react/ui/useAppAppearance.d.ts +3 -0
  49. package/dist/types/react/ui/useDataAppPresentation.d.ts +3 -0
  50. package/dist/types/react/view-controls.d.ts +1 -1
  51. package/dist/{bootstrap.js → worker.js} +161 -29
  52. package/docs/app-authoring.md +24 -4
  53. package/docs/appearance.md +21 -8
  54. package/docs/client.md +40 -3
  55. package/docs/contract.md +9 -7
  56. package/docs/embed.md +114 -19
  57. package/docs/react-embed.md +84 -24
  58. package/docs/react.md +51 -0
  59. package/docs/releasing.md +5 -0
  60. package/docs/server-bun.md +5 -0
  61. package/docs/starter-agent-instructions.md +4 -2
  62. package/docs/worker.md +57 -0
  63. package/package.json +9 -5
  64. package/dist/chunks/contract-14vxdcrs.js.map +0 -10
  65. package/dist/chunks/contract-jksbmt5q.js.map +0 -10
  66. package/dist/chunks/contract-mb5nfzwg.js.map +0 -12
  67. package/dist/chunks/contract-tkkc28tg.js.map +0 -12
  68. package/dist/chunks/contract-tqrf3ykr.js.map +0 -11
  69. package/dist/types/embed/standalone.d.ts +0 -1
  70. package/dist/types/react/embed/shell.d.ts +0 -10
  71. package/docs/bootstrap.md +0 -76
package/docs/embed.md CHANGED
@@ -6,13 +6,13 @@ app UI stylesheet.
6
6
 
7
7
  ## Sources
8
8
 
9
- `attachDataAppShell` owns iframe loading, sandbox policy, startup timeout, and
9
+ `attachDataAppBridge` in source mode owns iframe loading, sandbox policy, startup timeout, and
10
10
  message delivery. The host supplies an iframe and a validated message dispatcher:
11
11
 
12
12
  ```ts
13
- import { attachDataAppShell } from '@altertable/data-app/embed';
13
+ import { attachDataAppBridge } from '@altertable/data-app/embed';
14
14
 
15
- const dispose = attachDataAppShell({
15
+ const host = attachDataAppBridge({
16
16
  iframe,
17
17
  source: { type: 'url', url: 'https://apps.example.com/report' },
18
18
  onMessage: router.dispatch,
@@ -20,8 +20,8 @@ const dispose = attachDataAppShell({
20
20
  ```
21
21
 
22
22
  The host defines `iframe` and `router`; see [message contracts](contract.md#message-routes).
23
- Dispose before replacing the source or retrying. A URL source requires HTTP(S)
24
- and a different origin from its host. The shell adds `__altertable_parent` to the
23
+ Call `host.dispose()` before replacing the source or retrying. A URL source requires HTTP(S)
24
+ and a different origin from its host. The source bridge adds `__altertable_parent` to the
25
25
  app URL. A hosted app must explicitly install a transport to its configured,
26
26
  trusted parent origin using the [client API](client.md#iframe-transport).
27
27
  The query parameter alone does not establish trust.
@@ -31,24 +31,25 @@ A bundle source has this shape:
31
31
  ```ts
32
32
  const source = {
33
33
  type: 'bundle' as const,
34
- bootstrapUrl: 'https://preview.example.com/bootstrap',
34
+ bootstrapUrl: 'https://my-report-app-1.apps.example.net/',
35
35
  javascript: bundle.javascript,
36
- revision: bundle.revision,
37
36
  };
38
37
  ```
39
38
 
40
- The host provides a self-contained JavaScript bundle and a revision identifying
41
- that bundle. Serve the bootstrap page with a CSP compatible with the bundle.
39
+ The host provides a self-contained JavaScript bundle. React bridges replace the
40
+ iframe whenever its JavaScript content changes. Serve the bootstrap page with a CSP compatible with the bundle.
42
41
  Bundle mode uses `sandbox="allow-scripts"` and an opaque origin; it cannot read
43
42
  the host document or use same-origin privileges. Both modes use
44
43
  `referrerPolicy="no-referrer"`.
45
44
 
46
45
  ## Trusted bootstrap
47
46
 
48
- For backend HTML that embeds a ready-made script without bundling, use the
49
- [standalone bootstrap asset](bootstrap.md).
47
+ For Cloudflare hosting, upload the [Worker asset](worker.md). It includes the
48
+ bootstrap HTML and security policy; deployments supply the runtime domain and
49
+ trusted parent origins through bindings.
50
50
 
51
- Bundle this initializer into the trusted bootstrap document, before any app code:
51
+ For other hosts that own their HTML and security policy, bundle this initializer
52
+ into the bootstrap document before any app code:
52
53
 
53
54
  ```ts
54
55
  import { startDataAppBootstrap } from '@altertable/data-app/embed';
@@ -59,7 +60,7 @@ const dispose = startDataAppBootstrap({
59
60
  ```
60
61
 
61
62
  The bootstrap URL is trusted executable code: it receives the app script and
62
- session token. Its hosting service must enforce its CSP. The shell cannot impose
63
+ session token. Its hosting service must enforce its CSP. The bridge cannot impose
63
64
  CSP on a remote response. A starting policy for self-contained scripts and styles
64
65
  is `default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline';
65
66
  img-src data: blob:; connect-src 'none'; base-uri 'none'; form-action 'none'`.
@@ -70,16 +71,15 @@ app bundle; non-React apps use `createDataAppNavigation` from `/client`.
70
71
 
71
72
  ## Delivery and navigation
72
73
 
73
- `attachDataAppBridge` is the lower-level API for a host-owned iframe. Supply
74
+ `attachDataAppBridge` also supports connection mode for a host-owned iframe. Supply
74
75
  `connection: { type: 'origin', origin }` or `{ type: 'opaque', token }`, and an
75
- `onMessage` dispatcher. It returns cleanup and owns source/origin checks, request
76
- correlation, cancellation, bounded pending requests, and reconnection. Use the
77
- shell for bundle loading and token rotation. The opaque destination requires
76
+ `onMessage` dispatcher. Both modes use the same transport. It returns `{ dispose, setPresentation }` and owns source/origin checks, request
77
+ correlation, cancellation, bounded pending requests, and reconnection. Use source mode for bundle loading and token rotation. The opaque destination requires
78
78
  wildcard delivery, but incoming messages still require the exact iframe window,
79
79
  null origin, token, document, and session to match.
80
80
 
81
81
  `createNavigationHandler({ window, reservedSearchParams })` handles the shared
82
- `navigation.update` route using browser history. Reserved host query parameters
82
+ `navigation:update` route using browser history. Reserved host query parameters
83
83
  survive app changes. Supply your own handler when integrating a framework router.
84
84
  Host Back/Forward events publish opaque host state through the bridge. The app's
85
85
  optional navigation adapter interprets search/hash and synchronizes controls;
@@ -91,8 +91,103 @@ opaque apps keep that state in memory rather than modifying their document URL.
91
91
  `disconnected`. `connected` means transport initialization; `ready` means a URL
92
92
  client acknowledged initialization or a bundle script finished evaluation. It
93
93
  does not mean asynchronous data queries finished. Script errors report `failed`.
94
- The shell's `startupTimeoutMs` defaults to 30 seconds.
94
+ The source bridge's `startupTimeoutMs` defaults to 30 seconds.
95
95
 
96
96
  `onDiagnostic` receives only message `direction` and `type`, never tokens or
97
97
  payloads. Routed handlers must authorize every data request. Message validation
98
98
  and iframe isolation do not grant access to data or execute SQL.
99
+
100
+ Message types follow `{scope}:{action}`: `bridge:connect`, `bridge:ready`,
101
+ `bridge:initialize`, `bridge:request`, `bridge:result`, `bridge:error`,
102
+ `bridge:cancel`, `bridge:disconnect`, `runtime:ready`, `runtime:error`,
103
+ `script:load`, and `state:update`. Routed requests use the same convention,
104
+ including `data:query` and `navigation:update`. Hosts, apps, and bootstrap scripts
105
+ must use matching names; the former unscoped and dot-separated names are no
106
+ longer supported.
107
+
108
+ ## SQL query route
109
+
110
+ Hosts serving browser-owned operations register `sqlQueryRoute` explicitly:
111
+
112
+ ```ts
113
+ import {
114
+ createMessageRouter,
115
+ sqlQueryRoute,
116
+ } from '@altertable/data-app/contract';
117
+ import { createSqlQueryHandler } from '@altertable/data-app/embed';
118
+
119
+ const router = createMessageRouter(
120
+ { 'data:sql': sqlQueryRoute },
121
+ {
122
+ 'data:sql': createSqlQueryHandler(async (query, { signal }) => {
123
+ return authorizedLakehouseForCurrentViewer(query, signal);
124
+ }),
125
+ }
126
+ );
127
+ // Supply router.dispatch as the shell's onMessage handler.
128
+ ```
129
+
130
+ The host supplies `authorizedLakehouseForCurrentViewer`. Its backend must enforce
131
+ viewer/dataset permissions, permitted query behavior, maximum rows, execution
132
+ time, concurrency, and response size independently of browser policy. Route
133
+ validation is not SQL authorization. `createSqlQueryHandler` calls authorization
134
+ for each query, forwards cancellation, and preserves `DataSourceError` reasons
135
+ as public `source_*` errors with request IDs. Authorization failures return
136
+ `forbidden`; unknown query errors are hidden. Custom handlers can return deliberate
137
+ public failures with `MessageRoutingError`.
138
+
139
+ `SqlQueryInput` (exported from `/contract` and `/embed`) carries
140
+ `{ statement: string, limit: number }`; responses are
141
+ `{ columns: { name: string, type?: string }[], rows: unknown[][], queryId?: string }`.
142
+ The route rejects empty statements, unsafe or nonpositive limits, malformed
143
+ results, and results exceeding the requested limit. The bridge's existing payload
144
+ and pending-call limits apply, and cancellation reaches the handler's signal.
145
+ Operation names and inputs stay in the app; query evidence is assembled there.
146
+
147
+ `dataAppRoutes` retains named `data:query` and navigation routes for existing
148
+ server-backed hosts. SQL hosts opt into `data:sql`; they need no named-operation
149
+ handler unless they also serve HTTP-style apps. Update the host before switching
150
+ an app to browser-owned operations. Older hosts reject `data:sql` as unknown.
151
+
152
+ ## Parent presentation
153
+
154
+ The parent declares where the iframe is mounted and owns its resolved theme:
155
+
156
+ ```ts
157
+ const host = attachDataAppBridge({
158
+ iframe,
159
+ source: { type: 'url', url: 'https://apps.example.com/report' },
160
+ presentation: { surface: 'embedded', theme: 'dark' },
161
+ onMessage: router.dispatch,
162
+ });
163
+
164
+ // Update presentation without replacing the iframe or its bridge session.
165
+ host.setPresentation({ surface: 'embedded', theme: 'light' });
166
+ ```
167
+
168
+ Both source and connection modes support the `presentation` option and
169
+ `host.setPresentation(presentation)` method. `DataAppPresentation` is exported from
170
+ `/embed` and `/client`. Use `surface: 'embedded'` when the parent provides page chrome, as in the
171
+ Altertable frontend, and `'standalone'` when the app provides its own header and
172
+ footer. `theme` must be resolved to `'light'` or
173
+ `'dark'`; the parent decides how its system preference is resolved.
174
+
175
+ Presentation travels over `postMessage` in the authenticated `bridge:initialize` and
176
+ `state:update` messages, alongside `search` and `hash`. Framework-neutral apps
177
+ can narrow the unknown state returned by `bridge.snapshot()` to read its
178
+ `presentation` field, and subscribe through `bridge.subscribe`. React `DataApp` consumes it automatically.
179
+ An embedded surface renders toolbar actions without the page header or footer.
180
+ Both surfaces follow the parent's theme, including presentation mode, without
181
+ changing saved viewer preferences. Omitting presentation preserves standalone behavior;
182
+ `host.setPresentation(undefined)` restores it.
183
+
184
+ The host attachment is an explicit `{ dispose, setPresentation }` object. Replace
185
+ former cleanup calls (`dispose()`) with `host.dispose()`. React hosts manage this
186
+ lifecycle automatically.
187
+
188
+ ## Migration
189
+
190
+ `attachDataAppShell` and `DataAppShellOptions` have been removed. Use
191
+ `attachDataAppBridge` with the same source options and `DataAppBridgeOptions`.
192
+ Source and connection options are mutually exclusive. The frontend and CLI own
193
+ their shell UI; this package provides iframe bridges only.
@@ -1,36 +1,70 @@
1
1
  # React embedding
2
2
 
3
- Import `DataAppShell` and `DataAppBridge` from
4
- `@altertable/data-app/react/embed`. This entry depends on React and the embedding
5
- engine, and does not load the app's widgets, React Query, or CSS.
3
+ Import `DataAppBridge` from `@altertable/data-app/react/embed`. This entry depends
4
+ on React and the embedding engine, and does not load the app's widgets, React
5
+ Query, or CSS.
6
6
 
7
- ## Shell
7
+ The bridge owns iframe setup and `postMessage` communication. The consuming
8
+ frontend or CLI owns its shell: fetching a bundle, subscriptions, layout, loading
9
+ and error UI, and retry controls. Both hosts use the same bridge and transport.
10
+
11
+ ## Source-managed iframe
12
+
13
+ Use `source` for local URL apps or hosted bundles. The bridge creates the iframe,
14
+ configures its sandbox, loads its source, and reports connection status. It renders
15
+ only the iframe; it adds no loading, error, or retry UI and does not hide the frame.
8
16
 
9
17
  ```tsx
10
- import { DataAppShell } from '@altertable/data-app/react/embed';
11
-
12
- <DataAppShell
13
- title="Activity report"
14
- source={{ type: 'url', url: 'https://apps.example.com/activity' }}
15
- onMessage={router.dispatch}
16
- loading={<p>Loading report…</p>}
17
- renderError={retry => <button onClick={retry}>Retry report</button>}
18
- />;
18
+ import { useReducer, useState } from 'react';
19
+ import { DataAppBridge } from '@altertable/data-app/react/embed';
20
+ import type { DataAppStatus } from '@altertable/data-app/embed';
21
+
22
+ function Shell() {
23
+ const [status, setStatus] = useState<DataAppStatus>('connecting');
24
+ const [attempt, retry] = useReducer(value => value + 1, 0);
25
+
26
+ return (
27
+ <div className="app-shell">
28
+ {status !== 'ready' && status !== 'failed' && <p>Loading report…</p>}
29
+ {status === 'failed' && <button onClick={retry}>Retry report</button>}
30
+ <DataAppBridge
31
+ key={attempt}
32
+ title="Activity report"
33
+ source={{ type: 'url', url: 'http://127.0.0.1:25837/' }}
34
+ onMessage={router.dispatch}
35
+ onStatusChange={setStatus}
36
+ iframeProps={{ className: 'app-frame', hidden: status !== 'ready' }}
37
+ />
38
+ </div>
39
+ );
40
+ }
19
41
  ```
20
42
 
21
43
  The host supplies `router` using [message contracts](contract.md#message-routes).
22
- For bundles, use `{ type: 'bundle', bootstrapUrl, javascript, revision }` as
23
- `source`. See [embedding](embed.md) for trust, sandbox, CSP, and bootstrap setup.
44
+ The local URL must have a different origin from its shell. For hosted bundles,
45
+ replace `source` with:
24
46
 
25
- The shell creates and owns the iframe. Source changes, bundle revision changes,
26
- and retries replace the entire frame. Handler changes use the latest callbacks
27
- without resetting the session. `startupTimeoutMs`, `onStatusChange`, and
28
- `onDiagnostic` have the same meaning as in the framework-neutral API. The iframe
29
- remains hidden until ready; loading and error UI have sensible defaults.
47
+ ```tsx
48
+ source={{ type: 'bundle', bootstrapUrl, javascript }}
49
+ ```
50
+
51
+ The host supplies those bundle values. See [embedding](embed.md) for trust,
52
+ sandbox, CSP, and bootstrap setup.
53
+
54
+ Source URL or JavaScript content changes replace the entire iframe. Change the
55
+ bridge's React `key` to retry with a fresh frame. Handler changes use the latest
56
+ callbacks without resetting the session. `startupTimeoutMs`, `onStatusChange`, and
57
+ `onDiagnostic` have the same meaning as in the framework-neutral API.
58
+
59
+ `iframeProps` forwards presentation and accessibility attributes to the iframe,
60
+ including `className`, `style`, and `hidden`. The bridge controls `src`, `srcDoc`,
61
+ `sandbox`, `referrerPolicy`, `loading`, and the callback ref. Loading is always
62
+ `eager` so an iframe hidden until ready can start. Supply `title` directly.
30
63
 
31
64
  ## Host-owned iframe
32
65
 
33
- Use `DataAppBridge` when the host owns iframe rendering:
66
+ Use the same `DataAppBridge` with `iframe` and `connection` when the host already
67
+ owns a loaded iframe and its security policy:
34
68
 
35
69
  ```tsx
36
70
  import { useState, type ComponentRef } from 'react';
@@ -52,6 +86,32 @@ function Host() {
52
86
  ```
53
87
 
54
88
  The app supplies `appUrl` and `router`. A callback ref lets the bridge observe late
55
- mounting and replacement; listeners attach to the iframe's owner document. The
56
- bridge handles delivery only. Use `DataAppShell` to manage source loading,
57
- sandbox policy, bundle tokens, and startup errors.
89
+ mounting and replacement; listeners attach to the iframe's owner document. This
90
+ mode renders nothing and handles delivery only. Use source mode for bundle
91
+ loading, sandbox policy, token rotation, and startup timeout. The two prop modes
92
+ are mutually exclusive.
93
+
94
+ ## Migration from the package shell
95
+
96
+ `DataAppShell` and `DataAppShellProps` have been removed. Replace the import with
97
+ `DataAppBridge` and keep `source`, `title`, and message/status callbacks. Move
98
+ `loading` and `renderError` into the consuming shell, use `iframeProps.hidden` to
99
+ control visibility, and change the bridge's key for retries. Frontend bundle
100
+ fetching and subscriptions and CLI local-server forwarding remain host concerns.
101
+
102
+ ## Loading placeholder
103
+
104
+ For a shared placeholder while building or connecting, render `DataAppSkeleton`
105
+ from `@altertable/data-app/react` in the consuming shell. Import
106
+ `@altertable/data-app/react/styles.css` once in that host's browser entry. See
107
+ [React](react.md#loading-an-embedded-app) for usage. The bridge does not render
108
+ loading UI itself.
109
+
110
+ ## Parent-owned presentation
111
+
112
+ Pass `presentation={{ surface: 'embedded', theme: resolvedTheme }}`
113
+ to `DataAppBridge` in either source or connection mode. Use `'standalone'` when the app should render its own page chrome.
114
+ Prop updates publish trusted state without reloading the iframe or reconnecting
115
+ the session. Resolve system preference in the parent to `'light'` or `'dark'`.
116
+ Inside an embedded surface, `DataApp` retains toolbar actions and hides its header
117
+ and footer. See [parent presentation](embed.md#parent-presentation).
package/docs/react.md CHANGED
@@ -28,6 +28,34 @@ All of these APIs are exported from `/react`. Each component's stylesheet lives
28
28
  | Present loaded findings | [PresentStory](https://github.com/altertable-ai/data-app/blob/main/src/react/ui/PresentStory.tsx) | StoryFinding |
29
29
  | Build custom controls and overlays | [Button](https://github.com/altertable-ai/data-app/blob/main/src/react/ui/Button.tsx), [Sheet](https://github.com/altertable-ai/data-app/blob/main/src/react/ui/Sheet.tsx) | IconButton, Tooltip, HelpPopover, Kbd |
30
30
 
31
+ ## Loading an embedded app
32
+
33
+ `DataAppSkeleton` is a host-side placeholder while an embedded app is building or
34
+ starting. It composes the shared metric, visualization, and data widgets with
35
+ skeleton content: three metric cards, a chart, and table rows. Optional `header`
36
+ and `footer` React nodes let the host supply its own layout; neither renders by
37
+ default.
38
+ It adapts to container width and inherits the package's appearance variables and
39
+ reduced-motion skeleton animation.
40
+
41
+ ```tsx
42
+ import { DataAppSkeleton } from '@altertable/data-app/react';
43
+ import '@altertable/data-app/react/styles.css';
44
+
45
+ <DataAppSkeleton
46
+ aria-label="Loading activity report"
47
+ className="app-loading"
48
+ header={<ReportHeaderSkeleton />}
49
+ footer={<ReportFooterSkeleton />}
50
+ />;
51
+ ```
52
+
53
+ The container announces a loading status; its widget placeholders are hidden from
54
+ assistive technology. Supplied header and footer nodes remain accessible and can
55
+ use the exported `Skeleton` component for their own placeholders. Standard output props, including `style` and `ref`, can be used
56
+ for layout. The consuming shell controls when to display it. The `/react/embed`
57
+ entry remains independent of UI components and the stylesheet.
58
+
31
59
  ## Bound views and widgets
32
60
 
33
61
  | Definition | Runtime owns |
@@ -224,6 +252,18 @@ zero counts. `SelectableBarChart` can share controlled selection with the picker
224
252
  inspection sheets render the same visual and controls. Keep interactive state
225
253
  above both mounts when authoring custom children.
226
254
 
255
+ Widget headings open inspection when evidence is available; a disclosure arrow
256
+ appears beside the heading on hover or keyboard focus. The top-end toolbar is
257
+ reserved for actions and request status. Empty widgets omit their footer, charts
258
+ omit interaction instructions without data, and table pagination appears only
259
+ when there is more than one page. Local `DataBoundary` and `DataSection` inline
260
+ notices sit above retained section content; use widget status for widget feedback
261
+ and page controls or notices for page feedback.
262
+
263
+ Combobox search text aligns with option labels. Empty search inputs blur on
264
+ Escape; a second Escape dismisses an open picker. Picker failures show a centered
265
+ message with the retry action below it.
266
+
227
267
  `DataApp.story` receives the displayed snapshot, including its original input
228
268
  during refresh or failure. Return one to four `StoryFinding` values with unique
229
269
  IDs and registered evidence. `PresentStory` presents those findings directly;
@@ -246,3 +286,14 @@ and phone interactions. Fixtures are excluded from the published package.
246
286
  Previous data and evidence are retained only when the input changes within the
247
287
  same operation. Switching to another operation shows its own cached response or
248
288
  an initial loading/error state; it never inherits another operation's result.
289
+
290
+ Operation and facet caches are scoped to the `DataClient` instance. Hooks created
291
+ from the same client share queries; separate clients do not share results, stale
292
+ data, or cancellation even under one provider. Keep client instances stable
293
+ across renders to preserve their cache.
294
+
295
+ When a trusted parent supplies [parent presentation](embed.md#parent-presentation),
296
+ `DataApp` follows its resolved theme and suppresses local theme controls,
297
+ including in presentations. On an embedded surface (`surface: 'embedded'`),
298
+ only toolbar actions remain above the app body; the title, scope, description, and
299
+ footer are omitted. Variables and request states remain available.
package/docs/releasing.md CHANGED
@@ -37,6 +37,11 @@ The manifest starts at the imported runtime version `0.59.1`. This records the
37
37
  version baseline, not a claim that it is published. Release Please computes the
38
38
  next version from release-relevant commits on `main`.
39
39
 
40
+ While the package is below `1.0.0`, breaking changes bump the minor version via
41
+ `bump-minor-pre-major`. Features also bump the minor version, and fixes bump the
42
+ patch version. Breaking changes remain documented in release notes. Remove this
43
+ option when preparing the first stable `1.0.0` release.
44
+
40
45
  ## Release flow
41
46
 
42
47
  1. Merge Conventional Commits into `main`.
@@ -24,3 +24,8 @@ Missing credentials fail the query. Keep these variables out of browser code.
24
24
  Local serving permits SQL disclosure and does not authenticate hosted viewers.
25
25
  Use [the portable server handler](server.md) with per-request authorization when
26
26
  hosting an app for other people.
27
+
28
+ The local adapter validates NDJSON metadata, optional string or typed column
29
+ headers, and array rows before returning a result. Query IDs must be strings.
30
+ Rows must match the column count when a header is present; headerless array rows
31
+ remain supported with empty column metadata. Empty results remain valid.
@@ -47,5 +47,7 @@ interactions at phone and desktop widths, including loading, empty, error, and
47
47
  stale states. Verify that the exploration answers the user's question.
48
48
  ```
49
49
 
50
- The CLI's existing starter remains owned by its repository. Updating its template
51
- and checks to consume the published package is a separate migration.
50
+ A runnable port of the CLI starter lives in
51
+ [`examples/starter`](https://github.com/altertable-ai/data-app/tree/main/examples/starter). It consumes the built public
52
+ package and keeps its own app instructions. Updating CLI scaffolding to consume
53
+ the published package remains a separate migration.
package/docs/worker.md ADDED
@@ -0,0 +1,57 @@
1
+ # Cloudflare Worker asset
2
+
3
+ `@altertable/data-app/worker` resolves to a precompiled, self-contained ESM Worker
4
+ script. Upload this one file as the deployment's main module. It includes the
5
+ bootstrap and HTML; no bundling, HTML generation,
6
+ runtime imports, or asset fetches are needed in the hosting backend.
7
+
8
+ This export is a script asset, not a Node.js or Bun API. Pin an exact package
9
+ version and commit the lockfile. Resolve and read the installed asset, then pass
10
+ its contents to your deployment tooling:
11
+
12
+ ```js
13
+ import { readFile } from 'node:fs/promises';
14
+
15
+ const workerScript = await readFile(
16
+ new URL(import.meta.resolve('@altertable/data-app/worker')),
17
+ 'utf8'
18
+ );
19
+ // Upload workerScript as worker.js, with main_module = "worker.js".
20
+ ```
21
+
22
+ The hosting backend retains ownership of domains, routes, deployment settings,
23
+ and these string bindings:
24
+
25
+ | Binding | Meaning | Example |
26
+ | ---------------- | -------------------------------------------------------------------------- | ------------------------------------------------------- |
27
+ | `DOMAIN_NAME` | Runtime domain without scheme, port, or trailing dot | `apps.example.net` |
28
+ | `PARENT_ORIGINS` | Space-separated trusted HTTP(S) origins; first exact origin is the default | `https://app.example.com https://*.preview.example.com` |
29
+
30
+ The Worker serves only `/` on a single preview label under `DOMAIN_NAME` matching
31
+ `/^(?=.{1,63}$)[a-z0-9]+(?:-[a-z0-9]+)+-app-[1-9][0-9]*$/`. Other hosts and paths
32
+ return 404. GET returns HTML; HEAD returns the same headers with no body. Other
33
+ methods return 405 with `Allow: GET, HEAD`.
34
+
35
+ `__altertable_parent` selects an **exact** parent origin only after validation
36
+ against `PARENT_ORIGINS`. It cannot add trust. HTTPS wildcard entries allow one
37
+ subdomain label, without a port, for staging previews. Their apex and deeper
38
+ subdomains are rejected. Exact origins cannot contain paths, trailing slashes,
39
+ credentials, queries, or fragments. Invalid, duplicate, or unapproved selections
40
+ return 403. Missing or invalid origin configuration returns 503; an absent
41
+ selection requires an exact default origin. Referrers and messages never supply
42
+ trust.
43
+
44
+ The package owns the unstyled HTML (`#root` followed by an inline classic script),
45
+ attribute and script escaping, restrictive CSP with configured `frame-ancestors`,
46
+ `no-referrer`, and `nosniff`. App scripts load through the authenticated
47
+ `altertable:data-app` bridge and retain opaque host state. Apps own styling and
48
+ navigation. Bundle apps execute their operation registry in the browser and send
49
+ SQL through the [host query route](embed.md#sql-query-route); the Worker does not
50
+ resolve operation names or execute queries. Data requests require backend authorization.
51
+
52
+ If Terraform reads a file, copy the resolved `/worker` asset unchanged to that
53
+ file during deployment preparation. Configure `DOMAIN_NAME`, `PARENT_ORIGINS`,
54
+ domains, routes, and deployment settings in Terraform. Upgrade the host package
55
+ and Worker together for protocol changes.
56
+ Custom hosts that bundle their own bootstrap can use `startDataAppBootstrap` from
57
+ [/embed](embed.md#trusted-bootstrap).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@altertable/data-app",
3
- "version": "0.59.1",
3
+ "version": "0.62.0",
4
4
  "description": "Contracts, transport, and React UI for Altertable data apps",
5
5
  "keywords": [
6
6
  "altertable",
@@ -22,6 +22,9 @@
22
22
  "type": "git",
23
23
  "url": "git+https://github.com/altertable-ai/data-app.git"
24
24
  },
25
+ "workspaces": [
26
+ "examples/starter"
27
+ ],
25
28
  "files": [
26
29
  "dist",
27
30
  "docs",
@@ -71,8 +74,8 @@
71
74
  "types": "./dist/types/embed/index.d.ts",
72
75
  "import": "./dist/embed/index.js"
73
76
  },
74
- "./bootstrap": {
75
- "default": "./dist/bootstrap.js"
77
+ "./worker": {
78
+ "default": "./dist/worker.js"
76
79
  },
77
80
  "./react/embed": {
78
81
  "types": "./dist/types/react/embed/index.d.ts",
@@ -90,11 +93,12 @@
90
93
  "format": "oxfmt .",
91
94
  "format:check": "oxfmt --check .",
92
95
  "test": "bun test ./tests",
93
- "check": "bun run typecheck && bun run lint && bun run format:check && bun run test && bun run build && bun run test:package",
96
+ "check": "bun run build && bun run typecheck && bun run lint && bun run format:check && bun run test && bun run test:package && bun run test:starter",
94
97
  "test:package": "bun run scripts/check-package.ts",
95
98
  "check:workflows": "bash scripts/check-workflows.sh",
96
99
  "test:browser": "playwright test --config browser-tests/playwright.config.ts",
97
- "lint:fix": "oxlint --type-aware --fix src tests scripts browser-tests"
100
+ "lint:fix": "oxlint --type-aware --fix src tests scripts browser-tests",
101
+ "test:starter": "bun run scripts/check-starter.ts"
98
102
  },
99
103
  "dependencies": {
100
104
  "@floating-ui/react": "0.27.20",
@@ -1,10 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../src/core/appearance.ts"],
4
- "sourcesContent": [
5
- "/**\n * Brand settings; parsing is portable, while theme application requires the browser.\n * @module @altertable/data-app/appearance\n * @see https://github.com/altertable-ai/data-app/blob/main/docs/appearance.md\n */\nimport { invariant } from '@/src/core/invariant';\n\nexport type AppearanceSettings = {\n mode: 'light' | 'dark' | 'system';\n baseColor: 'neutral' | 'slate' | 'warm';\n accentColor: string;\n darkAccentColor?: string;\n chartColors: string[];\n density: 'compact' | 'comfortable' | 'spacious';\n cornerRadius: 'none' | 'small' | 'medium' | 'large';\n elevation: 'flat' | 'subtle' | 'raised';\n typography: { body: string; heading: string };\n};\n\nexport type ThemeMode = AppearanceSettings['mode'];\n\nexport type ThemeController = {\n getMode: () => ThemeMode;\n setMode: (mode: ThemeMode) => void;\n subscribe: (listener: () => void) => () => void;\n};\n\nconst defaults: AppearanceSettings = {\n mode: 'light',\n baseColor: 'neutral',\n accentColor: '#405d47',\n darkAccentColor: '#a6c4ad',\n chartColors: [\n '#285fc0',\n '#a95319',\n '#147862',\n '#7243aa',\n '#aa3958',\n '#475569',\n ],\n density: 'comfortable',\n cornerRadius: 'medium',\n elevation: 'subtle',\n typography: { body: 'system', heading: 'system' },\n};\n\nfunction record(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction oneOf<T extends string>(\n value: unknown,\n options: readonly T[]\n): value is T {\n return typeof value === 'string' && options.includes(value as T);\n}\n\nfunction color(value: unknown): value is string {\n return typeof value === 'string' && /^#[0-9a-fA-F]{6}$/.test(value);\n}\n\nfunction font(value: unknown): value is string {\n return (\n typeof value === 'string' &&\n value.length > 0 &&\n value.length <= 120 &&\n !/[;{}()]/.test(value)\n );\n}\n\nexport function parseAppearance(value: unknown): AppearanceSettings {\n if (value === undefined)\n return {\n ...defaults,\n chartColors: [...defaults.chartColors],\n typography: { ...defaults.typography },\n };\n invariant(\n record(value) &&\n Object.keys(value).every(key =>\n [\n 'mode',\n 'baseColor',\n 'accentColor',\n 'darkAccentColor',\n 'chartColors',\n 'density',\n 'cornerRadius',\n 'elevation',\n 'typography',\n ].includes(key)\n ),\n 'Invalid appearance settings.'\n );\n const typography = value.typography;\n const validFields =\n (value.mode === undefined ||\n oneOf(value.mode, ['light', 'dark', 'system'])) &&\n (value.baseColor === undefined ||\n oneOf(value.baseColor, ['neutral', 'slate', 'warm'])) &&\n (value.accentColor === undefined || color(value.accentColor)) &&\n (value.darkAccentColor === undefined || color(value.darkAccentColor)) &&\n (value.chartColors === undefined ||\n (Array.isArray(value.chartColors) &&\n value.chartColors.length >= 1 &&\n value.chartColors.length <= 8 &&\n value.chartColors.every(color))) &&\n (value.density === undefined ||\n oneOf(value.density, ['compact', 'comfortable', 'spacious'])) &&\n (value.cornerRadius === undefined ||\n oneOf(value.cornerRadius, ['none', 'small', 'medium', 'large'])) &&\n (value.elevation === undefined ||\n oneOf(value.elevation, ['flat', 'subtle', 'raised'])) &&\n (typography === undefined ||\n (record(typography) &&\n Object.keys(typography).every(\n key => key === 'body' || key === 'heading'\n ) &&\n (typography.body === undefined || font(typography.body)) &&\n (typography.heading === undefined || font(typography.heading))));\n invariant(validFields, 'Invalid appearance settings.');\n\n return {\n mode: (value.mode ?? defaults.mode) as AppearanceSettings['mode'],\n baseColor: (value.baseColor ??\n defaults.baseColor) as AppearanceSettings['baseColor'],\n accentColor: (value.accentColor ?? defaults.accentColor) as string,\n darkAccentColor: (value.darkAccentColor ??\n (value.accentColor === undefined\n ? defaults.darkAccentColor\n : undefined)) as string | undefined,\n chartColors: (value.chartColors ?? defaults.chartColors) as string[],\n density: (value.density ??\n defaults.density) as AppearanceSettings['density'],\n cornerRadius: (value.cornerRadius ??\n defaults.cornerRadius) as AppearanceSettings['cornerRadius'],\n elevation: (value.elevation ??\n defaults.elevation) as AppearanceSettings['elevation'],\n typography: {\n body:\n record(typography) && typeof typography.body === 'string'\n ? typography.body\n : defaults.typography.body,\n heading:\n record(typography) && typeof typography.heading === 'string'\n ? typography.heading\n : defaults.typography.heading,\n },\n };\n}\n\nconst palettes = {\n neutral: {\n light: ['#ffffff', '#ffffff', '#f6f7f7', '#202124', '#687078', '#e3e6e8'],\n dark: ['#151719', '#202326', '#292d31', '#f2f3f4', '#aeb5bc', '#3b4248'],\n },\n slate: {\n light: ['#f8fafc', '#ffffff', '#f1f5f9', '#17212f', '#607083', '#dce3ea'],\n dark: ['#111820', '#1b2530', '#263340', '#f0f4f8', '#a8b5c3', '#3a4857'],\n },\n warm: {\n light: ['#fbfaf8', '#ffffff', '#f5f2ed', '#292723', '#736e66', '#e8e2da'],\n dark: ['#1b1916', '#25221e', '#302c27', '#f5f1eb', '#bcb3a8', '#494239'],\n },\n} as const;\n\nconst spaces = {\n compact: ['4px', '8px', '12px', '18px', '24px'],\n comfortable: ['5px', '10px', '16px', '24px', '32px'],\n spacious: ['6px', '12px', '20px', '30px', '40px'],\n} as const;\n\nconst radii = {\n none: ['0px', '0px', '0px'],\n small: ['4px', '7px', '9px'],\n medium: ['7px', '12px', '16px'],\n large: ['10px', '18px', '24px'],\n} as const;\n\nfunction fontStack(family: string): string {\n return family === 'system'\n ? \"-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, ui-sans-serif, sans-serif\"\n : `${JSON.stringify(family)}, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, ui-sans-serif, sans-serif`;\n}\n\n/**\n * Install semantic tokens on the document root, including portaled UI. Returns a cleanup for\n * system-theme listening.\n */\nexport function applyAppearance(value: unknown): () => void {\n const settings = parseAppearance(value);\n const root = document.documentElement;\n const preference = window.matchMedia('(prefers-color-scheme: dark)');\n const [xs, sm, md, lg, xl] = spaces[settings.density];\n const [control, surface, overlay] = radii[settings.cornerRadius];\n const tokens: Record<string, string> = {\n '--at-font': fontStack(settings.typography.body),\n '--at-font-heading': fontStack(settings.typography.heading),\n '--at-space-xs': xs,\n '--at-space-sm': sm,\n '--at-space-md': md,\n '--at-space-lg': lg,\n '--at-space-xl': xl,\n '--at-layout-gap': `clamp(${md}, 2.5vw, ${lg})`,\n '--at-radius-control': control,\n '--at-radius-surface': surface,\n '--at-radius-overlay': overlay,\n '--at-shadow-overlay':\n settings.elevation === 'flat'\n ? 'none'\n : settings.elevation === 'raised'\n ? '0 24px 72px rgb(0 0 0 / 24%)'\n : '0 18px 50px rgb(0 0 0 / 16%)',\n '--at-shadow-surface':\n settings.elevation === 'flat'\n ? 'none'\n : settings.elevation === 'raised'\n ? '0 8px 28px rgb(20 28 40 / 10%)'\n : '0 3px 16px rgb(20 28 40 / 5%)',\n };\n\n function applyColors(): void {\n const dark =\n settings.mode === 'dark' ||\n (settings.mode === 'system' && preference.matches);\n const [background, surfaceColor, subtle, text, muted, border] =\n palettes[settings.baseColor][dark ? 'dark' : 'light'];\n const accent = dark\n ? (settings.darkAccentColor ??\n `color-mix(in srgb, ${settings.accentColor} 50%, white)`)\n : settings.accentColor;\n settings.chartColors.forEach((chartColor, index) => {\n tokens[`--at-chart-${index + 1}`] = dark\n ? `color-mix(in srgb, ${chartColor} 60%, white)`\n : chartColor;\n });\n Object.assign(tokens, {\n '--at-background': background,\n '--at-surface': surfaceColor,\n '--at-subtle': subtle,\n '--at-text': text,\n '--at-muted': muted,\n '--at-border': border,\n '--at-accent': accent,\n '--at-focus-color': muted,\n '--at-control-hover-border': `color-mix(in srgb, ${muted} 45%, ${border})`,\n '--at-accent-hover': `color-mix(in srgb, ${accent} 80%, ${dark ? 'white' : 'black'})`,\n '--at-accent-subtle': `color-mix(in srgb, ${accent} ${dark ? 22 : 12}%, ${surfaceColor})`,\n '--at-on-accent': dark ? background : '#ffffff',\n '--at-danger': dark ? '#f97066' : '#b42318',\n '--at-backdrop': dark ? 'rgb(0 0 0 / 55%)' : 'rgb(15 23 30 / 22%)',\n '--at-code-surface': dark ? '#242a31' : '#f4f6f9',\n '--at-code-text': dark ? '#e5e9ef' : '#273242',\n });\n root.style.colorScheme = dark ? 'dark' : 'light';\n for (const [name, token] of Object.entries(tokens))\n root.style.setProperty(name, token);\n }\n applyColors();\n if (settings.mode === 'system')\n preference.addEventListener('change', applyColors);\n\n return () => {\n preference.removeEventListener('change', applyColors);\n };\n}\n\n/** Viewer color mode persists independently of app-authored brand tokens. */\nexport function createThemeController(value: unknown): ThemeController {\n const settings = parseAppearance(value);\n const storageKey = 'altertable.data-app.theme-mode';\n let mode = settings.mode;\n try {\n const saved = window.localStorage.getItem(storageKey);\n if (oneOf(saved, ['light', 'dark', 'system'])) mode = saved;\n } catch {\n // Storage can be unavailable in private browsing or embedded contexts.\n }\n let stopAppearance = applyAppearance({ ...settings, mode });\n const listeners = new Set<() => void>();\n\n return {\n getMode() {\n return mode;\n },\n setMode(nextMode) {\n if (nextMode === mode) return;\n stopAppearance();\n mode = nextMode;\n stopAppearance = applyAppearance({ ...settings, mode });\n try {\n window.localStorage.setItem(storageKey, mode);\n } catch {\n // The selection still applies to this page.\n }\n listeners.forEach(listener => listener());\n },\n subscribe(listener) {\n listeners.add(listener);\n\n return () => listeners.delete(listener);\n },\n };\n}\n"
6
- ],
7
- "mappings": "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",
8
- "debugId": "88E94DEE7384735E64756E2164756E21",
9
- "names": []
10
- }
@@ -1,10 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../src/core/messages.ts"],
4
- "sourcesContent": [
5
- "import type { TransportResponse } from '@/src/core/bridge';\nimport type { DisclosedQuery } from '@/src/core/contract';\n\nexport type MessageContext = { signal: AbortSignal };\nexport type RoutedMessage = { route: string; payload: unknown };\nexport type MessageDispatcher = (\n message: unknown,\n context: MessageContext\n) => Promise<unknown>;\nexport type MessageTransport = (\n message: RoutedMessage,\n signal?: AbortSignal\n) => Promise<unknown>;\nexport type MessageRoute<Input, Output> = {\n input: (value: unknown) => Input;\n output: (value: unknown, input: Input) => Output;\n};\nexport type MessageRoutes = Record<\n string,\n {\n input: (value: unknown) => unknown;\n output: (value: unknown, input: never) => unknown;\n }\n>;\nexport type MessageInput<Route extends MessageRoutes[string]> = ReturnType<\n Route['input']\n>;\nexport type MessageHandlers<Routes extends MessageRoutes> = {\n [Name in keyof Routes]: (\n payload: MessageInput<Routes[Name]>,\n context: MessageContext\n ) =>\n | ReturnType<Routes[Name]['output']>\n | Promise<ReturnType<Routes[Name]['output']>>;\n};\n\n// A bootstrap and its app may bundle independent copies in the same window.\nconst errorBrand = Symbol.for('@altertable/data-app/MessageRoutingError');\n\n/** Only these deliberate, public failures cross the message boundary. Other errors stay private. */\nexport class MessageRoutingError extends Error {\n readonly [errorBrand] = true;\n\n static [Symbol.hasInstance](value: unknown): boolean {\n return (\n value instanceof Error &&\n (value as { [errorBrand]?: unknown })[errorBrand] === true\n );\n }\n\n constructor(\n public readonly code: string,\n message: string,\n public readonly requestId?: string\n ) {\n super(message);\n this.name = 'MessageRoutingError';\n }\n}\n\nexport function defineMessageRoute<Input, Output>(\n route: MessageRoute<Input, Output>\n): MessageRoute<Input, Output> {\n return route;\n}\n\n/** Contracts are shared; handlers are supplied by the host and never belong in the app bundle. */\nexport function createMessageRouter<const Routes extends MessageRoutes>(\n routes: Routes,\n handlers: MessageHandlers<NoInfer<Routes>>\n) {\n const registered = new Map(\n Object.entries(routes).map(([name, contract]) => {\n const handler = handlers[name];\n if (!name || name.length > 256 || typeof handler !== 'function')\n throw new Error(\n 'Each message route requires a handler and a bounded name.'\n );\n\n return [name, { contract, handler }] as const;\n })\n );\n\n async function dispatch(\n value: unknown,\n context: MessageContext\n ): Promise<unknown> {\n if (\n !value ||\n typeof value !== 'object' ||\n typeof (value as RoutedMessage).route !== 'string'\n )\n throw new MessageRoutingError(\n 'invalid_message',\n 'Invalid routed message.'\n );\n const message = value as RoutedMessage;\n const entry = registered.get(message.route);\n if (!entry)\n throw new MessageRoutingError('unknown_route', 'Unknown message route.');\n let payload: unknown;\n try {\n payload = entry.contract.input(message.payload);\n } catch {\n throw new MessageRoutingError(\n 'invalid_payload',\n 'Invalid message payload.'\n );\n }\n context.signal.throwIfAborted();\n let result: unknown;\n try {\n result = await entry.handler(payload as never, context);\n } catch (error) {\n if (context.signal.aborted) throw context.signal.reason;\n if (error instanceof MessageRoutingError) throw error;\n throw new MessageRoutingError(\n 'request_failed',\n 'The message request failed. Retry the request.'\n );\n }\n context.signal.throwIfAborted();\n try {\n return entry.contract.output(result, payload as never);\n } catch (error) {\n if (error instanceof MessageRoutingError) throw error;\n throw new MessageRoutingError(\n 'invalid_response',\n 'Invalid message response.'\n );\n }\n }\n\n return { dispatch };\n}\n\nexport type OperationContracts = Record<\n string,\n {\n input: (value: unknown) => unknown;\n output: (value: unknown) => unknown;\n }\n>;\nexport type DataQueryInput<Operations extends OperationContracts> = {\n [Name in keyof Operations & string]: {\n operation: Name;\n input: ReturnType<Operations[Name]['input']>;\n };\n}[keyof Operations & string];\nexport type DataQueryBody<Output> = {\n data: Output;\n requestId: string;\n queriedAt: string;\n queryIds: string[];\n queries?: DisclosedQuery[];\n};\nexport type DataQueryRoute<Operations extends OperationContracts> =\n MessageRoute<DataQueryInput<Operations>, TransportResponse> & {\n operations: Operations | undefined;\n };\nexport type MessageOutput<Route extends MessageRoutes[string], Input> =\n Route extends DataQueryRoute<infer Operations>\n ? Input extends { operation: infer Name extends keyof Operations }\n ? {\n status: number;\n body: DataQueryBody<ReturnType<Operations[Name]['output']>>;\n }\n : never\n : ReturnType<Route['output']>;\n\n/** Omit contracts only in generic hosts such as the CLI shell, where Bun validates operations. */\nexport function defineDataQueryRoute<\n Operations extends OperationContracts = OperationContracts,\n>(operations?: Operations): DataQueryRoute<Operations> {\n return {\n operations,\n input(value) {\n if (!value || typeof value !== 'object')\n throw new Error('Invalid query.');\n const request = value as { operation?: unknown; input?: unknown };\n if (\n typeof request.operation !== 'string' ||\n !request.operation ||\n request.operation.length > 256 ||\n request.operation.includes('/')\n )\n throw new Error('Invalid operation.');\n const operation =\n operations && Object.hasOwn(operations, request.operation)\n ? operations[request.operation]\n : undefined;\n if (operations && !operation) throw new Error('Unknown operation.');\n\n return {\n operation: request.operation,\n input: operation ? operation.input(request.input) : request.input,\n } as DataQueryInput<Operations>;\n },\n output(value, input) {\n if (!value || typeof value !== 'object')\n throw new Error('Invalid data response.');\n const response = value as TransportResponse;\n if (\n !Number.isInteger(response.status) ||\n response.status < 100 ||\n response.status > 599 ||\n !response.body ||\n typeof response.body !== 'object'\n )\n throw new Error('Invalid data response.');\n const body = response.body as DataQueryBody<unknown> & {\n error?: { code?: unknown; message?: unknown; requestId?: unknown };\n };\n if (response.status < 200 || response.status >= 300) {\n const failure = body.error;\n throw new MessageRoutingError(\n typeof failure?.code === 'string' ? failure.code : 'request_failed',\n typeof failure?.message === 'string'\n ? failure.message\n : 'Could not load data.',\n typeof failure?.requestId === 'string' ? failure.requestId : undefined\n );\n }\n if (\n !('data' in body) ||\n typeof body.requestId !== 'string' ||\n typeof body.queriedAt !== 'string' ||\n !Array.isArray(body.queryIds) ||\n !body.queryIds.every(id => typeof id === 'string')\n )\n throw new Error('Invalid data envelope.');\n if (\n body.queries !== undefined &&\n (!Array.isArray(body.queries) ||\n !body.queries.every(\n query =>\n query &&\n typeof query === 'object' &&\n typeof query.name === 'string' &&\n typeof query.statement === 'string' &&\n (query.queryId === undefined || typeof query.queryId === 'string')\n ))\n )\n throw new Error('Invalid query evidence.');\n const operation = operations?.[input.operation];\n\n return {\n status: response.status,\n body: {\n ...body,\n data: operation ? operation.output(body.data) : body.data,\n },\n };\n },\n };\n}\n\nexport type NavigationUpdate = {\n search: string;\n hash: string;\n mode: 'push' | 'replace';\n title?: string;\n};\nexport const navigationUpdateRoute = /* @__PURE__ */ defineMessageRoute({\n input(value: unknown): NavigationUpdate {\n if (!value || typeof value !== 'object')\n throw new Error('Invalid navigation.');\n const input = value as NavigationUpdate;\n if (\n typeof input.search !== 'string' ||\n input.search.length > 16_384 ||\n (input.search !== '' && !input.search.startsWith('?')) ||\n typeof input.hash !== 'string' ||\n input.hash.length > 4096 ||\n (input.hash !== '' && !input.hash.startsWith('#')) ||\n (input.mode !== 'push' && input.mode !== 'replace') ||\n (input.title !== undefined &&\n (typeof input.title !== 'string' || input.title.length > 512))\n )\n throw new Error('Invalid navigation.');\n\n return {\n search: input.search,\n hash: input.hash,\n mode: input.mode,\n ...(input.title === undefined ? {} : { title: input.title }),\n };\n },\n output(value: unknown): null {\n if (value !== null) throw new Error('Invalid navigation response.');\n\n return null;\n },\n});\nexport const dataAppRoutes = {\n 'data.query': /* @__PURE__ */ defineDataQueryRoute(),\n 'navigation.update': navigationUpdateRoute,\n};\n"
6
- ],
7
- "mappings": ";AAqCA,IAAM,aAAa,OAAO,IAAI,0CAA0C;AAAA;AAGjE,MAAM,6BAA4B,MAAM;AAAA,EAW3B;AAAA,EAEA;AAAA,GAZR,cAAc;AAAA,UAEhB,OAAO,YAAY,CAAC,OAAyB;AAAA,IACnD,OACE,iBAAiB,SAChB,MAAqC,gBAAgB;AAAA;AAAA,EAI1D,WAAW,CACO,MAChB,SACgB,WAChB;AAAA,IACA,MAAM,OAAO;AAAA,IAJG;AAAA,IAEA;AAAA,IAGhB,KAAK,OAAO;AAAA;AAEhB;AAEO,SAAS,mBAAiC,CAC/C,OAC6B;AAAA,EAC7B,OAAO;AAAA;AAIF,SAAS,oBAAuD,CACrE,QACA,UACA;AAAA,EACA,MAAM,aAAa,IAAI,IACrB,OAAO,QAAQ,MAAM,EAAE,IAAI,EAAE,MAAM,cAAc;AAAA,IAC/C,MAAM,UAAU,SAAS;AAAA,IACzB,IAAI,CAAC,QAAQ,KAAK,SAAS,OAAO,OAAO,YAAY;AAAA,MACnD,MAAM,IAAI,MACR,2DACF;AAAA,IAEF,OAAO,CAAC,MAAM,EAAE,UAAU,QAAQ,CAAC;AAAA,GACpC,CACH;AAAA,EAEA,eAAe,QAAQ,CACrB,OACA,SACkB;AAAA,IAClB,IACE,CAAC,SACD,OAAO,UAAU,YACjB,OAAQ,MAAwB,UAAU;AAAA,MAE1C,MAAM,IAAI,qBACR,mBACA,yBACF;AAAA,IACF,MAAM,UAAU;AAAA,IAChB,MAAM,QAAQ,WAAW,IAAI,QAAQ,KAAK;AAAA,IAC1C,IAAI,CAAC;AAAA,MACH,MAAM,IAAI,qBAAoB,iBAAiB,wBAAwB;AAAA,IACzE,IAAI;AAAA,IACJ,IAAI;AAAA,MACF,UAAU,MAAM,SAAS,MAAM,QAAQ,OAAO;AAAA,MAC9C,MAAM;AAAA,MACN,MAAM,IAAI,qBACR,mBACA,0BACF;AAAA;AAAA,IAEF,QAAQ,OAAO,eAAe;AAAA,IAC9B,IAAI;AAAA,IACJ,IAAI;AAAA,MACF,SAAS,MAAM,MAAM,QAAQ,SAAkB,OAAO;AAAA,MACtD,OAAO,OAAO;AAAA,MACd,IAAI,QAAQ,OAAO;AAAA,QAAS,MAAM,QAAQ,OAAO;AAAA,MACjD,IAAI,iBAAiB;AAAA,QAAqB,MAAM;AAAA,MAChD,MAAM,IAAI,qBACR,kBACA,gDACF;AAAA;AAAA,IAEF,QAAQ,OAAO,eAAe;AAAA,IAC9B,IAAI;AAAA,MACF,OAAO,MAAM,SAAS,OAAO,QAAQ,OAAgB;AAAA,MACrD,OAAO,OAAO;AAAA,MACd,IAAI,iBAAiB;AAAA,QAAqB,MAAM;AAAA,MAChD,MAAM,IAAI,qBACR,oBACA,2BACF;AAAA;AAAA;AAAA,EAIJ,OAAO,EAAE,SAAS;AAAA;AAsCb,SAAS,qBAEf,CAAC,YAAqD;AAAA,EACrD,OAAO;AAAA,IACL;AAAA,IACA,KAAK,CAAC,OAAO;AAAA,MACX,IAAI,CAAC,SAAS,OAAO,UAAU;AAAA,QAC7B,MAAM,IAAI,MAAM,gBAAgB;AAAA,MAClC,MAAM,UAAU;AAAA,MAChB,IACE,OAAO,QAAQ,cAAc,YAC7B,CAAC,QAAQ,aACT,QAAQ,UAAU,SAAS,OAC3B,QAAQ,UAAU,SAAS,GAAG;AAAA,QAE9B,MAAM,IAAI,MAAM,oBAAoB;AAAA,MACtC,MAAM,YACJ,cAAc,OAAO,OAAO,YAAY,QAAQ,SAAS,IACrD,WAAW,QAAQ,aACnB;AAAA,MACN,IAAI,cAAc,CAAC;AAAA,QAAW,MAAM,IAAI,MAAM,oBAAoB;AAAA,MAElE,OAAO;AAAA,QACL,WAAW,QAAQ;AAAA,QACnB,OAAO,YAAY,UAAU,MAAM,QAAQ,KAAK,IAAI,QAAQ;AAAA,MAC9D;AAAA;AAAA,IAEF,MAAM,CAAC,OAAO,OAAO;AAAA,MACnB,IAAI,CAAC,SAAS,OAAO,UAAU;AAAA,QAC7B,MAAM,IAAI,MAAM,wBAAwB;AAAA,MAC1C,MAAM,WAAW;AAAA,MACjB,IACE,CAAC,OAAO,UAAU,SAAS,MAAM,KACjC,SAAS,SAAS,OAClB,SAAS,SAAS,OAClB,CAAC,SAAS,QACV,OAAO,SAAS,SAAS;AAAA,QAEzB,MAAM,IAAI,MAAM,wBAAwB;AAAA,MAC1C,MAAM,OAAO,SAAS;AAAA,MAGtB,IAAI,SAAS,SAAS,OAAO,SAAS,UAAU,KAAK;AAAA,QACnD,MAAM,UAAU,KAAK;AAAA,QACrB,MAAM,IAAI,qBACR,OAAO,SAAS,SAAS,WAAW,QAAQ,OAAO,kBACnD,OAAO,SAAS,YAAY,WACxB,QAAQ,UACR,wBACJ,OAAO,SAAS,cAAc,WAAW,QAAQ,YAAY,SAC/D;AAAA,MACF;AAAA,MACA,IACE,EAAE,UAAU,SACZ,OAAO,KAAK,cAAc,YAC1B,OAAO,KAAK,cAAc,YAC1B,CAAC,MAAM,QAAQ,KAAK,QAAQ,KAC5B,CAAC,KAAK,SAAS,MAAM,QAAM,OAAO,OAAO,QAAQ;AAAA,QAEjD,MAAM,IAAI,MAAM,wBAAwB;AAAA,MAC1C,IACE,KAAK,YAAY,cAChB,CAAC,MAAM,QAAQ,KAAK,OAAO,KAC1B,CAAC,KAAK,QAAQ,MACZ,WACE,SACA,OAAO,UAAU,YACjB,OAAO,MAAM,SAAS,YACtB,OAAO,MAAM,cAAc,aAC1B,MAAM,YAAY,aAAa,OAAO,MAAM,YAAY,SAC7D;AAAA,QAEF,MAAM,IAAI,MAAM,yBAAyB;AAAA,MAC3C,MAAM,YAAY,aAAa,MAAM;AAAA,MAErC,OAAO;AAAA,QACL,QAAQ,SAAS;AAAA,QACjB,MAAM;AAAA,aACD;AAAA,UACH,MAAM,YAAY,UAAU,OAAO,KAAK,IAAI,IAAI,KAAK;AAAA,QACvD;AAAA,MACF;AAAA;AAAA,EAEJ;AAAA;AASK,IAAM,yCAAwC,oBAAmB;AAAA,EACtE,KAAK,CAAC,OAAkC;AAAA,IACtC,IAAI,CAAC,SAAS,OAAO,UAAU;AAAA,MAC7B,MAAM,IAAI,MAAM,qBAAqB;AAAA,IACvC,MAAM,QAAQ;AAAA,IACd,IACE,OAAO,MAAM,WAAW,YACxB,MAAM,OAAO,SAAS,SACrB,MAAM,WAAW,MAAM,CAAC,MAAM,OAAO,WAAW,GAAG,KACpD,OAAO,MAAM,SAAS,YACtB,MAAM,KAAK,SAAS,QACnB,MAAM,SAAS,MAAM,CAAC,MAAM,KAAK,WAAW,GAAG,KAC/C,MAAM,SAAS,UAAU,MAAM,SAAS,aACxC,MAAM,UAAU,cACd,OAAO,MAAM,UAAU,YAAY,MAAM,MAAM,SAAS;AAAA,MAE3D,MAAM,IAAI,MAAM,qBAAqB;AAAA,IAEvC,OAAO;AAAA,MACL,QAAQ,MAAM;AAAA,MACd,MAAM,MAAM;AAAA,MACZ,MAAM,MAAM;AAAA,SACR,MAAM,UAAU,YAAY,CAAC,IAAI,EAAE,OAAO,MAAM,MAAM;AAAA,IAC5D;AAAA;AAAA,EAEF,MAAM,CAAC,OAAsB;AAAA,IAC3B,IAAI,UAAU;AAAA,MAAM,MAAM,IAAI,MAAM,8BAA8B;AAAA,IAElE,OAAO;AAAA;AAEX,CAAC;AACM,IAAM,iBAAgB;AAAA,EAC3B,8BAA8B,sBAAqB;AAAA,EACnD,qBAAqB;AACvB;",
8
- "debugId": "6376725AC558B20364756E2164756E21",
9
- "names": []
10
- }