@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
package/AGENTS.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# @altertable/data-app
|
|
2
|
+
|
|
3
|
+
## Build an app with this package
|
|
4
|
+
|
|
5
|
+
Start with [app authoring](docs/app-authoring.md) for the data flow and ownership
|
|
6
|
+
boundaries. Read the documentation for the entry you use:
|
|
7
|
+
|
|
8
|
+
| Task | Documentation |
|
|
9
|
+
| -------------------------------- | ------------------------------------------------------------------ |
|
|
10
|
+
| Define operations and inputs | [Contract](docs/contract.md) |
|
|
11
|
+
| Call operations from the browser | [Client](docs/client.md) |
|
|
12
|
+
| Authorize and execute requests | [Server](docs/server.md) |
|
|
13
|
+
| Serve locally with Bun | [Bun server](docs/server-bun.md) |
|
|
14
|
+
| Compose views and request states | [React](docs/react.md), [stylesheet](docs/react-styles.md) |
|
|
15
|
+
| Embed apps in a host | [Embedding](docs/embed.md), [React embedding](docs/react-embed.md) |
|
|
16
|
+
| Serve a trusted bootstrap | [Standalone bootstrap](docs/bootstrap.md) |
|
|
17
|
+
| Configure identity and theme | [Config](docs/config.md), [appearance](docs/appearance.md) |
|
|
18
|
+
| Format values | [Format](docs/format.md) |
|
|
19
|
+
|
|
20
|
+
Import public package entries. Keep SQL, credentials, and execution on the server;
|
|
21
|
+
use `import type` for operation types in browser code. Import the React stylesheet
|
|
22
|
+
once in the browser entry. Edit app-owned files, not installed package files.
|
|
23
|
+
|
|
24
|
+
Inspect source data and time coverage before choosing an exploration. Build
|
|
25
|
+
findings from observed results and preserve loading, empty, error, and stale
|
|
26
|
+
states. The consuming app's instructions define its paths, commands, and analysis
|
|
27
|
+
context; the [starter template](docs/starter-agent-instructions.md) shows how to
|
|
28
|
+
connect them to this guide.
|
|
29
|
+
|
|
30
|
+
## Contribute to the package source
|
|
31
|
+
|
|
32
|
+
When working in this package's source repository, read
|
|
33
|
+
[Contributing](CONTRIBUTING.md) for setup, architecture, and checks. Update docs
|
|
34
|
+
when changing public APIs and run `bun run check` before committing. Do not edit
|
|
35
|
+
generated `dist` files.
|
package/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Install the versions of Bun and Node.js in `.bun-version` and `.node-version`.
|
|
4
|
+
Node supplies npm for the packed-package check; Bun runs the build, tools, and tests.
|
|
5
|
+
`bunfig.toml` sets `install.exact = true` so newly added dependencies use exact
|
|
6
|
+
versions. Keep `packageManager` aligned with `.bun-version`.
|
|
7
|
+
|
|
8
|
+
```fish
|
|
9
|
+
bun install --frozen-lockfile
|
|
10
|
+
bun run check
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Repository layout
|
|
14
|
+
|
|
15
|
+
`src/core` holds shared contracts and data logic. `src/client` owns Fetch transport,
|
|
16
|
+
`src/embed` owns iframe hosting and trusted bootstrap initialization.
|
|
17
|
+
`src/server` owns request handling and the local Bun adapter, and `src/react` owns
|
|
18
|
+
React bindings; `src/react/embed` provides hosts without app UI dependencies.
|
|
19
|
+
Components and their styles live together in `src/react/ui`.
|
|
20
|
+
`docs` describes each public entry. `scripts` builds and checks the published
|
|
21
|
+
artifact. Workflow validation lives in `scripts/check-workflows.sh`, and release
|
|
22
|
+
helpers live in `scripts/release`. Generated `dist` files are ignored; edit source
|
|
23
|
+
instead.
|
|
24
|
+
|
|
25
|
+
`src/embed/standalone.ts` emits `dist/bootstrap.js` as a self-contained classic
|
|
26
|
+
script for backend HTML. It reads its trusted parent origin from the script
|
|
27
|
+
element; keep app navigation and UI dependencies out of this artifact.
|
|
28
|
+
|
|
29
|
+
Keep core, client, server, and embed free of React and UI imports. Client and
|
|
30
|
+
server may depend on core; neither may import the other. Embed may depend on core
|
|
31
|
+
and client. Browser code must not import server
|
|
32
|
+
implementations or credentials. React implementations import focused modules,
|
|
33
|
+
not their own public barrel. Preserve the explicit stylesheet export. Public
|
|
34
|
+
exports and their docs are the consumer interface; internal modules may change.
|
|
35
|
+
|
|
36
|
+
Use repository-root imports (`@/src/...`, `@/package.json`) in source, tests, and
|
|
37
|
+
scripts. Oxlint rejects relative imports and requires declarations for named
|
|
38
|
+
functions (`func-style`). Use method shorthand for object behavior; retain arrows for inline
|
|
39
|
+
callbacks such as collection transforms and hooks. Leave blank lines around
|
|
40
|
+
function declarations, effect hooks, and before returns. Use `useReducer(value =>
|
|
41
|
+
value + 1, 0)` with a `bump…` dispatcher for increment-only counters rather than
|
|
42
|
+
state setters. Run `bun run lint:fix` and `bun run format` for automated fixes.
|
|
43
|
+
The declaration build rewrites aliases to portable relative imports for npm
|
|
44
|
+
consumers.
|
|
45
|
+
|
|
46
|
+
## Checks
|
|
47
|
+
|
|
48
|
+
| Command | Purpose |
|
|
49
|
+
| ------------------------- | ------------------------------------------------ |
|
|
50
|
+
| `bun run typecheck` | Validate source, tests, and scripts |
|
|
51
|
+
| `bun run lint` | Run type-aware Oxlint checks |
|
|
52
|
+
| `bun run format` | Format source and documentation with Oxfmt |
|
|
53
|
+
| `bun run test` | Run contract, transport, and component tests |
|
|
54
|
+
| `bun run build` | Emit ESM, declarations, and the React stylesheet |
|
|
55
|
+
| `bun run test:package` | Check the built npm archive as a consumer |
|
|
56
|
+
| `bun run test:browser` | Test embedding against built exports in Chromium |
|
|
57
|
+
| `bun run check:workflows` | Validate workflows and shell scripts |
|
|
58
|
+
| `bun run check` | Run all required checks |
|
|
59
|
+
|
|
60
|
+
Add focused tests for changed behavior and update the corresponding entry docs
|
|
61
|
+
when changing public APIs. JSDoc should explain constraints, ownership, units,
|
|
62
|
+
or runtime boundaries without duplicating the full API guide. The packed-package
|
|
63
|
+
check verifies documentation, exports, declarations, browser CSS, and server
|
|
64
|
+
imports. The package retains the
|
|
65
|
+
runtime's Bun test suite; tests use observable output and contract behavior.
|
|
66
|
+
|
|
67
|
+
Use Conventional Commits (`fix:`, `feat:`, `docs:`) and describe behavior changes
|
|
68
|
+
and verification in pull requests. PR titles are checked against these types.
|
|
69
|
+
Flag breaking API changes explicitly.
|
|
70
|
+
|
|
71
|
+
Build the package, then run `bash scripts/install-test-browser.sh` once and
|
|
72
|
+
`bun run test:browser` for iframe work. Browser fixtures import public built
|
|
73
|
+
exports; the bootstrap uses the published standalone asset and app scripts are
|
|
74
|
+
bundled independently.
|
|
75
|
+
|
|
76
|
+
CI runs source and packed-package checks, validates workflow syntax, reviews
|
|
77
|
+
dependency changes, and analyzes JavaScript and TypeScript with CodeQL.
|
|
78
|
+
Dependabot updates Bun dependencies and GitHub Actions weekly. Release Please
|
|
79
|
+
creates version and changelog PRs; merging a release PR publishes the verified
|
|
80
|
+
release tag to npm. See [Releasing](docs/releasing.md) for setup and recovery.
|
|
81
|
+
|
|
82
|
+
App-authoring instructions belong to a consuming app's `AGENTS.md`; see the
|
|
83
|
+
[starter template](docs/starter-agent-instructions.md).
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) Altertable
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Altertable Data App
|
|
2
|
+
|
|
3
|
+
[](./LICENSE)
|
|
4
|
+
|
|
5
|
+
TypeScript contracts, a data client, server handlers, and React UI for building
|
|
6
|
+
interactive apps with Altertable data.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
```fish
|
|
11
|
+
# npm
|
|
12
|
+
npm install @altertable/data-app
|
|
13
|
+
|
|
14
|
+
# pnpm
|
|
15
|
+
pnpm add @altertable/data-app
|
|
16
|
+
|
|
17
|
+
# Bun
|
|
18
|
+
bun add @altertable/data-app
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
For React apps, also install React 19.2 or newer and React DOM 19.2 or newer, then import the stylesheet
|
|
22
|
+
once in your browser entry:
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
import '@altertable/data-app/react/styles.css';
|
|
26
|
+
import { mountDataApp } from '@altertable/data-app/react';
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Documentation
|
|
30
|
+
|
|
31
|
+
| Entry | Use |
|
|
32
|
+
| ----------------------------------------- | ----------------------------------------------------- |
|
|
33
|
+
| [/contract](docs/contract.md) | Define typed operations, parsers, and date ranges |
|
|
34
|
+
| [/client](docs/client.md) | Call server operations with `fetch` |
|
|
35
|
+
| [/server](docs/server.md) | Authorize and execute requests on a hosted server |
|
|
36
|
+
| [/server/bun](docs/server-bun.md) | Serve an app locally with Bun |
|
|
37
|
+
| [/embed](docs/embed.md) | Host URL apps or sandboxed JavaScript bundles |
|
|
38
|
+
| [/bootstrap](docs/bootstrap.md) | Embed the standalone bootstrap script in backend HTML |
|
|
39
|
+
| [/react/embed](docs/react-embed.md) | Embed apps with React shells and bridges |
|
|
40
|
+
| [/react](docs/react.md) | Compose views, filters, widgets, and request states |
|
|
41
|
+
| [/react/styles.css](docs/react-styles.md) | Load the React UI stylesheet |
|
|
42
|
+
| [/config](docs/config.md) | Define app identity and scope |
|
|
43
|
+
| [/appearance](docs/appearance.md) | Configure brand tokens and viewer theme |
|
|
44
|
+
| [/format](docs/format.md) | Format dates, counts, ratios, and currencies |
|
|
45
|
+
|
|
46
|
+
Start with [app authoring](docs/app-authoring.md) for the complete data flow.
|
|
47
|
+
The package ships these docs alongside its built JavaScript and declarations.
|
|
48
|
+
For agent-assisted work, direct your agent to
|
|
49
|
+
`node_modules/@altertable/data-app/AGENTS.md`.
|
|
50
|
+
|
|
51
|
+
## Development
|
|
52
|
+
|
|
53
|
+
```fish
|
|
54
|
+
bun install --frozen-lockfile
|
|
55
|
+
bun run check
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
See [Contributing](CONTRIBUTING.md) for repository structure and focused checks.
|
|
59
|
+
Releases use [Release Please and npm trusted publishing](docs/releasing.md).
|
|
60
|
+
|
|
61
|
+
## License
|
|
62
|
+
|
|
63
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
(() => {
|
|
2
|
+
// src/core/bridge.ts
|
|
3
|
+
var BRIDGE = "altertable:data-app";
|
|
4
|
+
var MAX_PENDING = 128;
|
|
5
|
+
var REQUEST_TIMEOUT_MS = 60000;
|
|
6
|
+
function isBridgeMessage(value) {
|
|
7
|
+
if (!value || typeof value !== "object")
|
|
8
|
+
return false;
|
|
9
|
+
const message = value;
|
|
10
|
+
return message.channel === BRIDGE && message.version === 1 && typeof message.type === "string" && typeof message.documentId === "string" && message.documentId.length > 0 && message.documentId.length <= 128;
|
|
11
|
+
}
|
|
12
|
+
function validId(value) {
|
|
13
|
+
return typeof value === "string" && /^[a-zA-Z0-9_-]{1,128}$/.test(value);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// src/core/messages.ts
|
|
17
|
+
var errorBrand = Symbol.for("@altertable/data-app/MessageRoutingError");
|
|
18
|
+
|
|
19
|
+
class MessageRoutingError extends Error {
|
|
20
|
+
code;
|
|
21
|
+
requestId;
|
|
22
|
+
[errorBrand] = true;
|
|
23
|
+
static [Symbol.hasInstance](value) {
|
|
24
|
+
return value instanceof Error && value[errorBrand] === true;
|
|
25
|
+
}
|
|
26
|
+
constructor(code, message, requestId) {
|
|
27
|
+
super(message);
|
|
28
|
+
this.code = code;
|
|
29
|
+
this.requestId = requestId;
|
|
30
|
+
this.name = "MessageRoutingError";
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function defineDataQueryRoute(operations) {
|
|
34
|
+
return {
|
|
35
|
+
operations,
|
|
36
|
+
input(value) {
|
|
37
|
+
if (!value || typeof value !== "object")
|
|
38
|
+
throw new Error("Invalid query.");
|
|
39
|
+
const request = value;
|
|
40
|
+
if (typeof request.operation !== "string" || !request.operation || request.operation.length > 256 || request.operation.includes("/"))
|
|
41
|
+
throw new Error("Invalid operation.");
|
|
42
|
+
const operation = operations && Object.hasOwn(operations, request.operation) ? operations[request.operation] : undefined;
|
|
43
|
+
if (operations && !operation)
|
|
44
|
+
throw new Error("Unknown operation.");
|
|
45
|
+
return {
|
|
46
|
+
operation: request.operation,
|
|
47
|
+
input: operation ? operation.input(request.input) : request.input
|
|
48
|
+
};
|
|
49
|
+
},
|
|
50
|
+
output(value, input) {
|
|
51
|
+
if (!value || typeof value !== "object")
|
|
52
|
+
throw new Error("Invalid data response.");
|
|
53
|
+
const response = value;
|
|
54
|
+
if (!Number.isInteger(response.status) || response.status < 100 || response.status > 599 || !response.body || typeof response.body !== "object")
|
|
55
|
+
throw new Error("Invalid data response.");
|
|
56
|
+
const body = response.body;
|
|
57
|
+
if (response.status < 200 || response.status >= 300) {
|
|
58
|
+
const failure = body.error;
|
|
59
|
+
throw new MessageRoutingError(typeof failure?.code === "string" ? failure.code : "request_failed", typeof failure?.message === "string" ? failure.message : "Could not load data.", typeof failure?.requestId === "string" ? failure.requestId : undefined);
|
|
60
|
+
}
|
|
61
|
+
if (!("data" in body) || typeof body.requestId !== "string" || typeof body.queriedAt !== "string" || !Array.isArray(body.queryIds) || !body.queryIds.every((id) => typeof id === "string"))
|
|
62
|
+
throw new Error("Invalid data envelope.");
|
|
63
|
+
if (body.queries !== undefined && (!Array.isArray(body.queries) || !body.queries.every((query) => query && typeof query === "object" && typeof query.name === "string" && typeof query.statement === "string" && (query.queryId === undefined || typeof query.queryId === "string"))))
|
|
64
|
+
throw new Error("Invalid query evidence.");
|
|
65
|
+
const operation = operations?.[input.operation];
|
|
66
|
+
return {
|
|
67
|
+
status: response.status,
|
|
68
|
+
body: {
|
|
69
|
+
...body,
|
|
70
|
+
data: operation ? operation.output(body.data) : body.data
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// src/client/messages.ts
|
|
78
|
+
function createMessageClient(routes, transport) {
|
|
79
|
+
return {
|
|
80
|
+
async request(route, payload, { signal } = {}) {
|
|
81
|
+
signal?.throwIfAborted();
|
|
82
|
+
if (!Object.hasOwn(routes, route))
|
|
83
|
+
throw new MessageRoutingError("unknown_route", "Unknown message route.");
|
|
84
|
+
const contract = routes[route];
|
|
85
|
+
let input;
|
|
86
|
+
try {
|
|
87
|
+
input = contract.input(payload);
|
|
88
|
+
} catch {
|
|
89
|
+
throw new MessageRoutingError("invalid_payload", "Invalid message payload.");
|
|
90
|
+
}
|
|
91
|
+
const response = await transport({ route, payload: input }, signal);
|
|
92
|
+
signal?.throwIfAborted();
|
|
93
|
+
try {
|
|
94
|
+
return contract.output(response, input);
|
|
95
|
+
} catch (error) {
|
|
96
|
+
if (error instanceof MessageRoutingError)
|
|
97
|
+
throw error;
|
|
98
|
+
throw new MessageRoutingError("invalid_response", "Invalid message response.");
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// src/client/transport.ts
|
|
105
|
+
var errorBrand2 = Symbol.for("@altertable/data-app/DataAppError");
|
|
106
|
+
|
|
107
|
+
class DataAppError extends Error {
|
|
108
|
+
code;
|
|
109
|
+
requestId;
|
|
110
|
+
[errorBrand2] = true;
|
|
111
|
+
static [Symbol.hasInstance](value) {
|
|
112
|
+
return value instanceof Error && value[errorBrand2] === true;
|
|
113
|
+
}
|
|
114
|
+
constructor(message, code, requestId) {
|
|
115
|
+
super(message);
|
|
116
|
+
this.code = code;
|
|
117
|
+
this.requestId = requestId;
|
|
118
|
+
this.name = "DataAppError";
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// src/client/iframe.ts
|
|
123
|
+
function createIframeTransport({
|
|
124
|
+
parentOrigin,
|
|
125
|
+
window: frame = window,
|
|
126
|
+
timeoutMs = REQUEST_TIMEOUT_MS,
|
|
127
|
+
mode = "url",
|
|
128
|
+
loadScript
|
|
129
|
+
}) {
|
|
130
|
+
if (new URL(parentOrigin).origin !== parentOrigin)
|
|
131
|
+
throw new Error("Expected an exact parent origin.");
|
|
132
|
+
const documentId = crypto.randomUUID();
|
|
133
|
+
let token;
|
|
134
|
+
let sessionId;
|
|
135
|
+
let disposed = false;
|
|
136
|
+
const pending = new Map;
|
|
137
|
+
let hostState;
|
|
138
|
+
const stateListeners = new Set;
|
|
139
|
+
function send(message) {
|
|
140
|
+
frame.parent.postMessage({ channel: BRIDGE, version: 1, documentId, sessionId, token, ...message }, parentOrigin);
|
|
141
|
+
}
|
|
142
|
+
function receiveState(state) {
|
|
143
|
+
hostState = state;
|
|
144
|
+
for (const listener of stateListeners)
|
|
145
|
+
listener(state);
|
|
146
|
+
}
|
|
147
|
+
function receive(event) {
|
|
148
|
+
if (event.origin !== parentOrigin || event.source !== frame.parent || !isBridgeMessage(event.data))
|
|
149
|
+
return;
|
|
150
|
+
const message = event.data;
|
|
151
|
+
if (message.type === "connect") {
|
|
152
|
+
if (mode === "bundle") {
|
|
153
|
+
if (!validId(message.token))
|
|
154
|
+
return;
|
|
155
|
+
token = message.token;
|
|
156
|
+
}
|
|
157
|
+
send({ type: "ready" });
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
if (mode === "bundle" && (!token || message.token !== token))
|
|
161
|
+
return;
|
|
162
|
+
if (message.documentId !== documentId)
|
|
163
|
+
return;
|
|
164
|
+
if (message.type === "initialize" && validId(message.sessionId)) {
|
|
165
|
+
if (sessionId && sessionId !== message.sessionId) {
|
|
166
|
+
for (const entry of pending.values())
|
|
167
|
+
entry.reject(new DataAppError("The preview reconnected. Retry the request.", "bridge_reset"));
|
|
168
|
+
}
|
|
169
|
+
const first = !sessionId;
|
|
170
|
+
sessionId = message.sessionId;
|
|
171
|
+
if (first)
|
|
172
|
+
for (const entry of pending.values())
|
|
173
|
+
entry.start();
|
|
174
|
+
receiveState(message.state);
|
|
175
|
+
if (mode === "url")
|
|
176
|
+
send({ type: "runtime.ready" });
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (!sessionId || message.sessionId !== sessionId)
|
|
180
|
+
return;
|
|
181
|
+
if (message.type === "state") {
|
|
182
|
+
receiveState(message.state);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
if (message.type === "script.load" && mode === "bundle" && typeof message.javascript === "string") {
|
|
186
|
+
loadScript?.(message.javascript);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
if (typeof message.id !== "string")
|
|
190
|
+
return;
|
|
191
|
+
const entry = pending.get(message.id);
|
|
192
|
+
if (!entry)
|
|
193
|
+
return;
|
|
194
|
+
if (message.type === "result" && "response" in message) {
|
|
195
|
+
entry.resolve(message.response);
|
|
196
|
+
} else if (message.type === "error" && typeof message.code === "string" && typeof message.message === "string") {
|
|
197
|
+
entry.reject(new MessageRoutingError(message.code, message.message, typeof message.requestId === "string" ? message.requestId : undefined));
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
frame.addEventListener("message", receive);
|
|
201
|
+
function requestMessage(message, signal) {
|
|
202
|
+
return new Promise((resolve, reject) => {
|
|
203
|
+
if (disposed)
|
|
204
|
+
return reject(new DataAppError("The preview has closed.", "bridge_closed"));
|
|
205
|
+
if (signal?.aborted)
|
|
206
|
+
return reject(signal.reason);
|
|
207
|
+
if (pending.size >= MAX_PENDING)
|
|
208
|
+
return reject(new DataAppError("Too many pending data requests.", "bridge_busy"));
|
|
209
|
+
const id = crypto.randomUUID();
|
|
210
|
+
let sent = false;
|
|
211
|
+
function cleanup() {
|
|
212
|
+
clearTimeout(timer);
|
|
213
|
+
signal?.removeEventListener("abort", abort);
|
|
214
|
+
pending.delete(id);
|
|
215
|
+
}
|
|
216
|
+
function abort() {
|
|
217
|
+
if (sent)
|
|
218
|
+
send({ type: "cancel", id });
|
|
219
|
+
cleanup();
|
|
220
|
+
reject(signal?.reason);
|
|
221
|
+
}
|
|
222
|
+
const timer = setTimeout(() => {
|
|
223
|
+
if (sent)
|
|
224
|
+
send({ type: "cancel", id });
|
|
225
|
+
cleanup();
|
|
226
|
+
reject(new DataAppError(sessionId ? "The data request timed out." : "The preview shell is unavailable. Reload the preview.", sessionId ? "timeout" : "bridge_unavailable"));
|
|
227
|
+
}, timeoutMs);
|
|
228
|
+
const entry = {
|
|
229
|
+
start() {
|
|
230
|
+
if (sent)
|
|
231
|
+
return;
|
|
232
|
+
try {
|
|
233
|
+
send({
|
|
234
|
+
type: "request",
|
|
235
|
+
id,
|
|
236
|
+
route: message.route,
|
|
237
|
+
payload: message.payload
|
|
238
|
+
});
|
|
239
|
+
sent = true;
|
|
240
|
+
} catch {
|
|
241
|
+
cleanup();
|
|
242
|
+
reject(new DataAppError("The message payload could not be sent.", "invalid_payload"));
|
|
243
|
+
}
|
|
244
|
+
},
|
|
245
|
+
resolve(value) {
|
|
246
|
+
cleanup();
|
|
247
|
+
resolve(value);
|
|
248
|
+
},
|
|
249
|
+
reject(error) {
|
|
250
|
+
cleanup();
|
|
251
|
+
reject(error);
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
pending.set(id, entry);
|
|
255
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
256
|
+
if (sessionId)
|
|
257
|
+
entry.start();
|
|
258
|
+
else
|
|
259
|
+
send({ type: "ready" });
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
const messages = createMessageClient({ "data.query": defineDataQueryRoute() }, requestMessage);
|
|
263
|
+
async function queryData(operation, input, signal) {
|
|
264
|
+
try {
|
|
265
|
+
return await messages.request("data.query", { operation, input }, { signal });
|
|
266
|
+
} catch (error) {
|
|
267
|
+
if (error instanceof MessageRoutingError)
|
|
268
|
+
throw new DataAppError(error.message, error.code, error.requestId);
|
|
269
|
+
throw error;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
function disconnect() {
|
|
273
|
+
send({ type: "disconnect" });
|
|
274
|
+
sessionId = undefined;
|
|
275
|
+
for (const entry of pending.values())
|
|
276
|
+
entry.reject(new DataAppError("The preview has closed.", "bridge_closed"));
|
|
277
|
+
}
|
|
278
|
+
function resume(event) {
|
|
279
|
+
if (event.persisted)
|
|
280
|
+
send({ type: "ready" });
|
|
281
|
+
}
|
|
282
|
+
function dispose() {
|
|
283
|
+
if (disposed)
|
|
284
|
+
return;
|
|
285
|
+
disconnect();
|
|
286
|
+
disposed = true;
|
|
287
|
+
frame.removeEventListener("message", receive);
|
|
288
|
+
frame.removeEventListener("pagehide", disconnect);
|
|
289
|
+
frame.removeEventListener("pageshow", resume);
|
|
290
|
+
stateListeners.clear();
|
|
291
|
+
}
|
|
292
|
+
frame.addEventListener("pagehide", disconnect);
|
|
293
|
+
frame.addEventListener("pageshow", resume);
|
|
294
|
+
if (mode === "url")
|
|
295
|
+
send({ type: "ready" });
|
|
296
|
+
return {
|
|
297
|
+
request: requestMessage,
|
|
298
|
+
transport: queryData,
|
|
299
|
+
dispose,
|
|
300
|
+
mode,
|
|
301
|
+
snapshot() {
|
|
302
|
+
return hostState;
|
|
303
|
+
},
|
|
304
|
+
subscribe(listener) {
|
|
305
|
+
stateListeners.add(listener);
|
|
306
|
+
return () => {
|
|
307
|
+
stateListeners.delete(listener);
|
|
308
|
+
};
|
|
309
|
+
},
|
|
310
|
+
ready() {
|
|
311
|
+
send({ type: "runtime.ready" });
|
|
312
|
+
},
|
|
313
|
+
fail() {
|
|
314
|
+
send({ type: "runtime.error" });
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
var bridgeKey = Symbol.for("altertable.localFrameBridge");
|
|
319
|
+
var installedKey = Symbol.for("altertable.installedTransport");
|
|
320
|
+
function installDataAppTransport(bridge, frame = window) {
|
|
321
|
+
const target = frame;
|
|
322
|
+
if (target[installedKey])
|
|
323
|
+
throw new Error("A data app transport is already installed.");
|
|
324
|
+
target[bridgeKey] = bridge;
|
|
325
|
+
target[installedKey] = bridge;
|
|
326
|
+
return () => {
|
|
327
|
+
if (target[bridgeKey] === bridge)
|
|
328
|
+
delete target[bridgeKey];
|
|
329
|
+
if (target[installedKey] === bridge)
|
|
330
|
+
delete target[installedKey];
|
|
331
|
+
bridge.dispose();
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// src/embed/bootstrap.ts
|
|
336
|
+
function startDataAppBootstrap({
|
|
337
|
+
parentOrigin,
|
|
338
|
+
window: frame = window
|
|
339
|
+
}) {
|
|
340
|
+
let loaded;
|
|
341
|
+
let failed = false;
|
|
342
|
+
const bridge = createIframeTransport({
|
|
343
|
+
parentOrigin,
|
|
344
|
+
window: frame,
|
|
345
|
+
mode: "bundle",
|
|
346
|
+
loadScript(javascript) {
|
|
347
|
+
if (loaded !== undefined) {
|
|
348
|
+
if (javascript === loaded) {
|
|
349
|
+
if (failed)
|
|
350
|
+
bridge.fail();
|
|
351
|
+
else
|
|
352
|
+
bridge.ready();
|
|
353
|
+
} else
|
|
354
|
+
bridge.fail();
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
loaded = javascript;
|
|
358
|
+
try {
|
|
359
|
+
const script = frame.document.createElement("script");
|
|
360
|
+
script.textContent = javascript;
|
|
361
|
+
frame.document.body.append(script);
|
|
362
|
+
if (!failed)
|
|
363
|
+
bridge.ready();
|
|
364
|
+
} catch {
|
|
365
|
+
reportError();
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
function reportError() {
|
|
370
|
+
failed = true;
|
|
371
|
+
bridge.fail();
|
|
372
|
+
}
|
|
373
|
+
frame.addEventListener("error", reportError);
|
|
374
|
+
frame.addEventListener("unhandledrejection", reportError);
|
|
375
|
+
const uninstall = installDataAppTransport(bridge, frame);
|
|
376
|
+
return () => {
|
|
377
|
+
frame.removeEventListener("error", reportError);
|
|
378
|
+
frame.removeEventListener("unhandledrejection", reportError);
|
|
379
|
+
uninstall();
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// src/embed/standalone.ts
|
|
384
|
+
var parentOrigin = document.currentScript?.dataset.parentOrigin;
|
|
385
|
+
if (!parentOrigin)
|
|
386
|
+
throw new Error("Missing trusted parent origin.");
|
|
387
|
+
startDataAppBootstrap({ parentOrigin });
|
|
388
|
+
})();
|