Skip to content

@askdb/client/charts API reference

Reference · @askdb/client/charts · Experimental

@askdb/client/charts plans a chart for a query result. The model sees the question, the SQL with its literals masked, and the result’s column names and kinds. It never sees a row.

import { classifyResult, planChart, validateChartSpec } from "@askdb/client/charts";

The entry point imports only @askdb/client’s peers, @askdb/core and ai. Plan a chart for a query result shows it end to end.

Classifies a result’s columns in your process. It makes no model call. Returns a ClassifiedResult.

FieldTypeDescription
columns{ name: string; type?: string }[]The result’s columns, in order. type is the database type, when your driver reports it.
rowCountnumberHow many rows you have.
truncatedbooleanTrue when the query had more rows than you returned. Default false.
rowsRecord<string, unknown>[] or unknown[][]Optional. Objects keyed by column name, or arrays in column order. Read to tell numbers from text and to find negative values, distinct counts and label lengths. Not kept in the result.
schemaAnyNormalizedSchemaOptional. A column takes the type of the one schema column with its name.

Each column gets one kind. The name decides first, then the database type, then the values.

KindRead from
identifierA name whose last word is id or uuid: customer_id, orderId. paid is not one.
yearA name with the word year, yr or fy.
dateA date or time database type; values that are all Date objects or ISO dates; a name with a word such as date, day, month, quarter or at.
percentA number whose name has pct, percent, rate, ratio or share.
currencyA number of type money, or whose name has a word such as amount, cost, revenue, total or price.
measureAny other number.
categoryAnything else, including a text column named like a number.

Identifiers and years are never series. measure, currency and percent are the kinds a series may have.

FieldDescription
shapeWhat the model sees: columns ({ name, kind }[]), rowCount and truncated.
localPer column: additive (false for averages, minimums, maximums and percents), and, when you passed rows, hasNegative, distinctCount and maxLabelLength. It stays in your process.

Asks the model for a chart and checks it. Returns Promise<ChartPlan>. It never rejects because of the model.

OptionTypeDescription
questionstringThe user’s question.
sqlstringThe query you ran.
resultClassifiedResultFrom classifyResult(). Only result.shape reaches the model.
modelLanguageModelAny AI SDK model: one you build, or one from @askdb/ai’s registry.
dialectAskDialectInputOptional. As ask() takes it: a built-in id, a DialectSpec or a custom dialect. Decides how literals are found.
schemaAnyNormalizedSchemaOptional. The schema artifact the SQL was generated against. With omitSensitiveIdentifiers, it says which names are sensitive.
omitSensitiveIdentifiersbooleanOptional. Keeps sensitive names out of the prompt, as ask()’s omitSensitiveIdentifiersFromNlToSqlPrompt does. Pass modes.omitSensitiveFromPrompt. Default false.
providerOptionsRecord<string, unknown>Optional. Passed to the model call unchanged, as in ask().
abortSignalAbortSignalOptional. Ends the model call; the plan comes from the fallback.
timeoutMsnumberOptional. How long to wait for the model, in milliseconds. Default 15000. Infinity waits for abortSignal only. A negative or non-number value uses the default; a fraction is rounded down.
  • The question.
  • The SQL, with every literal replaced by ?. Strings, numbers and bound tenant values become ?, and comments are dropped. Names, keywords and parameters such as $1 stay. The literals are found with the lexer the SQL checks use: under the dialect’s own readings for a built-in engine, and under every engine’s readings, with both string settings, for any other dialect or none. If a string or comment never closes, or a literal survives a second read, the model gets no SQL.
  • The shape: each column’s name and kind, the row count, and whether the result is truncated. A column name that holds a literal is shown as column_<n>, its position in the result. SQLite and MySQL name an unaliased expression by its text, so COUNT(CASE WHEN status = 'refunded' THEN 1 END) would carry 'refunded'. planChart() maps a label in the model’s answer back to the real name.
  • Under omission (omitSensitiveIdentifiers with schema): no SQL when it mentions a sensitive column, by the rule the sensitive check uses, and a column_<n> label for a result column named like a sensitive column.

A fixed system prompt states the rubric and the rules. It holds no data.

The model returns a JSON object through the AI SDK’s structured output (generateText with Output.object): one primary chart spec and an alternate or null.

FieldTypeDescription
primaryChartSpec | nullThe chart to draw. Null when no column holds a number to plot, or when a result of several rows has no column to group them by.
alternateChartSpecOptional. A second chart. An alternate that fails the checks is dropped.
source"model" | "fallback"Who picked the chart.
fallbackReason"timeout" | "aborted" | "model-error" | "invalid-spec"Set when source is "fallback".
issuesChartSpecIssue[]Set when the model’s primary chart failed the checks.
latencyMsnumberHow long planning took.
FieldTypeDescription
type"metric" | "line" | "area" | "bar" | "horizontal-bar" | "pie"The chart type.
categorystringThe column along the axis, or the slices. A metric has none.
seriesstring[]The columns to plot as numbers.
titlestringA factual title.
descriptionstringOne sentence on what is plotted.

The model and the fallback pick the type by these rules, in order:

  1. One row with one headline number: metric.
  2. A measure over ordered time: line. The model picks area only when the filled volume is the point.
  3. Part of a whole across 2–8 categories: pie, when the part-to-whole policy allows it.
  4. A ranking (top, bottom, highest, lowest…): horizontal-bar.
  5. A few short categories: bar, or horizontal-bar when the labels are long.

The model judges long labels from the column names and the question; it never sees a label. The fallback switches to horizontal-bar when a label is longer than 16 characters, which it knows only when you passed rows to classifyResult(). The fallback reads a part-of-whole question from words such as “share”, “breakdown” and “proportion”, and a ranking from words such as “top” and “highest”. It plots at most 4 series. It never picks area.

Checks a chart spec against a classified result. planChart() runs it on the model’s charts; call it yourself for a spec you stored or edited. Pass context.question to refuse a pie for a ranking. Returns { ok: true } or { ok: false, issues }.

Issue codeWhen
UNKNOWN_COLUMNThe spec names a column the result doesn’t have.
SERIES_NOT_MEASUREA series isn’t a measure, currency or percent column, or there is no series.
INVALID_CATEGORYA chart other than a metric has no category, or its category is a measure.
METRIC_NOT_SCALARA metric for more than one row, or with more than one series.
PIE_NOT_PART_TO_WHOLEA pie that fails the part-to-whole policy.

A pie shows one additive total or count across 2–8 categories. The check refuses a pie for:

  • more than one series;
  • an average, minimum, maximum, rate, ratio or percent;
  • negative values (known only when you passed rows);
  • a date or year category;
  • fewer than 2 or more than 8 rows, or a category that repeats across rows;
  • a truncated result;
  • a ranking question.

Every type the entry point exports:

TypeWhat it is
ClassifyResultInputclassifyResult()’s input.
ResultColumnInputOne input column: { name, type? }.
ColumnKind"identifier" | "year" | "date" | "currency" | "percent" | "category" | "measure".
ClassifiedResultclassifyResult()’s result.
ShapeColumnOne column in shape: { name, kind }.
LocalColumnFactsOne column in local.
PlanChartOptionsplanChart()’s options.
ChartPlanplanChart()’s result.
ChartFallbackReason"timeout" | "aborted" | "model-error" | "invalid-spec".
ChartSpecA chart spec.
ChartTypeChartSpec["type"].
ValidateChartSpecResultvalidateChartSpec()’s result.
ChartSpecIssueOne issue: { code, column?, message }.
ChartSpecIssueCodeThe issue codes in the table above.