@altertable/data-app 0.62.0 → 0.63.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 +4 -34
- package/CONTRIBUTING.md +63 -75
- package/README.md +14 -38
- package/dist/chunks/{contract-xjv197ck.js → contract-8wdybxj7.js} +8 -8
- package/dist/chunks/{contract-xjv197ck.js.map → contract-8wdybxj7.js.map} +3 -3
- package/dist/chunks/{contract-ryyf6dme.js → contract-cxr9t12b.js} +2 -5
- package/dist/chunks/contract-cxr9t12b.js.map +10 -0
- package/dist/chunks/contract-farfe948.js.map +2 -2
- package/dist/chunks/contract-mev09s5v.js.map +2 -2
- package/dist/chunks/{contract-3bnrf5pf.js → contract-tf8c3qpv.js} +11 -3
- package/dist/chunks/contract-tf8c3qpv.js.map +11 -0
- package/dist/chunks/{contract-4vrw9zk9.js → contract-tkc552ze.js} +69 -60
- package/dist/chunks/contract-tkc552ze.js.map +12 -0
- package/dist/chunks/{contract-nt819swq.js → contract-wd8qe3mt.js} +6 -1
- package/dist/chunks/{contract-nt819swq.js.map → contract-wd8qe3mt.js.map} +3 -3
- package/dist/chunks/contract-wz59z8pq.js.map +1 -1
- package/dist/chunks/{contract-rrm7s5zp.js → contract-zr3jd7mr.js} +5 -5
- package/dist/chunks/contract-zr3jd7mr.js.map +12 -0
- package/dist/client/index.js +7 -8
- package/dist/client/index.js.map +1 -1
- package/dist/core/contract.js +2 -2
- package/dist/embed/index.js +9 -11
- package/dist/embed/index.js.map +2 -2
- package/dist/local.js +7 -31
- package/dist/local.js.map +5 -7
- package/dist/react/embed/index.js +2 -3
- package/dist/react/embed/index.js.map +2 -2
- package/dist/react/index.js +5437 -221
- package/dist/react/index.js.map +70 -65
- package/dist/server.js +7 -31
- package/dist/server.js.map +5 -7
- package/dist/types/client/data-client.d.ts +33 -0
- package/dist/types/client/index.d.ts +5 -41
- package/dist/types/client/location.d.ts +4 -6
- package/dist/types/client/navigation.d.ts +2 -1
- package/dist/types/core/bridge.d.ts +2 -8
- package/dist/types/core/config.d.ts +1 -1
- package/dist/types/core/contract.d.ts +9 -56
- package/dist/types/core/format.d.ts +0 -1
- package/dist/types/core/messages.d.ts +5 -30
- package/dist/types/core/navigation.d.ts +11 -0
- package/dist/types/core/operation-types.d.ts +76 -0
- package/dist/types/core/operation.d.ts +2 -8
- package/dist/types/core/variables.d.ts +2 -2
- package/dist/types/embed/host.d.ts +5 -3
- package/dist/types/embed/index.d.ts +0 -1
- package/dist/types/embed/source.d.ts +2 -9
- package/dist/types/react/content.d.ts +3 -3
- package/dist/types/react/hooks.d.ts +65 -64
- package/dist/types/react/index.d.ts +135 -2
- package/dist/types/react/injectStyles.d.ts +7 -0
- package/dist/types/react/shellStyles.d.ts +3 -0
- package/dist/types/react/styles.d.ts +8 -0
- package/dist/types/react/ui/AboutData.d.ts +0 -1
- package/dist/types/react/ui/AppFooter.d.ts +0 -1
- package/dist/types/react/ui/AppHeader.d.ts +0 -1
- package/dist/types/react/ui/AppLayout.d.ts +0 -1
- package/dist/types/react/ui/AppScope.d.ts +0 -1
- package/dist/types/react/ui/AppToolbar.d.ts +0 -1
- package/dist/types/react/ui/Breakdown.d.ts +0 -1
- package/dist/types/react/ui/Button.d.ts +0 -1
- package/dist/types/react/ui/Checkbox.d.ts +0 -1
- package/dist/types/react/ui/Combobox.d.ts +0 -1
- package/dist/types/react/ui/ComparisonVisual.d.ts +0 -1
- package/dist/types/react/ui/ContentSkeleton.d.ts +2 -5
- package/dist/types/react/ui/DataApp.d.ts +2 -2
- package/dist/types/react/ui/DataAppSkeleton.d.ts +0 -1
- package/dist/types/react/ui/DataBoundary.d.ts +0 -1
- package/dist/types/react/ui/DataSection.d.ts +2 -4
- package/dist/types/react/ui/DataTable.d.ts +2 -3
- package/dist/types/react/ui/DataViewToast.d.ts +0 -1
- package/dist/types/react/ui/DataWidget.d.ts +4 -14
- package/dist/types/react/ui/DateRangePicker.d.ts +0 -1
- package/dist/types/react/ui/DateTimeTooltip.d.ts +0 -1
- package/dist/types/react/ui/EmptyState.d.ts +2 -5
- package/dist/types/react/ui/GettingStarted.d.ts +0 -1
- package/dist/types/react/ui/GlossaryDefinition.d.ts +0 -1
- package/dist/types/react/ui/GlossaryExplanation.d.ts +0 -1
- package/dist/types/react/ui/GradientScroll.d.ts +0 -1
- package/dist/types/react/ui/Grid.d.ts +0 -1
- package/dist/types/react/ui/HelpPopover.d.ts +0 -2
- package/dist/types/react/ui/Kbd.d.ts +0 -1
- package/dist/types/react/ui/LiveControl.d.ts +0 -1
- package/dist/types/react/ui/MetricWidget.d.ts +0 -2
- package/dist/types/react/ui/PeriodSummary.d.ts +0 -1
- package/dist/types/react/ui/PresentStory.d.ts +0 -1
- package/dist/types/react/ui/QueryList.d.ts +0 -1
- package/dist/types/react/ui/Ranking.d.ts +0 -1
- package/dist/types/react/ui/RefreshControl.d.ts +0 -1
- package/dist/types/react/ui/RefreshRegion.d.ts +0 -1
- package/dist/types/react/ui/RequestHint.d.ts +0 -1
- package/dist/types/react/ui/SearchField.d.ts +0 -1
- package/dist/types/react/ui/SearchInput.d.ts +0 -1
- package/dist/types/react/ui/SearchMatch.d.ts +0 -1
- package/dist/types/react/ui/SelectableBarChart.d.ts +0 -1
- package/dist/types/react/ui/SelectionMark.d.ts +0 -1
- package/dist/types/react/ui/Sheet.d.ts +0 -1
- package/dist/types/react/ui/Skeleton.d.ts +0 -1
- package/dist/types/react/ui/Stack.d.ts +0 -1
- package/dist/types/react/ui/StatusPanel.d.ts +0 -1
- package/dist/types/react/ui/TableWidget.d.ts +2 -3
- package/dist/types/react/ui/Tabs.d.ts +0 -1
- package/dist/types/react/ui/TextContent.d.ts +4 -0
- package/dist/types/react/ui/TextWidget.d.ts +19 -0
- package/dist/types/react/ui/ThemeSelector.d.ts +0 -1
- package/dist/types/react/ui/Tooltip.d.ts +0 -1
- package/dist/types/react/ui/UpdatedAt.d.ts +0 -1
- package/dist/types/react/ui/VariableBar.d.ts +0 -1
- package/dist/types/react/ui/VisualizationWidget.d.ts +5 -13
- package/dist/types/react/ui/WidgetDisclosure.d.ts +0 -1
- package/dist/types/react/ui/WidgetViewTabs.d.ts +2 -3
- package/dist/types/react/ui/data-identifiers.d.ts +0 -1
- package/dist/types/react/ui/presentation.d.ts +19 -0
- package/dist/types/react/view-controls.d.ts +1 -1
- package/dist/types/react/view.d.ts +2 -2
- package/docs/app-authoring.md +60 -44
- package/docs/client.md +11 -19
- package/docs/contract.md +9 -9
- package/docs/embed.md +10 -29
- package/docs/hosted-apps.md +39 -0
- package/docs/local-data-apps.md +14 -0
- package/docs/react-embed.md +10 -19
- package/docs/react.md +108 -134
- package/docs/server-bun.md +4 -6
- package/docs/server.md +2 -2
- package/docs/worker.md +3 -5
- package/examples/starter-data-app/index.tsx +159 -0
- package/package.json +16 -10
- package/dist/chunks/contract-3bnrf5pf.js.map +0 -10
- package/dist/chunks/contract-4vrw9zk9.js.map +0 -12
- package/dist/chunks/contract-rrm7s5zp.js.map +0 -12
- package/dist/chunks/contract-ryyf6dme.js.map +0 -10
- package/dist/react/index.css +0 -4628
- package/dist/react.css.d.ts +0 -1
- package/dist/types/react/ui/index.d.ts +0 -131
- package/docs/appearance.md +0 -43
- package/docs/config.md +0 -21
- package/docs/format.md +0 -29
- package/docs/react-styles.md +0 -17
- package/docs/starter-agent-instructions.md +0 -53
|
@@ -2,10 +2,9 @@ import { type ComponentPropsWithRef, type ReactNode } from 'react';
|
|
|
2
2
|
import type { WidgetEvidence } from './WidgetEvidence.js';
|
|
3
3
|
import type { WidgetStatus } from './RequestHint.js';
|
|
4
4
|
import { type DataTableSearch } from './DataTable.js';
|
|
5
|
-
import type {
|
|
5
|
+
import type { EmptyContent } from './presentation.js';
|
|
6
6
|
import { type SearchHit, type SearchItemsOptions } from './searchItems.js';
|
|
7
7
|
import type { DataReading } from '../../core/reading.js';
|
|
8
|
-
|
|
9
8
|
export type TableWidgetColumn<Row> = {
|
|
10
9
|
/** Stable, nonempty identity; unique within this table. */
|
|
11
10
|
id: string;
|
|
@@ -28,7 +27,7 @@ type TableWidgetBaseProps<Row> = {
|
|
|
28
27
|
evidence?: WidgetEvidence;
|
|
29
28
|
search?: TableWidgetSearch<Row>;
|
|
30
29
|
/** Valid result with no rows; the header remains visible. */
|
|
31
|
-
empty:
|
|
30
|
+
empty: EmptyContent;
|
|
32
31
|
} & ({
|
|
33
32
|
/** Positive integer preview cap after search; disables pagination. */
|
|
34
33
|
limit: number;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { Tabs, TabList, Tab, TabPanels, TabPanel } from 'react-aria-components/Tabs';
|
|
2
|
-
|
|
3
2
|
/** React Aria tabs with keyboard and ARIA behavior; pair each Tab and TabPanel by stable id. */
|
|
4
3
|
export { Tabs, TabList, Tab, TabPanels, TabPanel };
|
|
5
4
|
/** Keep a page-level tab in `?view=`; reserve `?tab=` for the inspect sheet. */
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ComponentPropsWithRef } from 'react';
|
|
2
|
+
export type TextContentProps = ComponentPropsWithRef<'div'>;
|
|
3
|
+
/** Prose typography for page introductions, widget bodies, and inspection content. */
|
|
4
|
+
export declare function TextContent({ className, ...props }: TextContentProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { DataReading } from '../../core/reading.js';
|
|
3
|
+
import { type DataWidgetProps } from './DataWidget.js';
|
|
4
|
+
import type { WidgetEvidence } from './WidgetEvidence.js';
|
|
5
|
+
type TextWidgetBaseProps = Omit<Extract<DataWidgetProps, {
|
|
6
|
+
reading?: never;
|
|
7
|
+
}>, 'reading' | 'children' | 'isEmpty' | 'empty' | 'skeleton' | 'bodyPadding'>;
|
|
8
|
+
/** Framed narrative context. Bind claims and scope to the same displayed reading.
|
|
9
|
+
* The renderer handles every ready value, including zero and empty collections. */
|
|
10
|
+
export type TextWidgetProps<Data = unknown> = TextWidgetBaseProps & ({
|
|
11
|
+
reading: DataReading<Data>;
|
|
12
|
+
evidence: WidgetEvidence;
|
|
13
|
+
children: (data: Data) => ReactNode;
|
|
14
|
+
} | {
|
|
15
|
+
reading?: never;
|
|
16
|
+
children: ReactNode;
|
|
17
|
+
});
|
|
18
|
+
export declare function TextWidget<Data>(props: TextWidgetProps<Data>): import("react").JSX.Element;
|
|
19
|
+
export {};
|
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
import { type ComponentPropsWithRef, type ReactNode } from 'react';
|
|
2
2
|
import type { WidgetEvidence } from './WidgetEvidence.js';
|
|
3
|
-
import {
|
|
4
|
-
import type {
|
|
5
|
-
import type { DataReading } from '../../core/reading.js';
|
|
6
|
-
import { type ContentSkeletonProps } from './ContentSkeleton.js';
|
|
7
|
-
|
|
3
|
+
import type { WidgetStatus } from './RequestHint.js';
|
|
4
|
+
import type { EmptyContent, BoundWidgetReading } from './presentation.js';
|
|
8
5
|
type VisualizationWidgetBaseProps = {
|
|
9
6
|
title: ReactNode;
|
|
10
7
|
description?: ReactNode;
|
|
11
8
|
insight?: ReactNode;
|
|
12
9
|
action?: ReactNode;
|
|
13
10
|
evidence?: WidgetEvidence;
|
|
14
|
-
status?:
|
|
15
|
-
empty?:
|
|
11
|
+
status?: WidgetStatus;
|
|
12
|
+
empty?: EmptyContent;
|
|
16
13
|
} & Omit<ComponentPropsWithRef<'section'>, 'about' | 'title' | 'children'>;
|
|
17
14
|
type UnboundVisualizationWidgetProps = VisualizationWidgetBaseProps & ({
|
|
18
15
|
loading: true;
|
|
@@ -27,12 +24,7 @@ export type VisualizationWidgetView<Data> = {
|
|
|
27
24
|
label: ReactNode;
|
|
28
25
|
render: (data: Data) => ReactNode;
|
|
29
26
|
};
|
|
30
|
-
type BoundVisualizationWidgetBase<Data> = VisualizationWidgetBaseProps & {
|
|
31
|
-
evidence: WidgetEvidence;
|
|
32
|
-
reading: DataReading<Data>;
|
|
33
|
-
isEmpty: (data: Data) => boolean;
|
|
34
|
-
empty: Pick<EmptyStateProps, 'title' | 'description'>;
|
|
35
|
-
skeleton?: Pick<ContentSkeletonProps, 'variant' | 'rows'>;
|
|
27
|
+
type BoundVisualizationWidgetBase<Data> = VisualizationWidgetBaseProps & BoundWidgetReading<Data> & {
|
|
36
28
|
visual?: never;
|
|
37
29
|
loading?: never;
|
|
38
30
|
};
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import type { ComponentPropsWithRef, ReactNode } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import type { EmptyContent } from './presentation.js';
|
|
4
3
|
export type WidgetView = {
|
|
5
4
|
id: string;
|
|
6
5
|
label: ReactNode;
|
|
7
6
|
content: ReactNode;
|
|
8
|
-
empty:
|
|
7
|
+
empty: EmptyContent;
|
|
9
8
|
isEmpty: boolean;
|
|
10
9
|
};
|
|
11
10
|
export type WidgetViewTabsProps<Views extends readonly WidgetView[] = readonly WidgetView[]> = {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { DataReading } from '../../core/reading.js';
|
|
3
|
+
import type { WidgetEvidence } from './WidgetEvidence.js';
|
|
4
|
+
export type EmptyContent = {
|
|
5
|
+
title: ReactNode;
|
|
6
|
+
description?: ReactNode;
|
|
7
|
+
};
|
|
8
|
+
export type SkeletonContent = {
|
|
9
|
+
variant: 'metric' | 'panel' | 'ranking';
|
|
10
|
+
rows?: number;
|
|
11
|
+
};
|
|
12
|
+
/** A bound widget must explain empty results and link the reading to its evidence. */
|
|
13
|
+
export type BoundWidgetReading<Data> = {
|
|
14
|
+
reading: DataReading<Data>;
|
|
15
|
+
isEmpty: (data: Data) => boolean;
|
|
16
|
+
empty: EmptyContent;
|
|
17
|
+
evidence: WidgetEvidence;
|
|
18
|
+
skeleton?: SkeletonContent;
|
|
19
|
+
};
|
|
@@ -6,7 +6,7 @@ export declare function useViewVariables<Variables extends VariableCollection>(d
|
|
|
6
6
|
values: AppVariableValues<Variables>;
|
|
7
7
|
set: <Key extends keyof Variables & string>(name: Key, value: AppVariableValues<Variables>[Key]) => void;
|
|
8
8
|
reset: <Key extends keyof Variables & string>(name: Key) => void;
|
|
9
|
-
update: (next: Partial<AppVariableValues<Variables>>, history?: import("../core/
|
|
9
|
+
update: (next: Partial<AppVariableValues<Variables>>, history?: import("../core/navigation.js").HistoryMode) => void;
|
|
10
10
|
bind: <Key extends keyof Variables & string>(name: Key) => {
|
|
11
11
|
value: AppVariableValues<Variables>[Key];
|
|
12
12
|
onChange(value: AppVariableValues<Variables>[Key]): void;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { EmptyContent } from './ui/presentation.js';
|
|
2
2
|
import type { AppVariableValues, DateRangeVariable, VariableCollection } from '../core/variables.js';
|
|
3
3
|
import type { DateRangeRequest } from '../core/contract.js';
|
|
4
4
|
export type ResolvedVariables<Variables extends VariableCollection> = {
|
|
@@ -23,7 +23,7 @@ export type DataViewDefinition<Name extends string, Variables extends VariableCo
|
|
|
23
23
|
bindings?: ViewBindings<Variables, Input>;
|
|
24
24
|
/** App-owned semantics: measured zero need not mean an empty result. */
|
|
25
25
|
isEmpty: (data: Data) => boolean;
|
|
26
|
-
empty:
|
|
26
|
+
empty: EmptyContent;
|
|
27
27
|
} & ({
|
|
28
28
|
date: ViewDate<Variables, Input>;
|
|
29
29
|
describeInput?: (input: Input) => string;
|
package/docs/app-authoring.md
CHANGED
|
@@ -1,46 +1,62 @@
|
|
|
1
1
|
# Author a data app
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
|
35
|
-
|
|
|
36
|
-
|
|
|
37
|
-
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
the
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
3
|
+
Build an exploration that answers the user's question and a story that presents
|
|
4
|
+
its strongest findings. Both use the same queries, definitions, and evidence.
|
|
5
|
+
|
|
6
|
+
## Inspect the data
|
|
7
|
+
|
|
8
|
+
Inspect the relevant catalogs, tables, and fields, their time coverage, and
|
|
9
|
+
existing definitions. Choose a question the available data can answer.
|
|
10
|
+
|
|
11
|
+
## Build the exploration
|
|
12
|
+
|
|
13
|
+
Choose the execution path:
|
|
14
|
+
|
|
15
|
+
| App | Guide |
|
|
16
|
+
| ---------------------------------- | --------------------------------------- |
|
|
17
|
+
| Data app (hosted / remote / cloud) | [Single-file authoring](hosted-apps.md) |
|
|
18
|
+
| Local data app | [Local authoring](local-data-apps.md) |
|
|
19
|
+
|
|
20
|
+
Lead with a supported finding and expose the relevant fields as filter variables. Use a date filter for questions worth exploring over time,
|
|
21
|
+
or a fixed period snapshot for a deliberate historical analysis.
|
|
22
|
+
Register inspected tables and fields with `defineDataIdentifiers()` and use
|
|
23
|
+
`<DataIdentifier>` when naming sources. Register terms and query evidence so
|
|
24
|
+
readers can inspect the source of each claim.
|
|
25
|
+
|
|
26
|
+
Connect visualizations with introductions and explanations. Use `<TextWidget>`
|
|
27
|
+
for a narrative panel with the standard widget frame, or `<TextContent>` for
|
|
28
|
+
borderless prose. Bind claims to `result.select((data, input) => ...)` so their
|
|
29
|
+
values and scope follow the displayed results through filter changes, refresh,
|
|
30
|
+
and failure.
|
|
31
|
+
|
|
32
|
+
| Task | Documentation |
|
|
33
|
+
| ------------------------------------------ | ---------------------------------------------------------- |
|
|
34
|
+
| Define queries, inputs, and result parsing | [Operations](contract.md) |
|
|
35
|
+
| Build views, filters, and request states | [React](react.md) |
|
|
36
|
+
| Introduce and explain visualizations | [Narrative text](react.md#narrative-text) |
|
|
37
|
+
| Find formatters and presentation helpers | [App helpers](react.md#reuse-app-helpers) |
|
|
38
|
+
| Register source names | [Source identifiers](react.md#register-source-identifiers) |
|
|
39
|
+
| Choose date and field filters | [Filter variables](react.md#time-views-and-field-filters) |
|
|
40
|
+
| Handle refresh and stale results | [Displayed results](react.md#preserve-displayed-results) |
|
|
41
|
+
| Bind definitions and source evidence | [Data context](react.md#bind-evidence) |
|
|
42
|
+
|
|
43
|
+
Use the exported types for configuration, appearance, formatting, and component
|
|
44
|
+
options.
|
|
45
|
+
|
|
46
|
+
## Present the findings
|
|
47
|
+
|
|
48
|
+
Compose a [story](react.md#present-data-with-stories) from the exploration's
|
|
49
|
+
findings. Lead with the answer, then show the evidence and comparisons that
|
|
50
|
+
explain it. Select the findings that matter to the audience; do not turn every
|
|
51
|
+
row or chart into a step.
|
|
52
|
+
|
|
53
|
+
## Verify the app
|
|
54
|
+
|
|
55
|
+
Verify findings against the source and the user's question. Distinguish measured
|
|
56
|
+
zero, unavailable values, and empty results. Check filters, refresh, loading,
|
|
57
|
+
empty, error, and stale states, then present the story. Inspect both experiences
|
|
58
|
+
at phone and desktop widths in light and dark themes.
|
|
59
|
+
|
|
60
|
+
The app owns its queries, result parsing, business definitions, configuration,
|
|
61
|
+
and presentation. Credentials, authorization, and enforced access/query limits
|
|
62
|
+
stay backend-owned. Edit app-owned files; installed package files are dependencies.
|
package/docs/client.md
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
# Client
|
|
2
2
|
|
|
3
|
-
Import `createDataClient` and `DataAppError` from
|
|
3
|
+
Import `createDataClient()` and `DataAppError` from
|
|
4
4
|
`@altertable/data-app/client`. The client uses Fetch APIs and has no React or
|
|
5
5
|
server dependency.
|
|
6
6
|
|
|
7
|
+
## HTTP operations
|
|
8
|
+
|
|
7
9
|
```ts
|
|
8
10
|
import { createDataClient } from '@altertable/data-app/client';
|
|
9
11
|
import type { operations } from './operations';
|
|
@@ -36,6 +38,8 @@ See [contracts](contract.md), [server handlers](server.md), and
|
|
|
36
38
|
|
|
37
39
|
## Browser-owned operations for bundle apps
|
|
38
40
|
|
|
41
|
+
Start with the complete [single-file example](../examples/starter-data-app/index.tsx)
|
|
42
|
+
and [data app authoring guide](hosted-apps.md).
|
|
39
43
|
Bundle apps pass their operation registry as a value:
|
|
40
44
|
|
|
41
45
|
```ts
|
|
@@ -49,8 +53,7 @@ const response = await client.query('connection', {});
|
|
|
49
53
|
```
|
|
50
54
|
|
|
51
55
|
The client runs input parsing, operation logic, and output parsing in the browser.
|
|
52
|
-
|
|
53
|
-
bounds, response size, and query evidence. Each query sends `{ statement, limit }`
|
|
56
|
+
Operation policy bounds rows, duration, and response size and records query evidence. Each query sends `{ statement, limit }`
|
|
54
57
|
to the installed iframe bridge's `data:sql` route; the host needs no operation
|
|
55
58
|
registry. SQL is visible in the browser, even when `exposeSql` is false; that flag
|
|
56
59
|
only controls evidence in the returned response. Credentials remain backend-owned.
|
|
@@ -72,7 +75,7 @@ the host through the existing bridge cancellation protocol.
|
|
|
72
75
|
`createDataClient({ transport })` accepts a `DataTransport`. Without an explicit
|
|
73
76
|
transport, endpoint, or Fetch implementation, it discovers an installed iframe
|
|
74
77
|
transport before falling back to HTTP. Explicit endpoint/Fetch options select
|
|
75
|
-
HTTP. `createHttpTransport` provides the underlying operation delivery adapter.
|
|
78
|
+
HTTP. `createHttpTransport()` provides the underlying operation delivery adapter.
|
|
76
79
|
|
|
77
80
|
A URL-hosted app configures trust and installs the bridge before mounting:
|
|
78
81
|
|
|
@@ -93,8 +96,8 @@ URL controls attach the optional navigation adapter to that connection. Cleanup
|
|
|
93
96
|
the installation and disposes pending work. Bundle apps receive this installation
|
|
94
97
|
from the [trusted bootstrap](embed.md#trusted-bootstrap).
|
|
95
98
|
|
|
96
|
-
`getDataAppTransport()` returns the explicitly installed bridge. Its `request`
|
|
97
|
-
|
|
99
|
+
`getDataAppTransport()` returns the explicitly installed bridge. Its `request()`
|
|
100
|
+
method supports custom typed routes:
|
|
98
101
|
|
|
99
102
|
```ts
|
|
100
103
|
import {
|
|
@@ -129,7 +132,7 @@ navigation.publish('replace');
|
|
|
129
132
|
navigation.dispose();
|
|
130
133
|
```
|
|
131
134
|
|
|
132
|
-
The adapter provides `snapshot`, `subscribe`, and `update`. Opaque sandboxes keep
|
|
135
|
+
The adapter provides `snapshot()`, `subscribe()`, and `update()`. Opaque sandboxes keep
|
|
133
136
|
search/hash in memory; URL frames preserve their URL and local-preview parent
|
|
134
137
|
marker. Host Back/Forward state is applied without publishing it back.
|
|
135
138
|
|
|
@@ -137,14 +140,6 @@ marker. Host Back/Forward state is applied without publishing it back.
|
|
|
137
140
|
and shares one adapter per document. React mounting and URL-backed controls call
|
|
138
141
|
it automatically. Apps that only use data delivery do not attach navigation.
|
|
139
142
|
|
|
140
|
-
Migration: replace `bridge.appLocation` with the navigation adapter and
|
|
141
|
-
`bridge.location(mode)` with `navigation.publish(mode)`. The version-1 wire envelope
|
|
142
|
-
now carries host context in `initialize.state` and subsequent `state` messages;
|
|
143
|
-
upgrade independently deployed hosts and runtimes together.
|
|
144
|
-
Transport state is shared per window across separately bundled entry points;
|
|
145
|
-
install only one transport per document. Public error classes retain `instanceof`
|
|
146
|
-
recognition across independent bootstrap and app bundles in that window.
|
|
147
|
-
|
|
148
143
|
## Pending request limits
|
|
149
144
|
|
|
150
145
|
Each iframe bridge accepts at most 128 unresolved requests, including requests
|
|
@@ -152,10 +147,7 @@ waiting for the connection handshake. Further calls reject with `bridge_busy`
|
|
|
152
147
|
until a pending call completes, is cancelled, or times out. This bounds the
|
|
153
148
|
client's promises, timers, and queued messages.
|
|
154
149
|
|
|
155
|
-
|
|
156
|
-
messages directly without using the client helper. The shared cap is a resource
|
|
157
|
-
policy, not a requirement of the message protocol. It rejects excess requests;
|
|
158
|
-
it does not queue them or limit the total number of calls over a session.
|
|
150
|
+
Hosts enforce their own request limits independently of the client.
|
|
159
151
|
|
|
160
152
|
A failed local-preview verification can be retried by the next explicit data
|
|
161
153
|
request. Concurrent callers share the current verification attempt; aborting
|
package/docs/contract.md
CHANGED
|
@@ -7,8 +7,8 @@ should import their operation types using `import type`.
|
|
|
7
7
|
|
|
8
8
|
## Execute named queries
|
|
9
9
|
|
|
10
|
-
The app supplies `calendar`, `parseActivity`, `checkInput`, `buildActivitySql`,
|
|
11
|
-
and `parseActivityRows` in this example.
|
|
10
|
+
The app supplies `calendar`, `parseActivity()`, `checkInput`, `buildActivitySql()`,
|
|
11
|
+
and `parseActivityRows()` in this example.
|
|
12
12
|
|
|
13
13
|
```ts
|
|
14
14
|
import {
|
|
@@ -30,14 +30,14 @@ const activity = defineOperation({
|
|
|
30
30
|
});
|
|
31
31
|
```
|
|
32
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.
|
|
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. Responses include executed SQL and query IDs when disclosure is allowed. HTTP browser modules import operation types with `import type`. Bundle apps import their browser-owned operation registry as a value and use [browser execution](client.md#browser-owned-operations-for-bundle-apps). Never bundle credentials or server adapters.
|
|
34
34
|
|
|
35
35
|
## Shared date ranges
|
|
36
36
|
|
|
37
|
-
Use `defineDateRangeContract` in a browser-safe module to share source coverage,
|
|
37
|
+
Use `defineDateRangeContract()` in a browser-safe module to share source coverage,
|
|
38
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`.
|
|
39
|
+
React variables. The server uses `calendar.parseRequest()`; React uses the same
|
|
40
|
+
contract with `dateRangeVariable()`.
|
|
41
41
|
|
|
42
42
|
```ts
|
|
43
43
|
import { defineDateRangeContract } from '@altertable/data-app/contract';
|
|
@@ -49,7 +49,7 @@ export const calendar = defineDateRangeContract({
|
|
|
49
49
|
});
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
-
`parseEmptyInput`, `parseTrue`, `parseCount`, and `parseDateRangeInput` validate
|
|
52
|
+
`parseEmptyInput()`, `parseTrue()`, `parseCount()`, and `parseDateRangeInput()` validate
|
|
53
53
|
common inputs and results. `connectionCheck()` defines a bounded connectivity
|
|
54
54
|
operation. A successful connectivity check confirms access; it is not an
|
|
55
55
|
analysis result.
|
|
@@ -83,14 +83,14 @@ const router = createMessageRouter(routes, {
|
|
|
83
83
|
});
|
|
84
84
|
```
|
|
85
85
|
|
|
86
|
-
The app defines `parseString` to validate unknown values. `router.dispatch` checks
|
|
86
|
+
The app defines `parseString()` to validate unknown values. `router.dispatch()` checks
|
|
87
87
|
registered routes, input, output, and cancellation. `MessageRoutingError` exposes
|
|
88
88
|
an intentional public code, message, and optional request ID; other handler
|
|
89
89
|
errors are replaced with a generic failure.
|
|
90
90
|
|
|
91
91
|
`defineDataQueryRoute(operationContracts)` validates the selected operation's
|
|
92
92
|
input and output and preserves its response evidence. It infers the result type
|
|
93
|
-
from the selected operation when used with `createMessageClient`. Share input and
|
|
93
|
+
from the selected operation when used with `createMessageClient()`. Share input and
|
|
94
94
|
output parsers, not operation implementations containing SQL or credentials.
|
|
95
95
|
`dataAppRoutes` supplies generic `data:query` and `navigation:update` contracts;
|
|
96
96
|
generic hosts must delegate operation validation and authorization to their
|
package/docs/embed.md
CHANGED
|
@@ -6,7 +6,7 @@ app UI stylesheet.
|
|
|
6
6
|
|
|
7
7
|
## Sources
|
|
8
8
|
|
|
9
|
-
`attachDataAppBridge` in source mode owns iframe loading, sandbox policy, startup timeout, and
|
|
9
|
+
`attachDataAppBridge()` in source mode owns iframe loading, sandbox policy, startup timeout, and
|
|
10
10
|
message delivery. The host supplies an iframe and a validated message dispatcher:
|
|
11
11
|
|
|
12
12
|
```ts
|
|
@@ -67,13 +67,13 @@ img-src data: blob:; connect-src 'none'; base-uri 'none'; form-action 'none'`.
|
|
|
67
67
|
The initializer installs a shared transport before evaluating the app script;
|
|
68
68
|
data clients discover it even when independently bundled. The bootstrap contains
|
|
69
69
|
no app navigation adapter. React mounting or URL controls attach navigation in the
|
|
70
|
-
app bundle; non-React apps use `createDataAppNavigation` from `/client`.
|
|
70
|
+
app bundle; non-React apps use `createDataAppNavigation()` from `/client`.
|
|
71
71
|
|
|
72
72
|
## Delivery and navigation
|
|
73
73
|
|
|
74
|
-
`attachDataAppBridge` also supports connection mode for a host-owned iframe. Supply
|
|
74
|
+
`attachDataAppBridge()` also supports connection mode for a host-owned iframe. Supply
|
|
75
75
|
`connection: { type: 'origin', origin }` or `{ type: 'opaque', token }`, and an
|
|
76
|
-
`onMessage` dispatcher. Both modes use the same transport. It returns `
|
|
76
|
+
`onMessage` dispatcher. Both modes use the same transport. It returns `dispose()` and `setPresentation()` methods and owns source/origin checks, request
|
|
77
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.
|
|
@@ -97,14 +97,6 @@ The source bridge's `startupTimeoutMs` defaults to 30 seconds.
|
|
|
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
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
100
|
## SQL query route
|
|
109
101
|
|
|
110
102
|
Hosts serving browser-owned operations register `sqlQueryRoute` explicitly:
|
|
@@ -127,16 +119,16 @@ const router = createMessageRouter(
|
|
|
127
119
|
// Supply router.dispatch as the shell's onMessage handler.
|
|
128
120
|
```
|
|
129
121
|
|
|
130
|
-
The host supplies `authorizedLakehouseForCurrentViewer`. Its backend must enforce
|
|
122
|
+
The host supplies `authorizedLakehouseForCurrentViewer()`. Its backend must enforce
|
|
131
123
|
viewer/dataset permissions, permitted query behavior, maximum rows, execution
|
|
132
124
|
time, concurrency, and response size independently of browser policy. Route
|
|
133
|
-
validation is not SQL authorization. `createSqlQueryHandler` calls authorization
|
|
125
|
+
validation is not SQL authorization. `createSqlQueryHandler()` calls authorization
|
|
134
126
|
for each query, forwards cancellation, and preserves `DataSourceError` reasons
|
|
135
127
|
as public `source_*` errors with request IDs. Authorization failures return
|
|
136
128
|
`forbidden`; unknown query errors are hidden. Custom handlers can return deliberate
|
|
137
129
|
public failures with `MessageRoutingError`.
|
|
138
130
|
|
|
139
|
-
`SqlQueryInput` (exported from `/contract`
|
|
131
|
+
`SqlQueryInput` (exported from `/contract`) carries
|
|
140
132
|
`{ statement: string, limit: number }`; responses are
|
|
141
133
|
`{ columns: { name: string, type?: string }[], rows: unknown[][], queryId?: string }`.
|
|
142
134
|
The route rejects empty statements, unsafe or nonpositive limits, malformed
|
|
@@ -167,27 +159,16 @@ host.setPresentation({ surface: 'embedded', theme: 'light' });
|
|
|
167
159
|
|
|
168
160
|
Both source and connection modes support the `presentation` option and
|
|
169
161
|
`host.setPresentation(presentation)` method. `DataAppPresentation` is exported from
|
|
170
|
-
`/embed
|
|
162
|
+
`/embed`. Use `surface: 'embedded'` when the parent provides page chrome, as in the
|
|
171
163
|
Altertable frontend, and `'standalone'` when the app provides its own header and
|
|
172
164
|
footer. `theme` must be resolved to `'light'` or
|
|
173
165
|
`'dark'`; the parent decides how its system preference is resolved.
|
|
174
166
|
|
|
175
|
-
Presentation travels over `postMessage` in the authenticated `bridge:initialize` and
|
|
167
|
+
Presentation travels over `postMessage()` in the authenticated `bridge:initialize` and
|
|
176
168
|
`state:update` messages, alongside `search` and `hash`. Framework-neutral apps
|
|
177
169
|
can narrow the unknown state returned by `bridge.snapshot()` to read its
|
|
178
|
-
`presentation` field, and subscribe through `bridge.subscribe`. React
|
|
170
|
+
`presentation` field, and subscribe through `bridge.subscribe()`. React `<DataApp>` consumes it automatically.
|
|
179
171
|
An embedded surface renders toolbar actions without the page header or footer.
|
|
180
172
|
Both surfaces follow the parent's theme, including presentation mode, without
|
|
181
173
|
changing saved viewer preferences. Omitting presentation preserves standalone behavior;
|
|
182
174
|
`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.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Author a data app
|
|
2
|
+
|
|
3
|
+
Follow the [shared authoring flow](app-authoring.md) using [index.tsx](../examples/starter-data-app/index.tsx) for a hosted / remote / cloud data app. Keep the app in
|
|
4
|
+
one file with public package imports; omit server files, HTML, credentials, and
|
|
5
|
+
relative or app-alias imports.
|
|
6
|
+
|
|
7
|
+
Replace the sample SQL, parsers, filters, data context, story, and configuration with
|
|
8
|
+
an exploration of the source data you inspected. The starter uses two SQL
|
|
9
|
+
`VALUES` rows, so it needs no production table.
|
|
10
|
+
|
|
11
|
+
For execution details, see [browser-owned operations](client.md#browser-owned-operations-for-bundle-apps).
|
|
12
|
+
|
|
13
|
+
## Convert a local data app
|
|
14
|
+
|
|
15
|
+
1. Combine the app's operations and parsers, data context, views, story,
|
|
16
|
+
configuration, and browser entry into one `index.tsx`, following the
|
|
17
|
+
[single-file starter](../examples/starter-data-app/index.tsx).
|
|
18
|
+
2. Replace the HTTP client with `createDataClient({ operations })`, using the
|
|
19
|
+
operation registry as a value. See [browser-owned operations](client.md#browser-owned-operations-for-bundle-apps)
|
|
20
|
+
for execution through the host.
|
|
21
|
+
3. Remove the Bun server, HTML, server adapters, credentials, and relative or
|
|
22
|
+
app-alias imports. Rewrite any operation that depends on server-only code
|
|
23
|
+
to use the operation's `query()` helper.
|
|
24
|
+
4. Confirm the host can query the same catalogs, tables, and fields.
|
|
25
|
+
[Verify the app](app-authoring.md#verify-the-app) in the hosted runtime against
|
|
26
|
+
the local version's filters and findings.
|
|
27
|
+
|
|
28
|
+
## Preview in this repository
|
|
29
|
+
|
|
30
|
+
```fish
|
|
31
|
+
bun install --frozen-lockfile
|
|
32
|
+
bun run build
|
|
33
|
+
bun browser-tests/server.ts
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Open [the starter preview](http://127.0.0.1:27418/starter-data-app).
|
|
37
|
+
Its test host executes the sample SQL through the iframe bridge using SQLite.
|
|
38
|
+
|
|
39
|
+
For checks, see [Contributing](../CONTRIBUTING.md).
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Author a local data app
|
|
2
|
+
|
|
3
|
+
Start from the CLI scaffold or the [local starter](https://github.com/altertable-ai/data-app/tree/main/examples/starter-local-data-app).
|
|
4
|
+
Use the starter's README for setup and its AGENTS.md to find app-owned files.
|
|
5
|
+
Replace the connectivity screen with the exploration and story from the
|
|
6
|
+
[shared authoring flow](app-authoring.md).
|
|
7
|
+
|
|
8
|
+
| Task | Documentation |
|
|
9
|
+
| --------------------------------------- | ---------------------------------------- |
|
|
10
|
+
| Serve locally with Bun | [Bun server](server-bun.md) |
|
|
11
|
+
| Execute and authorize server operations | [Server](server.md) |
|
|
12
|
+
| Call operations from the browser | [HTTP client](client.md#http-operations) |
|
|
13
|
+
|
|
14
|
+
You can also [convert the local app to a hosted data app](hosted-apps.md#convert-a-local-data-app).
|