From dd2bd651c5ce8f4d623beaece7d7688babe44259 Mon Sep 17 00:00:00 2001 From: James Broadhead Date: Wed, 29 Apr 2026 15:10:30 +0000 Subject: [PATCH 1/3] refactor: rename AnalyticsFormat values to API enum names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Renames the client-side analytics format model from "JSON"/"ARROW" to "JSON_ARRAY"/"ARROW_STREAM" to match the Statement Execution API enum verbatim — no more local-name to API-name translation. Pure mechanical rename. No behavior change. Internal type values only; the lowercase user-facing values passed to useChartData ("json", "arrow", "auto") are unchanged. Carved out of #256 (#327 is layer 1, this is layer 2). The actual inline-Arrow-IPC + warehouse-fallback fix sits on top of this in layer 3. Note: this is a breaking change for any direct consumer of useAnalyticsQuery passing explicit format: "JSON" or "ARROW" — they will need to update to "JSON_ARRAY" / "ARROW_STREAM". Consumers using useChartData (lowercase "json"/"arrow"/"auto") are unaffected. Co-authored-by: Isaac --- .../hooks/__tests__/use-chart-data.test.ts | 16 +++++++-------- packages/appkit-ui/src/react/hooks/types.ts | 8 ++++---- .../src/react/hooks/use-analytics-query.ts | 10 +++++----- .../src/react/hooks/use-chart-data.ts | 20 +++++++++---------- .../appkit/src/plugins/analytics/analytics.ts | 4 ++-- .../appkit/src/plugins/analytics/types.ts | 2 +- 6 files changed, 30 insertions(+), 30 deletions(-) diff --git a/packages/appkit-ui/src/react/hooks/__tests__/use-chart-data.test.ts b/packages/appkit-ui/src/react/hooks/__tests__/use-chart-data.test.ts index 3d5e96f11..a4d99a916 100644 --- a/packages/appkit-ui/src/react/hooks/__tests__/use-chart-data.test.ts +++ b/packages/appkit-ui/src/react/hooks/__tests__/use-chart-data.test.ts @@ -89,7 +89,7 @@ describe("useChartData", () => { expect(mockUseAnalyticsQuery).toHaveBeenCalledWith( "test", undefined, - expect.objectContaining({ format: "JSON" }), + expect.objectContaining({ format: "JSON_ARRAY" }), ); }); @@ -110,7 +110,7 @@ describe("useChartData", () => { expect(mockUseAnalyticsQuery).toHaveBeenCalledWith( "test", undefined, - expect.objectContaining({ format: "ARROW" }), + expect.objectContaining({ format: "ARROW_STREAM" }), ); }); @@ -132,7 +132,7 @@ describe("useChartData", () => { expect(mockUseAnalyticsQuery).toHaveBeenCalledWith( "test", { limit: 1000 }, - expect.objectContaining({ format: "ARROW" }), + expect.objectContaining({ format: "ARROW_STREAM" }), ); }); @@ -157,7 +157,7 @@ describe("useChartData", () => { expect(mockUseAnalyticsQuery).toHaveBeenCalledWith( "test", expect.objectContaining({ startDate: "2025-01-01" }), - expect.objectContaining({ format: "ARROW" }), + expect.objectContaining({ format: "ARROW_STREAM" }), ); }); @@ -179,7 +179,7 @@ describe("useChartData", () => { expect(mockUseAnalyticsQuery).toHaveBeenCalledWith( "test", expect.anything(), - expect.objectContaining({ format: "JSON" }), + expect.objectContaining({ format: "JSON_ARRAY" }), ); }); @@ -201,7 +201,7 @@ describe("useChartData", () => { expect(mockUseAnalyticsQuery).toHaveBeenCalledWith( "test", expect.anything(), - expect.objectContaining({ format: "ARROW" }), + expect.objectContaining({ format: "ARROW_STREAM" }), ); }); @@ -223,7 +223,7 @@ describe("useChartData", () => { expect(mockUseAnalyticsQuery).toHaveBeenCalledWith( "test", { limit: 100 }, - expect.objectContaining({ format: "JSON" }), + expect.objectContaining({ format: "JSON_ARRAY" }), ); }); @@ -243,7 +243,7 @@ describe("useChartData", () => { expect(mockUseAnalyticsQuery).toHaveBeenCalledWith( "test", undefined, - expect.objectContaining({ format: "JSON" }), + expect.objectContaining({ format: "JSON_ARRAY" }), ); }); }); diff --git a/packages/appkit-ui/src/react/hooks/types.ts b/packages/appkit-ui/src/react/hooks/types.ts index 03e943e2a..e5e178c90 100644 --- a/packages/appkit-ui/src/react/hooks/types.ts +++ b/packages/appkit-ui/src/react/hooks/types.ts @@ -5,7 +5,7 @@ import type { Table } from "apache-arrow"; // ============================================================================ /** Supported response formats for analytics queries */ -export type AnalyticsFormat = "JSON" | "ARROW"; +export type AnalyticsFormat = "JSON_ARRAY" | "ARROW_STREAM"; /** * Typed Arrow Table - preserves row type information for type inference. @@ -32,8 +32,8 @@ export interface TypedArrowTable< // ============================================================================ /** Options for configuring an analytics SSE query */ -export interface UseAnalyticsQueryOptions { - /** Response format - "JSON" returns typed arrays, "ARROW" returns TypedArrowTable */ +export interface UseAnalyticsQueryOptions { + /** Response format - "JSON_ARRAY" returns typed arrays, "ARROW_STREAM" returns TypedArrowTable */ format?: F; /** Maximum size of serialized parameters in bytes */ @@ -120,7 +120,7 @@ export type InferResultByFormat< T, K, F extends AnalyticsFormat, -> = F extends "ARROW" ? TypedArrowTable> : InferResult; +> = F extends "ARROW_STREAM" ? TypedArrowTable> : InferResult; /** * Infers parameters type from QueryRegistry[K]["parameters"] diff --git a/packages/appkit-ui/src/react/hooks/use-analytics-query.ts b/packages/appkit-ui/src/react/hooks/use-analytics-query.ts index 24e03ea3b..0bd0b2f02 100644 --- a/packages/appkit-ui/src/react/hooks/use-analytics-query.ts +++ b/packages/appkit-ui/src/react/hooks/use-analytics-query.ts @@ -27,8 +27,8 @@ function getArrowStreamUrl(id: string) { * Integration hook between client and analytics plugin. * * The return type is automatically inferred based on the format: - * - `format: "JSON"` (default): Returns typed array from QueryRegistry - * - `format: "ARROW"`: Returns TypedArrowTable with row type preserved + * - `format: "JSON_ARRAY"` (default): Returns typed array from QueryRegistry + * - `format: "ARROW_STREAM"`: Returns TypedArrowTable with row type preserved * * Note: User context execution is determined by query file naming: * - `queryKey.obo.sql`: Executes as user (OBO = on-behalf-of / user delegation) @@ -47,20 +47,20 @@ function getArrowStreamUrl(id: string) { * * @example Arrow format * ```typescript - * const { data } = useAnalyticsQuery("spend_data", params, { format: "ARROW" }); + * const { data } = useAnalyticsQuery("spend_data", params, { format: "ARROW_STREAM" }); * // data: TypedArrowTable<{ group_key: string; cost_usd: number; ... }> | null * ``` */ export function useAnalyticsQuery< T = unknown, K extends QueryKey = QueryKey, - F extends AnalyticsFormat = "JSON", + F extends AnalyticsFormat = "JSON_ARRAY", >( queryKey: K, parameters?: InferParams | null, options: UseAnalyticsQueryOptions = {} as UseAnalyticsQueryOptions, ): UseAnalyticsQueryResult> { - const format = options?.format ?? "JSON"; + const format = options?.format ?? "JSON_ARRAY"; const maxParametersSize = options?.maxParametersSize ?? 100 * 1024; const autoStart = options?.autoStart ?? true; diff --git a/packages/appkit-ui/src/react/hooks/use-chart-data.ts b/packages/appkit-ui/src/react/hooks/use-chart-data.ts index d8d0bd386..a90481a2e 100644 --- a/packages/appkit-ui/src/react/hooks/use-chart-data.ts +++ b/packages/appkit-ui/src/react/hooks/use-chart-data.ts @@ -50,32 +50,32 @@ export interface UseChartDataResult { function resolveFormat( format: DataFormat, parameters?: Record, -): "JSON" | "ARROW" { +): "JSON_ARRAY" | "ARROW_STREAM" { // Explicit format selection - if (format === "json") return "JSON"; - if (format === "arrow") return "ARROW"; + if (format === "json") return "JSON_ARRAY"; + if (format === "arrow") return "ARROW_STREAM"; // Auto-selection heuristics if (format === "auto") { // Check for explicit hint in parameters - if (parameters?._preferArrow === true) return "ARROW"; - if (parameters?._preferJson === true) return "JSON"; + if (parameters?._preferArrow === true) return "ARROW_STREAM"; + if (parameters?._preferJson === true) return "JSON_ARRAY"; // Check limit parameter as data size hint const limit = parameters?.limit; if (typeof limit === "number" && limit > ARROW_THRESHOLD) { - return "ARROW"; + return "ARROW_STREAM"; } // Check for date range queries (often large) if (parameters?.startDate && parameters?.endDate) { - return "ARROW"; + return "ARROW_STREAM"; } - return "JSON"; + return "JSON_ARRAY"; } - return "JSON"; + return "JSON_ARRAY"; } // ============================================================================ @@ -110,7 +110,7 @@ export function useChartData(options: UseChartDataOptions): UseChartDataResult { [format, parameters], ); - const isArrowFormat = resolvedFormat === "ARROW"; + const isArrowFormat = resolvedFormat === "ARROW_STREAM"; // Fetch data using the analytics query hook const { diff --git a/packages/appkit/src/plugins/analytics/analytics.ts b/packages/appkit/src/plugins/analytics/analytics.ts index fdcb16b43..564b4cfb4 100644 --- a/packages/appkit/src/plugins/analytics/analytics.ts +++ b/packages/appkit/src/plugins/analytics/analytics.ts @@ -128,7 +128,7 @@ export class AnalyticsPlugin extends Plugin implements ToolProvider { res: express.Response, ): Promise { const { query_key } = req.params; - const { parameters, format = "JSON" } = req.body as IAnalyticsQueryRequest; + const { parameters, format = "JSON_ARRAY" } = req.body as IAnalyticsQueryRequest; // Request-scoped logging with WideEvent tracking logger.debug(req, "Executing query: %s (format=%s)", query_key, format); @@ -164,7 +164,7 @@ export class AnalyticsPlugin extends Plugin implements ToolProvider { const executorKey = isAsUser ? this.resolveUserId(req) : "global"; const queryParameters = - format === "ARROW" + format === "ARROW_STREAM" ? { formatParameters: { disposition: "EXTERNAL_LINKS", diff --git a/packages/appkit/src/plugins/analytics/types.ts b/packages/appkit/src/plugins/analytics/types.ts index c58b6ecfe..c0e72fdba 100644 --- a/packages/appkit/src/plugins/analytics/types.ts +++ b/packages/appkit/src/plugins/analytics/types.ts @@ -4,7 +4,7 @@ export interface IAnalyticsConfig extends BasePluginConfig { timeout?: number; } -export type AnalyticsFormat = "JSON" | "ARROW"; +export type AnalyticsFormat = "JSON_ARRAY" | "ARROW_STREAM"; export interface IAnalyticsQueryRequest { parameters?: Record; format?: AnalyticsFormat; From 265a9ed68bf6b4112a1e7c9165edd5d1e78d0496 Mon Sep 17 00:00:00 2001 From: James Broadhead Date: Mon, 11 May 2026 16:11:14 +0000 Subject: [PATCH 2/3] refactor(analytics): accept legacy "JSON"/"ARROW" format aliases Widen AnalyticsFormat to also include the pre-rename "JSON" and "ARROW" spellings, both marked @deprecated with a JSDoc note describing the removal condition (no consumer on appkit/appkit-ui < 0.33.0). Add a normalizeAnalyticsFormat helper and call it at the analytics route handler entry point so all downstream code (cache key, format branching, formatParameters) continues to operate on the canonical "JSON_ARRAY" | "ARROW_STREAM" values. InferResultByFormat is widened to also match "ARROW" so callers passing the legacy spelling still get TypedArrowTable<...> inferred. This lifts the breaking-change carve-out from the rename, so callers of useAnalyticsQuery({ format: "JSON" | "ARROW" }) keep working with only an IDE deprecation hint. Signed-off-by: James Broadhead --- packages/appkit-ui/src/react/hooks/types.ts | 20 +++++++++-- .../appkit/src/plugins/analytics/analytics.ts | 5 ++- .../appkit/src/plugins/analytics/types.ts | 33 ++++++++++++++++++- 3 files changed, 53 insertions(+), 5 deletions(-) diff --git a/packages/appkit-ui/src/react/hooks/types.ts b/packages/appkit-ui/src/react/hooks/types.ts index e5e178c90..f775113f3 100644 --- a/packages/appkit-ui/src/react/hooks/types.ts +++ b/packages/appkit-ui/src/react/hooks/types.ts @@ -4,8 +4,20 @@ import type { Table } from "apache-arrow"; // Data Format Types // ============================================================================ -/** Supported response formats for analytics queries */ -export type AnalyticsFormat = "JSON_ARRAY" | "ARROW_STREAM"; +/** + * Supported response formats for analytics queries. + * + * "JSON" and "ARROW" are legacy aliases kept for backwards compatibility + * with appkit/appkit-ui < 0.33.0 — safe to remove once no consumer is on + * a pre-0.33.0 version. + */ +export type AnalyticsFormat = + | "JSON_ARRAY" + | "ARROW_STREAM" + /** @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"; /** * Typed Arrow Table - preserves row type information for type inference. @@ -120,7 +132,9 @@ export type InferResultByFormat< T, K, F extends AnalyticsFormat, -> = F extends "ARROW_STREAM" ? TypedArrowTable> : InferResult; +> = F extends "ARROW_STREAM" | "ARROW" + ? TypedArrowTable> + : InferResult; /** * Infers parameters type from QueryRegistry[K]["parameters"] diff --git a/packages/appkit/src/plugins/analytics/analytics.ts b/packages/appkit/src/plugins/analytics/analytics.ts index 564b4cfb4..bfee250fb 100644 --- a/packages/appkit/src/plugins/analytics/analytics.ts +++ b/packages/appkit/src/plugins/analytics/analytics.ts @@ -24,6 +24,7 @@ import type { PluginManifest } from "../../registry"; import { queryDefaults } from "./defaults"; import manifest from "./manifest.json"; import { QueryProcessor } from "./query"; +import { normalizeAnalyticsFormat } from "./types"; import type { AnalyticsQueryResponse, IAnalyticsConfig, @@ -128,7 +129,9 @@ export class AnalyticsPlugin extends Plugin implements ToolProvider { res: express.Response, ): Promise { const { query_key } = req.params; - const { parameters, format = "JSON_ARRAY" } = req.body as IAnalyticsQueryRequest; + const { parameters, format: rawFormat = "JSON_ARRAY" } = + req.body as IAnalyticsQueryRequest; + const format = normalizeAnalyticsFormat(rawFormat); // Request-scoped logging with WideEvent tracking logger.debug(req, "Executing query: %s (format=%s)", query_key, format); diff --git a/packages/appkit/src/plugins/analytics/types.ts b/packages/appkit/src/plugins/analytics/types.ts index c0e72fdba..2a84e32ec 100644 --- a/packages/appkit/src/plugins/analytics/types.ts +++ b/packages/appkit/src/plugins/analytics/types.ts @@ -4,7 +4,38 @@ export interface IAnalyticsConfig extends BasePluginConfig { timeout?: number; } -export type AnalyticsFormat = "JSON_ARRAY" | "ARROW_STREAM"; +/** + * Supported response formats for analytics queries. + * + * "JSON" and "ARROW" are legacy aliases kept for backwards compatibility + * with appkit/appkit-ui < 0.33.0 — safe to remove once no consumer is on + * a pre-0.33.0 version. The route handler normalizes them to their + * canonical equivalents before any downstream code reads the value. + */ +export type AnalyticsFormat = + | "JSON_ARRAY" + | "ARROW_STREAM" + /** @deprecated Use "JSON_ARRAY". Safe to remove once no consumer is on appkit < 0.33.0. */ + | "JSON" + /** @deprecated Use "ARROW_STREAM". Safe to remove once no consumer is on appkit < 0.33.0. */ + | "ARROW"; + +/** Canonical (post-normalization) analytics format values. */ +export type CanonicalAnalyticsFormat = "JSON_ARRAY" | "ARROW_STREAM"; + +/** + * Map a (possibly legacy) AnalyticsFormat to its canonical form. + * Legacy values come from appkit/appkit-ui < 0.33.0 and can be removed + * along with the deprecated aliases once no such consumer remains. + */ +export function normalizeAnalyticsFormat( + f: AnalyticsFormat, +): CanonicalAnalyticsFormat { + if (f === "JSON") return "JSON_ARRAY"; + if (f === "ARROW") return "ARROW_STREAM"; + return f; +} + export interface IAnalyticsQueryRequest { parameters?: Record; format?: AnalyticsFormat; From c6ef78aa00d02025bc284f505383c84dc49e578c Mon Sep 17 00:00:00 2001 From: James Broadhead Date: Tue, 12 May 2026 09:00:32 +0000 Subject: [PATCH 3/3] style: fix biome lint and unused-export errors blocking CI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI was failing on Lint & Type Check with two biome errors: - `packages/appkit-ui/src/react/hooks/types.ts` — formatter line-wrap for `UseAnalyticsQueryOptions` and `InferResultByFormat` - `packages/appkit/src/plugins/analytics/analytics.ts` — import order (type import group must precede value import) Both resolved by `biome check --write`. No behaviour change. Also drops the `export` keyword from `CanonicalAnalyticsFormat` in `analytics/types.ts`. The type is only consumed inside the file as the return type of `normalizeAnalyticsFormat`; nothing imports it. Pre-commit `knip` flagged it as an unused export, which was the actual blocker behind the lint-staged hook failure. Co-authored-by: Isaac Signed-off-by: James Broadhead --- packages/appkit-ui/src/react/hooks/types.ts | 12 ++++++------ packages/appkit/src/plugins/analytics/analytics.ts | 2 +- packages/appkit/src/plugins/analytics/types.ts | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/appkit-ui/src/react/hooks/types.ts b/packages/appkit-ui/src/react/hooks/types.ts index f775113f3..60fc5f63b 100644 --- a/packages/appkit-ui/src/react/hooks/types.ts +++ b/packages/appkit-ui/src/react/hooks/types.ts @@ -44,7 +44,9 @@ export interface TypedArrowTable< // ============================================================================ /** Options for configuring an analytics SSE query */ -export interface UseAnalyticsQueryOptions { +export interface UseAnalyticsQueryOptions< + F extends AnalyticsFormat = "JSON_ARRAY", +> { /** Response format - "JSON_ARRAY" returns typed arrays, "ARROW_STREAM" returns TypedArrowTable */ format?: F; @@ -128,11 +130,9 @@ export type InferRowType = K extends AugmentedRegistry * - JSON format: Returns the typed array from QueryRegistry * - ARROW format: Returns TypedArrowTable with row type preserved */ -export type InferResultByFormat< - T, - K, - F extends AnalyticsFormat, -> = F extends "ARROW_STREAM" | "ARROW" +export type InferResultByFormat = F extends + | "ARROW_STREAM" + | "ARROW" ? TypedArrowTable> : InferResult; diff --git a/packages/appkit/src/plugins/analytics/analytics.ts b/packages/appkit/src/plugins/analytics/analytics.ts index bfee250fb..75537b357 100644 --- a/packages/appkit/src/plugins/analytics/analytics.ts +++ b/packages/appkit/src/plugins/analytics/analytics.ts @@ -24,12 +24,12 @@ import type { PluginManifest } from "../../registry"; import { queryDefaults } from "./defaults"; import manifest from "./manifest.json"; import { QueryProcessor } from "./query"; -import { normalizeAnalyticsFormat } from "./types"; import type { AnalyticsQueryResponse, IAnalyticsConfig, IAnalyticsQueryRequest, } from "./types"; +import { normalizeAnalyticsFormat } from "./types"; const logger = createLogger("analytics"); diff --git a/packages/appkit/src/plugins/analytics/types.ts b/packages/appkit/src/plugins/analytics/types.ts index 2a84e32ec..b43dd18bc 100644 --- a/packages/appkit/src/plugins/analytics/types.ts +++ b/packages/appkit/src/plugins/analytics/types.ts @@ -21,7 +21,7 @@ export type AnalyticsFormat = | "ARROW"; /** Canonical (post-normalization) analytics format values. */ -export type CanonicalAnalyticsFormat = "JSON_ARRAY" | "ARROW_STREAM"; +type CanonicalAnalyticsFormat = "JSON_ARRAY" | "ARROW_STREAM"; /** * Map a (possibly legacy) AnalyticsFormat to its canonical form.