Rayfin

KQL databases

Run KQL queries and Kusto management commands against a Fabric KQL Database from a Rayfin app.

Changed in 1.36

Use this page only for projects that already declare a kusto connector. New Kusto connector authoring is held in Rayfin 1.36.2.

Run KQL against an existing Fabric KQL Database when your app needs operational or telemetry data owned by Fabric. The kusto connector is a Category B function-bridge connector: it exposes executeQuery for KQL, executeCommand for management commands, uses delegated authentication only, and is pinned to an adapter version (version: '1' today).

Warning

New Kusto connectors cannot be added with Rayfin 1.36.2. rayfin connector add --type kusto fails with Connector type "kusto" is not available in this release. Supported types: [fabric-sqlanalytics, fabric-warehouse, fabric-sqldatabase, fabric-semanticmodel].

Existing kusto entries remain in the catalog for validation and deployment. rayfin up rejects auth.type: application because Category B connectors are delegated-only.

Keep the generated schema

In projects created before 1.36, npx rayfin connector add --type kusto resolved the KQL Database routing values from the Fabric (workspaceId, itemId) pair and writes them into rayfin/connectors/<name>/schema.ts. The generated file is complete for this connector type: it exports the phantom marker used in AppConnectorsSchema and the Kusto-specific connectorConfig.

rayfin/connectors/telemetry/schema.ts
// @generated — do not edit.

import type { Kusto, KustoConnectorConfig } from '@microsoft/rayfin-connector-kusto';

export type TelemetrySchema = Kusto<'executeQuery' | 'executeCommand'>;

export const connectorConfig = {
  connector: 'kusto',
  queryServiceUri: 'https://<cluster>.kusto.fabric.microsoft.com',
  databaseName: '<database>',
} as const satisfies KustoConnectorConfig;

Do not hand-edit this file. If queryServiceUri or databaseName looks wrong, regenerate it only with a Rayfin version that still authors Kusto connectors.

In 1.36.2, removing the connector means you cannot add it back with connector add --type kusto:

Do not remove an existing Kusto connector on Rayfin 1.36.2 unless you are prepared to wait for a release that re-enables Kusto authoring or to use a Rayfin version that still supports `connector add --type kusto`.

queryServiceUri and databaseName live only in the generated schema.ts. They are not part of the rayfin/rayfin.yml schema, must not be written into rayfin.yml, and must not be sent from app code.

Register the runtime

Register kusto() in the connector runtime map before calling the connector. The runtime injects the generated queryServiceUri and databaseName after caller input, so a caller cannot override the cluster routing. Without the runtime map, nothing injects that routing and the connector cannot reach the cluster. See Wiring connectors into your app for the full client setup.

src/services/rayfinClient.ts
import { ConnectorsRayfinClient } from '@microsoft/rayfin-client';
import { kusto } from '@microsoft/rayfin-connector-kusto';
import type { AppSchema } from '../../rayfin/data/schema';
import type { TelemetrySchema } from '../../rayfin/connectors/telemetry/schema';
import { connectorConfig as telemetryConfig } from '../../rayfin/connectors/telemetry/schema';

type AppConnectorsSchema = {
  telemetry: TelemetrySchema;
};

export const client = new ConnectorsRayfinClient<
  AppSchema,
  Record<string, never>,
  AppConnectorsSchema
>(
  {
    baseUrl: import.meta.env.VITE_RAYFIN_API_URL,
    publishableKey: import.meta.env.VITE_RAYFIN_PUBLISHABLE_KEY,
    connectors: {
      telemetry: telemetryConfig,
    },
  },
  {
    telemetry: kusto(),
  }
);

The key must match the connector name in rayfin.yml, the AppConnectorsSchema property, the connectors option, and the runtime map.

Generate a client request id

Kusto correlation travels outside the response body. Generate a clientRequestId, pass it to the operation, and pass the same value to toQueryResult. The connector forwards it as the x-ms-client-request-id header. If you omit it, the runtime generates one for the Kusto request, but your caller cannot correlate the normalized result because the native response body does not carry the id.

