@databricks/appkit-ui 0.1.4 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (194) hide show
  1. package/AGENTS.md +89 -12
  2. package/CLAUDE.md +89 -12
  3. package/NOTICE.md +4 -0
  4. package/README.md +21 -15
  5. package/dist/js/arrow/arrow-client.js.map +1 -1
  6. package/dist/js/arrow/lazy-arrow.js.map +1 -1
  7. package/dist/js/sse/connect-sse.js.map +1 -1
  8. package/dist/react/charts/area/index.d.ts +2 -2
  9. package/dist/react/charts/bar/index.d.ts +2 -2
  10. package/dist/react/charts/base.d.ts +2 -2
  11. package/dist/react/charts/base.js.map +1 -1
  12. package/dist/react/charts/create-chart.d.ts +2 -2
  13. package/dist/react/charts/create-chart.js +2 -1
  14. package/dist/react/charts/create-chart.js.map +1 -1
  15. package/dist/react/charts/heatmap/index.d.ts +2 -2
  16. package/dist/react/charts/line/index.d.ts +2 -2
  17. package/dist/react/charts/normalize.d.ts +1 -1
  18. package/dist/react/charts/normalize.js +1 -1
  19. package/dist/react/charts/normalize.js.map +1 -1
  20. package/dist/react/charts/pie/index.d.ts +3 -3
  21. package/dist/react/charts/radar/index.d.ts +2 -2
  22. package/dist/react/charts/scatter/index.d.ts +2 -2
  23. package/dist/react/charts/theme.js.map +1 -1
  24. package/dist/react/charts/types.d.ts +5 -0
  25. package/dist/react/charts/types.d.ts.map +1 -1
  26. package/dist/react/charts/types.js.map +1 -1
  27. package/dist/react/charts/utils.js.map +1 -1
  28. package/dist/react/charts/wrapper.d.ts +4 -2
  29. package/dist/react/charts/wrapper.d.ts.map +1 -1
  30. package/dist/react/charts/wrapper.js +4 -2
  31. package/dist/react/charts/wrapper.js.map +1 -1
  32. package/dist/react/hooks/types.d.ts +2 -0
  33. package/dist/react/hooks/types.d.ts.map +1 -1
  34. package/dist/react/hooks/use-analytics-query.js +9 -3
  35. package/dist/react/hooks/use-analytics-query.js.map +1 -1
  36. package/dist/react/hooks/use-chart-data.d.ts +2 -0
  37. package/dist/react/hooks/use-chart-data.d.ts.map +1 -1
  38. package/dist/react/hooks/use-chart-data.js +3 -2
  39. package/dist/react/hooks/use-chart-data.js.map +1 -1
  40. package/dist/react/hooks/use-mobile.js +3 -3
  41. package/dist/react/hooks/use-mobile.js.map +1 -1
  42. package/dist/react/index.d.ts +3 -1
  43. package/dist/react/index.js +3 -1
  44. package/dist/react/lib/utils.d.ts +7 -0
  45. package/dist/react/lib/utils.d.ts.map +1 -0
  46. package/dist/react/portal-container-context.d.ts +48 -0
  47. package/dist/react/portal-container-context.d.ts.map +1 -0
  48. package/dist/react/portal-container-context.js +51 -0
  49. package/dist/react/portal-container-context.js.map +1 -0
  50. package/dist/react/table/data-table.d.ts +2 -2
  51. package/dist/react/table/data-table.d.ts.map +1 -1
  52. package/dist/react/table/table-wrapper.js +3 -2
  53. package/dist/react/table/table-wrapper.js.map +1 -1
  54. package/dist/react/table/types.d.ts.map +1 -1
  55. package/dist/react/ui/accordion.d.ts +6 -6
  56. package/dist/react/ui/accordion.d.ts.map +1 -1
  57. package/dist/react/ui/alert-dialog.d.ts +14 -13
  58. package/dist/react/ui/alert-dialog.d.ts.map +1 -1
  59. package/dist/react/ui/alert-dialog.js +3 -1
  60. package/dist/react/ui/alert-dialog.js.map +1 -1
  61. package/dist/react/ui/alert.d.ts +5 -5
  62. package/dist/react/ui/alert.d.ts.map +1 -1
  63. package/dist/react/ui/aspect-ratio.d.ts +2 -2
  64. package/dist/react/ui/avatar.d.ts +5 -5
  65. package/dist/react/ui/avatar.d.ts.map +1 -1
  66. package/dist/react/ui/badge.d.ts +6 -6
  67. package/dist/react/ui/badge.d.ts.map +1 -1
  68. package/dist/react/ui/breadcrumb.d.ts +10 -10
  69. package/dist/react/ui/breadcrumb.d.ts.map +1 -1
  70. package/dist/react/ui/button-group.d.ts +6 -6
  71. package/dist/react/ui/button.d.ts +6 -6
  72. package/dist/react/ui/button.d.ts.map +1 -1
  73. package/dist/react/ui/calendar.d.ts +6 -6
  74. package/dist/react/ui/calendar.d.ts.map +1 -1
  75. package/dist/react/ui/calendar.js +3 -3
  76. package/dist/react/ui/calendar.js.map +1 -1
  77. package/dist/react/ui/card.d.ts +9 -9
  78. package/dist/react/ui/card.d.ts.map +1 -1
  79. package/dist/react/ui/carousel.d.ts +7 -7
  80. package/dist/react/ui/carousel.d.ts.map +1 -1
  81. package/dist/react/ui/carousel.js +11 -11
  82. package/dist/react/ui/carousel.js.map +1 -1
  83. package/dist/react/ui/chart.d.ts +52 -16
  84. package/dist/react/ui/chart.d.ts.map +1 -1
  85. package/dist/react/ui/chart.js +23 -7
  86. package/dist/react/ui/chart.js.map +1 -1
  87. package/dist/react/ui/checkbox.d.ts +3 -3
  88. package/dist/react/ui/checkbox.d.ts.map +1 -1
  89. package/dist/react/ui/checkbox.js +1 -1
  90. package/dist/react/ui/checkbox.js.map +1 -1
  91. package/dist/react/ui/collapsible.d.ts +4 -4
  92. package/dist/react/ui/command.d.ts +12 -12
  93. package/dist/react/ui/command.d.ts.map +1 -1
  94. package/dist/react/ui/context-menu.d.ts +21 -20
  95. package/dist/react/ui/context-menu.d.ts.map +1 -1
  96. package/dist/react/ui/context-menu.js +11 -6
  97. package/dist/react/ui/context-menu.js.map +1 -1
  98. package/dist/react/ui/dialog.d.ts +14 -13
  99. package/dist/react/ui/dialog.d.ts.map +1 -1
  100. package/dist/react/ui/dialog.js +3 -1
  101. package/dist/react/ui/dialog.js.map +1 -1
  102. package/dist/react/ui/drawer.d.ts +13 -12
  103. package/dist/react/ui/drawer.d.ts.map +1 -1
  104. package/dist/react/ui/drawer.js +3 -1
  105. package/dist/react/ui/drawer.js.map +1 -1
  106. package/dist/react/ui/dropdown-menu.d.ts +21 -20
  107. package/dist/react/ui/dropdown-menu.d.ts.map +1 -1
  108. package/dist/react/ui/dropdown-menu.js +12 -7
  109. package/dist/react/ui/dropdown-menu.js.map +1 -1
  110. package/dist/react/ui/empty.d.ts +7 -7
  111. package/dist/react/ui/field.d.ts +11 -11
  112. package/dist/react/ui/form.d.ts +9 -9
  113. package/dist/react/ui/form.d.ts.map +1 -1
  114. package/dist/react/ui/form.js +6 -6
  115. package/dist/react/ui/form.js.map +1 -1
  116. package/dist/react/ui/hover-card.d.ts +5 -5
  117. package/dist/react/ui/hover-card.d.ts.map +1 -1
  118. package/dist/react/ui/hover-card.js +2 -0
  119. package/dist/react/ui/hover-card.js.map +1 -1
  120. package/dist/react/ui/input-group.d.ts +11 -11
  121. package/dist/react/ui/input-group.d.ts.map +1 -1
  122. package/dist/react/ui/input-otp.d.ts +8 -8
  123. package/dist/react/ui/input-otp.d.ts.map +1 -1
  124. package/dist/react/ui/input-otp.js +2 -2
  125. package/dist/react/ui/input-otp.js.map +1 -1
  126. package/dist/react/ui/input.d.ts +3 -3
  127. package/dist/react/ui/input.d.ts.map +1 -1
  128. package/dist/react/ui/item.d.ts +16 -16
  129. package/dist/react/ui/item.d.ts.map +1 -1
  130. package/dist/react/ui/kbd.d.ts +3 -3
  131. package/dist/react/ui/label.d.ts +3 -3
  132. package/dist/react/ui/label.d.ts.map +1 -1
  133. package/dist/react/ui/menubar.d.ts +22 -21
  134. package/dist/react/ui/menubar.d.ts.map +1 -1
  135. package/dist/react/ui/menubar.js +3 -1
  136. package/dist/react/ui/menubar.js.map +1 -1
  137. package/dist/react/ui/navigation-menu.d.ts +11 -11
  138. package/dist/react/ui/navigation-menu.d.ts.map +1 -1
  139. package/dist/react/ui/pagination.d.ts +10 -10
  140. package/dist/react/ui/pagination.d.ts.map +1 -1
  141. package/dist/react/ui/popover.d.ts +6 -6
  142. package/dist/react/ui/popover.d.ts.map +1 -1
  143. package/dist/react/ui/popover.js +11 -7
  144. package/dist/react/ui/popover.js.map +1 -1
  145. package/dist/react/ui/progress.d.ts +3 -3
  146. package/dist/react/ui/progress.d.ts.map +1 -1
  147. package/dist/react/ui/radio-group.d.ts +4 -4
  148. package/dist/react/ui/radio-group.d.ts.map +1 -1
  149. package/dist/react/ui/resizable.d.ts +6 -6
  150. package/dist/react/ui/resizable.d.ts.map +1 -1
  151. package/dist/react/ui/scroll-area.d.ts +4 -4
  152. package/dist/react/ui/scroll-area.d.ts.map +1 -1
  153. package/dist/react/ui/select.d.ts +13 -13
  154. package/dist/react/ui/select.d.ts.map +1 -1
  155. package/dist/react/ui/select.js +19 -15
  156. package/dist/react/ui/select.js.map +1 -1
  157. package/dist/react/ui/separator.d.ts +3 -3
  158. package/dist/react/ui/separator.d.ts.map +1 -1
  159. package/dist/react/ui/sheet.d.ts +11 -11
  160. package/dist/react/ui/sheet.d.ts.map +1 -1
  161. package/dist/react/ui/sheet.js +3 -1
  162. package/dist/react/ui/sheet.js.map +1 -1
  163. package/dist/react/ui/sidebar.d.ts +34 -34
  164. package/dist/react/ui/sidebar.d.ts.map +1 -1
  165. package/dist/react/ui/sidebar.js +10 -10
  166. package/dist/react/ui/sidebar.js.map +1 -1
  167. package/dist/react/ui/skeleton.d.ts +2 -2
  168. package/dist/react/ui/slider.d.ts +3 -3
  169. package/dist/react/ui/slider.d.ts.map +1 -1
  170. package/dist/react/ui/slider.js +2 -2
  171. package/dist/react/ui/slider.js.map +1 -1
  172. package/dist/react/ui/sonner.d.ts +2 -2
  173. package/dist/react/ui/spinner.d.ts +2 -2
  174. package/dist/react/ui/switch.d.ts +3 -3
  175. package/dist/react/ui/switch.d.ts.map +1 -1
  176. package/dist/react/ui/table.d.ts +10 -10
  177. package/dist/react/ui/table.d.ts.map +1 -1
  178. package/dist/react/ui/tabs.d.ts +6 -6
  179. package/dist/react/ui/tabs.d.ts.map +1 -1
  180. package/dist/react/ui/textarea.d.ts +3 -3
  181. package/dist/react/ui/textarea.d.ts.map +1 -1
  182. package/dist/react/ui/toggle-group.d.ts +5 -5
  183. package/dist/react/ui/toggle-group.d.ts.map +1 -1
  184. package/dist/react/ui/toggle-group.js +3 -3
  185. package/dist/react/ui/toggle-group.js.map +1 -1
  186. package/dist/react/ui/toggle.d.ts +3 -3
  187. package/dist/react/ui/toggle.d.ts.map +1 -1
  188. package/dist/react/ui/tooltip.d.ts +6 -6
  189. package/dist/react/ui/tooltip.d.ts.map +1 -1
  190. package/dist/react/ui/tooltip.js +11 -7
  191. package/dist/react/ui/tooltip.js.map +1 -1
  192. package/dist/shared/src/sql/helpers.js.map +1 -1
  193. package/llms.txt +89 -12
  194. package/package.json +1 -1
