@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,321 @@
|
|
|
1
|
+
import {
|
|
2
|
+
formatDateRange2
|
|
3
|
+
} from "./contract-mev09s5v.js";
|
|
4
|
+
import {
|
|
5
|
+
invariant
|
|
6
|
+
} from "./contract-8q35dcyh.js";
|
|
7
|
+
|
|
8
|
+
// src/core/contract.ts
|
|
9
|
+
import { parseDate, today } from "@internationalized/date";
|
|
10
|
+
|
|
11
|
+
// src/core/dimension.ts
|
|
12
|
+
var allSelection = { kind: "all" };
|
|
13
|
+
function dimensionMemberKey(member) {
|
|
14
|
+
return member.kind === "missing" ? "missing" : `value:${typeof member.value}:${member.value}`;
|
|
15
|
+
}
|
|
16
|
+
function dimensionFilter2(config) {
|
|
17
|
+
const { key, label, selection, valueType } = config;
|
|
18
|
+
const options = config.options ?? [];
|
|
19
|
+
invariant(!!config.options !== !!config.facet, "Choose fixed options or one facet operation.");
|
|
20
|
+
const optionKeys = options.map((option) => dimensionMemberKey({ kind: "value", value: option.value }));
|
|
21
|
+
invariant(!!key && options.length <= 200 && new Set(optionKeys).size === options.length, "Dimension options need a key, at most 200 distinct values.");
|
|
22
|
+
invariant(options.every((option) => typeof option.value === valueType && !!option.label.trim() && (option.count === undefined || Number.isSafeInteger(option.count) && option.count >= 0)), "Dimension options need typed values, labels, and nonnegative counts.");
|
|
23
|
+
const allowed = new Set(optionKeys);
|
|
24
|
+
const maxSelected = config.maxSelected ?? (selection === "single" ? 1 : 20);
|
|
25
|
+
invariant(Number.isInteger(maxSelected) && maxSelected >= 1 && maxSelected <= 50 && (selection !== "single" || maxSelected === 1), "Dimension selection limit is invalid.");
|
|
26
|
+
const allowMissing = config.allowMissing ?? false;
|
|
27
|
+
const defaultValue = allSelection;
|
|
28
|
+
function validMember(member) {
|
|
29
|
+
if (member.kind === "missing")
|
|
30
|
+
return allowMissing;
|
|
31
|
+
if (member.kind !== "value" || typeof member.value !== valueType)
|
|
32
|
+
return false;
|
|
33
|
+
if (!config.facet)
|
|
34
|
+
return allowed.has(dimensionMemberKey(member));
|
|
35
|
+
return typeof member.value === "number" ? Number.isFinite(member.value) : member.value.length <= 100;
|
|
36
|
+
}
|
|
37
|
+
function valid(value) {
|
|
38
|
+
if (!value || typeof value !== "object")
|
|
39
|
+
return false;
|
|
40
|
+
if (value.kind === "all")
|
|
41
|
+
return true;
|
|
42
|
+
if (value.kind !== "include" || !Array.isArray(value.members) || !value.members.length || value.members.length > maxSelected)
|
|
43
|
+
return false;
|
|
44
|
+
const ids = value.members.map(dimensionMemberKey);
|
|
45
|
+
return new Set(ids).size === ids.length && value.members.every(validMember);
|
|
46
|
+
}
|
|
47
|
+
function encode(value) {
|
|
48
|
+
return value.kind === "all" ? null : JSON.stringify(value.members.map((member) => member.kind === "missing" ? ["m"] : ["v", member.value]));
|
|
49
|
+
}
|
|
50
|
+
function parse(raw) {
|
|
51
|
+
if (raw === null)
|
|
52
|
+
return defaultValue;
|
|
53
|
+
if (raw.length > 4096)
|
|
54
|
+
return defaultValue;
|
|
55
|
+
try {
|
|
56
|
+
const tokens = JSON.parse(raw);
|
|
57
|
+
if (!Array.isArray(tokens))
|
|
58
|
+
return defaultValue;
|
|
59
|
+
const value = {
|
|
60
|
+
kind: "include",
|
|
61
|
+
members: tokens.map((token) => Array.isArray(token) && token.length === 1 && token[0] === "m" ? { kind: "missing" } : Array.isArray(token) && token.length === 2 && token[0] === "v" ? { kind: "value", value: token[1] } : { kind: "invalid" })
|
|
62
|
+
};
|
|
63
|
+
return valid(value) ? value : defaultValue;
|
|
64
|
+
} catch {
|
|
65
|
+
return defaultValue;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
kind: "dimension",
|
|
70
|
+
label,
|
|
71
|
+
valueType,
|
|
72
|
+
selection,
|
|
73
|
+
options,
|
|
74
|
+
facet: config.facet,
|
|
75
|
+
allowMissing,
|
|
76
|
+
maxSelected,
|
|
77
|
+
urlKeys: [key],
|
|
78
|
+
defaultValue,
|
|
79
|
+
history: config.history ?? "push",
|
|
80
|
+
valid,
|
|
81
|
+
same(left, right) {
|
|
82
|
+
return encode(left) === encode(right);
|
|
83
|
+
},
|
|
84
|
+
read(params) {
|
|
85
|
+
return parse(params.get(key));
|
|
86
|
+
},
|
|
87
|
+
write(value) {
|
|
88
|
+
return { [key]: encode(value) };
|
|
89
|
+
},
|
|
90
|
+
describe(value) {
|
|
91
|
+
return value.kind === "all" ? `All ${label.toLocaleLowerCase()}` : value.members.map((member) => member.kind === "missing" ? "Missing" : options.find((option) => option.value === member.value)?.label ?? String(member.value)).join(", ");
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function parseDimensionSelection2(value, filter) {
|
|
96
|
+
invariant(filter.valid(value), `Invalid ${filter.label} selection.`);
|
|
97
|
+
return value;
|
|
98
|
+
}
|
|
99
|
+
function parseFacetOptions2(value, filter) {
|
|
100
|
+
invariant(Array.isArray(value) && value.length <= 200, "Facet options exceed their bound.");
|
|
101
|
+
const options = [];
|
|
102
|
+
const seen = new Set;
|
|
103
|
+
for (const item of value) {
|
|
104
|
+
invariant(item && typeof item === "object", "Invalid facet option.");
|
|
105
|
+
const option = item;
|
|
106
|
+
invariant(typeof option.value === filter.valueType && filter.valid({
|
|
107
|
+
kind: "include",
|
|
108
|
+
members: [{ kind: "value", value: option.value }]
|
|
109
|
+
}) && typeof option.label === "string" && !!option.label.trim() && option.label.length <= 100 && (option.count === undefined || Number.isSafeInteger(option.count) && option.count >= 0), "Invalid facet option.");
|
|
110
|
+
const id = dimensionMemberKey({ kind: "value", value: option.value });
|
|
111
|
+
invariant(!seen.has(id), "Duplicate facet option.");
|
|
112
|
+
seen.add(id);
|
|
113
|
+
options.push(option);
|
|
114
|
+
}
|
|
115
|
+
return options;
|
|
116
|
+
}
|
|
117
|
+
function dimensionPredicate2(column, selection, allowedColumns) {
|
|
118
|
+
invariant(allowedColumns.includes(column) && /^[A-Za-z_][A-Za-z_0-9]*(?:\.[A-Za-z_][A-Za-z_0-9]*)*$/.test(column), "Invalid dimension column.");
|
|
119
|
+
if (selection.kind === "all")
|
|
120
|
+
return "";
|
|
121
|
+
invariant(selection.members.length > 0, "Empty dimension inclusion is invalid.");
|
|
122
|
+
const values = selection.members.filter((member) => member.kind === "value");
|
|
123
|
+
const missing = selection.members.some((member) => member.kind === "missing");
|
|
124
|
+
const literals = values.map(({ value }) => {
|
|
125
|
+
if (typeof value === "number") {
|
|
126
|
+
invariant(Number.isFinite(value), "Invalid numeric dimension value.");
|
|
127
|
+
return String(value);
|
|
128
|
+
}
|
|
129
|
+
const text = value;
|
|
130
|
+
for (let index = 0;index < text.length; index++)
|
|
131
|
+
invariant(text.charCodeAt(index) >= 32 && text.charCodeAt(index) !== 127, "Invalid control character in dimension value.");
|
|
132
|
+
return `'${text.replaceAll("'", "''")}'`;
|
|
133
|
+
});
|
|
134
|
+
const clauses = [
|
|
135
|
+
...literals.length ? [`${column} IN (${literals.join(", ")})`] : [],
|
|
136
|
+
...missing ? [`${column} IS NULL`] : []
|
|
137
|
+
];
|
|
138
|
+
return `(${clauses.join(" OR ")})`;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// src/core/contract.ts
|
|
142
|
+
function parseEmptyInput2(value) {
|
|
143
|
+
invariant(typeof value === "object" && value !== null && !Array.isArray(value) && Object.keys(value).length === 0, "This operation takes no inputs.");
|
|
144
|
+
return {};
|
|
145
|
+
}
|
|
146
|
+
function parseTrue2(value) {
|
|
147
|
+
invariant(value === true, "Expected a successful check.");
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
function previousDateRange2({
|
|
151
|
+
start,
|
|
152
|
+
end
|
|
153
|
+
}) {
|
|
154
|
+
const first = parseDate(start);
|
|
155
|
+
const last = parseDate(end);
|
|
156
|
+
const days = (last.toDate("UTC").getTime() - first.toDate("UTC").getTime()) / 86400000 + 1;
|
|
157
|
+
invariant(days >= 1, "Date range must start before it ends.");
|
|
158
|
+
const previousEnd = first.subtract({ days: 1 });
|
|
159
|
+
return {
|
|
160
|
+
start: previousEnd.subtract({ days: days - 1 }).toString(),
|
|
161
|
+
end: previousEnd.toString()
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
function defineDateRangeContract2(options) {
|
|
165
|
+
invariant(Number.isInteger(options.maxRangeDays) && options.maxRangeDays > 0, "Date range needs a positive maxRangeDays.");
|
|
166
|
+
if (options.minDate)
|
|
167
|
+
parseDate(options.minDate);
|
|
168
|
+
if (typeof options.maxDate === "string")
|
|
169
|
+
parseDate(options.maxDate);
|
|
170
|
+
today(options.timeZone);
|
|
171
|
+
function bounds() {
|
|
172
|
+
const latest = today(options.timeZone).subtract({
|
|
173
|
+
days: options.completeDays === false ? 0 : 1
|
|
174
|
+
});
|
|
175
|
+
const sourceMax = typeof options.maxDate === "function" ? options.maxDate() : options.maxDate;
|
|
176
|
+
const maxDate = sourceMax && parseDate(sourceMax).compare(latest) < 0 ? parseDate(sourceMax).toString() : latest.toString();
|
|
177
|
+
return {
|
|
178
|
+
minDate: options.minDate,
|
|
179
|
+
maxDate,
|
|
180
|
+
maxRangeDays: options.maxRangeDays,
|
|
181
|
+
timeZone: options.timeZone
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
function period({ start, end }) {
|
|
185
|
+
return {
|
|
186
|
+
kind: "calendar",
|
|
187
|
+
start,
|
|
188
|
+
end,
|
|
189
|
+
timeZone: options.timeZone
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
function describeInput(input) {
|
|
193
|
+
return `${formatDateRange2(input)} ${options.timeZone}`;
|
|
194
|
+
}
|
|
195
|
+
function comparison(input) {
|
|
196
|
+
const previous = previousDateRange2(input);
|
|
197
|
+
return options.minDate && previous.start < options.minDate ? null : previous;
|
|
198
|
+
}
|
|
199
|
+
function request(value, compare = false) {
|
|
200
|
+
const range = parseDateRangeInput2(value, bounds());
|
|
201
|
+
const previous = compare ? comparison(range) : null;
|
|
202
|
+
invariant(!compare || previous, "Comparison is outside available source coverage.");
|
|
203
|
+
return { range, comparison: previous };
|
|
204
|
+
}
|
|
205
|
+
function parseRequest(value) {
|
|
206
|
+
invariant(value && typeof value === "object", "Choose a date range.");
|
|
207
|
+
const input = value;
|
|
208
|
+
const parsed = request(input.range, input.comparison != null);
|
|
209
|
+
if (input.comparison != null) {
|
|
210
|
+
const supplied = parseDateRangeInput2(input.comparison, bounds());
|
|
211
|
+
invariant(supplied.start === parsed.comparison?.start && supplied.end === parsed.comparison?.end, "Comparison must be the preceding equal-length range.");
|
|
212
|
+
}
|
|
213
|
+
return parsed;
|
|
214
|
+
}
|
|
215
|
+
return {
|
|
216
|
+
request,
|
|
217
|
+
parseRequest,
|
|
218
|
+
bounds,
|
|
219
|
+
parse(value) {
|
|
220
|
+
return parseDateRangeInput2(value, bounds());
|
|
221
|
+
},
|
|
222
|
+
period,
|
|
223
|
+
comparison,
|
|
224
|
+
describeInput,
|
|
225
|
+
view: { period, describeInput }
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
function parseDateRangeInput2(value, {
|
|
229
|
+
minDate,
|
|
230
|
+
maxDate,
|
|
231
|
+
maxRangeDays
|
|
232
|
+
}) {
|
|
233
|
+
invariant(value && typeof value === "object" && !Array.isArray(value), "Choose a date range.");
|
|
234
|
+
const { start, end } = value;
|
|
235
|
+
function day(input) {
|
|
236
|
+
invariant(typeof input === "string" && /^\d{4}-\d{2}-\d{2}$/.test(input), "Dates must be YYYY-MM-DD.");
|
|
237
|
+
const timestamp = Date.parse(`${input}T00:00:00Z`);
|
|
238
|
+
invariant(Number.isFinite(timestamp) && new Date(timestamp).toISOString().slice(0, 10) === input, "Choose valid calendar dates.");
|
|
239
|
+
return timestamp;
|
|
240
|
+
}
|
|
241
|
+
const first = day(start);
|
|
242
|
+
const last = day(end);
|
|
243
|
+
invariant(Number.isInteger(maxRangeDays) && maxRangeDays > 0, "Maximum range days must be positive.");
|
|
244
|
+
invariant(first <= last && (last - first) / 86400000 < maxRangeDays && (minDate === undefined || first >= day(minDate)) && (maxDate === undefined || last <= day(maxDate)), `Choose up to ${maxRangeDays} days within the available range.`);
|
|
245
|
+
return { start, end };
|
|
246
|
+
}
|
|
247
|
+
function parseCount2(value) {
|
|
248
|
+
const number = typeof value === "number" || typeof value === "string" ? Number(value) : NaN;
|
|
249
|
+
invariant(Number.isSafeInteger(number) && number >= 0 && (typeof value !== "string" || !!value.trim()), "Invalid count in query result.");
|
|
250
|
+
return number;
|
|
251
|
+
}
|
|
252
|
+
function parseLabel2(value, maxLength = 100) {
|
|
253
|
+
invariant(typeof value === "string" && !!value && value.length <= maxLength, "Invalid label in query result.");
|
|
254
|
+
return value;
|
|
255
|
+
}
|
|
256
|
+
function rowsAsRecords2(result, requiredColumns) {
|
|
257
|
+
const names = result.columns.map((column) => column.name);
|
|
258
|
+
invariant(new Set(names).size === names.length && requiredColumns.every((name) => names.includes(name)), "Query result columns do not match the expected shape.");
|
|
259
|
+
return result.rows.map((row) => {
|
|
260
|
+
invariant(row.length === names.length, "Query result row has the wrong width.");
|
|
261
|
+
return Object.fromEntries(names.map((name, index) => [name, row[index]]));
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
function defineQueryNames2(names) {
|
|
265
|
+
const values = Object.values(names);
|
|
266
|
+
invariant(values.every((name) => !!name.trim()) && new Set(values).size === values.length, "Query names must be nonempty and unique.");
|
|
267
|
+
return names;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
class DataSourceError2 extends Error {
|
|
271
|
+
reason;
|
|
272
|
+
status;
|
|
273
|
+
queryName;
|
|
274
|
+
constructor(reason, status) {
|
|
275
|
+
super(`Data source ${reason}${status ? ` (${status})` : ""}.`);
|
|
276
|
+
this.reason = reason;
|
|
277
|
+
this.status = status;
|
|
278
|
+
this.name = "DataSourceError";
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
function defineOperation2(operation) {
|
|
282
|
+
invariant(Array.isArray(operation.checks) && operation.checks.length > 0 && Number.isInteger(operation.policy.maxQueryRows) && operation.policy.maxQueryRows > 0 && Number.isInteger(operation.policy.maxDurationMs) && operation.policy.maxDurationMs > 0 && (operation.policy.maxResponseBytes === undefined || Number.isInteger(operation.policy.maxResponseBytes) && operation.policy.maxResponseBytes > 0), "Each data operation needs check inputs and positive row and duration limits.");
|
|
283
|
+
if (operation.queryNames)
|
|
284
|
+
defineQueryNames2(operation.queryNames);
|
|
285
|
+
for (const input of operation.checks)
|
|
286
|
+
operation.input(input);
|
|
287
|
+
return {
|
|
288
|
+
...operation,
|
|
289
|
+
run(context, input) {
|
|
290
|
+
function query(name, statement, options) {
|
|
291
|
+
invariant(operation.queryNames && Object.values(operation.queryNames).includes(name), `Unknown query name: ${name}.`);
|
|
292
|
+
return context.lakehouse.queryAll(statement, {
|
|
293
|
+
name,
|
|
294
|
+
limit: options?.limit ?? operation.policy.maxQueryRows,
|
|
295
|
+
signal: context.signal
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
return operation.run({ ...context, query }, input);
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
var connectionQueryNames2 = defineQueryNames2({
|
|
303
|
+
connection: "connection-check"
|
|
304
|
+
});
|
|
305
|
+
function connectionCheck2() {
|
|
306
|
+
return defineOperation2({
|
|
307
|
+
input: parseEmptyInput2,
|
|
308
|
+
output: parseTrue2,
|
|
309
|
+
checks: [{}],
|
|
310
|
+
queryNames: connectionQueryNames2,
|
|
311
|
+
policy: { maxQueryRows: 1, maxDurationMs: 15000, exposeSql: true },
|
|
312
|
+
async run({ query }) {
|
|
313
|
+
await query(connectionQueryNames2.connection, "SELECT 1 AS connection_check");
|
|
314
|
+
return true;
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
export { dimensionMemberKey, dimensionFilter2, parseDimensionSelection2, parseFacetOptions2, dimensionPredicate2, parseEmptyInput2, parseTrue2, previousDateRange2, defineDateRangeContract2, parseDateRangeInput2, parseCount2, parseLabel2, rowsAsRecords2, defineQueryNames2, DataSourceError2, defineOperation2, connectionQueryNames2, connectionCheck2 };
|
|
320
|
+
|
|
321
|
+
//# debugId=65216D55145AC13D64756E2164756E21
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/core/contract.ts", "../src/core/dimension.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * Shared operation contracts and input/output validation for browser and server.\n * @module @altertable/data-app/contract\n * @see https://github.com/altertable-ai/data-app/blob/main/docs/contract.md\n */\nimport { parseDate, today } from '@internationalized/date';\nimport { formatDateRange } from '@/src/core/format';\nimport { invariant } from '@/src/core/invariant';\n\nexport type QueryResult = {\n columns: { name: string; type?: string }[];\n rows: unknown[][];\n queryId?: string;\n};\n\nexport function parseEmptyInput(value: unknown): Record<string, never> {\n invariant(\n typeof value === 'object' &&\n value !== null &&\n !Array.isArray(value) &&\n Object.keys(value).length === 0,\n 'This operation takes no inputs.'\n );\n\n return {};\n}\n\nexport function parseTrue(value: unknown): true {\n invariant(value === true, 'Expected a successful check.');\n\n return true;\n}\n\nexport type DateRangeInput = { start: string; end: string };\nexport type DateRangeRequest = {\n range: DateRangeInput;\n comparison: DateRangeInput | null;\n};\n\n/** The immediately preceding, equally long set of calendar days. */\nexport function previousDateRange({\n start,\n end,\n}: DateRangeInput): DateRangeInput {\n const first = parseDate(start);\n const last = parseDate(end);\n const days =\n (last.toDate('UTC').getTime() - first.toDate('UTC').getTime()) /\n 86_400_000 +\n 1;\n invariant(days >= 1, 'Date range must start before it ends.');\n const previousEnd = first.subtract({ days: 1 });\n\n return {\n start: previousEnd.subtract({ days: days - 1 }).toString(),\n end: previousEnd.toString(),\n };\n}\n\nexport type ReportingPeriod =\n | { kind: 'rolling'; amount: number; unit: 'hour' | 'day'; end: string }\n | { kind: 'calendar'; start: string; end: string; timeZone: string };\n\nexport type DateRangeContractOptions = {\n minDate?: string;\n maxDate?: string | (() => string);\n maxRangeDays: number;\n timeZone: string;\n completeDays?: boolean;\n};\n\n/** One source policy for the server parser, URL variable, and displayed reporting period. */\nexport function defineDateRangeContract(options: DateRangeContractOptions) {\n invariant(\n Number.isInteger(options.maxRangeDays) && options.maxRangeDays > 0,\n 'Date range needs a positive maxRangeDays.'\n );\n if (options.minDate) parseDate(options.minDate);\n if (typeof options.maxDate === 'string') parseDate(options.maxDate);\n today(options.timeZone);\n\n function bounds() {\n const latest = today(options.timeZone).subtract({\n days: options.completeDays === false ? 0 : 1,\n });\n const sourceMax =\n typeof options.maxDate === 'function'\n ? options.maxDate()\n : options.maxDate;\n const maxDate =\n sourceMax && parseDate(sourceMax).compare(latest) < 0\n ? parseDate(sourceMax).toString()\n : latest.toString();\n\n return {\n minDate: options.minDate,\n maxDate,\n maxRangeDays: options.maxRangeDays,\n timeZone: options.timeZone,\n };\n }\n\n function period({ start, end }: DateRangeInput): ReportingPeriod {\n return {\n kind: 'calendar',\n start,\n end,\n timeZone: options.timeZone,\n };\n }\n\n function describeInput(input: DateRangeInput) {\n return `${formatDateRange(input)} ${options.timeZone}`;\n }\n\n function comparison(input: DateRangeInput): DateRangeInput | null {\n const previous = previousDateRange(input);\n\n return options.minDate && previous.start < options.minDate\n ? null\n : previous;\n }\n\n function request(value: unknown, compare = false): DateRangeRequest {\n const range = parseDateRangeInput(value, bounds());\n const previous = compare ? comparison(range) : null;\n invariant(\n !compare || previous,\n 'Comparison is outside available source coverage.'\n );\n\n return { range, comparison: previous };\n }\n\n function parseRequest(value: unknown): DateRangeRequest {\n invariant(value && typeof value === 'object', 'Choose a date range.');\n const input = value as Record<string, unknown>;\n const parsed = request(input.range, input.comparison != null);\n if (input.comparison != null) {\n const supplied = parseDateRangeInput(input.comparison, bounds());\n invariant(\n supplied.start === parsed.comparison?.start &&\n supplied.end === parsed.comparison?.end,\n 'Comparison must be the preceding equal-length range.'\n );\n }\n\n return parsed;\n }\n\n return {\n request,\n parseRequest,\n bounds,\n parse(value: unknown) {\n return parseDateRangeInput(value, bounds());\n },\n period,\n comparison,\n describeInput,\n view: { period, describeInput },\n };\n}\n\nexport type DateRangeContract = ReturnType<typeof defineDateRangeContract>;\n\n/** Validate calendar dates and a bounded inclusive range on the server. */\nexport function parseDateRangeInput(\n value: unknown,\n {\n minDate,\n maxDate,\n maxRangeDays,\n }: { minDate?: string; maxDate?: string; maxRangeDays: number }\n): DateRangeInput {\n invariant(\n value && typeof value === 'object' && !Array.isArray(value),\n 'Choose a date range.'\n );\n const { start, end } = value as Record<string, unknown>;\n\n function day(input: unknown): number {\n invariant(\n typeof input === 'string' && /^\\d{4}-\\d{2}-\\d{2}$/.test(input),\n 'Dates must be YYYY-MM-DD.'\n );\n const timestamp = Date.parse(`${input}T00:00:00Z`);\n invariant(\n Number.isFinite(timestamp) &&\n new Date(timestamp).toISOString().slice(0, 10) === input,\n 'Choose valid calendar dates.'\n );\n\n return timestamp;\n }\n const first = day(start);\n const last = day(end);\n invariant(\n Number.isInteger(maxRangeDays) && maxRangeDays > 0,\n 'Maximum range days must be positive.'\n );\n invariant(\n first <= last &&\n (last - first) / 86_400_000 < maxRangeDays &&\n (minDate === undefined || first >= day(minDate)) &&\n (maxDate === undefined || last <= day(maxDate)),\n `Choose up to ${maxRangeDays} days within the available range.`\n );\n\n return { start: start as string, end: end as string };\n}\n\n/** Accepts numeric strings from DuckDB; rejects negative, fractional, and unsafe integers. */\nexport function parseCount(value: unknown): number {\n const number =\n typeof value === 'number' || typeof value === 'string'\n ? Number(value)\n : NaN;\n invariant(\n Number.isSafeInteger(number) &&\n number >= 0 &&\n (typeof value !== 'string' || !!value.trim()),\n 'Invalid count in query result.'\n );\n\n return number;\n}\n\nexport function parseLabel(value: unknown, maxLength = 100): string {\n invariant(\n typeof value === 'string' && !!value && value.length <= maxLength,\n 'Invalid label in query result.'\n );\n\n return value;\n}\n\n/** Rejects missing or duplicate columns and rows whose width differs from the column list. */\nexport function rowsAsRecords(\n result: QueryResult,\n requiredColumns: readonly string[]\n): Record<string, unknown>[] {\n const names = result.columns.map(column => column.name);\n invariant(\n new Set(names).size === names.length &&\n requiredColumns.every(name => names.includes(name)),\n 'Query result columns do not match the expected shape.'\n );\n\n return result.rows.map(row => {\n invariant(\n row.length === names.length,\n 'Query result row has the wrong width.'\n );\n\n return Object.fromEntries(names.map((name, index) => [name, row[index]]));\n });\n}\n\n/** One named statement an operation ran. Returned only when SQL disclosure is allowed. */\nexport type DisclosedQuery = {\n name: string;\n statement: string;\n queryId?: string;\n};\n\nexport function defineQueryNames<const Names extends Record<string, string>>(\n names: Names\n): Names {\n const values = Object.values(names);\n invariant(\n values.every(name => !!name.trim()) &&\n new Set(values).size === values.length,\n 'Query names must be nonempty and unique.'\n );\n\n return names;\n}\n\nexport class DataSourceError extends Error {\n queryName?: string;\n\n constructor(\n public readonly reason:\n | 'unauthorized'\n | 'forbidden'\n | 'rate_limited'\n | 'query_rejected'\n | 'unavailable',\n public readonly status?: number\n ) {\n super(`Data source ${reason}${status ? ` (${status})` : ''}.`);\n this.name = 'DataSourceError';\n }\n}\n\n/** Server-only query interface supplied by a local or hosted adapter. */\nexport type Lakehouse = {\n queryAll(\n statement: string,\n options: { limit: number; signal: AbortSignal; name?: string }\n ): Promise<QueryResult>;\n};\n\nexport type OperationContext = { lakehouse: Lakehouse; signal: AbortSignal };\n\n/**\n * Both parsers run on the server before results cross the JSON boundary. Schema libraries can be\n * used inside either parser.\n */\nexport type DataOperation<Input, Output> = {\n input: (value: unknown) => Input;\n output: (value: unknown) => Output;\n run: (context: OperationContext, input: Input) => Promise<Output>;\n checks: readonly Input[];\n queryNames?: Readonly<Record<string, string>>;\n policy: {\n maxQueryRows: number;\n maxDurationMs: number;\n maxResponseBytes?: number;\n exposeSql?: boolean;\n };\n};\n\nexport type OperationQuery<Names extends Readonly<Record<string, string>>> = (\n name: Names[keyof Names],\n statement: string,\n options?: { limit?: number }\n) => Promise<QueryResult>;\n\nexport function defineOperation<\n Input,\n Output,\n const Names extends Readonly<Record<string, string>> = Record<string, never>,\n>(\n operation: Omit<DataOperation<Input, Output>, 'run' | 'queryNames'> & {\n queryNames?: Names;\n run: (\n context: OperationContext & { query: OperationQuery<NoInfer<Names>> },\n input: Input\n ) => Promise<Output>;\n }\n): DataOperation<Input, Output> & { queryNames?: Names } {\n invariant(\n Array.isArray(operation.checks) &&\n operation.checks.length > 0 &&\n Number.isInteger(operation.policy.maxQueryRows) &&\n operation.policy.maxQueryRows > 0 &&\n Number.isInteger(operation.policy.maxDurationMs) &&\n operation.policy.maxDurationMs > 0 &&\n (operation.policy.maxResponseBytes === undefined ||\n (Number.isInteger(operation.policy.maxResponseBytes) &&\n operation.policy.maxResponseBytes > 0)),\n 'Each data operation needs check inputs and positive row and duration limits.'\n );\n if (operation.queryNames) defineQueryNames(operation.queryNames);\n for (const input of operation.checks) operation.input(input);\n\n return {\n ...operation,\n run(context, input) {\n function query(\n name: Names[keyof Names],\n statement: string,\n options?: { limit?: number }\n ): Promise<QueryResult> {\n invariant(\n operation.queryNames &&\n Object.values(operation.queryNames).includes(name),\n `Unknown query name: ${name}.`\n );\n\n return context.lakehouse.queryAll(statement, {\n name,\n limit: options?.limit ?? operation.policy.maxQueryRows,\n signal: context.signal,\n });\n }\n\n return operation.run({ ...context, query }, input);\n },\n };\n}\n\nexport const connectionQueryNames = defineQueryNames({\n connection: 'connection-check',\n});\n\n/** Success requires a bounded SQL query; it does not establish access to a particular dataset. */\nexport function connectionCheck(): DataOperation<Record<string, never>, true> {\n return defineOperation({\n input: parseEmptyInput,\n output: parseTrue,\n checks: [{}],\n queryNames: connectionQueryNames,\n policy: { maxQueryRows: 1, maxDurationMs: 15_000, exposeSql: true },\n async run({ query }): Promise<true> {\n await query(\n connectionQueryNames.connection,\n 'SELECT 1 AS connection_check'\n );\n\n return true;\n },\n });\n}\n\nexport type DataOperations = Record<\n string,\n {\n input: (value: unknown) => unknown;\n output: (value: unknown) => unknown;\n run: (context: OperationContext, input: never) => Promise<unknown>;\n checks: readonly unknown[];\n queryNames?: Readonly<Record<string, string>>;\n policy: DataOperation<never, unknown>['policy'];\n }\n>;\nexport {\n dimensionFilter,\n parseDimensionSelection,\n parseFacetOptions,\n dimensionPredicate,\n} from '@/src/core/dimension';\nexport type {\n DimensionSelection,\n DimensionMember,\n DimensionOption,\n DimensionVariable,\n DimensionFilterOptions,\n} from '@/src/core/dimension';\n\nexport * from '@/src/core/messages';\nexport type { TransportResponse } from '@/src/core/bridge';\n",
|
|
6
|
+
"import type { AppVariable, HistoryMode } from '@/src/core/variables';\nimport { invariant } from '@/src/core/invariant';\n\nexport type DimensionValue = string | number;\nexport type DimensionMember<T extends DimensionValue = DimensionValue> =\n | { kind: 'value'; value: T }\n | { kind: 'missing' };\nexport type DimensionSelection<T extends DimensionValue = DimensionValue> =\n | { kind: 'all' }\n | { kind: 'include'; members: readonly DimensionMember<T>[] };\nexport type DimensionOption<T extends DimensionValue = DimensionValue> = {\n value: T;\n label: string;\n count?: number;\n};\nexport type DimensionVariable<T extends DimensionValue = DimensionValue> = Omit<\n AppVariable<DimensionSelection<T>, 'dimension'>,\n 'options'\n> & {\n label: string;\n selection: 'single' | 'multiple';\n valueType: 'string' | 'number';\n allowMissing: boolean;\n maxSelected: number;\n options: readonly DimensionOption<T>[];\n facet?: {\n operation: string;\n input: (values: Record<string, unknown>) => unknown;\n };\n describe: (selection: DimensionSelection<T>) => string;\n};\n\nconst allSelection: DimensionSelection<never> = { kind: 'all' };\n\nexport function dimensionMemberKey(member: DimensionMember) {\n return member.kind === 'missing'\n ? 'missing'\n : `value:${typeof member.value}:${member.value}`;\n}\n\nexport type DimensionFilterOptions<T extends DimensionValue> = {\n key: string;\n label: string;\n valueType: T extends number ? 'number' : 'string';\n selection: 'single' | 'multiple';\n allowMissing?: boolean;\n maxSelected?: number;\n history?: HistoryMode;\n} & (\n | { options: readonly DimensionOption<T>[]; facet?: never }\n | {\n /** A bounded operation supplies options and counts; input chooses other filters affecting counts. */\n facet: {\n operation: string;\n input: (values: Record<string, unknown>) => unknown;\n };\n options?: never;\n }\n);\n\n/** A bounded categorical filter. URL tags distinguish missing from literal \"null\". */\nexport function dimensionFilter<const T extends DimensionValue>(\n config: DimensionFilterOptions<T>\n): DimensionVariable<T> {\n const { key, label, selection, valueType } = config;\n const options = config.options ?? [];\n invariant(\n !!config.options !== !!config.facet,\n 'Choose fixed options or one facet operation.'\n );\n const optionKeys = options.map(option =>\n dimensionMemberKey({ kind: 'value', value: option.value })\n );\n invariant(\n !!key &&\n options.length <= 200 &&\n new Set(optionKeys).size === options.length,\n 'Dimension options need a key, at most 200 distinct values.'\n );\n invariant(\n options.every(\n option =>\n typeof option.value === valueType &&\n !!option.label.trim() &&\n (option.count === undefined ||\n (Number.isSafeInteger(option.count) && option.count >= 0))\n ),\n 'Dimension options need typed values, labels, and nonnegative counts.'\n );\n const allowed = new Set(optionKeys);\n const maxSelected = config.maxSelected ?? (selection === 'single' ? 1 : 20);\n invariant(\n Number.isInteger(maxSelected) &&\n maxSelected >= 1 &&\n maxSelected <= 50 &&\n (selection !== 'single' || maxSelected === 1),\n 'Dimension selection limit is invalid.'\n );\n const allowMissing = config.allowMissing ?? false;\n const defaultValue = allSelection;\n\n function validMember(member: DimensionMember<T>): boolean {\n if (member.kind === 'missing') return allowMissing;\n if (member.kind !== 'value' || typeof member.value !== valueType)\n return false;\n if (!config.facet) return allowed.has(dimensionMemberKey(member));\n\n return typeof member.value === 'number'\n ? Number.isFinite(member.value)\n : member.value.length <= 100;\n }\n\n function valid(value: DimensionSelection<T>): boolean {\n if (!value || typeof value !== 'object') return false;\n if (value.kind === 'all') return true;\n if (\n value.kind !== 'include' ||\n !Array.isArray(value.members) ||\n !value.members.length ||\n value.members.length > maxSelected\n )\n return false;\n const ids = value.members.map(dimensionMemberKey);\n\n return new Set(ids).size === ids.length && value.members.every(validMember);\n }\n\n function encode(value: DimensionSelection<T>) {\n return value.kind === 'all'\n ? null\n : JSON.stringify(\n value.members.map(member =>\n member.kind === 'missing' ? ['m'] : ['v', member.value]\n )\n );\n }\n\n function parse(raw: string | null): DimensionSelection<T> {\n if (raw === null) return defaultValue;\n if (raw.length > 4096) return defaultValue;\n try {\n const tokens = JSON.parse(raw);\n if (!Array.isArray(tokens)) return defaultValue;\n const value = {\n kind: 'include' as const,\n members: tokens.map(token =>\n Array.isArray(token) && token.length === 1 && token[0] === 'm'\n ? { kind: 'missing' as const }\n : Array.isArray(token) && token.length === 2 && token[0] === 'v'\n ? { kind: 'value' as const, value: token[1] as T }\n : { kind: 'invalid' as const }\n ),\n };\n\n return valid(value as DimensionSelection<T>)\n ? (value as DimensionSelection<T>)\n : defaultValue;\n } catch {\n return defaultValue;\n }\n }\n\n return {\n kind: 'dimension',\n label,\n valueType,\n selection,\n options,\n facet: config.facet,\n allowMissing,\n maxSelected,\n urlKeys: [key],\n defaultValue,\n history: config.history ?? 'push',\n valid,\n same(left, right) {\n return encode(left) === encode(right);\n },\n read(params) {\n return parse(params.get(key));\n },\n write(value) {\n return { [key]: encode(value) };\n },\n describe(value) {\n return value.kind === 'all'\n ? `All ${label.toLocaleLowerCase()}`\n : value.members\n .map(member =>\n member.kind === 'missing'\n ? 'Missing'\n : (options.find(option => option.value === member.value)\n ?.label ?? String(member.value))\n )\n .join(', ');\n },\n };\n}\n\n/** Server-side parser; call from the operation input parser before building SQL. */\nexport function parseDimensionSelection<T extends DimensionValue>(\n value: unknown,\n filter: DimensionVariable<T>\n): DimensionSelection<T> {\n invariant(\n filter.valid(value as DimensionSelection<T>),\n `Invalid ${filter.label} selection.`\n );\n\n return value as DimensionSelection<T>;\n}\n\n/** Validate bounded facet results before showing them in a picker. */\nexport function parseFacetOptions<T extends DimensionValue>(\n value: unknown,\n filter: DimensionVariable<T>\n): DimensionOption<T>[] {\n invariant(\n Array.isArray(value) && value.length <= 200,\n 'Facet options exceed their bound.'\n );\n const options: DimensionOption<T>[] = [];\n const seen = new Set<string>();\n for (const item of value) {\n invariant(item && typeof item === 'object', 'Invalid facet option.');\n const option = item as DimensionOption<T>;\n invariant(\n typeof option.value === filter.valueType &&\n filter.valid({\n kind: 'include',\n members: [{ kind: 'value', value: option.value }],\n }) &&\n typeof option.label === 'string' &&\n !!option.label.trim() &&\n option.label.length <= 100 &&\n (option.count === undefined ||\n (Number.isSafeInteger(option.count) && option.count >= 0)),\n 'Invalid facet option.'\n );\n const id = dimensionMemberKey({ kind: 'value', value: option.value });\n invariant(!seen.has(id), 'Duplicate facet option.');\n seen.add(id);\n options.push(option);\n }\n\n return options;\n}\n\n/** Central SQL literal encoding for transports without bound parameters. Column is an allowlisted identifier. */\nexport function dimensionPredicate<\n T extends DimensionValue,\n const Columns extends readonly string[],\n>(\n column: Columns[number],\n selection: DimensionSelection<T>,\n allowedColumns: Columns\n): string {\n invariant(\n allowedColumns.includes(column) &&\n /^[A-Za-z_][A-Za-z_0-9]*(?:\\.[A-Za-z_][A-Za-z_0-9]*)*$/.test(column),\n 'Invalid dimension column.'\n );\n if (selection.kind === 'all') return '';\n invariant(\n selection.members.length > 0,\n 'Empty dimension inclusion is invalid.'\n );\n const values = selection.members.filter(\n (member): member is Extract<DimensionMember<T>, { kind: 'value' }> =>\n member.kind === 'value'\n );\n const missing = selection.members.some(member => member.kind === 'missing');\n const literals = values.map(({ value }) => {\n if (typeof value === 'number') {\n invariant(Number.isFinite(value), 'Invalid numeric dimension value.');\n\n return String(value);\n }\n const text = value as string;\n for (let index = 0; index < text.length; index++)\n invariant(\n text.charCodeAt(index) >= 32 && text.charCodeAt(index) !== 127,\n 'Invalid control character in dimension value.'\n );\n\n return `'${text.replaceAll(\"'\", \"''\")}'`;\n });\n const clauses = [\n ...(literals.length ? [`${column} IN (${literals.join(', ')})`] : []),\n ...(missing ? [`${column} IS NULL`] : []),\n ];\n\n return `(${clauses.join(' OR ')})`;\n}\n"
|
|
7
|
+
],
|
|
8
|
+
"mappings": "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",
|
|
9
|
+
"debugId": "65216D55145AC13D64756E2164756E21",
|
|
10
|
+
"names": []
|
|
11
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// src/core/bridge.ts
|
|
2
|
+
var BRIDGE = "altertable:data-app";
|
|
3
|
+
var PARENT_PARAM = "__altertable_parent";
|
|
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
|
+
function validLocation(message) {
|
|
16
|
+
return typeof message.search === "string" && message.search.length <= 16384 && (message.search === "" || message.search.startsWith("?")) && typeof message.hash === "string" && message.hash.length <= 4096 && (message.hash === "" || message.hash.startsWith("#"));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export { BRIDGE, PARENT_PARAM, MAX_PENDING, REQUEST_TIMEOUT_MS, isBridgeMessage, validId, validLocation };
|
|
20
|
+
|
|
21
|
+
//# debugId=0094FE477158BED064756E2164756E21
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/core/bridge.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/** Data app delivery protocol. IDs identify a document, a shell session, and an individual call. */\nexport const BRIDGE = 'altertable:data-app';\nexport const PARENT_PARAM = '__altertable_parent';\n/** Bound pending requests in the client and host independently.\n * The host enforces its own limit because frames can send messages directly. */\nexport const MAX_PENDING = 128;\nexport const REQUEST_TIMEOUT_MS = 60_000;\nexport type TransportResponse = { status: number; body: unknown };\nexport type BridgeMessage = {\n channel: typeof BRIDGE;\n version: 1;\n type: string;\n documentId: string;\n sessionId?: string;\n id?: string;\n route?: string;\n payload?: unknown;\n response?: unknown;\n code?: string;\n message?: string;\n /** Opaque host context, interpreted by optional app adapters. */\n state?: unknown;\n requestId?: string;\n token?: string;\n javascript?: string;\n};\n\nexport function isBridgeMessage(value: unknown): value is BridgeMessage {\n if (!value || typeof value !== 'object') return false;\n const message = value as BridgeMessage;\n\n return (\n message.channel === BRIDGE &&\n message.version === 1 &&\n typeof message.type === 'string' &&\n typeof message.documentId === 'string' &&\n message.documentId.length > 0 &&\n message.documentId.length <= 128\n );\n}\n\nexport function validId(value: unknown): value is string {\n return typeof value === 'string' && /^[a-zA-Z0-9_-]{1,128}$/.test(value);\n}\n\nexport function validLocation(message: {\n search?: unknown;\n hash?: unknown;\n}): boolean {\n return (\n typeof message.search === 'string' &&\n message.search.length <= 16_384 &&\n (message.search === '' || message.search.startsWith('?')) &&\n typeof message.hash === 'string' &&\n message.hash.length <= 4096 &&\n (message.hash === '' || message.hash.startsWith('#'))\n );\n}\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": ";AACO,IAAM,SAAS;AACf,IAAM,eAAe;AAGrB,IAAM,cAAc;AACpB,IAAM,qBAAqB;AAqB3B,SAAS,eAAe,CAAC,OAAwC;AAAA,EACtE,IAAI,CAAC,SAAS,OAAO,UAAU;AAAA,IAAU,OAAO;AAAA,EAChD,MAAM,UAAU;AAAA,EAEhB,OACE,QAAQ,YAAY,UACpB,QAAQ,YAAY,KACpB,OAAO,QAAQ,SAAS,YACxB,OAAO,QAAQ,eAAe,YAC9B,QAAQ,WAAW,SAAS,KAC5B,QAAQ,WAAW,UAAU;AAAA;AAI1B,SAAS,OAAO,CAAC,OAAiC;AAAA,EACvD,OAAO,OAAO,UAAU,YAAY,yBAAyB,KAAK,KAAK;AAAA;AAGlE,SAAS,aAAa,CAAC,SAGlB;AAAA,EACV,OACE,OAAO,QAAQ,WAAW,YAC1B,QAAQ,OAAO,UAAU,UACxB,QAAQ,WAAW,MAAM,QAAQ,OAAO,WAAW,GAAG,MACvD,OAAO,QAAQ,SAAS,YACxB,QAAQ,KAAK,UAAU,SACtB,QAAQ,SAAS,MAAM,QAAQ,KAAK,WAAW,GAAG;AAAA;",
|
|
8
|
+
"debugId": "0094FE477158BED064756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DataAppError2,
|
|
3
|
+
createHttpTransport2,
|
|
4
|
+
createMessageClient2,
|
|
5
|
+
localFrameBridge,
|
|
6
|
+
getDataAppTransport2
|
|
7
|
+
} from "./contract-mb5nfzwg.js";
|
|
8
|
+
import {
|
|
9
|
+
navigationUpdateRoute2
|
|
10
|
+
} from "./contract-jksbmt5q.js";
|
|
11
|
+
import {
|
|
12
|
+
PARENT_PARAM,
|
|
13
|
+
validLocation
|
|
14
|
+
} from "./contract-ryyf6dme.js";
|
|
15
|
+
// src/client/location.ts
|
|
16
|
+
function createAppLocation(frame, virtual, publish) {
|
|
17
|
+
let state = { search: "", hash: "" };
|
|
18
|
+
let disposed = false;
|
|
19
|
+
const listeners = new Set;
|
|
20
|
+
function notify() {
|
|
21
|
+
for (const listener of listeners)
|
|
22
|
+
listener();
|
|
23
|
+
}
|
|
24
|
+
function snapshot() {
|
|
25
|
+
if (virtual)
|
|
26
|
+
return state;
|
|
27
|
+
const url = new URL(frame.location.href);
|
|
28
|
+
url.searchParams.delete(PARENT_PARAM);
|
|
29
|
+
if (state.search !== url.search || state.hash !== url.hash)
|
|
30
|
+
state = { search: url.search, hash: url.hash };
|
|
31
|
+
return state;
|
|
32
|
+
}
|
|
33
|
+
function apply(next) {
|
|
34
|
+
const previous = snapshot();
|
|
35
|
+
if (previous.search === next.search && previous.hash === next.hash)
|
|
36
|
+
return;
|
|
37
|
+
if (virtual)
|
|
38
|
+
state = next;
|
|
39
|
+
else {
|
|
40
|
+
const url = new URL(frame.location.href);
|
|
41
|
+
const parent = url.searchParams.get(PARENT_PARAM);
|
|
42
|
+
url.search = next.search;
|
|
43
|
+
if (parent)
|
|
44
|
+
url.searchParams.set(PARENT_PARAM, parent);
|
|
45
|
+
url.hash = next.hash;
|
|
46
|
+
frame.history.replaceState(null, "", url);
|
|
47
|
+
frame.dispatchEvent(new Event("popstate"));
|
|
48
|
+
}
|
|
49
|
+
if (virtual)
|
|
50
|
+
notify();
|
|
51
|
+
}
|
|
52
|
+
const location = {
|
|
53
|
+
snapshot,
|
|
54
|
+
subscribe(listener) {
|
|
55
|
+
listeners.add(listener);
|
|
56
|
+
if (!virtual)
|
|
57
|
+
frame.addEventListener("popstate", listener);
|
|
58
|
+
return () => {
|
|
59
|
+
listeners.delete(listener);
|
|
60
|
+
frame.removeEventListener("popstate", listener);
|
|
61
|
+
};
|
|
62
|
+
},
|
|
63
|
+
update(next, mode = "replace") {
|
|
64
|
+
if (disposed)
|
|
65
|
+
return;
|
|
66
|
+
if (!validLocation(next))
|
|
67
|
+
throw new Error("Invalid app location.");
|
|
68
|
+
const before = snapshot();
|
|
69
|
+
if (before.search === next.search && before.hash === next.hash)
|
|
70
|
+
return;
|
|
71
|
+
apply(next);
|
|
72
|
+
publish(next, mode);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
function dispose() {
|
|
76
|
+
disposed = true;
|
|
77
|
+
for (const listener of listeners)
|
|
78
|
+
frame.removeEventListener("popstate", listener);
|
|
79
|
+
listeners.clear();
|
|
80
|
+
}
|
|
81
|
+
return { location, apply, dispose };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// src/client/navigation.ts
|
|
85
|
+
function createDataAppNavigation2({
|
|
86
|
+
bridge,
|
|
87
|
+
window: frame = window
|
|
88
|
+
}) {
|
|
89
|
+
return attachNavigation(frame, bridge.mode === "bundle", bridge).navigation;
|
|
90
|
+
}
|
|
91
|
+
function attachNavigation(frame, virtual, connection) {
|
|
92
|
+
let disposed = false;
|
|
93
|
+
let unsubscribe;
|
|
94
|
+
let send;
|
|
95
|
+
let pendingUpdate;
|
|
96
|
+
const { location, apply, dispose } = createAppLocation(frame, virtual, publish);
|
|
97
|
+
function publish(location, mode) {
|
|
98
|
+
const update = { ...location, mode, title: frame.document.title };
|
|
99
|
+
if (send)
|
|
100
|
+
send(update);
|
|
101
|
+
else
|
|
102
|
+
pendingUpdate = update;
|
|
103
|
+
}
|
|
104
|
+
function attach(bridge) {
|
|
105
|
+
if (disposed)
|
|
106
|
+
return;
|
|
107
|
+
unsubscribe?.();
|
|
108
|
+
const messages = createMessageClient2({ "navigation.update": navigationUpdateRoute2 }, bridge.request);
|
|
109
|
+
function sendUpdate(update) {
|
|
110
|
+
messages.request("navigation.update", update).catch(() => {});
|
|
111
|
+
}
|
|
112
|
+
function receiveState(state) {
|
|
113
|
+
if (!state || typeof state !== "object" || !validLocation(state))
|
|
114
|
+
return;
|
|
115
|
+
pendingUpdate = undefined;
|
|
116
|
+
apply(state);
|
|
117
|
+
}
|
|
118
|
+
send = sendUpdate;
|
|
119
|
+
unsubscribe = bridge.subscribe(receiveState);
|
|
120
|
+
receiveState(bridge.snapshot());
|
|
121
|
+
if (pendingUpdate) {
|
|
122
|
+
sendUpdate(pendingUpdate);
|
|
123
|
+
pendingUpdate = undefined;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if ("then" in connection)
|
|
127
|
+
connection.then(attach).catch(() => {});
|
|
128
|
+
else
|
|
129
|
+
attach(connection);
|
|
130
|
+
const navigation = {
|
|
131
|
+
...location,
|
|
132
|
+
publish(mode = "replace") {
|
|
133
|
+
if (!disposed)
|
|
134
|
+
publish(location.snapshot(), mode);
|
|
135
|
+
},
|
|
136
|
+
dispose() {
|
|
137
|
+
disposed = true;
|
|
138
|
+
unsubscribe?.();
|
|
139
|
+
dispose();
|
|
140
|
+
send = undefined;
|
|
141
|
+
pendingUpdate = undefined;
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
return {
|
|
145
|
+
navigation,
|
|
146
|
+
attach,
|
|
147
|
+
isDisposed() {
|
|
148
|
+
return disposed;
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
var navigationKey = Symbol.for("altertable.appNavigation");
|
|
153
|
+
function getDataAppNavigation2(frame = window) {
|
|
154
|
+
const target = frame;
|
|
155
|
+
const bridge = getDataAppTransport2(frame);
|
|
156
|
+
const cached = target[navigationKey]?.isDisposed() ? undefined : target[navigationKey];
|
|
157
|
+
if (cached) {
|
|
158
|
+
if (bridge && cached.bridge !== bridge) {
|
|
159
|
+
cached.attach(bridge);
|
|
160
|
+
cached.bridge = bridge;
|
|
161
|
+
}
|
|
162
|
+
return cached.navigation;
|
|
163
|
+
}
|
|
164
|
+
if (bridge) {
|
|
165
|
+
const adapter = attachNavigation(frame, bridge.mode === "bundle", bridge);
|
|
166
|
+
target[navigationKey] = { bridge, ...adapter };
|
|
167
|
+
return adapter.navigation;
|
|
168
|
+
}
|
|
169
|
+
const preview = localFrameBridge(frame);
|
|
170
|
+
if (!preview?.connect)
|
|
171
|
+
return;
|
|
172
|
+
const connection = preview.connect();
|
|
173
|
+
const adapter = attachNavigation(frame, false, connection);
|
|
174
|
+
const entry = {
|
|
175
|
+
...adapter,
|
|
176
|
+
bridge: undefined
|
|
177
|
+
};
|
|
178
|
+
target[navigationKey] = entry;
|
|
179
|
+
connection.then((bridge) => {
|
|
180
|
+
entry.bridge = bridge;
|
|
181
|
+
}).catch(() => {});
|
|
182
|
+
return adapter.navigation;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// src/client/index.ts
|
|
186
|
+
function createDataClient2(options = {}) {
|
|
187
|
+
const http = createHttpTransport2(options);
|
|
188
|
+
return {
|
|
189
|
+
async query(name, input, { signal } = {}) {
|
|
190
|
+
const transport = options.transport ?? (options.endpoint !== undefined || options.fetch !== undefined ? http : localFrameBridge()?.transport ?? http);
|
|
191
|
+
const response = await transport(name, input, signal);
|
|
192
|
+
const ok = response.status >= 200 && response.status < 300;
|
|
193
|
+
let body;
|
|
194
|
+
try {
|
|
195
|
+
const parsed = response.body;
|
|
196
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
|
|
197
|
+
throw new Error("Invalid response envelope.");
|
|
198
|
+
body = parsed;
|
|
199
|
+
} catch (error) {
|
|
200
|
+
if (signal?.aborted)
|
|
201
|
+
throw error;
|
|
202
|
+
throw new DataAppError2(ok ? "The data response was invalid." : "Could not load data.", ok ? "invalid_response" : "request_failed");
|
|
203
|
+
}
|
|
204
|
+
if (!ok)
|
|
205
|
+
throw new DataAppError2(body.error?.message ?? "Could not load data.", body.error?.code ?? "request_failed", body.error?.requestId);
|
|
206
|
+
return { ...body, input };
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export { createDataAppNavigation2, getDataAppNavigation2, createDataClient2 };
|
|
212
|
+
|
|
213
|
+
//# debugId=235551129B40A86C64756E2164756E21
|