@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.
- package/AGENTS.md +5 -3
- package/CONTRIBUTING.md +16 -4
- package/README.md +20 -14
- package/dist/chunks/{contract-jksbmt5q.js → contract-3bnrf5pf.js} +27 -5
- package/dist/chunks/contract-3bnrf5pf.js.map +10 -0
- package/dist/chunks/{contract-tkkc28tg.js → contract-4vrw9zk9.js} +43 -13
- package/dist/chunks/contract-4vrw9zk9.js.map +12 -0
- package/dist/chunks/{contract-14vxdcrs.js → contract-farfe948.js} +17 -21
- package/dist/chunks/contract-farfe948.js.map +10 -0
- package/dist/chunks/contract-nt819swq.js.map +1 -1
- package/dist/chunks/{contract-tqrf3ykr.js → contract-rrm7s5zp.js} +61 -32
- package/dist/chunks/contract-rrm7s5zp.js.map +12 -0
- package/dist/chunks/{contract-mb5nfzwg.js → contract-xjv197ck.js} +178 -31
- package/dist/chunks/contract-xjv197ck.js.map +13 -0
- package/dist/client/index.js +6 -4
- package/dist/client/index.js.map +1 -1
- package/dist/core/appearance.js +1 -1
- package/dist/core/contract.js +5 -3
- package/dist/core/contract.js.map +1 -1
- package/dist/embed/index.js +42 -8
- package/dist/embed/index.js.map +5 -4
- package/dist/local.js +191 -79
- package/dist/local.js.map +8 -7
- package/dist/react/embed/index.js +84 -99
- package/dist/react/embed/index.js.map +4 -5
- package/dist/react/index.css +156 -6
- package/dist/react/index.js +313 -152
- package/dist/react/index.js.map +17 -13
- package/dist/server.js +172 -76
- package/dist/server.js.map +7 -6
- package/dist/types/client/iframe.d.ts +14 -0
- package/dist/types/client/index.d.ts +19 -13
- package/dist/types/core/appearance.d.ts +7 -6
- package/dist/types/core/contract.d.ts +3 -3
- package/dist/types/core/messages.d.ts +9 -3
- package/dist/types/core/operation.d.ts +27 -0
- package/dist/types/core/presentation.d.ts +7 -0
- package/dist/types/embed/bridge.d.ts +12 -0
- package/dist/types/embed/host.d.ts +10 -3
- package/dist/types/embed/index.d.ts +7 -4
- package/dist/types/embed/{shell.d.ts → source.d.ts} +5 -4
- package/dist/types/embed/sql.d.ts +4 -0
- package/dist/types/react/embed/bridge.d.ts +19 -10
- package/dist/types/react/embed/index.d.ts +0 -2
- package/dist/types/react/ui/DataAppSkeleton.d.ts +8 -0
- package/dist/types/react/ui/SearchInput.d.ts +1 -1
- package/dist/types/react/ui/index.d.ts +2 -0
- package/dist/types/react/ui/useAppAppearance.d.ts +3 -0
- package/dist/types/react/ui/useDataAppPresentation.d.ts +3 -0
- package/dist/types/react/view-controls.d.ts +1 -1
- package/dist/{bootstrap.js → worker.js} +161 -29
- package/docs/app-authoring.md +24 -4
- package/docs/appearance.md +21 -8
- package/docs/client.md +40 -3
- package/docs/contract.md +9 -7
- package/docs/embed.md +114 -19
- package/docs/react-embed.md +84 -24
- package/docs/react.md +51 -0
- package/docs/releasing.md +5 -0
- package/docs/server-bun.md +5 -0
- package/docs/starter-agent-instructions.md +4 -2
- package/docs/worker.md +57 -0
- package/package.json +9 -5
- package/dist/chunks/contract-14vxdcrs.js.map +0 -10
- package/dist/chunks/contract-jksbmt5q.js.map +0 -10
- package/dist/chunks/contract-mb5nfzwg.js.map +0 -12
- package/dist/chunks/contract-tkkc28tg.js.map +0 -12
- package/dist/chunks/contract-tqrf3ykr.js.map +0 -11
- package/dist/types/embed/standalone.d.ts +0 -1
- package/dist/types/react/embed/shell.d.ts +0 -10
- 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
|
-
`
|
|
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 {
|
|
13
|
+
import { attachDataAppBridge } from '@altertable/data-app/embed';
|
|
14
14
|
|
|
15
|
-
const
|
|
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
|
-
|
|
24
|
-
and a different origin from its host. 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://
|
|
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
|
|
41
|
-
|
|
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
|
|
49
|
-
|
|
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
|
-
|
|
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
|
|
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`
|
|
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
|
|
76
|
-
correlation, cancellation, bounded pending requests, and reconnection. Use
|
|
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
|
|
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
|
|
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.
|
package/docs/react-embed.md
CHANGED
|
@@ -1,36 +1,70 @@
|
|
|
1
1
|
# React embedding
|
|
2
2
|
|
|
3
|
-
Import `
|
|
4
|
-
|
|
5
|
-
|
|
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
|
-
|
|
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 {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
23
|
-
`source
|
|
44
|
+
The local URL must have a different origin from its shell. For hosted bundles,
|
|
45
|
+
replace `source` with:
|
|
24
46
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
|
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.
|
|
56
|
-
|
|
57
|
-
sandbox policy,
|
|
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`.
|
package/docs/server-bun.md
CHANGED
|
@@ -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
|
-
|
|
51
|
-
|
|
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.
|
|
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
|
-
"./
|
|
75
|
-
"default": "./dist/
|
|
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
|
|
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": "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",
|
|
8
|
-
"debugId": "6376725AC558B20364756E2164756E21",
|
|
9
|
-
"names": []
|
|
10
|
-
}
|