Skip to main content
Glama

Convex MCP server

Official
by get-convex
relationships.ts7.04 kB
import { FieldTypeFromFieldPath, Indexes, NamedTableInfo, SystemTableNames, } from "convex/server"; import { DataModel, Doc, TableNames } from "../_generated/dataModel"; import { DatabaseReader } from "../_generated/server"; import { GenericId } from "convex/values"; // We don't want to include system tables for joins as they have different // patterns for .get and .query type Id<TableName extends TableNames> = GenericId<TableName>; /** * asyncMap returns the results of applying an async function over an list. * * @param list - Iterable object of items, e.g. an Array, Set, Object.keys * @param asyncTransform * @returns */ export async function asyncMap<FromType, ToType>( list: Iterable<FromType>, asyncTransform: (item: FromType) => Promise<ToType>, ): Promise<ToType[]> { const promises: Promise<ToType>[] = []; for (const item of list) { promises.push(asyncTransform(item)); } return Promise.all(promises); } /** * getAll returns a list of Documents corresponding to the `Id`s passed in. * @param db A database object, usually passed from a mutation or query ctx. * @param ids An list (or other iterable) of Ids pointing to a table. * @returns The Documents referenced by the Ids, in order. `null` if not found. */ export async function getAll<TableName extends TableNames>( db: DatabaseReader, ids: Id<TableName>[], ): Promise<(Doc<TableName> | null)[]> { return asyncMap(ids, (id) => db.get(id)); } // `FieldPath`s that have a `"FieldPath"` index on [`FieldPath`, ...] // type LookupFieldPaths<TableName extends TableNames> = {[FieldPath in DataModel[TableName]["fieldPaths"]]: FieldPath extends keyof DataModel[TableName]["indexes"]? Indexes<NamedTableInfo<DataModel, TableName>>[FieldPath][0] extends FieldPath ? FieldPath : never: never}[DataModel[TableName]["fieldPaths"]] // `FieldPath`s that have a `"by_${FieldPath}""` index on [`FieldPath`, ...] type LookupFieldPaths<TableName extends TableNames> = { [FieldPath in DataModel[TableName]["fieldPaths"]]: `by_${FieldPath}` extends keyof DataModel[TableName]["indexes"] ? Indexes< NamedTableInfo<DataModel, TableName> >[`by_${FieldPath}`][0] extends FieldPath ? FieldPath : never : never; }[DataModel[TableName]["fieldPaths"]]; type TablesWithLookups = { [TableName in TableNames]: LookupFieldPaths<TableName> extends never ? never : TableName; }[TableNames]; /** * Get a document that references a value with a field indexed `by_${field}` * * Useful for fetching a document with a one-to-one relationship via backref. * @param db DatabaseReader, passed in from the function ctx * @param table The table to fetch the target document from. * @param field The field on that table that should match the specified value. * @param value The value to look up the document by, usually an ID. * @returns The document matching the value, or null if none found. */ export async function getOneFrom< TableName extends TablesWithLookups, Field extends LookupFieldPaths<TableName>, >( db: DatabaseReader, table: TableName, field: Field, value: FieldTypeFromFieldPath<Doc<TableName>, Field>, ): Promise<Doc<TableName> | null> { const ret = db .query(table) .withIndex("by_" + field, (q) => q.eq(field, value as any)) .unique(); return ret; } /** * Get a list of documents matching a value with a field indexed `by_${field}`. * * Useful for fetching many documents related to a given value via backrefs. * @param db DatabaseReader, passed in from the function ctx * @param table The table to fetch the target document from. * @param field The field on that table that should match the specified value. * @param value The value to look up the document by, usually an ID. * @returns The documents matching the value, if any. */ export async function getManyFrom< TableName extends TablesWithLookups, Field extends LookupFieldPaths<TableName>, >( db: DatabaseReader, table: TableName, field: Field, value: FieldTypeFromFieldPath<Doc<TableName>, Field>, ): Promise<(Doc<TableName> | null)[]> { return db .query(table) .withIndex("by_" + field, (q) => q.eq(field, value as any)) .collect(); } // File paths to fields that are IDs, excluding "_id". type IdFilePaths< InTableName extends TablesWithLookups, TableName extends TableNames, > = { [FieldName in DataModel[InTableName]["fieldPaths"]]: FieldTypeFromFieldPath< Doc<InTableName>, FieldName > extends Id<TableName> ? TableName extends SystemTableNames ? never : FieldName extends "_id" ? never : FieldName : never; }[DataModel[InTableName]["fieldPaths"]]; // Whether a table has an ID field that isn't its sole lookup field. // These can operate as join tables, going from one table to another. // One field has an indexed field for lookup, and another has the ID to get. type LookupAndIdFilePaths<TableName extends TablesWithLookups> = { [FieldPath in IdFilePaths< TableName, TableNames >]: LookupFieldPaths<TableName> extends FieldPath ? never : true; }[IdFilePaths<TableName, TableNames>]; // The table names that match LookupAndIdFields. // These are the possible "join" or "edge" or "relationship" tables. type JoinTables = { [TableName in TablesWithLookups]: LookupAndIdFilePaths<TableName> extends never ? never : TableName; }[TablesWithLookups]; // many-to-many via lookup table /** * Get related documents by using a join table. * * It will find all join table entries matching a value, then look up all the * documents pointed to by the join table entries. Useful for many-to-many * relationships. * @param db DatabaseReader, passed in from the function ctx * @param table The table to fetch the target document from. * @param toField The ID field on the table pointing at target documents. * @param fromField The field on the table to compare to the value. * @param value The value to match the fromField on the table, usually an ID. * @returns The documents targeted by matching documents in the table, if any. */ export async function getManyVia< JoinTableName extends JoinTables, ToField extends IdFilePaths<JoinTableName, TableNames>, FromField extends Exclude<LookupFieldPaths<JoinTableName>, ToField>, TargetTableName extends TableNames = FieldTypeFromFieldPath< Doc<JoinTableName>, ToField > extends Id<infer TargetTableName> ? TargetTableName : never, >( db: DatabaseReader, table: JoinTableName, toField: ToField, fromField: FromField, value: FieldTypeFromFieldPath<Doc<JoinTableName>, FromField>, ): Promise<(Doc<TargetTableName> | null)[]> { return asyncMap(await getManyFrom(db, table, fromField, value), (link) => db.get((link as any)[toField]), ); } /** * Filters out null elements from an array. * @param list List of elements that might be null. * @returns List of elements with nulls removed. */ export function pruneNull<T>(list: (T | null)[]): T[] { return list.filter((i) => i !== null) as T[]; }

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