@databricks/appkit-ui 0.44.0 → 0.45.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.
- package/dist/js/arrow/arrow-client.d.ts +8 -1
- package/dist/js/arrow/arrow-client.d.ts.map +1 -1
- package/dist/js/arrow/arrow-client.js +21 -2
- package/dist/js/arrow/arrow-client.js.map +1 -1
- package/dist/js/sse/connect-sse.js +1 -1
- package/dist/js/sse/connect-sse.js.map +1 -1
- package/dist/react/charts/types.d.ts +11 -4
- package/dist/react/charts/types.d.ts.map +1 -1
- package/dist/react/charts/types.js.map +1 -1
- package/dist/react/hooks/types.d.ts +10 -2
- package/dist/react/hooks/types.d.ts.map +1 -1
- package/dist/react/hooks/use-analytics-query.d.ts +5 -3
- package/dist/react/hooks/use-analytics-query.d.ts.map +1 -1
- package/dist/react/hooks/use-analytics-query.js +114 -33
- package/dist/react/hooks/use-analytics-query.js.map +1 -1
- package/dist/react/hooks/use-chart-data.d.ts +3 -3
- package/dist/react/hooks/use-chart-data.d.ts.map +1 -1
- package/dist/react/hooks/use-chart-data.js +3 -3
- package/dist/react/hooks/use-chart-data.js.map +1 -1
- package/dist/shared/src/index.d.ts +1 -0
- package/dist/shared/src/sse/analytics.d.ts +1 -0
- package/docs/api/appkit/Class.AppKitError.md +40 -8
- package/docs/api/appkit/Class.AuthenticationError.md +56 -16
- package/docs/api/appkit/Class.ConfigurationError.md +57 -17
- package/docs/api/appkit/Class.ConnectionError.md +57 -17
- package/docs/api/appkit/Class.ExecutionError.md +80 -22
- package/docs/api/appkit/Class.InitializationError.md +55 -15
- package/docs/api/appkit/Class.ServerError.md +55 -15
- package/docs/api/appkit/Class.TunnelError.md +56 -16
- package/docs/api/appkit/Class.ValidationError.md +55 -15
- package/package.json +1 -1
- package/sbom.cdx.json +1 -1
|
@@ -8,9 +8,16 @@ declare class ArrowClient {
|
|
|
8
8
|
* Lazily loads the Apache Arrow library on first use.
|
|
9
9
|
*
|
|
10
10
|
* @param buffer - The Arrow IPC format buffer
|
|
11
|
+
* @param columnNames - Optional real column names to relabel the schema
|
|
12
|
+
* with. Databricks encodes ARROW_STREAM schemas positionally (`col_0`,
|
|
13
|
+
* …); the analytics server sends the manifest names in a response header
|
|
14
|
+
* so charts can look columns up by their real name. Relabels in-place by
|
|
15
|
+
* rebuilding the Table around the existing vectors (no re-decode). A
|
|
16
|
+
* no-op when the names already match, the count differs, or the names
|
|
17
|
+
* aren't unique.
|
|
11
18
|
* @returns Promise resolving to an Arrow Table
|
|
12
19
|
*/
|
|
13
|
-
static processArrowBuffer(buffer: Uint8Array): Promise<Table$1>;
|
|
20
|
+
static processArrowBuffer(buffer: Uint8Array, columnNames?: string[]): Promise<Table$1>;
|
|
14
21
|
/**
|
|
15
22
|
* Fetches Arrow data from a URL and processes it into a Table.
|
|
16
23
|
* Convenience method that combines fetchArrow and processArrowBuffer.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"arrow-client.d.ts","names":[],"sources":["../../../src/js/arrow/arrow-client.ts"],"mappings":";;;;cAmBa,WAAA;EAAW
|
|
1
|
+
{"version":3,"file":"arrow-client.d.ts","names":[],"sources":["../../../src/js/arrow/arrow-client.ts"],"mappings":";;;;cAmBa,WAAA;EAAW;;;;;;;;;;;;;;EAAA,OAeT,kBAAA,CACX,MAAA,EAAQ,UAAA,EACR,WAAA,cACC,OAAA,CAAQ,OAAA;EAuSC;;;;;;;;;;;;;EAAA,OA3PC,oBAAA,CACX,GAAA,UACA,OAAA,GAAU,MAAA,mBACT,OAAA,CAAQ,OAAA;EADT;;;;;;;;;;;EAAA,OA0BK,kBAAA,CAAmB,KAAA,EAAO,OAAA;;;;EA4CK;;;;;;;;;;;;EAAA,OAvB/B,mBAAA,CAAoB,KAAA,EAAO,OAAA,GAAQ,MAAA;EA0M9B;;;;;;AAwBd;;;EAxBc,OAnLL,gBAAA,CAAiB,KAAA,EAAO,OAAA,EAAO,IAAA,UAAc,KAAA;;cAyN1C,MAAA;EAAA;;;;;;;;;;;;;;;;;;;SAnKH,qBAAA,CACL,KAAA,EAAO,OAAA,EACP,WAAA,+BACC,cAAA;IAAmB,SAAA;EAAA;;;;;;;;;;;;;SAwHT,UAAA,CACX,GAAA,UACA,OAAA,GAAU,MAAA,mBACT,OAAA,CAAQ,UAAA;AAAA;AAAA,UAuBI,cAAA;;EAEf,MAAA;;EAEA,OAAA;AAAA"}
|
|
@@ -8,13 +8,32 @@ var ArrowClient = class ArrowClient {
|
|
|
8
8
|
* Lazily loads the Apache Arrow library on first use.
|
|
9
9
|
*
|
|
10
10
|
* @param buffer - The Arrow IPC format buffer
|
|
11
|
+
* @param columnNames - Optional real column names to relabel the schema
|
|
12
|
+
* with. Databricks encodes ARROW_STREAM schemas positionally (`col_0`,
|
|
13
|
+
* …); the analytics server sends the manifest names in a response header
|
|
14
|
+
* so charts can look columns up by their real name. Relabels in-place by
|
|
15
|
+
* rebuilding the Table around the existing vectors (no re-decode). A
|
|
16
|
+
* no-op when the names already match, the count differs, or the names
|
|
17
|
+
* aren't unique.
|
|
11
18
|
* @returns Promise resolving to an Arrow Table
|
|
12
19
|
*/
|
|
13
|
-
static async processArrowBuffer(buffer) {
|
|
20
|
+
static async processArrowBuffer(buffer, columnNames) {
|
|
14
21
|
try {
|
|
15
22
|
const arrow = await getArrowModule();
|
|
16
23
|
await initializeTypeIdSets();
|
|
17
|
-
|
|
24
|
+
const table = arrow.tableFromIPC(buffer);
|
|
25
|
+
if (!columnNames || columnNames.length !== table.numCols) return table;
|
|
26
|
+
const fields = table.schema.fields;
|
|
27
|
+
const targetNames = columnNames.map((n, i) => n && n.length > 0 ? n : fields[i].name);
|
|
28
|
+
if (targetNames.every((n, i) => n === fields[i].name)) return table;
|
|
29
|
+
if (new Set(targetNames).size !== targetNames.length) return table;
|
|
30
|
+
const children = {};
|
|
31
|
+
for (let i = 0; i < fields.length; i++) {
|
|
32
|
+
const child = table.getChildAt(i);
|
|
33
|
+
if (!child) return table;
|
|
34
|
+
children[targetNames[i]] = child;
|
|
35
|
+
}
|
|
36
|
+
return new arrow.Table(children);
|
|
18
37
|
} catch (error) {
|
|
19
38
|
throw new Error(`Failed to process Arrow buffer: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
20
39
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"arrow-client.js","names":[],"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 getDecimalTypeId,\n getTypeIdSets,\n initializeTypeIdSets,\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 /**\n * Fetches Arrow data from a URL and processes it into a Table.\n * Convenience method that combines fetchArrow and processArrowBuffer.\n *\n * @param url - URL to fetch Arrow data from\n * @param headers - Optional HTTP headers to include in the request\n * @returns Promise resolving to an Arrow Table\n * @example\n * ```typescript\n * const table = await ArrowClient.fetchAndProcessArrow('/api/data/arrow');\n * console.log(`Loaded ${table.numRows} rows`);\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 /**\n * Extracts field metadata (name and type) from an Arrow Table.\n *\n * @param table - Arrow Table to extract fields from\n * @returns Array of field metadata objects\n * @example\n * ```typescript\n * const fields = ArrowClient.extractArrowFields(table);\n * // [{ name: \"date\", type: Date32 }, { name: \"value\", type: Float64 }]\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 /**\n * Extracts all columns from an Arrow Table as JavaScript arrays.\n * Each column is converted to a native JavaScript array.\n *\n * @param table - Arrow Table to extract columns from\n * @returns Object mapping column names to arrays\n * @example\n * ```typescript\n * const columns = ArrowClient.extractArrowColumns(table);\n * // { date: [\"2024-01-01\", \"2024-01-02\"], value: [100, 200] }\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 /**\n * Fetches raw Arrow IPC data from a URL.\n *\n * @param url - URL to fetch Arrow data from\n * @param headers - Optional HTTP headers to include in the request\n * @returns Promise resolving to the raw Arrow buffer as Uint8Array\n * @example\n * ```typescript\n * const buffer = await ArrowClient.fetchArrow('/api/data/arrow');\n * const table = await ArrowClient.processArrowBuffer(buffer);\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":[],"sources":["../../../src/js/arrow/arrow-client.ts"],"sourcesContent":["import type { Field, Table, Vector } from \"apache-arrow\";\nimport {\n DATE_FIELD_PATTERNS,\n METADATA_DATE_PATTERNS,\n NAME_FIELD_PATTERNS,\n} from \"../constants\";\nimport {\n getArrowModule,\n getDecimalTypeId,\n getTypeIdSets,\n initializeTypeIdSets,\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 * @param columnNames - Optional real column names to relabel the schema\n * with. Databricks encodes ARROW_STREAM schemas positionally (`col_0`,\n * …); the analytics server sends the manifest names in a response header\n * so charts can look columns up by their real name. Relabels in-place by\n * rebuilding the Table around the existing vectors (no re-decode). A\n * no-op when the names already match, the count differs, or the names\n * aren't unique.\n * @returns Promise resolving to an Arrow Table\n */\n static async processArrowBuffer(\n buffer: Uint8Array,\n columnNames?: string[],\n ): Promise<Table> {\n try {\n const arrow = await getArrowModule();\n // Initialize type ID sets now that Arrow is loaded\n await initializeTypeIdSets();\n const table = arrow.tableFromIPC(buffer);\n\n if (!columnNames || columnNames.length !== table.numCols) return table;\n const fields = table.schema.fields;\n const targetNames = columnNames.map((n, i) =>\n n && n.length > 0 ? n : fields[i].name,\n );\n if (targetNames.every((n, i) => n === fields[i].name)) return table;\n if (new Set(targetNames).size !== targetNames.length) return table;\n\n const children: Record<string, Vector> = {};\n for (let i = 0; i < fields.length; i++) {\n const child = table.getChildAt(i);\n if (!child) return table;\n children[targetNames[i]] = child;\n }\n return new arrow.Table(children);\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 /**\n * Fetches Arrow data from a URL and processes it into a Table.\n * Convenience method that combines fetchArrow and processArrowBuffer.\n *\n * @param url - URL to fetch Arrow data from\n * @param headers - Optional HTTP headers to include in the request\n * @returns Promise resolving to an Arrow Table\n * @example\n * ```typescript\n * const table = await ArrowClient.fetchAndProcessArrow('/api/data/arrow');\n * console.log(`Loaded ${table.numRows} rows`);\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 /**\n * Extracts field metadata (name and type) from an Arrow Table.\n *\n * @param table - Arrow Table to extract fields from\n * @returns Array of field metadata objects\n * @example\n * ```typescript\n * const fields = ArrowClient.extractArrowFields(table);\n * // [{ name: \"date\", type: Date32 }, { name: \"value\", type: Float64 }]\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 /**\n * Extracts all columns from an Arrow Table as JavaScript arrays.\n * Each column is converted to a native JavaScript array.\n *\n * @param table - Arrow Table to extract columns from\n * @returns Object mapping column names to arrays\n * @example\n * ```typescript\n * const columns = ArrowClient.extractArrowColumns(table);\n * // { date: [\"2024-01-01\", \"2024-01-02\"], value: [100, 200] }\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 /**\n * Fetches raw Arrow IPC data from a URL.\n *\n * @param url - URL to fetch Arrow data from\n * @param headers - Optional HTTP headers to include in the request\n * @returns Promise resolving to the raw Arrow buffer as Uint8Array\n * @example\n * ```typescript\n * const buffer = await ArrowClient.fetchArrow('/api/data/arrow');\n * const table = await ArrowClient.processArrowBuffer(buffer);\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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"}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* @returns Promise that resolves when the stream ends or retries stop
|
|
7
7
|
*/
|
|
8
8
|
async function connectSSE(options, attempt = 0) {
|
|
9
|
-
const { url, payload, onMessage, signal, lastEventId: initialLastEventId = null, retryDelay = 2e3, maxRetries = 3, maxBufferSize = 1024 * 1024, timeout = 3e5, onError } = options;
|
|
9
|
+
const { url, payload, onMessage, signal, lastEventId: initialLastEventId = null, retryDelay = 2e3, maxRetries = 3, maxBufferSize = 1 * 1024 * 1024, timeout = 3e5, onError } = options;
|
|
10
10
|
if (!url || url.trim().length <= 0) throw new Error("connectSSE: 'url' must be a non-empty string.");
|
|
11
11
|
if (retryDelay <= 0) throw new Error("connectSSE: 'retryDelay' must be >= 0.");
|
|
12
12
|
if (maxBufferSize <= 0) throw new Error("connectSSE: 'maxBufferSize' must be > 0.");
|
|
@@ -1 +1 @@
|
|
|
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 =
|
|
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 // 1 MiB — matches the server's `streamDefaults.maxEventSize`. SSE\n // carries only short JSON control messages; bulk Arrow payloads stream\n // back as the raw HTTP response body of the query request, so this\n // buffer never needs to hold multi-MiB attachments.\n maxBufferSize = 1 * 1024 * 1024,\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,8 +1,15 @@
|
|
|
1
1
|
import { Table } from "apache-arrow";
|
|
2
2
|
|
|
3
3
|
//#region src/react/charts/types.d.ts
|
|
4
|
-
/**
|
|
5
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Supported data formats for analytics queries.
|
|
6
|
+
*
|
|
7
|
+
* "json" and "arrow" are legacy aliases kept for backwards compatibility
|
|
8
|
+
* with appkit-ui < 0.33.0 — safe to remove once no consumer is on a
|
|
9
|
+
* pre-0.33.0 version. resolveFormat() normalizes them to their canonical
|
|
10
|
+
* equivalents before any downstream code reads the value.
|
|
11
|
+
*/
|
|
12
|
+
type DataFormat = "json_array" | "arrow_stream" | "auto" /** @deprecated Use "json_array". Safe to remove once no consumer is on appkit-ui < 0.33.0. */ | "json" /** @deprecated Use "arrow_stream". Safe to remove once no consumer is on appkit-ui < 0.33.0. */ | "arrow";
|
|
6
13
|
/** Chart orientation */
|
|
7
14
|
type Orientation = "vertical" | "horizontal";
|
|
8
15
|
/** Supported chart types */
|
|
@@ -60,8 +67,8 @@ interface QueryProps extends ChartBaseProps {
|
|
|
60
67
|
parameters?: Record<string, unknown>;
|
|
61
68
|
/**
|
|
62
69
|
* Data format to use
|
|
63
|
-
* - "
|
|
64
|
-
* - "
|
|
70
|
+
* - "json_array": Use JSON format (smaller payloads, simpler)
|
|
71
|
+
* - "arrow_stream": Use Arrow format (faster for large datasets)
|
|
65
72
|
* - "auto": Automatically select based on expected data size
|
|
66
73
|
* @default "auto"
|
|
67
74
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","names":[],"sources":["../../../src/react/charts/types.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"types.d.ts","names":[],"sources":["../../../src/react/charts/types.ts"],"mappings":";;;;;AAcA;;;;;AAUA;KAVY,UAAA;;KAUA,WAAA;;KAGA,SAAA;AAWZ;AAAA,KAAY,SAAA,GAAY,KAAA,GAAQ,MAAA;;KAOpB,iBAAA;;UAGK,aAAA;EAHY;EAK3B,SAAA;EAL2B;EAO3B,SAAA;EAJe;EAMf,IAAA;;EAEA,SAAA;AAAA;;UAIe,cAAA;EAJf;EAMA,KAAA;EANS;EAQT,UAAA;EAJ6B;;;;;;EAW7B,YAAA,GAAe,iBAAA;EAEf;EAAA,MAAA;EAIA;EAFA,MAAA;EAOA;EALA,SAAA;EAUA;EAPA,IAAA;EAUU;EARV,IAAA;EAQgB;EALhB,SAAA;EAa0B;EAX1B,MAAA;EAea;EAZb,OAAA,GAAU,MAAA;AAAA;;UAQK,UAAA,SAAmB,cAAA;EAAc;EAEhD,QAAA;EAFkC;EAIlC,UAAA,GAAa,MAAA;EAAb;;;;;;;EAQA,MAAA,GAAS,UAAA;EAEqB;EAA9B,WAAA,OAAkB,IAAA,EAAM,CAAA,KAAM,CAAA;EAE9B,IAAA;AAAA;AAQF;AAAA,UAAiB,SAAA,SAAkB,cAAA;;EAEjC,IAAA,EAAM,SAAA;EAGN,QAAA;EACA,UAAA;EACA,MAAA;EACA,WAAA;AAAA;;KAQU,iBAAA,GAAoB,UAAA,GAAa,SAAA;;UAO5B,qBAAA;EAPL;EASV,WAAA,GAAc,WAAA;;EAEd,OAAA;AAAA;AAJF;AAAA,UAQiB,sBAAA;;EAEf,WAAA,GAAc,WAAA;EARd;EAUA,UAAA;EARA;EAUA,MAAA;AAAA;AANF;AAAA,UAUiB,sBAAA;;EAEf,WAAA,GAAc,WAAA;EAVd;EAYA,UAAA;EAVA;EAYA,MAAA;EAVM;EAYN,OAAA;AAAA;;UAIe,yBAAA;EAVU;EAYzB,UAAA;AAAA;;UAIe,qBAAA;EAVf;EAYA,WAAA;EAZO;EAcP,UAAA;EAVwC;EAYxC,aAAA;AAAA;;UAIe,uBAAA;EAVqB;EAYpC,QAAA;AAAA;;UAIe,yBAAA;EAVf;;;AAIF;EAWE,QAAA;;EAEA,GAAA;EAXQ;EAaR,GAAA;EATwC;EAWxC,UAAA;AAAA;AAAA,KAOU,aAAA,IAAiB,UAAA,GAAa,SAAA,IAAa,qBAAA;AAAA,KAC3C,cAAA,IAAkB,UAAA,GAAa,SAAA,IAAa,sBAAA;AAAA,KAC5C,cAAA,IAAkB,UAAA,GAAa,SAAA,IAAa,sBAAA;AAAA,KAC5C,iBAAA,IAAqB,UAAA,GAAa,SAAA,IAC5C,yBAAA;AAAA,KACU,aAAA,IAAiB,UAAA,GAAa,SAAA,IAAa,qBAAA;AAAA,KAC3C,eAAA,IAAmB,UAAA,GAAa,SAAA,IAAa,qBAAA;AAAA,KAC7C,eAAA,IAAmB,UAAA,GAAa,SAAA,IAC1C,uBAAA;AAAA,KACU,iBAAA,IAAqB,UAAA,GAAa,SAAA,IAC5C,yBAAA;;UAOe,uBAAA;EACf,KAAA;EACA,MAAA;EACA,OAAA;EACA,SAAA;AAAA;;UAIe,mBAAA,SAA4B,uBAAA;EAC3C,QAAA,EAAU,MAAA;AAAA;AAzBZ;AAAA,iBA6BgB,YAAA,CAAa,IAAA,EAAM,SAAA,GAAY,IAAA,IAAQ,KAAA;;iBAWvC,YAAA,CAAa,KAAA,EAAO,iBAAA,GAAoB,KAAA,IAAS,UAAA;;iBASjD,WAAA,CAAY,KAAA,EAAO,iBAAA,GAAoB,KAAA,IAAS,SAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","names":[],"sources":["../../../src/react/charts/types.ts"],"sourcesContent":["import type { Table } from \"apache-arrow\";\n\n// ============================================================================\n// Data Format Types\n// ============================================================================\n\n
|
|
1
|
+
{"version":3,"file":"types.js","names":[],"sources":["../../../src/react/charts/types.ts"],"sourcesContent":["import type { Table } from \"apache-arrow\";\n\n// ============================================================================\n// Data Format Types\n// ============================================================================\n\n/**\n * Supported data formats for analytics queries.\n *\n * \"json\" and \"arrow\" are legacy aliases kept for backwards compatibility\n * with appkit-ui < 0.33.0 — safe to remove once no consumer is on a\n * pre-0.33.0 version. resolveFormat() normalizes them to their canonical\n * equivalents before any downstream code reads the value.\n */\nexport type DataFormat =\n | \"json_array\"\n | \"arrow_stream\"\n | \"auto\"\n /** @deprecated Use \"json_array\". Safe to remove once no consumer is on appkit-ui < 0.33.0. */\n | \"json\"\n /** @deprecated Use \"arrow_stream\". Safe to remove once no consumer is on appkit-ui < 0.33.0. */\n | \"arrow\";\n\n/** Chart orientation */\nexport type Orientation = \"vertical\" | \"horizontal\";\n\n/** Supported chart types */\nexport type ChartType =\n | \"bar\"\n | \"line\"\n | \"area\"\n | \"pie\"\n | \"donut\"\n | \"scatter\"\n | \"radar\"\n | \"heatmap\";\n\n/** Data that can be passed to unified charts */\nexport type ChartData = Table | Record<string, unknown>[];\n\n// ============================================================================\n// Base Props (shared by all charts)\n// ============================================================================\n\n/** Color palette types for different visualization needs */\nexport type ChartColorPalette = \"categorical\" | \"sequential\" | \"diverging\";\n\n/** Resolved colors for the chart UI — axis text, titles, grid lines, and tooltip. */\nexport interface ChartUITokens {\n /** Axis tick labels (≈ `--muted-foreground`) */\n axisLabel: string;\n /** Axis names, legend, title, and visualMap text (≈ `--foreground`) */\n axisTitle: string;\n /** Axis, tick, and split (grid) lines (≈ `--border`) */\n grid: string;\n /** Tooltip popover background (≈ `--popover`) */\n tooltipBg: string;\n}\n\n/** Common visual and behavior props for all charts */\nexport interface ChartBaseProps {\n /** Chart title */\n title?: string;\n /** Show legend */\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/intensity (heatmap)\n * - \"diverging\": Two-tone for positive/negative values\n */\n colorPalette?: ChartColorPalette;\n /** Custom colors for series (overrides colorPalette) */\n colors?: string[];\n /** Chart height in pixels @default 300 */\n height?: number;\n /** Additional CSS classes */\n className?: string;\n\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\n /** Accessibility label for screen readers */\n ariaLabel?: string;\n /** Test ID for automated testing */\n testId?: string;\n\n /** Additional ECharts options to merge */\n options?: Record<string, unknown>;\n}\n\n// ============================================================================\n// Query-based Props (chart fetches data)\n// ============================================================================\n\n/** Props for query-based data fetching */\nexport interface QueryProps extends ChartBaseProps {\n /** Analytics query key registered with analytics plugin */\n queryKey: string;\n /** Query parameters passed to the analytics endpoint */\n parameters?: Record<string, unknown>;\n /**\n * Data format to use\n * - \"json_array\": Use JSON format (smaller payloads, simpler)\n * - \"arrow_stream\": Use Arrow format (faster for large datasets)\n * - \"auto\": Automatically select based on expected data size\n * @default \"auto\"\n */\n format?: DataFormat;\n /** Transform raw data before rendering */\n transformer?: <T>(data: T) => T;\n // Discriminator: cannot use direct data with query\n data?: never;\n}\n\n// ============================================================================\n// Data-based Props (chart receives data externally)\n// ============================================================================\n\n/** Props for direct data injection */\nexport interface DataProps extends ChartBaseProps {\n /** Arrow Table or JSON array */\n data: ChartData;\n\n // Discriminator: cannot use query props with direct data\n queryKey?: never;\n parameters?: never;\n format?: never;\n transformer?: never;\n}\n\n// ============================================================================\n// Union Types for Each Chart\n// ============================================================================\n\n/** Base union type - either query-based or data-based */\nexport type UnifiedChartProps = QueryProps | DataProps;\n\n// ============================================================================\n// Chart-Specific Props\n// ============================================================================\n\n/** Props specific to bar charts */\nexport interface BarChartSpecificProps {\n /** Chart orientation @default \"vertical\" */\n orientation?: Orientation;\n /** Stack bars */\n stacked?: boolean;\n}\n\n/** Props specific to line charts */\nexport interface LineChartSpecificProps {\n /** Chart orientation @default \"vertical\" */\n orientation?: Orientation;\n /** Show data point symbols @default false */\n showSymbol?: boolean;\n /** Smooth line curves @default true */\n smooth?: boolean;\n}\n\n/** Props specific to area charts */\nexport interface AreaChartSpecificProps {\n /** Chart orientation @default \"vertical\" */\n orientation?: Orientation;\n /** Show data point symbols @default false */\n showSymbol?: boolean;\n /** Smooth line curves @default true */\n smooth?: boolean;\n /** Stack areas @default false */\n stacked?: boolean;\n}\n\n/** Props specific to scatter charts */\nexport interface ScatterChartSpecificProps {\n /** Symbol size @default 8 */\n symbolSize?: number;\n}\n\n/** Props specific to pie/donut charts */\nexport interface PieChartSpecificProps {\n /** Inner radius for donut charts (0-100%) @default 0 */\n innerRadius?: number;\n /** Show labels on slices @default true */\n showLabels?: boolean;\n /** Label position @default \"outside\" */\n labelPosition?: \"outside\" | \"inside\" | \"center\";\n}\n\n/** Props specific to radar charts */\nexport interface RadarChartSpecificProps {\n /** Show area fill @default true */\n showArea?: boolean;\n}\n\n/** Props specific to heatmap charts */\nexport interface HeatmapChartSpecificProps {\n /**\n * Field key for the Y-axis categories.\n * For heatmaps, data should have: xKey (column), yAxisKey (row), and yKey (value).\n */\n yAxisKey?: string;\n /** Min value for color scale (auto-detected if not provided) */\n min?: number;\n /** Max value for color scale (auto-detected if not provided) */\n max?: number;\n /** Show value labels on cells @default false */\n showLabels?: boolean;\n}\n\n// ============================================================================\n// Complete Chart Props (union + specific)\n// ============================================================================\n\nexport type BarChartProps = (QueryProps | DataProps) & BarChartSpecificProps;\nexport type LineChartProps = (QueryProps | DataProps) & LineChartSpecificProps;\nexport type AreaChartProps = (QueryProps | DataProps) & AreaChartSpecificProps;\nexport type ScatterChartProps = (QueryProps | DataProps) &\n ScatterChartSpecificProps;\nexport type PieChartProps = (QueryProps | DataProps) & PieChartSpecificProps;\nexport type DonutChartProps = (QueryProps | DataProps) & PieChartSpecificProps;\nexport type RadarChartProps = (QueryProps | DataProps) &\n RadarChartSpecificProps;\nexport type HeatmapChartProps = (QueryProps | DataProps) &\n HeatmapChartSpecificProps;\n\n// ============================================================================\n// Internal Types\n// ============================================================================\n\n/** Base normalized data shared by all chart types */\nexport interface NormalizedChartDataBase {\n xData: (string | number)[];\n xField: string;\n yFields: string[];\n chartType: \"timeseries\" | \"categorical\";\n}\n\n/** Normalized chart data for rendering (standard charts) */\nexport interface NormalizedChartData extends NormalizedChartDataBase {\n yDataMap: Record<string, (string | number)[]>;\n}\n\n/** Type guard to check if data is an Arrow Table */\nexport function isArrowTable(data: ChartData): data is Table {\n return (\n data !== null &&\n typeof data === \"object\" &&\n \"schema\" in data &&\n \"numRows\" in data &&\n typeof (data as Table).getChild === \"function\"\n );\n}\n\n/** Type guard to check if props are query-based */\nexport function isQueryProps(props: UnifiedChartProps): props is QueryProps {\n return (\n \"queryKey\" in props &&\n typeof props.queryKey === \"string\" &&\n props.queryKey.length > 0\n );\n}\n\n/** Type guard to check if props are data-based */\nexport function isDataProps(props: UnifiedChartProps): props is DataProps {\n return \"data\" in props && props.data != null;\n}\n"],"mappings":";;AAqPA,SAAgB,aAAa,MAAgC;AAC3D,QACE,SAAS,QACT,OAAO,SAAS,YAChB,YAAY,QACZ,aAAa,QACb,OAAQ,KAAe,aAAa;;;AAKxC,SAAgB,aAAa,OAA+C;AAC1E,QACE,cAAc,SACd,OAAO,MAAM,aAAa,YAC1B,MAAM,SAAS,SAAS;;;AAK5B,SAAgB,YAAY,OAA8C;AACxE,QAAO,UAAU,SAAS,MAAM,QAAQ"}
|
|
@@ -28,7 +28,7 @@ interface TypedArrowTable<TRow extends Record<string, unknown> = Record<string,
|
|
|
28
28
|
}
|
|
29
29
|
/** Options for configuring an analytics SSE query */
|
|
30
30
|
interface UseAnalyticsQueryOptions<F extends AnalyticsFormat = "JSON_ARRAY"> {
|
|
31
|
-
/** Response format - "JSON_ARRAY" returns typed arrays, "ARROW_STREAM"
|
|
31
|
+
/** Response format - "JSON_ARRAY" (default) returns typed arrays, "ARROW_STREAM" uses Arrow (inline or external links) */
|
|
32
32
|
format?: F;
|
|
33
33
|
/** Maximum size of serialized parameters in bytes */
|
|
34
34
|
maxParametersSize?: number;
|
|
@@ -62,8 +62,16 @@ interface UseAnalyticsQueryResult<T> {
|
|
|
62
62
|
data: T | null;
|
|
63
63
|
/** Loading state of the query */
|
|
64
64
|
loading: boolean;
|
|
65
|
-
/** Error state of the query */
|
|
65
|
+
/** Error state of the query — sanitized human-readable message */
|
|
66
66
|
error: string | null;
|
|
67
|
+
/**
|
|
68
|
+
* Structured upstream error code when the server attaches one to the
|
|
69
|
+
* error payload (e.g. `RESULT_TOO_LARGE_FOR_JSON_FALLBACK`,
|
|
70
|
+
* `ARROW_DELIVERY_UNSUPPORTED`, `NOT_IMPLEMENTED`). Prefer branching UI
|
|
71
|
+
* on this stable identifier rather than substring-matching `error`,
|
|
72
|
+
* which is a free-form sanitized message.
|
|
73
|
+
*/
|
|
74
|
+
errorCode: string | null;
|
|
67
75
|
/**
|
|
68
76
|
* Latest warehouse status emitted by the server while waiting for the SQL
|
|
69
77
|
* warehouse to reach RUNNING. `null` until the first status event arrives;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","names":[],"sources":["../../../src/react/hooks/types.ts"],"mappings":";;;;;AAaA;;;;;KAAY,eAAA;;;;;;;;;;;UAkBK,eAAA,cACF,MAAA,oBAA0B,MAAA,2BAC/B,KAAA;EAKiB;AAQ3B;;;EAR2B,SAAhB,SAAA,GAAY,IAAA;AAAA;;UAQN,wBAAA,WACL,eAAA;EAGD;EAAT,MAAA,GAAS,CAAA;EAMT;EAHA,iBAAA;EAGS;EAAT,SAAA;AAAA;;;;AA0BF;;KAlBY,cAAA;;;;;;;AAyBZ;;;;UAPiB,eAAA;EACf,KAAA,EAAO,cAAA;EAQD;EANN,SAAA;AAAA;;UAIe,uBAAA;
|
|
1
|
+
{"version":3,"file":"types.d.ts","names":[],"sources":["../../../src/react/hooks/types.ts"],"mappings":";;;;;AAaA;;;;;KAAY,eAAA;;;;;;;;;;;UAkBK,eAAA,cACF,MAAA,oBAA0B,MAAA,2BAC/B,KAAA;EAKiB;AAQ3B;;;EAR2B,SAAhB,SAAA,GAAY,IAAA;AAAA;;UAQN,wBAAA,WACL,eAAA;EAGD;EAAT,MAAA,GAAS,CAAA;EAMT;EAHA,iBAAA;EAGS;EAAT,SAAA;AAAA;;;;AA0BF;;KAlBY,cAAA;;;;;;;AAyBZ;;;;UAPiB,eAAA;EACf,KAAA,EAAO,cAAA;EAQD;EANN,SAAA;AAAA;;UAIe,uBAAA;EAoBE;EAlBjB,IAAA,EAAM,CAAA;EAkB0B;EAhBhC,OAAA;EAqC4B;EAnC5B,KAAA;EAsCoB;;;;;;;EA9BpB,SAAA;EAoCU;;;;;EA9BV,eAAA,EAAiB,eAAA;AAAA;;;;;;;;;;;;;AAmCnB;;;;;;UAdiB,aAAA;EAAA,CACd,GAAA;IACC,IAAA;IACA,UAAA,EAAY,MAAA;IACZ,MAAA;EAAA;AAAA;;KAKQ,iBAAA,0BACE,CAAA,mBAAoB,CAAA,WAAY,CAAA,GAAI,CAAA,CAAE,CAAA;;KAIxC,QAAA,GAAW,iBAAA,CAAkB,aAAA,2BAErC,iBAAA,CAAkB,aAAA;;;;;KAMV,WAAA,SAAoB,CAAA,SAAU,iBAAA,CAAkB,aAAA,IACxD,aAAA,CAAc,CAAA;EAAa,MAAA;AAAA,IACzB,CAAA,GACA,CAAA,GACF,CAAA;;;;;KAMQ,YAAA,MAAkB,CAAA,SAAU,iBAAA,CAAkB,aAAA,IACtD,aAAA,CAAc,CAAA;EAAa,MAAA,EAAQ,KAAA;AAAA,IACjC,CAAA,SAAU,MAAA,oBACR,CAAA,GACA,MAAA,oBACF,MAAA,oBACF,MAAA;;;;;;KAOQ,mBAAA,iBAAoC,eAAA,IAAmB,CAAA,oCAG/D,eAAA,CAAgB,YAAA,CAAa,CAAA,KAC7B,WAAA,CAAY,CAAA,EAAG,CAAA;;;AAjBnB;KAsBY,WAAA,MAAiB,CAAA,SAAU,iBAAA,CAAkB,aAAA,IACrD,aAAA,CAAc,CAAA;EAAa,UAAA;AAAA,IACzB,CAAA,GACA,MAAA,oBACF,MAAA;AAAA,UAEa,cAAA;EAAA,CACd,GAAA,WAAc,MAAA;AAAA;;;;;;;;;;;;;;;UA2BA,uBAAA;;KAGL,YAAA,GACV,iBAAA,CAAkB,uBAAA,2BAEd,iBAAA,CAAkB,uBAAA;;KAGZ,iBAAA,MACV,CAAA,SAAU,iBAAA,CAAkB,uBAAA,IACxB,uBAAA,CAAwB,CAAA;EAAa,KAAA;AAAA,IACnC,CAAA;;KAKI,oBAAA,MACV,CAAA,SAAU,iBAAA,CAAkB,uBAAA,IACxB,uBAAA,CAAwB,CAAA;EAAa,QAAA;AAAA,IACnC,CAAA;;KAKI,mBAAA,MACV,CAAA,SAAU,iBAAA,CAAkB,uBAAA,IACxB,uBAAA,CAAwB,CAAA;EAAa,OAAA;AAAA,IACnC,GAAA,GACA,MAAA,oBACF,MAAA"}
|
|
@@ -2,7 +2,9 @@ import { AnalyticsFormat, InferParams, InferResultByFormat, QueryKey, UseAnalyti
|
|
|
2
2
|
|
|
3
3
|
//#region src/react/hooks/use-analytics-query.d.ts
|
|
4
4
|
/**
|
|
5
|
-
* Subscribe to an analytics query
|
|
5
|
+
* Subscribe to an analytics query and return its latest result. JSON_ARRAY
|
|
6
|
+
* results stream over SSE (with warehouse-readiness progress); ARROW_STREAM
|
|
7
|
+
* results are fetched as raw Arrow bytes directly from the query endpoint.
|
|
6
8
|
* Integration hook between client and analytics plugin.
|
|
7
9
|
*
|
|
8
10
|
* The return type is automatically inferred based on the format:
|
|
@@ -18,13 +20,13 @@ import { AnalyticsFormat, InferParams, InferResultByFormat, QueryKey, UseAnalyti
|
|
|
18
20
|
* @param options - Analytics query settings including format
|
|
19
21
|
* @returns Query result state with format-appropriate data type
|
|
20
22
|
*
|
|
21
|
-
* @example
|
|
23
|
+
* @example JSON_ARRAY format (default)
|
|
22
24
|
* ```typescript
|
|
23
25
|
* const { data } = useAnalyticsQuery("spend_data", params);
|
|
24
26
|
* // data: Array<{ group_key: string; cost_usd: number; ... }> | null
|
|
25
27
|
* ```
|
|
26
28
|
*
|
|
27
|
-
* @example
|
|
29
|
+
* @example ARROW_STREAM format
|
|
28
30
|
* ```typescript
|
|
29
31
|
* const { data } = useAnalyticsQuery("spend_data", params, { format: "ARROW_STREAM" });
|
|
30
32
|
* // data: TypedArrowTable<{ group_key: string; cost_usd: number; ... }> | null
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-analytics-query.d.ts","names":[],"sources":["../../../src/react/hooks/use-analytics-query.ts"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"use-analytics-query.d.ts","names":[],"sources":["../../../src/react/hooks/use-analytics-query.ts"],"mappings":";;;;;AA6SA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAgB,iBAAA,wBAEJ,QAAA,GAAW,QAAA,YACX,eAAA,gBAAA,CAEV,QAAA,EAAU,CAAA,EACV,UAAA,GAAa,WAAA,CAAY,CAAA,UACzB,OAAA,GAAS,wBAAA,CAAyB,CAAA,IACjC,uBAAA,CAAwB,mBAAA,CAAoB,CAAA,EAAG,CAAA,EAAG,CAAA"}
|
|
@@ -28,10 +28,15 @@ function getDevMode() {
|
|
|
28
28
|
const dev = new URL(window.location.href).searchParams.get("dev");
|
|
29
29
|
return dev ? `?dev=${dev}` : "";
|
|
30
30
|
}
|
|
31
|
-
function getArrowStreamUrl(id) {
|
|
32
|
-
return `/api/analytics/arrow-result/${id}`;
|
|
33
|
-
}
|
|
34
31
|
const GENERIC_LOAD_ERROR = "Unable to load data, please try again";
|
|
32
|
+
/** Map a fetch/SSE transport error to a user-facing message. */
|
|
33
|
+
function userFacingFetchError(error) {
|
|
34
|
+
if (error instanceof Error) {
|
|
35
|
+
if (error.name === "AbortError") return "Request timed out, please try again";
|
|
36
|
+
if (error.message.includes("Failed to fetch")) return "Network error. Please check your connection.";
|
|
37
|
+
}
|
|
38
|
+
return GENERIC_LOAD_ERROR;
|
|
39
|
+
}
|
|
35
40
|
function isWarehouseStatusPayload(value) {
|
|
36
41
|
return typeof value === "object" && value !== null && typeof value.state === "string";
|
|
37
42
|
}
|
|
@@ -50,35 +55,95 @@ async function handleAnalyticsSseMessage(parsed, ctx) {
|
|
|
50
55
|
}
|
|
51
56
|
if (parsed.type === "result") {
|
|
52
57
|
ctx.setLoading(false);
|
|
53
|
-
ctx.setData(parsed.data);
|
|
58
|
+
ctx.setData(Array.isArray(parsed.data) ? parsed.data : []);
|
|
54
59
|
ctx.unpublishWarehouseStatus();
|
|
55
60
|
return;
|
|
56
61
|
}
|
|
57
|
-
if (parsed.type === "arrow") {
|
|
58
|
-
try {
|
|
59
|
-
const arrowData = await ArrowClient.fetchArrow(getArrowStreamUrl(parsed.statement_id));
|
|
60
|
-
const table = await ArrowClient.processArrowBuffer(arrowData);
|
|
61
|
-
ctx.setLoading(false);
|
|
62
|
-
ctx.setData(table);
|
|
63
|
-
ctx.unpublishWarehouseStatus();
|
|
64
|
-
} catch (error) {
|
|
65
|
-
console.error("[useAnalyticsQuery] Failed to fetch Arrow data", error);
|
|
66
|
-
ctx.setLoading(false);
|
|
67
|
-
ctx.setError(GENERIC_LOAD_ERROR);
|
|
68
|
-
ctx.unpublishWarehouseStatus();
|
|
69
|
-
}
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
62
|
if (parsed.type === "error" || parsed.error || parsed.code) {
|
|
73
63
|
const errorMsg = parsed.error || parsed.message || "Unable to execute query";
|
|
74
64
|
ctx.setLoading(false);
|
|
75
65
|
ctx.setError(errorMsg);
|
|
76
66
|
ctx.unpublishWarehouseStatus();
|
|
67
|
+
if (typeof parsed.errorCode === "string") ctx.setErrorCode(parsed.errorCode);
|
|
77
68
|
if (parsed.code) console.error(`[useAnalyticsQuery] Code: ${parsed.code}, Message: ${errorMsg}`);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
console.error("[useAnalyticsQuery] Unrecognized SSE payload", parsed);
|
|
72
|
+
ctx.setLoading(false);
|
|
73
|
+
ctx.setError(GENERIC_LOAD_ERROR);
|
|
74
|
+
ctx.unpublishWarehouseStatus();
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Fetch the real column names for a statement from the fallback endpoint,
|
|
78
|
+
* used when a very wide schema's names didn't fit in the response header.
|
|
79
|
+
* Returns undefined on any failure so decoding falls back to the raw Arrow
|
|
80
|
+
* schema names.
|
|
81
|
+
*/
|
|
82
|
+
async function fetchArrowColumns(statementId, signal) {
|
|
83
|
+
try {
|
|
84
|
+
const res = await fetch(`/api/analytics/columns/${encodeURIComponent(statementId)}`, { signal });
|
|
85
|
+
if (!res.ok) return void 0;
|
|
86
|
+
const body = await res.json();
|
|
87
|
+
return Array.isArray(body.columns) ? body.columns : void 0;
|
|
88
|
+
} catch {
|
|
89
|
+
return;
|
|
78
90
|
}
|
|
79
91
|
}
|
|
80
92
|
/**
|
|
81
|
-
*
|
|
93
|
+
* Fetch an ARROW_STREAM query result as raw Arrow IPC bytes directly from
|
|
94
|
+
* the query endpoint (no SSE, no second /arrow-result request) and decode
|
|
95
|
+
* it into a Table. The server streams the bytes back as the POST response
|
|
96
|
+
* body; errors before the first byte arrive as a JSON `{ error, errorCode }`.
|
|
97
|
+
*/
|
|
98
|
+
async function fetchArrowDirect(ctx) {
|
|
99
|
+
try {
|
|
100
|
+
const response = await fetch(ctx.url, {
|
|
101
|
+
method: "POST",
|
|
102
|
+
headers: { "Content-Type": "application/json" },
|
|
103
|
+
body: ctx.payload,
|
|
104
|
+
signal: ctx.signal
|
|
105
|
+
});
|
|
106
|
+
if (ctx.signal.aborted) return;
|
|
107
|
+
if (!response.ok) {
|
|
108
|
+
let message = GENERIC_LOAD_ERROR;
|
|
109
|
+
let code = null;
|
|
110
|
+
try {
|
|
111
|
+
const body = await response.json();
|
|
112
|
+
if (body.error) message = body.error;
|
|
113
|
+
if (typeof body.errorCode === "string") code = body.errorCode;
|
|
114
|
+
} catch {}
|
|
115
|
+
ctx.setLoading(false);
|
|
116
|
+
ctx.setError(message);
|
|
117
|
+
if (code) ctx.setErrorCode(code);
|
|
118
|
+
ctx.unpublishWarehouseStatus();
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
const buffer = await response.arrayBuffer();
|
|
122
|
+
if (ctx.signal.aborted) return;
|
|
123
|
+
let columnNames;
|
|
124
|
+
const header = response.headers.get("X-Appkit-Arrow-Columns");
|
|
125
|
+
if (header) try {
|
|
126
|
+
columnNames = JSON.parse(decodeURIComponent(header));
|
|
127
|
+
} catch {}
|
|
128
|
+
else {
|
|
129
|
+
const ref = response.headers.get("X-Appkit-Arrow-Columns-Ref");
|
|
130
|
+
if (ref) columnNames = await fetchArrowColumns(ref, ctx.signal);
|
|
131
|
+
}
|
|
132
|
+
const table = await ArrowClient.processArrowBuffer(new Uint8Array(buffer), columnNames);
|
|
133
|
+
ctx.setData(table);
|
|
134
|
+
ctx.setLoading(false);
|
|
135
|
+
ctx.unpublishWarehouseStatus();
|
|
136
|
+
} catch (error) {
|
|
137
|
+
if (ctx.signal.aborted) return;
|
|
138
|
+
ctx.setLoading(false);
|
|
139
|
+
ctx.unpublishWarehouseStatus();
|
|
140
|
+
ctx.setError(userFacingFetchError(error));
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Subscribe to an analytics query and return its latest result. JSON_ARRAY
|
|
145
|
+
* results stream over SSE (with warehouse-readiness progress); ARROW_STREAM
|
|
146
|
+
* results are fetched as raw Arrow bytes directly from the query endpoint.
|
|
82
147
|
* Integration hook between client and analytics plugin.
|
|
83
148
|
*
|
|
84
149
|
* The return type is automatically inferred based on the format:
|
|
@@ -94,13 +159,13 @@ async function handleAnalyticsSseMessage(parsed, ctx) {
|
|
|
94
159
|
* @param options - Analytics query settings including format
|
|
95
160
|
* @returns Query result state with format-appropriate data type
|
|
96
161
|
*
|
|
97
|
-
* @example
|
|
162
|
+
* @example JSON_ARRAY format (default)
|
|
98
163
|
* ```typescript
|
|
99
164
|
* const { data } = useAnalyticsQuery("spend_data", params);
|
|
100
165
|
* // data: Array<{ group_key: string; cost_usd: number; ... }> | null
|
|
101
166
|
* ```
|
|
102
167
|
*
|
|
103
|
-
* @example
|
|
168
|
+
* @example ARROW_STREAM format
|
|
104
169
|
* ```typescript
|
|
105
170
|
* const { data } = useAnalyticsQuery("spend_data", params, { format: "ARROW_STREAM" });
|
|
106
171
|
* // data: TypedArrowTable<{ group_key: string; cost_usd: number; ... }> | null
|
|
@@ -115,6 +180,7 @@ function useAnalyticsQuery(queryKey, parameters, options = {}) {
|
|
|
115
180
|
const [data, setData] = useState(null);
|
|
116
181
|
const [loading, setLoading] = useState(false);
|
|
117
182
|
const [error, setError] = useState(null);
|
|
183
|
+
const [errorCode, setErrorCode] = useState(null);
|
|
118
184
|
const [warehouseStatus, setWarehouseStatus] = useState(null);
|
|
119
185
|
const abortControllerRef = useRef(null);
|
|
120
186
|
const { publish: publishWarehouseStatus, unpublish: unpublishWarehouseStatus } = useAnalyticsWarehousePublisher(useId(), queryKey);
|
|
@@ -145,14 +211,29 @@ function useAnalyticsQuery(queryKey, parameters, options = {}) {
|
|
|
145
211
|
abortControllerRef.current?.abort();
|
|
146
212
|
setLoading(true);
|
|
147
213
|
setError(null);
|
|
214
|
+
setErrorCode(null);
|
|
148
215
|
setData(null);
|
|
149
216
|
setWarehouseStatus(null);
|
|
150
217
|
publishWarehouseStatus(null);
|
|
151
218
|
const abortController = new AbortController();
|
|
152
219
|
abortControllerRef.current = abortController;
|
|
220
|
+
if (format === "ARROW_STREAM") {
|
|
221
|
+
fetchArrowDirect({
|
|
222
|
+
url: urlSuffix,
|
|
223
|
+
payload,
|
|
224
|
+
signal: abortController.signal,
|
|
225
|
+
setLoading,
|
|
226
|
+
setError,
|
|
227
|
+
setErrorCode,
|
|
228
|
+
setData: (table) => setData(table),
|
|
229
|
+
unpublishWarehouseStatus
|
|
230
|
+
});
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
153
233
|
const sseContext = {
|
|
154
234
|
setLoading,
|
|
155
235
|
setError,
|
|
236
|
+
setErrorCode,
|
|
156
237
|
setData,
|
|
157
238
|
setWarehouseStatus,
|
|
158
239
|
publishWarehouseStatus,
|
|
@@ -168,29 +249,28 @@ function useAnalyticsQuery(queryKey, parameters, options = {}) {
|
|
|
168
249
|
await handleAnalyticsSseMessage(JSON.parse(message.data), sseContext);
|
|
169
250
|
} catch (error) {
|
|
170
251
|
console.warn("[useAnalyticsQuery] Malformed message received", error);
|
|
252
|
+
setLoading(false);
|
|
253
|
+
setError(GENERIC_LOAD_ERROR);
|
|
254
|
+
abortController.abort();
|
|
171
255
|
}
|
|
172
256
|
},
|
|
173
257
|
onError: (error) => {
|
|
174
258
|
if (abortController.signal.aborted) return;
|
|
175
259
|
setLoading(false);
|
|
176
260
|
unpublishWarehouseStatus();
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
error: error.message,
|
|
184
|
-
stack: error.stack
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
setError(userMessage);
|
|
261
|
+
if (error instanceof Error) console.error("[useAnalyticsQuery] Error", {
|
|
262
|
+
queryKey,
|
|
263
|
+
error: error.message,
|
|
264
|
+
stack: error.stack
|
|
265
|
+
});
|
|
266
|
+
setError(userFacingFetchError(error));
|
|
188
267
|
}
|
|
189
268
|
});
|
|
190
269
|
}, [
|
|
191
270
|
queryKey,
|
|
192
271
|
payload,
|
|
193
272
|
urlSuffix,
|
|
273
|
+
format,
|
|
194
274
|
publishWarehouseStatus,
|
|
195
275
|
unpublishWarehouseStatus
|
|
196
276
|
]);
|
|
@@ -210,6 +290,7 @@ function useAnalyticsQuery(queryKey, parameters, options = {}) {
|
|
|
210
290
|
data,
|
|
211
291
|
loading,
|
|
212
292
|
error,
|
|
293
|
+
errorCode,
|
|
213
294
|
warehouseStatus
|
|
214
295
|
};
|
|
215
296
|
}
|