const clientRequestId = `KPC.rayfin_kusto_v1;${crypto.randomUUID()}`;

Normalized results echo the clientRequestId you give to toQueryResult and may carry an activityId when one is available.

Pass KQL safely

Treat KQL as source code. Do not concatenate untrusted text directly into a query or management command.

Prefer fixed query templates with validated values:

  • Map UI choices to known table names, column names, and time windows instead of accepting those names as free text.
  • Escape string values before placing them in KQL string literals, or keep the value set closed by choosing from IDs your app controls.
  • Use executeCommand only for known management commands that your app explicitly exposes. Do not pass arbitrary dot commands from a text box.
  • For CLI probes, prefer --file with a JSON payload so shell quoting does not change the KQL before Rayfin reads it.
rayfin/queries/recent-errors.json
{
  "query": "AppEvents | where Severity == \"Error\" | take 10"
}
npx rayfin connector invoke telemetry executeQuery --file rayfin\queries\recent-errors.json

Call executeQuery

executeQuery accepts a KQL query and resolves to the native Kusto v1 { Tables } document. The connector function is a byte pump: it relays the Kusto response untouched, so the caller must normalize it with toQueryResult(response, { clientRequestId }).

src/features/telemetry/loadErrorsByState.ts
import { toQueryResult } from '@microsoft/rayfin-connector-kusto';
import { client } from '../../services/rayfinClient';

export async function loadErrorsByState() {
  const clientRequestId = `KPC.rayfin_kusto_v1;${crypto.randomUUID()}`;
  const response = await client.connectors.telemetry.executeQuery({
    query: `
AppEvents
| where Severity == "Error"
| summarize ErrorCount = count() by State
| top 10 by ErrorCount desc
`,
    clientRequestId,
  });

  const result = toQueryResult(response, { clientRequestId });

  if (result.status === 'error') {
    throw new Error(
      `${result.error.message} Client request id: ${result.clientRequestId}`
    );
  }

  return result.tables.flatMap((table) =>
    table.rows.map((row) => {
      const record = Object.fromEntries(
        table.columns.map((column, index) => [column.name, row[index]])
      );

      return {
        state: String(record.State ?? ''),
        errorCount: Number(record.ErrorCount ?? 0),
        tableName: table.name,
        clientRequestId: result.clientRequestId,
      };
    })
  );
}

Call executeCommand

executeCommand runs a Kusto management command. The command text starts with a leading dot and routes to the management endpoint. It returns the same native Kusto v1 { Tables } document as executeQuery, so normalize it the same way.

src/features/telemetry/showDatabases.ts
import { toQueryResult } from '@microsoft/rayfin-connector-kusto';
import { client } from '../../services/rayfinClient';

export async function showDatabases() {
  const clientRequestId = `KPC.rayfin_kusto_v1;${crypto.randomUUID()}`;
  const response = await client.connectors.telemetry.executeCommand({
    command: '.show databases',
    clientRequestId,
  });

  const result = toQueryResult(response, { clientRequestId });
  if (result.status === 'error') {
    throw new Error(result.error.message);
  }

  return result.tables;
}

Use executeCommand only for Kusto management commands. Use executeQuery for KQL query text.

Normalize the Kusto table shape

KustoOperationCatalog.executeQuery is typed as OperationDef<ExecuteQueryInput, KustoQueryResponse>, so the operation returns the native wire document. toQueryResult converts that document into a discriminated union:

type KustoQueryResult =
  | {
      status: 'success';
      tables: KustoTable[];
      clientRequestId: string;
      activityId?: string;
    }
  | {
      status: 'error';
      error: {
        message: string;
        code?: string;
      };
      clientRequestId: string;
      activityId?: string;
    };

type KustoTable = {
  name: string;
  columns: { name: string; type: string }[];
  rows: unknown[][];
};

