import type { IRestApiAdapter } from '../adapters/rest/rest-api-adapter.js'; import type { ISoqlQueryAdapter } from '../adapters/soql/soql-query-adapter.js'; import type { DescribeSObjectResult, DescribeFieldResult } from '../adapters/connection-facade.js'; import type { ServiceResult } from '../models/service-result.js'; import type { RecordTypeService, RecordTypeInfo } from './RecordTypeService.js'; import type { ProfilingDefinitionService, ProfilingDefinition } from './ProfilingDefinitionService.js'; import type { ProfilingSummaryService } from './ProfilingSummaryService.js'; /** * Business process information with associated record types and record age distribution. * Used by object describe to show per-process record existence grid. */ export type ObjectBusinessProcessInfo = { /** BusinessProcess ID */ id: string; /** Process name */ name: string; /** Whether the process is active */ isActive: boolean; /** Number of record types using this process */ recordTypeCount: number; /** Record type IDs using this process */ recordTypeIds: string[]; /** Per-process record age distribution (populated after age queries) */ recordAgeDistribution?: RecordAgeDistribution; }; /** * Field classification information derived from describe metadata. */ export type FieldClassification = { /** Total number of fields on the object */ totalFields: number; /** Count of custom fields (field.custom === true) */ customFields: number; /** Count of formula fields (field.calculated === true) */ formulaFields: number; /** Count of lookup/reference fields (field.referenceTo?.length > 0) */ lookupFields: number; /** Count of required fields (createable && !nillable && !defaultValue) */ requiredFields: number; /** Count of picklist fields (type === 'picklist' || 'multipicklist') */ picklistFields: number; }; /** * Record age information for the object. */ export type RecordAgeInfo = { /** Whether record age information is available */ available: boolean; /** ISO 8601 date of oldest record, if available */ oldestRecordDate?: string; /** ISO 8601 date of newest record, if available */ newestRecordDate?: string; }; /** * Year-level record existence status for a single calendar year. */ export type YearRecordAge = { /** The calendar year */ year: number; /** Whether records exist in this year */ hasRecords: boolean; }; /** * Record age distribution showing year-by-year record existence. * * Array-based structure for extensibility and clean MCP/JSON consumption. * Years are ordered newest-first (descending). */ export type RecordAgeDistribution = { /** Per-year record existence, ordered newest-first */ years: YearRecordAge[]; /** ISO 8601 date of oldest record, or null if none */ oldestRecordDate: string | null; }; /** * Field type distribution entry. */ export type FieldTypeCount = { /** The display type (e.g., 'Text', 'Number', 'Lookup') */ type: string; /** Count of fields with this type */ count: number; /** Percentage of total fields (rounded to 1 decimal) */ percent: number; }; /** * Namespace breakdown entry. */ export type NamespaceCount = { /** The namespace prefix or null for standard/unpackaged fields */ namespace: string | null; /** Count of fields in this namespace */ fieldCount: number; }; /** * External reference from another object (inbound relationship). */ export type ExternalReference = { /** The child object API name that references this object */ objectName: string; /** The field on the child object that holds the reference */ fieldName: string; /** The relationship name for SOQL queries */ relationshipName: string | null; }; /** * Lookup field with target object information. */ export type LookupFieldInfo = { /** The lookup field API name */ fieldName: string; /** The field label */ label: string; /** Target object(s) this field references */ targetObjects: string[]; /** The relationship name for SOQL traversal */ relationshipName: string | null; }; /** * Relationship summary for an object. */ export type RelationshipSummary = { /** Number of objects that reference this object */ referencedByCount: number; /** Details of objects that reference this object (from childRelationships) */ referencedBy: ExternalReference[]; /** Lookup fields on this object pointing to other objects */ lookupFields: LookupFieldInfo[]; }; /** * Enriched profiling status for the object. */ export type ProfilingStatus = { /** Whether profiling definitions exist for this object */ hasDefinitions: boolean; /** Number of profiling definitions for this object */ definitionCount: number; /** List of profiling definitions (if available) */ definitions?: ProfilingDefinition[]; /** Most recent profiling summary date, if available */ lastProfiledDate?: string; /** Total summary count across all definitions */ summaryCount?: number; }; /** * Comprehensive object metadata with field analysis and enrichment. */ export type ObjectDescribeResult = { /** The object API name */ objectName: string; /** The object label */ label: string; /** The object plural label */ labelPlural: string; /** Whether this is a custom object */ isCustom: boolean; /** Whether the object is queryable */ isQueryable: boolean; /** Whether the object is createable */ isCreateable: boolean; /** Whether the object is updateable */ isUpdateable: boolean; /** Whether the object is deletable */ isDeletable: boolean; /** Whether the object is searchable */ isSearchable: boolean; /** Field classification statistics */ fieldClassification: FieldClassification; /** All fields from the describe result */ fields: DescribeFieldResult[]; /** Record type information (enriched if RecordTypeService provided) */ recordTypes?: RecordTypeInfo[]; /** Record age information (if queryable) */ recordAge?: RecordAgeInfo; /** Profiling status (enriched if profiling services provided) */ profilingStatus?: ProfilingStatus; /** Relationship summary including external references and lookup fields */ relationships?: RelationshipSummary; /** Whether PersonAccount is enabled (only populated for Account object) */ isPersonAccountEnabled?: boolean; }; /** * Configuration for ObjectDescribeService. */ export type IObjectDescribeServiceConfig = { /** REST API adapter for describing objects */ restApiAdapter: IRestApiAdapter; /** SOQL query adapter for record age queries */ soqlAdapter: ISoqlQueryAdapter; /** Optional RecordTypeService for enriching record type information */ recordTypeService?: RecordTypeService; /** Optional ProfilingDefinitionService for enriching profiling status */ profilingDefinitionService?: ProfilingDefinitionService; /** Optional ProfilingSummaryService for enriching summary information */ profilingSummaryService?: ProfilingSummaryService; /** Optional logger for debug output */ logger?: Console; /** Optional REST client for server-side describe via ISV REST API */ restClient?: import('../adapters/rest/profiling-rest-client.js').ProfilingRestClient; }; /** * Service for retrieving comprehensive object metadata with field analysis and profiling status. * * This service combines REST API describe results with optional enrichment from: * - RecordTypeService: Record type information * - ProfilingDefinitionService: Existing profiling definitions * - ProfilingSummaryService: Profiling summary statistics * * Object Rejection Rules: * - Big Objects (__b suffix) are rejected with E4805 * - External Objects (__x suffix) are rejected with E4806 * * @example * ```typescript * const service = new ObjectDescribeService({ * restApiAdapter, * soqlAdapter, * recordTypeService, * profilingDefinitionService, * profilingSummaryService, * }); * * const result = await service.describeObject('Account'); * if (result.success) { * console.log(`${result.data.objectName}: ${result.data.fieldClassification.totalFields} fields`); * } * ``` */ export declare class ObjectDescribeService { /** Lookup table for field type to display category mapping */ private static readonly FIELD_TYPE_MAP; private readonly restApiAdapter; private readonly soqlAdapter; private readonly recordTypeService?; private readonly profilingDefinitionService?; private readonly profilingSummaryService?; private readonly logger?; private readonly restClient?; constructor(config: IObjectDescribeServiceConfig); /** * Calculates field type distribution from an array of fields. * * @param fields - Array of DescribeFieldResult from object describe * @returns ServiceResult containing array of FieldTypeCount sorted by count descending */ static getFieldTypeDistribution(fields: DescribeFieldResult[]): ServiceResult; /** * Calculates namespace breakdown from an array of fields. * * @param fields - Array of DescribeFieldResult from object describe * @returns ServiceResult containing array of NamespaceCount sorted by fieldCount descending */ static getNamespaceBreakdown(fields: DescribeFieldResult[]): ServiceResult; /** * Extracts external references from childRelationships array. * * External references represent objects that have lookup/master-detail * relationships pointing TO this object. * * @param describeResult - The describe result containing childRelationships * @returns Array of external references sorted alphabetically by object name */ static extractExternalReferences(describeResult: DescribeSObjectResult): ExternalReference[]; /** * Extracts lookup fields from fields array with their target objects. * * @param fields - Array of field describe results * @returns Array of lookup field info sorted alphabetically by field name */ static extractLookupFields(fields: DescribeFieldResult[]): LookupFieldInfo[]; /** * Detects if PersonAccount is enabled for the Account object. * * PersonAccount is detected by checking for the presence of the IsPersonAccount field, * which only exists when PersonAccount is enabled in the org. * * @param objectName - The object being described * @param fields - The fields from the describe result * @returns true if PersonAccount is enabled (only for Account object), undefined otherwise */ static detectPersonAccount(objectName: string, fields: DescribeFieldResult[]): boolean | undefined; /** * Builds a relationship summary from describe result. * * @param describeResult - The describe result * @returns RelationshipSummary with external references and lookup fields */ static buildRelationshipSummary(describeResult: DescribeSObjectResult): RelationshipSummary; /** Builds RelationshipSummary from server childRelationships + fields. */ private static mapServerRelationships; /** Maps server profilingStatus block to CLI ProfilingStatus. */ private static mapServerProfilingStatus; /** Maps server recordTypeInfos to CLI RecordTypeInfo[], filtering out Master. */ private static mapServerRecordTypes; /** * Maps a Salesforce field type to a display category. * * @param field - The field describe result * @returns Display category string */ private static mapFieldToDisplayType; /** * Extracts the namespace from a field API name. * * @param fieldName - The field API name (e.g., 'Name', 'Custom__c', 'pnova__Field__c') * @returns The namespace (e.g., 'pnova') or null for standard/unmanaged fields */ private static extractNamespace; /** * Checks if the object is a Big Object (ends with __b). * * @param objectName - The object API name * @returns true if it's a Big Object */ private static isBigObject; /** * Checks if the object is an External Object (ends with __x). * * @param objectName - The object API name * @returns true if it's an External Object */ private static isExternalObject; /** * Classifies fields from a describe result. * * @param fields - Array of field describe results * @returns Field classification statistics */ private static classifyFields; /** * Maps a DescribeSObjectResult to our ObjectDescribeResult base structure. * * @param describeResult - The raw describe result from REST API * @returns ObjectDescribeResult without enrichment */ private static mapToObjectDescribeResult; /** * Creates an empty result for failure cases. * * @param objectName - The object name for the empty result * @returns Empty ObjectDescribeResult */ private static createEmptyResult; /** * Extracts a meaningful message from an unknown error. * * Handles the edge case where Error instances have an empty message * but a meaningful name property (e.g., sinon's rejects behavior). * * @param error - The error to extract a message from * @returns A human-readable error message */ private static getErrorMessage; /** * Applies RecordTypeId filter to a query builder. * Single string uses `=`, array uses `IN`. * The builder auto-promotes to first WHERE if no prior condition exists. * * @param builder - The query builder to modify * @param recordTypeIds - Single ID, array of IDs, or undefined (no filter) */ private static applyRecordTypeFilter; /** * Describes a Salesforce object with comprehensive metadata and optional enrichment. * * This method: * 1. Validates the object name * 2. Rejects Big Objects (__b) and External Objects (__x) * 3. Retrieves describe metadata via REST API * 4. Classifies fields (custom, formula, lookup, required, picklist) * 5. Enriches with record types, record age, and profiling status (in parallel) * * @param objectName - The Salesforce object API name (e.g., 'Account', 'Custom_Object__c') * @returns ServiceResult containing the ObjectDescribeResult */ describeObject(objectName: string): Promise>; /** * Describes multiple Salesforce objects in parallel. * * @param objectNames - Array of Salesforce object API names to describe * @returns ServiceResult containing array of ObjectDescribeResult (one per successful describe) */ describeMultiple(objectNames: string[]): Promise>; /** * Retrieves record age distribution for an object showing year-by-year record existence. * * @param objectName - The Salesforce object API name * @param recordTypeIds - Optional RecordTypeId(s) to filter. Single string uses `=`, array uses `IN`. * @returns ServiceResult containing RecordAgeDistribution with year-by-year status */ getRecordAgeDistribution(objectName: string, recordTypeIds?: string | string[]): Promise>; /** * Retrieves business processes for a specific object with their record type IDs. * * Only Opportunity, Case, and Lead support business processes. For other objects, * returns an empty array without issuing any queries. * * @param objectName - The Salesforce object API name * @param recordTypes - Record types already enriched (used to cross-reference BusinessProcessId) * @returns ServiceResult containing array of ObjectBusinessProcessInfo */ getBusinessProcesses(objectName: string, recordTypes: RecordTypeInfo[]): Promise>; /** * Delegates object describe to the ISV REST API. * * @param objectName - The Salesforce object API name to describe * @param startTime - Timestamp for duration tracking * @returns ServiceResult containing ObjectDescribeResult from REST endpoint */ private describeObjectViaRest; /** * Falls back to standard REST describe for fields when ISV REST API * returns field counts but omits the individual field array. * * @returns The field array to use (original or fallback) */ private resolveFieldFallback; /** * Enriches record type information using RecordTypeService. * Errors are propagated to the caller for centralized handling. * * @param objectName - The object API name * @returns Array of RecordTypeInfo or undefined if not configured or query fails gracefully * @throws Error if service call throws */ private enrichRecordTypes; /** * Enriches profiling status using ProfilingDefinitionService and ProfilingSummaryService. * Errors are propagated to the caller for centralized handling. * * @param objectName - The object API name * @returns ProfilingStatus or undefined if not configured or query fails gracefully * @throws Error if service call throws */ private enrichProfilingStatus; }