@databricks/appkit-ui 0.39.0 → 0.41.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 (47) hide show
  1. package/dist/cli/commands/generate-types.js +114 -5
  2. package/dist/cli/commands/generate-types.js.map +1 -1
  3. package/dist/cli/commands/spawn-lock.js +116 -0
  4. package/dist/cli/commands/spawn-lock.js.map +1 -0
  5. package/dist/cli/index.js +1 -1
  6. package/dist/cli/index.js.map +1 -1
  7. package/dist/react/charts/index.d.ts +1 -0
  8. package/dist/react/charts/index.js +1 -0
  9. package/dist/react/charts/loading.d.ts +24 -0
  10. package/dist/react/charts/loading.d.ts.map +1 -0
  11. package/dist/react/charts/loading.js +20 -2
  12. package/dist/react/charts/loading.js.map +1 -1
  13. package/dist/react/charts/wrapper.d.ts.map +1 -1
  14. package/dist/react/charts/wrapper.js +9 -2
  15. package/dist/react/charts/wrapper.js.map +1 -1
  16. package/dist/react/hooks/index.d.ts +3 -1
  17. package/dist/react/hooks/index.js +2 -0
  18. package/dist/react/hooks/types.d.ts +28 -1
  19. package/dist/react/hooks/types.d.ts.map +1 -1
  20. package/dist/react/hooks/use-analytics-query.d.ts.map +1 -1
  21. package/dist/react/hooks/use-analytics-query.js +77 -45
  22. package/dist/react/hooks/use-analytics-query.js.map +1 -1
  23. package/dist/react/hooks/use-analytics-warehouse-status.js +48 -0
  24. package/dist/react/hooks/use-analytics-warehouse-status.js.map +1 -0
  25. package/dist/react/hooks/use-chart-data.d.ts +8 -0
  26. package/dist/react/hooks/use-chart-data.d.ts.map +1 -1
  27. package/dist/react/hooks/use-chart-data.js +3 -2
  28. package/dist/react/hooks/use-chart-data.js.map +1 -1
  29. package/dist/react/hooks/use-resource-status.d.ts +92 -0
  30. package/dist/react/hooks/use-resource-status.d.ts.map +1 -0
  31. package/dist/react/hooks/use-resource-status.js +199 -0
  32. package/dist/react/hooks/use-resource-status.js.map +1 -0
  33. package/dist/react/index.d.ts +5 -2
  34. package/dist/react/index.js +5 -2
  35. package/dist/react/resource-status-indicator.d.ts +78 -0
  36. package/dist/react/resource-status-indicator.d.ts.map +1 -0
  37. package/dist/react/resource-status-indicator.js +155 -0
  38. package/dist/react/resource-status-indicator.js.map +1 -0
  39. package/dist/react/ui/index.js +1 -1
  40. package/dist/react/ui/sonner.js +1 -1
  41. package/dist/schemas/manifest.d.ts.map +1 -1
  42. package/dist/schemas/manifest.js +1 -1
  43. package/dist/schemas/manifest.js.map +1 -1
  44. package/docs/development/type-generation.md +13 -0
  45. package/docs/plugins/analytics.md +156 -0
  46. package/package.json +1 -1
  47. package/sbom.cdx.json +1 -1
@@ -1,18 +1,12 @@
1
1
  import { ArrowClient } from "../../js/arrow/arrow-client.js";
2
2
  import { connectSSE } from "../../js/sse/connect-sse.js";
3
3
  import "../../js/index.js";
4
+ import { useAnalyticsWarehousePublisher } from "./use-analytics-warehouse-status.js";
4
5
  import { useQueryHMR } from "./use-query-hmr.js";
5
- import { useCallback, useEffect, useMemo, useRef, useState } from "react";
6
+ import { useCallback, useEffect, useId, useMemo, useRef, useState } from "react";
6
7
 
7
8
  //#region src/react/hooks/use-analytics-query.ts
8
- /**
9
- * Shallow structural equality for analytics query parameter objects.
10
- *
11
- * Analytics query parameters are produced by the `sql.*` builders and are
12
- * always plain objects keyed to primitive values (string | number | boolean
13
- * | null | undefined), so shallow equality is sufficient and substantially
14
- * cheaper than a full deep-equal.
15
- */
9
+ /** Shallow equality for plain-object query parameters (primitive values only). */
16
10
  function shallowEqualParams(a, b) {
17
11
  if (Object.is(a, b)) return true;
18
12
  if (a === null || b === null || typeof a !== "object" || typeof b !== "object") return false;
@@ -25,12 +19,7 @@ function shallowEqualParams(a, b) {
25
19
  }
26
20
  return true;
27
21
  }
