Skip to main content
Glama
checkForLargeIndexDeletion.ts5.95 kB
import chalk from "chalk"; import { Context } from "../../bundler/context.js"; import { changeSpinner, logFinishedStep, logMessage, stopSpinner, } from "../../bundler/log.js"; import { formatIndex } from "./indexes.js"; import { promptYesNo } from "./utils/prompts.js"; import { Span } from "./tracing.js"; import { StartPushRequest } from "./deployApi/startPush.js"; import { evaluatePush } from "./deploy2.js"; import { DeveloperIndexConfig, IndexDiff } from "./deployApi/finishPush.js"; import { runSystemQuery } from "./run.js"; const MIN_DOCUMENTS_FOR_INDEX_DELETE_WARNING = 100_000; export async function checkForLargeIndexDeletion({ ctx, span, request, options, askForConfirmation, }: { ctx: Context; span: Span; request: StartPushRequest; options: { url: string; deploymentName: string | null; adminKey: string; }; askForConfirmation: boolean; }): Promise<void> { changeSpinner("Verifying that the push isn’t deleting large indexes..."); const { schemaChange } = await evaluatePush(ctx, span, request, options); const indexDiffs = schemaChange.indexDiffs ?? {}; const deletedIndexes = Object.entries(indexDiffs).flatMap( ([componentDefinitionPath, indexDiff]) => indexDiff.removed_indexes.map((index) => ({ componentDefinitionPath, index, })), ); if (deletedIndexes.length === 0) { logFinishedStep("No indexes are deleted by this push"); return; } const tablesWithDeletedIndexes = [ ...new Set( deletedIndexes.map( ({ componentDefinitionPath, index }) => `${componentDefinitionPath}:${getTableName(index)}`, ), ), ].map((str) => { const [componentDefinitionPath, table] = str.split(":"); return { componentDefinitionPath, table }; }); changeSpinner("Checking whether the deleted indexes are on large tables..."); const documentCounts = await Promise.all( tablesWithDeletedIndexes.map( async ({ componentDefinitionPath, table }) => ({ componentDefinitionPath, table, count: (await runSystemQuery(ctx, { deploymentUrl: options.url, adminKey: options.adminKey, functionName: "_system/cli/tableSize:default", componentPath: componentDefinitionPath, args: { tableName: table }, })) as number, }), ), ); const deletedIndexesWithDocumentsCount = deletedIndexes.map( ({ componentDefinitionPath, index }) => ({ componentDefinitionPath, index, count: documentCounts.find( (count) => count.table === getTableName(index) && count.componentDefinitionPath === componentDefinitionPath, )!.count, }), ); const minDocumentsForWarning = minDocumentsForIndexDeleteWarning(); if ( !deletedIndexesWithDocumentsCount.some( ({ count }) => count >= minDocumentsForWarning, ) ) { logFinishedStep("No large indexes are deleted by this push"); return; } logMessage(`⚠️ This code push will ${chalk.bold("delete")} the following ${deletedIndexesWithDocumentsCount.length === 1 ? "index" : "indexes"} from your production deployment (${options.url}): ${deletedIndexesWithDocumentsCount .map(({ componentDefinitionPath, index, count }) => formatDeletedIndex({ componentDefinitionPath, index, indexDiff: indexDiffs[componentDefinitionPath], documentsCount: count, minDocumentsForWarning, }), ) .join("\n")} The documents that are in the index won’t be deleted, but the index will need to be backfilled again if you want to restore it later. `); if (!askForConfirmation) { logFinishedStep( "Proceeding with push since --allow-deleting-large-indexes is set", ); return; } if (!process.stdin.isTTY) { return ctx.crash({ exitCode: 1, errorType: "fatal", printedMessage: `To confirm the push: • run the deploy command in an ${chalk.bold("interactive terminal")} • or run the deploy command with the ${chalk.bold("--allow-deleting-large-indexes")} flag`, }); } stopSpinner(); if ( !(await promptYesNo(ctx, { message: `Delete these ${deletedIndexesWithDocumentsCount.length === 1 ? "index" : "indexes"}?`, default: false, })) ) { return ctx.crash({ exitCode: 1, errorType: "fatal", printedMessage: `Canceling push`, }); } logFinishedStep("Proceeding with push."); } function formatDeletedIndex({ componentDefinitionPath, index, indexDiff, documentsCount, minDocumentsForWarning, }: { componentDefinitionPath: string; index: DeveloperIndexConfig; indexDiff: IndexDiff; documentsCount: number; minDocumentsForWarning: number; }) { const componentNameFormatted = componentDefinitionPath !== "" ? `${chalk.gray(componentDefinitionPath)}:` : ""; const documentsCountFormatted = documentsCount >= minDocumentsForWarning ? ` ${chalk.yellowBright(`⚠️ ${documentsCount.toLocaleString()} documents`)}` : ` ${documentsCount.toLocaleString()} ${documentsCount === 1 ? "document" : "documents"}`; const replacedBy = indexDiff.added_indexes.find((i) => i.name === index.name); const replacedByFormatted = replacedBy ? `\n ${chalk.green("→ replaced by:")} ${formatIndex(replacedBy)}` : ""; return ( "⛔ " + componentNameFormatted + formatIndex(index) + documentsCountFormatted + replacedByFormatted ); } function getTableName(index: DeveloperIndexConfig) { const [tableName, _indexName] = index.name.split("."); return tableName; } function minDocumentsForIndexDeleteWarning(): number { const envValue = process.env.CONVEX_MIN_DOCUMENTS_FOR_INDEX_DELETE_WARNING; if (envValue !== undefined) { const parsed = parseInt(envValue, 10); if (!isNaN(parsed)) { return parsed; } } return MIN_DOCUMENTS_FOR_INDEX_DELETE_WARNING; }

Latest Blog Posts

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