# OmniStudio Dependency Patterns ## Overview OmniStudio components form a directed graph where each component type can reference others. Dependencies are not stored in lookup fields — they are embedded in JSON configuration fields (`PropertySetConfig`, `Definition`, `InputObjectName`/`OutputObjectName`). Extracting dependencies requires parsing these JSON structures. --- ## Dependency Direction Summary ``` OmniScript ──→ Integration Procedure (via IP Action element) OmniScript ──→ Data Mapper (via DataRaptor Action element) OmniScript ──→ OmniScript (via embedded OmniScript element) OmniScript ──→ Apex Class (via Remote Action element) OmniScript ──→ LWC (via Custom Lightning Web Component element) OmniScript ──→ HTTP Endpoint (via HTTP Action element) Integration Procedure ──→ Data Mapper (via DataRaptor Action element) Integration Procedure ──→ Apex Class (via Remote Action element) Integration Procedure ──→ HTTP Endpoint (via HTTP Action element) Integration Procedure ──→ Integration Procedure (via nested IP Action element) Integration Procedure ──→ OmniScript (via OmniScript Action element — uncommon) FlexCard ──→ Integration Procedure (via data source configuration) FlexCard ──→ Apex Class (via Apex data source) FlexCard ──→ FlexCard (via child card reference) FlexCard ──→ OmniScript (via action configuration — launches OS) Data Mapper ──→ Salesforce Object (via InputObjectName — read) Data Mapper ──→ Salesforce Object (via OutputObjectName — write) ``` --- ## OmniScript Dependencies ### Element Types and Their Dependency Targets OmniScript elements are stored as `OmniProcessElement` records (Core) or `Element__c` records (Vlocity). Each element has a `PropertySetConfig` / `PropertySet__c` JSON field containing the configuration. #### DataRaptor Transform Action Calls a Data Mapper to extract, transform, or load data. **PropertySetConfig structure**: ```json { "Type": "DataRaptor Transform Action", "PropertySet": { "bundle": "AccountExtract", "bundleName": "AccountExtract", "dataRaptorType": "Extract" } } ``` **Extraction rule**: `PropertySet.bundle` or `PropertySet.bundleName` → resolves to an `OmniDataTransform` record by `Name`. #### DataRaptor Turbo Action High-performance variant of DataRaptor Transform Action. Same JSON structure, same extraction rule. **PropertySetConfig structure**: ```json { "Type": "DataRaptor Turbo Action", "PropertySet": { "bundle": "AccountTurboExtract", "bundleName": "AccountTurboExtract" } } ``` #### Integration Procedure Action Calls an Integration Procedure. **PropertySetConfig structure**: ```json { "Type": "Integration Procedure Action", "PropertySet": { "integrationProcedureKey": "TypeName_SubTypeName", "ipMethod": "TypeName", "ipType": "SubTypeName", "integrationProcedureVersion": 1 } } ``` **Extraction rule**: `PropertySet.integrationProcedureKey` → resolves to an `OmniProcess` record where `Type_SubType` matches and `TypeCategory = 'IntegrationProcedure'`. #### OmniScript Action (Embedded OmniScript) Embeds or launches another OmniScript. **PropertySetConfig structure**: ```json { "Type": "OmniScript", "PropertySet": { "Type": "ChildScriptType", "Sub Type": "ChildScriptSubType", "Language": "English" } } ``` **Extraction rule**: `PropertySet.Type` + `PropertySet["Sub Type"]` + `PropertySet.Language` → resolves to an `OmniProcess` where `TypeCategory = 'OmniScript'` and fields match. #### Remote Action Calls an Apex class method. **PropertySetConfig structure**: ```json { "Type": "Remote Action", "PropertySet": { "remoteClass": "MyApexClassName", "remoteMethod": "myMethodName", "remoteTimeout": 30000 } } ``` **Extraction rule**: `PropertySet.remoteClass` → Apex class name. `PropertySet.remoteMethod` → method name. Dependency is `remoteClass.remoteMethod`. #### HTTP Action Calls an external HTTP endpoint. **PropertySetConfig structure**: ```json { "Type": "HTTP Action", "PropertySet": { "httpUrl": "{externalEndpointUrl}", "httpMethod": "POST", "namedCredential": "MyNamedCredential" } } ``` **Extraction rule**: `PropertySet.httpUrl` or `PropertySet.namedCredential` → external dependency. If `namedCredential` is present, it references a Named Credential record. #### Custom Lightning Web Component Embeds an LWC inside the OmniScript. **PropertySetConfig structure**: ```json { "Type": "Custom Lightning Web Component", "PropertySet": { "lwcName": "myCustomComponent", "lwcComponentName": "c-my-custom-component" } } ``` **Extraction rule**: `PropertySet.lwcName` or `PropertySet.lwcComponentName` → LWC component reference. #### DocuSign Envelope Action Triggers a DocuSign envelope. **PropertySetConfig structure**: ```json { "Type": "DocuSign Envelope Action", "PropertySet": { "docuSignTemplateId": "template-uuid" } } ``` **Extraction rule**: `PropertySet.docuSignTemplateId` → DocuSign template (external dependency). --- ## Integration Procedure Dependencies Integration Procedures use the same element types as OmniScripts but are filtered by `TypeCategory = 'IntegrationProcedure'` (Core) or `IsIntegrationProcedure__c = true` (Vlocity). ### Element Types Available in IPs | Element Type | Available in IP | Dependency Target | |-------------|----------------|-------------------| | DataRaptor Transform Action | Yes | Data Mapper | | DataRaptor Turbo Action | Yes | Data Mapper | | Remote Action | Yes | Apex Class | | HTTP Action | Yes | External endpoint | | Integration Procedure Action | Yes (nested) | Another IP | | Matrix Action | Yes | Calculation Matrix | | Set Values | Yes | None (internal) | | Conditional Block | Yes | None (internal) | | Loop Block | Yes | None (internal) | | Response Action | Yes | None (internal) | | List Action | Yes | None (internal) | ### Nested IP Pattern An Integration Procedure can call another Integration Procedure, creating a chain: ``` IP: OrderValidation └── IP Action → IP: CustomerLookup └── DR Action → DM: CustomerExtract └── IP Action → IP: InventoryCheck └── HTTP Action → External inventory API └── DR Action → DM: OrderTransform ``` --- ## FlexCard Dependencies FlexCards store their entire configuration in the `Definition` JSON field on `OmniUiCard` (Core) or `VlocityUITemplate__c` (Vlocity). ### Data Source Parsing The `Definition` JSON contains a `dataSources` array: ```json { "dataSources": [ { "name": "AccountData", "type": "IntegrationProcedure", "value": { "key": "fetchAccountData", "inputMap": { "AccountId": "{recordId}" } } }, { "name": "DirectApex", "type": "Apex", "value": { "className": "AccountSummaryController", "methodName": "getSummary" } }, { "name": "SObjectData", "type": "SObject", "value": { "sObjectType": "Account", "fields": ["Name", "Industry", "Phone"] } } ] } ``` **Extraction rules by data source type**: | Data Source Type | JSON Path | Dependency Target | |-----------------|-----------|-------------------| | `IntegrationProcedure` | `value.key` | Integration Procedure (Type_SubType key) | | `Apex` | `value.className` | Apex Class | | `SObject` | `value.sObjectType` | Salesforce Object (direct SOQL) | | `DataRaptor` | `value.bundle` | Data Mapper | ### Child Card References FlexCards can embed other FlexCards: ```json { "children": [ { "cardName": "ContactListCard", "cardType": "childCard" } ] } ``` **Extraction rule**: `children[].cardName` → resolves to another `OmniUiCard` by Name. ### Action References FlexCard actions can launch OmniScripts: ```json { "actions": [ { "actionType": "OmniScript", "actionValue": { "type": "editAccount", "subType": "step1", "language": "English" } } ] } ``` **Extraction rule**: `actions[].actionValue.type` + `subType` + `language` → resolves to an OmniScript. --- ## Data Mapper Dependencies Data Mappers (DataRaptors) reference Salesforce objects through their items. ### Object References via Items Each `OmniDataTransformItem` (Core) or `DRMapItem__c` (Vlocity) record contains: | Field | Purpose | Dependency Type | |-------|---------|----------------| | `InputObjectName` / `InterfaceObject__c` | Source sObject for reads | sObject (read access) | | `OutputObjectName` / `TargetFieldObjectType__c` | Target sObject for writes | sObject (write access) | | `InputFieldName` / `InterfaceFieldAPIName__c` | Source field | Field-level dependency | | `OutputFieldName` / `TargetFieldAPIName__c` | Target field | Field-level dependency | ### Extract Type Data Mapper Example ``` DM: AccountExtract (Type: Extract) ├── Item 1: InputObjectName = "Account" │ InputFieldName = "Name" │ OutputFieldName = "AccountName" ├── Item 2: InputObjectName = "Account" │ InputFieldName = "Industry" │ OutputFieldName = "AccountIndustry" └── Item 3: InputObjectName = "Contact" InputFieldName = "Email" OutputFieldName = "PrimaryEmail" Dependencies: Account (read), Contact (read) ``` ### Load Type Data Mapper Example ``` DM: OrderCreate (Type: Load) ├── Item 1: OutputObjectName = "Order" │ InputFieldName = "OrderData.accountId" │ OutputFieldName = "AccountId" └── Item 2: OutputObjectName = "OrderItem" InputFieldName = "OrderData.lineItems[].productId" OutputFieldName = "Product2Id" Dependencies: Order (write), OrderItem (write) ``` ### Transform Type Data Mapper Transform type Data Mappers do not reference Salesforce objects directly — they map between data structures. They have no sObject dependencies but may participate in a chain: ``` OmniScript → DR Extract (reads Account) → DR Transform (reshapes data) → DR Load (writes CustomObj__c) ``` --- ## Circular Dependency Detection ### Why Circular Dependencies Occur Circular references happen when component A depends on component B, which directly or transitively depends back on component A. Common scenarios: 1. **OmniScript ↔ IP**: OmniScript calls IP via IP Action, IP calls back to OmniScript via OmniScript Action 2. **IP ↔ IP**: IP A calls IP B via nested IP Action, IP B calls IP A 3. **FlexCard → IP → OmniScript → FlexCard**: FlexCard sources data from IP, IP triggers OmniScript, OmniScript launches FlexCard ### Detection Algorithm ``` function detectCircularDependencies(graph): cycles = [] for each node N in graph: visited = empty set path = empty list dfs(N, visited, path, graph, cycles) return cycles function dfs(node, visited, path, graph, cycles): if node is in path: // Circular reference found cycleStart = index of node in path cycle = path[cycleStart:] + [node] cycles.append(cycle) return if node is in visited: return visited.add(node) path.append(node) for each neighbor of node in graph: dfs(neighbor, visited, path, graph, cycles) path.removeLast() ``` ### Reporting Circular References When a cycle is detected, report it clearly: ``` CIRCULAR DEPENDENCY DETECTED: OS:editAccount → IP:validateAccount → OS:editAccount Components in cycle: 1. OmniScript "editAccount" (IP Action → validateAccount) 2. Integration Procedure "validateAccount" (OmniScript Action → editAccount) Risk: Runtime infinite loop if not guarded by conditional logic. Recommendation: Review whether the back-reference is intentional and has a termination condition. ``` --- ## Dependency Graph Construction ### Step-by-Step Process ``` 1. DETECT namespace (see namespace-guide.md) 2. QUERY all container objects: - OmniProcess (OmniScripts + IPs) - OmniUiCard (FlexCards) - OmniDataTransform (Data Mappers) 3. QUERY all element objects: - OmniProcessElement (for each OmniProcess) - OmniDataTransformItem (for each OmniDataTransform) 4. PARSE each element's PropertySetConfig: - Identify element Type - Extract dependency reference per extraction rules above - Resolve reference to a known component record 5. PARSE each FlexCard's Definition: - Extract dataSources array - Extract children array - Extract actions array - Resolve references to known components 6. PARSE each Data Mapper's items: - Extract InputObjectName / OutputObjectName - Resolve to sObject names 7. BUILD directed graph: - Nodes = all components + referenced sObjects + external endpoints - Edges = dependency references with type labels 8. DETECT circular references: - Run DFS cycle detection - Record all cycles found 9. COMPUTE impact analysis: - For each node, compute transitive closure of inbound edges - "If X changes, these components are affected" ``` ### Impact Analysis: Reverse Dependency Lookup To answer "what breaks if I change Data Mapper X?", reverse the dependency direction: ``` Given: DM:AccountExtract Direct dependents (components that reference this DM): → IP:fetchAccountData (DataRaptor Action) Transitive dependents (components that reference the direct dependents): → OS:updateAccount (IP Action → fetchAccountData) → FC:AccountSummaryCard (Data Source → fetchAccountData) Full impact set: [IP:fetchAccountData, OS:updateAccount, FC:AccountSummaryCard] ``` --- ## Property Set Config Parsing Tips ### Handling Large JSON `PropertySetConfig` can exceed 100KB for complex elements. When querying via SOQL: - SOQL `SELECT` returns the full field value - For very large configs, the Tooling API may be necessary - Parse incrementally if memory is a concern ### Nested Property Sets Some elements have nested structures. Always check for: - `PropertySet.bundle` (top-level reference) - `PropertySet.elementProperties` (per-field configs) - `PropertySet.conditionalProperties` (conditional logic) - `PropertySet.remoteOptions` (additional remote action config) ### Common Pitfalls | Pitfall | Handling | |---------|---------| | `bundleName` vs `bundle` | Both may exist; prefer `bundle` as the canonical reference | | `integrationProcedureKey` format | Always `Type_SubType` with underscore separator | | Version-specific references | Some elements reference a specific version; default is latest active | | Null PropertySetConfig | Skip elements with null/empty config — they have no dependencies | | JSON parsing errors | Malformed JSON in PropertySetConfig can occur on manually edited records; catch and log |