/** * Copyright (c) 2026, Salesforce, Inc., * All rights reserved. * For full license text, see the LICENSE.txt file */ import { CommandError, getSessionSchema, selectLeafInSession, parsePathInput, printQuery, } from "./query-helpers.js"; import { loadSession, saveSession, getNavigationContext, isInArgsContext, queryNavToSchemaPath, getArgsFieldPath, getInputSubPath, getChildren, getFocusedNodeAtPath, formatPath, findDescendantByAlias, removeNodeByIdWithPrune, removeSelectionAtPath, removeVariable, deepRemoveArg, removeListElement, appendListElement, createSiblingFieldInstance, type FieldProjectionNode, } from "../lib/session.js"; import { resolveFieldOnPath } from "../lib/walker.js"; export function querySelect(sessionId: string, leafName: string, opts: { alias?: string }): void { const session = loadSession(sessionId); selectLeafInSession(session, leafName, opts.alias); saveSession(session); console.log(`Selected ${leafName}${opts.alias ? ` as ${opts.alias}` : ""}.`); console.log(""); printQuery(session); } export function querySelectLs(sessionId: string): void { const session = loadSession(sessionId); const currentSchemaPath = queryNavToSchemaPath(session.navigationPath); const leaves = session.nodes.filter((node): node is FieldProjectionNode => { if (node.kind !== "field") return false; return getChildren(session, node.id).length === 0; }); if (leaves.length === 0) { console.log("No fields selected. Use `select ` or `select ` to add fields."); return; } console.log(`Selected fields (${leaves.length}):`); console.log(""); for (const leaf of leaves) { const fullPath = leaf.schemaPath; let displayPath: string; if ( currentSchemaPath.length > 0 && fullPath.length > currentSchemaPath.length && currentSchemaPath.every((seg, i) => seg === fullPath[i]) ) { displayPath = fullPath.slice(currentSchemaPath.length).join("."); } else { displayPath = "/" + fullPath.join("/"); } const aliasPart = leaf.alias ? ` → ${leaf.alias}` : ""; const optPart = leaf.directives.some((d) => d.name === "optional") ? " @optional" : ""; console.log(` ${displayPath}${aliasPart}${optPart}`); } console.log(""); console.log("Remove with: rm or rm "); } export function queryRm(sessionId: string, rawInput: string): void { const session = loadSession(sessionId); const ctx = getNavigationContext(session.navigationPath); if (rawInput.startsWith("$") && !isInArgsContext(session.navigationPath)) { const cleanName = rawInput.replace(/^\$/, ""); const removed = removeVariable(session, cleanName); if (!removed) { throw new CommandError(`Variable "$${cleanName}" is not defined.`); } saveSession(session); console.log(`Removed variable $${cleanName} and all references to it.`); console.log("Tip: use `undo` to restore it."); console.log(""); printQuery(session); return; } if (ctx === "variables" && !isInArgsContext(session.navigationPath)) { const cleanName = rawInput.replace(/^\$/, ""); const removed = removeVariable(session, cleanName); if (!removed) { throw new CommandError(`Variable "$${cleanName}" is not defined.`); } saveSession(session); console.log(`Removed variable $${cleanName} and all references to it.`); console.log("Tip: use `undo` to restore it."); console.log(""); printQuery(session); return; } if (isInArgsContext(session.navigationPath)) { const fieldSchemaPath = getArgsFieldPath(session.navigationPath); const inputSubPath = getInputSubPath(session.navigationPath); const fullPath = [...inputSubPath, ...rawInput.split(".")]; if (fullPath.length === 0) { throw new CommandError("Usage: rm or rm "); } const argName = fullPath[0]; const nested = fullPath.slice(1); if (/^\d+$/.test(rawInput) && inputSubPath.length > 0) { const parentArgName = inputSubPath[0]; const parentNested = inputSubPath.slice(1); const removed = removeListElement( session, fieldSchemaPath, parentArgName, parentNested, Number(rawInput), ); if (removed) { saveSession(session); console.log(`Removed list element ${rawInput}.`); console.log(""); printQuery(session); return; } } const removed = deepRemoveArg(session, fieldSchemaPath, argName, nested); if (!removed) { throw new CommandError(`No value set at "${rawInput}".`); } saveSession(session); console.log(`Removed "${rawInput}".`); console.log(""); printQuery(session); return; } const currentSchemaPath = queryNavToSchemaPath(session.navigationPath); let spec = rawInput; let explicitAlias: string | undefined; const colonIdx = rawInput.lastIndexOf(":"); const afterColon = colonIdx > 0 ? rawInput.slice(colonIdx + 1) : ""; if (colonIdx > 0 && afterColon.length > 0 && !/[./:]/.test(afterColon)) { spec = rawInput.slice(0, colonIdx); explicitAlias = afterColon; } const normalizedSpec = spec.includes(".") && !spec.includes("/") ? spec.replace(/\./g, "/") : spec; if (normalizedSpec.includes("/")) { const parts = normalizedSpec.split("/"); const leafName = parts.pop()!; const dirPath = parts.join("/"); const resolved = parsePathInput(session.navigationPath, dirPath); const targetSchemaPath = queryNavToSchemaPath(resolved); const selector = explicitAlias ?? undefined; const removed = removeSelectionAtPath(session, [...targetSchemaPath, leafName], selector) || removeSelectionAtPath(session, [...targetSchemaPath, leafName]); if (!removed) { throw new CommandError(`No selected projection matching "${rawInput}" exists.`); } saveSession(session); console.log(`Removed "${rawInput}".`); console.log(""); printQuery(session); return; } const focusedNode = getFocusedNodeAtPath(session, currentSchemaPath, false); const parentId = focusedNode?.id ?? null; const aliasedChild = getChildren(session, parentId).find( (n): n is FieldProjectionNode => n.kind === "field" && n.alias === rawInput, ); if (aliasedChild) { const removed = removeSelectionAtPath(session, [...currentSchemaPath, aliasedChild.fieldName], rawInput) || removeSelectionAtPath(session, [...currentSchemaPath, aliasedChild.fieldName]); if (removed) { saveSession(session); console.log(`Removed "${rawInput}".`); console.log(""); printQuery(session); return; } } const fieldRemoved = removeSelectionAtPath(session, [...currentSchemaPath, rawInput], rawInput) || removeSelectionAtPath(session, [...currentSchemaPath, rawInput]); if (fieldRemoved) { saveSession(session); console.log(`Removed "${rawInput}".`); console.log(""); printQuery(session); return; } const deepNode = findDescendantByAlias(session, parentId, rawInput); if (deepNode) { removeNodeByIdWithPrune(session, deepNode.id); saveSession(session); console.log(`Removed "${rawInput}".`); console.log(""); printQuery(session); return; } throw new CommandError( `No selected projection matching "${rawInput}" exists under ${formatPath(session.navigationPath)}.` + "\nTip: Use dot-path syntax (e.g. `rm Owner.Name.value`) or alias name. Run `select ls` to see all selections.", ); } export function queryMkdir(sessionId: string, rest: string[]): void { const session = loadSession(sessionId); const ctx = getNavigationContext(session.navigationPath); if (isInArgsContext(session.navigationPath)) { const fieldSchemaPath = getArgsFieldPath(session.navigationPath); const inputSubPath = getInputSubPath(session.navigationPath); if (inputSubPath.length === 0) { throw new CommandError( "Navigate to a specific argument first (e.g. `cd orderBy`), then use `mkdir` to add a list element.", ); } const argName = inputSubPath[0]; const nestedPath = inputSubPath.slice(1); const idx = appendListElement(session, fieldSchemaPath, argName, nestedPath); saveSession(session); console.log(`Created element ${idx}/`); console.log(`Navigate into it with: cd ${idx}`); return; } if (ctx === "query") { if (rest.length < 2) { throw new CommandError( "Usage: mkdir — creates an aliased projection instance.", ); } const alias = rest[0]; const fieldName = rest[1]; const schemaPath = queryNavToSchemaPath(session.navigationPath); const schema = getSessionSchema(session); resolveFieldOnPath(schema, session.operation, schemaPath, fieldName); const targetPath = [...schemaPath, fieldName]; createSiblingFieldInstance(session, targetPath, alias); saveSession(session); console.log(`Created aliased instance: ${alias}(${fieldName})/`); console.log(`Navigate into it with: cd ${alias}`); console.log(""); printQuery(session); return; } throw new CommandError( "`mkdir` works inside `query/` (to create aliased instances) or inside `@args/` (to add list elements).", ); }