GridCue
API reference

Schema

defineSchema, schemaFromTanStack and describeProviderPayload, and the column kinds, capabilities and operators they produce.

A ViewSchema tells GridCue which columns exist, what kind of data each holds, what the User may do with each, and which names the Intent Provider may see. You rarely write one by hand: build it with defineSchema from the columns your table already has, or with schemaFromTanStack from a TanStack Table.

defineSchema

import { defineSchema } from "gridcue";

function defineSchema(columns: readonly ColumnInput[], options?: SchemaOptions): ViewSchema;

interface ColumnInput {
  id: string;
  label?: string;
  kind?: ColumnKind;
}

Builds a ViewSchema from a list of columns and per-column additions. For each column:

  • label is the override's label, else the input's label, else the ID made readable (market_value and marketValue both become "Market value").
  • kind is the override's kind, else the input's kind, else enum when the override lists enumValues, else inferred from sampleRows. See Column kinds.
  • capabilities is the override's capabilities, else the defaults. A restricted column gets none.
  • sensitivity is restricted for columns listed in restricted, internal for every other column. A restricted column is also marked exposeToProvider: false.

The result is checked against the ViewSchema shape before it is returned.

const schema = defineSchema(
  [{ id: "account_number" }, { id: "account_type" }, { id: "market_value" }, { id: "advisor" }, { id: "ssn" }],
  {
    id: "accounts",
    rowNoun: "account",
    restricted: ["ssn"],
    columns: {
      market_value: { kind: "currency", aliases: ["balance", "aum"] },
      account_type: {
        enumValues: [
          { id: "ira", label: "IRA" },
          { id: "roth_ira", label: "Roth IRA", aliases: ["roth"] },
          { id: "taxable", label: "Taxable" },
        ],
        valueGroups: [{ label: "Retirement", values: ["ira", "roth_ira"] }],
      },
    },
  },
);

SchemaOptions

interface SchemaOptions {
  id?: string;
  version?: string;
  columns?: Record<string, ColumnOverride>;
  restricted?: string[];
  rowNoun?: string;
  sampleRows?: ReadonlyArray<Record<string, unknown>>;
}
NameTypeDefaultDescription
idstring"default"The schema's ID.
versionstring"1"The schema's version.
columnsRecord<string, ColumnOverride>nonePer-column additions, keyed by column ID: aliases, descriptions, approved enum values, narrower capabilities.
restrictedstring[]noneColumn IDs GridCue must never expose to a provider or act on.
rowNounstringnoneWhat one row is, such as "account". Its name in a request means the rows, not a column.
sampleRowsReadonlyArray<Record<string, unknown>>noneRows used only to infer missing kinds. They never leave the caller.

ColumnOverride

interface ColumnOverride {
  label?: string;
  kind?: ColumnKind;
  description?: string;
  aliases?: string[];
  capabilities?: ColumnCapability[];
  allowedOperators?: FilterOperator[];
  enumValues?: EnumValue[];
  entity?: string;
  valueGroups?: Array<{ label: string; aliases?: string[]; values: string[] }>;
}
NameTypeDefaultDescription
labelstringinput label, else humanized IDThe name Users and the provider see.
kindColumnKindinput kind, else inferredThe column's data kind. Declare currency and percent: they can't be inferred.
descriptionstringnoneA short description sent to the provider.
aliasesstring[]noneOther names Users call this column, such as ["balance", "aum"].
capabilitiesColumnCapability[]DEFAULT_CAPABILITIESWhat GridCue may do with this column. Pass a narrower list to forbid, say, grouping.
allowedOperatorsFilterOperator[]the kind's operatorsNarrows the filter operators. It can only remove operators the kind allows, never add one.
enumValuesEnumValue[]noneThe approved values, each { id, label, aliases? }. Setting this without a kind makes the column an enum.
entitystringnoneThe other record this column names, such as "household", so "largest households first" can be read either as the column or as the records.
valueGroupsArray<{ label, aliases?, values }>noneHost-named categories over the enum values, such as { label: "Retirement", values: ["ira", "roth_ira"] }. Every value must be one of the column's enumValues IDs.

Column kinds

