@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.
Files changed (174) hide show
  1. package/AGENTS.md +35 -0
  2. package/CONTRIBUTING.md +83 -0
  3. package/LICENSE +21 -0
  4. package/README.md +63 -0
  5. package/dist/bootstrap.js +388 -0
  6. package/dist/chunks/contract-14vxdcrs.js +192 -0
  7. package/dist/chunks/contract-14vxdcrs.js.map +10 -0
  8. package/dist/chunks/contract-8q35dcyh.js +9 -0
  9. package/dist/chunks/contract-8q35dcyh.js.map +10 -0
  10. package/dist/chunks/contract-jksbmt5q.js +133 -0
  11. package/dist/chunks/contract-jksbmt5q.js.map +10 -0
  12. package/dist/chunks/contract-mb5nfzwg.js +375 -0
  13. package/dist/chunks/contract-mb5nfzwg.js.map +12 -0
  14. package/dist/chunks/contract-mev09s5v.js +77 -0
  15. package/dist/chunks/contract-mev09s5v.js.map +10 -0
  16. package/dist/chunks/contract-nt819swq.js +321 -0
  17. package/dist/chunks/contract-nt819swq.js.map +11 -0
  18. package/dist/chunks/contract-ryyf6dme.js +21 -0
  19. package/dist/chunks/contract-ryyf6dme.js.map +10 -0
  20. package/dist/chunks/contract-tkkc28tg.js +213 -0
  21. package/dist/chunks/contract-tkkc28tg.js.map +12 -0
  22. package/dist/chunks/contract-tqrf3ykr.js +232 -0
  23. package/dist/chunks/contract-tqrf3ykr.js.map +11 -0
  24. package/dist/chunks/contract-wz59z8pq.js +8 -0
  25. package/dist/chunks/contract-wz59z8pq.js.map +10 -0
  26. package/dist/client/index.js +27 -0
  27. package/dist/client/index.js.map +9 -0
  28. package/dist/core/appearance.js +12 -0
  29. package/dist/core/appearance.js.map +9 -0
  30. package/dist/core/config.js +8 -0
  31. package/dist/core/config.js.map +9 -0
  32. package/dist/core/contract.js +55 -0
  33. package/dist/core/contract.js.map +9 -0
  34. package/dist/core/format.js +18 -0
  35. package/dist/core/format.js.map +9 -0
  36. package/dist/embed/index.js +91 -0
  37. package/dist/embed/index.js.map +11 -0
  38. package/dist/local.js +332 -0
  39. package/dist/local.js.map +15 -0
  40. package/dist/react/embed/index.js +130 -0
  41. package/dist/react/embed/index.js.map +11 -0
  42. package/dist/react/index.css +4478 -0
  43. package/dist/react/index.js +6105 -0
  44. package/dist/react/index.js.map +90 -0
  45. package/dist/react.css.d.ts +1 -0
  46. package/dist/server.js +231 -0
  47. package/dist/server.js.map +14 -0
  48. package/dist/types/client/iframe.d.ts +39 -0
  49. package/dist/types/client/index.d.ts +42 -0
  50. package/dist/types/client/location.d.ts +15 -0
  51. package/dist/types/client/messages.d.ts +7 -0
  52. package/dist/types/client/navigation.d.ts +14 -0
  53. package/dist/types/client/transport.d.ts +15 -0
  54. package/dist/types/core/appearance.d.ts +28 -0
  55. package/dist/types/core/bridge.d.ts +35 -0
  56. package/dist/types/core/config.d.ts +14 -0
  57. package/dist/types/core/contract.d.ts +139 -0
  58. package/dist/types/core/data-view.d.ts +46 -0
  59. package/dist/types/core/date-range.d.ts +20 -0
  60. package/dist/types/core/dimension.d.ts +60 -0
  61. package/dist/types/core/format.d.ts +40 -0
  62. package/dist/types/core/invariant.d.ts +2 -0
  63. package/dist/types/core/messages.d.ts +77 -0
  64. package/dist/types/core/reading.d.ts +17 -0
  65. package/dist/types/core/variables.d.ts +82 -0
  66. package/dist/types/embed/bootstrap.d.ts +5 -0
  67. package/dist/types/embed/host.d.ts +23 -0
  68. package/dist/types/embed/index.d.ts +11 -0
  69. package/dist/types/embed/navigation.d.ts +6 -0
  70. package/dist/types/embed/shell.d.ts +21 -0
  71. package/dist/types/embed/standalone.d.ts +1 -0
  72. package/dist/types/react/content.d.ts +23 -0
  73. package/dist/types/react/embed/bridge.d.ts +12 -0
  74. package/dist/types/react/embed/index.d.ts +9 -0
  75. package/dist/types/react/embed/shell.d.ts +10 -0
  76. package/dist/types/react/hooks.d.ts +831 -0
  77. package/dist/types/react/index.d.ts +11 -0
  78. package/dist/types/react/mount.d.ts +11 -0
  79. package/dist/types/react/ui/AboutData.d.ts +61 -0
  80. package/dist/types/react/ui/AltertableLogo.d.ts +3 -0
  81. package/dist/types/react/ui/AppFooter.d.ts +7 -0
  82. package/dist/types/react/ui/AppHeader.d.ts +12 -0
  83. package/dist/types/react/ui/AppLayout.d.ts +17 -0
  84. package/dist/types/react/ui/AppScope.d.ts +8 -0
  85. package/dist/types/react/ui/AppToolbar.d.ts +32 -0
  86. package/dist/types/react/ui/Breakdown.d.ts +14 -0
  87. package/dist/types/react/ui/Button.d.ts +12 -0
  88. package/dist/types/react/ui/Checkbox.d.ts +11 -0
  89. package/dist/types/react/ui/Combobox.d.ts +43 -0
  90. package/dist/types/react/ui/ComparisonVisual.d.ts +15 -0
  91. package/dist/types/react/ui/ContentSkeleton.d.ts +8 -0
  92. package/dist/types/react/ui/DataApp.d.ts +56 -0
  93. package/dist/types/react/ui/DataBoundary.d.ts +16 -0
  94. package/dist/types/react/ui/DataSection.d.ts +28 -0
  95. package/dist/types/react/ui/DataTable.d.ts +28 -0
  96. package/dist/types/react/ui/DataViewToast.d.ts +12 -0
  97. package/dist/types/react/ui/DataWidget.d.ts +39 -0
  98. package/dist/types/react/ui/DateRangePicker.d.ts +29 -0
  99. package/dist/types/react/ui/DateTimeTooltip.d.ts +10 -0
  100. package/dist/types/react/ui/DimensionPicker.d.ts +11 -0
  101. package/dist/types/react/ui/EmptyState.d.ts +9 -0
  102. package/dist/types/react/ui/GettingStarted.d.ts +9 -0
  103. package/dist/types/react/ui/GlossaryDefinition.d.ts +8 -0
  104. package/dist/types/react/ui/GlossaryExplanation.d.ts +17 -0
  105. package/dist/types/react/ui/GradientScroll.d.ts +16 -0
  106. package/dist/types/react/ui/Grid.d.ts +13 -0
  107. package/dist/types/react/ui/GridItem.d.ts +7 -0
  108. package/dist/types/react/ui/HelpPopover.d.ts +24 -0
  109. package/dist/types/react/ui/IconButton.d.ts +19 -0
  110. package/dist/types/react/ui/InspectionContext.d.ts +10 -0
  111. package/dist/types/react/ui/Kbd.d.ts +8 -0
  112. package/dist/types/react/ui/LiveControl.d.ts +11 -0
  113. package/dist/types/react/ui/MetricWidget.d.ts +48 -0
  114. package/dist/types/react/ui/PeriodSummary.d.ts +17 -0
  115. package/dist/types/react/ui/PresentStory.d.ts +34 -0
  116. package/dist/types/react/ui/QueryList.d.ts +15 -0
  117. package/dist/types/react/ui/Ranking.d.ts +14 -0
  118. package/dist/types/react/ui/RefreshControl.d.ts +11 -0
  119. package/dist/types/react/ui/RefreshRegion.d.ts +10 -0
  120. package/dist/types/react/ui/RequestHint.d.ts +21 -0
  121. package/dist/types/react/ui/SearchField.d.ts +23 -0
  122. package/dist/types/react/ui/SearchInput.d.ts +9 -0
  123. package/dist/types/react/ui/SearchMatch.d.ts +7 -0
  124. package/dist/types/react/ui/SelectableBarChart.d.ts +16 -0
  125. package/dist/types/react/ui/SelectionMark.d.ts +5 -0
  126. package/dist/types/react/ui/Sheet.d.ts +19 -0
  127. package/dist/types/react/ui/Skeleton.d.ts +6 -0
  128. package/dist/types/react/ui/Stack.d.ts +8 -0
  129. package/dist/types/react/ui/StatusPanel.d.ts +11 -0
  130. package/dist/types/react/ui/TableWidget.d.ts +57 -0
  131. package/dist/types/react/ui/Tabs.d.ts +6 -0
  132. package/dist/types/react/ui/ThemeSelector.d.ts +13 -0
  133. package/dist/types/react/ui/Tooltip.d.ts +23 -0
  134. package/dist/types/react/ui/UpdatedAt.d.ts +10 -0
  135. package/dist/types/react/ui/VariableBar.d.ts +7 -0
  136. package/dist/types/react/ui/VisualizationWidget.d.ts +52 -0
  137. package/dist/types/react/ui/WidgetDisclosure.d.ts +7 -0
  138. package/dist/types/react/ui/WidgetEvidence.d.ts +12 -0
  139. package/dist/types/react/ui/WidgetViewTabs.d.ts +17 -0
  140. package/dist/types/react/ui/chartColor.d.ts +1 -0
  141. package/dist/types/react/ui/classNames.d.ts +1 -0
  142. package/dist/types/react/ui/comparison.d.ts +30 -0
  143. package/dist/types/react/ui/data-context.d.ts +65 -0
  144. package/dist/types/react/ui/data-identifiers.d.ts +33 -0
  145. package/dist/types/react/ui/icons.d.ts +42 -0
  146. package/dist/types/react/ui/index.d.ts +129 -0
  147. package/dist/types/react/ui/metric.d.ts +11 -0
  148. package/dist/types/react/ui/search.d.ts +6 -0
  149. package/dist/types/react/ui/searchItems.d.ts +34 -0
  150. package/dist/types/react/ui/shortcuts.d.ts +32 -0
  151. package/dist/types/react/ui/story.d.ts +20 -0
  152. package/dist/types/react/ui/variables.d.ts +36 -0
  153. package/dist/types/react/ui/widget-views.d.ts +3 -0
  154. package/dist/types/react/view-controls.d.ts +17 -0
  155. package/dist/types/react/view.d.ts +49 -0
  156. package/dist/types/server/handler.d.ts +13 -0
  157. package/dist/types/server/index.d.ts +7 -0
  158. package/dist/types/server/local.d.ts +17 -0
  159. package/docs/app-authoring.md +26 -0
  160. package/docs/appearance.md +30 -0
  161. package/docs/bootstrap.md +76 -0
  162. package/docs/client.md +127 -0
  163. package/docs/config.md +21 -0
  164. package/docs/contract.md +95 -0
  165. package/docs/embed.md +98 -0
  166. package/docs/format.md +29 -0
  167. package/docs/react-embed.md +57 -0
  168. package/docs/react-styles.md +17 -0
  169. package/docs/react.md +248 -0
  170. package/docs/releasing.md +74 -0
  171. package/docs/server-bun.md +26 -0
  172. package/docs/server.md +47 -0
  173. package/docs/starter-agent-instructions.md +51 -0
  174. package/package.json +124 -0