Rows are row-major arrays aligned with columns. A query can return more than one table, so render or inspect every entry in result.tables.

src/features/telemetry/KustoTables.tsx
import type { KustoQueryResult } from '@microsoft/rayfin-connector-kusto';

export function KustoTables({ result }: { result: KustoQueryResult }) {
  if (result.status === 'error') {
    return <p>{result.error.message}</p>;
  }

  return (
    <>
      {result.tables.map((table) => (
        <section key={table.name}>
          <h2>{table.name}</h2>
          <table>
            <thead>
              <tr>
                {table.columns.map((column) => (
                  <th key={column.name}>{column.name}</th>
                ))}
              </tr>
            </thead>
            <tbody>
              {table.rows.map((row, rowIndex) => (
                <tr key={rowIndex}>
                  {table.columns.map((column, columnIndex) => (
                    <td key={column.name}>{String(row[columnIndex] ?? '')}</td>
                  ))}
                </tr>
              ))}
            </tbody>
          </table>
        </section>
      ))}
    </>
  );
}

Exercise the connector from the CLI

connector inspect does not support kusto; it errors with Unsupported connector type: kusto. There is no ad-hoc query path for Kusto today, so connector invoke is the development loop.

npx rayfin up
npx rayfin connector invoke telemetry executeQuery --input '{"query":"AppEvents | take 10"}'
npx rayfin connector invoke telemetry executeCommand --input '{"command":".show databases"}'

Unlike fabric-semanticmodel, Kusto connector invoke POSTs to the deployed item, so a real query requires a deployed backend (rayfin up). A resolved invocation is not automatically a success: a connector returning the raw envelope can report status: 'Failed', and the CLI exits non-zero for that failure.

See CLI connector reference for payload rules and error handling.

Troubleshoot KQL database queries

SymptomLikely causeFix
rayfin up rejects auth.type: applicationCategory B connectors are delegated-only.Set auth.type: delegated.
executeQuery or executeCommand cannot reach the cluster.kusto() is missing from the runtime map, so routing was not injected.Register { telemetry: kusto() } as the client's second constructor argument.
A caller tries to pass queryServiceUri or databaseName.Those are connector-owned fields injected from generated config after caller input.Remove them from app code and re-add the connector if the generated values are wrong.
connector inspect reports Unsupported connector type: kusto.The inspect command has no Kusto path.Use npx rayfin connector invoke telemetry executeQuery --file ./query.json after deploying.
connector invoke reports No remote endpoint configured.Kusto invoke uses the deployed item transport.Run npx rayfin up, then invoke again.
toQueryResult returns clientRequestId: ''.The caller did not pass the generated id to toQueryResult.Reuse the same clientRequestId for the operation and normalization call.
client.connectors.telemetry is not typed.The connector key differs across rayfin.yml, AppConnectorsSchema, the connectors option, or the runtime map.Use the connector name from rayfin.yml in all four places.
PromptQuery a Fabric KQL Database from a Rayfin app
In my Rayfin project, use the existing KQL Database connector named telemetry and call it from the frontend. Do not run `rayfin connector add --type kusto` on Rayfin 1.36.2 because new Kusto authoring is held in this release. If the connector is not already declared in rayfin/rayfin.yml, stop and report that it cannot be added with 1.36.2. Do not put queryServiceUri or databaseName in rayfin/rayfin.yml or pass them from app code. Wire `TelemetrySchema` into `AppConnectorsSchema`, pass `connectorConfig` in the ConnectorsRayfinClient `connectors` option, and register `{ telemetry: kusto() }` in the runtime map. For a KQL query, create a client request id in the `KPC.rayfin_kusto_v1;<uuid>` format, pass it to `client.connectors.telemetry.executeQuery({ query, clientRequestId })`, then normalize with `toQueryResult(response, { clientRequestId })` and branch on `result.status`. Use `executeCommand` only for Kusto management commands whose text starts with a leading dot.
Something wrong on this page?Report an issueEdit this page

On this page