/** * Copyright (c) 2026, Salesforce, Inc., * All rights reserved. * For full license text, see the LICENSE.txt file */ /* eslint-disable @typescript-eslint/no-explicit-any -- graphiti traverses untyped schema/introspection JSON; see follow-up to replace with `unknown` + narrowing */ import type { GraphQLSchema } from "graphql"; import { getOrgAuth } from "../lib/auth.js"; import { getObjectInfo, getRequiredCreateFields, type ObjectInfoResult, type FieldMetadata, } from "../lib/object-info.js"; import { getSchema, inspectType } from "../lib/walker.js"; export async function describeCommand(orgAlias: string, sObjectName: string): Promise { const auth = await getOrgAuth(orgAlias); const schema = getSchema(auth.instanceUrl); let objInfo: ObjectInfoResult | null = null; try { objInfo = await getObjectInfo(auth, orgAlias, sObjectName); } catch { // Fall back to schema-only } if (!objInfo) { console.log(`Could not fetch ObjectInfo for "${sObjectName}". Showing schema-only view.`); console.log(""); showSchemaOnlyView(schema, sObjectName); return; } const _metaByApi = new Map(objInfo.fields.map((f) => [f.apiName, f])); // Object header const capabilities: string[] = []; if (objInfo.queryable) capabilities.push("queryable"); if (objInfo.createable) capabilities.push("createable"); if (objInfo.updateable) capabilities.push("updateable"); if (objInfo.deletable) capabilities.push("deletable"); if (objInfo.searchable) capabilities.push("searchable"); console.log( `${sObjectName} "${objInfo.labelPlural ?? objInfo.label ?? sObjectName}" (keyPrefix: ${objInfo.keyPrefix ?? "n/a"}, nameField: ${objInfo.nameFields.join(", ") || "n/a"})`, ); console.log(` ${capabilities.join(", ")}`); if (objInfo.recordTypeInfos.length > 1) { const available = objInfo.recordTypeInfos.filter((r) => r.available); console.log(` Record types: ${available.map((r) => r.name).join(", ")}`); } console.log(""); // Fields table console.log(`Fields (${objInfo.fields.length}):`); const sortedFields = [...objInfo.fields].sort((a, b) => { // Name field first, then required, then alphabetical if (a.nameField && !b.nameField) return -1; if (!a.nameField && b.nameField) return 1; if (a.required && !b.required) return -1; if (!a.required && b.required) return 1; return a.apiName.localeCompare(b.apiName); }); for (const field of sortedFields) { const line = formatFieldRow(field, objInfo); console.log(` ${line}`); } // Create fields const createFields = objInfo.fields.filter((f) => f.createable); const requiredOnCreate = getRequiredCreateFields(objInfo); if (createFields.length > 0) { console.log(""); console.log( `Create fields (${createFields.length}): ${createFields.map((f) => f.apiName).join(", ")}`, ); if (requiredOnCreate.length > 0) { console.log(` Required on create: ${requiredOnCreate.map((f) => f.apiName).join(", ")}`); } else { console.log(" Required on create: (none -- all have defaults or are optional)"); } } // Update fields const updateFields = objInfo.fields.filter((f) => f.updateable); if (updateFields.length > 0) { console.log(""); console.log( `Update fields (${updateFields.length}): ${updateFields.map((f) => f.apiName).join(", ")}`, ); const createOnly = objInfo.fields.filter((f) => f.createable && !f.updateable); if (createOnly.length > 0) { console.log(` Create-only (cannot update): ${createOnly.map((f) => f.apiName).join(", ")}`); } } // Child relationships const namedRels = objInfo.childRelationships.filter((cr) => cr.relationshipName); if (namedRels.length > 0) { console.log(""); console.log(`Child relationships (${namedRels.length}):`); for (const cr of namedRels.slice(0, 20)) { console.log( ` ${cr.relationshipName!.padEnd(30)} -> ${cr.childObjectApiName}.${cr.fieldName}`, ); } if (namedRels.length > 20) { console.log(` ... ${namedRels.length - 20} more`); } } // Picklists summary if (objInfo.picklists.length > 0) { console.log(""); console.log(`Picklist fields (${objInfo.picklists.length}):`); for (const pl of objInfo.picklists) { const vals = pl.values.map((v) => v.value).filter((v): v is string => v !== null); console.log(` ${pl.apiName.padEnd(25)} ${vals.join(", ")}`); } } // Filter and OrderBy examples console.log(""); console.log("Filter examples:"); const filterableFields = objInfo.fields.filter((f) => f.filterable && !f.compound); const picklistField = filterableFields.find((f) => f.dataType === "PICKLIST"); const stringField = filterableFields.find((f) => f.dataType === "STRING" && f.filterable); const dateField = filterableFields.find((f) => f.dataType === "DATETIME" && f.filterable); if (picklistField) { const pl = objInfo.picklists.find((p) => p.apiName === picklistField.apiName); const val = pl?.values[0]?.value ?? ""; console.log(` { "${picklistField.apiName}": { "eq": "${val}" } }`); } if (stringField) { console.log(` { "${stringField.apiName}": { "like": "%search%" } }`); } if (picklistField && dateField) { console.log( ` { "and": [{ "${picklistField.apiName}": { "eq": "" } }, { "${dateField.apiName}": { "gt": "2024-01-01T00:00:00Z" } }] }`, ); } const sortableFields = objInfo.fields.filter((f) => f.sortable && !f.compound); if (sortableFields.length > 0) { console.log(""); console.log("OrderBy examples:"); const first = sortableFields[0]; console.log(` { "${first.apiName}": { "order": "DESC" } }`); if (sortableFields.length > 1) { console.log( ` { "${sortableFields[0].apiName}": { "order": "ASC" }, "${sortableFields[1].apiName}": { "order": "DESC" } }`, ); } } } function formatFieldRow(field: FieldMetadata, objInfo: ObjectInfoResult): string { const name = field.apiName.padEnd(28); const dataType = (field.dataType ?? "UNKNOWN").padEnd(14); const label = field.label && field.label !== field.apiName ? `"${field.label}"` : ""; const tags: string[] = []; if (field.required && field.createable && !field.defaultedOnCreate) tags.push("required"); if (field.required && field.defaultedOnCreate) tags.push("auto"); if (field.nameField) tags.push("name-field"); if (!field.filterable) tags.push("no-filter"); if (!field.sortable) tags.push("no-sort"); if (!field.createable && !field.updateable) tags.push("read-only"); if (field.createable && !field.updateable) tags.push("create-only"); if (field.defaultedOnCreate && field.createable) tags.push("default-on-create"); if (field.calculated) tags.push("formula"); if (field.custom) tags.push("custom"); if (field.compound) tags.push("compound"); if (field.compoundFieldName) tags.push(`child of ${field.compoundFieldName}`); let extra = ""; if (field.reference && field.referenceToInfos.length > 0) { const targets = field.referenceToInfos.map((r) => { const nameFields = r.nameFields.length > 0 ? ` (${r.nameFields.join(", ")})` : ""; return `${r.apiName}${nameFields}`; }); extra = `-> ${targets.join(", ")}`; } const picklist = objInfo.picklists.find((p) => p.apiName === field.apiName); if (picklist && picklist.values.length > 0) { const vals = picklist.values.map((v) => v.value).filter((v): v is string => v !== null); if (vals.length <= 6) { extra = `values: [${vals.join(", ")}]`; } else { extra = `values: [${vals.slice(0, 5).join(", ")}, ... +${vals.length - 5}]`; } } if (field.extraTypeInfo) { extra += extra ? ` (${field.extraTypeInfo})` : `(${field.extraTypeInfo})`; } if (field.precision > 0) { extra += extra ? ` precision=${field.precision}` : `precision=${field.precision}`; if (field.scale > 0) extra += `,scale=${field.scale}`; } if (field.inlineHelpText) { extra += extra ? ` -- ${field.inlineHelpText}` : field.inlineHelpText; } const tagStr = tags.length > 0 ? `[${tags.join(", ")}]` : ""; const labelStr = label ? ` ${label}` : ""; return `${name} ${dataType}${labelStr} ${tagStr} ${extra}`.trimEnd(); } function showSchemaOnlyView(schema: GraphQLSchema, typeName: string): void { try { const info = inspectType(schema, typeName); console.log(`${typeName} (${info.kind})`); if (info.description) console.log(` ${info.description}`); console.log(""); if (info.fields.length > 0) { console.log("Fields:"); for (const f of info.fields) { console.log(` ${f.name.padEnd(28)} ${f.typeName}${f.isNonNull ? "!" : ""}`); } } if (info.inputFields.length > 0) { console.log("Input fields:"); for (const f of info.inputFields) { console.log(` ${f.name.padEnd(28)} ${f.typeName}${f.isNonNull ? "!" : ""}`); } } if (info.enumValues.length > 0) { console.log("Values:"); for (const v of info.enumValues) { console.log(` ${v.name}${v.description ? ` -- ${v.description}` : ""}`); } } } catch (_e: any) { console.error(`Type "${typeName}" not found in schema.`); } }