28
- /**
29
- * Stabilize a value's identity across renders when it is structurally equal
30
- * to the previous value. Used to make object-literal parameters safe to pass
31
- * directly to `useAnalyticsQuery` without forcing every consumer to wrap
32
- * params in `useMemo`.
33
- */
22
+ /** Keep structurally-equal params referentially stable across renders. */
34
23
  function useStableParams(value) {
35
24
  const ref = useRef(value);
36
25
  if (!shallowEqualParams(ref.current, value)) ref.current = value;
@@ -43,6 +32,52 @@ function getDevMode() {
43
32
  function getArrowStreamUrl(id) {
44
33
  return `/api/analytics/arrow-result/${id}`;
45
34
  }
35
+ const GENERIC_LOAD_ERROR = "Unable to load data, please try again";
36
+ function isWarehouseStatusPayload(value) {
37
+ return typeof value === "object" && value !== null && typeof value.state === "string";
38
+ }
39
+ async function handleAnalyticsSseMessage(parsed, ctx) {
40
+ if (parsed.type === "warehouse_status") {
41
+ if (!isWarehouseStatusPayload(parsed.status)) {
42
+ ctx.setLoading(false);
43
+ ctx.setError(GENERIC_LOAD_ERROR);
44
+ ctx.unpublishWarehouseStatus();
45
+ console.error("[useAnalyticsQuery] Malformed warehouse_status event", parsed);
46
+ return;
47
+ }
48
+ ctx.setWarehouseStatus(parsed.status);
49
+ ctx.publishWarehouseStatus(parsed.status);
50
+ return;
51
+ }
52
+ if (parsed.type === "result") {
53
+ ctx.setLoading(false);
54
+ ctx.setData(parsed.data);
55
+ ctx.unpublishWarehouseStatus();
56
+ return;
57
+ }
58
+ if (parsed.type === "arrow") {
59
+ try {
60
+ const arrowData = await ArrowClient.fetchArrow(getArrowStreamUrl(parsed.statement_id));
61
+ const table = await ArrowClient.processArrowBuffer(arrowData);
62
+ ctx.setLoading(false);
63
+ ctx.setData(table);
64
+ ctx.unpublishWarehouseStatus();
65
+ } catch (error) {
66
+ console.error("[useAnalyticsQuery] Failed to fetch Arrow data", error);
67
+ ctx.setLoading(false);
68
+ ctx.setError(GENERIC_LOAD_ERROR);
69
+ ctx.unpublishWarehouseStatus();
70
+ }
71
+ return;
72
+ }
73
+ if (parsed.type === "error" || parsed.error || parsed.code) {
74
+ const errorMsg = parsed.error || parsed.message || "Unable to execute query";
75
+ ctx.setLoading(false);
76
+ ctx.setError(errorMsg);
77
+ ctx.unpublishWarehouseStatus();
78
+ if (parsed.code) console.error(`[useAnalyticsQuery] Code: ${parsed.code}, Message: ${errorMsg}`);
79
+ }
80
+ }
46
81
  /**
47
82
  * Subscribe to an analytics query over SSE and returns its latest result.
48
83
  * Integration hook between client and analytics plugin.
@@ -81,7 +116,9 @@ function useAnalyticsQuery(queryKey, parameters, options = {}) {
81
116
  const [data, setData] = useState(null);
82
117
  const [loading, setLoading] = useState(false);
83
118
  const [error, setError] = useState(null);
119
+ const [warehouseStatus, setWarehouseStatus] = useState(null);
84
120
  const abortControllerRef = useRef(null);
121
+ const { publish: publishWarehouseStatus, unpublish: unpublishWarehouseStatus } = useAnalyticsWarehousePublisher(useId(), queryKey);
85
122
  if (!queryKey || queryKey.trim().length === 0) throw new Error("useAnalyticsQuery: 'queryKey' must be a non-empty string.");
86
123
  const stableParameters = useStableParams(parameters);
87
124
  const payload = useMemo(() => {
@@ -106,43 +143,29 @@ function useAnalyticsQuery(queryKey, parameters, options = {}) {
106
143
  setError("Failed to serialize query parameters");
107
144
  return;
108
145
  }
109
- if (abortControllerRef.current) abortControllerRef.current.abort();
146
+ abortControllerRef.current?.abort();
110
147
  setLoading(true);
111
148
  setError(null);
112
149
  setData(null);
150
+ setWarehouseStatus(null);
151
+ publishWarehouseStatus(null);
113
152
  const abortController = new AbortController();
114
153
  abortControllerRef.current = abortController;
154
+ const sseContext = {
155
+ setLoading,
156
+ setError,
157
+ setData,
158
+ setWarehouseStatus,
159
+ publishWarehouseStatus,
160
+ unpublishWarehouseStatus
161
+ };
115
162
  connectSSE({
116
163
  url: urlSuffix,
117
164
  payload,
118
165
  signal: abortController.signal,
119
166
  onMessage: async (message) => {
120
167
  try {
121
- const parsed = JSON.parse(message.data);
122
- if (parsed.type === "result") {
123
- setLoading(false);
124
- setData(parsed.data);
125
- return;
126
- }
127
- if (parsed.type === "arrow") try {
128
- const arrowData = await ArrowClient.fetchArrow(getArrowStreamUrl(parsed.statement_id));
129
- const table = await ArrowClient.processArrowBuffer(arrowData);
130
- setLoading(false);
131
- setData(table);
132
- return;
133
- } catch (error) {
134
- console.error("[useAnalyticsQuery] Failed to fetch Arrow data", error);
135
- setLoading(false);
136
- setError("Unable to load data, please try again");
137
- return;
138
- }
139
- if (parsed.type === "error" || parsed.error || parsed.code) {
140
- const errorMsg = parsed.error || parsed.message || "Unable to execute query";
141
- setLoading(false);
142
- setError(errorMsg);
143
- if (parsed.code) console.error(`[useAnalyticsQuery] Code: ${parsed.code}, Message: ${errorMsg}`);
144
- return;
145
- }
168
+ await handleAnalyticsSseMessage(JSON.parse(message.data), sseContext);
146
169
  } catch (error) {
147
170
  console.warn("[useAnalyticsQuery] Malformed message received", error);
148
171
  }
@@ -150,7 +173,8 @@ function useAnalyticsQuery(queryKey, parameters, options = {}) {
150
173
  onError: (error) => {
151
174
  if (abortController.signal.aborted) return;
152
175
  setLoading(false);
153
- let userMessage = "Unable to load data, please try again";
176
+ unpublishWarehouseStatus();
177
+ let userMessage = GENERIC_LOAD_ERROR;
154
178
  if (error instanceof Error) {
155
179
  if (error.name === "AbortError") userMessage = "Request timed out, please try again";
156
180
  else if (error.message.includes("Failed to fetch")) userMessage = "Network error. Please check your connection.";
@@ -166,19 +190,27 @@ function useAnalyticsQuery(queryKey, parameters, options = {}) {
166
190
  }, [
167
191
  queryKey,
168
192
  payload,
169
- urlSuffix
193
+ urlSuffix,
194
+ publishWarehouseStatus,
195
+ unpublishWarehouseStatus
170
196
  ]);
171
197
  useEffect(() => {
172
198
  if (autoStart) start();
173
199
  return () => {
174
200
  abortControllerRef.current?.abort();
201
+ unpublishWarehouseStatus();
175
202
  };
176
- }, [start, autoStart]);
203
+ }, [
204
+ start,
205
+ autoStart,
206
+ unpublishWarehouseStatus
207
+ ]);
177
208
  useQueryHMR(queryKey, start);
178
209
  return {
179
210
  data,
180
211
  loading,
181
- error
212
+ error,
213
+ warehouseStatus
182
214
  };
183
215
  }
184
216
 
@@ -1 +1 @@
1
- {"version":3,"file":"use-analytics-query.js","names":[],"sources":["../../../src/react/hooks/use-analytics-query.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { ArrowClient, connectSSE } from \"@/js\";\nimport type {\n AnalyticsFormat,\n InferParams,\n InferResultByFormat,\n QueryKey,\n UseAnalyticsQueryOptions,\n UseAnalyticsQueryResult,\n} from \"./types\";\nimport { useQueryHMR } from \"./use-query-hmr\";\n\n/**\n * Shallow structural equality for analytics query parameter objects.\n *\n * Analytics query parameters are produced by the `sql.*` builders and are\n * always plain objects keyed to primitive values (string | number | boolean\n * | null | undefined), so shallow equality is sufficient and substantially\n * cheaper than a full deep-equal.\n */\nfunction shallowEqualParams(a: unknown, b: unknown): boolean {\n if (Object.is(a, b)) return true;\n if (\n a === null ||\n b === null ||\n typeof a !== \"object\" ||\n typeof b !== \"object\"\n ) {\n return false;\n }\n const aKeys = Object.keys(a as Record<string, unknown>);\n const bKeys = Object.keys(b as Record<string, unknown>);\n if (aKeys.length !== bKeys.length) return false;\n for (const key of aKeys) {\n if (!Object.hasOwn(b, key)) return false;\n if (\n !Object.is(\n (a as Record<string, unknown>)[key],\n (b as Record<string, unknown>)[key],\n )\n ) {\n return false;\n }\n }\n return true;\n}\n\n/**\n * Stabilize a value's identity across renders when it is structurally equal\n * to the previous value. Used to make object-literal parameters safe to pass\n * directly to `useAnalyticsQuery` without forcing every consumer to wrap\n * params in `useMemo`.\n */\nfunction useStableParams<T>(value: T): T {\n const ref = useRef<T>(value);\n if (!shallowEqualParams(ref.current, value)) {\n ref.current = value;\n }\n return ref.current;\n}\n\nfunction getDevMode() {\n const url = new URL(window.location.href);\n const searchParams = url.searchParams;\n const dev = searchParams.get(\"dev\");\n\n return dev ? `?dev=${dev}` : \"\";\n}\n\nfunction getArrowStreamUrl(id: string) {\n return `/api/analytics/arrow-result/${id}`;\n}\n\n/**\n * Subscribe to an analytics query over SSE and returns its latest result.\n * Integration hook between client and analytics plugin.\n *\n * The return type is automatically inferred based on the format:\n * - `format: \"JSON_ARRAY\"` (default): Returns typed array from QueryRegistry\n * - `format: \"ARROW_STREAM\"`: Returns TypedArrowTable with row type preserved\n *\n * Note: User context execution is determined by query file naming:\n * - `queryKey.obo.sql`: Executes as user (OBO = on-behalf-of / user delegation)\n * - `queryKey.sql`: Executes as service principal\n *\n * @param queryKey - Analytics query identifier\n * @param parameters - Query parameters (type-safe based on QueryRegistry)\n * @param options - Analytics query settings including format\n * @returns Query result state with format-appropriate data type\n *\n * @example JSON format (default)\n * ```typescript\n * const { data } = useAnalyticsQuery(\"spend_data\", params);\n * // data: Array<{ group_key: string; cost_usd: number; ... }> | null\n * ```\n *\n * @example Arrow format\n * ```typescript\n * const { data } = useAnalyticsQuery(\"spend_data\", params, { format: \"ARROW_STREAM\" });\n * // data: TypedArrowTable<{ group_key: string; cost_usd: number; ... }> | null\n * ```\n */\nexport function useAnalyticsQuery<\n T = unknown,\n K extends QueryKey = QueryKey,\n F extends AnalyticsFormat = \"JSON_ARRAY\",\n>(\n queryKey: K,\n parameters?: InferParams<K> | null,\n options: UseAnalyticsQueryOptions<F> = {} as UseAnalyticsQueryOptions<F>,\n): UseAnalyticsQueryResult<InferResultByFormat<T, K, F>> {\n const format = options?.format ?? \"JSON_ARRAY\";\n const maxParametersSize = options?.maxParametersSize ?? 100 * 1024;\n const autoStart = options?.autoStart ?? true;\n\n const devMode = getDevMode();\n const urlSuffix = `/api/analytics/query/${encodeURIComponent(queryKey)}${devMode}`;\n\n type ResultType = InferResultByFormat<T, K, F>;\n const [data, setData] = useState<ResultType | null>(null);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const abortControllerRef = useRef<AbortController | null>(null);\n\n if (!queryKey || queryKey.trim().length === 0) {\n throw new Error(\n \"useAnalyticsQuery: 'queryKey' must be a non-empty string.\",\n );\n }\n\n // Stabilize the parameters reference across renders. Without this, a fresh\n // object literal at the call site (e.g. `useAnalyticsQuery(\"k\", { limit: 10 })`)\n // would change identity every render, invalidating the `payload` memo and\n // re-running `start` -> infinite refetch loop.\n const stableParameters = useStableParams(parameters);\n\n const payload = useMemo(() => {\n try {\n const serialized = JSON.stringify({\n parameters: stableParameters,\n format,\n });\n const sizeInBytes = new Blob([serialized]).size;\n if (sizeInBytes > maxParametersSize) {\n throw new Error(\n \"useAnalyticsQuery: Parameters size exceeds the maximum allowed size\",\n );\n }\n\n return serialized;\n } catch (error) {\n console.error(\"useAnalyticsQuery: Failed to serialize parameters\", error);\n return null;\n }\n }, [stableParameters, format, maxParametersSize]);\n\n const start = useCallback(() => {\n if (payload === null) {\n setError(\"Failed to serialize query parameters\");\n return;\n }\n\n // Abort previous request if exists\n if (abortControllerRef.current) {\n abortControllerRef.current.abort();\n }\n\n setLoading(true);\n setError(null);\n setData(null);\n\n const abortController = new AbortController();\n abortControllerRef.current = abortController;\n\n connectSSE({\n url: urlSuffix,\n payload: payload,\n signal: abortController.signal,\n onMessage: async (message) => {\n try {\n const parsed = JSON.parse(message.data);\n\n // success - JSON format\n if (parsed.type === \"result\") {\n setLoading(false);\n setData(parsed.data as ResultType);\n return;\n }\n\n // success - Arrow format\n if (parsed.type === \"arrow\") {\n try {\n const arrowData = await ArrowClient.fetchArrow(\n getArrowStreamUrl(parsed.statement_id),\n );\n const table = await ArrowClient.processArrowBuffer(arrowData);\n setLoading(false);\n // Table is cast to TypedArrowTable with row type from QueryRegistry\n setData(table as ResultType);\n return;\n } catch (error) {\n console.error(\n \"[useAnalyticsQuery] Failed to fetch Arrow data\",\n error,\n );\n setLoading(false);\n setError(\"Unable to load data, please try again\");\n return;\n }\n }\n\n // error\n if (parsed.type === \"error\" || parsed.error || parsed.code) {\n const errorMsg =\n parsed.error || parsed.message || \"Unable to execute query\";\n\n setLoading(false);\n setError(errorMsg);\n\n if (parsed.code) {\n console.error(\n `[useAnalyticsQuery] Code: ${parsed.code}, Message: ${errorMsg}`,\n );\n }\n return;\n }\n } catch (error) {\n console.warn(\"[useAnalyticsQuery] Malformed message received\", error);\n }\n },\n onError: (error) => {\n if (abortController.signal.aborted) return;\n setLoading(false);\n\n let userMessage = \"Unable to load data, please try again\";\n\n if (error instanceof Error) {\n if (error.name === \"AbortError\") {\n userMessage = \"Request timed out, please try again\";\n } else if (error.message.includes(\"Failed to fetch\")) {\n userMessage = \"Network error. Please check your connection.\";\n }\n\n console.error(\"[useAnalyticsQuery] Error\", {\n queryKey,\n error: error.message,\n stack: error.stack,\n });\n }\n setError(userMessage);\n },\n });\n }, [queryKey, payload, urlSuffix]);\n\n useEffect(() => {\n if (autoStart) {\n start();\n }\n\n return () => {\n abortControllerRef.current?.abort();\n };\n }, [start, autoStart]);\n\n // Enable HMR for query updates in dev mode\n useQueryHMR(queryKey, start);\n\n return { data, loading, error };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"use-analytics-query.js","names":[],"sources":["../../../src/react/hooks/use-analytics-query.ts"],"sourcesContent":["import {\n useCallback,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport { ArrowClient, connectSSE } from \"@/js\";\nimport type {\n AnalyticsFormat,\n InferParams,\n InferResultByFormat,\n QueryKey,\n UseAnalyticsQueryOptions,\n UseAnalyticsQueryResult,\n WarehouseStatus,\n} from \"./types\";\nimport { useAnalyticsWarehousePublisher } from \"./use-analytics-warehouse-status\";\nimport { useQueryHMR } from \"./use-query-hmr\";\n\n/** Shallow equality for plain-object query parameters (primitive values only). */\nfunction shallowEqualParams(a: unknown, b: unknown): boolean {\n if (Object.is(a, b)) return true;\n if (\n a === null ||\n b === null ||\n typeof a !== \"object\" ||\n typeof b !== \"object\"\n ) {\n return false;\n }\n const aKeys = Object.keys(a as Record<string, unknown>);\n const bKeys = Object.keys(b as Record<string, unknown>);\n if (aKeys.length !== bKeys.length) return false;\n for (const key of aKeys) {\n if (!Object.hasOwn(b, key)) return false;\n if (\n !Object.is(\n (a as Record<string, unknown>)[key],\n (b as Record<string, unknown>)[key],\n )\n ) {\n return false;\n }\n }\n return true;\n}\n\n/** Keep structurally-equal params referentially stable across renders. */\nfunction useStableParams<T>(value: T): T {\n const ref = useRef<T>(value);\n if (!shallowEqualParams(ref.current, value)) {\n ref.current = value;\n }\n return ref.current;\n}\n\nfunction getDevMode(): string {\n const dev = new URL(window.location.href).searchParams.get(\"dev\");\n return dev ? `?dev=${dev}` : \"\";\n}\n\nfunction getArrowStreamUrl(id: string): string {\n return `/api/analytics/arrow-result/${id}`;\n}\n\nconst GENERIC_LOAD_ERROR = \"Unable to load data, please try again\";\n\ninterface AnalyticsQuerySseContext<ResultType> {\n setLoading: (loading: boolean) => void;\n setError: (error: string | null) => void;\n setData: (data: ResultType | null) => void;\n setWarehouseStatus: (status: WarehouseStatus | null) => void;\n publishWarehouseStatus: (status: WarehouseStatus | null) => void;\n unpublishWarehouseStatus: () => void;\n}\n\nfunction isWarehouseStatusPayload(value: unknown): value is WarehouseStatus {\n return (\n typeof value === \"object\" &&\n value !== null &&\n typeof (value as WarehouseStatus).state === \"string\"\n );\n}\n\nasync function handleAnalyticsSseMessage<ResultType>(\n parsed: Record<string, unknown>,\n ctx: AnalyticsQuerySseContext<ResultType>,\n): Promise<void> {\n if (parsed.type === \"warehouse_status\") {\n if (!isWarehouseStatusPayload(parsed.status)) {\n ctx.setLoading(false);\n ctx.setError(GENERIC_LOAD_ERROR);\n ctx.unpublishWarehouseStatus();\n console.error(\n \"[useAnalyticsQuery] Malformed warehouse_status event\",\n parsed,\n );\n return;\n }\n ctx.setWarehouseStatus(parsed.status);\n ctx.publishWarehouseStatus(parsed.status);\n return;\n }\n\n if (parsed.type === \"result\") {\n ctx.setLoading(false);\n ctx.setData(parsed.data as ResultType);\n ctx.unpublishWarehouseStatus();\n return;\n }\n\n if (parsed.type === \"arrow\") {\n try {\n const arrowData = await ArrowClient.fetchArrow(\n getArrowStreamUrl(parsed.statement_id as string),\n );\n const table = await ArrowClient.processArrowBuffer(arrowData);\n ctx.setLoading(false);\n ctx.setData(table as ResultType);\n ctx.unpublishWarehouseStatus();\n } catch (error) {\n console.error(\"[useAnalyticsQuery] Failed to fetch Arrow data\", error);\n ctx.setLoading(false);\n ctx.setError(GENERIC_LOAD_ERROR);\n ctx.unpublishWarehouseStatus();\n }\n return;\n }\n\n if (parsed.type === \"error\" || parsed.error || parsed.code) {\n const errorMsg =\n (parsed.error as string | undefined) ||\n (parsed.message as string | undefined) ||\n \"Unable to execute query\";\n ctx.setLoading(false);\n ctx.setError(errorMsg);\n ctx.unpublishWarehouseStatus();\n if (parsed.code) {\n console.error(\n `[useAnalyticsQuery] Code: ${parsed.code}, Message: ${errorMsg}`,\n );\n }\n }\n}\n\n/**\n * Subscribe to an analytics query over SSE and returns its latest result.\n * Integration hook between client and analytics plugin.\n *\n * The return type is automatically inferred based on the format:\n * - `format: \"JSON_ARRAY\"` (default): Returns typed array from QueryRegistry\n * - `format: \"ARROW_STREAM\"`: Returns TypedArrowTable with row type preserved\n *\n * Note: User context execution is determined by query file naming:\n * - `queryKey.obo.sql`: Executes as user (OBO = on-behalf-of / user delegation)\n * - `queryKey.sql`: Executes as service principal\n *\n * @param queryKey - Analytics query identifier\n * @param parameters - Query parameters (type-safe based on QueryRegistry)\n * @param options - Analytics query settings including format\n * @returns Query result state with format-appropriate data type\n *\n * @example JSON format (default)\n * ```typescript\n * const { data } = useAnalyticsQuery(\"spend_data\", params);\n * // data: Array<{ group_key: string; cost_usd: number; ... }> | null\n * ```\n *\n * @example Arrow format\n * ```typescript\n * const { data } = useAnalyticsQuery(\"spend_data\", params, { format: \"ARROW_STREAM\" });\n * // data: TypedArrowTable<{ group_key: string; cost_usd: number; ... }> | null\n * ```\n */\nexport function useAnalyticsQuery<\n T = unknown,\n K extends QueryKey = QueryKey,\n F extends AnalyticsFormat = \"JSON_ARRAY\",\n>(\n queryKey: K,\n parameters?: InferParams<K> | null,\n options: UseAnalyticsQueryOptions<F> = {} as UseAnalyticsQueryOptions<F>,\n): UseAnalyticsQueryResult<InferResultByFormat<T, K, F>> {\n const format = options?.format ?? \"JSON_ARRAY\";\n const maxParametersSize = options?.maxParametersSize ?? 100 * 1024;\n const autoStart = options?.autoStart ?? true;\n\n const devMode = getDevMode();\n const urlSuffix = `/api/analytics/query/${encodeURIComponent(queryKey)}${devMode}`;\n\n type ResultType = InferResultByFormat<T, K, F>;\n const [data, setData] = useState<ResultType | null>(null);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [warehouseStatus, setWarehouseStatus] =\n useState<WarehouseStatus | null>(null);\n const abortControllerRef = useRef<AbortController | null>(null);\n\n const publisherId = useId();\n const {\n publish: publishWarehouseStatus,\n unpublish: unpublishWarehouseStatus,\n } = useAnalyticsWarehousePublisher(publisherId, queryKey);\n\n if (!queryKey || queryKey.trim().length === 0) {\n throw new Error(\n \"useAnalyticsQuery: 'queryKey' must be a non-empty string.\",\n );\n }\n\n const stableParameters = useStableParams(parameters);\n\n const payload = useMemo(() => {\n try {\n const serialized = JSON.stringify({\n parameters: stableParameters,\n format,\n });\n const sizeInBytes = new Blob([serialized]).size;\n if (sizeInBytes > maxParametersSize) {\n throw new Error(\n \"useAnalyticsQuery: Parameters size exceeds the maximum allowed size\",\n );\n }\n\n return serialized;\n } catch (error) {\n console.error(\"useAnalyticsQuery: Failed to serialize parameters\", error);\n return null;\n }\n }, [stableParameters, format, maxParametersSize]);\n\n const start = useCallback(() => {\n if (payload === null) {\n setError(\"Failed to serialize query parameters\");\n return;\n }\n\n abortControllerRef.current?.abort();\n\n setLoading(true);\n setError(null);\n setData(null);\n setWarehouseStatus(null);\n publishWarehouseStatus(null);\n\n const abortController = new AbortController();\n abortControllerRef.current = abortController;\n\n const sseContext: AnalyticsQuerySseContext<ResultType> = {\n setLoading,\n setError,\n setData,\n setWarehouseStatus,\n publishWarehouseStatus,\n unpublishWarehouseStatus,\n };\n\n connectSSE({\n url: urlSuffix,\n payload,\n signal: abortController.signal,\n onMessage: async (message) => {\n try {\n const parsed = JSON.parse(message.data) as Record<string, unknown>;\n await handleAnalyticsSseMessage(parsed, sseContext);\n } catch (error) {\n console.warn(\"[useAnalyticsQuery] Malformed message received\", error);\n }\n },\n onError: (error) => {\n if (abortController.signal.aborted) return;\n setLoading(false);\n unpublishWarehouseStatus();\n\n let userMessage = GENERIC_LOAD_ERROR;\n if (error instanceof Error) {\n if (error.name === \"AbortError\") {\n userMessage = \"Request timed out, please try again\";\n } else if (error.message.includes(\"Failed to fetch\")) {\n userMessage = \"Network error. Please check your connection.\";\n }\n console.error(\"[useAnalyticsQuery] Error\", {\n queryKey,\n error: error.message,\n stack: error.stack,\n });\n }\n setError(userMessage);\n },\n });\n }, [\n queryKey,\n payload,\n urlSuffix,\n publishWarehouseStatus,\n unpublishWarehouseStatus,\n ]);\n\n useEffect(() => {\n if (autoStart) {\n start();\n }\n\n return () => {\n abortControllerRef.current?.abort();\n unpublishWarehouseStatus();\n };\n }, [start, autoStart, unpublishWarehouseStatus]);\n\n useQueryHMR(queryKey, start);\n\n return { data, loading, error, warehouseStatus };\n}\n"],"mappings":"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"}
@@ -0,0 +1,48 @@
1
+ import { useResourceStatusPublisher } from "./use-resource-status.js";
2
+ import { useCallback, useRef } from "react";
3
+
4
+ //#region src/react/hooks/use-analytics-warehouse-status.ts
5
+ const ANALYTICS_WAREHOUSE_RESOURCE_KIND = "warehouse";
6
+ /**
7
+ * - `RUNNING` → `null`; callers `unpublish` instead.
8
+ * - `STARTING` / `STOPPED` / `STOPPING` → `pending`.
9
+ * - `DELETED` / `DELETING` → `error` (config change required).
10
+ */
11
+ function severityForWarehouseState(state) {
12
+ switch (state) {
13
+ case "RUNNING": return null;
14
+ case "DELETED":
15
+ case "DELETING": return "error";
16
+ default: return "pending";
17
+ }
18
+ }
19
+ /**
20
+ * Internal hook used by `useAnalyticsQuery` to mirror its current warehouse
21
+ * status into the nearest provider. No-op when no provider is mounted.
22
+ */
23
+ function useAnalyticsWarehousePublisher(id, queryKey) {
24
+ const { publish: publishGeneric, unpublish } = useResourceStatusPublisher(id, queryKey, { kindHint: ANALYTICS_WAREHOUSE_RESOURCE_KIND });
25
+ const startedAtRef = useRef(null);
26
+ return {
27
+ publish: useCallback((status) => {
28
+ const severity = status && severityForWarehouseState(status.state);
29
+ if (!status || !severity) {
30
+ startedAtRef.current = null;
31
+ publishGeneric(null);
32
+ return;
33
+ }
34
+ if (startedAtRef.current === null) startedAtRef.current = Date.now() - Math.max(0, status.elapsedMs);
35
+ publishGeneric({
36
+ kind: ANALYTICS_WAREHOUSE_RESOURCE_KIND,
37
+ state: status.state,
38
+ severity,
39
+ startedAt: startedAtRef.current
40
+ });
41
+ }, [publishGeneric]),
42
+ unpublish
43
+ };
44
+ }
45
+
46
+ //#endregion
47
+ export { useAnalyticsWarehousePublisher };
48
+ //# sourceMappingURL=use-analytics-warehouse-status.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-analytics-warehouse-status.js","names":[],"sources":["../../../src/react/hooks/use-analytics-warehouse-status.ts"],"sourcesContent":["import { useCallback, useRef } from \"react\";\nimport type { WarehouseStatus } from \"./types\";\nimport {\n type ResourceSeverity,\n useResourceStatusPublisher,\n} from \"./use-resource-status\";\n\nconst ANALYTICS_WAREHOUSE_RESOURCE_KIND = \"warehouse\";\n\n/**\n * - `RUNNING` → `null`; callers `unpublish` instead.\n * - `STARTING` / `STOPPED` / `STOPPING` → `pending`.\n * - `DELETED` / `DELETING` → `error` (config change required).\n */\nfunction severityForWarehouseState(\n state: WarehouseStatus[\"state\"],\n): ResourceSeverity | null {\n switch (state) {\n case \"RUNNING\":\n return null;\n case \"DELETED\":\n case \"DELETING\":\n return \"error\";\n default:\n return \"pending\";\n }\n}\n\n/**\n * Internal hook used by `useAnalyticsQuery` to mirror its current warehouse\n * status into the nearest provider. No-op when no provider is mounted.\n */\nexport function useAnalyticsWarehousePublisher(\n id: string,\n queryKey: string,\n): {\n publish: (status: WarehouseStatus | null) => void;\n unpublish: () => void;\n} {\n const { publish: publishGeneric, unpublish } = useResourceStatusPublisher(\n id,\n queryKey,\n { kindHint: ANALYTICS_WAREHOUSE_RESOURCE_KIND },\n );\n\n // Anchor `startedAt` to the first non-null status so `elapsedMs`\n // advances monotonically across successive `warehouse_status` events.\n const startedAtRef = useRef<number | null>(null);\n\n const publish = useCallback(\n (status: WarehouseStatus | null) => {\n // null covers \"register with no status yet\" *and* RUNNING — both\n // keep the slot registered without contributing to the aggregate.\n const severity = status && severityForWarehouseState(status.state);\n if (!status || !severity) {\n startedAtRef.current = null;\n publishGeneric(null);\n return;\n }\n if (startedAtRef.current === null) {\n startedAtRef.current = Date.now() - Math.max(0, status.elapsedMs);\n }\n publishGeneric({\n kind: ANALYTICS_WAREHOUSE_RESOURCE_KIND,\n state: status.state,\n severity,\n startedAt: startedAtRef.current,\n });\n },\n [publishGeneric],\n );\n\n return { publish, unpublish };\n}\n"],"mappings":";;;;AAOA,MAAM,oCAAoC;;;;;;AAO1C,SAAS,0BACP,OACyB;AACzB,SAAQ,OAAR;EACE,KAAK,UACH,QAAO;EACT,KAAK;EACL,KAAK,WACH,QAAO;EACT,QACE,QAAO;;;;;;;AAQb,SAAgB,+BACd,IACA,UAIA;CACA,MAAM,EAAE,SAAS,gBAAgB,cAAc,2BAC7C,IACA,UACA,EAAE,UAAU,mCAAmC,CAChD;CAID,MAAM,eAAe,OAAsB,KAAK;AAyBhD,QAAO;EAAE,SAvBO,aACb,WAAmC;GAGlC,MAAM,WAAW,UAAU,0BAA0B,OAAO,MAAM;AAClE,OAAI,CAAC,UAAU,CAAC,UAAU;AACxB,iBAAa,UAAU;AACvB,mBAAe,KAAK;AACpB;;AAEF,OAAI,aAAa,YAAY,KAC3B,cAAa,UAAU,KAAK,KAAK,GAAG,KAAK,IAAI,GAAG,OAAO,UAAU;AAEnE,kBAAe;IACb,MAAM;IACN,OAAO,OAAO;IACd;IACA,WAAW,aAAa;IACzB,CAAC;KAEJ,CAAC,eAAe,CACjB;EAEiB;EAAW"}
@@ -1,4 +1,5 @@
1
1
  import { ChartData, DataFormat } from "../charts/types.js";
2
+ import { WarehouseStatus } from "./types.js";
2
3
 
3
4
  //#region src/react/hooks/use-chart-data.d.ts
4
5
  interface UseChartDataOptions {
@@ -28,6 +29,13 @@ interface UseChartDataResult {
28
29
  error: string | null;
29
30
  /** Whether the data is empty */
30
31
  isEmpty: boolean;
32
+ /**
33
+ * Latest warehouse readiness status from SSE. Retains the last value
34
+ * (including `RUNNING`) until the next `start()`; `null` only before
35
+ * the first event. Use with `loading` to distinguish warehouse warm-up
36
+ * from in-flight SQL fetch.
37
+ */
38
+ warehouseStatus: WarehouseStatus | null;
31
39
  }
32
40
  /**
33
41
  * Hook for fetching chart data in either JSON or Arrow format.
@@ -1 +1 @@
1
- {"version":3,"file":"use-chart-data.d.ts","names":[],"sources":["../../../src/react/hooks/use-chart-data.ts"],"mappings":";;;UAYiB,mBAAA;;EAEf,QAAA;EAFkC;EAIlC,UAAA,GAAa,MAAA;EAAA;;;;;;;EAQb,MAAA,GAAS,UAAA;EARI;EAUb,WAAA,OAAkB,IAAA,EAAM,CAAA,KAAM,CAAA;AAAA;AAAA,UAGf,kBAAA;EAHA;EAKf,IAAA,EAAM,SAAA;EALY;EAOlB,OAAA;EAP+B;EAS/B,OAAA;EANe;EAQf,KAAA;;EAEA,OAAA;AAAA;;;;;;;;AAgEF;;;;;;;;;;;;iBAAgB,YAAA,CAAa,OAAA,EAAS,mBAAA,GAAsB,kBAAA"}
1
+ {"version":3,"file":"use-chart-data.d.ts","names":[],"sources":["../../../src/react/hooks/use-chart-data.ts"],"mappings":";;;;UAaiB,mBAAA;;EAEf,QAAA;EAFkC;EAIlC,UAAA,GAAa,MAAA;EAAA;;;;;;;EAQb,MAAA,GAAS,UAAA;EARI;EAUb,WAAA,OAAkB,IAAA,EAAM,CAAA,KAAM,CAAA;AAAA;AAAA,UAGf,kBAAA;EAHA;EAKf,IAAA,EAAM,SAAA;EALY;EAOlB,OAAA;EAP+B;EAS/B,OAAA;EANe;EAQf,KAAA;;EAEA,OAAA;EARA;;;;;;EAeA,eAAA,EAAiB,eAAA;AAAA;;;AAgEnB;;;;;;;;;;;;;;;;;iBAAgB,YAAA,CAAa,OAAA,EAAS,mBAAA,GAAsB,kBAAA"}
@@ -43,7 +43,7 @@ function useChartData(options) {
43
43
  const { queryKey, parameters, format = "auto", transformer } = options;
44
44
  const resolvedFormat = useMemo(() => resolveFormat(format, parameters), [format, parameters]);
45
45
  const isArrowFormat = resolvedFormat === "ARROW_STREAM";
46
- const { data: rawData, loading, error } = useAnalyticsQuery(queryKey, parameters, {
46
+ const { data: rawData, loading, error, warehouseStatus } = useAnalyticsQuery(queryKey, parameters, {
47
47
  autoStart: true,
48
48
  format: resolvedFormat
49
49
  });
@@ -71,7 +71,8 @@ function useChartData(options) {
71
71
  }, [processedData, isArrowFormat]),
72
72
  loading,
73
73
  error,
74
- isEmpty
74
+ isEmpty,
75
+ warehouseStatus
75
76
  };
76
77
  }
77
78
 
@@ -1 +1 @@
1
- {"version":3,"file":"use-chart-data.js","names":[],"sources":["../../../src/react/hooks/use-chart-data.ts"],"sourcesContent":["import type { Table } from \"apache-arrow\";\nimport { useMemo } from \"react\";\nimport type { ChartData, DataFormat } from \"../charts/types\";\nimport { useAnalyticsQuery } from \"./use-analytics-query\";\n\n/** Threshold for auto-selecting Arrow format (row count hint) */\nconst ARROW_THRESHOLD = 500;\n\n// ============================================================================\n// Hook Options & Result Types\n// ============================================================================\n\nexport interface UseChartDataOptions {\n /** Analytics query key */\n queryKey: string;\n /** Query parameters */\n parameters?: Record<string, unknown>;\n /**\n * Data format preference\n * - \"json\": Force JSON format\n * - \"arrow\": Force Arrow format\n * - \"auto\": Auto-select based on heuristics\n * @default \"auto\"\n */\n format?: DataFormat;\n /** Transform data after fetching */\n transformer?: <T>(data: T) => T;\n}\n\nexport interface UseChartDataResult {\n /** The fetched data (Arrow Table or JSON array) */\n data: ChartData | null;\n /** Whether the data is in Arrow format */\n isArrow: boolean;\n /** Loading state */\n loading: boolean;\n /** Error message if any */\n error: string | null;\n /** Whether the data is empty */\n isEmpty: boolean;\n}\n\n// ============================================================================\n// Format Resolution\n// ============================================================================\n\n/**\n * Resolves the data format based on hints and preferences\n */\nfunction resolveFormat(\n format: DataFormat,\n parameters?: Record<string, unknown>,\n): \"JSON_ARRAY\" | \"ARROW_STREAM\" {\n // Explicit format selection\n if (format === \"json\") return \"JSON_ARRAY\";\n if (format === \"arrow\") return \"ARROW_STREAM\";\n\n // Auto-selection heuristics\n if (format === \"auto\") {\n // Check for explicit hint in parameters\n if (parameters?._preferArrow === true) return \"ARROW_STREAM\";\n if (parameters?._preferJson === true) return \"JSON_ARRAY\";\n\n // Check limit parameter as data size hint\n const limit = parameters?.limit;\n if (typeof limit === \"number\" && limit > ARROW_THRESHOLD) {\n return \"ARROW_STREAM\";\n }\n\n // Check for date range queries (often large)\n if (parameters?.startDate && parameters?.endDate) {\n return \"ARROW_STREAM\";\n }\n\n return \"JSON_ARRAY\";\n }\n\n return \"JSON_ARRAY\";\n}\n\n// ============================================================================\n// Main Hook\n// ============================================================================\n\n/**\n * Hook for fetching chart data in either JSON or Arrow format.\n * Automatically selects the best format based on query hints.\n *\n * @example\n * ```tsx\n * // Auto-select format\n * const { data, isArrow, loading } = useChartData({\n * queryKey: \"spend_data\",\n * parameters: { limit: 1000 }\n * });\n *\n * // Force Arrow format\n * const { data } = useChartData({\n * queryKey: \"big_query\",\n * format: \"arrow\"\n * });\n * ```\n */\nexport function useChartData(options: UseChartDataOptions): UseChartDataResult {\n const { queryKey, parameters, format = \"auto\", transformer } = options;\n\n // Resolve the format to use\n const resolvedFormat = useMemo(\n () => resolveFormat(format, parameters),\n [format, parameters],\n );\n\n const isArrowFormat = resolvedFormat === \"ARROW_STREAM\";\n\n // Fetch data using the analytics query hook\n const {\n data: rawData,\n loading,\n error,\n } = useAnalyticsQuery(queryKey, parameters, {\n autoStart: true,\n format: resolvedFormat,\n });\n\n // Process and transform data\n const processedData = useMemo(() => {\n if (!rawData) return null;\n\n // Apply transformer if provided\n if (transformer) {\n try {\n return transformer(rawData);\n } catch (err) {\n console.error(\"[useChartData] Transformer error:\", err);\n return rawData;\n }\n }\n\n return rawData;\n }, [rawData, transformer]);\n\n // Determine if data is empty\n const isEmpty = useMemo(() => {\n if (!processedData) return true;\n\n // Arrow Table - check using duck typing\n if (\n typeof processedData === \"object\" &&\n \"numRows\" in processedData &&\n typeof (processedData as Table).numRows === \"number\"\n ) {\n return (processedData as Table).numRows === 0;\n }\n\n // JSON Array\n if (Array.isArray(processedData)) {\n return processedData.length === 0;\n }\n\n return true;\n }, [processedData]);\n\n // Detect actual data type (may differ from requested if server doesn't support format)\n const isArrow = useMemo(() => {\n if (!processedData) return isArrowFormat;\n // Duck type check for Arrow Table\n return (\n typeof processedData === \"object\" &&\n processedData !== null &&\n \"schema\" in processedData &&\n \"numRows\" in processedData &&\n typeof (processedData as Table).getChild === \"function\"\n );\n }, [processedData, isArrowFormat]);\n\n return {\n data: processedData as ChartData | null,\n isArrow,\n loading,\n error,\n isEmpty,\n };\n}\n"],"mappings":";;;;;AAMA,MAAM,kBAAkB;;;;AA2CxB,SAAS,cACP,QACA,YAC+B;AAE/B,KAAI,WAAW,OAAQ,QAAO;AAC9B,KAAI,WAAW,QAAS,QAAO;AAG/B,KAAI,WAAW,QAAQ;AAErB,MAAI,YAAY,iBAAiB,KAAM,QAAO;AAC9C,MAAI,YAAY,gBAAgB,KAAM,QAAO;EAG7C,MAAM,QAAQ,YAAY;AAC1B,MAAI,OAAO,UAAU,YAAY,QAAQ,gBACvC,QAAO;AAIT,MAAI,YAAY,aAAa,YAAY,QACvC,QAAO;AAGT,SAAO;;AAGT,QAAO;;;;;;;;;;;;;;;;;;;;;AA0BT,SAAgB,aAAa,SAAkD;CAC7E,MAAM,EAAE,UAAU,YAAY,SAAS,QAAQ,gBAAgB;CAG/D,MAAM,iBAAiB,cACf,cAAc,QAAQ,WAAW,EACvC,CAAC,QAAQ,WAAW,CACrB;CAED,MAAM,gBAAgB,mBAAmB;CAGzC,MAAM,EACJ,MAAM,SACN,SACA,UACE,kBAAkB,UAAU,YAAY;EAC1C,WAAW;EACX,QAAQ;EACT,CAAC;CAGF,MAAM,gBAAgB,cAAc;AAClC,MAAI,CAAC,QAAS,QAAO;AAGrB,MAAI,YACF,KAAI;AACF,UAAO,YAAY,QAAQ;WACpB,KAAK;AACZ,WAAQ,MAAM,qCAAqC,IAAI;AACvD,UAAO;;AAIX,SAAO;IACN,CAAC,SAAS,YAAY,CAAC;CAG1B,MAAM,UAAU,cAAc;AAC5B,MAAI,CAAC,cAAe,QAAO;AAG3B,MACE,OAAO,kBAAkB,YACzB,aAAa,iBACb,OAAQ,cAAwB,YAAY,SAE5C,QAAQ,cAAwB,YAAY;AAI9C,MAAI,MAAM,QAAQ,cAAc,CAC9B,QAAO,cAAc,WAAW;AAGlC,SAAO;IACN,CAAC,cAAc,CAAC;AAenB,QAAO;EACL,MAAM;EACN,SAdc,cAAc;AAC5B,OAAI,CAAC,cAAe,QAAO;AAE3B,UACE,OAAO,kBAAkB,YACzB,kBAAkB,QAClB,YAAY,iBACZ,aAAa,iBACb,OAAQ,cAAwB,aAAa;KAE9C,CAAC,eAAe,cAAc,CAAC;EAKhC;EACA;EACA;EACD"}
1
+ {"version":3,"file":"use-chart-data.js","names":[],"sources":["../../../src/react/hooks/use-chart-data.ts"],"sourcesContent":["import type { Table } from \"apache-arrow\";\nimport { useMemo } from \"react\";\nimport type { ChartData, DataFormat } from \"../charts/types\";\nimport type { WarehouseStatus } from \"./types\";\nimport { useAnalyticsQuery } from \"./use-analytics-query\";\n\n/** Threshold for auto-selecting Arrow format (row count hint) */\nconst ARROW_THRESHOLD = 500;\n\n// ============================================================================\n// Hook Options & Result Types\n// ============================================================================\n\nexport interface UseChartDataOptions {\n /** Analytics query key */\n queryKey: string;\n /** Query parameters */\n parameters?: Record<string, unknown>;\n /**\n * Data format preference\n * - \"json\": Force JSON format\n * - \"arrow\": Force Arrow format\n * - \"auto\": Auto-select based on heuristics\n * @default \"auto\"\n */\n format?: DataFormat;\n /** Transform data after fetching */\n transformer?: <T>(data: T) => T;\n}\n\nexport interface UseChartDataResult {\n /** The fetched data (Arrow Table or JSON array) */\n data: ChartData | null;\n /** Whether the data is in Arrow format */\n isArrow: boolean;\n /** Loading state */\n loading: boolean;\n /** Error message if any */\n error: string | null;\n /** Whether the data is empty */\n isEmpty: boolean;\n /**\n * Latest warehouse readiness status from SSE. Retains the last value\n * (including `RUNNING`) until the next `start()`; `null` only before\n * the first event. Use with `loading` to distinguish warehouse warm-up\n * from in-flight SQL fetch.\n */\n warehouseStatus: WarehouseStatus | null;\n}\n\n// ============================================================================\n// Format Resolution\n// ============================================================================\n\n/**\n * Resolves the data format based on hints and preferences\n */\nfunction resolveFormat(\n format: DataFormat,\n parameters?: Record<string, unknown>,\n): \"JSON_ARRAY\" | \"ARROW_STREAM\" {\n // Explicit format selection\n if (format === \"json\") return \"JSON_ARRAY\";\n if (format === \"arrow\") return \"ARROW_STREAM\";\n\n // Auto-selection heuristics\n if (format === \"auto\") {\n // Check for explicit hint in parameters\n if (parameters?._preferArrow === true) return \"ARROW_STREAM\";\n if (parameters?._preferJson === true) return \"JSON_ARRAY\";\n\n // Check limit parameter as data size hint\n const limit = parameters?.limit;\n if (typeof limit === \"number\" && limit > ARROW_THRESHOLD) {\n return \"ARROW_STREAM\";\n }\n\n // Check for date range queries (often large)\n if (parameters?.startDate && parameters?.endDate) {\n return \"ARROW_STREAM\";\n }\n\n return \"JSON_ARRAY\";\n }\n\n return \"JSON_ARRAY\";\n}\n\n// ============================================================================\n// Main Hook\n// ============================================================================\n\n/**\n * Hook for fetching chart data in either JSON or Arrow format.\n * Automatically selects the best format based on query hints.\n *\n * @example\n * ```tsx\n * // Auto-select format\n * const { data, isArrow, loading } = useChartData({\n * queryKey: \"spend_data\",\n * parameters: { limit: 1000 }\n * });\n *\n * // Force Arrow format\n * const { data } = useChartData({\n * queryKey: \"big_query\",\n * format: \"arrow\"\n * });\n * ```\n */\nexport function useChartData(options: UseChartDataOptions): UseChartDataResult {\n const { queryKey, parameters, format = \"auto\", transformer } = options;\n\n // Resolve the format to use\n const resolvedFormat = useMemo(\n () => resolveFormat(format, parameters),\n [format, parameters],\n );\n\n const isArrowFormat = resolvedFormat === \"ARROW_STREAM\";\n\n // Fetch data using the analytics query hook\n const {\n data: rawData,\n loading,\n error,\n warehouseStatus,\n } = useAnalyticsQuery(queryKey, parameters, {\n autoStart: true,\n format: resolvedFormat,\n });\n\n // Process and transform data\n const processedData = useMemo(() => {\n if (!rawData) return null;\n\n // Apply transformer if provided\n if (transformer) {\n try {\n return transformer(rawData);\n } catch (err) {\n console.error(\"[useChartData] Transformer error:\", err);\n return rawData;\n }\n }\n\n return rawData;\n }, [rawData, transformer]);\n\n // Determine if data is empty\n const isEmpty = useMemo(() => {\n if (!processedData) return true;\n\n // Arrow Table - check using duck typing\n if (\n typeof processedData === \"object\" &&\n \"numRows\" in processedData &&\n typeof (processedData as Table).numRows === \"number\"\n ) {\n return (processedData as Table).numRows === 0;\n }\n\n // JSON Array\n if (Array.isArray(processedData)) {\n return processedData.length === 0;\n }\n\n return true;\n }, [processedData]);\n\n // Detect actual data type (may differ from requested if server doesn't support format)\n const isArrow = useMemo(() => {\n if (!processedData) return isArrowFormat;\n // Duck type check for Arrow Table\n return (\n typeof processedData === \"object\" &&\n processedData !== null &&\n \"schema\" in processedData &&\n \"numRows\" in processedData &&\n typeof (processedData as Table).getChild === \"function\"\n );\n }, [processedData, isArrowFormat]);\n\n return {\n data: processedData as ChartData | null,\n isArrow,\n loading,\n error,\n isEmpty,\n warehouseStatus,\n };\n}\n"],"mappings":";;;;;AAOA,MAAM,kBAAkB;;;;AAkDxB,SAAS,cACP,QACA,YAC+B;AAE/B,KAAI,WAAW,OAAQ,QAAO;AAC9B,KAAI,WAAW,QAAS,QAAO;AAG/B,KAAI,WAAW,QAAQ;AAErB,MAAI,YAAY,iBAAiB,KAAM,QAAO;AAC9C,MAAI,YAAY,gBAAgB,KAAM,QAAO;EAG7C,MAAM,QAAQ,YAAY;AAC1B,MAAI,OAAO,UAAU,YAAY,QAAQ,gBACvC,QAAO;AAIT,MAAI,YAAY,aAAa,YAAY,QACvC,QAAO;AAGT,SAAO;;AAGT,QAAO;;;;;;;;;;;;;;;;;;;;;AA0BT,SAAgB,aAAa,SAAkD;CAC7E,MAAM,EAAE,UAAU,YAAY,SAAS,QAAQ,gBAAgB;CAG/D,MAAM,iBAAiB,cACf,cAAc,QAAQ,WAAW,EACvC,CAAC,QAAQ,WAAW,CACrB;CAED,MAAM,gBAAgB,mBAAmB;CAGzC,MAAM,EACJ,MAAM,SACN,SACA,OACA,oBACE,kBAAkB,UAAU,YAAY;EAC1C,WAAW;EACX,QAAQ;EACT,CAAC;CAGF,MAAM,gBAAgB,cAAc;AAClC,MAAI,CAAC,QAAS,QAAO;AAGrB,MAAI,YACF,KAAI;AACF,UAAO,YAAY,QAAQ;WACpB,KAAK;AACZ,WAAQ,MAAM,qCAAqC,IAAI;AACvD,UAAO;;AAIX,SAAO;IACN,CAAC,SAAS,YAAY,CAAC;CAG1B,MAAM,UAAU,cAAc;AAC5B,MAAI,CAAC,cAAe,QAAO;AAG3B,MACE,OAAO,kBAAkB,YACzB,aAAa,iBACb,OAAQ,cAAwB,YAAY,SAE5C,QAAQ,cAAwB,YAAY;AAI9C,MAAI,MAAM,QAAQ,cAAc,CAC9B,QAAO,cAAc,WAAW;AAGlC,SAAO;IACN,CAAC,cAAc,CAAC;AAenB,QAAO;EACL,MAAM;EACN,SAdc,cAAc;AAC5B,OAAI,CAAC,cAAe,QAAO;AAE3B,UACE,OAAO,kBAAkB,YACzB,kBAAkB,QAClB,YAAY,iBACZ,aAAa,iBACb,OAAQ,cAAwB,aAAa;KAE9C,CAAC,eAAe,cAAc,CAAC;EAKhC;EACA;EACA;EACA;EACD"}
@@ -0,0 +1,92 @@
1
+ import { ReactNode } from "react";
2
+ import * as react_jsx_runtime0 from "react/jsx-runtime";
3
+
4
+ //#region src/react/hooks/use-resource-status.d.ts
5
+ /**
6
+ * Cross-kind severity, ordered worst-first (`error > warning > pending`).
7
+ * Callers `unpublish` rather than publishing a status for ready resources.
8
+ */
9
+ type ResourceSeverity = "pending" | "warning" | "error";
10
+ /**
11
+ * Readiness snapshot for a single resource (SQL warehouse, Lakebase
12
+ * connection, model-serving endpoint, …). Plugins publish these while a
13
+ * user-visible cold start / warm-up / unavailability is in flight.
14
+ */
15
+ interface ResourceStatus {
16
+ /** Resource family, conventionally lowercase-kebab (`"warehouse"`, `"lakebase"`). */
17
+ kind: string;
18
+ /** Resource-specific raw state, e.g. `"STARTING"`, `"DELETED"`. Opaque to the aggregator. */
19
+ state: string;
20
+ severity: ResourceSeverity;
21
+ /** Human-readable summary forwarded to the indicator UI. */
22
+ summary?: string;
23
+ /** Epoch ms when the publisher started waiting; drives `elapsedMs`. */
24
+ startedAt: number;
25
+ }
26
+ /** Aggregate view of every active publisher; returned by {@link useResourceStatus}. */
27
+ interface AggregatedResourceStatus {
28
+ /** Highest-severity status across all publishers, or `null` when nothing is pending. */
29
+ worst: ResourceStatus | null;
30
+ /** Worst status per `kind`. */
31
+ byKind: Record<string, ResourceStatus>;
32
+ /** De-duped, sorted labels of every publisher with a non-null status. */
33
+ affectedLabels: string[];
34
+ /** Total registered publishers (including those whose status is `null`). */
35
+ activeCount: number;
36
+ /** Milliseconds since the worst entry's `startedAt`; `0` when nothing is pending. */
37
+ elapsedMs: number;
38
+ /** Monotonic counter bumped on every `publish`/`unpublish`. */
39
+ version: number;
40
+ }
41
+ /** Optional filter for {@link useResourceStatus}. */
42
+ interface ResourceStatusFilter {
43
+ /** Restrict the aggregate to a single resource kind. */
44
+ kind?: string;
45
+ }
46
+ interface ResourceStatusProviderProps {
47
+ children: ReactNode;
48
+ }
49
+ /**
50
+ * Mount once near the root of your app to enable a global, cross-plugin
51
+ * readiness aggregate. Plugins publish {@link ResourceStatus} snapshots
52
+ * while a resource is warming up / unavailable; {@link useResourceStatus}
53
+ * exposes the worst across all of them.
54
+ *
55
+ * Without a provider, `useResourceStatus` and `useResourceStatusPublisher`
56
+ * fall back to no-ops, so plugins are safe to call them unconditionally.
57
+ *
58
+ * @example
59
+ * ```tsx
60
+ * <ResourceStatusProvider>
61
+ * <ResourceStatusIndicator />
62
+ * <App />
63
+ * </ResourceStatusProvider>
64
+ * ```
65
+ */
66
+ declare function ResourceStatusProvider({
67
+ children
68
+ }: ResourceStatusProviderProps): react_jsx_runtime0.JSX.Element;
69
+ /**
70
+ * Returns the aggregated resource-readiness snapshot across every active
71
+ * publisher under the nearest {@link ResourceStatusProvider}. Falls back
72
+ * to the empty/idle aggregate when no provider is mounted.
73
+ *
74
+ * @param filter Optional `{ kind }` to scope to a single resource kind.
75
+ */
76
+ declare function useResourceStatus(filter?: ResourceStatusFilter): AggregatedResourceStatus;
77
+ /**
78
+ * Register a publisher with the nearest {@link ResourceStatusProvider}.
79
+ * Safe to call without a provider — `publish`/`unpublish` are no-ops.
80
+ *
81
+ * @param id Stable identifier (e.g. a `useId()` value).
82
+ * @param label Human-readable label surfaced via `affectedLabels`.
83
+ */
84
+ declare function useResourceStatusPublisher(id: string, label: string, options?: {
85
+ kindHint?: string;
86
+ }): {
87
+ publish: (status: ResourceStatus | null) => void;
88
+ unpublish: () => void;
89
+ };
90
+ //#endregion
91
+ export { AggregatedResourceStatus, ResourceSeverity, ResourceStatus, ResourceStatusFilter, ResourceStatusProvider, ResourceStatusProviderProps, useResourceStatus, useResourceStatusPublisher };
92
+ //# sourceMappingURL=use-resource-status.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-resource-status.d.ts","names":[],"sources":["../../../src/react/hooks/use-resource-status.tsx"],"mappings":";;;;;;;AAgBA;KAAY,gBAAA;;;;AAOZ;;UAAiB,cAAA;EAKW;EAH1B,IAAA;EAEA;EAAA,KAAA;EACA,QAAA,EAAU,gBAAA;EAEV;EAAA,OAAA;EAES;EAAT,SAAA;AAAA;;UAIe,wBAAA;EAER;EAAP,KAAA,EAAO,cAAA;EAEC;EAAR,MAAA,EAAQ,MAAA,SAAe,cAAA;EAAT;EAEd,cAAA;EAJO;EAMP,WAAA;EAJQ;EAMR,SAAA;EAJA;EAMA,OAAA;AAAA;;UAIe,oBAAA;EAJR;EAMP,IAAA;AAAA;AAAA,UAsKe,2BAAA;EACf,QAAA,EAAU,SAAA;AAAA;AADZ;;;;;AAqBA;;;;;;;;;;;;AArBA,iBAqBgB,sBAAA,CAAA;EACd;AAAA,GACC,2BAAA,GAA2B,kBAAA,CAAA,GAAA,CAAA,OAAA;;;AAuB9B;;;;;iBAAgB,iBAAA,CACd,MAAA,GAAS,oBAAA,GACR,wBAAA;;;;AAgCH;;;;iBAAgB,0BAAA,CACd,EAAA,UACA,KAAA,UACA,OAAA;EAAY,QAAA;AAAA;EAEZ,OAAA,GAAU,MAAA,EAAQ,cAAA;EAClB,SAAA;AAAA"}