package/AGENTS.md CHANGED
@@ -440,23 +440,49 @@ Formats:
440
440
  - `format: "JSON"` (default) returns JSON rows
441
441
  - `format: "ARROW"` returns an Arrow “statement_id” payload over SSE, then the client fetches binary Arrow from `/api/analytics/arrow-result/:jobId`
442
442
 
443
- ### Request context (`getRequestContext()`)
443
+ ### Execution context and `asUser(req)`
444
444
 
445
- If a plugin sets `requiresDatabricksClient = true`, AppKit adds middleware that provides request context.
445
+ AppKit manages Databricks authentication via two contexts:
446
446
 
447
- Headers used:
447
+ - **ServiceContext** (singleton): Initialized at app startup with service principal credentials
448
+ - **ExecutionContext**: Determined at runtime - either service principal or user context
449
+
450
+ **Headers used for user context:**
448
451
 
449
452
  - `x-forwarded-user`: required in production; identifies the user
450
- - `x-forwarded-access-token`: optional; enables **user token passthrough** if `DATABRICKS_HOST` is set
453
+ - `x-forwarded-access-token`: required for user token passthrough
454
+
455
+ **Using `asUser(req)` for user-scoped operations:**
456
+
457
+ The `asUser(req)` pattern allows plugins to execute operations using the requesting user's credentials:
458
+
459
+ ```ts
460
+ // In a custom plugin route handler
461
+ router.post("/users/me/data", async (req, res) => {
462
+ // Execute as the user (uses their Databricks permissions)
463
+ const result = await this.asUser(req).query("SELECT ...");
464
+ res.json(result);
465
+ });
466
+
467
+ // Service principal execution (default)
468
+ router.post("/system/data", async (req, res) => {
469
+ const result = await this.query("SELECT ...");
470
+ res.json(result);
471
+ });
472
+ ```
473
+
474
+ **Context helper functions (exported from `@databricks/appkit`):**
451
475
 
452
- Context fields (real behavior):
476
+ - `getExecutionContext()`: Returns current context (user or service)
477
+ - `getCurrentUserId()`: Returns user ID in user context, service user ID otherwise
478
+ - `getWorkspaceClient()`: Returns the appropriate WorkspaceClient for current context
479
+ - `getWarehouseId()`: `Promise<string>` (from `DATABRICKS_WAREHOUSE_ID` or auto-selected in dev)
480
+ - `getWorkspaceId()`: `Promise<string>` (from `DATABRICKS_WORKSPACE_ID` or fetched)
481
+ - `isInUserContext()`: Returns `true` if currently executing in user context
453
482
 
454
- - `userId`: derived from `x-forwarded-user` (in development it falls back to `serviceUserId`)
455
- - `serviceUserId`: service principal/user ID
456
- - `warehouseId`: `Promise<string>` (from `DATABRICKS_WAREHOUSE_ID`, or auto-selected in development)
457
- - `workspaceId`: `Promise<string>` (from `DATABRICKS_WORKSPACE_ID` or fetched)
458
- - `userDatabricksClient`: present only when passthrough is available (or in dev it equals service client)
459
- - `serviceDatabricksClient`: always present
483
+ **Development mode behavior:**
484
+
485
+ In local development (`NODE_ENV=development`), if `asUser(req)` is called without a user token, it logs a warning and falls back to the service principal.
460
486
 
461
487
  ### Custom plugins (backend)
462
488
 
