Skip to main content
Glama

Convex MCP server

Official
by get-convex
TableSchema.tsx8.14 kB
import { useQuery } from "convex/react"; import { useMemo } from "react"; import { Shape } from "shapes"; import udfs from "@common/udfs"; import { parseAndFilterToSingleTable } from "system-udfs/convex/_system/frontend/lib/filters"; import { SchemaJson } from "@common/lib/format"; import { useNents } from "@common/lib/useNents"; import { useTableShapes } from "@common/lib/deploymentApi"; import { LoadingTransition } from "@ui/Loading"; import { ShowSchema } from "@common/features/data/components/ShowSchema"; export interface TableSchemaStatus { tableName: string; isDefined: boolean; referencedByTable: string | undefined; isValidationRunning: boolean; } interface Schema { tables: Table[]; schemaValidation: boolean; } export interface Table { tableName: string; documentType: Validator | null; } type Validator = | { type: "null" } | { type: "number" } | { type: "bigint" } | { type: "boolean" } | { type: "string" } | { type: "boolean" } | { type: "bytes" } | { type: "any" } | { type: "literal"; value: any } | { type: "id"; tableName: string } | { type: "array"; value: Validator } | { type: "record"; keys: Validator; values: Validator } | { type: "union"; value: Validator[] } | { type: "object"; value: Record<string, { fieldType: Validator; optional: boolean }>; }; function validatorReferencesTable( validator: Validator, tableName: string, ): boolean { switch (validator.type) { case "id": return validator.tableName === tableName; case "array": return validatorReferencesTable(validator.value, tableName); case "record": return ( validatorReferencesTable(validator.keys, tableName) || validatorReferencesTable(validator.values, tableName) ); case "union": return validator.value.some((v) => validatorReferencesTable(v, tableName), ); case "object": return Object.values(validator.value).some((v) => validatorReferencesTable(v.fieldType, tableName), ); default: return false; } } export function useSingleTableSchemaStatus( tableName: string, ): TableSchemaStatus | undefined { const schemas = useQuery(udfs.getSchemas.default, { componentId: useNents().selectedNent?.id ?? null, }); if (!schemas) { return undefined; } const active: Schema | undefined = schemas.active ? JSON.parse(schemas.active) : undefined; const isDefined = active?.tables.find((table) => table.tableName === tableName) !== undefined; const referencedByTable = active?.tables.find((table) => validatorReferencesTable(table.documentType ?? { type: "any" }, tableName), )?.tableName; const isValidationRunning = schemas.inProgress !== undefined; return { tableName, isDefined, referencedByTable, isValidationRunning, }; } export function useSingleTableEnforcedSchema(tableName: string): Table | null { const schemas = useQuery(udfs.getSchemas.default, { componentId: useNents().selectedNent?.id ?? null, }); if (!schemas) { return null; } const active: Schema | undefined = schemas.active ? JSON.parse(schemas.active) : undefined; if (active?.schemaValidation === false) { return null; } const tableSchema = active?.tables.find( (table) => table.tableName === tableName, ); return tableSchema ?? null; } // Adds a comment '// Other tables here...' into code, assuming that code // represents a valid schema.ts file containing only one table whose name is // the given tableName. function insertOtherTablesComment(tableName: string, code: string): string { const splitLines = code.split("\n"); // Use a token to find the start of the table definition // assumes that the string 'tableName: defineTable' is unique in the file const tableNameToken = `${tableName}: defineTable`; const tableStartIndex = splitLines.findIndex((value) => value.trim().includes(tableNameToken), ); // Check for a single line table definition definition like: // export default defineSchema({ Test: defineTable({ my_id: s.string() }) }); const tableStartLine = splitLines[tableStartIndex]; const isSingleLineDefinition = tableStartLine .trim() .startsWith("export default defineSchema"); if (isSingleLineDefinition) { const tableStartIndexInLine = tableStartLine.indexOf(tableNameToken); const chars = tableStartLine.split(""); // -1 to remove the ' ' character chars.splice(tableStartIndexInLine - 1, 1, "\n "); // export default defineSchema({ // Test: defineTable({ my_id: s.string() }) }); // -1 to remove the ' ' character chars.splice(chars.length - 4, 1, "\n"); // export default defineSchema({ // Test: defineTable({ my_id: s.string() }) // }); const linesToAdd = chars.join("").split("\n"); splitLines.splice(tableStartIndex, 1, ...linesToAdd); } splitLines.splice( tableStartIndex + (isSingleLineDefinition ? 1 : 0), 0, " // Other tables here...\n", ); // export default defineSchema({ // // Other tables here... // // Test: defineTable({ my_id: s.string() }) // }); return splitLines.join("\n"); } function TableSchema({ tables, tableName, activeSchema, inProgressSchema, hadShapeError, }: { tables: Map<string, Shape>; tableName: string; activeSchema?: SchemaJson; inProgressSchema?: SchemaJson; hadShapeError: boolean; }) { const tableShape = tables.get(tableName); if ((!tableShape || tableShape.type === "Never") && !activeSchema) { return ( <div className="sm:px-2"> Add at least one document to your table to see a suggested schema here. </div> ); } const singleTableShapes = new Map(); singleTableShapes.set(tableName, tableShape); const lineHighlighter = (code: string) => { const splitLines = code.split("\n"); const tableStartIndex = splitLines.findIndex((value) => value.trim().startsWith(tableName)) + 1; return { startLineNumber: tableStartIndex, endLineNumber: splitLines.length - 1, }; }; const codeTransformation = (code: string) => insertOtherTablesComment(tableName, code); return ( <ShowSchema activeSchema={activeSchema} inProgressSchema={inProgressSchema} shapes={singleTableShapes} hasShapeError={hadShapeError} showLearnMoreLink={false} lineHighlighter={lineHighlighter} codeTransformation={codeTransformation} /> ); } export function TableSchemaContainer({ tableName }: { tableName: string }) { const schemas = useQuery(udfs.getSchemas.default, { componentId: useNents().selectedNent?.id ?? null, }); const { activeSchema, inProgressSchema } = useMemo(() => { if (!schemas) return {}; return { activeSchema: parseAndFilterToSingleTable(tableName, schemas.active), inProgressSchema: parseAndFilterToSingleTable( tableName, schemas.inProgress, ), }; }, [tableName, schemas]); const { tables, hadError: hadShapeError } = useTableShapes(); return ( <LoadingTransition> {tables && schemas && ( <TableSchema tables={tables} tableName={tableName} activeSchema={activeSchema} inProgressSchema={inProgressSchema} hadShapeError={hadShapeError} /> )} </LoadingTransition> ); } export function topLevelFieldsForValidator(validator: Validator): { fields: Array<string>; areFieldsComplete: boolean; } { if (validator.type === "object") { return { fields: ["_id", "_creationTime", ...Object.keys(validator.value)], areFieldsComplete: true, }; } if (validator.type === "union") { const fields = new Set<string>(); let areFieldsComplete = true; validator.value.forEach((v) => { const result = topLevelFieldsForValidator(v); result.fields.forEach((f) => fields.add(f)); areFieldsComplete = areFieldsComplete && result.areFieldsComplete; }); return { fields: Array.from(fields), areFieldsComplete }; } return { fields: [], areFieldsComplete: false }; }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/get-convex/convex-backend'

If you have feedback or need assistance with the MCP directory API, please join our Discord server