Skip to main content
Glama

Convex MCP server

Official
by get-convex
dataModel.ts11.5 kB
import { Context } from "../../bundler/context.js"; import { SystemIndexes } from "../../server/system_fields.js"; import { ComponentDirectory, toComponentDefinitionPath, } from "../lib/components/definition/directoryStructure.js"; import { AnalyzedSchema, TableDefinition, } from "../lib/deployApi/componentDefinition.js"; import { StartPushResponse } from "../lib/deployApi/startPush.js"; import { ConvexValidator } from "../lib/deployApi/validator.js"; import { header } from "./common.js"; import { validatorToType } from "./validator_helpers.js"; export function noSchemaDataModelDTS() { return ` ${header("Generated data model types.")} import { AnyDataModel } from "convex/server"; import type { GenericId } from "convex/values"; /** * No \`schema.ts\` file found! * * This generated code has permissive types like \`Doc = any\` because * Convex doesn't know your schema. If you'd like more type safety, see * https://docs.convex.dev/using/schemas for instructions on how to add a * schema file. * * After you change a schema, rerun codegen with \`npx convex dev\`. */ /** * The names of all of your Convex tables. */ export type TableNames = string; /** * The type of a document stored in Convex. */ export type Doc = any; /** * An identifier for a document in Convex. * * Convex documents are uniquely identified by their \`Id\`, which is accessible * on the \`_id\` field. To learn more, see [Document IDs](https://docs.convex.dev/using/document-ids). * * Documents can be loaded using \`db.get(id)\` in query and mutation functions. * * IDs are just strings at runtime, but this type can be used to distinguish them from other * strings when type checking. */ export type Id<TableName extends TableNames = TableNames> = GenericId<TableName>; /** * A type describing your Convex data model. * * This type includes information about what tables you have, the type of * documents stored in those tables, and the indexes defined on them. * * This type is used to parameterize methods like \`queryGeneric\` and * \`mutationGeneric\` to make them type-safe. */ export type DataModel = AnyDataModel;`; } export function dynamicDataModelDTS() { return ` ${header("Generated data model types.")} import type { DataModelFromSchemaDefinition, DocumentByName, TableNamesInDataModel, SystemTableNames } from "convex/server"; import type { GenericId } from "convex/values"; import schema from "../schema.js"; /** * The names of all of your Convex tables. */ export type TableNames = TableNamesInDataModel<DataModel>; /** * The type of a document stored in Convex. * * @typeParam TableName - A string literal type of the table name (like "users"). */ export type Doc<TableName extends TableNames> = DocumentByName<DataModel, TableName>; /** * An identifier for a document in Convex. * * Convex documents are uniquely identified by their \`Id\`, which is accessible * on the \`_id\` field. To learn more, see [Document IDs](https://docs.convex.dev/using/document-ids). * * Documents can be loaded using \`db.get(id)\` in query and mutation functions. * * IDs are just strings at runtime, but this type can be used to distinguish them from other * strings when type checking. * * @typeParam TableName - A string literal type of the table name (like "users"). */ export type Id<TableName extends TableNames | SystemTableNames> = GenericId<TableName>; /** * A type describing your Convex data model. * * This type includes information about what tables you have, the type of * documents stored in those tables, and the indexes defined on them. * * This type is used to parameterize methods like \`queryGeneric\` and * \`mutationGeneric\` to make them type-safe. */ export type DataModel = DataModelFromSchemaDefinition<typeof schema>; `; } export async function staticDataModelDTS( ctx: Context, startPush: StartPushResponse, rootComponent: ComponentDirectory, componentDirectory: ComponentDirectory, ) { const definitionPath = toComponentDefinitionPath( rootComponent, componentDirectory, ); const analysis = startPush.analysis[definitionPath]; if (!analysis) { return await ctx.crash({ exitCode: 1, errorType: "fatal", printedMessage: `No analysis found for component ${definitionPath} orig: ${definitionPath}\nin\n${Object.keys(startPush.analysis).toString()}`, }); } if (!analysis.schema) { return noSchemaDataModelDTS(); } const lines = [ header("Generated data model types."), `import type { DocumentByName, TableNamesInDataModel, SystemTableNames, AnyDataModel } from "convex/server";`, `import type { GenericId } from "convex/values";`, ]; for await (const line of codegenDataModel(ctx, analysis.schema)) { lines.push(line); } lines.push(` /** * The names of all of your Convex tables. */ export type TableNames = TableNamesInDataModel<DataModel>; /** * The type of a document stored in Convex. * * @typeParam TableName - A string literal type of the table name (like "users"). */ export type Doc<TableName extends TableNames> = DocumentByName<DataModel, TableName>; /** * An identifier for a document in Convex. * * Convex documents are uniquely identified by their \`Id\`, which is accessible * on the \`_id\` field. To learn more, see [Document IDs](https://docs.convex.dev/using/document-ids). * * Documents can be loaded using \`db.get(id)\` in query and mutation functions. * * IDs are just strings at runtime, but this type can be used to distinguish them from other * strings when type checking. * * @typeParam TableName - A string literal type of the table name (like "users"). */ export type Id<TableName extends TableNames | SystemTableNames> = GenericId<TableName>; `); return lines.join("\n"); } async function* codegenDataModel(ctx: Context, schema: AnalyzedSchema) { yield ` /** * A type describing your Convex data model. * * This type includes information about what tables you have, the type of * documents stored in those tables, and the indexes defined on them. * * This type is used to parameterize methods like \`queryGeneric\` and * \`mutationGeneric\` to make them type-safe. */ `; const tables = [...schema.tables]; tables.sort((a, b) => a.tableName.localeCompare(b.tableName)); yield `export type DataModel = {`; for (const table of tables) { yield ` ${table.tableName}:`; yield* codegenTable(ctx, table); yield `,`; } yield `}`; if (!schema.schemaValidation) { yield ` & AnyDataModel`; } yield `;`; } async function* codegenTable(ctx: Context, table: TableDefinition) { const documentType = await addSystemFields( ctx, table.tableName, table.documentType, ); const indexJson: Record<string, string[]> = {}; for (const index of table.indexes) { indexJson[index.indexDescriptor] = index.fields; } yield `{`; yield ` document: ${validatorToType(documentType, true)},`; const fieldPaths = new Set<string>(); for (const fieldPath of extractFieldPaths(documentType)) { fieldPaths.add(fieldPath.join(".")); } yield ` fieldPaths: ${stringLiteralUnionType(Array.from(fieldPaths).sort())},`; yield ` indexes: {`; const systemIndexes: SystemIndexes = { by_id: ["_id"], by_creation_time: ["_creationTime"], }; const indexes: Record<string, string[]> = {}; for (const [indexDescriptor, fields] of Object.entries(systemIndexes)) { indexes[indexDescriptor] = fields; } for (const index of table.indexes) { if (indexes[index.indexDescriptor]) { yield await ctx.crash({ exitCode: 1, errorType: "fatal", printedMessage: `Duplicate index name ${index.indexDescriptor} in table ${table.tableName}.`, }); } indexes[index.indexDescriptor] = index.fields; } for (const [indexDescriptor, fields] of Object.entries(indexes)) { yield ` "${indexDescriptor}": ${JSON.stringify(fields)},`; } yield ` },`; yield ` searchIndexes: {`; for (const index of table.searchIndexes ?? []) { yield ` "${index.indexDescriptor}": {`; yield ` searchField: "${index.searchField}",`; yield ` filterFields: ${stringLiteralUnionType(index.filterFields)},`; yield ` },`; } yield ` },`; yield ` vectorIndexes: {`; for (const index of table.vectorIndexes ?? []) { yield ` "${index.indexDescriptor}": {`; yield ` vectorField: "${index.vectorField}",`; yield ` dimensions: ${index.dimensions},`; yield ` filterFields: ${stringLiteralUnionType(index.filterFields)},`; yield ` },`; } yield ` },`; yield `}`; } const SYSTEM_FIELDS = ["_id", "_creationTime"]; async function addSystemFields( ctx: Context, tableName: string, validator: ConvexValidator, ): Promise<ConvexValidator> { if (validator.type === "object") { return addSystemFieldsToObject(ctx, tableName, validator); } else if (validator.type === "any") { return { type: "any" }; } else if (validator.type === "union") { const newSubValidators = []; for (const subValidator of validator.value) { const newSubValidator = await addSystemFieldsToObject( ctx, tableName, subValidator, ); newSubValidators.push(newSubValidator); } return { type: "union", value: newSubValidators }; } else { return await ctx.crash({ exitCode: 1, errorType: "fatal", printedMessage: `Invalid top-level validator for ${tableName}.`, }); } } async function addSystemFieldsToObject( ctx: Context, tableName: string, validator: ConvexValidator, ): Promise<ConvexValidator> { if (validator.type !== "object") { return await ctx.crash({ exitCode: 1, errorType: "fatal", printedMessage: `System fields can only be added to objects.`, }); } for (const systemField of SYSTEM_FIELDS) { if (Object.hasOwn(validator.value, systemField)) { return await ctx.crash({ exitCode: 1, errorType: "fatal", printedMessage: `System field ${systemField} present in table ${tableName}.`, }); } } return { type: "object", value: { ...validator.value, _id: { fieldType: { type: "id", tableName }, optional: false, }, _creationTime: { fieldType: { type: "number" }, optional: false, }, }, }; } function* extractFieldPaths(validator: ConvexValidator): Generator<string[]> { if (validator.type === "object") { for (const [fieldName, fieldValidator] of Object.entries(validator.value)) { for (const subFieldPath of extractFieldPaths(fieldValidator.fieldType)) { yield [fieldName, ...subFieldPath]; } } } else if (validator.type === "union") { for (const subValidator of validator.value) { yield* extractFieldPaths(subValidator); } } else { yield []; } } function stringLiteralUnionType(fields: string[]) { if (fields.length === 0) { return "never"; } else if (fields.length === 1) { return `"${fields[0]}"`; } else { return fields.map((field) => `"${field}"`).join(" | "); } }

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