@@ -0,0 +1,375 @@
1
+ import {
2
+ MessageRoutingError2,
3
+ defineDataQueryRoute2
4
+ } from "./contract-jksbmt5q.js";
5
+ import {
6
+ BRIDGE,
7
+ PARENT_PARAM,
8
+ MAX_PENDING,
9
+ REQUEST_TIMEOUT_MS,
10
+ isBridgeMessage,
11
+ validId
12
+ } from "./contract-ryyf6dme.js";
13
+
14
+ // src/client/transport.ts
15
+ var errorBrand = Symbol.for("@altertable/data-app/DataAppError");
16
+
17
+ class DataAppError2 extends Error {
18
+ code;
19
+ requestId;
20
+ [errorBrand] = true;
21
+ static [Symbol.hasInstance](value) {
22
+ return value instanceof Error && value[errorBrand] === true;
23
+ }
24
+ constructor(message, code, requestId) {
25
+ super(message);
26
+ this.code = code;
27
+ this.requestId = requestId;
28
+ this.name = "DataAppError";
29
+ }
30
+ }
31
+ function createHttpTransport2({
32
+ endpoint = "/api/data",
33
+ fetch: request = globalThis.fetch
34
+ } = {}) {
35
+ async function requestData(operation, input, signal) {
36
+ const response = await request(`${endpoint.replace(/\/$/, "")}/${encodeURIComponent(operation)}`, {
37
+ method: "POST",
38
+ headers: { "content-type": "application/json" },
39
+ body: JSON.stringify(input),
40
+ signal
41
+ });
42
+ try {
43
+ return { status: response.status, body: await response.json() };
44
+ } catch (error) {
45
+ if (signal?.aborted)
46
+ throw error;
47
+ throw new DataAppError2(response.ok ? "The data response was invalid." : "Could not load data.", response.ok ? "invalid_response" : "request_failed");
48
+ }
49
+ }
50
+ return requestData;
51
+ }
52
+
53
+ // src/client/messages.ts
54
+ function createMessageClient2(routes, transport) {
55
+ return {
56
+ async request(route, payload, { signal } = {}) {
57
+ signal?.throwIfAborted();
58
+ if (!Object.hasOwn(routes, route))
59
+ throw new MessageRoutingError2("unknown_route", "Unknown message route.");
60
+ const contract = routes[route];
61
+ let input;
62
+ try {
63
+ input = contract.input(payload);
64
+ } catch {
65
+ throw new MessageRoutingError2("invalid_payload", "Invalid message payload.");
66
+ }
67
+ const response = await transport({ route, payload: input }, signal);
68
+ signal?.throwIfAborted();
69
+ try {
70
+ return contract.output(response, input);
71
+ } catch (error) {
72
+ if (error instanceof MessageRoutingError2)
73
+ throw error;
74
+ throw new MessageRoutingError2("invalid_response", "Invalid message response.");
75
+ }
76
+ }
77
+ };
78
+ }
79
+
80
+ // src/client/iframe.ts
81
+ function createIframeTransport2({
82
+ parentOrigin,
83
+ window: frame = window,
84
+ timeoutMs = REQUEST_TIMEOUT_MS,
85
+ mode = "url",
86
+ loadScript
87
+ }) {
88
+ if (new URL(parentOrigin).origin !== parentOrigin)
89
+ throw new Error("Expected an exact parent origin.");
90
+ const documentId = crypto.randomUUID();
91
+ let token;
92
+ let sessionId;
93
+ let disposed = false;
94
+ const pending = new Map;
95
+ let hostState;
96
+ const stateListeners = new Set;
97
+ function send(message) {
98
+ frame.parent.postMessage({ channel: BRIDGE, version: 1, documentId, sessionId, token, ...message }, parentOrigin);
99
+ }
100
+ function receiveState(state) {
101
+ hostState = state;
102
+ for (const listener of stateListeners)
103
+ listener(state);
104
+ }
105
+ function receive(event) {
106
+ if (event.origin !== parentOrigin || event.source !== frame.parent || !isBridgeMessage(event.data))
107
+ return;
108
+ const message = event.data;
109
+ if (message.type === "connect") {
110
+ if (mode === "bundle") {
111
+ if (!validId(message.token))
112
+ return;
113
+ token = message.token;
114
+ }
115
+ send({ type: "ready" });
116
+ return;
117
+ }
118
+ if (mode === "bundle" && (!token || message.token !== token))
119
+ return;
120
+ if (message.documentId !== documentId)
121
+ return;
122
+ if (message.type === "initialize" && validId(message.sessionId)) {
123
+ if (sessionId && sessionId !== message.sessionId) {
124
+ for (const entry of pending.values())
125
+ entry.reject(new DataAppError2("The preview reconnected. Retry the request.", "bridge_reset"));
126
+ }
127
+ const first = !sessionId;
128
+ sessionId = message.sessionId;
129
+ if (first)
130
+ for (const entry of pending.values())
131
+ entry.start();
132
+ receiveState(message.state);
133
+ if (mode === "url")
134
+ send({ type: "runtime.ready" });
135
+ return;
136
+ }
137
+ if (!sessionId || message.sessionId !== sessionId)
138
+ return;
139
+ if (message.type === "state") {
140
+ receiveState(message.state);
141
+ return;
142
+ }
143
+ if (message.type === "script.load" && mode === "bundle" && typeof message.javascript === "string") {
144
+ loadScript?.(message.javascript);
145
+ return;
146
+ }
147
+ if (typeof message.id !== "string")
148
+ return;
149
+ const entry = pending.get(message.id);
150
+ if (!entry)
151
+ return;
152
+ if (message.type === "result" && "response" in message) {
153
+ entry.resolve(message.response);
154
+ } else if (message.type === "error" && typeof message.code === "string" && typeof message.message === "string") {
155
+ entry.reject(new MessageRoutingError2(message.code, message.message, typeof message.requestId === "string" ? message.requestId : undefined));
156
+ }
157
+ }
158
+ frame.addEventListener("message", receive);
159
+ function requestMessage(message, signal) {
160
+ return new Promise((resolve, reject) => {
161
+ if (disposed)
162
+ return reject(new DataAppError2("The preview has closed.", "bridge_closed"));
163
+ if (signal?.aborted)
164
+ return reject(signal.reason);
165
+ if (pending.size >= MAX_PENDING)
166
+ return reject(new DataAppError2("Too many pending data requests.", "bridge_busy"));
167
+ const id = crypto.randomUUID();
168
+ let sent = false;
169
+ function cleanup() {
170
+ clearTimeout(timer);
171
+ signal?.removeEventListener("abort", abort);
172
+ pending.delete(id);
173
+ }
174
+ function abort() {
175
+ if (sent)
176
+ send({ type: "cancel", id });
177
+ cleanup();
178
+ reject(signal?.reason);
179
+ }
180
+ const timer = setTimeout(() => {
181
+ if (sent)
182
+ send({ type: "cancel", id });
183
+ cleanup();
184
+ reject(new DataAppError2(sessionId ? "The data request timed out." : "The preview shell is unavailable. Reload the preview.", sessionId ? "timeout" : "bridge_unavailable"));
185
+ }, timeoutMs);
186
+ const entry = {
187
+ start() {
188
+ if (sent)
189
+ return;
190
+ try {
191
+ send({
192
+ type: "request",
193
+ id,
194
+ route: message.route,
195
+ payload: message.payload
196
+ });
197
+ sent = true;
198
+ } catch {
199
+ cleanup();
200
+ reject(new DataAppError2("The message payload could not be sent.", "invalid_payload"));
201
+ }
202
+ },
203
+ resolve(value) {
204
+ cleanup();
205
+ resolve(value);
206
+ },
207
+ reject(error) {
208
+ cleanup();
209
+ reject(error);
210
+ }
211
+ };
212
+ pending.set(id, entry);
213
+ signal?.addEventListener("abort", abort, { once: true });
214
+ if (sessionId)
215
+ entry.start();
216
+ else
217
+ send({ type: "ready" });
218
+ });
219
+ }
220
+ const messages = createMessageClient2({ "data.query": defineDataQueryRoute2() }, requestMessage);
221
+ async function queryData(operation, input, signal) {
222
+ try {
223
+ return await messages.request("data.query", { operation, input }, { signal });
224
+ } catch (error) {
225
+ if (error instanceof MessageRoutingError2)
226
+ throw new DataAppError2(error.message, error.code, error.requestId);
227
+ throw error;
228
+ }
229
+ }
230
+ function disconnect() {
231
+ send({ type: "disconnect" });
232
+ sessionId = undefined;
233
+ for (const entry of pending.values())
234
+ entry.reject(new DataAppError2("The preview has closed.", "bridge_closed"));
235
+ }
236
+ function resume(event) {
237
+ if (event.persisted)
238
+ send({ type: "ready" });
239
+ }
240
+ function dispose() {
241
+ if (disposed)
242
+ return;
243
+ disconnect();
244
+ disposed = true;
245
+ frame.removeEventListener("message", receive);
246
+ frame.removeEventListener("pagehide", disconnect);
247
+ frame.removeEventListener("pageshow", resume);
248
+ stateListeners.clear();
249
+ }
250
+ frame.addEventListener("pagehide", disconnect);
251
+ frame.addEventListener("pageshow", resume);
252
+ if (mode === "url")
253
+ send({ type: "ready" });
254
+ return {
255
+ request: requestMessage,
256
+ transport: queryData,
257
+ dispose,
258
+ mode,
259
+ snapshot() {
260
+ return hostState;
261
+ },
262
+ subscribe(listener) {
263
+ stateListeners.add(listener);
264
+ return () => {
265
+ stateListeners.delete(listener);
266
+ };
267
+ },
268
+ ready() {
269
+ send({ type: "runtime.ready" });
270
+ },
271
+ fail() {
272
+ send({ type: "runtime.error" });
273
+ }
274
+ };
275
+ }
276
+ var bridgeKey = Symbol.for("altertable.localFrameBridge");
277
+ var installedKey = Symbol.for("altertable.installedTransport");
278
+ function localFrameBridge(window2) {
279
+ const activeWindow = window2 ?? (typeof globalThis.window === "undefined" ? undefined : globalThis.window);
280
+ if (!activeWindow)
281
+ return;
282
+ const frame = activeWindow;
283
+ if (frame[bridgeKey])
284
+ return frame[bridgeKey];
285
+ const parentOrigin = new URL(frame.location.href).searchParams.get(PARENT_PARAM) ?? "";
286
+ if (!parentOrigin)
287
+ return;
288
+ async function connect(parentOrigin) {
289
+ try {
290
+ const response = await frame.fetch("/__altertable/parent", {
291
+ signal: AbortSignal.timeout(5000)
292
+ });
293
+ const config = await response.json();
294
+ if (!response.ok || config.origin !== parentOrigin || frame.parent === frame)
295
+ throw new Error("Invalid local parent configuration.");
296
+ if (frame[installedKey])
297
+ return frame[installedKey];
298
+ if (frame[bridgeKey] !== proxy)
299
+ throw new Error("Preview transport owner changed.");
300
+ const bridge = createIframeTransport2({ parentOrigin, window: frame });
301
+ frame[bridgeKey] = bridge;
302
+ frame[installedKey] = bridge;
303
+ return bridge;
304
+ } catch {
305
+ throw new DataAppError2("The preview shell could not be verified. Open the URL printed by altertable app dev.", "bridge_unavailable");
306
+ }
307
+ }
308
+ let connection;
309
+ function getConnection() {
310
+ const installed = frame[installedKey];
311
+ if (installed)
312
+ return Promise.resolve(installed);
313
+ if (!connection) {
314
+ const attempt = connect(parentOrigin);
315
+ connection = attempt;
316
+ attempt.catch(() => {
317
+ if (connection === attempt)
318
+ connection = undefined;
319
+ });
320
+ }
321
+ return connection;
322
+ }
323
+ const proxy = {
324
+ async transport(operation, input, signal) {
325
+ signal?.throwIfAborted();
326
+ const attempt = getConnection();
327
+ let abort;
328
+ try {
329
+ const bridge = signal ? await Promise.race([
330
+ attempt,
331
+ new Promise((_, reject) => {
332
+ const requestSignal = signal;
333
+ function handleAbort() {
334
+ reject(requestSignal.reason);
335
+ }
336
+ abort = handleAbort;
337
+ requestSignal.addEventListener("abort", handleAbort, {
338
+ once: true
339
+ });
340
+ })
341
+ ]) : await attempt;
342
+ signal?.throwIfAborted();
343
+ return bridge.transport(operation, input, signal);
344
+ } finally {
345
+ if (abort)
346
+ signal?.removeEventListener("abort", abort);
347
+ }
348
+ },
349
+ connect: getConnection
350
+ };
351
+ frame[bridgeKey] = proxy;
352
+ getConnection().catch(() => {});
353
+ return proxy;
354
+ }
355
+ function installDataAppTransport2(bridge, frame = window) {
356
+ const target = frame;
357
+ if (target[installedKey])
358
+ throw new Error("A data app transport is already installed.");
359
+ target[bridgeKey] = bridge;
360
+ target[installedKey] = bridge;
361
+ return () => {
362
+ if (target[bridgeKey] === bridge)
363
+ delete target[bridgeKey];
364
+ if (target[installedKey] === bridge)
365
+ delete target[installedKey];
366
+ bridge.dispose();
367
+ };
368
+ }
369
+ function getDataAppTransport2(frame = window) {
370
+ return frame[installedKey];
371
+ }
372
+
373
+ export { DataAppError2, createHttpTransport2, createMessageClient2, createIframeTransport2, localFrameBridge, installDataAppTransport2, getDataAppTransport2 };
374
+
375
+ //# debugId=CBF217D545192BD464756E2164756E21
@@ -0,0 +1,12 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/client/transport.ts", "../src/client/messages.ts", "../src/client/iframe.ts"],
4
+ "sourcesContent": [
5
+ "import type { TransportResponse } from '@/src/core/bridge';\n\nexport type DataTransport = (\n operation: string,\n input: unknown,\n signal?: AbortSignal\n) => Promise<TransportResponse>;\n\n// A bootstrap and its app may bundle independent copies in the same window.\nconst errorBrand = Symbol.for('@altertable/data-app/DataAppError');\n\nexport class DataAppError extends Error {\n readonly [errorBrand] = true;\n\n static [Symbol.hasInstance](value: unknown): boolean {\n return (\n value instanceof Error &&\n (value as { [errorBrand]?: unknown })[errorBrand] === true\n );\n }\n\n constructor(\n message: string,\n public readonly code: string,\n public readonly requestId?: string\n ) {\n super(message);\n this.name = 'DataAppError';\n }\n}\n\nexport function createHttpTransport({\n endpoint = '/api/data',\n fetch: request = globalThis.fetch,\n}: {\n endpoint?: string;\n fetch?: typeof fetch;\n} = {}): DataTransport {\n async function requestData(\n operation: string,\n input: unknown,\n signal?: AbortSignal\n ): Promise<TransportResponse> {\n const response = await request(\n `${endpoint.replace(/\\/$/, '')}/${encodeURIComponent(operation)}`,\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(input),\n signal,\n }\n );\n try {\n return { status: response.status, body: await response.json() };\n } catch (error) {\n if (signal?.aborted) throw error;\n throw new DataAppError(\n response.ok ? 'The data response was invalid.' : 'Could not load data.',\n response.ok ? 'invalid_response' : 'request_failed'\n );\n }\n }\n\n return requestData;\n}\n",
6
+ "import {\n MessageRoutingError,\n type MessageInput,\n type MessageOutput,\n type MessageRoutes,\n type MessageTransport,\n} from '@/src/core/messages';\n\n/** Both ends validate using the same contracts; the transport owns delivery and cancellation. */\nexport function createMessageClient<const Routes extends MessageRoutes>(\n routes: Routes,\n transport: MessageTransport\n) {\n return {\n async request<\n Name extends keyof Routes & string,\n const Input extends MessageInput<NoInfer<Routes[Name]>>,\n >(\n route: Name,\n payload: Input,\n { signal }: { signal?: AbortSignal } = {}\n ): Promise<MessageOutput<Routes[Name], Input>> {\n signal?.throwIfAborted();\n if (!Object.hasOwn(routes, route))\n throw new MessageRoutingError(\n 'unknown_route',\n 'Unknown message route.'\n );\n const contract = routes[route]!;\n let input: unknown;\n try {\n input = contract.input(payload);\n } catch {\n throw new MessageRoutingError(\n 'invalid_payload',\n 'Invalid message payload.'\n );\n }\n const response = await transport({ route, payload: input }, signal);\n signal?.throwIfAborted();\n try {\n return contract.output(response, input as never) as MessageOutput<\n Routes[Name],\n Input\n >;\n } catch (error) {\n if (error instanceof MessageRoutingError) throw error;\n throw new MessageRoutingError(\n 'invalid_response',\n 'Invalid message response.'\n );\n }\n },\n };\n}\n",
7
+ "import {\n BRIDGE,\n PARENT_PARAM,\n MAX_PENDING,\n REQUEST_TIMEOUT_MS,\n isBridgeMessage,\n validId,\n type BridgeMessage,\n type TransportResponse,\n} from '@/src/core/bridge';\nimport {\n defineDataQueryRoute,\n MessageRoutingError,\n type RoutedMessage,\n} from '@/src/core/messages';\nimport { createMessageClient } from '@/src/client/messages';\nimport { DataAppError } from '@/src/client/transport';\n\ntype Pending = {\n start: () => void;\n reject: (error: unknown) => void;\n resolve: (value: unknown) => void;\n};\n\n/** One bridge per document, shared by all clients and retained across module hot replacement. */\nexport function createIframeTransport({\n parentOrigin,\n window: frame = window,\n timeoutMs = REQUEST_TIMEOUT_MS,\n mode = 'url',\n loadScript,\n}: {\n parentOrigin: string;\n window?: Window;\n timeoutMs?: number;\n mode?: 'url' | 'bundle';\n loadScript?: (javascript: string) => void;\n}) {\n if (new URL(parentOrigin).origin !== parentOrigin)\n throw new Error('Expected an exact parent origin.');\n const documentId = crypto.randomUUID();\n let token: string | undefined;\n let sessionId: string | undefined;\n let disposed = false;\n const pending = new Map<string, Pending>();\n let hostState: unknown;\n const stateListeners = new Set<(state: unknown) => void>();\n\n function send(message: Partial<BridgeMessage>) {\n frame.parent.postMessage(\n { channel: BRIDGE, version: 1, documentId, sessionId, token, ...message },\n parentOrigin\n );\n }\n\n function receiveState(state: unknown) {\n hostState = state;\n for (const listener of stateListeners) listener(state);\n }\n\n function receive(event: MessageEvent) {\n if (\n event.origin !== parentOrigin ||\n event.source !== frame.parent ||\n !isBridgeMessage(event.data)\n )\n return;\n const message = event.data;\n if (message.type === 'connect') {\n if (mode === 'bundle') {\n if (!validId(message.token)) return;\n token = message.token;\n }\n send({ type: 'ready' });\n\n return;\n }\n if (mode === 'bundle' && (!token || message.token !== token)) return;\n if (message.documentId !== documentId) return;\n if (message.type === 'initialize' && validId(message.sessionId)) {\n if (sessionId && sessionId !== message.sessionId) {\n for (const entry of pending.values())\n entry.reject(\n new DataAppError(\n 'The preview reconnected. Retry the request.',\n 'bridge_reset'\n )\n );\n }\n const first = !sessionId;\n sessionId = message.sessionId;\n if (first) for (const entry of pending.values()) entry.start();\n receiveState(message.state);\n if (mode === 'url') send({ type: 'runtime.ready' });\n\n return;\n }\n if (!sessionId || message.sessionId !== sessionId) return;\n if (message.type === 'state') {\n receiveState(message.state);\n\n return;\n }\n if (\n message.type === 'script.load' &&\n mode === 'bundle' &&\n typeof message.javascript === 'string'\n ) {\n loadScript?.(message.javascript);\n\n return;\n }\n if (typeof message.id !== 'string') return;\n const entry = pending.get(message.id);\n if (!entry) return;\n if (message.type === 'result' && 'response' in message) {\n entry.resolve(message.response);\n } else if (\n message.type === 'error' &&\n typeof message.code === 'string' &&\n typeof message.message === 'string'\n ) {\n entry.reject(\n new MessageRoutingError(\n message.code,\n message.message,\n typeof message.requestId === 'string' ? message.requestId : undefined\n )\n );\n }\n }\n\n frame.addEventListener('message', receive);\n\n function requestMessage(\n message: RoutedMessage,\n signal?: AbortSignal\n ): Promise<unknown> {\n return new Promise((resolve, reject) => {\n if (disposed)\n return reject(\n new DataAppError('The preview has closed.', 'bridge_closed')\n );\n if (signal?.aborted) return reject(signal.reason);\n if (pending.size >= MAX_PENDING)\n return reject(\n new DataAppError('Too many pending data requests.', 'bridge_busy')\n );\n const id = crypto.randomUUID();\n let sent = false;\n\n function cleanup() {\n clearTimeout(timer);\n signal?.removeEventListener('abort', abort);\n pending.delete(id);\n }\n\n function abort() {\n if (sent) send({ type: 'cancel', id });\n cleanup();\n reject(signal?.reason);\n }\n\n const timer = setTimeout(() => {\n if (sent) send({ type: 'cancel', id });\n cleanup();\n reject(\n new DataAppError(\n sessionId\n ? 'The data request timed out.'\n : 'The preview shell is unavailable. Reload the preview.',\n sessionId ? 'timeout' : 'bridge_unavailable'\n )\n );\n }, timeoutMs);\n const entry: Pending = {\n start() {\n if (sent) return;\n try {\n send({\n type: 'request',\n id,\n route: message.route,\n payload: message.payload,\n });\n sent = true;\n } catch {\n cleanup();\n reject(\n new DataAppError(\n 'The message payload could not be sent.',\n 'invalid_payload'\n )\n );\n }\n },\n resolve(value) {\n cleanup();\n resolve(value);\n },\n reject(error) {\n cleanup();\n reject(error);\n },\n };\n pending.set(id, entry);\n signal?.addEventListener('abort', abort, { once: true });\n if (sessionId) entry.start();\n else send({ type: 'ready' });\n });\n }\n\n const messages = createMessageClient(\n { 'data.query': defineDataQueryRoute() },\n requestMessage\n );\n\n async function queryData(\n operation: string,\n input: unknown,\n signal?: AbortSignal\n ): Promise<TransportResponse> {\n try {\n return await messages.request(\n 'data.query',\n { operation, input },\n { signal }\n );\n } catch (error) {\n if (error instanceof MessageRoutingError)\n throw new DataAppError(error.message, error.code, error.requestId);\n throw error;\n }\n }\n\n function disconnect() {\n send({ type: 'disconnect' });\n sessionId = undefined;\n for (const entry of pending.values())\n entry.reject(\n new DataAppError('The preview has closed.', 'bridge_closed')\n );\n }\n\n function resume(event: PageTransitionEvent) {\n if (event.persisted) send({ type: 'ready' });\n }\n\n function dispose() {\n if (disposed) return;\n disconnect();\n disposed = true;\n frame.removeEventListener('message', receive);\n frame.removeEventListener('pagehide', disconnect);\n frame.removeEventListener('pageshow', resume);\n stateListeners.clear();\n }\n\n frame.addEventListener('pagehide', disconnect);\n frame.addEventListener('pageshow', resume);\n if (mode === 'url') send({ type: 'ready' });\n\n return {\n request: requestMessage,\n transport: queryData,\n dispose,\n mode,\n snapshot() {\n return hostState;\n },\n subscribe(listener: (state: unknown) => void) {\n stateListeners.add(listener);\n\n return () => {\n stateListeners.delete(listener);\n };\n },\n ready() {\n send({ type: 'runtime.ready' });\n },\n fail() {\n send({ type: 'runtime.error' });\n },\n };\n}\n\nexport type IframeTransport = ReturnType<typeof createIframeTransport>;\n\nconst bridgeKey = Symbol.for('altertable.localFrameBridge');\nconst installedKey = Symbol.for('altertable.installedTransport');\ntype LocalBridge = Pick<\n ReturnType<typeof createIframeTransport>,\n 'transport'\n> & { connect?: () => Promise<ReturnType<typeof createIframeTransport>> };\ntype FrameWindow = Window & {\n [bridgeKey]?: LocalBridge;\n [installedKey]?: ReturnType<typeof createIframeTransport>;\n};\n\n/** The URL opts into local preview; only same-origin server configuration establishes trust. */\nexport function localFrameBridge(window?: Window): LocalBridge | undefined {\n const activeWindow =\n window ??\n (typeof globalThis.window === 'undefined' ? undefined : globalThis.window);\n if (!activeWindow) return undefined;\n const frame = activeWindow as FrameWindow;\n if (frame[bridgeKey]) return frame[bridgeKey];\n const parentOrigin =\n new URL(frame.location.href).searchParams.get(PARENT_PARAM) ?? '';\n if (!parentOrigin) return undefined;\n\n async function connect(parentOrigin: string) {\n try {\n const response = await frame.fetch('/__altertable/parent', {\n signal: AbortSignal.timeout(5000),\n });\n const config = (await response.json()) as { origin?: unknown };\n if (\n !response.ok ||\n config.origin !== parentOrigin ||\n frame.parent === frame\n )\n throw new Error('Invalid local parent configuration.');\n // Another owner can install a trusted bridge while configuration is pending.\n if (frame[installedKey]) return frame[installedKey];\n if (frame[bridgeKey] !== proxy)\n throw new Error('Preview transport owner changed.');\n const bridge = createIframeTransport({ parentOrigin, window: frame });\n frame[bridgeKey] = bridge;\n frame[installedKey] = bridge;\n\n return bridge;\n } catch {\n throw new DataAppError(\n 'The preview shell could not be verified. Open the URL printed by altertable app dev.',\n 'bridge_unavailable'\n );\n }\n }\n\n let connection: Promise<ReturnType<typeof createIframeTransport>> | undefined;\n\n function getConnection() {\n const installed = frame[installedKey];\n if (installed) return Promise.resolve(installed);\n if (!connection) {\n const attempt = connect(parentOrigin);\n connection = attempt;\n // Clear only this failed attempt; retained proxy references can explicitly retry.\n void attempt.catch(() => {\n if (connection === attempt) connection = undefined;\n });\n }\n\n return connection;\n }\n\n const proxy: LocalBridge = {\n async transport(operation, input, signal) {\n signal?.throwIfAborted();\n const attempt = getConnection();\n let abort: (() => void) | undefined;\n try {\n const bridge = signal\n ? await Promise.race([\n attempt,\n new Promise<never>((_, reject) => {\n const requestSignal = signal;\n\n function handleAbort() {\n reject(requestSignal.reason);\n }\n\n abort = handleAbort;\n requestSignal.addEventListener('abort', handleAbort, {\n once: true,\n });\n }),\n ])\n : await attempt;\n signal?.throwIfAborted();\n\n return bridge.transport(operation, input, signal);\n } finally {\n if (abort) signal?.removeEventListener('abort', abort);\n }\n },\n connect: getConnection,\n };\n frame[bridgeKey] = proxy;\n // Static mounting still verifies eagerly, without an unhandled rejection.\n void getConnection().catch(() => {});\n\n return proxy;\n}\n\n/** Install an explicitly trusted transport for hosted apps and their URL-backed controls. */\nexport function installDataAppTransport(\n bridge: ReturnType<typeof createIframeTransport>,\n frame: Window = window\n) {\n const target = frame as FrameWindow;\n if (target[installedKey])\n throw new Error('A data app transport is already installed.');\n target[bridgeKey] = bridge;\n target[installedKey] = bridge;\n\n return () => {\n if (target[bridgeKey] === bridge) delete target[bridgeKey];\n if (target[installedKey] === bridge) delete target[installedKey];\n bridge.dispose();\n };\n}\n\n/** The bootstrap installs this before executing app code. URL apps can install an explicit connection. */\nexport function getDataAppTransport(frame: Window = window) {\n return (frame as FrameWindow)[installedKey];\n}\n"
8
+ ],
9
+ "mappings": "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",
10
+ "debugId": "CBF217D545192BD464756E2164756E21",
11
+ "names": []
12
+ }
@@ -0,0 +1,77 @@
1
+ // src/core/format.ts
2
+ function formatDateRange2({
3
+ start,
4
+ end
5
+ }) {
6
+ const first = new Date(`${start}T00:00:00Z`);
7
+ const last = new Date(`${end}T00:00:00Z`);
8
+ function format(date, options) {
9
+ return new Intl.DateTimeFormat("en-US", {
10
+ ...options,
11
+ timeZone: "UTC"
12
+ }).format(date);
13
+ }
14
+ function formatFullDate(date) {
15
+ return format(date, { month: "short", day: "numeric", year: "numeric" });
16
+ }
17
+ if (start === end)
18
+ return formatFullDate(first);
19
+ if (first.getUTCFullYear() !== last.getUTCFullYear())
20
+ return `${formatFullDate(first)}–${formatFullDate(last)}`;
21
+ const firstMonth = format(first, { month: "short" });
22
+ const lastMonth = format(last, { month: "short" });
23
+ const firstDay = first.getUTCDate();
24
+ const lastDay = last.getUTCDate();
25
+ return firstMonth === lastMonth ? `${firstMonth} ${firstDay}–${lastDay}, ${last.getUTCFullYear()}` : `${firstMonth} ${firstDay}–${lastMonth} ${lastDay}, ${last.getUTCFullYear()}`;
26
+ }
27
+ function formatMetric2(value, format) {
28
+ if (format.kind === "count")
29
+ return formatCount2(value, format);
30
+ if (format.kind === "ratio")
31
+ return formatPercent2(value, format);
32
+ return formatNumber2(value, {
33
+ style: "currency",
34
+ currency: format.currency,
35
+ locale: format.locale
36
+ });
37
+ }
38
+ function formatNumber2(value, options = {}) {
39
+ const { locale = "en-US", missing = "—", ...numberOptions } = options;
40
+ return value === null || value === undefined || !Number.isFinite(value) ? missing : new Intl.NumberFormat(locale, numberOptions).format(Object.is(value, -0) ? 0 : value);
41
+ }
42
+ function formatCount2(value, options = {}) {
43
+ const { compact = false, ...common } = options;
44
+ if (value !== null && value !== undefined && (!Number.isInteger(value) || value < 0)) {
45
+ return common.missing ?? "—";
46
+ }
47
+ return formatNumber2(value, {
48
+ ...common,
49
+ notation: compact ? "compact" : "standard",
50
+ maximumFractionDigits: compact ? 1 : 0
51
+ });
52
+ }
53
+ var pluralRules = new Intl.PluralRules;
54
+ function pluralize2(count, singular, plural = `${singular}s`) {
55
+ return pluralRules.select(count) === "one" ? singular : plural;
56
+ }
57
+ function formatPercent2(ratio, options = {}) {
58
+ if (ratio === null || ratio === undefined || !Number.isFinite(ratio)) {
59
+ return options.missing ?? "—";
60
+ }
61
+ const digits = options.maximumFractionDigits ?? (Math.abs(ratio) < 0.01 ? 2 : 1);
62
+ const percentOptions = {
63
+ locale: options.locale,
64
+ style: "percent",
65
+ maximumFractionDigits: digits
66
+ };
67
+ const smallestVisibleRatio = 10 ** -digits / 100;
68
+ if (ratio !== 0 && Math.round(Math.abs(ratio) / smallestVisibleRatio) === 0) {
69
+ const boundary = formatNumber2(smallestVisibleRatio, percentOptions);
70
+ return ratio > 0 ? `<${boundary}` : `>−${boundary}`;
71
+ }
72
+ return formatNumber2(ratio, percentOptions);
73
+ }
74
+
75
+ export { formatDateRange2, formatMetric2, formatNumber2, formatCount2, pluralize2, formatPercent2 };
76
+
77
+ //# debugId=01AB707F92225BAC64756E2164756E21
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/core/format.ts"],
4
+ "sourcesContent": [
5
+ "/**\n * Date and number formatting with explicit units and missing-value behavior.\n * @module @altertable/data-app/format\n * @see https://github.com/altertable-ai/data-app/blob/main/docs/format.md\n */\ntype CommonOptions = { locale?: string; missing?: string };\n\n/** Compact, unambiguous calendar dates for labels and request status. */\nexport function formatDateRange({\n start,\n end,\n}: {\n start: string;\n end: string;\n}): string {\n const first = new Date(`${start}T00:00:00Z`);\n const last = new Date(`${end}T00:00:00Z`);\n\n function format(date: Date, options: Intl.DateTimeFormatOptions) {\n return new Intl.DateTimeFormat('en-US', {\n ...options,\n timeZone: 'UTC',\n }).format(date);\n }\n\n function formatFullDate(date: Date) {\n return format(date, { month: 'short', day: 'numeric', year: 'numeric' });\n }\n\n if (start === end) return formatFullDate(first);\n if (first.getUTCFullYear() !== last.getUTCFullYear())\n return `${formatFullDate(first)}–${formatFullDate(last)}`;\n const firstMonth = format(first, { month: 'short' });\n const lastMonth = format(last, { month: 'short' });\n const firstDay = first.getUTCDate();\n const lastDay = last.getUTCDate();\n\n return firstMonth === lastMonth\n ? `${firstMonth} ${firstDay}–${lastDay}, ${last.getUTCFullYear()}`\n : `${firstMonth} ${firstDay}–${lastMonth} ${lastDay}, ${last.getUTCFullYear()}`;\n}\n\nexport type MetricFormat =\n | { kind: 'count'; compact?: boolean; locale?: string }\n | { kind: 'ratio'; maximumFractionDigits?: number; locale?: string }\n | { kind: 'currency'; currency: string; locale?: string };\n\nexport function formatMetric(value: number, format: MetricFormat): string {\n if (format.kind === 'count') return formatCount(value, format);\n if (format.kind === 'ratio') return formatPercent(value, format);\n\n return formatNumber(value, {\n style: 'currency',\n currency: format.currency,\n locale: format.locale,\n });\n}\n\n/** Missing and non-finite values use the `missing` label; negative zero renders as zero. */\nexport function formatNumber(\n value: number | null | undefined,\n options: CommonOptions & Intl.NumberFormatOptions = {}\n): string {\n const { locale = 'en-US', missing = '—', ...numberOptions } = options;\n\n return value === null || value === undefined || !Number.isFinite(value)\n ? missing\n : new Intl.NumberFormat(locale, numberOptions).format(\n Object.is(value, -0) ? 0 : value\n );\n}\n\n/** Negative and fractional counts render as missing. Compact notation is opt-in. */\nexport function formatCount(\n value: number | null | undefined,\n options: CommonOptions & { compact?: boolean } = {}\n): string {\n const { compact = false, ...common } = options;\n if (\n value !== null &&\n value !== undefined &&\n (!Number.isInteger(value) || value < 0)\n ) {\n return common.missing ?? '—';\n }\n\n return formatNumber(value, {\n ...common,\n notation: compact ? 'compact' : 'standard',\n maximumFractionDigits: compact ? 1 : 0,\n });\n}\n\nconst pluralRules = new Intl.PluralRules();\n\nexport function pluralize(\n count: number,\n singular: string,\n plural = `${singular}s`\n): string {\n return pluralRules.select(count) === 'one' ? singular : plural;\n}\n\n/** Accepts a ratio (0.116), not a percentage (11.6). */\nexport function formatPercent(\n ratio: number | null | undefined,\n options: CommonOptions & { maximumFractionDigits?: number } = {}\n): string {\n if (ratio === null || ratio === undefined || !Number.isFinite(ratio)) {\n return options.missing ?? '—';\n }\n const digits =\n options.maximumFractionDigits ?? (Math.abs(ratio) < 0.01 ? 2 : 1);\n const percentOptions = {\n locale: options.locale,\n style: 'percent' as const,\n maximumFractionDigits: digits,\n };\n const smallestVisibleRatio = 10 ** -digits / 100;\n if (ratio !== 0 && Math.round(Math.abs(ratio) / smallestVisibleRatio) === 0) {\n const boundary = formatNumber(smallestVisibleRatio, percentOptions);\n\n return ratio > 0 ? `<${boundary}` : `>−${boundary}`;\n }\n\n return formatNumber(ratio, percentOptions);\n}\n"
6
+ ],
7
+ "mappings": "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",
8
+ "debugId": "01AB707F92225BAC64756E2164756E21",
9
+ "names": []
10
+ }