@@ -469,7 +495,6 @@ import type express from "express";
469
495
  class MyPlugin extends Plugin {
470
496
  name = "my-plugin";
471
497
  envVars = []; // list required env vars here
472
- requiresDatabricksClient = false; // set true if you need getRequestContext()
473
498
 
474
499
  injectRoutes(router: express.Router) {
475
500
  this.route(router, {
@@ -643,6 +668,56 @@ export function SpendChart() {
643
668
  }
644
669
  ```
645
670
 
671
+ **Chart props reference (important):**
672
+
673
+ Charts are **self-contained ECharts components**. Configure via props, NOT children:
674
+
675
+ ```tsx
676
+ // ✅ Correct: use props for customization
677
+ <BarChart
678
+ queryKey="sales_by_region"
679
+ parameters={{}}
680
+ xKey="region" // X-axis field
681
+ yKey={["revenue", "expenses"]} // Y-axis field(s) - string or string[]
682
+ colors={['#40d1f5', '#4462c9']} // Custom colors
683
+ stacked // Stack bars (BarChart, AreaChart)
684
+ orientation="horizontal" // "vertical" (default) | "horizontal"
685
+ showLegend // Show legend
686
+ height={400} // Height in pixels (default: 300)
687
+ />
688
+
689
+ <LineChart
690
+ queryKey="trend_data"
691
+ parameters={{}}
692
+ xKey="date"
693
+ yKey="value"
694
+ smooth // Smooth curves (default: true)
695
+ showSymbol={false} // Hide data point markers
696
+ />
697
+ ```
698
+
699
+ **❌ CRITICAL: Charts do NOT accept Recharts children**
700
+
701
+ ```tsx
702
+ // ❌ WRONG - AppKit charts are NOT Recharts wrappers
703
+ import { BarChart } from "@databricks/appkit-ui/react";
704
+ import { Bar, XAxis, YAxis, CartesianGrid } from "recharts";
705
+
706
+ <BarChart queryKey="data" parameters={{}}>
707
+ <CartesianGrid /> // ❌ This will cause TypeScript errors
708
+ <XAxis dataKey="x" /> // ❌ Not supported
709
+ <Bar dataKey="y" /> // ❌ Not supported
710
+ </BarChart>
711
+
712
+ // ✅ CORRECT - use props instead
713
+ <BarChart
714
+ queryKey="data"
715
+ parameters={{}}
716
+ xKey="x"
717
+ yKey="y"
718
+ />
719
+ ```
720
+
646
721
  ### SQL helpers (`sql.*`)
647
722
 
648
723
  Use these to build typed parameters (they return marker objects: `{ __sql_type, value }`):
@@ -1144,6 +1219,7 @@ env:
1144
1219
  - `useMemo` wraps parameters objects
1145
1220
  - Loading/error/empty states are explicit
1146
1221
  - Charts use `format="auto"` unless you have a reason to force `"json"`/`"arrow"`
1222
+ - Charts use props (`xKey`, `yKey`, `colors`) NOT children (they're ECharts-based, not Recharts)
1147
1223
  - If using tooltips: root is wrapped with `<TooltipProvider>`
1148
1224
 
1149
1225
  - **Never**
@@ -1151,4 +1227,5 @@ env:
1151
1227
  - Don't pass untyped raw params for annotated queries
1152
1228
  - Don't ignore `createApp()`'s promise
1153
1229
  - Don't invent UI components not listed in this file
1230
+ - Don't pass Recharts children (`<Bar>`, `<XAxis>`, etc.) to AppKit chart components
1154
1231
 
package/CLAUDE.md CHANGED
@@ -440,23 +440,49 @@ Formats:
440
440
  - `format: "JSON"` (default) returns JSON rows
441
441
  - `format: "ARROW"` returns an Arrow “statement_id” payload over SSE, then the client fetches binary Arrow from `/api/analytics/arrow-result/:jobId`
442
442
 
443
- ### Request context (`getRequestContext()`)
443
+ ### Execution context and `asUser(req)`
444
444
 
445
- If a plugin sets `requiresDatabricksClient = true`, AppKit adds middleware that provides request context.
445
+ AppKit manages Databricks authentication via two contexts:
446
446
 
447
- Headers used:
447
+ - **ServiceContext** (singleton): Initialized at app startup with service principal credentials
448
+ - **ExecutionContext**: Determined at runtime - either service principal or user context
449
+
450
+ **Headers used for user context:**
448
451
 
449
452
  - `x-forwarded-user`: required in production; identifies the user
450
- - `x-forwarded-access-token`: optional; enables **user token passthrough** if `DATABRICKS_HOST` is set
453
+ - `x-forwarded-access-token`: required for user token passthrough
454
+
455
+ **Using `asUser(req)` for user-scoped operations:**
456
+
457
+ The `asUser(req)` pattern allows plugins to execute operations using the requesting user's credentials:
458
+
459
+ ```ts
460
+ // In a custom plugin route handler
461
+ router.post("/users/me/data", async (req, res) => {
462
+ // Execute as the user (uses their Databricks permissions)
463
+ const result = await this.asUser(req).query("SELECT ...");
464
+ res.json(result);
465
+ });
466
+
467
+ // Service principal execution (default)
468
+ router.post("/system/data", async (req, res) => {
469
+ const result = await this.query("SELECT ...");
470
+ res.json(result);
471
+ });
472
+ ```
473
+
474
+ **Context helper functions (exported from `@databricks/appkit`):**
451
475
 
452
- Context fields (real behavior):
476
+ - `getExecutionContext()`: Returns current context (user or service)
477
+ - `getCurrentUserId()`: Returns user ID in user context, service user ID otherwise
478
+ - `getWorkspaceClient()`: Returns the appropriate WorkspaceClient for current context
479
+ - `getWarehouseId()`: `Promise<string>` (from `DATABRICKS_WAREHOUSE_ID` or auto-selected in dev)
480
+ - `getWorkspaceId()`: `Promise<string>` (from `DATABRICKS_WORKSPACE_ID` or fetched)
481
+ - `isInUserContext()`: Returns `true` if currently executing in user context
453
482
 
454
- - `userId`: derived from `x-forwarded-user` (in development it falls back to `serviceUserId`)
455
- - `serviceUserId`: service principal/user ID
456
- - `warehouseId`: `Promise<string>` (from `DATABRICKS_WAREHOUSE_ID`, or auto-selected in development)
457
- - `workspaceId`: `Promise<string>` (from `DATABRICKS_WORKSPACE_ID` or fetched)
458
- - `userDatabricksClient`: present only when passthrough is available (or in dev it equals service client)
459
- - `serviceDatabricksClient`: always present
483
+ **Development mode behavior:**
484
+
485
+ In local development (`NODE_ENV=development`), if `asUser(req)` is called without a user token, it logs a warning and falls back to the service principal.
460
486
 
461
487
  ### Custom plugins (backend)
462
488
 
@@ -469,7 +495,6 @@ import type express from "express";
469
495
  class MyPlugin extends Plugin {
470
496
  name = "my-plugin";
471
497
  envVars = []; // list required env vars here
472
- requiresDatabricksClient = false; // set true if you need getRequestContext()
473
498
 
474
499
  injectRoutes(router: express.Router) {
475
500
  this.route(router, {
@@ -643,6 +668,56 @@ export function SpendChart() {
643
668
  }
644
669
  ```
645
670
 
671
+ **Chart props reference (important):**
672
+
673
+ Charts are **self-contained ECharts components**. Configure via props, NOT children:
674
+
675
+ ```tsx
676
+ // ✅ Correct: use props for customization
677
+ <BarChart
678
+ queryKey="sales_by_region"
679
+ parameters={{}}
680
+ xKey="region" // X-axis field
681
+ yKey={["revenue", "expenses"]} // Y-axis field(s) - string or string[]
682
+ colors={['#40d1f5', '#4462c9']} // Custom colors
683
+ stacked // Stack bars (BarChart, AreaChart)
684
+ orientation="horizontal" // "vertical" (default) | "horizontal"
685
+ showLegend // Show legend
686
+ height={400} // Height in pixels (default: 300)
687
+ />
688
+
689
+ <LineChart
690
+ queryKey="trend_data"
691
+ parameters={{}}
692
+ xKey="date"
693
+ yKey="value"
694
+ smooth // Smooth curves (default: true)
695
+ showSymbol={false} // Hide data point markers
696
+ />
697
+ ```
698
+
699
+ **❌ CRITICAL: Charts do NOT accept Recharts children**
700
+
701
+ ```tsx
702
+ // ❌ WRONG - AppKit charts are NOT Recharts wrappers
703
+ import { BarChart } from "@databricks/appkit-ui/react";
704
+ import { Bar, XAxis, YAxis, CartesianGrid } from "recharts";
705
+
706
+ <BarChart queryKey="data" parameters={{}}>
707
+ <CartesianGrid /> // ❌ This will cause TypeScript errors
708
+ <XAxis dataKey="x" /> // ❌ Not supported
709
+ <Bar dataKey="y" /> // ❌ Not supported
710
+ </BarChart>
711
+
712
+ // ✅ CORRECT - use props instead
713
+ <BarChart
714
+ queryKey="data"
715
+ parameters={{}}
716
+ xKey="x"
717
+ yKey="y"
718
+ />
719
+ ```
720
+
646
721
  ### SQL helpers (`sql.*`)
647
722
 
648
723
  Use these to build typed parameters (they return marker objects: `{ __sql_type, value }`):
@@ -1144,6 +1219,7 @@ env:
1144
1219
  - `useMemo` wraps parameters objects
1145
1220
  - Loading/error/empty states are explicit
1146
1221
  - Charts use `format="auto"` unless you have a reason to force `"json"`/`"arrow"`
1222
+ - Charts use props (`xKey`, `yKey`, `colors`) NOT children (they're ECharts-based, not Recharts)
1147
1223
  - If using tooltips: root is wrapped with `<TooltipProvider>`
1148
1224
 
1149
1225
  - **Never**
@@ -1151,4 +1227,5 @@ env:
1151
1227
  - Don't pass untyped raw params for annotated queries
1152
1228
  - Don't ignore `createApp()`'s promise
1153
1229
  - Don't invent UI components not listed in this file
1230
+ - Don't pass Recharts children (`<Bar>`, `<XAxis>`, etc.) to AppKit chart components
1154
1231
 
package/NOTICE.md CHANGED
@@ -6,6 +6,7 @@ This Software contains code from the following open source projects:
6
6
 
7
7
  | Name | Installed version | License | Code |
8
8
  | :--------------- | :---------------- | :----------- | :--------------------------------------------------- |
9
+ | [@ast-grep/napi](https://www.npmjs.com/package/@ast-grep/napi) | 0.37.0 | MIT | https://ast-grep.github.io |
9
10
  | [@hookform/resolvers](https://www.npmjs.com/package/@hookform/resolvers) | 5.2.2 | MIT | https://react-hook-form.com |
10
11
  | [@opentelemetry/api](https://www.npmjs.com/package/@opentelemetry/api) | 1.9.0 | Apache-2.0 | https://github.com/open-telemetry/opentelemetry-js/tree/main/api |
11
12
  | [@opentelemetry/api-logs](https://www.npmjs.com/package/@opentelemetry/api-logs) | 0.208.0 | Apache-2.0 | https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/api-logs |
@@ -49,6 +50,7 @@ This Software contains code from the following open source projects:
49
50
  | [@radix-ui/react-tooltip](https://www.npmjs.com/package/@radix-ui/react-tooltip) | 1.2.8 | MIT | https://radix-ui.com/primitives |
50
51
  | [@tanstack/react-table](https://www.npmjs.com/package/@tanstack/react-table) | 8.21.3 | MIT | https://tanstack.com/table |
51
52
  | [@tanstack/react-virtual](https://www.npmjs.com/package/@tanstack/react-virtual) | 3.13.12 | MIT | https://tanstack.com/virtual |
53
+ | [@types/semver](https://www.npmjs.com/package/@types/semver) | 7.7.1 | MIT | https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/semver |
52
54
  | [apache-arrow](https://www.npmjs.com/package/apache-arrow) | 21.1.0 | Apache-2.0 | https://arrow.apache.org/js/ |
53
55
  | [class-variance-authority](https://www.npmjs.com/package/class-variance-authority) | 0.7.1 | Apache-2.0 | https://github.com/joe-bell/cva#readme |
54
56
  | [clsx](https://www.npmjs.com/package/clsx) | 2.1.1 | MIT | https://github.com/lukeed/clsx#readme |
@@ -62,10 +64,12 @@ This Software contains code from the following open source projects:
62
64
  | [input-otp](https://www.npmjs.com/package/input-otp) | 1.4.2 | MIT | https://input-otp.rodz.dev/ |
63
65
  | [lucide-react](https://www.npmjs.com/package/lucide-react) | 0.554.0 | ISC | https://lucide.dev |
64
66
  | [next-themes](https://www.npmjs.com/package/next-themes) | 0.4.6 | MIT | https://github.com/pacocoursey/next-themes#readme |
67
+ | [obug](https://www.npmjs.com/package/obug) | 2.1.1 | MIT | https://github.com/sxzz/obug#readme |
65
68
  | [pg](https://www.npmjs.com/package/pg) | 8.16.3 | MIT | https://github.com/brianc/node-postgres |
66
69
  | [react-day-picker](https://www.npmjs.com/package/react-day-picker) | 9.12.0 | MIT | https://daypicker.dev |
67
70
  | [react-hook-form](https://www.npmjs.com/package/react-hook-form) | 7.68.0 | MIT | https://react-hook-form.com |
68
71
  | [react-resizable-panels](https://www.npmjs.com/package/react-resizable-panels) | 3.0.6 | MIT | https://github.com/bvaughn/react-resizable-panels#readme |
72
+ | [semver](https://www.npmjs.com/package/semver) | 6.3.1, 7.7.3 | ISC | https://github.com/npm/node-semver#readme |
69
73
  | [sonner](https://www.npmjs.com/package/sonner) | 2.0.7 | MIT | https://sonner.emilkowal.ski/ |
70
74
  | [tailwind-merge](https://www.npmjs.com/package/tailwind-merge) | 3.4.0 | MIT | https://github.com/dcastil/tailwind-merge |
71
75
  | [vaul](https://www.npmjs.com/package/vaul) | 1.1.2 | MIT | https://vaul.emilkowal.ski/ |
package/README.md CHANGED
@@ -1,7 +1,8 @@
1
1
  # AppKit
2
2
 
3
- > [!WARNING]
4
- > ## ⚠️ PREVIEW - NOT FOR PRODUCTION USE
3
+ Build Databricks Apps faster with our brand-new Node.js + React SDK. Built for humans and AI.
4
+
5
+ > [!WARNING] PREVIEW - NOT FOR PRODUCTION USE
5
6
  >
6
7
  > **This SDK is in preview and is subject to change without notice.**
7
8
  >
@@ -11,25 +12,30 @@
11
12
  > - 📝 **Use for development and testing only**
12
13
  >
13
14
 
14
- ## Contributing
15
+ ## Introduction
15
16
 
16
- See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and contribution guidelines.
17
+ AppKit is a TypeScript SDK for building production-ready Databricks applications with a plugin-based architecture. It provides opinionated defaults, built-in observability, and seamless integration with Databricks services.
17
18
 
18
- ## Documentation
19
+ AppKit simplifies building data applications on Databricks by providing:
20
+
21
+ - **Plugin architecture**: Modular design with built-in server and analytics plugins
22
+ - **Type safety**: End-to-end TypeScript with automatic query type generation
23
+ - **Production-ready features**: Built-in caching, telemetry, retry logic, and error handling
24
+ - **Developer experience**: Remote hot reload, file-based queries, optimized for AI-assisted development
25
+ - **Databricks native**: Seamless integration with SQL Warehouses, Unity Catalog, and other workspace resources
19
26
 
20
- The `docs/` directory contains the AppKit documentation site, built with Docusaurus.
27
+ ## Getting started
21
28
 
22
- **Working with docs:**
29
+ Follow the [Getting Started](https://databricks.github.io/appkit/docs/) guide to get started with AppKit.
23
30
 
24
- ```bash
25
- # From root
26
- pnpm docs:dev # Start dev server
27
- pnpm docs:build # Build docs
28
- pnpm docs:serve # Serve built docs
29
- ```
31
+ ## Documentation
30
32
 
31
- See [docs/README.md](./docs/README.md) for more details.
33
+ 📖 For full AppKit documentation, visit the [AppKit Documentation](https://databricks.github.io/appkit/) website.
32
34
 
33
35
  👉 For AI/code assistants:
34
36
  - Use [llms-compact.txt](./llms-compact.txt) for quick usage patterns.
35
- - See [llms.txt](./llms.txt) for full guidance and anti-patterns.
37
+ - See [llms.txt](./llms.txt) for full guidance and anti-patterns.
38
+
39
+ ## Contributing
40
+
41
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and contribution guidelines.
@@ -1 +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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"}
1
+ {"version":3,"file":"arrow-client.js","names":["xField"],"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":";;;;AAmBA,IAAa,cAAb,MAAa,YAAY;;;;;;;;CAQvB,aAAa,mBAAmB,QAAoC;AAClE,MAAI;GACF,MAAM,QAAQ,MAAM,gBAAgB;AAEpC,SAAM,sBAAsB;AAC5B,UAAO,MAAM,aAAa,OAAO;WAC1B,OAAO;AACd,SAAM,IAAI,MACR,mCACE,iBAAiB,QAAQ,MAAM,UAAU,kBAE5C;;;CAIL,aAAa,qBACX,KACA,SACgB;AAChB,MAAI;GACF,MAAM,SAAS,MAAM,YAAY,WAAW,KAAK,QAAQ;AAEzD,UAAO,YAAY,mBAAmB,OAAO;WACtC,OAAO;AACd,SAAM,IAAI,MACR,+BACE,iBAAiB,QAAQ,MAAM,UAAU,kBAE5C;;;CAIL,OAAO,mBAAmB,OAAc;AACtC,SAAO,MAAM,OAAO,OAAO,KAAK,UAAiB;AAC/C,UAAO;IACL,MAAM,MAAM;IACZ,MAAM,MAAM;IACb;IACD;;CAGJ,OAAO,oBAAoB,OAAmC;EAC5D,MAAM,OAA4B,EAAE;AAEpC,OAAK,MAAM,SAAS,MAAM,OAAO,QAAQ;GACvC,MAAM,QAAQ,MAAM,SAAS,MAAM,KAAK;AAExC,OAAI,MACF,MAAK,MAAM,QAAQ,MAAM,SAAS;;AAItC,SAAO;;;;;;;;;;;CAYT,OAAO,iBAAiB,OAAc,MAAc,OAAiB;AAEnE,MAAI,MAAM,YAAY,EACpB,QAAO;EAIT,MAAM,cAAc,kBAAkB;EAGtC,MAAM,kCAAkB,IAAI,KAAqB;AACjD,OAAK,MAAM,SAAS,MAAM,OAAO,OAC/B,KAAI,MAAM,WAAW,aAAa;GAChC,MAAM,UAAU,MAAM;AACtB,OAAI,OAAO,QAAQ,UAAU,SAE3B,iBAAgB,IAAI,MAAM,MAAM,MAAM,QAAQ,MAAM;;EAO1D,MAAM,QAAQ,oBADD,MAAM,SAAS,KAAK,EACO,gBAAgB,IAAI,KAAK,CAAC;EAGlE,MAAM,WAAgD,EAAE;AACxD,OAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;GACrC,MAAM,MAAM,MAAM;AAElB,YAAS,OAAO,oBADJ,MAAM,SAAS,IAAI,EACU,gBAAgB,IAAI,IAAI,CAAC;;AAGpE,SAAO;GAAE;GAAO;GAAU;;;;;;;;;;;;;;;;;;;;CAqB5B,OAAO,sBACL,OACA,aAC8D;EAC9D,MAAM,SAAS,MAAM,OAAO;AAE5B,MAAI,OAAO,WAAW,EACpB,QAAO;GAAE,QAAQ;GAAK,SAAS,CAAC,IAAI;GAAE,WAAW;GAAe;EAGlE,MAAM,aAAa,OAAO,KAAK,MAAM,EAAE,KAAK;EAG5C,MAAM,aAAa,eAAe;EAGlC,MAAM,iBAA2B,EAAE;EACnC,MAAM,gBAA0B,EAAE;EAClC,MAAM,eAAyB,EAAE;AAEjC,OAAK,MAAM,SAAS,QAAQ;GAC1B,MAAM,SAAS,MAAM;AAErB,OAAI,WAAW,SAAS,IAAI,OAAO,CACjC,gBAAe,KAAK,MAAM,KAAK;YACtB,WAAW,QAAQ,IAAI,OAAO,CACvC,eAAc,KAAK,MAAM,KAAK;YACrB,WAAW,OAAO,IAAI,OAAO,CACtC,cAAa,KAAK,MAAM,KAAK;;EAKjC,IAAI,aAAa,aAAa,QAAQ,SACpC,oBAAoB,MAAM,YACxB,KAAK,aAAa,CAAC,SAAS,QAAQ,CACrC,CACF;AAGD,MAAI,WAAW,WAAW,EACxB,cAAa,aAAa,QACvB,SAAS,CAAC,KAAK,aAAa,CAAC,SAAS,MAAM,CAC9C;EAIH,MAAM,kBAAkB,eAAe,QACpC,SACC,CAAC,uBAAuB,MAAM,YAC5B,KAAK,aAAa,CAAC,SAAS,QAAQ,CACrC,CACJ;EACD,MAAM,qBAAqB,eAAe,QAAQ,SAChD,uBAAuB,MAAM,YAC3B,KAAK,aAAa,CAAC,SAAS,QAAQ,CACrC,CACF;EAGD,MAAM,mBAAmB,aAAa,QACnC,SACC,oBAAoB,MAAM,YACxB,KAAK,aAAa,CAAC,SAAS,QAAQ,CACrC,IACD,CAAC,uBAAuB,MAAM,YAC5B,KAAK,aAAa,CAAC,SAAS,QAAQ,CACrC,CACJ;EAED,MAAM,oBAAoB,CAAC,GAAG,iBAAiB,GAAG,iBAAiB;EAInE,MAAM,eACJ,kBAAkB,SAAS,KAAK,gBAAgB;EAClD,MAAM,gBACJ,WAAW,SAAS,MACnB,kBAAkB,WAAW,KAAK,gBAAgB;AAErD,MAAI,gBAAgB,gBAAgB,eAAe;GAEjD,MAAMA,WACJ,WAAW,MACX,kBAAkB,MAClB,mBAAmB,MACnB,WAAW;AAKb,UAAO;IAAE;IAAQ,SAHf,cAAc,SAAS,IACnB,gBACA,WAAW,QAAQ,MAAM,MAAMA,SAAO;IAClB,WAAW;IAAe;;EAItD,MAAM,SACJ,kBAAkB,MAClB,mBAAmB,MACnB,WAAW,MACX,WAAW;AAKb,SAAO;GACL;GACA,SALA,cAAc,SAAS,IACnB,gBACA,WAAW,QAAQ,MAAM,MAAM,OAAO;GAI1C,WAAW,eAAe,eAAe;GAC1C;;CAGH,aAAa,WACX,KACA,SACqB;AACrB,MAAI;GACF,MAAM,WAAW,MAAM,MAAM,KAAK,EAChC,SAAS;IAAE,gBAAgB;IAA4B,GAAG;IAAS,EACpE,CAAC;AAEF,OAAI,CAAC,SAAS,GACZ,OAAM,IAAI,MAAM,QAAQ,SAAS,OAAO,IAAI,SAAS,aAAa;GAGpE,MAAM,SAAS,MAAM,SAAS,aAAa;AAE3C,UAAO,IAAI,WAAW,OAAO;WACtB,OAAO;AACd,SAAM,IAAI,MACR,+BACE,iBAAiB,QAAQ,MAAM,UAAU,kBAE5C;;;;AAiBP,MAAM,eAGF;CACF,OAAO,EAAE;CACT,UAAU,EAAE;CACb;;;;;;;;;AAUD,SAAS,oBACP,KACA,SACqB;AACrB,KAAI,CAAC,IAAK,QAAO,EAAE;CAGnB,MAAM,MAAM,IAAI;CAChB,MAAM,SAA8B,IAAI,MAAM,IAAI;AAElD,MAAK,IAAI,IAAI,GAAG,IAAI,KAAK,KAAK;EAC5B,MAAM,MAAM,IAAI,IAAI,EAAE;AACtB,MAAI,QAAQ,QAAQ,QAAQ,OAC1B,QAAO,KAAK;WACH,OAAO,QAAQ,UAAU;GAElC,MAAM,MAAM,OAAO,IAAI;AACvB,UAAO,KAAK,YAAY,SAAY,MAAM,UAAU;aAC3C,OAAO,QAAQ,SAExB,QAAO,KAAK,YAAY,SAAY,MAAM,UAAU;WAC3C,OAAO,QAAQ,SACxB,QAAO,KAAK;OACP;GAEL,MAAM,MAAM,OAAO,IAAI;AACvB,UAAO,KAAK,YAAY,SAAY,MAAM,UAAU;;;AAGxD,QAAO"}
@@ -1 +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"}
1
+ {"version":3,"file":"lazy-arrow.js","names":[],"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,IAAI,qBAAoE;;;;;;;;;;;AAYxE,eAAsB,iBAAyD;AAC7E,KAAI,CAAC,mBACH,sBAAqB,OAAO;AAE9B,QAAO;;AAQT,IAAI,kBAAsC;AAC1C,IAAI,iBAAqC;AACzC,IAAI,gBAAoC;AACxC,IAAI,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"}
@@ -1 +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":"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"}
1
+ {"version":3,"file":"connect-sse.js","names":[],"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":"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"}
@@ -1,5 +1,5 @@
1
1
  import { AreaChartProps } from "../types.js";
2
- import * as react_jsx_runtime273 from "react/jsx-runtime";
2
+ import * as react_jsx_runtime275 from "react/jsx-runtime";
3
3
 
4
4
  //#region src/react/charts/area/index.d.ts
5
5
 
@@ -25,7 +25,7 @@ import * as react_jsx_runtime273 from "react/jsx-runtime";
25
25
  * ```
26
26
  */
27
27
  declare const AreaChart: {
28
- (props: AreaChartProps): react_jsx_runtime273.JSX.Element;
28
+ (props: AreaChartProps): react_jsx_runtime275.JSX.Element;
29
29
  displayName: string;
30
30
  };
31
31
  //#endregion
@@ -1,5 +1,5 @@
1
1
  import { BarChartProps } from "../types.js";
2
- import * as react_jsx_runtime274 from "react/jsx-runtime";
2
+ import * as react_jsx_runtime276 from "react/jsx-runtime";
3
3
 
4
4
  //#region src/react/charts/bar/index.d.ts
5
5
 
@@ -35,7 +35,7 @@ import * as react_jsx_runtime274 from "react/jsx-runtime";
35
35
  * ```
36
36
  */
37
37
  declare const BarChart: {
38
- (props: BarChartProps): react_jsx_runtime274.JSX.Element;
38
+ (props: BarChartProps): react_jsx_runtime276.JSX.Element;
39
39
  displayName: string;
40
40
  };
41
41
  //#endregion
@@ -1,5 +1,5 @@
1
1
  import { ChartColorPalette, ChartData, ChartType, Orientation } from "./types.js";
2
- import * as react_jsx_runtime281 from "react/jsx-runtime";
2
+ import * as react_jsx_runtime283 from "react/jsx-runtime";
3
3
 
4
4
  //#region src/react/charts/base.d.ts
5
5
  interface BaseChartProps {
@@ -83,7 +83,7 @@ declare function BaseChart({
83
83
  max,
84
84
  options: customOptions,
85
85
  className
86
- }: BaseChartProps): react_jsx_runtime281.JSX.Element;
86
+ }: BaseChartProps): react_jsx_runtime283.JSX.Element;
87
87
  //#endregion
88
88
  export { BaseChart, BaseChartProps };
89
89
  //# sourceMappingURL=base.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"base.js","names":["baseCtx: OptionBuilderContext","opt: Record<string, unknown>"],"sources":["../../../src/react/charts/base.tsx"],"sourcesContent":["import type { ECharts } from \"echarts\";\nimport ReactECharts from \"echarts-for-react\";\nimport { useCallback, useMemo, useRef } from \"react\";\nimport { normalizeChartData, normalizeHeatmapData } from \"./normalize\";\nimport {\n buildCartesianOption,\n buildHeatmapOption,\n buildHorizontalBarOption,\n buildPieOption,\n buildRadarOption,\n type OptionBuilderContext,\n} from \"./options\";\nimport { useThemeColors } from \"./theme\";\nimport type {\n ChartColorPalette,\n ChartData,\n ChartType,\n Orientation,\n} from \"./types\";\n\n// ============================================================================\n// Palette Selection\n// ============================================================================\n\n/**\n * Determines the appropriate color palette for a chart type.\n * - Heatmaps use sequential (low → high intensity)\n * - All other charts use categorical (distinct categories)\n */\nfunction getDefaultPalette(chartType: ChartType): ChartColorPalette {\n switch (chartType) {\n case \"heatmap\":\n return \"sequential\";\n default:\n return \"categorical\";\n }\n}\n\n// ============================================================================\n// Component Props\n// ============================================================================\n\nexport interface BaseChartProps {\n /** Chart data (Arrow Table or JSON array) - format is auto-detected */\n data: ChartData;\n /** Chart type */\n chartType: ChartType;\n /** X-axis field key (auto-detected from schema if not provided) */\n xKey?: string;\n /** Y-axis field key(s) (auto-detected from schema if not provided) */\n yKey?: string | string[];\n /** Chart orientation @default \"vertical\" */\n orientation?: Orientation;\n /** Chart height in pixels @default 300 */\n height?: number;\n /** Chart title */\n title?: string;\n /** Show legend @default true */\n showLegend?: boolean;\n /**\n * Color palette to use. Auto-selected based on chart type if not specified.\n * - \"categorical\": Distinct colors for different categories (bar, pie, line)\n * - \"sequential\": Gradient for magnitude (heatmap)\n * - \"diverging\": Two-tone for positive/negative (correlation)\n */\n colorPalette?: ChartColorPalette;\n /** Custom colors (overrides colorPalette) */\n colors?: string[];\n /** Show data point symbols (line/area charts) @default false */\n showSymbol?: boolean;\n /** Smooth line curves (line/area charts) @default true */\n smooth?: boolean;\n /** Stack series @default false */\n stacked?: boolean;\n /** Symbol size for scatter charts @default 8 */\n symbolSize?: number;\n /** Show area fill for radar charts @default true */\n showArea?: boolean;\n /** Inner radius for pie/donut (0-100) @default 0 */\n innerRadius?: number;\n /** Show labels on pie/donut slices @default true */\n showLabels?: boolean;\n /** Label position for pie/donut @default \"outside\" */\n labelPosition?: \"outside\" | \"inside\" | \"center\";\n /** Y-axis field key for heatmap (the row dimension) */\n yAxisKey?: string;\n /** Min value for heatmap color scale */\n min?: number;\n /** Max value for heatmap color scale */\n max?: number;\n /** Additional ECharts options to merge */\n options?: Record<string, unknown>;\n /** Additional CSS classes */\n className?: string;\n}\n\n// ============================================================================\n// Base Chart Component\n// ============================================================================\n\n/**\n * Base chart component that handles both Arrow and JSON data.\n * Renders using ECharts for consistent output across both formats.\n */\nexport function BaseChart({\n data,\n chartType,\n xKey,\n yKey,\n orientation,\n height = 300,\n title,\n showLegend = true,\n colorPalette,\n colors: customColors,\n showSymbol = false,\n smooth = true,\n stacked = false,\n symbolSize = 8,\n showArea = true,\n innerRadius = 0,\n showLabels = true,\n labelPosition = \"outside\",\n yAxisKey,\n min,\n max,\n options: customOptions,\n className,\n}: BaseChartProps) {\n // Determine the appropriate color palette based on chart type\n const resolvedPalette = colorPalette ?? getDefaultPalette(chartType);\n const themeColors = useThemeColors(resolvedPalette);\n const colors = customColors ?? themeColors;\n\n // Store ECharts instance directly to avoid stale ref issues on unmount\n const echartsInstanceRef = useRef<ECharts | null>(null);\n\n // Callback ref pattern: captures the ECharts instance when ReactECharts mounts\n // This ensures we always have a stable reference to the actual instance\n const chartRefCallback = useCallback((node: ReactECharts | null) => {\n // Dispose previous instance if component is being replaced\n if (\n echartsInstanceRef.current &&\n !echartsInstanceRef.current.isDisposed()\n ) {\n echartsInstanceRef.current.dispose();\n }\n\n // Store the new instance\n if (node) {\n echartsInstanceRef.current = node.getEchartsInstance();\n } else {\n // Component unmounting - dispose the stored instance\n if (\n echartsInstanceRef.current &&\n !echartsInstanceRef.current.isDisposed()\n ) {\n echartsInstanceRef.current.dispose();\n }\n echartsInstanceRef.current = null;\n }\n }, []);\n\n // Memoize data normalization\n const normalized = useMemo(\n () =>\n chartType === \"heatmap\"\n ? normalizeHeatmapData(data, xKey, yAxisKey, yKey)\n : normalizeChartData(data, xKey, yKey, orientation),\n [data, xKey, yKey, yAxisKey, orientation, chartType],\n );\n\n // Memoize option building\n const option = useMemo(() => {\n const { xData, yFields, chartType: detectedChartType } = normalized;\n\n if (xData.length === 0) return null;\n\n // Determine chart mode first (needed to handle yDataMap)\n const isHeatmap = chartType === \"heatmap\";\n\n // Heatmaps use heatmapData instead of yDataMap\n // For other charts, yDataMap is required\n const yDataMap = \"yDataMap\" in normalized ? normalized.yDataMap : {};\n\n const baseCtx: OptionBuilderContext = {\n xData,\n yDataMap,\n yFields,\n colors,\n title,\n showLegend,\n };\n const isPie = chartType === \"pie\" || chartType === \"donut\";\n const isRadar = chartType === \"radar\";\n const isHorizontal =\n !isPie &&\n !isRadar &&\n !isHeatmap &&\n (orientation === \"horizontal\" ||\n (detectedChartType === \"categorical\" &&\n !orientation &&\n chartType === \"bar\"));\n const isTimeSeries =\n detectedChartType === \"timeseries\" &&\n !isHorizontal &&\n !isRadar &&\n !isHeatmap;\n\n // Build option based on chart type\n let opt: Record<string, unknown>;\n\n if (isHeatmap && \"yAxisData\" in normalized && \"heatmapData\" in normalized) {\n const heatmapNorm = normalized as {\n yAxisData: (string | number)[];\n heatmapData: [number, number, number][];\n min: number;\n max: number;\n } & typeof normalized;\n opt = buildHeatmapOption({\n ...baseCtx,\n yAxisData: heatmapNorm.yAxisData,\n heatmapData: heatmapNorm.heatmapData,\n min: min ?? heatmapNorm.min,\n max: max ?? heatmapNorm.max,\n showLabels,\n });\n } else if (isRadar) {\n opt = buildRadarOption(baseCtx, showArea);\n } else if (isPie) {\n opt = buildPieOption(\n baseCtx,\n chartType as \"pie\" | \"donut\",\n innerRadius,\n showLabels,\n labelPosition,\n );\n } else if (isHorizontal) {\n opt = buildHorizontalBarOption(baseCtx, stacked);\n } else {\n opt = buildCartesianOption({\n ...baseCtx,\n chartType,\n isTimeSeries,\n stacked,\n smooth,\n showSymbol,\n symbolSize,\n });\n }\n\n // Merge custom options\n return customOptions ? { ...opt, ...customOptions } : opt;\n }, [\n normalized,\n colors,\n title,\n showLegend,\n chartType,\n orientation,\n innerRadius,\n showLabels,\n labelPosition,\n stacked,\n smooth,\n showSymbol,\n symbolSize,\n showArea,\n min,\n max,\n customOptions,\n ]);\n\n if (!option) {\n return (\n <div className=\"flex items-center justify-center h-full text-muted-foreground\">\n No data\n </div>\n );\n }\n\n return (\n <ReactECharts\n ref={chartRefCallback}\n option={option}\n style={{ height }}\n className={className}\n opts={{ renderer: \"canvas\" }}\n notMerge={false}\n lazyUpdate={true}\n />\n );\n}\n"],"mappings":";;;;;;;;;;;;;AA6BA,SAAS,kBAAkB,WAAyC;AAClE,SAAQ,WAAR;EACE,KAAK,UACH,QAAO;EACT,QACE,QAAO;;;;;;;AAsEb,SAAgB,UAAU,EACxB,MACA,WACA,MACA,MACA,aACA,SAAS,KACT,OACA,aAAa,MACb,cACA,QAAQ,cACR,aAAa,OACb,SAAS,MACT,UAAU,OACV,aAAa,GACb,WAAW,MACX,cAAc,GACd,aAAa,MACb,gBAAgB,WAChB,UACA,KACA,KACA,SAAS,eACT,aACiB;CAGjB,MAAM,cAAc,eADI,gBAAgB,kBAAkB,UAAU,CACjB;CACnD,MAAM,SAAS,gBAAgB;CAG/B,MAAM,qBAAqB,OAAuB,KAAK;CAIvD,MAAM,mBAAmB,aAAa,SAA8B;AAElE,MACE,mBAAmB,WACnB,CAAC,mBAAmB,QAAQ,YAAY,CAExC,oBAAmB,QAAQ,SAAS;AAItC,MAAI,KACF,oBAAmB,UAAU,KAAK,oBAAoB;OACjD;AAEL,OACE,mBAAmB,WACnB,CAAC,mBAAmB,QAAQ,YAAY,CAExC,oBAAmB,QAAQ,SAAS;AAEtC,sBAAmB,UAAU;;IAE9B,EAAE,CAAC;CAGN,MAAM,aAAa,cAEf,cAAc,YACV,qBAAqB,MAAM,MAAM,UAAU,KAAK,GAChD,mBAAmB,MAAM,MAAM,MAAM,YAAY,EACvD;EAAC;EAAM;EAAM;EAAM;EAAU;EAAa;EAAU,CACrD;CAGD,MAAM,SAAS,cAAc;EAC3B,MAAM,EAAE,OAAO,SAAS,WAAW,sBAAsB;AAEzD,MAAI,MAAM,WAAW,EAAG,QAAO;EAG/B,MAAM,YAAY,cAAc;EAMhC,MAAMA,UAAgC;GACpC;GACA,UAJe,cAAc,aAAa,WAAW,WAAW,EAAE;GAKlE;GACA;GACA;GACA;GACD;EACD,MAAM,QAAQ,cAAc,SAAS,cAAc;EACnD,MAAM,UAAU,cAAc;EAC9B,MAAM,eACJ,CAAC,SACD,CAAC,WACD,CAAC,cACA,gBAAgB,gBACd,sBAAsB,iBACrB,CAAC,eACD,cAAc;EACpB,MAAM,eACJ,sBAAsB,gBACtB,CAAC,gBACD,CAAC,WACD,CAAC;EAGH,IAAIC;AAEJ,MAAI,aAAa,eAAe,cAAc,iBAAiB,YAAY;GACzE,MAAM,cAAc;AAMpB,SAAM,mBAAmB;IACvB,GAAG;IACH,WAAW,YAAY;IACvB,aAAa,YAAY;IACzB,KAAK,OAAO,YAAY;IACxB,KAAK,OAAO,YAAY;IACxB;IACD,CAAC;aACO,QACT,OAAM,iBAAiB,SAAS,SAAS;WAChC,MACT,OAAM,eACJ,SACA,WACA,aACA,YACA,cACD;WACQ,aACT,OAAM,yBAAyB,SAAS,QAAQ;MAEhD,OAAM,qBAAqB;GACzB,GAAG;GACH;GACA;GACA;GACA;GACA;GACA;GACD,CAAC;AAIJ,SAAO,gBAAgB;GAAE,GAAG;GAAK,GAAG;GAAe,GAAG;IACrD;EACD;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CAAC;AAEF,KAAI,CAAC,OACH,QACE,oBAAC;EAAI,WAAU;YAAgE;GAEzE;AAIV,QACE,oBAAC;EACC,KAAK;EACG;EACR,OAAO,EAAE,QAAQ;EACN;EACX,MAAM,EAAE,UAAU,UAAU;EAC5B,UAAU;EACV,YAAY;GACZ"}
1
+ {"version":3,"file":"base.js","names":[],"sources":["../../../src/react/charts/base.tsx"],"sourcesContent":["import type { ECharts } from \"echarts\";\nimport ReactECharts from \"echarts-for-react\";\nimport { useCallback, useMemo, useRef } from \"react\";\nimport { normalizeChartData, normalizeHeatmapData } from \"./normalize\";\nimport {\n buildCartesianOption,\n buildHeatmapOption,\n buildHorizontalBarOption,\n buildPieOption,\n buildRadarOption,\n type OptionBuilderContext,\n} from \"./options\";\nimport { useThemeColors } from \"./theme\";\nimport type {\n ChartColorPalette,\n ChartData,\n ChartType,\n Orientation,\n} from \"./types\";\n\n// ============================================================================\n// Palette Selection\n// ============================================================================\n\n/**\n * Determines the appropriate color palette for a chart type.\n * - Heatmaps use sequential (low → high intensity)\n * - All other charts use categorical (distinct categories)\n */\nfunction getDefaultPalette(chartType: ChartType): ChartColorPalette {\n switch (chartType) {\n case \"heatmap\":\n return \"sequential\";\n default:\n return \"categorical\";\n }\n}\n\n// ============================================================================\n// Component Props\n// ============================================================================\n\nexport interface BaseChartProps {\n /** Chart data (Arrow Table or JSON array) - format is auto-detected */\n data: ChartData;\n /** Chart type */\n chartType: ChartType;\n /** X-axis field key (auto-detected from schema if not provided) */\n xKey?: string;\n /** Y-axis field key(s) (auto-detected from schema if not provided) */\n yKey?: string | string[];\n /** Chart orientation @default \"vertical\" */\n orientation?: Orientation;\n /** Chart height in pixels @default 300 */\n height?: number;\n /** Chart title */\n title?: string;\n /** Show legend @default true */\n showLegend?: boolean;\n /**\n * Color palette to use. Auto-selected based on chart type if not specified.\n * - \"categorical\": Distinct colors for different categories (bar, pie, line)\n * - \"sequential\": Gradient for magnitude (heatmap)\n * - \"diverging\": Two-tone for positive/negative (correlation)\n */\n colorPalette?: ChartColorPalette;\n /** Custom colors (overrides colorPalette) */\n colors?: string[];\n /** Show data point symbols (line/area charts) @default false */\n showSymbol?: boolean;\n /** Smooth line curves (line/area charts) @default true */\n smooth?: boolean;\n /** Stack series @default false */\n stacked?: boolean;\n /** Symbol size for scatter charts @default 8 */\n symbolSize?: number;\n /** Show area fill for radar charts @default true */\n showArea?: boolean;\n /** Inner radius for pie/donut (0-100) @default 0 */\n innerRadius?: number;\n /** Show labels on pie/donut slices @default true */\n showLabels?: boolean;\n /** Label position for pie/donut @default \"outside\" */\n labelPosition?: \"outside\" | \"inside\" | \"center\";\n /** Y-axis field key for heatmap (the row dimension) */\n yAxisKey?: string;\n /** Min value for heatmap color scale */\n min?: number;\n /** Max value for heatmap color scale */\n max?: number;\n /** Additional ECharts options to merge */\n options?: Record<string, unknown>;\n /** Additional CSS classes */\n className?: string;\n}\n\n// ============================================================================\n// Base Chart Component\n// ============================================================================\n\n/**\n * Base chart component that handles both Arrow and JSON data.\n * Renders using ECharts for consistent output across both formats.\n */\nexport function BaseChart({\n data,\n chartType,\n xKey,\n yKey,\n orientation,\n height = 300,\n title,\n showLegend = true,\n colorPalette,\n colors: customColors,\n showSymbol = false,\n smooth = true,\n stacked = false,\n symbolSize = 8,\n showArea = true,\n innerRadius = 0,\n showLabels = true,\n labelPosition = \"outside\",\n yAxisKey,\n min,\n max,\n options: customOptions,\n className,\n}: BaseChartProps) {\n // Determine the appropriate color palette based on chart type\n const resolvedPalette = colorPalette ?? getDefaultPalette(chartType);\n const themeColors = useThemeColors(resolvedPalette);\n const colors = customColors ?? themeColors;\n\n // Store ECharts instance directly to avoid stale ref issues on unmount\n const echartsInstanceRef = useRef<ECharts | null>(null);\n\n // Callback ref pattern: captures the ECharts instance when ReactECharts mounts\n // This ensures we always have a stable reference to the actual instance\n const chartRefCallback = useCallback((node: ReactECharts | null) => {\n // Dispose previous instance if component is being replaced\n if (\n echartsInstanceRef.current &&\n !echartsInstanceRef.current.isDisposed()\n ) {\n echartsInstanceRef.current.dispose();\n }\n\n // Store the new instance\n if (node) {\n echartsInstanceRef.current = node.getEchartsInstance();\n } else {\n // Component unmounting - dispose the stored instance\n if (\n echartsInstanceRef.current &&\n !echartsInstanceRef.current.isDisposed()\n ) {\n echartsInstanceRef.current.dispose();\n }\n echartsInstanceRef.current = null;\n }\n }, []);\n\n // Memoize data normalization\n const normalized = useMemo(\n () =>\n chartType === \"heatmap\"\n ? normalizeHeatmapData(data, xKey, yAxisKey, yKey)\n : normalizeChartData(data, xKey, yKey, orientation),\n [data, xKey, yKey, yAxisKey, orientation, chartType],\n );\n\n // Memoize option building\n const option = useMemo(() => {\n const { xData, yFields, chartType: detectedChartType } = normalized;\n\n if (xData.length === 0) return null;\n\n // Determine chart mode first (needed to handle yDataMap)\n const isHeatmap = chartType === \"heatmap\";\n\n // Heatmaps use heatmapData instead of yDataMap\n // For other charts, yDataMap is required\n const yDataMap = \"yDataMap\" in normalized ? normalized.yDataMap : {};\n\n const baseCtx: OptionBuilderContext = {\n xData,\n yDataMap,\n yFields,\n colors,\n title,\n showLegend,\n };\n const isPie = chartType === \"pie\" || chartType === \"donut\";\n const isRadar = chartType === \"radar\";\n const isHorizontal =\n !isPie &&\n !isRadar &&\n !isHeatmap &&\n (orientation === \"horizontal\" ||\n (detectedChartType === \"categorical\" &&\n !orientation &&\n chartType === \"bar\"));\n const isTimeSeries =\n detectedChartType === \"timeseries\" &&\n !isHorizontal &&\n !isRadar &&\n !isHeatmap;\n\n // Build option based on chart type\n let opt: Record<string, unknown>;\n\n if (isHeatmap && \"yAxisData\" in normalized && \"heatmapData\" in normalized) {\n const heatmapNorm = normalized as {\n yAxisData: (string | number)[];\n heatmapData: [number, number, number][];\n min: number;\n max: number;\n } & typeof normalized;\n opt = buildHeatmapOption({\n ...baseCtx,\n yAxisData: heatmapNorm.yAxisData,\n heatmapData: heatmapNorm.heatmapData,\n min: min ?? heatmapNorm.min,\n max: max ?? heatmapNorm.max,\n showLabels,\n });\n } else if (isRadar) {\n opt = buildRadarOption(baseCtx, showArea);\n } else if (isPie) {\n opt = buildPieOption(\n baseCtx,\n chartType as \"pie\" | \"donut\",\n innerRadius,\n showLabels,\n labelPosition,\n );\n } else if (isHorizontal) {\n opt = buildHorizontalBarOption(baseCtx, stacked);\n } else {\n opt = buildCartesianOption({\n ...baseCtx,\n chartType,\n isTimeSeries,\n stacked,\n smooth,\n showSymbol,\n symbolSize,\n });\n }\n\n // Merge custom options\n return customOptions ? { ...opt, ...customOptions } : opt;\n }, [\n normalized,\n colors,\n title,\n showLegend,\n chartType,\n orientation,\n innerRadius,\n showLabels,\n labelPosition,\n stacked,\n smooth,\n showSymbol,\n symbolSize,\n showArea,\n min,\n max,\n customOptions,\n ]);\n\n if (!option) {\n return (\n <div className=\"flex items-center justify-center h-full text-muted-foreground\">\n No data\n </div>\n );\n }\n\n return (\n <ReactECharts\n ref={chartRefCallback}\n option={option}\n style={{ height }}\n className={className}\n opts={{ renderer: \"canvas\" }}\n notMerge={false}\n lazyUpdate={true}\n />\n );\n}\n"],"mappings":";;;;;;;;;;;;;AA6BA,SAAS,kBAAkB,WAAyC;AAClE,SAAQ,WAAR;EACE,KAAK,UACH,QAAO;EACT,QACE,QAAO;;;;;;;AAsEb,SAAgB,UAAU,EACxB,MACA,WACA,MACA,MACA,aACA,SAAS,KACT,OACA,aAAa,MACb,cACA,QAAQ,cACR,aAAa,OACb,SAAS,MACT,UAAU,OACV,aAAa,GACb,WAAW,MACX,cAAc,GACd,aAAa,MACb,gBAAgB,WAChB,UACA,KACA,KACA,SAAS,eACT,aACiB;CAGjB,MAAM,cAAc,eADI,gBAAgB,kBAAkB,UAAU,CACjB;CACnD,MAAM,SAAS,gBAAgB;CAG/B,MAAM,qBAAqB,OAAuB,KAAK;CAIvD,MAAM,mBAAmB,aAAa,SAA8B;AAElE,MACE,mBAAmB,WACnB,CAAC,mBAAmB,QAAQ,YAAY,CAExC,oBAAmB,QAAQ,SAAS;AAItC,MAAI,KACF,oBAAmB,UAAU,KAAK,oBAAoB;OACjD;AAEL,OACE,mBAAmB,WACnB,CAAC,mBAAmB,QAAQ,YAAY,CAExC,oBAAmB,QAAQ,SAAS;AAEtC,sBAAmB,UAAU;;IAE9B,EAAE,CAAC;CAGN,MAAM,aAAa,cAEf,cAAc,YACV,qBAAqB,MAAM,MAAM,UAAU,KAAK,GAChD,mBAAmB,MAAM,MAAM,MAAM,YAAY,EACvD;EAAC;EAAM;EAAM;EAAM;EAAU;EAAa;EAAU,CACrD;CAGD,MAAM,SAAS,cAAc;EAC3B,MAAM,EAAE,OAAO,SAAS,WAAW,sBAAsB;AAEzD,MAAI,MAAM,WAAW,EAAG,QAAO;EAG/B,MAAM,YAAY,cAAc;EAMhC,MAAM,UAAgC;GACpC;GACA,UAJe,cAAc,aAAa,WAAW,WAAW,EAAE;GAKlE;GACA;GACA;GACA;GACD;EACD,MAAM,QAAQ,cAAc,SAAS,cAAc;EACnD,MAAM,UAAU,cAAc;EAC9B,MAAM,eACJ,CAAC,SACD,CAAC,WACD,CAAC,cACA,gBAAgB,gBACd,sBAAsB,iBACrB,CAAC,eACD,cAAc;EACpB,MAAM,eACJ,sBAAsB,gBACtB,CAAC,gBACD,CAAC,WACD,CAAC;EAGH,IAAI;AAEJ,MAAI,aAAa,eAAe,cAAc,iBAAiB,YAAY;GACzE,MAAM,cAAc;AAMpB,SAAM,mBAAmB;IACvB,GAAG;IACH,WAAW,YAAY;IACvB,aAAa,YAAY;IACzB,KAAK,OAAO,YAAY;IACxB,KAAK,OAAO,YAAY;IACxB;IACD,CAAC;aACO,QACT,OAAM,iBAAiB,SAAS,SAAS;WAChC,MACT,OAAM,eACJ,SACA,WACA,aACA,YACA,cACD;WACQ,aACT,OAAM,yBAAyB,SAAS,QAAQ;MAEhD,OAAM,qBAAqB;GACzB,GAAG;GACH;GACA;GACA;GACA;GACA;GACA;GACD,CAAC;AAIJ,SAAO,gBAAgB;GAAE,GAAG;GAAK,GAAG;GAAe,GAAG;IACrD;EACD;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CAAC;AAEF,KAAI,CAAC,OACH,QACE,oBAAC;EAAI,WAAU;YAAgE;GAEzE;AAIV,QACE,oBAAC;EACC,KAAK;EACG;EACR,OAAO,EAAE,QAAQ;EACN;EACX,MAAM,EAAE,UAAU,UAAU;EAC5B,UAAU;EACV,YAAY;GACZ"}
@@ -1,5 +1,5 @@
1
1
  import { ChartType, UnifiedChartProps } from "./types.js";
2
- import * as react_jsx_runtime282 from "react/jsx-runtime";
2
+ import * as react_jsx_runtime284 from "react/jsx-runtime";
3
3
 
4
4
  //#region src/react/charts/create-chart.d.ts
5
5
 
@@ -18,7 +18,7 @@ import * as react_jsx_runtime282 from "react/jsx-runtime";
18
18
  * ```
19
19
  */
20
20
  declare function createChart<TProps extends UnifiedChartProps>(chartType: ChartType, displayName: string): {
21
- (props: TProps): react_jsx_runtime282.JSX.Element;
21
+ (props: TProps): react_jsx_runtime284.JSX.Element;
22
22
  displayName: string;
23
23
  };
24
24
  //#endregion