@altertable/data-app 0.59.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +35 -0
- package/CONTRIBUTING.md +83 -0
- package/LICENSE +21 -0
- package/README.md +63 -0
- package/dist/bootstrap.js +388 -0
- package/dist/chunks/contract-14vxdcrs.js +192 -0
- package/dist/chunks/contract-14vxdcrs.js.map +10 -0
- package/dist/chunks/contract-8q35dcyh.js +9 -0
- package/dist/chunks/contract-8q35dcyh.js.map +10 -0
- package/dist/chunks/contract-jksbmt5q.js +133 -0
- package/dist/chunks/contract-jksbmt5q.js.map +10 -0
- package/dist/chunks/contract-mb5nfzwg.js +375 -0
- package/dist/chunks/contract-mb5nfzwg.js.map +12 -0
- package/dist/chunks/contract-mev09s5v.js +77 -0
- package/dist/chunks/contract-mev09s5v.js.map +10 -0
- package/dist/chunks/contract-nt819swq.js +321 -0
- package/dist/chunks/contract-nt819swq.js.map +11 -0
- package/dist/chunks/contract-ryyf6dme.js +21 -0
- package/dist/chunks/contract-ryyf6dme.js.map +10 -0
- package/dist/chunks/contract-tkkc28tg.js +213 -0
- package/dist/chunks/contract-tkkc28tg.js.map +12 -0
- package/dist/chunks/contract-tqrf3ykr.js +232 -0
- package/dist/chunks/contract-tqrf3ykr.js.map +11 -0
- package/dist/chunks/contract-wz59z8pq.js +8 -0
- package/dist/chunks/contract-wz59z8pq.js.map +10 -0
- package/dist/client/index.js +27 -0
- package/dist/client/index.js.map +9 -0
- package/dist/core/appearance.js +12 -0
- package/dist/core/appearance.js.map +9 -0
- package/dist/core/config.js +8 -0
- package/dist/core/config.js.map +9 -0
- package/dist/core/contract.js +55 -0
- package/dist/core/contract.js.map +9 -0
- package/dist/core/format.js +18 -0
- package/dist/core/format.js.map +9 -0
- package/dist/embed/index.js +91 -0
- package/dist/embed/index.js.map +11 -0
- package/dist/local.js +332 -0
- package/dist/local.js.map +15 -0
- package/dist/react/embed/index.js +130 -0
- package/dist/react/embed/index.js.map +11 -0
- package/dist/react/index.css +4478 -0
- package/dist/react/index.js +6105 -0
- package/dist/react/index.js.map +90 -0
- package/dist/react.css.d.ts +1 -0
- package/dist/server.js +231 -0
- package/dist/server.js.map +14 -0
- package/dist/types/client/iframe.d.ts +39 -0
- package/dist/types/client/index.d.ts +42 -0
- package/dist/types/client/location.d.ts +15 -0
- package/dist/types/client/messages.d.ts +7 -0
- package/dist/types/client/navigation.d.ts +14 -0
- package/dist/types/client/transport.d.ts +15 -0
- package/dist/types/core/appearance.d.ts +28 -0
- package/dist/types/core/bridge.d.ts +35 -0
- package/dist/types/core/config.d.ts +14 -0
- package/dist/types/core/contract.d.ts +139 -0
- package/dist/types/core/data-view.d.ts +46 -0
- package/dist/types/core/date-range.d.ts +20 -0
- package/dist/types/core/dimension.d.ts +60 -0
- package/dist/types/core/format.d.ts +40 -0
- package/dist/types/core/invariant.d.ts +2 -0
- package/dist/types/core/messages.d.ts +77 -0
- package/dist/types/core/reading.d.ts +17 -0
- package/dist/types/core/variables.d.ts +82 -0
- package/dist/types/embed/bootstrap.d.ts +5 -0
- package/dist/types/embed/host.d.ts +23 -0
- package/dist/types/embed/index.d.ts +11 -0
- package/dist/types/embed/navigation.d.ts +6 -0
- package/dist/types/embed/shell.d.ts +21 -0
- package/dist/types/embed/standalone.d.ts +1 -0
- package/dist/types/react/content.d.ts +23 -0
- package/dist/types/react/embed/bridge.d.ts +12 -0
- package/dist/types/react/embed/index.d.ts +9 -0
- package/dist/types/react/embed/shell.d.ts +10 -0
- package/dist/types/react/hooks.d.ts +831 -0
- package/dist/types/react/index.d.ts +11 -0
- package/dist/types/react/mount.d.ts +11 -0
- package/dist/types/react/ui/AboutData.d.ts +61 -0
- package/dist/types/react/ui/AltertableLogo.d.ts +3 -0
- package/dist/types/react/ui/AppFooter.d.ts +7 -0
- package/dist/types/react/ui/AppHeader.d.ts +12 -0
- package/dist/types/react/ui/AppLayout.d.ts +17 -0
- package/dist/types/react/ui/AppScope.d.ts +8 -0
- package/dist/types/react/ui/AppToolbar.d.ts +32 -0
- package/dist/types/react/ui/Breakdown.d.ts +14 -0
- package/dist/types/react/ui/Button.d.ts +12 -0
- package/dist/types/react/ui/Checkbox.d.ts +11 -0
- package/dist/types/react/ui/Combobox.d.ts +43 -0
- package/dist/types/react/ui/ComparisonVisual.d.ts +15 -0
- package/dist/types/react/ui/ContentSkeleton.d.ts +8 -0
- package/dist/types/react/ui/DataApp.d.ts +56 -0
- package/dist/types/react/ui/DataBoundary.d.ts +16 -0
- package/dist/types/react/ui/DataSection.d.ts +28 -0
- package/dist/types/react/ui/DataTable.d.ts +28 -0
- package/dist/types/react/ui/DataViewToast.d.ts +12 -0
- package/dist/types/react/ui/DataWidget.d.ts +39 -0
- package/dist/types/react/ui/DateRangePicker.d.ts +29 -0
- package/dist/types/react/ui/DateTimeTooltip.d.ts +10 -0
- package/dist/types/react/ui/DimensionPicker.d.ts +11 -0
- package/dist/types/react/ui/EmptyState.d.ts +9 -0
- package/dist/types/react/ui/GettingStarted.d.ts +9 -0
- package/dist/types/react/ui/GlossaryDefinition.d.ts +8 -0
- package/dist/types/react/ui/GlossaryExplanation.d.ts +17 -0
- package/dist/types/react/ui/GradientScroll.d.ts +16 -0
- package/dist/types/react/ui/Grid.d.ts +13 -0
- package/dist/types/react/ui/GridItem.d.ts +7 -0
- package/dist/types/react/ui/HelpPopover.d.ts +24 -0
- package/dist/types/react/ui/IconButton.d.ts +19 -0
- package/dist/types/react/ui/InspectionContext.d.ts +10 -0
- package/dist/types/react/ui/Kbd.d.ts +8 -0
- package/dist/types/react/ui/LiveControl.d.ts +11 -0
- package/dist/types/react/ui/MetricWidget.d.ts +48 -0
- package/dist/types/react/ui/PeriodSummary.d.ts +17 -0
- package/dist/types/react/ui/PresentStory.d.ts +34 -0
- package/dist/types/react/ui/QueryList.d.ts +15 -0
- package/dist/types/react/ui/Ranking.d.ts +14 -0
- package/dist/types/react/ui/RefreshControl.d.ts +11 -0
- package/dist/types/react/ui/RefreshRegion.d.ts +10 -0
- package/dist/types/react/ui/RequestHint.d.ts +21 -0
- package/dist/types/react/ui/SearchField.d.ts +23 -0
- package/dist/types/react/ui/SearchInput.d.ts +9 -0
- package/dist/types/react/ui/SearchMatch.d.ts +7 -0
- package/dist/types/react/ui/SelectableBarChart.d.ts +16 -0
- package/dist/types/react/ui/SelectionMark.d.ts +5 -0
- package/dist/types/react/ui/Sheet.d.ts +19 -0
- package/dist/types/react/ui/Skeleton.d.ts +6 -0
- package/dist/types/react/ui/Stack.d.ts +8 -0
- package/dist/types/react/ui/StatusPanel.d.ts +11 -0
- package/dist/types/react/ui/TableWidget.d.ts +57 -0
- package/dist/types/react/ui/Tabs.d.ts +6 -0
- package/dist/types/react/ui/ThemeSelector.d.ts +13 -0
- package/dist/types/react/ui/Tooltip.d.ts +23 -0
- package/dist/types/react/ui/UpdatedAt.d.ts +10 -0
- package/dist/types/react/ui/VariableBar.d.ts +7 -0
- package/dist/types/react/ui/VisualizationWidget.d.ts +52 -0
- package/dist/types/react/ui/WidgetDisclosure.d.ts +7 -0
- package/dist/types/react/ui/WidgetEvidence.d.ts +12 -0
- package/dist/types/react/ui/WidgetViewTabs.d.ts +17 -0
- package/dist/types/react/ui/chartColor.d.ts +1 -0
- package/dist/types/react/ui/classNames.d.ts +1 -0
- package/dist/types/react/ui/comparison.d.ts +30 -0
- package/dist/types/react/ui/data-context.d.ts +65 -0
- package/dist/types/react/ui/data-identifiers.d.ts +33 -0
- package/dist/types/react/ui/icons.d.ts +42 -0
- package/dist/types/react/ui/index.d.ts +129 -0
- package/dist/types/react/ui/metric.d.ts +11 -0
- package/dist/types/react/ui/search.d.ts +6 -0
- package/dist/types/react/ui/searchItems.d.ts +34 -0
- package/dist/types/react/ui/shortcuts.d.ts +32 -0
- package/dist/types/react/ui/story.d.ts +20 -0
- package/dist/types/react/ui/variables.d.ts +36 -0
- package/dist/types/react/ui/widget-views.d.ts +3 -0
- package/dist/types/react/view-controls.d.ts +17 -0
- package/dist/types/react/view.d.ts +49 -0
- package/dist/types/server/handler.d.ts +13 -0
- package/dist/types/server/index.d.ts +7 -0
- package/dist/types/server/local.d.ts +17 -0
- package/docs/app-authoring.md +26 -0
- package/docs/appearance.md +30 -0
- package/docs/bootstrap.md +76 -0
- package/docs/client.md +127 -0
- package/docs/config.md +21 -0
- package/docs/contract.md +95 -0
- package/docs/embed.md +98 -0
- package/docs/format.md +29 -0
- package/docs/react-embed.md +57 -0
- package/docs/react-styles.md +17 -0
- package/docs/react.md +248 -0
- package/docs/releasing.md +74 -0
- package/docs/server-bun.md +26 -0
- package/docs/server.md +47 -0
- package/docs/starter-agent-instructions.md +51 -0
- package/package.json +124 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Standalone bootstrap
|
|
2
|
+
|
|
3
|
+
`@altertable/data-app/bootstrap` resolves to a self-contained classic browser
|
|
4
|
+
script. It has no imports, React dependencies, or app navigation behavior. The
|
|
5
|
+
package builds it once; a backend can embed the published file without running a
|
|
6
|
+
bundler.
|
|
7
|
+
|
|
8
|
+
This entry is a script asset, not a module API. Resolve and read it on the server;
|
|
9
|
+
do not import it for execution in Node.js or Bun. For a bootstrap you bundle
|
|
10
|
+
yourself, use `startDataAppBootstrap` from [embedding](embed.md).
|
|
11
|
+
|
|
12
|
+
## Generate the backend HTML
|
|
13
|
+
|
|
14
|
+
Install an exact package version in the backend runtime's `package.json`, commit
|
|
15
|
+
the lockfile, and run this generation step before building or deploying the worker:
|
|
16
|
+
|
|
17
|
+
```js
|
|
18
|
+
// scripts/build-runtime.mjs
|
|
19
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
20
|
+
|
|
21
|
+
const parentOrigin = 'https://app.altertable.ai';
|
|
22
|
+
const path = import.meta.resolve('@altertable/data-app/bootstrap');
|
|
23
|
+
const javascript = await readFile(new URL(path), 'utf8');
|
|
24
|
+
const inline = javascript.replace(/<\/script/gi, '<\\/script');
|
|
25
|
+
const originAttribute = parentOrigin
|
|
26
|
+
.replaceAll('&', '&')
|
|
27
|
+
.replaceAll('"', '"')
|
|
28
|
+
.replaceAll('<', '<')
|
|
29
|
+
.replaceAll('>', '>');
|
|
30
|
+
|
|
31
|
+
const html = `<!doctype html>
|
|
32
|
+
<html>
|
|
33
|
+
<head>
|
|
34
|
+
<meta charset="utf-8">
|
|
35
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
36
|
+
</head>
|
|
37
|
+
<body>
|
|
38
|
+
<div id="root"></div>
|
|
39
|
+
<script data-parent-origin="${originAttribute}">${inline}</script>
|
|
40
|
+
</body>
|
|
41
|
+
</html>`;
|
|
42
|
+
|
|
43
|
+
await mkdir('generated', { recursive: true });
|
|
44
|
+
await writeFile(
|
|
45
|
+
'generated/runtime-html.js',
|
|
46
|
+
`export const RUNTIME_HTML = ${JSON.stringify(html)};\n`
|
|
47
|
+
);
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
```fish
|
|
51
|
+
node scripts/build-runtime.mjs
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The worker imports `RUNTIME_HTML` and serves it as `text/html; charset=utf-8`.
|
|
55
|
+
The resulting HTML needs no package imports or network fetches at runtime.
|
|
56
|
+
|
|
57
|
+
## Configuration and security
|
|
58
|
+
|
|
59
|
+
Use a regular inline `<script>` with `data-parent-origin` on that same element,
|
|
60
|
+
after the app's mount element. The script reads `document.currentScript` and
|
|
61
|
+
starts immediately. Do not use `type="module"`.
|
|
62
|
+
|
|
63
|
+
The hosting service must supply an exact trusted origin, such as
|
|
64
|
+
`https://app.altertable.ai`, through deployment configuration. Missing or invalid
|
|
65
|
+
origins fail before installing a transport. Do not derive this value from an
|
|
66
|
+
unverified query parameter, referrer, or incoming message.
|
|
67
|
+
|
|
68
|
+
Serve the HTML with the [bootstrap CSP](embed.md#trusted-bootstrap), including a
|
|
69
|
+
`frame-ancestors` header restricted to the trusted host. The script installs the
|
|
70
|
+
authenticated transport, retains opaque host state, evaluates the app bundle
|
|
71
|
+
sent by the host, and reports readiness or failure. The app attaches its own
|
|
72
|
+
navigation adapter. Backend authorization still applies to every data request.
|
|
73
|
+
|
|
74
|
+
Upgrade the host package and bootstrap artifact together when changing the bridge
|
|
75
|
+
protocol. See [client navigation](client.md#app-navigation) and
|
|
76
|
+
[embedding](embed.md) for the communication boundaries.
|
package/docs/client.md
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# Client
|
|
2
|
+
|
|
3
|
+
Import `createDataClient` and `DataAppError` from
|
|
4
|
+
`@altertable/data-app/client`. The client uses Fetch APIs and has no React or
|
|
5
|
+
server dependency.
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { createDataClient } from '@altertable/data-app/client';
|
|
9
|
+
import type { operations } from './operations';
|
|
10
|
+
|
|
11
|
+
const client = createDataClient<typeof operations>();
|
|
12
|
+
const response = await client.query('activity', input, { signal });
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The app defines `operations`, `input`, and an optional cancellation `signal`.
|
|
16
|
+
Operation input and output types are inferred from the server registry. Keep the
|
|
17
|
+
registry import type-only so its SQL and implementation stay on the server.
|
|
18
|
+
|
|
19
|
+
The default endpoint is `/api/data`. Override it with
|
|
20
|
+
`createDataClient({ endpoint, fetch })` to change the base URL or supply a Fetch
|
|
21
|
+
implementation. Calls POST JSON to `/api/data/:operation`; the client sends
|
|
22
|
+
operation inputs rather than SQL or credentials.
|
|
23
|
+
|
|
24
|
+
`DataResponse` contains `data`, the exact request `input`, `requestId`,
|
|
25
|
+
`queriedAt`, and `queryIds`. `queries` is present only when the server permits
|
|
26
|
+
SQL disclosure. Use the returned input when labeling stale data during a refresh.
|
|
27
|
+
`DataAppError` exposes a `code` and optional `requestId`; cancellation follows
|
|
28
|
+
the supplied abort signal.
|
|
29
|
+
|
|
30
|
+
See [contracts](contract.md), [server handlers](server.md), and
|
|
31
|
+
[React bindings](react.md).
|
|
32
|
+
|
|
33
|
+
## Iframe transport
|
|
34
|
+
|
|
35
|
+
`createDataClient({ transport })` accepts a `DataTransport`. Without an explicit
|
|
36
|
+
transport, endpoint, or Fetch implementation, it discovers an installed iframe
|
|
37
|
+
transport before falling back to HTTP. Explicit endpoint/Fetch options select
|
|
38
|
+
HTTP. `createHttpTransport` provides the underlying operation delivery adapter.
|
|
39
|
+
|
|
40
|
+
A URL-hosted app configures trust and installs the bridge before mounting:
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import {
|
|
44
|
+
createIframeTransport,
|
|
45
|
+
installDataAppTransport,
|
|
46
|
+
} from '@altertable/data-app/client';
|
|
47
|
+
|
|
48
|
+
const bridge = createIframeTransport({
|
|
49
|
+
parentOrigin: 'https://host.example.com',
|
|
50
|
+
});
|
|
51
|
+
const uninstall = installDataAppTransport(bridge);
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Use a configured trusted origin, not an unverified URL parameter. Installation makes data clients discover the connection. React mounting and
|
|
55
|
+
URL controls attach the optional navigation adapter to that connection. Cleanup removes
|
|
56
|
+
the installation and disposes pending work. Bundle apps receive this installation
|
|
57
|
+
from the [trusted bootstrap](embed.md#trusted-bootstrap).
|
|
58
|
+
|
|
59
|
+
`getDataAppTransport()` returns the explicitly installed bridge. Its `request`
|
|
60
|
+
transport supports custom typed routes:
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
import {
|
|
64
|
+
createMessageClient,
|
|
65
|
+
getDataAppTransport,
|
|
66
|
+
} from '@altertable/data-app/client';
|
|
67
|
+
|
|
68
|
+
const bridge = getDataAppTransport();
|
|
69
|
+
if (!bridge) throw new Error('An iframe transport must be installed first.');
|
|
70
|
+
const messages = createMessageClient(routes, bridge.request);
|
|
71
|
+
const result = await messages.request('echo', 'hello', { signal });
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
The app supplies shared `routes` and optional `signal`. Both client and host
|
|
75
|
+
validate messages. The bridge retains opaque host state through `snapshot()` and
|
|
76
|
+
`subscribe(listener)`; only authenticated, current-session state reaches subscribers.
|
|
77
|
+
It never changes browser history or interprets the host state.
|
|
78
|
+
|
|
79
|
+
## App navigation
|
|
80
|
+
|
|
81
|
+
Navigation is an optional app adapter, separate from bootstrap and data delivery:
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
import { createDataAppNavigation } from '@altertable/data-app/client';
|
|
85
|
+
|
|
86
|
+
const navigation = createDataAppNavigation({ bridge });
|
|
87
|
+
const currentLocation = navigation.snapshot();
|
|
88
|
+
navigation.update({ search: '?period=last-7', hash: '#daily' }, 'push');
|
|
89
|
+
// If app code changes its URL directly:
|
|
90
|
+
navigation.publish('replace');
|
|
91
|
+
// Before removing the app or its transport:
|
|
92
|
+
navigation.dispose();
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
The adapter provides `snapshot`, `subscribe`, and `update`. Opaque sandboxes keep
|
|
96
|
+
search/hash in memory; URL frames preserve their URL and local-preview parent
|
|
97
|
+
marker. Host Back/Forward state is applied without publishing it back.
|
|
98
|
+
|
|
99
|
+
`getDataAppNavigation()` discovers an installed or verified local-preview bridge
|
|
100
|
+
and shares one adapter per document. React mounting and URL-backed controls call
|
|
101
|
+
it automatically. Apps that only use data delivery do not attach navigation.
|
|
102
|
+
|
|
103
|
+
Migration: replace `bridge.appLocation` with the navigation adapter and
|
|
104
|
+
`bridge.location(mode)` with `navigation.publish(mode)`. The version-1 wire envelope
|
|
105
|
+
now carries host context in `initialize.state` and subsequent `state` messages;
|
|
106
|
+
upgrade independently deployed hosts and runtimes together.
|
|
107
|
+
Transport state is shared per window across separately bundled entry points;
|
|
108
|
+
install only one transport per document. Public error classes retain `instanceof`
|
|
109
|
+
recognition across independent bootstrap and app bundles in that window.
|
|
110
|
+
|
|
111
|
+
## Pending request limits
|
|
112
|
+
|
|
113
|
+
Each iframe bridge accepts at most 128 unresolved requests, including requests
|
|
114
|
+
waiting for the connection handshake. Further calls reject with `bridge_busy`
|
|
115
|
+
until a pending call completes, is cancelled, or times out. This bounds the
|
|
116
|
+
client's promises, timers, and queued messages.
|
|
117
|
+
|
|
118
|
+
The host independently limits pending requests to 128: an iframe can send
|
|
119
|
+
messages directly without using the client helper. The shared cap is a resource
|
|
120
|
+
policy, not a requirement of the message protocol. It rejects excess requests;
|
|
121
|
+
it does not queue them or limit the total number of calls over a session.
|
|
122
|
+
|
|
123
|
+
A failed local-preview verification can be retried by the next explicit data
|
|
124
|
+
request. Concurrent callers share the current verification attempt; aborting
|
|
125
|
+
one caller does not cancel the others. Retrying does not automatically repeat
|
|
126
|
+
a data operation or bypass the configured parent-origin check, and verification
|
|
127
|
+
failure never falls back to untrusted iframe or HTTP delivery.
|
package/docs/config.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# App configuration
|
|
2
|
+
|
|
3
|
+
Import `DataAppConfig` and `dataAppTitle` from `@altertable/data-app/config`.
|
|
4
|
+
Configuration is shared by the app shell and browser mounting code.
|
|
5
|
+
|
|
6
|
+
```ts
|
|
7
|
+
import type { DataAppConfig } from '@altertable/data-app/config';
|
|
8
|
+
|
|
9
|
+
export const config = {
|
|
10
|
+
title: 'Activity',
|
|
11
|
+
scope: { organization: 'example', environment: 'production' },
|
|
12
|
+
appearance: {},
|
|
13
|
+
} satisfies DataAppConfig;
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
`title` names the exploration. `scope` identifies its organization and
|
|
17
|
+
environment for display; it does not grant access to data.
|
|
18
|
+
`appearance` is validated by the [appearance APIs](appearance.md).
|
|
19
|
+
|
|
20
|
+
`dataAppTitle(config)` produces a document title containing the app title and
|
|
21
|
+
scope. [React mounting](react.md) applies it automatically.
|
package/docs/contract.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Contracts
|
|
2
|
+
|
|
3
|
+
Import operation definitions, parsers, and shared types from
|
|
4
|
+
`@altertable/data-app/contract`. This entry is safe to import in browser and server
|
|
5
|
+
modules. Keep SQL and operation implementations on the server; browser modules
|
|
6
|
+
should import their operation types using `import type`.
|
|
7
|
+
|
|
8
|
+
## Execute named queries
|
|
9
|
+
|
|
10
|
+
The app supplies `calendar`, `parseActivity`, `checkInput`, `buildActivitySql`,
|
|
11
|
+
and `parseActivityRows` in this example.
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import {
|
|
15
|
+
defineOperation,
|
|
16
|
+
defineQueryNames,
|
|
17
|
+
} from '@altertable/data-app/contract';
|
|
18
|
+
|
|
19
|
+
const queries = defineQueryNames({ activity: 'feature-activity' });
|
|
20
|
+
const activity = defineOperation({
|
|
21
|
+
queryNames: queries,
|
|
22
|
+
input: calendar.parseRequest,
|
|
23
|
+
output: parseActivity,
|
|
24
|
+
checks: [checkInput],
|
|
25
|
+
policy: { maxQueryRows: 100, maxDurationMs: 15000, exposeSql: true },
|
|
26
|
+
async run({ query }, input) {
|
|
27
|
+
const result = await query(queries.activity, buildActivitySql(input));
|
|
28
|
+
return parseActivityRows(result);
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
`query` inherits the operation's limit and cancellation signal; `{ limit }` can lower a particular query's bound. Names are checked by TypeScript and at runtime. The server records the SQL and query ID when execution occurs, so evidence does not need a separate result field. Browser modules import operation types with `import type`; they never import server implementations.
|
|
34
|
+
|
|
35
|
+
## Shared date ranges
|
|
36
|
+
|
|
37
|
+
Use `defineDateRangeContract` in a browser-safe module to share source coverage,
|
|
38
|
+
time zone, maximum range, and comparison rules between the server parser and
|
|
39
|
+
React variables. The server uses `calendar.parseRequest`; React uses the same
|
|
40
|
+
contract with `dateRangeVariable`.
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import { defineDateRangeContract } from '@altertable/data-app/contract';
|
|
44
|
+
|
|
45
|
+
export const calendar = defineDateRangeContract({
|
|
46
|
+
minDate: '2026-01-01',
|
|
47
|
+
maxRangeDays: 90,
|
|
48
|
+
timeZone: 'UTC',
|
|
49
|
+
});
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
`parseEmptyInput`, `parseTrue`, `parseCount`, and `parseDateRangeInput` validate
|
|
53
|
+
common inputs and results. `connectionCheck()` defines a bounded connectivity
|
|
54
|
+
operation. A successful connectivity check confirms access; it is not an
|
|
55
|
+
analysis result.
|
|
56
|
+
|
|
57
|
+
See [server authorization](server.md) and [React views](react.md) for the two
|
|
58
|
+
sides of an operation.
|
|
59
|
+
|
|
60
|
+
## Message routes
|
|
61
|
+
|
|
62
|
+
Message contracts describe the payloads allowed between an embedded app and its
|
|
63
|
+
host. Share these contracts with the app; keep handlers and authorization in the
|
|
64
|
+
host/server.
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
import {
|
|
68
|
+
createMessageRouter,
|
|
69
|
+
defineMessageRoute,
|
|
70
|
+
navigationUpdateRoute,
|
|
71
|
+
} from '@altertable/data-app/contract';
|
|
72
|
+
import { createNavigationHandler } from '@altertable/data-app/embed';
|
|
73
|
+
|
|
74
|
+
const routes = {
|
|
75
|
+
echo: defineMessageRoute({ input: parseString, output: parseString }),
|
|
76
|
+
'navigation.update': navigationUpdateRoute,
|
|
77
|
+
};
|
|
78
|
+
const router = createMessageRouter(routes, {
|
|
79
|
+
echo: value => value,
|
|
80
|
+
'navigation.update': createNavigationHandler(),
|
|
81
|
+
});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The app defines `parseString` to validate unknown values. `router.dispatch` checks
|
|
85
|
+
registered routes, input, output, and cancellation. `MessageRoutingError` exposes
|
|
86
|
+
an intentional public code, message, and optional request ID; other handler
|
|
87
|
+
errors are replaced with a generic failure.
|
|
88
|
+
|
|
89
|
+
`defineDataQueryRoute(operationContracts)` validates the selected operation's
|
|
90
|
+
input and output and preserves its response evidence. It infers the result type
|
|
91
|
+
from the selected operation when used with `createMessageClient`. Share input and
|
|
92
|
+
output parsers, not operation implementations containing SQL or credentials.
|
|
93
|
+
`dataAppRoutes` supplies generic `data.query` and `navigation.update` contracts;
|
|
94
|
+
generic hosts must delegate operation validation and authorization to their
|
|
95
|
+
server. Request handlers receive `{ signal }` for cancellation.
|
package/docs/embed.md
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Embed a data app
|
|
2
|
+
|
|
3
|
+
Import framework-neutral host APIs from `@altertable/data-app/embed`. For React
|
|
4
|
+
hosts, see [React embedding](react-embed.md). Neither embedding entry requires the
|
|
5
|
+
app UI stylesheet.
|
|
6
|
+
|
|
7
|
+
## Sources
|
|
8
|
+
|
|
9
|
+
`attachDataAppShell` owns iframe loading, sandbox policy, startup timeout, and
|
|
10
|
+
message delivery. The host supplies an iframe and a validated message dispatcher:
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
import { attachDataAppShell } from '@altertable/data-app/embed';
|
|
14
|
+
|
|
15
|
+
const dispose = attachDataAppShell({
|
|
16
|
+
iframe,
|
|
17
|
+
source: { type: 'url', url: 'https://apps.example.com/report' },
|
|
18
|
+
onMessage: router.dispatch,
|
|
19
|
+
});
|
|
20
|
+
```
|
|
21
|
+
|
|
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
|
|
25
|
+
app URL. A hosted app must explicitly install a transport to its configured,
|
|
26
|
+
trusted parent origin using the [client API](client.md#iframe-transport).
|
|
27
|
+
The query parameter alone does not establish trust.
|
|
28
|
+
|
|
29
|
+
A bundle source has this shape:
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
const source = {
|
|
33
|
+
type: 'bundle' as const,
|
|
34
|
+
bootstrapUrl: 'https://preview.example.com/bootstrap',
|
|
35
|
+
javascript: bundle.javascript,
|
|
36
|
+
revision: bundle.revision,
|
|
37
|
+
};
|
|
38
|
+
```
|
|
39
|
+
|
|
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.
|
|
42
|
+
Bundle mode uses `sandbox="allow-scripts"` and an opaque origin; it cannot read
|
|
43
|
+
the host document or use same-origin privileges. Both modes use
|
|
44
|
+
`referrerPolicy="no-referrer"`.
|
|
45
|
+
|
|
46
|
+
## Trusted bootstrap
|
|
47
|
+
|
|
48
|
+
For backend HTML that embeds a ready-made script without bundling, use the
|
|
49
|
+
[standalone bootstrap asset](bootstrap.md).
|
|
50
|
+
|
|
51
|
+
Bundle this initializer into the trusted bootstrap document, before any app code:
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
import { startDataAppBootstrap } from '@altertable/data-app/embed';
|
|
55
|
+
|
|
56
|
+
const dispose = startDataAppBootstrap({
|
|
57
|
+
parentOrigin: 'https://host.example.com',
|
|
58
|
+
});
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
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
|
+
CSP on a remote response. A starting policy for self-contained scripts and styles
|
|
64
|
+
is `default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline';
|
|
65
|
+
img-src data: blob:; connect-src 'none'; base-uri 'none'; form-action 'none'`.
|
|
66
|
+
The initializer installs a shared transport before evaluating the app script;
|
|
67
|
+
data clients discover it even when independently bundled. The bootstrap contains
|
|
68
|
+
no app navigation adapter. React mounting or URL controls attach navigation in the
|
|
69
|
+
app bundle; non-React apps use `createDataAppNavigation` from `/client`.
|
|
70
|
+
|
|
71
|
+
## Delivery and navigation
|
|
72
|
+
|
|
73
|
+
`attachDataAppBridge` is the lower-level API for a host-owned iframe. Supply
|
|
74
|
+
`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
|
|
78
|
+
wildcard delivery, but incoming messages still require the exact iframe window,
|
|
79
|
+
null origin, token, document, and session to match.
|
|
80
|
+
|
|
81
|
+
`createNavigationHandler({ window, reservedSearchParams })` handles the shared
|
|
82
|
+
`navigation.update` route using browser history. Reserved host query parameters
|
|
83
|
+
survive app changes. Supply your own handler when integrating a framework router.
|
|
84
|
+
Host Back/Forward events publish opaque host state through the bridge. The app's
|
|
85
|
+
optional navigation adapter interprets search/hash and synchronizes controls;
|
|
86
|
+
opaque apps keep that state in memory rather than modifying their document URL.
|
|
87
|
+
|
|
88
|
+
## Status and diagnostics
|
|
89
|
+
|
|
90
|
+
`onStatusChange` receives `connecting`, `connected`, `ready`, `failed`, or
|
|
91
|
+
`disconnected`. `connected` means transport initialization; `ready` means a URL
|
|
92
|
+
client acknowledged initialization or a bundle script finished evaluation. It
|
|
93
|
+
does not mean asynchronous data queries finished. Script errors report `failed`.
|
|
94
|
+
The shell's `startupTimeoutMs` defaults to 30 seconds.
|
|
95
|
+
|
|
96
|
+
`onDiagnostic` receives only message `direction` and `type`, never tokens or
|
|
97
|
+
payloads. Routed handlers must authorize every data request. Message validation
|
|
98
|
+
and iframe isolation do not grant access to data or execute SQL.
|
package/docs/format.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Formatting
|
|
2
|
+
|
|
3
|
+
Import number and date helpers from `@altertable/data-app/format`. They use
|
|
4
|
+
JavaScript `Intl` APIs and can run in the browser or on the server.
|
|
5
|
+
|
|
6
|
+
```ts
|
|
7
|
+
import {
|
|
8
|
+
formatCount,
|
|
9
|
+
formatDateRange,
|
|
10
|
+
formatNumber,
|
|
11
|
+
formatPercent,
|
|
12
|
+
} from '@altertable/data-app/format';
|
|
13
|
+
|
|
14
|
+
formatCount(1200); // "1,200"
|
|
15
|
+
formatPercent(0.116); // "11.6%"
|
|
16
|
+
formatNumber(null); // "—"
|
|
17
|
+
formatDateRange({ start: '2026-01-01', end: '2026-01-03' });
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Percent values are ratios: `0.116` means 11.6%. Counts must be non-negative
|
|
21
|
+
integers. Missing or non-finite numbers render as `—`, customizable with
|
|
22
|
+
`missing`. Number formatting defaults to `en-US`; pass `locale` to override it.
|
|
23
|
+
|
|
24
|
+
`formatMetric` accepts a `MetricFormat` with `kind: 'count'`, `'ratio'`, or
|
|
25
|
+
`'currency'`; currency formats also require a currency code. `formatDateRange`
|
|
26
|
+
formats ISO calendar dates in UTC and retains the year in its labels.
|
|
27
|
+
`pluralize(count, singular, plural?)` selects a label for the count.
|
|
28
|
+
|
|
29
|
+
See [React metric definitions](react.md#bound-views-and-widgets).
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# React embedding
|
|
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.
|
|
6
|
+
|
|
7
|
+
## Shell
|
|
8
|
+
|
|
9
|
+
```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
|
+
/>;
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
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.
|
|
24
|
+
|
|
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.
|
|
30
|
+
|
|
31
|
+
## Host-owned iframe
|
|
32
|
+
|
|
33
|
+
Use `DataAppBridge` when the host owns iframe rendering:
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
import { useState, type ComponentRef } from 'react';
|
|
37
|
+
import { DataAppBridge } from '@altertable/data-app/react/embed';
|
|
38
|
+
|
|
39
|
+
function Host() {
|
|
40
|
+
const [iframe, setIframe] = useState<ComponentRef<'iframe'> | null>(null);
|
|
41
|
+
return (
|
|
42
|
+
<>
|
|
43
|
+
<iframe ref={setIframe} title="Report" src={appUrl} />
|
|
44
|
+
<DataAppBridge
|
|
45
|
+
iframe={iframe}
|
|
46
|
+
connection={{ type: 'origin', origin: new URL(appUrl).origin }}
|
|
47
|
+
onMessage={router.dispatch}
|
|
48
|
+
/>
|
|
49
|
+
</>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
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.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# React styles
|
|
2
|
+
|
|
3
|
+
Import the stylesheet once from the browser entry:
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import '@altertable/data-app/react/styles.css';
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
This entry is a CSS asset, with a declaration for TypeScript side-effect imports.
|
|
10
|
+
The app's bundler must process CSS imports. The React JavaScript entry does not
|
|
11
|
+
load it automatically, so server rendering can import components without
|
|
12
|
+
importing CSS into Node.
|
|
13
|
+
|
|
14
|
+
Stylesheets live beside their components in `src/react/ui` and are combined into
|
|
15
|
+
the published stylesheet during the build. Brand settings install semantic CSS
|
|
16
|
+
variables through [appearance](appearance.md); the [React app shell](react.md)
|
|
17
|
+
manages these for normal app usage.
|