Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ describe("useChartData", () => {
expect(mockUseAnalyticsQuery).toHaveBeenCalledWith(
"test",
undefined,
expect.objectContaining({ format: "JSON" }),
expect.objectContaining({ format: "JSON_ARRAY" }),
);
});

Expand All @@ -110,7 +110,7 @@ describe("useChartData", () => {
expect(mockUseAnalyticsQuery).toHaveBeenCalledWith(
"test",
undefined,
expect.objectContaining({ format: "ARROW" }),
expect.objectContaining({ format: "ARROW_STREAM" }),
);
});

Expand All @@ -132,7 +132,7 @@ describe("useChartData", () => {
expect(mockUseAnalyticsQuery).toHaveBeenCalledWith(
"test",
{ limit: 1000 },
expect.objectContaining({ format: "ARROW" }),
expect.objectContaining({ format: "ARROW_STREAM" }),
);
});

Expand All @@ -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" }),
);
});

Expand All @@ -179,7 +179,7 @@ describe("useChartData", () => {
expect(mockUseAnalyticsQuery).toHaveBeenCalledWith(
"test",
expect.anything(),
expect.objectContaining({ format: "JSON" }),
expect.objectContaining({ format: "JSON_ARRAY" }),
);
});

Expand All @@ -201,7 +201,7 @@ describe("useChartData", () => {
expect(mockUseAnalyticsQuery).toHaveBeenCalledWith(
"test",
expect.anything(),
expect.objectContaining({ format: "ARROW" }),
expect.objectContaining({ format: "ARROW_STREAM" }),
);
});

Expand All @@ -223,7 +223,7 @@ describe("useChartData", () => {
expect(mockUseAnalyticsQuery).toHaveBeenCalledWith(
"test",
{ limit: 100 },
expect.objectContaining({ format: "JSON" }),
expect.objectContaining({ format: "JSON_ARRAY" }),
);
});

Expand All @@ -243,7 +243,7 @@ describe("useChartData", () => {
expect(mockUseAnalyticsQuery).toHaveBeenCalledWith(
"test",
undefined,
expect.objectContaining({ format: "JSON" }),
expect.objectContaining({ format: "JSON_ARRAY" }),
);
});
});
Expand Down
32 changes: 23 additions & 9 deletions packages/appkit-ui/src/react/hooks/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,20 @@ import type { Table } from "apache-arrow";
// Data Format Types
// ============================================================================

/** Supported response formats for analytics queries */
export type AnalyticsFormat = "JSON" | "ARROW";
/**
* 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.
Expand All @@ -32,8 +44,10 @@ export interface TypedArrowTable<
// ============================================================================

/** Options for configuring an analytics SSE query */
export interface UseAnalyticsQueryOptions<F extends AnalyticsFormat = "JSON"> {
/** Response format - "JSON" returns typed arrays, "ARROW" returns TypedArrowTable */
export interface UseAnalyticsQueryOptions<
F extends AnalyticsFormat = "JSON_ARRAY",
> {
/** Response format - "JSON_ARRAY" returns typed arrays, "ARROW_STREAM" returns TypedArrowTable */
format?: F;

/** Maximum size of serialized parameters in bytes */
Expand Down Expand Up @@ -116,11 +130,11 @@ export type InferRowType<K> = K extends AugmentedRegistry<QueryRegistry>
* - 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" ? TypedArrowTable<InferRowType<K>> : InferResult<T, K>;
export type InferResultByFormat<T, K, F extends AnalyticsFormat> = F extends
| "ARROW_STREAM"
| "ARROW"
? TypedArrowTable<InferRowType<K>>
: InferResult<T, K>;

/**
* Infers parameters type from QueryRegistry[K]["parameters"]
Expand Down
10 changes: 5 additions & 5 deletions packages/appkit-ui/src/react/hooks/use-analytics-query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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<K> | null,
options: UseAnalyticsQueryOptions<F> = {} as UseAnalyticsQueryOptions<F>,
): UseAnalyticsQueryResult<InferResultByFormat<T, K, F>> {
const format = options?.format ?? "JSON";
const format = options?.format ?? "JSON_ARRAY";
const maxParametersSize = options?.maxParametersSize ?? 100 * 1024;
const autoStart = options?.autoStart ?? true;

Expand Down
20 changes: 10 additions & 10 deletions packages/appkit-ui/src/react/hooks/use-chart-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,32 +50,32 @@ export interface UseChartDataResult {
function resolveFormat(
format: DataFormat,
parameters?: Record<string, unknown>,
): "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";
}

// ============================================================================
Expand Down Expand Up @@ -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 {
Expand Down
7 changes: 5 additions & 2 deletions packages/appkit/src/plugins/analytics/analytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import type {
IAnalyticsConfig,
IAnalyticsQueryRequest,
} from "./types";
import { normalizeAnalyticsFormat } from "./types";

const logger = createLogger("analytics");

Expand Down Expand Up @@ -128,7 +129,9 @@ export class AnalyticsPlugin extends Plugin implements ToolProvider {
res: express.Response,
): Promise<void> {
const { query_key } = req.params;
const { parameters, format = "JSON" } = 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);
Expand Down Expand Up @@ -164,7 +167,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",
Expand Down
33 changes: 32 additions & 1 deletion packages/appkit/src/plugins/analytics/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,38 @@ export interface IAnalyticsConfig extends BasePluginConfig {
timeout?: number;
}

export type AnalyticsFormat = "JSON" | "ARROW";
/**
* 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. */
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<string, any>;
format?: AnalyticsFormat;
Expand Down
Loading