import type { ISoqlQueryAdapter } from '../adapters/soql/soql-query-adapter.js'; import type { IRestApiAdapter } from '../adapters/rest/rest-api-adapter.js'; import type { ServiceResult } from '../models/service-result.js'; /** * Valid profiling definition types. * Derived from boolean flags - no explicit field exists in Salesforce. */ export type DefinitionType = 'metadata' | 'historical' | 'comparative'; /** * Valid profiling status values. * Sourced from Agg_Status__c field. */ export type ProfilingStatus = 'NOT PROFILED' | 'IN PROGRESS' | 'SUCCESS' | 'COMPLETE w/ FAILURES' | 'ERROR'; /** * Represents a Cuneiform profiling definition with mapped field names. * * Field mappings are based on actual Salesforce schema at: * salesforce-isv/src/sfdc/base/main/dataprofiling-lib/objects/Profiling_Definition__c/ */ export type ProfilingDefinition = { /** The Salesforce record ID */ id: string; /** The unique definition key (e.g., "PD-0001") from standard Name field (AutoNumber) */ key: string; /** The definition name from Prop_Name__c, max 255 chars */ name: string; /** The target Salesforce object API name from Prop_SObjectApiName__c */ objectName: string; /** The target Salesforce object label from Prop_SObjectLabel__c */ objectLabel?: string; /** The definition type (derived from boolean flags, not stored) */ type: DefinitionType; /** The profiling status from Agg_Status__c */ status: ProfilingStatus; /** Optional category from Prop_Category__c, max 255 chars */ category?: string; /** Optional description from Prop_Description__c */ description?: string; /** Whether the definition is active from Prop_IsActive__c */ isActive: boolean; /** Whether Set A is filtered from Prop_IsFiltered_SetA__c */ isFilteredSetA: boolean; /** Whether Set B is filtered from Prop_IsFiltered_SetB__c (indicates comparative type) */ isFilteredSetB: boolean; /** Whether metadata-only profiling from PropFx_IsMetadataProfilingOnly__c (formula) */ isMetadataOnly: boolean; /** Whether profiling all records from PropFx_IsProfilingAllRecords__c (formula) */ isProfilingAllRecords: boolean; /** Whether definition has related Profiling_SObject_Results__c records from PFx_HasRelatedSummaries__c */ hasRelatedSummaries: boolean; /** Time category from Prop_TimeCategory__c (e.g., "2026 vs 2025", "Lifetime", "N/A") */ timeCategory?: string; /** Segment category from Prop_SegmentCategory__c (e.g., record type name, "Comparative", "Historical") */ segmentCategory?: string; /** Last profiled date from PFx_LastDateProfiled__c formula (ISO 8601) */ lastProfiledDate?: string; /** When the record was created (ISO 8601) */ createdDate?: string; /** When the record was last modified (ISO 8601) */ lastModifiedDate?: string; }; /** * Configuration for ProfilingDefinitionService. */ export type IProfilingDefinitionServiceConfig = { /** SOQL query adapter for executing queries */ soqlAdapter: ISoqlQueryAdapter; /** Optional REST API adapter for DML operations (create, update, delete) */ restAdapter?: IRestApiAdapter; /** Optional logger for debug output */ logger?: Console; }; /** * Input for creating a profiling definition. */ export type CreateDefinitionInput = { /** The definition name (required, max 255 chars) */ name: string; /** The target Salesforce object API name (required) */ objectName: string; /** The profiling method — every definition has exactly one method (no 'all'; expansion happens at the operation layer). * 'outcome' is the honest wire-level tag for outcome candidates produced by buildOutcomeCandidateInputs and * buildOutcomeInput. Prior to CLI-3375 these were tagged 'metadata', forcing client-side and apex-side defensive * guards to use filterJson presence as a proxy for "is this really metadata-only?". Honest tagging keeps the * three other methods semantically pure and unblocks the apex-side invariant simplification (ISV-7873). */ method: 'metadata' | 'historical' | 'comparative' | 'outcome'; /** The comparative year — required when method is 'comparative', undefined otherwise */ comparativeYear?: number; /** The historical year — set for year-specific historical definitions (e.g., 2026, 2025). Undefined means "Lifetime". */ historicalYear?: number; /** When true, comparative SetB uses all records before the year (unbounded). When false/undefined, SetB uses previous year only. */ usePrior?: boolean; /** When true, comparative variant is "lifetime vs X": SetA unfiltered, SetB carries the supplied secondary filter. */ lifetimePrimary?: boolean; /** Optional category for grouping (max 255 chars) */ category?: string; /** Optional description */ description?: string; /** Whether the definition is active (default: true) */ isActive?: boolean; /** Whether to exclude value frequency (CRc) from insight groups (default: false) */ noValueFrequency?: boolean; /** Record type name — present only for recordtype method definitions */ recordTypeName?: string; /** The object label (used for server-side name generation via GlobalProfilingService) */ objectLabel?: string; /** The full namespace-qualified object API name (defaults to objectName) */ objectFullApiName?: string; /** Let the server discover profileable fields (default: false) */ discoverFields?: boolean; /** Skip creation if a definition with the same resolved name already exists (default: false) */ skipExisting?: boolean; /** Time category for the definition (e.g., 'Lifetime', '2025') */ timeCategory?: string; /** Segment category for the definition (e.g., 'Historical', 'Comparative', record type name) */ segmentCategory?: string; /** Filter JSON for SetA/SetB population filtering (e.g., outcome-based comparisons). Passed directly to GlobalProfilingService. */ filterJson?: string; }; /** * Result of a failed definition creation. */ export type CreateDefinitionFailure = { /** The input that failed */ input: CreateDefinitionInput; /** The error message */ error: string; /** The error code */ errorCode: string; }; /** * Result of bulk definition creation. */ export type CreateDefinitionsResult = { /** Successfully created definitions */ succeeded: ProfilingDefinition[]; /** Failed creation attempts */ failed: CreateDefinitionFailure[]; /** Field counts per definition ID (from ISV response, not stored in DB) */ fieldCounts?: Record; }; /** * Partial attributes that can be updated on a profiling definition. */ export type UpdateAttributesInput = { /** New definition name (max 255 chars, pnova__Prop_Name__c) */ name?: string; /** New category (max 255 chars, pnova__Prop_Category__c) */ category?: string; /** New time category (max 255 chars, pnova__Prop_TimeCategory__c) */ timeCategory?: string; /** New segment category (max 255 chars, pnova__Prop_SegmentCategory__c) */ segmentCategory?: string; /** New description (max 8192 chars, pnova__Prop_Description__c) */ description?: string; }; /** * Result of updating definition attributes. */ export type UpdateAttributesResult = { /** The Salesforce record ID of the updated definition */ id: string; /** Whether any attributes were updated */ updated: boolean; /** Names of the attributes that were written */ updatedAttributes: string[]; }; /** * Reason why a definition was skipped during deletion. */ export type DeleteSkipReason = { /** The ID of the skipped definition */ id: string; /** The definition name (for display) */ name?: string; /** The reason for skipping */ reason: string; /** The current status if skipped due to status */ status?: string; }; /** * Information about a failed deletion. */ export type DeleteFailure = { /** The ID of the definition that failed to delete */ id: string; /** The definition name (for display) */ name?: string; /** The error message */ error: string; }; /** * Progress event emitted per-definition during deletion. */ export type DeleteProgressEvent = { /** The definition ID */ id: string; /** The definition key (e.g., PD-0001) */ key?: string; /** The definition name */ name: string; /** The target object API name */ objectName?: string; /** The target object label */ objectLabel?: string; /** Definition category */ category?: string; /** Time category */ timeCategory?: string; /** Segment category */ segmentCategory?: string; /** Profiling status */ profilingStatus?: string; /** The outcome of this definition */ status: 'deleted' | 'skipped' | 'failed'; /** Reason for skip or failure */ reason?: string; /** Current progress index (1-based) */ current: number; /** Total definitions to process */ total: number; }; /** * Options for the deleteDefinitions method. */ export type DeleteDefinitionsOptions = { /** Optional progress callback invoked per-definition */ onProgress?: (event: DeleteProgressEvent) => void; }; /** * Result of bulk definition deletion. */ export type DeleteDefinitionsResult = { /** Count of successfully deleted definitions */ deleted: number; /** Count of definitions skipped (wrong status or has summaries) */ skipped: number; /** Count of definitions that failed to delete */ failed: number; /** Details about why each definition was skipped */ skipReasons: DeleteSkipReason[]; /** Details about each deletion failure */ failures: DeleteFailure[]; }; /** * Filter and pagination options for querying profiling definitions. */ export type GetDefinitionsOptions = { /** Filter by definition type (metadata, historical, comparative) - filtered in SOQL via underlying boolean fields */ type?: DefinitionType; /** Filter by profiling status (NOT PROFILED, IN PROGRESS, SUCCESS, etc.) */ status?: ProfilingStatus; /** Filter by target object API name */ objectName?: string; /** Filter by multiple target object API names (IN clause) — takes precedence over objectName */ objectNames?: string[]; /** Filter by definition name pattern using SOQL LIKE (e.g., 'Account%' matches names starting with Account) */ namePattern?: string; /** Filter by definition category (e.g., "Baseline", "Metadata", "Record Types") */ category?: string; /** Filter by time category (e.g., "Lifetime", "2026 vs 2025", "This Year") */ timeCategory?: string; /** Filter by segment category (e.g., "Historical", "Comparative", "Enterprise Sales") */ segmentCategory?: string; /** Filter by active status */ isActive?: boolean; /** Filter by filtered Set B status (for comparative definitions) */ isFilteredSetB?: boolean; /** * Filter by SObject namespace prefix (e.g., 'pnova' or 'pnova__' → matches 'pnova__*' API names). * Applied server-side via SOQL LIKE on pnova__Prop_SObjectApiName__c so it composes with `limit` correctly (CLI-3430). */ namespace?: string; /** Maximum number of records to return (1-200, default 50) */ limit?: number; /** Number of records to skip for pagination */ offset?: number; }; /** * Filter options for listing profiling definitions. */ export type DefinitionListFilter = { /** Filter by active status: 'active' (isActive=true), 'inactive' (isActive=false), 'all' (no filter) */ status?: 'active' | 'inactive' | 'all'; /** Filter by profiling method (derived from boolean flags) */ method?: 'metadata' | 'historical' | 'comparative'; /** Filter by target object API names (IN clause) */ objects?: string[]; /** Filter by definition name pattern (SOQL LIKE, e.g., 'Account%') */ pattern?: string; /** Filter by category */ category?: string; /** Filter by namespace prefix on object name (LIKE 'namespace__%') */ namespace?: string; }; /** * Pagination options for listing profiling definitions. */ export type DefinitionListPagination = { /** Maximum number of records to return (1-200, default 25) */ limit?: number; /** Number of records to skip (default 0) */ offset?: number; }; /** * Sort options for listing profiling definitions. */ export type DefinitionListSort = { /** Field to sort by */ field: 'key' | 'name' | 'objectName' | 'lastProfiledDate'; /** Sort direction (default 'asc') */ direction?: 'asc' | 'desc'; }; /** * Combined options for the listDefinitions method. */ export type DefinitionListOptions = { /** Filter criteria */ filter?: DefinitionListFilter; /** Pagination parameters */ pagination?: DefinitionListPagination; /** Sort specification */ sort?: DefinitionListSort; }; /** * Aggregate summary of definitions matching the filter set, computed * server-side via SOQL GROUP BY. Bucket counts always reflect the full * filtered set, never the paginated page. * * Status taxonomy (preserved from prior in-memory display logic): * profiledCount covers SUCCESS and COMPLETE w/ FAILURES; notProfiledCount * covers NOT PROFILED and ERROR; IN PROGRESS and null statuses contribute * to totalCount and active/inactive split but not to profiled/notProfiled. */ export type ListSummary = { /** Definitions where Prop_IsActive__c is TRUE */ activeCount: number; /** Definitions where Prop_IsActive__c is FALSE */ inactiveCount: number; /** Definitions with status SUCCESS or COMPLETE w/ FAILURES */ profiledCount: number; /** Definitions with status NOT PROFILED or ERROR */ notProfiledCount: number; }; /** * Result of listing profiling definitions. */ export type DefinitionListResult = { /** The definitions matching the query (paginated) */ definitions: ProfilingDefinition[]; /** Total count of definitions matching the filters (ignoring pagination) */ totalCount: number; /** Aggregate bucket counts across the full filtered set (ignoring pagination) */ summary: ListSummary; }; export declare class ProfilingDefinitionService { private readonly soqlAdapter; private readonly restAdapter?; private readonly logger?; constructor(config: IProfilingDefinitionServiceConfig); /** * Derives the definition type from boolean flags. * * Logic: * - comparative: isFilteredSetB is true (comparing two data sets) * - metadata: isMetadataOnly is true (metadata-only profiling) * - historical: default (standard profiling) * * @param isFilteredSetB - Whether Set B is filtered * @param isMetadataOnly - Whether metadata-only profiling * @returns The derived definition type */ private static deriveDefinitionType; /** * Maps a raw Salesforce Profiling_Definition__c record to a ProfilingDefinition. * * @param record - The raw SOQL query result record * @returns Mapped ProfilingDefinition with camelCase field names */ private static mapToProfilingDefinition; /** * Builds a complete SOQL query string for Profiling_Definition__c using CuneiformQueryBuilder. * * Type filtering is pushed to SOQL using the underlying boolean fields: * - comparative: isFilteredSetB = true * - metadata: isMetadataOnly = true AND isFilteredSetB = false * - historical: isMetadataOnly = false AND isFilteredSetB = false * * @param options - The filter and pagination options * @returns Complete SOQL query string */ private static buildSoqlQuery; private static validateType; private static validateStatus; private static buildCreateDefinitionRequestBody; private static buildListWhereConditions; private static applyWhereConditions; /** * Folds aggregate rows from the GROUP BY query into a totalCount and named * bucket counts. Status taxonomy preserves the prior in-memory display logic: * profiledCount covers SUCCESS and COMPLETE w/ FAILURES; notProfiledCount * covers NOT PROFILED and ERROR; IN PROGRESS and null statuses contribute to * totalCount and the active/inactive split but are excluded from * profiled/notProfiled. */ private static foldAggregateRows; /** * Retrieves profiling definitions with optional filtering and pagination. * * Note: Type filtering is applied client-side after the query since the type * is derived from boolean flags (Prop_IsFiltered_SetB__c, PropFx_IsMetadataProfilingOnly__c). * * @param options - Filter and pagination options * @returns ServiceResult containing an array of ProfilingDefinition records */ getDefinitions(options?: GetDefinitionsOptions): Promise>; /** * Retrieves all profiling definitions matching the given filters, with automatic pagination. * * Fetches pages of MAX_PAGINATION_LIMIT (200) until a short page is returned. * The limit and offset options are ignored — pagination is handled internally. * * @param options - Filter options (limit/offset are overridden) * @returns ServiceResult containing all matching ProfilingDefinition records */ getAllDefinitions(options?: Omit): Promise>; /** * Lists profiling definitions with filtering, pagination, and sorting. * * Executes two SOQL queries: * * - An aggregate GROUP BY query over the full filter set (no pagination) that * yields totalCount and per-status / per-active bucket counts in one round-trip. * - A data query with SELECT, WHERE, ORDER BY, LIMIT, OFFSET for the page. * * @param options - Filter, pagination, and sort options * @returns ServiceResult containing definitions, totalCount, and aggregate summary */ listDefinitions(options: DefinitionListOptions): Promise>; /** * Retrieves a single profiling definition by its Salesforce ID. * * @param id - The Salesforce record ID (15 or 18 characters) * @returns ServiceResult containing the ProfilingDefinition or null if not found */ getDefinitionById(id: string): Promise>; /** * Retrieves a single profiling definition by its definition key (e.g., "PD-0001"). * * The key is stored in the standard Name field as an AutoNumber. * * @param key - The definition key (e.g., "PD-0001") * @returns ServiceResult containing the ProfilingDefinition or null if not found */ getDefinitionByKey(key: string): Promise>; /** * Retrieves multiple profiling definitions by their human-readable keys in a single SOQL query. * * Replaces the N+1 pattern of calling getDefinitionByKey() per key with a single * `WHERE Name IN (...)` query. Returns a Map keyed by the requested key values. * * @param keys - Array of definition keys (e.g., ['PD-0001', 'PD-0002']) * @returns ServiceResult containing a Map of key → ProfilingDefinition (missing keys have null) */ getDefinitionsByKeys(keys: string[]): Promise>>; /** * Retrieves profiling definitions filtered by type. * * Convenience method that delegates to getDefinitions() with the type filter. * * @param type - The definition type to filter by * @param options - Additional filter and pagination options * @returns ServiceResult containing an array of ProfilingDefinition records */ getDefinitionsByType(type: DefinitionType, options?: Omit): Promise>; /** * Retrieves profiling definitions filtered by status. * * Convenience method that delegates to getDefinitions() with the status filter. * * @param status - The profiling status to filter by * @param options - Additional filter and pagination options * @returns ServiceResult containing an array of ProfilingDefinition records */ getDefinitionsByStatus(status: ProfilingStatus, options?: Omit): Promise>; /** * Creates a new profiling definition via the Cuneiform REST API. * * Definition creation is routed through the REST endpoint which enforces * server-side permission checks in GlobalProfilingService. * * @param input - The definition input containing name, objectName, and optional fields * @returns ServiceResult containing the created ProfilingDefinition or null on failure */ createDefinition(input: CreateDefinitionInput): Promise>; /** * Creates multiple profiling definitions via the Cuneiform REST API. * * Each definition is created individually since the REST endpoint * currently supports single creation per request. * * @param inputs - Array of definition inputs (1-10 items) * @returns ServiceResult containing succeeded and failed arrays */ createDefinitions(inputs: CreateDefinitionInput[]): Promise>; /** * Updates metadata attributes on a profiling definition. * * Writes only the supplied attributes; untouched columns are preserved. * At least one attribute must be provided. * * @param definitionId - The Salesforce record ID of the definition to update * @param attributes - Partial attribute set to write * @returns ServiceResult containing the update result */ updateAttributes(definitionId: string, attributes: UpdateAttributesInput): Promise>; /** * Deletes profiling definitions by their Salesforce IDs. * * Safety checks are applied before deletion: * - Definition must exist * - Definition must have no associated summaries (hasRelatedSummaries field) * * Definitions can be deleted regardless of profiling status when no * associated summaries exist. * * Definitions that fail safety checks are skipped with reasons provided. * * @param ids - Array of Salesforce record IDs to delete (1-200 items) * @param options - Optional progress callback and configuration * @returns ServiceResult containing deletion counts and skip/failure details */ deleteDefinitions(ids: string[], options?: DeleteDefinitionsOptions): Promise>; /** * Creates a single definition via the Cuneiform REST API. */ private createDefinitionViaRest; /** * Creates multiple definitions via the Cuneiform REST API. * * The REST endpoint supports single creation, so definitions are created sequentially. */ private createDefinitionsViaRest; }