@databricks/appkit-ui 0.0.2
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/CLAUDE.md +3 -0
- package/DCO +25 -0
- package/LICENSE +203 -0
- package/NOTICE.md +73 -0
- package/README.md +35 -0
- package/bin/setup-claude.js +190 -0
- package/dist/js/arrow/arrow-client.d.ts +64 -0
- package/dist/js/arrow/arrow-client.d.ts.map +1 -0
- package/dist/js/arrow/arrow-client.js +181 -0
- package/dist/js/arrow/arrow-client.js.map +1 -0
- package/dist/js/arrow/index.js +3 -0
- package/dist/js/arrow/lazy-arrow.d.ts +23 -0
- package/dist/js/arrow/lazy-arrow.d.ts.map +1 -0
- package/dist/js/arrow/lazy-arrow.js +86 -0
- package/dist/js/arrow/lazy-arrow.js.map +1 -0
- package/dist/js/constants.d.ts +10 -0
- package/dist/js/constants.d.ts.map +1 -0
- package/dist/js/constants.js +30 -0
- package/dist/js/constants.js.map +1 -0
- package/dist/js/index.d.ts +8 -0
- package/dist/js/index.js +8 -0
- package/dist/js/sse/connect-sse.d.ts +14 -0
- package/dist/js/sse/connect-sse.d.ts.map +1 -0
- package/dist/js/sse/connect-sse.js +128 -0
- package/dist/js/sse/connect-sse.js.map +1 -0
- package/dist/js/sse/types.d.ts +34 -0
- package/dist/js/sse/types.d.ts.map +1 -0
- package/dist/react/charts/area/index.d.ts +33 -0
- package/dist/react/charts/area/index.d.ts.map +1 -0
- package/dist/react/charts/area/index.js +29 -0
- package/dist/react/charts/area/index.js.map +1 -0
- package/dist/react/charts/bar/index.d.ts +43 -0
- package/dist/react/charts/bar/index.d.ts.map +1 -0
- package/dist/react/charts/bar/index.js +39 -0
- package/dist/react/charts/bar/index.js.map +1 -0
- package/dist/react/charts/base.d.ts +89 -0
- package/dist/react/charts/base.d.ts.map +1 -0
- package/dist/react/charts/base.js +123 -0
- package/dist/react/charts/base.js.map +1 -0
- package/dist/react/charts/chart-error-boundary.js +37 -0
- package/dist/react/charts/chart-error-boundary.js.map +1 -0
- package/dist/react/charts/constants.d.ts +22 -0
- package/dist/react/charts/constants.d.ts.map +1 -0
- package/dist/react/charts/constants.js +86 -0
- package/dist/react/charts/constants.js.map +1 -0
- package/dist/react/charts/create-chart.d.ts +26 -0
- package/dist/react/charts/create-chart.d.ts.map +1 -0
- package/dist/react/charts/create-chart.js +55 -0
- package/dist/react/charts/create-chart.js.map +1 -0
- package/dist/react/charts/empty.js +16 -0
- package/dist/react/charts/empty.js.map +1 -0
- package/dist/react/charts/error.js +16 -0
- package/dist/react/charts/error.js.map +1 -0
- package/dist/react/charts/heatmap/index.d.ts +42 -0
- package/dist/react/charts/heatmap/index.d.ts.map +1 -0
- package/dist/react/charts/heatmap/index.js +38 -0
- package/dist/react/charts/heatmap/index.js.map +1 -0
- package/dist/react/charts/index.js +18 -0
- package/dist/react/charts/line/index.d.ts +34 -0
- package/dist/react/charts/line/index.d.ts.map +1 -0
- package/dist/react/charts/line/index.js +30 -0
- package/dist/react/charts/line/index.js.map +1 -0
- package/dist/react/charts/loading.js +13 -0
- package/dist/react/charts/loading.js.map +1 -0
- package/dist/react/charts/normalize.d.ts +37 -0
- package/dist/react/charts/normalize.d.ts.map +1 -0
- package/dist/react/charts/normalize.js +256 -0
- package/dist/react/charts/normalize.js.map +1 -0
- package/dist/react/charts/options.d.ts +39 -0
- package/dist/react/charts/options.d.ts.map +1 -0
- package/dist/react/charts/options.js +212 -0
- package/dist/react/charts/options.js.map +1 -0
- package/dist/react/charts/pie/index.d.ts +57 -0
- package/dist/react/charts/pie/index.d.ts.map +1 -0
- package/dist/react/charts/pie/index.js +50 -0
- package/dist/react/charts/pie/index.js.map +1 -0
- package/dist/react/charts/radar/index.d.ts +32 -0
- package/dist/react/charts/radar/index.d.ts.map +1 -0
- package/dist/react/charts/radar/index.js +28 -0
- package/dist/react/charts/radar/index.js.map +1 -0
- package/dist/react/charts/scatter/index.d.ts +32 -0
- package/dist/react/charts/scatter/index.d.ts.map +1 -0
- package/dist/react/charts/scatter/index.js +28 -0
- package/dist/react/charts/scatter/index.js.map +1 -0
- package/dist/react/charts/theme.d.ts +23 -0
- package/dist/react/charts/theme.d.ts.map +1 -0
- package/dist/react/charts/theme.js +96 -0
- package/dist/react/charts/theme.js.map +1 -0
- package/dist/react/charts/types.d.ts +160 -0
- package/dist/react/charts/types.d.ts.map +1 -0
- package/dist/react/charts/types.js +17 -0
- package/dist/react/charts/types.js.map +1 -0
- package/dist/react/charts/utils.d.ts +36 -0
- package/dist/react/charts/utils.d.ts.map +1 -0
- package/dist/react/charts/utils.js +77 -0
- package/dist/react/charts/utils.js.map +1 -0
- package/dist/react/charts/wrapper.d.ts +65 -0
- package/dist/react/charts/wrapper.d.ts.map +1 -0
- package/dist/react/charts/wrapper.js +94 -0
- package/dist/react/charts/wrapper.js.map +1 -0
- package/dist/react/hooks/index.js +2 -0
- package/dist/react/hooks/types.d.ts +101 -0
- package/dist/react/hooks/types.d.ts.map +1 -0
- package/dist/react/hooks/use-analytics-query.d.ts +33 -0
- package/dist/react/hooks/use-analytics-query.d.ts.map +1 -0
- package/dist/react/hooks/use-analytics-query.js +146 -0
- package/dist/react/hooks/use-analytics-query.js.map +1 -0
- package/dist/react/hooks/use-chart-data.d.ts +54 -0
- package/dist/react/hooks/use-chart-data.d.ts.map +1 -0
- package/dist/react/hooks/use-chart-data.js +80 -0
- package/dist/react/hooks/use-chart-data.js.map +1 -0
- package/dist/react/hooks/use-mobile.js +21 -0
- package/dist/react/hooks/use-mobile.js.map +1 -0
- package/dist/react/hooks/use-query-hmr.js +19 -0
- package/dist/react/hooks/use-query-hmr.js.map +1 -0
- package/dist/react/index.d.ts +75 -0
- package/dist/react/index.js +79 -0
- package/dist/react/lib/format.js +42 -0
- package/dist/react/lib/format.js.map +1 -0
- package/dist/react/lib/utils.js +11 -0
- package/dist/react/lib/utils.js.map +1 -0
- package/dist/react/table/data-table.d.ts +47 -0
- package/dist/react/table/data-table.d.ts.map +1 -0
- package/dist/react/table/data-table.js +205 -0
- package/dist/react/table/data-table.js.map +1 -0
- package/dist/react/table/empty.js +16 -0
- package/dist/react/table/empty.js.map +1 -0
- package/dist/react/table/error.js +16 -0
- package/dist/react/table/error.js.map +1 -0
- package/dist/react/table/index.js +1 -0
- package/dist/react/table/loading.js +50 -0
- package/dist/react/table/loading.js.map +1 -0
- package/dist/react/table/table-wrapper.js +143 -0
- package/dist/react/table/table-wrapper.js.map +1 -0
- package/dist/react/table/types.d.ts +55 -0
- package/dist/react/table/types.d.ts.map +1 -0
- package/dist/react/ui/accordion.d.ts +25 -0
- package/dist/react/ui/accordion.d.ts.map +1 -0
- package/dist/react/ui/accordion.js +45 -0
- package/dist/react/ui/accordion.js.map +1 -0
- package/dist/react/ui/alert-dialog.d.ts +49 -0
- package/dist/react/ui/alert-dialog.d.ts.map +1 -0
- package/dist/react/ui/alert-dialog.js +82 -0
- package/dist/react/ui/alert-dialog.js.map +1 -0
- package/dist/react/ui/alert.d.ts +25 -0
- package/dist/react/ui/alert.d.ts.map +1 -0
- package/dist/react/ui/alert.js +38 -0
- package/dist/react/ui/alert.js.map +1 -0
- package/dist/react/ui/aspect-ratio.d.ts +10 -0
- package/dist/react/ui/aspect-ratio.d.ts.map +1 -0
- package/dist/react/ui/aspect-ratio.js +16 -0
- package/dist/react/ui/aspect-ratio.js.map +1 -0
- package/dist/react/ui/avatar.d.ts +20 -0
- package/dist/react/ui/avatar.d.ts.map +1 -0
- package/dist/react/ui/avatar.js +30 -0
- package/dist/react/ui/avatar.js.map +1 -0
- package/dist/react/ui/badge.d.ts +20 -0
- package/dist/react/ui/badge.d.ts.map +1 -0
- package/dist/react/ui/badge.js +26 -0
- package/dist/react/ui/badge.js.map +1 -0
- package/dist/react/ui/breadcrumb.d.ts +38 -0
- package/dist/react/ui/breadcrumb.d.ts.map +1 -0
- package/dist/react/ui/breadcrumb.js +71 -0
- package/dist/react/ui/breadcrumb.js.map +1 -0
- package/dist/react/ui/button-group.d.ts +29 -0
- package/dist/react/ui/button-group.d.ts.map +1 -0
- package/dist/react/ui/button-group.js +41 -0
- package/dist/react/ui/button-group.js.map +1 -0
- package/dist/react/ui/button.d.ts +22 -0
- package/dist/react/ui/button.d.ts.map +1 -0
- package/dist/react/ui/button.js +45 -0
- package/dist/react/ui/button.js.map +1 -0
- package/dist/react/ui/calendar.d.ts +27 -0
- package/dist/react/ui/calendar.d.ts.map +1 -0
- package/dist/react/ui/calendar.js +109 -0
- package/dist/react/ui/calendar.js.map +1 -0
- package/dist/react/ui/card.d.ts +35 -0
- package/dist/react/ui/card.d.ts.map +1 -0
- package/dist/react/ui/card.js +57 -0
- package/dist/react/ui/card.js.map +1 -0
- package/dist/react/ui/carousel.d.ts +48 -0
- package/dist/react/ui/carousel.d.ts.map +1 -0
- package/dist/react/ui/carousel.js +134 -0
- package/dist/react/ui/carousel.js.map +1 -0
- package/dist/react/ui/chart.d.ts +80 -0
- package/dist/react/ui/chart.d.ts.map +1 -0
- package/dist/react/ui/chart.js +143 -0
- package/dist/react/ui/chart.js.map +1 -0
- package/dist/react/ui/checkbox.d.ts +12 -0
- package/dist/react/ui/checkbox.d.ts.map +1 -0
- package/dist/react/ui/checkbox.js +24 -0
- package/dist/react/ui/checkbox.js.map +1 -0
- package/dist/react/ui/collapsible.d.ts +16 -0
- package/dist/react/ui/collapsible.d.ts.map +1 -0
- package/dist/react/ui/collapsible.js +26 -0
- package/dist/react/ui/collapsible.js.map +1 -0
- package/dist/react/ui/command.d.ts +53 -0
- package/dist/react/ui/command.d.ts.map +1 -0
- package/dist/react/ui/command.js +87 -0
- package/dist/react/ui/command.js.map +1 -0
- package/dist/react/ui/context-menu.d.ts +77 -0
- package/dist/react/ui/context-menu.d.ts.map +1 -0
- package/dist/react/ui/context-menu.js +125 -0
- package/dist/react/ui/context-menu.js.map +1 -0
- package/dist/react/ui/dialog.d.ts +48 -0
- package/dist/react/ui/dialog.d.ts.map +1 -0
- package/dist/react/ui/dialog.js +87 -0
- package/dist/react/ui/dialog.js.map +1 -0
- package/dist/react/ui/drawer.d.ts +45 -0
- package/dist/react/ui/drawer.d.ts.map +1 -0
- package/dist/react/ui/drawer.js +81 -0
- package/dist/react/ui/drawer.js.map +1 -0
- package/dist/react/ui/dropdown-menu.d.ts +78 -0
- package/dist/react/ui/dropdown-menu.d.ts.map +1 -0
- package/dist/react/ui/dropdown-menu.js +124 -0
- package/dist/react/ui/dropdown-menu.js.map +1 -0
- package/dist/react/ui/empty.d.ts +36 -0
- package/dist/react/ui/empty.d.ts.map +1 -0
- package/dist/react/ui/empty.js +62 -0
- package/dist/react/ui/empty.js.map +1 -0
- package/dist/react/ui/field.d.ts +65 -0
- package/dist/react/ui/field.d.ts.map +1 -0
- package/dist/react/ui/field.js +120 -0
- package/dist/react/ui/field.js.map +1 -0
- package/dist/react/ui/form.d.ts +46 -0
- package/dist/react/ui/form.d.ts.map +1 -0
- package/dist/react/ui/form.js +92 -0
- package/dist/react/ui/form.js.map +1 -0
- package/dist/react/ui/hover-card.d.ts +20 -0
- package/dist/react/ui/hover-card.d.ts.map +1 -0
- package/dist/react/ui/hover-card.js +35 -0
- package/dist/react/ui/hover-card.js.map +1 -0
- package/dist/react/ui/index.js +53 -0
- package/dist/react/ui/input-group.d.ts +44 -0
- package/dist/react/ui/input-group.d.ts.map +1 -0
- package/dist/react/ui/input-group.js +82 -0
- package/dist/react/ui/input-group.js.map +1 -0
- package/dist/react/ui/input-otp.d.ts +29 -0
- package/dist/react/ui/input-otp.d.ts.map +1 -0
- package/dist/react/ui/input-otp.js +47 -0
- package/dist/react/ui/input-otp.js.map +1 -0
- package/dist/react/ui/input.d.ts +12 -0
- package/dist/react/ui/input.d.ts.map +1 -0
- package/dist/react/ui/input.js +16 -0
- package/dist/react/ui/input.js.map +1 -0
- package/dist/react/ui/item.d.ts +63 -0
- package/dist/react/ui/item.d.ts.map +1 -0
- package/dist/react/ui/item.js +118 -0
- package/dist/react/ui/item.js.map +1 -0
- package/dist/react/ui/kbd.d.ts +14 -0
- package/dist/react/ui/kbd.d.ts.map +1 -0
- package/dist/react/ui/kbd.js +22 -0
- package/dist/react/ui/kbd.js.map +1 -0
- package/dist/react/ui/label.d.ts +12 -0
- package/dist/react/ui/label.d.ts.map +1 -0
- package/dist/react/ui/label.js +18 -0
- package/dist/react/ui/label.js.map +1 -0
- package/dist/react/ui/menubar.d.ts +85 -0
- package/dist/react/ui/menubar.d.ts.map +1 -0
- package/dist/react/ui/menubar.js +134 -0
- package/dist/react/ui/menubar.js.map +1 -0
- package/dist/react/ui/navigation-menu.d.ts +47 -0
- package/dist/react/ui/navigation-menu.d.ts.map +1 -0
- package/dist/react/ui/navigation-menu.js +82 -0
- package/dist/react/ui/navigation-menu.js.map +1 -0
- package/dist/react/ui/pagination.d.ts +40 -0
- package/dist/react/ui/pagination.d.ts.map +1 -0
- package/dist/react/ui/pagination.js +80 -0
- package/dist/react/ui/pagination.js.map +1 -0
- package/dist/react/ui/popover.d.ts +23 -0
- package/dist/react/ui/popover.d.ts.map +1 -0
- package/dist/react/ui/popover.js +38 -0
- package/dist/react/ui/popover.js.map +1 -0
- package/dist/react/ui/progress.d.ts +13 -0
- package/dist/react/ui/progress.d.ts.map +1 -0
- package/dist/react/ui/progress.js +21 -0
- package/dist/react/ui/progress.js.map +1 -0
- package/dist/react/ui/radio-group.d.ts +16 -0
- package/dist/react/ui/radio-group.d.ts.map +1 -0
- package/dist/react/ui/radio-group.js +31 -0
- package/dist/react/ui/radio-group.js.map +1 -0
- package/dist/react/ui/resizable.d.ts +22 -0
- package/dist/react/ui/resizable.d.ts.map +1 -0
- package/dist/react/ui/resizable.js +34 -0
- package/dist/react/ui/resizable.js.map +1 -0
- package/dist/react/ui/scroll-area.d.ts +18 -0
- package/dist/react/ui/scroll-area.d.ts.map +1 -0
- package/dist/react/ui/scroll-area.js +39 -0
- package/dist/react/ui/scroll-area.js.map +1 -0
- package/dist/react/ui/select.d.ts +53 -0
- package/dist/react/ui/select.d.ts.map +1 -0
- package/dist/react/ui/select.js +98 -0
- package/dist/react/ui/select.js.map +1 -0
- package/dist/react/ui/separator.d.ts +14 -0
- package/dist/react/ui/separator.d.ts.map +1 -0
- package/dist/react/ui/separator.js +20 -0
- package/dist/react/ui/separator.js.map +1 -0
- package/dist/react/ui/sheet.d.ts +41 -0
- package/dist/react/ui/sheet.d.ts.map +1 -0
- package/dist/react/ui/sheet.js +83 -0
- package/dist/react/ui/sheet.js.map +1 -0
- package/dist/react/ui/sidebar.d.ts +167 -0
- package/dist/react/ui/sidebar.d.ts.map +1 -0
- package/dist/react/ui/sidebar.js +379 -0
- package/dist/react/ui/sidebar.js.map +1 -0
- package/dist/react/ui/skeleton.d.ts +10 -0
- package/dist/react/ui/skeleton.d.ts.map +1 -0
- package/dist/react/ui/skeleton.js +15 -0
- package/dist/react/ui/skeleton.js.map +1 -0
- package/dist/react/ui/slider.d.ts +16 -0
- package/dist/react/ui/slider.d.ts.map +1 -0
- package/dist/react/ui/slider.js +40 -0
- package/dist/react/ui/slider.js.map +1 -0
- package/dist/react/ui/sonner.d.ts +10 -0
- package/dist/react/ui/sonner.d.ts.map +1 -0
- package/dist/react/ui/sonner.js +31 -0
- package/dist/react/ui/sonner.js.map +1 -0
- package/dist/react/ui/spinner.d.ts +10 -0
- package/dist/react/ui/spinner.d.ts.map +1 -0
- package/dist/react/ui/spinner.js +17 -0
- package/dist/react/ui/spinner.js.map +1 -0
- package/dist/react/ui/switch.d.ts +12 -0
- package/dist/react/ui/switch.d.ts.map +1 -0
- package/dist/react/ui/switch.js +22 -0
- package/dist/react/ui/switch.js.map +1 -0
- package/dist/react/ui/table.d.ts +39 -0
- package/dist/react/ui/table.d.ts.map +1 -0
- package/dist/react/ui/table.js +68 -0
- package/dist/react/ui/table.js.map +1 -0
- package/dist/react/ui/tabs.d.ts +24 -0
- package/dist/react/ui/tabs.d.ts.map +1 -0
- package/dist/react/ui/tabs.js +39 -0
- package/dist/react/ui/tabs.js.map +1 -0
- package/dist/react/ui/textarea.d.ts +11 -0
- package/dist/react/ui/textarea.d.ts.map +1 -0
- package/dist/react/ui/textarea.js +15 -0
- package/dist/react/ui/textarea.js.map +1 -0
- package/dist/react/ui/toggle-group.d.ts +27 -0
- package/dist/react/ui/toggle-group.d.ts.map +1 -0
- package/dist/react/ui/toggle-group.js +50 -0
- package/dist/react/ui/toggle-group.js.map +1 -0
- package/dist/react/ui/toggle.d.ts +20 -0
- package/dist/react/ui/toggle.d.ts.map +1 -0
- package/dist/react/ui/toggle.js +38 -0
- package/dist/react/ui/toggle.js.map +1 -0
- package/dist/react/ui/tooltip.d.ts +24 -0
- package/dist/react/ui/tooltip.d.ts.map +1 -0
- package/dist/react/ui/tooltip.js +39 -0
- package/dist/react/ui/tooltip.js.map +1 -0
- package/dist/shared/src/sql/helpers.d.ts +160 -0
- package/dist/shared/src/sql/helpers.d.ts.map +1 -0
- package/dist/shared/src/sql/helpers.js +103 -0
- package/dist/shared/src/sql/helpers.js.map +1 -0
- package/dist/shared/src/sql/types.d.ts +34 -0
- package/dist/shared/src/sql/types.d.ts.map +1 -0
- package/dist/styles.css +425 -0
- package/llms.txt +193 -0
- package/package.json +98 -0
- package/scripts/postinstall.js +6 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { DATE_FIELD_PATTERNS, METADATA_DATE_PATTERNS, NAME_FIELD_PATTERNS } from "../constants.js";
|
|
2
|
+
import { getArrowModule, getDecimalTypeId, getTypeIdSets, initializeTypeIdSets } from "./lazy-arrow.js";
|
|
3
|
+
|
|
4
|
+
//#region src/js/arrow/arrow-client.ts
|
|
5
|
+
var ArrowClient = class ArrowClient {
|
|
6
|
+
/**
|
|
7
|
+
* Processes an Arrow IPC buffer into a Table.
|
|
8
|
+
* Lazily loads the Apache Arrow library on first use.
|
|
9
|
+
*
|
|
10
|
+
* @param buffer - The Arrow IPC format buffer
|
|
11
|
+
* @returns Promise resolving to an Arrow Table
|
|
12
|
+
*/
|
|
13
|
+
static async processArrowBuffer(buffer) {
|
|
14
|
+
try {
|
|
15
|
+
const arrow = await getArrowModule();
|
|
16
|
+
await initializeTypeIdSets();
|
|
17
|
+
return arrow.tableFromIPC(buffer);
|
|
18
|
+
} catch (error) {
|
|
19
|
+
throw new Error(`Failed to process Arrow buffer: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
static async fetchAndProcessArrow(url, headers) {
|
|
23
|
+
try {
|
|
24
|
+
const buffer = await ArrowClient.fetchArrow(url, headers);
|
|
25
|
+
return ArrowClient.processArrowBuffer(buffer);
|
|
26
|
+
} catch (error) {
|
|
27
|
+
throw new Error(`Failed to fetch Arrow data: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
static extractArrowFields(table) {
|
|
31
|
+
return table.schema.fields.map((field) => {
|
|
32
|
+
return {
|
|
33
|
+
name: field.name,
|
|
34
|
+
type: field.type
|
|
35
|
+
};
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
static extractArrowColumns(table) {
|
|
39
|
+
const cols = {};
|
|
40
|
+
for (const field of table.schema.fields) {
|
|
41
|
+
const child = table.getChild(field.name);
|
|
42
|
+
if (child) cols[field.name] = child.toArray();
|
|
43
|
+
}
|
|
44
|
+
return cols;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Extracts chart data from Arrow table.
|
|
48
|
+
* Uses get(i) to properly handle complex types like Decimal128.
|
|
49
|
+
* Applies decimal scaling for DECIMAL types.
|
|
50
|
+
*
|
|
51
|
+
* Note: This method assumes Arrow has been loaded (via processArrowBuffer).
|
|
52
|
+
*
|
|
53
|
+
* @returns xData for axis, yDataMap for series data
|
|
54
|
+
*/
|
|
55
|
+
static extractChartData(table, xKey, yKeys) {
|
|
56
|
+
if (table.numRows === 0) return EMPTY_RESULT;
|
|
57
|
+
const decimalType = getDecimalTypeId();
|
|
58
|
+
const decimalDivisors = /* @__PURE__ */ new Map();
|
|
59
|
+
for (const field of table.schema.fields) if (field.typeId === decimalType) {
|
|
60
|
+
const decType = field.type;
|
|
61
|
+
if (typeof decType.scale === "number") decimalDivisors.set(field.name, 10 ** decType.scale);
|
|
62
|
+
}
|
|
63
|
+
const xData = extractColumnValues(table.getChild(xKey), decimalDivisors.get(xKey));
|
|
64
|
+
const yDataMap = {};
|
|
65
|
+
for (let i = 0; i < yKeys.length; i++) {
|
|
66
|
+
const key = yKeys[i];
|
|
67
|
+
yDataMap[key] = extractColumnValues(table.getChild(key), decimalDivisors.get(key));
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
xData,
|
|
71
|
+
yDataMap
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Automatically detect which fields to use for chart axes from an Arrow table
|
|
76
|
+
* Uses the schema's type information for accurate field detection
|
|
77
|
+
*
|
|
78
|
+
* Note: This method assumes Arrow has been loaded (via processArrowBuffer).
|
|
79
|
+
*
|
|
80
|
+
* @param table - Arrow Table to analyze
|
|
81
|
+
* @param orientation - Chart orientation ("vertical" for time-series, "horizontal" for categorical)
|
|
82
|
+
* @returns Object containing the detected fields
|
|
83
|
+
* @example
|
|
84
|
+
* // Time-series data
|
|
85
|
+
* detectFieldsFromArrow(timeSeriesTable)
|
|
86
|
+
* // { xField: "date", yFields: ["revenue", "cost"], chartType: "timeseries" }
|
|
87
|
+
*
|
|
88
|
+
* // Categorical data
|
|
89
|
+
* detectFieldsFromArrow(categoricalTable)
|
|
90
|
+
* // { xField: "app_name", yFields: ["totalSpend"], chartType: "categorical" }
|
|
91
|
+
*/
|
|
92
|
+
static detectFieldsFromArrow(table, orientation) {
|
|
93
|
+
const fields = table.schema.fields;
|
|
94
|
+
if (fields.length === 0) return {
|
|
95
|
+
xField: "x",
|
|
96
|
+
yFields: ["y"],
|
|
97
|
+
chartType: "categorical"
|
|
98
|
+
};
|
|
99
|
+
const fieldNames = fields.map((f) => f.name);
|
|
100
|
+
const typeIdSets = getTypeIdSets();
|
|
101
|
+
const temporalFields = [];
|
|
102
|
+
const numericFields = [];
|
|
103
|
+
const stringFields = [];
|
|
104
|
+
for (const field of fields) {
|
|
105
|
+
const typeId = field.typeId;
|
|
106
|
+
if (typeIdSets.temporal.has(typeId)) temporalFields.push(field.name);
|
|
107
|
+
else if (typeIdSets.numeric.has(typeId)) numericFields.push(field.name);
|
|
108
|
+
else if (typeIdSets.string.has(typeId)) stringFields.push(field.name);
|
|
109
|
+
}
|
|
110
|
+
let nameFields = stringFields.filter((name) => NAME_FIELD_PATTERNS.some((pattern) => name.toLowerCase().includes(pattern)));
|
|
111
|
+
if (nameFields.length === 0) nameFields = stringFields.filter((name) => !name.toLowerCase().endsWith("_id"));
|
|
112
|
+
const chartDateFields = temporalFields.filter((name) => !METADATA_DATE_PATTERNS.some((pattern) => name.toLowerCase().includes(pattern)));
|
|
113
|
+
const metadataDateFields = temporalFields.filter((name) => METADATA_DATE_PATTERNS.some((pattern) => name.toLowerCase().includes(pattern)));
|
|
114
|
+
const stringDateFields = stringFields.filter((name) => DATE_FIELD_PATTERNS.some((pattern) => name.toLowerCase().includes(pattern)) && !METADATA_DATE_PATTERNS.some((pattern) => name.toLowerCase().includes(pattern)));
|
|
115
|
+
const primaryDateFields = [...chartDateFields, ...stringDateFields];
|
|
116
|
+
const isTimeSeries = primaryDateFields.length > 0 && orientation !== "horizontal";
|
|
117
|
+
const isCategorical = nameFields.length > 0 && (primaryDateFields.length === 0 || orientation === "horizontal");
|
|
118
|
+
if (orientation === "horizontal" || isCategorical) {
|
|
119
|
+
const xField$1 = nameFields[0] || primaryDateFields[0] || metadataDateFields[0] || fieldNames[0];
|
|
120
|
+
return {
|
|
121
|
+
xField: xField$1,
|
|
122
|
+
yFields: numericFields.length > 0 ? numericFields : fieldNames.filter((k) => k !== xField$1),
|
|
123
|
+
chartType: "categorical"
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
const xField = primaryDateFields[0] || metadataDateFields[0] || nameFields[0] || fieldNames[0];
|
|
127
|
+
return {
|
|
128
|
+
xField,
|
|
129
|
+
yFields: numericFields.length > 0 ? numericFields : fieldNames.filter((k) => k !== xField),
|
|
130
|
+
chartType: isTimeSeries ? "timeseries" : "categorical"
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
static async fetchArrow(url, headers) {
|
|
134
|
+
try {
|
|
135
|
+
const response = await fetch(url, { headers: {
|
|
136
|
+
"Content-Type": "application/octet-stream",
|
|
137
|
+
...headers
|
|
138
|
+
} });
|
|
139
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
140
|
+
const buffer = await response.arrayBuffer();
|
|
141
|
+
return new Uint8Array(buffer);
|
|
142
|
+
} catch (error) {
|
|
143
|
+
throw new Error(`Failed to fetch Arrow data: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
const EMPTY_RESULT = {
|
|
148
|
+
xData: [],
|
|
149
|
+
yDataMap: {}
|
|
150
|
+
};
|
|
151
|
+
/**
|
|
152
|
+
* Extracts values from an Arrow Vector properly.
|
|
153
|
+
* Uses get(i) to handle complex types like Decimal128 correctly.
|
|
154
|
+
* toArray() doesn't work properly for Decimal types - it returns raw bytes.
|
|
155
|
+
*
|
|
156
|
+
* @param col - The Arrow column/vector
|
|
157
|
+
* @param divisor - Pre-computed divisor for DECIMAL types (10^scale)
|
|
158
|
+
*/
|
|
159
|
+
function extractColumnValues(col, divisor) {
|
|
160
|
+
if (!col) return [];
|
|
161
|
+
const len = col.length;
|
|
162
|
+
const result = new Array(len);
|
|
163
|
+
for (let i = 0; i < len; i++) {
|
|
164
|
+
const val = col.get(i);
|
|
165
|
+
if (val === null || val === void 0) result[i] = 0;
|
|
166
|
+
else if (typeof val === "bigint") {
|
|
167
|
+
const num = Number(val);
|
|
168
|
+
result[i] = divisor !== void 0 ? num / divisor : num;
|
|
169
|
+
} else if (typeof val === "number") result[i] = divisor !== void 0 ? val / divisor : val;
|
|
170
|
+
else if (typeof val === "string") result[i] = val;
|
|
171
|
+
else {
|
|
172
|
+
const num = Number(val);
|
|
173
|
+
result[i] = divisor !== void 0 ? num / divisor : num;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return result;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
//#endregion
|
|
180
|
+
export { ArrowClient };
|
|
181
|
+
//# sourceMappingURL=arrow-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"arrow-client.js","names":["cols: Record<string, any>","yDataMap: Record<string, (string | number)[]>","temporalFields: string[]","numericFields: string[]","stringFields: string[]","xField","EMPTY_RESULT: {\n xData: (string | number)[];\n yDataMap: Record<string, (string | number)[]>;\n}","result: (string | number)[]"],"sources":["../../../src/js/arrow/arrow-client.ts"],"sourcesContent":["import type { Field, Table } from \"apache-arrow\";\nimport {\n DATE_FIELD_PATTERNS,\n METADATA_DATE_PATTERNS,\n NAME_FIELD_PATTERNS,\n} from \"../constants\";\nimport {\n getArrowModule,\n initializeTypeIdSets,\n getTypeIdSets,\n getDecimalTypeId,\n} from \"./lazy-arrow\";\n\n// Re-export for backward compatibility\nexport { DATE_FIELD_PATTERNS, NAME_FIELD_PATTERNS };\n\n// Re-export Table type for consumers\nexport type { Table, Field };\n\nexport class ArrowClient {\n /**\n * Processes an Arrow IPC buffer into a Table.\n * Lazily loads the Apache Arrow library on first use.\n *\n * @param buffer - The Arrow IPC format buffer\n * @returns Promise resolving to an Arrow Table\n */\n static async processArrowBuffer(buffer: Uint8Array): Promise<Table> {\n try {\n const arrow = await getArrowModule();\n // Initialize type ID sets now that Arrow is loaded\n await initializeTypeIdSets();\n return arrow.tableFromIPC(buffer);\n } catch (error) {\n throw new Error(\n `Failed to process Arrow buffer: ${\n error instanceof Error ? error.message : \"Unknown error\"\n }`,\n );\n }\n }\n\n static async fetchAndProcessArrow(\n url: string,\n headers?: Record<string, string>,\n ): Promise<Table> {\n try {\n const buffer = await ArrowClient.fetchArrow(url, headers);\n\n return ArrowClient.processArrowBuffer(buffer);\n } catch (error) {\n throw new Error(\n `Failed to fetch Arrow data: ${\n error instanceof Error ? error.message : \"Unknown error\"\n }`,\n );\n }\n }\n\n static extractArrowFields(table: Table) {\n return table.schema.fields.map((field: Field) => {\n return {\n name: field.name,\n type: field.type,\n };\n });\n }\n\n static extractArrowColumns(table: Table): Record<string, any> {\n const cols: Record<string, any> = {};\n\n for (const field of table.schema.fields) {\n const child = table.getChild(field.name);\n\n if (child) {\n cols[field.name] = child.toArray();\n }\n }\n\n return cols;\n }\n\n /**\n * Extracts chart data from Arrow table.\n * Uses get(i) to properly handle complex types like Decimal128.\n * Applies decimal scaling for DECIMAL types.\n *\n * Note: This method assumes Arrow has been loaded (via processArrowBuffer).\n *\n * @returns xData for axis, yDataMap for series data\n */\n static extractChartData(table: Table, xKey: string, yKeys: string[]) {\n // Early exit for empty tables - return cached empty object\n if (table.numRows === 0) {\n return EMPTY_RESULT;\n }\n\n // Get the Decimal type ID (Arrow must be loaded to have a Table)\n const decimalType = getDecimalTypeId();\n\n // Build a map of field name -> pre-computed divisor (10^scale) for decimal types\n const decimalDivisors = new Map<string, number>();\n for (const field of table.schema.fields) {\n if (field.typeId === decimalType) {\n const decType = field.type as { scale: number };\n if (typeof decType.scale === \"number\") {\n // Pre-compute divisor once per field instead of per-column call\n decimalDivisors.set(field.name, 10 ** decType.scale);\n }\n }\n }\n\n // Extract X column using proper value extraction\n const xCol = table.getChild(xKey);\n const xData = extractColumnValues(xCol, decimalDivisors.get(xKey));\n\n // Extract Y columns using proper value extraction\n const yDataMap: Record<string, (string | number)[]> = {};\n for (let i = 0; i < yKeys.length; i++) {\n const key = yKeys[i];\n const col = table.getChild(key);\n yDataMap[key] = extractColumnValues(col, decimalDivisors.get(key));\n }\n\n return { xData, yDataMap };\n }\n\n /**\n * Automatically detect which fields to use for chart axes from an Arrow table\n * Uses the schema's type information for accurate field detection\n *\n * Note: This method assumes Arrow has been loaded (via processArrowBuffer).\n *\n * @param table - Arrow Table to analyze\n * @param orientation - Chart orientation (\"vertical\" for time-series, \"horizontal\" for categorical)\n * @returns Object containing the detected fields\n * @example\n * // Time-series data\n * detectFieldsFromArrow(timeSeriesTable)\n * // { xField: \"date\", yFields: [\"revenue\", \"cost\"], chartType: \"timeseries\" }\n *\n * // Categorical data\n * detectFieldsFromArrow(categoricalTable)\n * // { xField: \"app_name\", yFields: [\"totalSpend\"], chartType: \"categorical\" }\n */\n static detectFieldsFromArrow(\n table: Table,\n orientation?: \"vertical\" | \"horizontal\",\n ): DetectedFields & { chartType: \"timeseries\" | \"categorical\" } {\n const fields = table.schema.fields;\n\n if (fields.length === 0) {\n return { xField: \"x\", yFields: [\"y\"], chartType: \"categorical\" };\n }\n\n const fieldNames = fields.map((f) => f.name);\n\n // Get type ID sets (Arrow must be loaded to have a Table)\n const typeIdSets = getTypeIdSets();\n\n // Categorize fields by their Arrow type\n const temporalFields: string[] = [];\n const numericFields: string[] = [];\n const stringFields: string[] = [];\n\n for (const field of fields) {\n const typeId = field.typeId;\n\n if (typeIdSets.temporal.has(typeId)) {\n temporalFields.push(field.name);\n } else if (typeIdSets.numeric.has(typeId)) {\n numericFields.push(field.name);\n } else if (typeIdSets.string.has(typeId)) {\n stringFields.push(field.name);\n }\n }\n\n // Detect name/category fields: string fields matching name patterns\n let nameFields = stringFields.filter((name) =>\n NAME_FIELD_PATTERNS.some((pattern) =>\n name.toLowerCase().includes(pattern),\n ),\n );\n\n // Fallback: use any string field that doesn't end with _id\n if (nameFields.length === 0) {\n nameFields = stringFields.filter(\n (name) => !name.toLowerCase().endsWith(\"_id\"),\n );\n }\n\n // Separate temporal fields into \"chart-worthy\" dates vs metadata dates\n const chartDateFields = temporalFields.filter(\n (name) =>\n !METADATA_DATE_PATTERNS.some((pattern) =>\n name.toLowerCase().includes(pattern),\n ),\n );\n const metadataDateFields = temporalFields.filter((name) =>\n METADATA_DATE_PATTERNS.some((pattern) =>\n name.toLowerCase().includes(pattern),\n ),\n );\n\n // Also check string fields for date patterns (but not metadata patterns)\n const stringDateFields = stringFields.filter(\n (name) =>\n DATE_FIELD_PATTERNS.some((pattern) =>\n name.toLowerCase().includes(pattern),\n ) &&\n !METADATA_DATE_PATTERNS.some((pattern) =>\n name.toLowerCase().includes(pattern),\n ),\n );\n\n const primaryDateFields = [...chartDateFields, ...stringDateFields];\n\n // Determine chart type: if we have good date fields for charting, it's time-series\n // If we only have metadata dates (like createdAt) and name fields, it's categorical\n const isTimeSeries =\n primaryDateFields.length > 0 && orientation !== \"horizontal\";\n const isCategorical =\n nameFields.length > 0 &&\n (primaryDateFields.length === 0 || orientation === \"horizontal\");\n\n if (orientation === \"horizontal\" || isCategorical) {\n // Categorical: x is name/category field, y is numeric field\n const xField =\n nameFields[0] ||\n primaryDateFields[0] ||\n metadataDateFields[0] ||\n fieldNames[0];\n const yFields =\n numericFields.length > 0\n ? numericFields\n : fieldNames.filter((k) => k !== xField);\n return { xField, yFields, chartType: \"categorical\" };\n }\n\n // Time-series (default): x is date/time field, y is numeric field\n const xField =\n primaryDateFields[0] ||\n metadataDateFields[0] ||\n nameFields[0] ||\n fieldNames[0];\n const yFields =\n numericFields.length > 0\n ? numericFields\n : fieldNames.filter((k) => k !== xField);\n return {\n xField,\n yFields,\n chartType: isTimeSeries ? \"timeseries\" : \"categorical\",\n };\n }\n\n static async fetchArrow(\n url: string,\n headers?: Record<string, string>,\n ): Promise<Uint8Array> {\n try {\n const response = await fetch(url, {\n headers: { \"Content-Type\": \"application/octet-stream\", ...headers },\n });\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n const buffer = await response.arrayBuffer();\n\n return new Uint8Array(buffer);\n } catch (error) {\n throw new Error(\n `Failed to fetch Arrow data: ${\n error instanceof Error ? error.message : \"Unknown error\"\n }`,\n );\n }\n }\n}\n\nexport interface DetectedFields {\n /** X field */\n xField: string;\n /** Y fields */\n yFields: string[];\n}\n\n// ============================================================================\n// Utility Functions\n// ============================================================================\n\n// Cached empty result to avoid allocations\nconst EMPTY_RESULT: {\n xData: (string | number)[];\n yDataMap: Record<string, (string | number)[]>;\n} = {\n xData: [],\n yDataMap: {},\n};\n\n/**\n * Extracts values from an Arrow Vector properly.\n * Uses get(i) to handle complex types like Decimal128 correctly.\n * toArray() doesn't work properly for Decimal types - it returns raw bytes.\n *\n * @param col - The Arrow column/vector\n * @param divisor - Pre-computed divisor for DECIMAL types (10^scale)\n */\nfunction extractColumnValues(\n col: { length: number; get: (i: number) => unknown } | null | undefined,\n divisor?: number,\n): (string | number)[] {\n if (!col) return [];\n\n // Pre-allocate array for better performance with large datasets\n const len = col.length;\n const result: (string | number)[] = new Array(len);\n\n for (let i = 0; i < len; i++) {\n const val = col.get(i);\n if (val === null || val === undefined) {\n result[i] = 0;\n } else if (typeof val === \"bigint\") {\n // Apply decimal scaling if needed\n const num = Number(val);\n result[i] = divisor !== undefined ? num / divisor : num;\n } else if (typeof val === \"number\") {\n // Apply decimal scaling if needed\n result[i] = divisor !== undefined ? val / divisor : val;\n } else if (typeof val === \"string\") {\n result[i] = val;\n } else {\n // For complex types (like Decimal), try to convert to number\n const num = Number(val);\n result[i] = divisor !== undefined ? num / divisor : num;\n }\n }\n return result;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as apache_arrow0 from "apache-arrow";
|
|
2
|
+
|
|
3
|
+
//#region src/js/arrow/lazy-arrow.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Lazily loads the Apache Arrow library.
|
|
7
|
+
* Returns cached module if already loaded.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* const arrow = await getArrowModule();
|
|
12
|
+
* const table = arrow.tableFromIPC(buffer);
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
declare function getArrowModule(): Promise<typeof apache_arrow0>;
|
|
16
|
+
/**
|
|
17
|
+
* Initializes the type ID sets from the Arrow Type enum.
|
|
18
|
+
* Call this after loading the Arrow module.
|
|
19
|
+
*/
|
|
20
|
+
declare function initializeTypeIdSets(): Promise<void>;
|
|
21
|
+
//#endregion
|
|
22
|
+
export { getArrowModule, initializeTypeIdSets };
|
|
23
|
+
//# sourceMappingURL=lazy-arrow.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lazy-arrow.d.ts","names":[],"sources":["../../../src/js/arrow/lazy-arrow.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;;iBA+BsB,cAAA,CAAA,GAAkB,eAAJ;;;;;iBAqBd,oBAAA,CAAA,GAAwB"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
//#region src/js/arrow/lazy-arrow.ts
|
|
2
|
+
let arrowModulePromise = null;
|
|
3
|
+
/**
|
|
4
|
+
* Lazily loads the Apache Arrow library.
|
|
5
|
+
* Returns cached module if already loaded.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* const arrow = await getArrowModule();
|
|
10
|
+
* const table = arrow.tableFromIPC(buffer);
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
async function getArrowModule() {
|
|
14
|
+
if (!arrowModulePromise) arrowModulePromise = import("apache-arrow");
|
|
15
|
+
return arrowModulePromise;
|
|
16
|
+
}
|
|
17
|
+
let temporalTypeIds = null;
|
|
18
|
+
let numericTypeIds = null;
|
|
19
|
+
let stringTypeIds = null;
|
|
20
|
+
let decimalTypeId = null;
|
|
21
|
+
/**
|
|
22
|
+
* Initializes the type ID sets from the Arrow Type enum.
|
|
23
|
+
* Call this after loading the Arrow module.
|
|
24
|
+
*/
|
|
25
|
+
async function initializeTypeIdSets() {
|
|
26
|
+
if (temporalTypeIds !== null) return;
|
|
27
|
+
const { Type } = await getArrowModule();
|
|
28
|
+
temporalTypeIds = new Set([
|
|
29
|
+
Type.Date,
|
|
30
|
+
Type.DateDay,
|
|
31
|
+
Type.DateMillisecond,
|
|
32
|
+
Type.Timestamp,
|
|
33
|
+
Type.TimestampSecond,
|
|
34
|
+
Type.TimestampMillisecond,
|
|
35
|
+
Type.TimestampMicrosecond,
|
|
36
|
+
Type.TimestampNanosecond,
|
|
37
|
+
Type.Time,
|
|
38
|
+
Type.TimeSecond,
|
|
39
|
+
Type.TimeMillisecond,
|
|
40
|
+
Type.TimeMicrosecond,
|
|
41
|
+
Type.TimeNanosecond
|
|
42
|
+
]);
|
|
43
|
+
numericTypeIds = new Set([
|
|
44
|
+
Type.Int,
|
|
45
|
+
Type.Int8,
|
|
46
|
+
Type.Int16,
|
|
47
|
+
Type.Int32,
|
|
48
|
+
Type.Int64,
|
|
49
|
+
Type.Uint8,
|
|
50
|
+
Type.Uint16,
|
|
51
|
+
Type.Uint32,
|
|
52
|
+
Type.Uint64,
|
|
53
|
+
Type.Float,
|
|
54
|
+
Type.Float16,
|
|
55
|
+
Type.Float32,
|
|
56
|
+
Type.Float64,
|
|
57
|
+
Type.Decimal
|
|
58
|
+
]);
|
|
59
|
+
stringTypeIds = new Set([Type.Utf8, Type.LargeUtf8]);
|
|
60
|
+
decimalTypeId = Type.Decimal;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Returns the cached type ID sets.
|
|
64
|
+
* Throws if called before initializeTypeIdSets().
|
|
65
|
+
* This is safe because you can't have a Table without first loading Arrow.
|
|
66
|
+
*/
|
|
67
|
+
function getTypeIdSets() {
|
|
68
|
+
if (!temporalTypeIds || !numericTypeIds || !stringTypeIds) throw new Error("Arrow type IDs not initialized. Call initializeTypeIdSets() first.");
|
|
69
|
+
return {
|
|
70
|
+
temporal: temporalTypeIds,
|
|
71
|
+
numeric: numericTypeIds,
|
|
72
|
+
string: stringTypeIds
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Returns the Decimal type ID.
|
|
77
|
+
* Throws if called before initializeTypeIdSets().
|
|
78
|
+
*/
|
|
79
|
+
function getDecimalTypeId() {
|
|
80
|
+
if (decimalTypeId === null) throw new Error("Arrow type IDs not initialized. Call initializeTypeIdSets() first.");
|
|
81
|
+
return decimalTypeId;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
//#endregion
|
|
85
|
+
export { getArrowModule, getDecimalTypeId, getTypeIdSets, initializeTypeIdSets };
|
|
86
|
+
//# sourceMappingURL=lazy-arrow.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lazy-arrow.js","names":["arrowModulePromise: Promise<typeof import(\"apache-arrow\")> | null","temporalTypeIds: Set<number> | null","numericTypeIds: Set<number> | null","stringTypeIds: Set<number> | null","decimalTypeId: number | null"],"sources":["../../../src/js/arrow/lazy-arrow.ts"],"sourcesContent":["/**\n * Lazy loader for Apache Arrow library.\n * The arrow library is substantial (~200KB+ gzipped), so we only load it\n * when Arrow format is actually used.\n *\n * This module caches the import promise to ensure the library is only\n * loaded once, even if multiple components request it simultaneously.\n */\n\nimport type { Table, Field } from \"apache-arrow\";\n\n// Re-export types for convenience (types don't add to bundle size)\nexport type { Table, Field };\n\n// ============================================================================\n// Lazy Module Loading\n// ============================================================================\n\n// Cache the import promise to avoid multiple loads\nlet arrowModulePromise: Promise<typeof import(\"apache-arrow\")> | null = null;\n\n/**\n * Lazily loads the Apache Arrow library.\n * Returns cached module if already loaded.\n *\n * @example\n * ```typescript\n * const arrow = await getArrowModule();\n * const table = arrow.tableFromIPC(buffer);\n * ```\n */\nexport async function getArrowModule(): Promise<typeof import(\"apache-arrow\")> {\n if (!arrowModulePromise) {\n arrowModulePromise = import(\"apache-arrow\");\n }\n return arrowModulePromise;\n}\n\n// ============================================================================\n// Cached Type ID Sets\n// ============================================================================\n\n// These are initialized lazily when Arrow is first loaded\nlet temporalTypeIds: Set<number> | null = null;\nlet numericTypeIds: Set<number> | null = null;\nlet stringTypeIds: Set<number> | null = null;\nlet decimalTypeId: number | null = null;\n\n/**\n * Initializes the type ID sets from the Arrow Type enum.\n * Call this after loading the Arrow module.\n */\nexport async function initializeTypeIdSets(): Promise<void> {\n if (temporalTypeIds !== null) return; // Already initialized\n\n const { Type } = await getArrowModule();\n\n temporalTypeIds = new Set([\n Type.Date,\n Type.DateDay,\n Type.DateMillisecond,\n Type.Timestamp,\n Type.TimestampSecond,\n Type.TimestampMillisecond,\n Type.TimestampMicrosecond,\n Type.TimestampNanosecond,\n Type.Time,\n Type.TimeSecond,\n Type.TimeMillisecond,\n Type.TimeMicrosecond,\n Type.TimeNanosecond,\n ]);\n\n numericTypeIds = new Set([\n Type.Int,\n Type.Int8,\n Type.Int16,\n Type.Int32,\n Type.Int64,\n Type.Uint8,\n Type.Uint16,\n Type.Uint32,\n Type.Uint64,\n Type.Float,\n Type.Float16,\n Type.Float32,\n Type.Float64,\n Type.Decimal,\n ]);\n\n stringTypeIds = new Set([Type.Utf8, Type.LargeUtf8]);\n\n decimalTypeId = Type.Decimal;\n}\n\n/**\n * Returns the cached type ID sets.\n * Throws if called before initializeTypeIdSets().\n * This is safe because you can't have a Table without first loading Arrow.\n */\nexport function getTypeIdSets(): {\n temporal: Set<number>;\n numeric: Set<number>;\n string: Set<number>;\n} {\n if (!temporalTypeIds || !numericTypeIds || !stringTypeIds) {\n throw new Error(\n \"Arrow type IDs not initialized. Call initializeTypeIdSets() first.\",\n );\n }\n return {\n temporal: temporalTypeIds,\n numeric: numericTypeIds,\n string: stringTypeIds,\n };\n}\n\n/**\n * Returns the Decimal type ID.\n * Throws if called before initializeTypeIdSets().\n */\nexport function getDecimalTypeId(): number {\n if (decimalTypeId === null) {\n throw new Error(\n \"Arrow type IDs not initialized. Call initializeTypeIdSets() first.\",\n );\n }\n return decimalTypeId;\n}\n"],"mappings":";AAmBA,IAAIA,qBAAoE;;;;;;;;;;;AAYxE,eAAsB,iBAAyD;AAC7E,KAAI,CAAC,mBACH,sBAAqB,OAAO;AAE9B,QAAO;;AAQT,IAAIC,kBAAsC;AAC1C,IAAIC,iBAAqC;AACzC,IAAIC,gBAAoC;AACxC,IAAIC,gBAA+B;;;;;AAMnC,eAAsB,uBAAsC;AAC1D,KAAI,oBAAoB,KAAM;CAE9B,MAAM,EAAE,SAAS,MAAM,gBAAgB;AAEvC,mBAAkB,IAAI,IAAI;EACxB,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACN,CAAC;AAEF,kBAAiB,IAAI,IAAI;EACvB,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACN,CAAC;AAEF,iBAAgB,IAAI,IAAI,CAAC,KAAK,MAAM,KAAK,UAAU,CAAC;AAEpD,iBAAgB,KAAK;;;;;;;AAQvB,SAAgB,gBAId;AACA,KAAI,CAAC,mBAAmB,CAAC,kBAAkB,CAAC,cAC1C,OAAM,IAAI,MACR,qEACD;AAEH,QAAO;EACL,UAAU;EACV,SAAS;EACT,QAAQ;EACT;;;;;;AAOH,SAAgB,mBAA2B;AACzC,KAAI,kBAAkB,KACpB,OAAM,IAAI,MACR,qEACD;AAEH,QAAO"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
//#region src/js/constants.d.ts
|
|
2
|
+
/** Field patterns to detect date/time fields by name */
|
|
3
|
+
declare const DATE_FIELD_PATTERNS: readonly ["date", "time", "period", "timestamp"];
|
|
4
|
+
/** Field patterns to detect name/category fields by name */
|
|
5
|
+
declare const NAME_FIELD_PATTERNS: readonly ["name", "label", "app", "user", "creator", "browser", "category"];
|
|
6
|
+
/** Patterns that indicate a date field is metadata, not for charting */
|
|
7
|
+
declare const METADATA_DATE_PATTERNS: readonly ["created", "updated", "modified", "deleted", "last_"];
|
|
8
|
+
//#endregion
|
|
9
|
+
export { DATE_FIELD_PATTERNS, METADATA_DATE_PATTERNS, NAME_FIELD_PATTERNS };
|
|
10
|
+
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","names":[],"sources":["../../src/js/constants.ts"],"sourcesContent":[],"mappings":";;AAOa,cAAA,mBAKH,EAAA,SAAA,CAAA,MAAA,EAAA,MAAA,EAAA,QAAA,EAAA,WAAA,CAAA;AAGV;AAWa,cAXA,mBAiBH,EAAA,SAAA,CAAA,MAAA,EAAA,OAAA,EAAA,KAAA,EAAA,MAAA,EAAA,SAAA,EAAA,SAAA,EAAA,UAAA,CAAA;;cANG"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
//#region src/js/constants.ts
|
|
2
|
+
/** Field patterns to detect date/time fields by name */
|
|
3
|
+
const DATE_FIELD_PATTERNS = [
|
|
4
|
+
"date",
|
|
5
|
+
"time",
|
|
6
|
+
"period",
|
|
7
|
+
"timestamp"
|
|
8
|
+
];
|
|
9
|
+
/** Field patterns to detect name/category fields by name */
|
|
10
|
+
const NAME_FIELD_PATTERNS = [
|
|
11
|
+
"name",
|
|
12
|
+
"label",
|
|
13
|
+
"app",
|
|
14
|
+
"user",
|
|
15
|
+
"creator",
|
|
16
|
+
"browser",
|
|
17
|
+
"category"
|
|
18
|
+
];
|
|
19
|
+
/** Patterns that indicate a date field is metadata, not for charting */
|
|
20
|
+
const METADATA_DATE_PATTERNS = [
|
|
21
|
+
"created",
|
|
22
|
+
"updated",
|
|
23
|
+
"modified",
|
|
24
|
+
"deleted",
|
|
25
|
+
"last_"
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
//#endregion
|
|
29
|
+
export { DATE_FIELD_PATTERNS, METADATA_DATE_PATTERNS, NAME_FIELD_PATTERNS };
|
|
30
|
+
//# sourceMappingURL=constants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.js","names":[],"sources":["../../src/js/constants.ts"],"sourcesContent":["// ============================================================================\n// Shared Constants for Field Detection\n// ============================================================================\n// These patterns are used by both Arrow processing and chart normalization\n// to detect field types from column names.\n\n/** Field patterns to detect date/time fields by name */\nexport const DATE_FIELD_PATTERNS = [\n \"date\",\n \"time\",\n \"period\",\n \"timestamp\",\n] as const;\n\n/** Field patterns to detect name/category fields by name */\nexport const NAME_FIELD_PATTERNS = [\n \"name\",\n \"label\",\n \"app\",\n \"user\",\n \"creator\",\n \"browser\",\n \"category\",\n] as const;\n\n/** Patterns that indicate a date field is metadata, not for charting */\nexport const METADATA_DATE_PATTERNS = [\n \"created\",\n \"updated\",\n \"modified\",\n \"deleted\",\n \"last_\",\n] as const;\n"],"mappings":";;AAOA,MAAa,sBAAsB;CACjC;CACA;CACA;CACA;CACD;;AAGD,MAAa,sBAAsB;CACjC;CACA;CACA;CACA;CACA;CACA;CACA;CACD;;AAGD,MAAa,yBAAyB;CACpC;CACA;CACA;CACA;CACA;CACD"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { SQLBinaryMarker, SQLBooleanMarker, SQLDateMarker, SQLNumberMarker, SQLStringMarker, SQLTimestampMarker, SQLTypeMarker } from "../shared/src/sql/types.js";
|
|
2
|
+
import { isSQLTypeMarker, sql } from "../shared/src/sql/helpers.js";
|
|
3
|
+
import { DATE_FIELD_PATTERNS, METADATA_DATE_PATTERNS, NAME_FIELD_PATTERNS } from "./constants.js";
|
|
4
|
+
import { ArrowClient, DetectedFields, Field, Table } from "./arrow/arrow-client.js";
|
|
5
|
+
import { getArrowModule, initializeTypeIdSets } from "./arrow/lazy-arrow.js";
|
|
6
|
+
import { ConnectSSEOptions, SSEMessage } from "./sse/types.js";
|
|
7
|
+
import { connectSSE } from "./sse/connect-sse.js";
|
|
8
|
+
export { ArrowClient, ConnectSSEOptions, DATE_FIELD_PATTERNS, DetectedFields, Field, METADATA_DATE_PATTERNS, NAME_FIELD_PATTERNS, type SQLBinaryMarker, type SQLBooleanMarker, type SQLDateMarker, type SQLNumberMarker, type SQLStringMarker, type SQLTimestampMarker, type SQLTypeMarker, SSEMessage, Table, connectSSE, getArrowModule, initializeTypeIdSets, isSQLTypeMarker, sql };
|
package/dist/js/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { isSQLTypeMarker, sql } from "../shared/src/sql/helpers.js";
|
|
2
|
+
import { DATE_FIELD_PATTERNS, METADATA_DATE_PATTERNS, NAME_FIELD_PATTERNS } from "./constants.js";
|
|
3
|
+
import { getArrowModule, initializeTypeIdSets } from "./arrow/lazy-arrow.js";
|
|
4
|
+
import { ArrowClient } from "./arrow/arrow-client.js";
|
|
5
|
+
import "./arrow/index.js";
|
|
6
|
+
import { connectSSE } from "./sse/connect-sse.js";
|
|
7
|
+
|
|
8
|
+
export { ArrowClient, DATE_FIELD_PATTERNS, METADATA_DATE_PATTERNS, NAME_FIELD_PATTERNS, connectSSE, getArrowModule, initializeTypeIdSets, isSQLTypeMarker, sql };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ConnectSSEOptions } from "./types.js";
|
|
2
|
+
|
|
3
|
+
//#region src/js/sse/connect-sse.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Connects to an SSE endpoint with automatic retries and exponential backoff
|
|
7
|
+
* @param options - SSE connection options
|
|
8
|
+
* @param attempt - Internal retry attempt counter
|
|
9
|
+
* @returns Promise that resolves when the stream ends or retries stop
|
|
10
|
+
*/
|
|
11
|
+
declare function connectSSE<Payload = unknown>(options: ConnectSSEOptions<Payload>, attempt?: number): Promise<void>;
|
|
12
|
+
//#endregion
|
|
13
|
+
export { connectSSE };
|
|
14
|
+
//# sourceMappingURL=connect-sse.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connect-sse.d.ts","names":[],"sources":["../../../src/js/sse/connect-sse.ts"],"sourcesContent":[],"mappings":";;;;;;AAQA;;;;AAEa,iBAFS,UAET,CAAA,UAAA,OAAA,CAAA,CAAA,OAAA,EADF,iBACE,CADgB,OAChB,CAAA,EAAA,OAAA,CAAA,EAAA,MAAA,CAAA,EAAA,OAAA,CAAA,IAAA,CAAA"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
//#region src/js/sse/connect-sse.ts
|
|
2
|
+
/**
|
|
3
|
+
* Connects to an SSE endpoint with automatic retries and exponential backoff
|
|
4
|
+
* @param options - SSE connection options
|
|
5
|
+
* @param attempt - Internal retry attempt counter
|
|
6
|
+
* @returns Promise that resolves when the stream ends or retries stop
|
|
7
|
+
*/
|
|
8
|
+
async function connectSSE(options, attempt = 0) {
|
|
9
|
+
const { url, payload, onMessage, signal, lastEventId: initialLastEventId = null, retryDelay = 2e3, maxRetries = 3, maxBufferSize = 1024 * 1024, timeout = 3e5, onError } = options;
|
|
10
|
+
if (!url || url.trim().length <= 0) throw new Error("connectSSE: 'url' must be a non-empty string.");
|
|
11
|
+
if (retryDelay <= 0) throw new Error("connectSSE: 'retryDelay' must be >= 0.");
|
|
12
|
+
if (maxBufferSize <= 0) throw new Error("connectSSE: 'maxBufferSize' must be > 0.");
|
|
13
|
+
const headers = { Accept: "text/event-stream" };
|
|
14
|
+
const hasPayload = typeof payload !== "undefined";
|
|
15
|
+
if (hasPayload) headers["Content-Type"] = "application/json";
|
|
16
|
+
let lastEventId = initialLastEventId;
|
|
17
|
+
if (lastEventId) headers["Last-Event-ID"] = lastEventId;
|
|
18
|
+
const method = hasPayload ? "POST" : "GET";
|
|
19
|
+
const body = hasPayload ? typeof payload === "string" ? payload : JSON.stringify(payload) : void 0;
|
|
20
|
+
const timeoutController = new AbortController();
|
|
21
|
+
const timeoutId = setTimeout(() => timeoutController.abort(), timeout);
|
|
22
|
+
const combinedSignal = signal ? createCombinedSignal(signal, timeoutController.signal) : timeoutController.signal;
|
|
23
|
+
try {
|
|
24
|
+
const response = await fetch(url, {
|
|
25
|
+
headers,
|
|
26
|
+
method,
|
|
27
|
+
body,
|
|
28
|
+
signal: combinedSignal
|
|
29
|
+
});
|
|
30
|
+
clearTimeout(timeoutId);
|
|
31
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
32
|
+
if (!response.body) throw new Error("No response body");
|
|
33
|
+
const reader = response.body.getReader();
|
|
34
|
+
const decoder = new TextDecoder("utf-8");
|
|
35
|
+
let buffer = "";
|
|
36
|
+
while (true) {
|
|
37
|
+
const { done, value } = await reader.read();
|
|
38
|
+
if (done) break;
|
|
39
|
+
const decoded = decoder.decode(value, { stream: true });
|
|
40
|
+
if (buffer.length + decoded.length > maxBufferSize) throw new Error("Buffer size exceeded");
|
|
41
|
+
buffer += decoded;
|
|
42
|
+
const parts = buffer.replace(/\r\n/g, "\n").split("\n\n");
|
|
43
|
+
buffer = parts.pop() ?? "";
|
|
44
|
+
for (const part of parts) {
|
|
45
|
+
const message = parseSSEEvent(part);
|
|
46
|
+
if (!message) continue;
|
|
47
|
+
if (message.id) lastEventId = message.id;
|
|
48
|
+
onMessage({
|
|
49
|
+
id: lastEventId ?? "",
|
|
50
|
+
data: message.data
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
} catch (error) {
|
|
55
|
+
clearTimeout(timeoutId);
|
|
56
|
+
if (onError) onError(error);
|
|
57
|
+
if (signal?.aborted) return;
|
|
58
|
+
if (attempt >= maxRetries) {
|
|
59
|
+
console.warn(`[connectSSE] Max retries (${maxRetries}) exceeded for ${url}`);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const nextAttempt = attempt + 1;
|
|
63
|
+
const delayMs = computeExponentialDelay(nextAttempt, retryDelay);
|
|
64
|
+
if (delayMs <= 0) return;
|
|
65
|
+
await new Promise((resolve) => {
|
|
66
|
+
setTimeout(() => {
|
|
67
|
+
connectSSE({
|
|
68
|
+
...options,
|
|
69
|
+
lastEventId
|
|
70
|
+
}, nextAttempt).finally(resolve);
|
|
71
|
+
}, delayMs);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Parses a raw SSE event chunk into a message
|
|
77
|
+
* @param chunk - Raw SSE event block
|
|
78
|
+
* @returns Parsed message or null when no data lines are present
|
|
79
|
+
* @private
|
|
80
|
+
*/
|
|
81
|
+
function parseSSEEvent(chunk) {
|
|
82
|
+
const lines = chunk.replace(/\r\n/g, "\n").split("\n");
|
|
83
|
+
let id;
|
|
84
|
+
const dataLines = [];
|
|
85
|
+
for (const rawLine of lines) {
|
|
86
|
+
const line = rawLine.trimEnd();
|
|
87
|
+
if (line === "") continue;
|
|
88
|
+
if (line.startsWith(":")) continue;
|
|
89
|
+
if (line.startsWith("id:")) id = line.slice(3).trimStart();
|
|
90
|
+
else if (line.startsWith("data:")) dataLines.push(line.slice(5).trimStart());
|
|
91
|
+
}
|
|
92
|
+
if (dataLines.length === 0) return null;
|
|
93
|
+
return {
|
|
94
|
+
id: id ?? "",
|
|
95
|
+
data: dataLines.join("\n")
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Compute exponential backoff delay in milliseconds.
|
|
100
|
+
* Uses a random jitter factor to avoid thundering herd problem.
|
|
101
|
+
* @param attempt - Retry attempt number (1-based)
|
|
102
|
+
* @param baseDelayMs - Base delay in milliseconds
|
|
103
|
+
* @returns Delay in milliseconds for this attempt
|
|
104
|
+
*/
|
|
105
|
+
function computeExponentialDelay(attempt, baseDelayMs) {
|
|
106
|
+
const safeAttempt = Math.max(1, attempt);
|
|
107
|
+
const rawDelayMs = baseDelayMs * Math.min(2 ** (safeAttempt - 1), 32);
|
|
108
|
+
const jitter = rawDelayMs * .2;
|
|
109
|
+
const randomizedDelayMs = rawDelayMs + Math.random() * jitter;
|
|
110
|
+
return Math.max(0, Math.floor(randomizedDelayMs));
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Combines two abort signals into a single abort signal
|
|
114
|
+
* @param signal1 - First abort signal
|
|
115
|
+
* @param signal2 - Second abort signal
|
|
116
|
+
* @returns Combined signal
|
|
117
|
+
*/
|
|
118
|
+
function createCombinedSignal(signal1, signal2) {
|
|
119
|
+
const controller = new AbortController();
|
|
120
|
+
const abort = () => controller.abort();
|
|
121
|
+
signal1.addEventListener("abort", abort);
|
|
122
|
+
signal2.addEventListener("abort", abort);
|
|
123
|
+
return controller.signal;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
//#endregion
|
|
127
|
+
export { connectSSE };
|
|
128
|
+
//# sourceMappingURL=connect-sse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connect-sse.js","names":["headers: HeadersInit","id: string | undefined","dataLines: string[]"],"sources":["../../../src/js/sse/connect-sse.ts"],"sourcesContent":["import type { ConnectSSEOptions, SSEMessage } from \"./types\";\n\n/**\n * Connects to an SSE endpoint with automatic retries and exponential backoff\n * @param options - SSE connection options\n * @param attempt - Internal retry attempt counter\n * @returns Promise that resolves when the stream ends or retries stop\n */\nexport async function connectSSE<Payload = unknown>(\n options: ConnectSSEOptions<Payload>,\n attempt = 0,\n) {\n const {\n url,\n payload,\n onMessage,\n signal,\n lastEventId: initialLastEventId = null,\n retryDelay = 2000,\n maxRetries = 3,\n maxBufferSize = 1024 * 1024, // 1MB\n timeout = 300000, // 5 minutes\n onError,\n } = options;\n\n if (!url || url.trim().length <= 0) {\n throw new Error(\"connectSSE: 'url' must be a non-empty string.\");\n }\n\n if (retryDelay <= 0) {\n throw new Error(\"connectSSE: 'retryDelay' must be >= 0.\");\n }\n if (maxBufferSize <= 0) {\n throw new Error(\"connectSSE: 'maxBufferSize' must be > 0.\");\n }\n const headers: HeadersInit = {\n Accept: \"text/event-stream\",\n };\n\n const hasPayload = typeof payload !== \"undefined\";\n\n if (hasPayload) {\n headers[\"Content-Type\"] = \"application/json\";\n }\n\n let lastEventId = initialLastEventId;\n\n if (lastEventId) {\n headers[\"Last-Event-ID\"] = lastEventId;\n }\n\n const method = hasPayload ? \"POST\" : \"GET\";\n const body = hasPayload\n ? typeof payload === \"string\"\n ? payload\n : JSON.stringify(payload)\n : undefined;\n\n const timeoutController = new AbortController();\n const timeoutId = setTimeout(() => timeoutController.abort(), timeout);\n\n const combinedSignal = signal\n ? createCombinedSignal(signal, timeoutController.signal)\n : timeoutController.signal;\n\n try {\n const response = await fetch(url, {\n headers,\n method,\n body,\n signal: combinedSignal,\n });\n\n clearTimeout(timeoutId);\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`);\n }\n\n if (!response.body) {\n throw new Error(\"No response body\");\n }\n\n const reader = response.body.getReader();\n const decoder = new TextDecoder(\"utf-8\");\n\n let buffer = \"\";\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n const decoded = decoder.decode(value, { stream: true });\n\n if (buffer.length + decoded.length > maxBufferSize) {\n throw new Error(\"Buffer size exceeded\");\n }\n buffer += decoded;\n\n const normalizedBuffer = buffer.replace(/\\r\\n/g, \"\\n\");\n const parts = normalizedBuffer.split(\"\\n\\n\");\n\n buffer = parts.pop() ?? \"\";\n for (const part of parts) {\n const message = parseSSEEvent(part);\n if (!message) continue;\n\n if (message.id) lastEventId = message.id;\n\n onMessage({\n id: lastEventId ?? \"\",\n data: message.data,\n });\n }\n }\n } catch (error) {\n clearTimeout(timeoutId);\n if (onError) onError(error);\n if (signal?.aborted) return;\n\n if (attempt >= maxRetries) {\n console.warn(\n `[connectSSE] Max retries (${maxRetries}) exceeded for ${url}`,\n );\n return;\n }\n\n const nextAttempt = attempt + 1;\n const delayMs = computeExponentialDelay(nextAttempt, retryDelay);\n\n if (delayMs <= 0) return;\n\n await new Promise<void>((resolve) => {\n setTimeout(() => {\n connectSSE({ ...options, lastEventId }, nextAttempt).finally(resolve);\n }, delayMs);\n });\n }\n}\n\n/**\n * Parses a raw SSE event chunk into a message\n * @param chunk - Raw SSE event block\n * @returns Parsed message or null when no data lines are present\n * @private\n */\nfunction parseSSEEvent(chunk: string): SSEMessage | null {\n const normalized = chunk.replace(/\\r\\n/g, \"\\n\");\n const lines = normalized.split(\"\\n\");\n\n let id: string | undefined;\n const dataLines: string[] = [];\n\n for (const rawLine of lines) {\n const line = rawLine.trimEnd();\n\n if (line === \"\") continue;\n if (line.startsWith(\":\")) continue;\n\n if (line.startsWith(\"id:\")) {\n id = line.slice(3).trimStart();\n } else if (line.startsWith(\"data:\")) {\n dataLines.push(line.slice(5).trimStart());\n }\n }\n\n if (dataLines.length === 0) return null;\n\n return {\n id: id ?? \"\",\n data: dataLines.join(\"\\n\"),\n };\n}\n\n/**\n * Compute exponential backoff delay in milliseconds.\n * Uses a random jitter factor to avoid thundering herd problem.\n * @param attempt - Retry attempt number (1-based)\n * @param baseDelayMs - Base delay in milliseconds\n * @returns Delay in milliseconds for this attempt\n */\nfunction computeExponentialDelay(attempt: number, baseDelayMs: number): number {\n const safeAttempt = Math.max(1, attempt);\n const multiplier = Math.min(2 ** (safeAttempt - 1), 32);\n const rawDelayMs = baseDelayMs * multiplier;\n\n const jitter = rawDelayMs * 0.2;\n const randomizedDelayMs = rawDelayMs + Math.random() * jitter;\n\n return Math.max(0, Math.floor(randomizedDelayMs));\n}\n\n/**\n * Combines two abort signals into a single abort signal\n * @param signal1 - First abort signal\n * @param signal2 - Second abort signal\n * @returns Combined signal\n */\nfunction createCombinedSignal(\n signal1: AbortSignal,\n signal2: AbortSignal,\n): AbortSignal {\n const controller = new AbortController();\n const abort = () => controller.abort();\n signal1.addEventListener(\"abort\", abort);\n signal2.addEventListener(\"abort\", abort);\n\n return controller.signal;\n}\n"],"mappings":";;;;;;;AAQA,eAAsB,WACpB,SACA,UAAU,GACV;CACA,MAAM,EACJ,KACA,SACA,WACA,QACA,aAAa,qBAAqB,MAClC,aAAa,KACb,aAAa,GACb,gBAAgB,OAAO,MACvB,UAAU,KACV,YACE;AAEJ,KAAI,CAAC,OAAO,IAAI,MAAM,CAAC,UAAU,EAC/B,OAAM,IAAI,MAAM,gDAAgD;AAGlE,KAAI,cAAc,EAChB,OAAM,IAAI,MAAM,yCAAyC;AAE3D,KAAI,iBAAiB,EACnB,OAAM,IAAI,MAAM,2CAA2C;CAE7D,MAAMA,UAAuB,EAC3B,QAAQ,qBACT;CAED,MAAM,aAAa,OAAO,YAAY;AAEtC,KAAI,WACF,SAAQ,kBAAkB;CAG5B,IAAI,cAAc;AAElB,KAAI,YACF,SAAQ,mBAAmB;CAG7B,MAAM,SAAS,aAAa,SAAS;CACrC,MAAM,OAAO,aACT,OAAO,YAAY,WACjB,UACA,KAAK,UAAU,QAAQ,GACzB;CAEJ,MAAM,oBAAoB,IAAI,iBAAiB;CAC/C,MAAM,YAAY,iBAAiB,kBAAkB,OAAO,EAAE,QAAQ;CAEtE,MAAM,iBAAiB,SACnB,qBAAqB,QAAQ,kBAAkB,OAAO,GACtD,kBAAkB;AAEtB,KAAI;EACF,MAAM,WAAW,MAAM,MAAM,KAAK;GAChC;GACA;GACA;GACA,QAAQ;GACT,CAAC;AAEF,eAAa,UAAU;AAEvB,MAAI,CAAC,SAAS,GACZ,OAAM,IAAI,MAAM,QAAQ,SAAS,SAAS;AAG5C,MAAI,CAAC,SAAS,KACZ,OAAM,IAAI,MAAM,mBAAmB;EAGrC,MAAM,SAAS,SAAS,KAAK,WAAW;EACxC,MAAM,UAAU,IAAI,YAAY,QAAQ;EAExC,IAAI,SAAS;AACb,SAAO,MAAM;GACX,MAAM,EAAE,MAAM,UAAU,MAAM,OAAO,MAAM;AAC3C,OAAI,KAAM;GAEV,MAAM,UAAU,QAAQ,OAAO,OAAO,EAAE,QAAQ,MAAM,CAAC;AAEvD,OAAI,OAAO,SAAS,QAAQ,SAAS,cACnC,OAAM,IAAI,MAAM,uBAAuB;AAEzC,aAAU;GAGV,MAAM,QADmB,OAAO,QAAQ,SAAS,KAAK,CACvB,MAAM,OAAO;AAE5C,YAAS,MAAM,KAAK,IAAI;AACxB,QAAK,MAAM,QAAQ,OAAO;IACxB,MAAM,UAAU,cAAc,KAAK;AACnC,QAAI,CAAC,QAAS;AAEd,QAAI,QAAQ,GAAI,eAAc,QAAQ;AAEtC,cAAU;KACR,IAAI,eAAe;KACnB,MAAM,QAAQ;KACf,CAAC;;;UAGC,OAAO;AACd,eAAa,UAAU;AACvB,MAAI,QAAS,SAAQ,MAAM;AAC3B,MAAI,QAAQ,QAAS;AAErB,MAAI,WAAW,YAAY;AACzB,WAAQ,KACN,6BAA6B,WAAW,iBAAiB,MAC1D;AACD;;EAGF,MAAM,cAAc,UAAU;EAC9B,MAAM,UAAU,wBAAwB,aAAa,WAAW;AAEhE,MAAI,WAAW,EAAG;AAElB,QAAM,IAAI,SAAe,YAAY;AACnC,oBAAiB;AACf,eAAW;KAAE,GAAG;KAAS;KAAa,EAAE,YAAY,CAAC,QAAQ,QAAQ;MACpE,QAAQ;IACX;;;;;;;;;AAUN,SAAS,cAAc,OAAkC;CAEvD,MAAM,QADa,MAAM,QAAQ,SAAS,KAAK,CACtB,MAAM,KAAK;CAEpC,IAAIC;CACJ,MAAMC,YAAsB,EAAE;AAE9B,MAAK,MAAM,WAAW,OAAO;EAC3B,MAAM,OAAO,QAAQ,SAAS;AAE9B,MAAI,SAAS,GAAI;AACjB,MAAI,KAAK,WAAW,IAAI,CAAE;AAE1B,MAAI,KAAK,WAAW,MAAM,CACxB,MAAK,KAAK,MAAM,EAAE,CAAC,WAAW;WACrB,KAAK,WAAW,QAAQ,CACjC,WAAU,KAAK,KAAK,MAAM,EAAE,CAAC,WAAW,CAAC;;AAI7C,KAAI,UAAU,WAAW,EAAG,QAAO;AAEnC,QAAO;EACL,IAAI,MAAM;EACV,MAAM,UAAU,KAAK,KAAK;EAC3B;;;;;;;;;AAUH,SAAS,wBAAwB,SAAiB,aAA6B;CAC7E,MAAM,cAAc,KAAK,IAAI,GAAG,QAAQ;CAExC,MAAM,aAAa,cADA,KAAK,IAAI,MAAM,cAAc,IAAI,GAAG;CAGvD,MAAM,SAAS,aAAa;CAC5B,MAAM,oBAAoB,aAAa,KAAK,QAAQ,GAAG;AAEvD,QAAO,KAAK,IAAI,GAAG,KAAK,MAAM,kBAAkB,CAAC;;;;;;;;AASnD,SAAS,qBACP,SACA,SACa;CACb,MAAM,aAAa,IAAI,iBAAiB;CACxC,MAAM,cAAc,WAAW,OAAO;AACtC,SAAQ,iBAAiB,SAAS,MAAM;AACxC,SAAQ,iBAAiB,SAAS,MAAM;AAExC,QAAO,WAAW"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
//#region src/js/sse/types.d.ts
|
|
2
|
+
/** Single SSE message payload. */
|
|
3
|
+
type SSEMessage = {
|
|
4
|
+
/** Event id from server */
|
|
5
|
+
id: string;
|
|
6
|
+
/** Raw data payload */
|
|
7
|
+
data: string;
|
|
8
|
+
};
|
|
9
|
+
/** Options for opening a resilient SSE connection */
|
|
10
|
+
interface ConnectSSEOptions<Payload = unknown> {
|
|
11
|
+
/** SSE endpoint URL */
|
|
12
|
+
url: string;
|
|
13
|
+
/** Optional request payload for POST */
|
|
14
|
+
payload?: Payload | string;
|
|
15
|
+
/** Called for each SSE message. */
|
|
16
|
+
onMessage: (message: SSEMessage) => Promise<void>;
|
|
17
|
+
/** Abort signal to stop streaming and retries. */
|
|
18
|
+
signal?: AbortSignal;
|
|
19
|
+
/** Last event id used to resume the stream. */
|
|
20
|
+
lastEventId?: string | null;
|
|
21
|
+
/** Base delay in ms for retry backoff. */
|
|
22
|
+
retryDelay?: number;
|
|
23
|
+
/** Maximum number of retry attempts */
|
|
24
|
+
maxRetries?: number;
|
|
25
|
+
/** Request timeout in ms */
|
|
26
|
+
timeout?: number;
|
|
27
|
+
/** Max in-memory buffer size before error. */
|
|
28
|
+
maxBufferSize?: number;
|
|
29
|
+
/** Called when a connection or parsing error occurs. */
|
|
30
|
+
onError?: (error: unknown) => void;
|
|
31
|
+
}
|
|
32
|
+
//#endregion
|
|
33
|
+
export { ConnectSSEOptions, SSEMessage };
|
|
34
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","names":[],"sources":["../../../src/js/sse/types.ts"],"sourcesContent":[],"mappings":";;AACY,KAAA,UAAA,GAAU;EAQL;EAAiB,EAAA,EAAA,MAAA;;MAMX,EAAA,MAAA;;;AAED,UARL,iBAQK,CAAA,UAAA,OAAA,CAAA,CAAA;;;;YAJV;;uBAEW,eAAe;;WAE3B"}
|