Skip to main content
Glama

Convex MCP server

Official
by get-convex
optimistic_updates_impl.ts7.44 kB
import { FunctionArgs, FunctionReference, FunctionReturnType, OptionalRestArgs, getFunctionName, } from "../../server/api.js"; import { parseArgs } from "../../common/index.js"; import { Value } from "../../values/index.js"; import { createHybridErrorStacktrace, forwardData } from "../logging.js"; import { FunctionResult } from "./function_result.js"; import { OptimisticLocalStore } from "./optimistic_updates.js"; import { RequestId } from "./protocol.js"; import { canonicalizeUdfPath, QueryToken, serializePathAndArgs, } from "./udf_path_utils.js"; import { ConvexError } from "../../values/errors.js"; /** * An optimistic update function that has been curried over its arguments. */ type WrappedOptimisticUpdate = (locaQueryStore: OptimisticLocalStore) => void; /** * The implementation of `OptimisticLocalStore`. * * This class provides the interface for optimistic updates to modify query results. */ class OptimisticLocalStoreImpl implements OptimisticLocalStore { // A references of the query results in OptimisticQueryResults private readonly queryResults: QueryResultsMap; // All of the queries modified by this class readonly modifiedQueries: QueryToken[]; constructor(queryResults: QueryResultsMap) { this.queryResults = queryResults; this.modifiedQueries = []; } getQuery<Query extends FunctionReference<"query">>( query: Query, ...args: OptionalRestArgs<Query> ): undefined | FunctionReturnType<Query> { const queryArgs = parseArgs(args[0]); const name = getFunctionName(query); const queryResult = this.queryResults.get( serializePathAndArgs(name, queryArgs), ); if (queryResult === undefined) { return undefined; } return OptimisticLocalStoreImpl.queryValue(queryResult.result); } getAllQueries<Query extends FunctionReference<"query">>( query: Query, ): { args: FunctionArgs<Query>; value: undefined | FunctionReturnType<Query>; }[] { const queriesWithName: { args: FunctionArgs<Query>; value: undefined | FunctionReturnType<Query>; }[] = []; const name = getFunctionName(query); for (const queryResult of this.queryResults.values()) { if (queryResult.udfPath === canonicalizeUdfPath(name)) { queriesWithName.push({ args: queryResult.args as FunctionArgs<Query>, value: OptimisticLocalStoreImpl.queryValue(queryResult.result), }); } } return queriesWithName; } setQuery<QueryReference extends FunctionReference<"query">>( queryReference: QueryReference, args: FunctionArgs<QueryReference>, value: undefined | FunctionReturnType<QueryReference>, ): void { const queryArgs = parseArgs(args); const name = getFunctionName(queryReference); const queryToken = serializePathAndArgs(name, queryArgs); let result: FunctionResult | undefined; if (value === undefined) { result = undefined; } else { result = { success: true, value, // It's an optimistic update, so there are no function logs to show. logLines: [], }; } const query: Query = { udfPath: name, args: queryArgs, result, }; this.queryResults.set(queryToken, query); this.modifiedQueries.push(queryToken); } private static queryValue( result: FunctionResult | undefined, ): Value | undefined { if (result === undefined) { return undefined; } else if (result.success) { return result.value; } else { // If the query is an error state, just return `undefined` as though // it's loading. Optimistic updates should already handle `undefined` well // and there isn't a need to break the whole update because it tried // to load a single query that errored. return undefined; } } } type OptimisticUpdateAndId = { update: WrappedOptimisticUpdate; mutationId: RequestId; }; type Query = { // undefined means the query was set to be loading (undefined) in an optimistic update. // Note that we can also have queries not present in the QueryResultMap // at all because they are still loading from the server and have no optimistic update // setting an optimistic value in advance. result: FunctionResult | undefined; udfPath: string; args: Record<string, Value>; }; export type QueryResultsMap = Map<QueryToken, Query>; type ChangedQueries = QueryToken[]; /** * A view of all of our query results with optimistic updates applied on top. */ export class OptimisticQueryResults { private queryResults: QueryResultsMap; private optimisticUpdates: OptimisticUpdateAndId[]; constructor() { this.queryResults = new Map(); this.optimisticUpdates = []; } /** * Apply all optimistic updates on top of server query results */ ingestQueryResultsFromServer( serverQueryResults: QueryResultsMap, optimisticUpdatesToDrop: Set<RequestId>, ): ChangedQueries { this.optimisticUpdates = this.optimisticUpdates.filter((updateAndId) => { return !optimisticUpdatesToDrop.has(updateAndId.mutationId); }); const oldQueryResults = this.queryResults; this.queryResults = new Map(serverQueryResults); const localStore = new OptimisticLocalStoreImpl(this.queryResults); for (const updateAndId of this.optimisticUpdates) { updateAndId.update(localStore); } // To find the changed queries, just do a shallow comparison // TODO(CX-733): Change this so we avoid unnecessary rerenders const changedQueries: ChangedQueries = []; for (const [queryToken, query] of this.queryResults) { const oldQuery = oldQueryResults.get(queryToken); if (oldQuery === undefined || oldQuery.result !== query.result) { changedQueries.push(queryToken); } } return changedQueries; } applyOptimisticUpdate( update: WrappedOptimisticUpdate, mutationId: RequestId, ): ChangedQueries { // Apply the update to our store this.optimisticUpdates.push({ update, mutationId, }); const localStore = new OptimisticLocalStoreImpl(this.queryResults); update(localStore); // Notify about any query results that changed // TODO(CX-733): Change this so we avoid unnecessary rerenders return localStore.modifiedQueries; } /** * @internal */ rawQueryResult(queryToken: QueryToken): Query | undefined { return this.queryResults.get(queryToken); } queryResult(queryToken: QueryToken): Value | undefined { const query = this.queryResults.get(queryToken); if (query === undefined) { return undefined; } const result = query.result; if (result === undefined) { return undefined; } else if (result.success) { return result.value; } else { if (result.errorData !== undefined) { throw forwardData( result, new ConvexError( createHybridErrorStacktrace("query", query.udfPath, result), ), ); } throw new Error( createHybridErrorStacktrace("query", query.udfPath, result), ); } } hasQueryResult(queryToken: QueryToken): boolean { return this.queryResults.get(queryToken) !== undefined; } /** * @internal */ queryLogs(queryToken: QueryToken): string[] | undefined { const query = this.queryResults.get(queryToken); return query?.result?.logLines; } }

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