import type { IConnectionFacade, DescribeSObjectResult, DescribeGlobalResult, HttpRequest } from '../connection-facade.js'; import type { ServiceResult } from '../../models/service-result.js'; import type { RestApiAdapterConfig } from './types.js'; /** * Result type for successful record insert operations. */ export type InsertRecordResult = { /** The ID of the newly created record */ id: string; }; /** * Result type for successful record update operations. */ export type UpdateRecordResult = { /** The ID of the updated record */ id: string; /** Whether the update succeeded */ success: boolean; }; /** * Result type for successful record delete operations. */ export type DeleteRecordResult = { /** The ID of the deleted record */ id: string; /** Whether the delete succeeded */ success: boolean; }; /** * Interface for REST API operations. * * Provides methods for describing Salesforce objects with caching support, * and writing sObject records. */ export type IRestApiAdapter = { /** * Describes a single Salesforce object. * * @param objectName - The API name of the object * @returns ServiceResult containing the describe result */ describeObject(objectName: string): Promise>; /** * Retrieves global describe information for all accessible objects. * * @returns ServiceResult containing the global describe result */ describeGlobal(): Promise>; /** * Gets the record count for a Salesforce object. * * @param objectName - The API name of the object * @returns ServiceResult containing the record count */ getRecordCount(objectName: string): Promise>; /** * Gets record counts for multiple Salesforce objects using the bulk REST endpoint. * * Uses `GET /limits/recordCount?sObjects=...` for a single API call instead of * N individual SOQL COUNT() queries. Falls back to per-object SOQL queries if * the REST endpoint fails. Cached counts are resolved first; only uncached * objects are included in the REST call. * * @param objectNames - Array of object API names * @returns ServiceResult containing Map of object name to count * * @example * ```typescript * const result = await adapter.getRecordCounts(['Account', 'Contact', 'Lead']); * if (result.success) { * for (const [name, count] of result.data) { * console.log(`${name}: ${count} records`); * } * } * ``` */ getRecordCounts(objectNames: string[]): Promise>>; /** * Describes multiple Salesforce objects. * * @param objectNames - Array of object API names * @returns ServiceResult containing a map of object names to describe results */ describeObjects(objectNames: string[]): Promise>>; /** * Inserts a new record into a Salesforce object. * * Uses the REST API POST /sobjects/{objectName} endpoint. * * @param objectName - The API name of the object (e.g., 'Account', 'PermissionSetAssignment') * @param record - The field values for the new record * @returns ServiceResult containing the created record's ID * * @example * ```typescript * // Insert a PermissionSetAssignment record * const result = await adapter.insertRecord('PermissionSetAssignment', { * AssigneeId: '005xx000001234567', * PermissionSetId: '0PS000000000001' * }); * if (result.success) { * console.log(`Created record: ${result.data.id}`); * } * ``` */ insertRecord(objectName: string, record: Record): Promise>; /** * Updates an existing record in a Salesforce object. * * Uses the REST API PATCH /sobjects/{objectName}/{recordId} endpoint. * * @param objectName - The API name of the object (must use pnova__ namespace, e.g., 'pnova__Profiling_Definition__c') * @param recordId - The ID of the record to update * @param record - The field values to update * @returns ServiceResult containing the update result with record ID and success status * * @example * ```typescript * // Update a Cuneiform record * const result = await adapter.updateRecord('pnova__Profiling_Definition__c', '001xx000003DKMPAA4', { * pnova__Prop_Name__c: 'Updated Definition', * }); * if (result.success) { * console.log(`Updated record: ${result.data.id}`); * } * ``` */ updateRecord(objectName: string, recordId: string, record: Record): Promise>; /** * Deletes a record from a Salesforce object. * * Uses the REST API DELETE /sobjects/{objectName}/{recordId} endpoint. * * @param objectName - The API name of the object (must use pnova__ namespace, e.g., 'pnova__Profiling_Summary__c') * @param recordId - The ID of the record to delete * @returns ServiceResult containing the delete result with record ID and success status * * @example * ```typescript * // Delete a Cuneiform record * const result = await adapter.deleteRecord('pnova__Profiling_Summary__c', '003xx000004TMJQAA4'); * if (result.success) { * console.log(`Deleted record: ${result.data.id}`); * } * ``` */ deleteRecord(objectName: string, recordId: string): Promise>; /** * Makes a generic HTTP request to a custom Salesforce REST endpoint. * * Supports custom Apex REST endpoints (e.g., `/services/apexrest/pnova/v1/...`) * and any other Salesforce REST API path. Unlike the typed methods above, * this provides a pass-through for arbitrary request/response shapes. * * @param request - The HTTP request configuration (url, method, body, headers) * @returns ServiceResult containing the deserialized response of type T * * @example * ```typescript * // Call a custom Apex REST endpoint * const result = await adapter.request<{ success: boolean; results: Array<{ id: string }> }>({ * url: '/services/apexrest/pnova/v1/profiling/requests', * method: 'POST', * body: JSON.stringify({ id: definitionId }), * headers: { 'content-type': 'application/json' }, * }); * ``` */ request(request: HttpRequest): Promise>; /** * Invalidates cached entries. * * @param pattern - Optional pattern to match keys (supports * wildcard). If not provided, clears all entries. */ invalidateCache(pattern?: string): void; }; /** * Adapter for REST API operations against Salesforce. * * Provides caching for describe operations to reduce API calls. * * @example * ```typescript * const adapter = new RestApiAdapter(connectionFacade); * * // Describe an object (cached) * const result = await adapter.describeObject('Account'); * * // Get global describe (cached) * const global = await adapter.describeGlobal(); * * // Describe multiple objects * const describes = await adapter.describeObjects(['Account', 'Contact', 'Opportunity']); * * // Invalidate cache * adapter.invalidateCache('describe:Account'); // Single object * adapter.invalidateCache('describe:*'); // All describes * adapter.invalidateCache(); // Everything * ``` */ export declare class RestApiAdapter implements IRestApiAdapter { private readonly connection; private readonly logger?; private readonly cache; private readonly cacheEnabled; private readonly lifecycle; private readonly retryConfig?; private readonly recordCountBatchSize; /** * Creates a new RestApiAdapter. * * @param connection - The connection facade to use for API calls * @param config - Optional configuration */ constructor(connection: IConnectionFacade, config?: RestApiAdapterConfig); /** * Validates that a write operation targets a permitted object. * * Only objects with the pnova__ namespace prefix are allowed for all write operations. * A small set of standard objects (WRITE_ALLOWLIST) are allowed for specific operations. * * @param objectName - The API name of the target object * @param operation - The write operation type * @returns undefined if allowed, or an error code and message if blocked */ private static validateWriteNamespace; /** * Describes a single Salesforce object. * * @param objectName - The API name of the object * @returns ServiceResult containing the describe result */ describeObject(objectName: string): Promise>; /** * Retrieves global describe information for all accessible objects. * * @returns ServiceResult containing the global describe result */ describeGlobal(): Promise>; /** * Gets the record count for a Salesforce object. * * @param objectName - The API name of the object * @returns ServiceResult containing the record count */ getRecordCount(objectName: string): Promise>; /** * Describes multiple Salesforce objects. * * @param objectNames - Array of object API names * @returns ServiceResult containing a map of object names to describe results */ describeObjects(objectNames: string[]): Promise>>; /** * Gets record counts for multiple Salesforce objects using the bulk REST endpoint. * * Uses `GET /limits/recordCount?sObjects=A,B,C` for a single API call instead of * N individual SOQL `SELECT COUNT()` queries. Falls back to per-object SOQL queries * if the REST endpoint is unavailable or fails. * * Counts from the bulk response are cached individually so subsequent * `getRecordCount()` calls benefit from the same cache. * * @param objectNames - Array of object API names * @returns ServiceResult containing Map of object name to count */ getRecordCounts(objectNames: string[]): Promise>>; /** * Inserts a new record into a Salesforce object. * * Uses the REST API POST /sobjects/{objectName} endpoint to create a record. * Does not use caching since write operations should always execute. * * @param objectName - The API name of the object (e.g., 'Account', 'PermissionSetAssignment') * @param record - The field values for the new record * @returns ServiceResult containing the created record's ID * * @example * ```typescript * const result = await adapter.insertRecord('PermissionSetAssignment', { * AssigneeId: '005xx000001234567', * PermissionSetId: '0PS000000000001' * }); * ``` */ insertRecord(objectName: string, record: Record): Promise>; /** * Updates an existing record in a Salesforce object. * * Uses the REST API PATCH /sobjects/{objectName}/{recordId} endpoint to update a record. * Does not use caching since write operations should always execute. * * @param objectName - The API name of the object (e.g., 'Account', 'Contact') * @param recordId - The ID of the record to update * @param record - The field values to update * @returns ServiceResult containing the update result with record ID and success status */ updateRecord(objectName: string, recordId: string, record: Record): Promise>; /** * Deletes a record from a Salesforce object. * * Uses the REST API DELETE /sobjects/{objectName}/{recordId} endpoint to delete a record. * Does not use caching since write operations should always execute. * * @param objectName - The API name of the object (e.g., 'Account', 'Contact') * @param recordId - The ID of the record to delete * @returns ServiceResult containing the delete result with record ID and success status */ deleteRecord(objectName: string, recordId: string): Promise>; /** * Makes a generic HTTP request to a custom Salesforce REST endpoint. * * Supports custom Apex REST endpoints and any other Salesforce REST API path. * Does not use caching since custom endpoints have unpredictable response shapes. * * The namespace guard is enforced for write operations (POST, PATCH, PUT, DELETE) that * target sObject endpoints (`/sobjects/{objectName}`). Custom Apex REST endpoints * (e.g., `/services/apexrest/pnova/v1/...`) and GET requests are not affected. * * @param httpRequest - The HTTP request configuration * @returns ServiceResult containing the deserialized response of type T */ request(httpRequest: HttpRequest): Promise>; /** * Invalidates cached entries. * * @param pattern - Optional pattern to match keys (supports * wildcard). If not provided, clears all entries. */ invalidateCache(pattern?: string): void; /** * Fetches record counts via the bulk REST endpoint `/limits/recordCount`. * * @param objectNames - Object API names to fetch counts for * @param results - Map to populate with counts * @returns true if the REST endpoint succeeded, false if it failed */ private fetchRecordCountsViaRest; /** * Fallback: fetches record counts via individual SOQL COUNT() queries. * * @param objectNames - Object API names to fetch counts for * @param results - Map to populate with counts */ private fetchRecordCountsViaSoql; /** * Handles DML insert errors and maps them to appropriate error codes. * * @param error - The caught error * @param startTime - The operation start time for duration calculation * @param emptyResult - The empty result to return on failure * @param context - Context for logging (object name) * @returns ServiceResult with success=false and mapped error code */ private handleInsertError; /** * Handles DML update errors and maps them to appropriate error codes. * * @param error - The caught error * @param startTime - The operation start time for duration calculation * @param emptyResult - The empty result to return on failure * @param context - Context for logging (object name/record ID) * @returns ServiceResult with success=false and mapped error code */ private handleUpdateError; /** * Handles DML delete errors and maps them to appropriate error codes. * * @param error - The caught error * @param startTime - The operation start time for duration calculation * @param emptyResult - The empty result to return on failure * @param context - Context for logging (object name/record ID) * @returns ServiceResult with success=false and mapped error code */ private handleDeleteError; /** * Handles errors and maps them to appropriate error codes. * * Accepts SfError, jsforce errors, or unknown error types. Extracts Salesforce * error codes when available and maps them to adapter-specific error codes. * * @param error - The caught error (SfError, jsforce error, or unknown) * @param startTime - The operation start time for duration calculation * @param emptyResult - The empty result to return on failure * @param context - Context for logging (e.g., "Account", "global describe") * @param defaultErrorCode - The default error code if no SF error code is found * @returns ServiceResult with success=false and mapped error code */ private handleError; }