import type { IConnectionFacade, DescribeGlobalResult } from '../adapters/connection-facade.js'; import type { ISoqlQueryAdapter } from '../adapters/soql/soql-query-adapter.js'; import type { IToolingApiAdapter } from '../adapters/tooling/tooling-api-adapter.js'; import type { IRestApiAdapter } from '../adapters/rest/rest-api-adapter.js'; import type { ServiceResult } from '../models/service-result.js'; /** * Org identity information from the Salesforce API. */ export type OrgIdentity = { /** The user ID of the current user */ userId: string; /** The organization ID */ organizationId: string; /** The username of the current user */ username: string; /** The organization name */ organizationName?: string; /** Whether this is a sandbox org */ isSandbox?: boolean; /** The org type (e.g., 'Developer Edition', 'Enterprise Edition') */ organizationType?: string; /** The org's namespace prefix (if any) */ namespacePrefix?: string; }; /** * API limit information from /limits endpoint. */ export type OrgLimit = { /** Name of the limit */ name: string; /** Maximum allowed value */ max: number; /** Number of used units */ used: number; /** Percentage used (0-100) */ percentUsed: number; }; /** * Org limits aggregated from /limits endpoint. */ export type OrgLimits = { /** All limits indexed by name */ limits: Record; /** Daily API requests limit */ dailyApiRequests?: OrgLimit; /** Data storage limit in MB */ dataStorageMB?: OrgLimit; /** File storage limit in MB */ fileStorageMB?: OrgLimit; /** Daily Bulk API 1.0 requests */ dailyBulkApiRequests?: OrgLimit; /** Daily Bulk API 2.0 query jobs */ dailyBulkV2QueryJobs?: OrgLimit; /** Daily async Apex executions */ dailyAsyncApexExecutions?: OrgLimit; /** Daily streaming/platform events delivered */ dailyStreamingApiEvents?: OrgLimit; }; /** * Information about an installed package. */ export type InstalledPackage = { /** The subscriber package ID */ id: string; /** The namespace prefix */ namespacePrefix: string; /** The package name */ name: string; /** The installed version number */ versionNumber: string; /** Whether this is a managed package */ isManaged: boolean; /** The product name (from NAMESPACE_PRODUCT_MAP) */ productName?: string; }; /** * Complete org information aggregated from multiple sources. */ export type OrgInfo = { /** Org identity information */ identity: OrgIdentity; /** API limits */ limits: OrgLimits; /** Installed packages */ packages: InstalledPackage[]; /** Active namespaces detected in the org */ activeNamespaces: string[]; /** Cuneiform version (null if not installed) */ cuneiformVersion: string | null; }; /** * Org features detected via object/field probing. */ export type OrgFeatures = { /** Whether multi-currency is enabled (CurrencyIsoCode field exists) */ multiCurrency: boolean; /** Whether enhanced notes is enabled (ContentNote object is queryable) */ enhancedNotes: boolean; /** Whether state/country picklists is enabled (CountryCode field exists on Address) */ stateCountryPicklists: boolean; /** Whether territory management 2.0 is enabled (Territory2 object exists) */ territoryManagement: boolean; /** Whether person accounts is enabled (IsPersonAccount field exists on Account) */ personAccounts: boolean; /** Whether digital experiences (communities) is enabled (Network object exists) */ digitalExperiences: boolean; /** Whether Einstein AI scoring features are enabled (AIRecordInsight object exists) */ einsteinAI: boolean; }; /** * Information about a detected Salesforce Cloud or product. */ export type DetectedCloud = { /** Display name: "Sales Cloud", "Financial Services Cloud" */ name: string; /** How it's detected: license-based or package-based */ type: 'license' | 'package'; /** Whether detected in org */ installed: boolean; /** Total licenses purchased (for license-based clouds) */ totalLicenses?: number; /** Licenses in use (for license-based clouds) */ usedLicenses?: number; /** Package namespace (for package-based clouds) */ namespace?: string; /** Version if installed (for package-based clouds) */ version?: string; /** License status from UserLicense.Status (for license-based clouds) */ status?: string; }; /** * User license information from UserLicense object. */ export type LicenseInfo = { /** The license ID */ id: string; /** The license name */ name: string; /** The master label */ masterLabel: string; /** Total licenses available */ totalLicenses: number; /** Licenses currently in use */ usedLicenses: number; /** License status */ status: string; /** License type: user or permissionSet */ type: 'user' | 'permissionSet'; }; /** * Information about an installed namespace with object count. */ export type InstalledNamespace = { /** Mapped product name or "Unknown" */ productName: string; /** Namespace prefix: "FinServ", "SBQQ" */ prefix: string; /** Number of objects with this namespace */ objectCount: number; /** Number of objects with at least one record */ populatedCount: number; /** Number of objects with zero records */ emptyCount: number; }; /** * Summary of namespaces discovered via describeGlobal(). */ export type NamespaceSummary = { /** Total unique namespaces */ count: number; /** Details per namespace */ namespaces: InstalledNamespace[]; }; /** * Extended Cuneiform package info with license details. */ export type CuneiformPackageInfo = { /** Whether the package is installed */ installed: boolean; /** Package version if installed */ version?: string; /** Install date if available */ installedDate?: string; /** License expiration date if available */ licenseExpirationDate?: string; /** License status */ licenseStatus: 'Active' | 'Expired' | 'Suspended' | 'Unknown' | 'Not Installed'; }; /** * Org type classification. */ export type OrgType = 'Production' | 'Sandbox' | 'Scratch' | 'Developer' | 'Trial'; /** * Outcome type for business process behavioral flags. */ export type BusinessProcessOutcomeType = 'win-loss' | 'closure' | 'conversion' | 'review'; /** * A business process with its associated record types. */ export type BusinessProcessInfo = { /** Process name */ name: string; /** Object API name (Opportunity, Case, Lead, Solution) */ objectName: string; /** Whether the process is active */ isActive: boolean; /** Description if provided */ description?: string; /** Number of record types using this process */ recordTypeCount: number; /** Record type names using this process */ recordTypeNames: string[]; }; /** * Summary of business processes discovered in the org. */ export type BusinessProcessSummary = { /** Total process count */ count: number; /** Processes grouped by object */ processes: BusinessProcessInfo[]; }; /** * Configuration for OrgInfoService. */ export type IOrgInfoServiceConfig = { /** Connection facade for identity and limits */ connectionFacade: IConnectionFacade; /** SOQL query adapter for org info queries */ soqlAdapter: ISoqlQueryAdapter; /** Optional Tooling API adapter for package queries */ toolingAdapter?: IToolingApiAdapter; /** Optional REST API adapter for describeGlobal and feature detection */ restAdapter?: IRestApiAdapter; /** Optional logger for debug output */ logger?: Console; /** Optional REST client for server-side org info via ISV REST API */ restClient?: import('../adapters/rest/profiling-rest-client.js').ProfilingRestClient; }; /** * Service for aggregating org-level metadata. * * Provides methods to retrieve org identity, API limits, installed packages, * active namespaces, and Cuneiform version information. * * @example * ```typescript * const service = new OrgInfoService({ * connectionFacade, * soqlAdapter, * toolingAdapter, // optional * }); * * const result = await service.getOrgInfo(); * if (result.success) { * console.log(`Org: ${result.data.identity.organizationName}`); * console.log(`Cuneiform: ${result.data.cuneiformVersion ?? 'Not installed'}`); * } * ``` */ export declare class OrgInfoService { private readonly connectionFacade; private readonly soqlAdapter; private readonly toolingAdapter?; private readonly restAdapter?; private readonly logger?; private readonly restClient?; constructor(config: IOrgInfoServiceConfig); /** * Classifies the org type based on Organization record data. * * @param isSandbox - Whether the org is marked as sandbox * @param orgType - The OrganizationType field value * @param trialExpirationDate - Trial expiration date if set * @returns The classified OrgType */ static classifyOrgType(isSandbox: boolean | undefined, orgType: string | undefined, trialExpirationDate?: string): OrgType; /** * Transforms raw limits response to OrgLimit format. * * Calculates percentage utilization from Max and Remaining values. * Returns 0% if Max is 0 to avoid division by zero. */ private static transformLimit; /** * Maps InstalledPackageRecord to InstalledPackage. * * Formats version as semantic version string (Major.Minor.Patch.Build) * and looks up human-readable product name by namespace prefix. */ private static mapPackage; /** * Extracts active namespaces from installed packages. */ private static extractActiveNamespaces; /** * Finds the Cuneiform installed package by namespace + identity signal. * * Disambiguates orgs where multiple records share `NamespacePrefix='pnova'` (CLI-3416, CLI-3417). * Matches namespace + `name === NAMESPACE_PRODUCT_MAP[CUNEIFORM_NAMESPACE]` ('Cuneiform for CRM'). * When no candidate carries the canonical product name, deterministically picks the first * namespace candidate — ensures the helper never returns `undefined` when at least one * `pnova`-namespace package exists, while remaining a pure transformation. */ private static findCuneiformPackage; /** * Finds the Cuneiform package version from installed packages. */ private static findCuneiformVersion; /** * Detects additional cloud products from PermissionSetLicense data. */ private static detectPslClouds; /** * Groups objects by namespace from global describe result. * * Extracts namespace prefixes from custom object names matching pattern: * Namespace__ObjectName__c * * @param globalDescribe - The global describe result * @returns Map of namespace prefix to object names and count */ private static groupNamespaceObjects; /** * Builds the SOQL string for a LIMIT 0 field-existence probe, sanitizing both * identifiers via sanitizeSoqlIdentifier. Returns null when either identifier * is invalid — callers MUST treat null as "field cannot exist" without invoking * the SOQL adapter. * * Pure, side-effect-free, and exposed at the class level so the sanitizer * hardening introduced in CLI-3174 can be unit-tested directly. */ private static buildProbeQuery; /** * Retrieves complete org information. * * Executes multiple API calls in parallel for performance: * - Identity + Organization SOQL (getOrgIdentity) * - Limits API (getOrgLimits) * - Installed packages via Tooling API (getInstalledPackages) * * @returns ServiceResult containing complete OrgInfo */ getOrgInfo(): Promise>; /** * Retrieves org identity information. * * Combines connection.identity() with Organization SOQL query for complete info. * * @returns ServiceResult containing OrgIdentity */ getOrgIdentity(): Promise>; /** * Retrieves org API limits. * * Calls /limits REST endpoint and transforms to OrgLimits format. * * @returns ServiceResult containing OrgLimits */ getOrgLimits(): Promise>; /** * Retrieves installed packages via Tooling API. * * If Tooling adapter is not provided, returns empty array with warning. * * @returns ServiceResult containing array of InstalledPackage */ getInstalledPackages(): Promise>; /** * Retrieves active namespaces from installed packages. * * @returns ServiceResult containing array of namespace strings */ getActiveNamespaces(): Promise>; /** * Retrieves the Cuneiform version if installed. * * @returns ServiceResult containing version string or null if not installed */ getCuneiformVersion(): Promise>; /** * Fetches the describeGlobal result from the REST adapter. * * Used by OrgDetailsOperation to cache the result across multiple service calls, * avoiding redundant API round-trips for features, clouds, and namespace detection. * * @returns ServiceResult containing DescribeGlobalResult */ fetchDescribeGlobal(): Promise>; /** * Detects org features via object/field probing. * * Checks for: * - Multi-Currency: CurrencyType object exists in describeGlobal * - Enhanced Notes: ContentNote object is queryable * - State/Country Picklists: BillingCountryCode field exists on Account * - Territory Management: Territory2 object exists * - Person Accounts: IsPersonAccount field exists on Account * - Digital Experiences: Network object exists in describeGlobal * - Einstein AI: AIRecordInsight object exists in describeGlobal * * @param cachedGlobal - Optional pre-fetched describeGlobal result to avoid redundant API calls * @returns ServiceResult containing OrgFeatures */ getOrgFeatures(cachedGlobal?: ServiceResult): Promise>; /** * Retrieves license information from UserLicense and PermissionSetLicense. * * @returns ServiceResult containing array of LicenseInfo */ getLicenseInfo(): Promise>; /** * Detects installed Salesforce Clouds (Sales Cloud, Service Cloud, Industry Clouds). * * Combines license-based detection (Sales/Service Cloud via UserLicense) with * package-based detection (Industry Clouds via namespace lookup). * * @param cachedGlobal - Optional pre-fetched describeGlobal result to avoid redundant API calls * @returns ServiceResult containing array of DetectedCloud */ getDetectedClouds(cachedGlobal?: ServiceResult, cachedPackages?: ServiceResult): Promise>; /** * Discovers installed namespaces via describeGlobal() with object counts. * * Extracts unique namespaces from custom object names (pattern: Namespace__ObjectName__c) * and counts objects per namespace. Maps known namespaces to product names. * * @param cachedGlobal - Optional pre-fetched describeGlobal result to avoid redundant API calls * @returns ServiceResult containing NamespaceSummary */ getInstalledNamespaces(cachedGlobal?: ServiceResult): Promise>; /** * Retrieves enhanced Cuneiform package information with license details. * * @returns ServiceResult containing CuneiformPackageInfo */ getCuneiformPackageInfo(cachedPackages?: ServiceResult): Promise>; /** * Retrieves business processes with associated record type counts. * * Queries BusinessProcess records and enriches each with the count and names * of RecordTypes linked via BusinessProcessId. If no business processes exist, * the RecordType query is skipped entirely. * * @returns ServiceResult containing BusinessProcessSummary */ getBusinessProcesses(): Promise>; /** * Delegates org info retrieval to the ISV REST API. * * @param startTime - Timestamp for duration tracking * @returns ServiceResult containing OrgInfo from REST endpoint */ private getOrgInfoViaRest; /** * Detects whether the Cuneiform namespace exists by querying a known CMT object. * * CLI-1569: InstalledSubscriberPackage is empty in scratch orgs where packages are * deployed as source metadata. This method queries pnova__Active_Configuration__mdt * as a lightweight namespace presence check — if the query succeeds (even with 0 * records), the namespace exists. If it fails with "sObject type not supported", * the namespace is absent. * * @returns true if the pnova namespace is present, false otherwise */ private detectCuneiformNamespace; /** * Probes whether a field exists on an object using a LIMIT 0 SOQL query. * Returns true if the query succeeds (field exists), false if it fails or the * sanitizer rejects the inputs. Much lighter than a full describe. * * **Input source contract**: callers must pass Salesforce API names already * classified as safe (hardcoded constants, describeGlobal results, or upstream * validated values). The sanitizer in buildProbeQuery defends against malformed * identifiers as a last line of defence, but this method is NOT a user-input * gateway — never wire it directly to a flag value or untrusted external input. * * Today's only call sites pass hardcoded constants from getOrgFeatures() * (Account / BillingCountryCode / IsPersonAccount). The sanitizer is in place * defensively so a future caller cannot accidentally introduce SOQL injection. */ private probeFieldExists; /** * Detects Sales Cloud and Service Cloud from license data and global describe. * * Uses license patterns to find Sales/Service Cloud licenses, then confirms * by checking for Opportunity (Sales) and Case (Service) objects in the org. */ private detectLicenseClouds; /** * Resolves whether Opportunity and Case objects exist in the org. * Uses cached global describe if available, otherwise fetches fresh. */ private resolveCloudObjects; }