type ColumnKind = "string" | "number" | "currency" | "percent" | "date" | "datetime" | "boolean" | "enum";

Each kind has a fixed set of filter operators. allowedOperators can narrow it.

KindOperators
stringeq, neq, contains, startsWith, in, isEmpty, isNotEmpty
numbereq, neq, gt, gte, lt, lte, between, isEmpty, isNotEmpty
currencyeq, neq, gt, gte, lt, lte, between, isEmpty, isNotEmpty
percenteq, neq, gt, gte, lt, lte, between, isEmpty, isNotEmpty
dateeq, gt, gte, lt, lte, between, isEmpty, isNotEmpty
datetimeeq, gt, gte, lt, lte, between, isEmpty, isNotEmpty
booleaneq
enumeq, neq, in, isEmpty, isNotEmpty

operatorsFor(column) returns the operators legal for a ColumnDescriptor: its kind's operators, narrowed by its allowedOperators.

Kind inference

When no kind is given and the column has no enumValues, inferKind reads the column's values in sampleRows, ignoring null, undefined and "":

All present values areKind
booleansboolean
finite numbersnumber
Date objects or strings starting YYYY-MM-DDTdatetime
strings of the form YYYY-MM-DDdate
anything else, or no values at allstring

A money or percentage column infers as number. Declare it as currency or percent so amounts like "$1M" and "10%" reach it.

Column capabilities

type ColumnCapability = "filter" | "sort" | "group" | "aggregate" | "show" | "hide" | "reorder" | "pin";

const DEFAULT_CAPABILITIES: ColumnCapability[] = ["filter", "sort", "group", "show", "hide", "reorder"];

Every column that is not restricted gets DEFAULT_CAPABILITIES unless its override says otherwise. aggregate and pin exist in the protocol but are not defaults, and no first-release adapter supports the operations that use them.

schemaFromTanStack

import { schemaFromTanStack } from "gridcue/tanstack-table";

function schemaFromTanStack(table: TanStackTableLike, options?: Omit<SchemaOptions, "sampleRows">): ViewSchema;

Builds a schema from a TanStack Table v9 instance. It reads every leaf column, uses a column's columnDef.header as its label when the header is a string, and infers kinds from the first 50 rows of the table's core row model. It takes every SchemaOptions field except sampleRows, which it fills itself, and passes them to defineSchema.

const schema = schemaFromTanStack(table, {
  rowNoun: "account",
  restricted: ["ssn"],
  columns: { market_value: { kind: "currency" } },
});

describeProviderPayload

import { describeProviderPayload } from "gridcue";

function describeProviderPayload(schema: ViewSchema): {
  columns: ProviderPayloadColumn[];
  rowNoun?: string;
  rows: "never sent";
};

interface ProviderPayloadColumn {
  id: string;
  label: string;
  kind: ColumnKind;
  aliases?: string[];
  description?: string;
  enumValues?: EnumValue[];
  entity?: string;
  valueGroups?: Array<{ label: string; aliases?: string[]; values: string[] }>;
}

Returns exactly what an Intent Provider may receive about this schema, so you can review or log it. Restricted columns, and columns with exposeToProvider: false, are left out. Rows are never included; the rows field always reads "never sent".

console.log(describeProviderPayload(schema));

INPUT_SCHEMA errors

defineSchema (and so schemaFromTanStack) throws a GridCueError with code INPUT_SCHEMA when:

ProblemMessage
Two columns share an IDColumn ids must be unique. Repeated: …
restricted names an ID that matches no columnrestricted names an id that matches no column: …
columns has a key that matches no columncolumns names an id that matches no column: …
A valueGroups entry names a value that is not in the column's enumValuesvalueGroups on <id> name values that are not in its enumValues: …
The built schema fails the ViewSchema shape, such as an empty label or IDInvalid schema: <path>: <problem>
import { defineSchema, isGridCueError } from "gridcue";

try {
  defineSchema(columns, options);
} catch (error) {
  if (isGridCueError(error) && error.code === "INPUT_SCHEMA") console.error(error.message);
}

The full ViewSchema and ColumnDescriptor shapes are on the Protocol page.

On this page