@sfdxy/mule-lint 1.20.0 → 1.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. package/README.md +63 -17
  2. package/dist/package.json +1 -1
  3. package/dist/src/core/XPathHelper.d.ts.map +1 -1
  4. package/dist/src/core/XPathHelper.js +8 -0
  5. package/dist/src/core/XPathHelper.js.map +1 -1
  6. package/dist/src/engine/LintEngine.d.ts +22 -0
  7. package/dist/src/engine/LintEngine.d.ts.map +1 -1
  8. package/dist/src/engine/LintEngine.js +105 -18
  9. package/dist/src/engine/LintEngine.js.map +1 -1
  10. package/dist/src/mcp/prompts/index.d.ts +1 -1
  11. package/dist/src/mcp/prompts/index.d.ts.map +1 -1
  12. package/dist/src/mcp/prompts/index.js +62 -1
  13. package/dist/src/mcp/prompts/index.js.map +1 -1
  14. package/dist/src/mcp/resources/index.js +114 -16
  15. package/dist/src/mcp/resources/index.js.map +1 -1
  16. package/dist/src/mcp/tools/getRuleDetails.d.ts.map +1 -1
  17. package/dist/src/mcp/tools/getRuleDetails.js +30 -1
  18. package/dist/src/mcp/tools/getRuleDetails.js.map +1 -1
  19. package/dist/src/rules/api-led/ApikitConsoleProductionRule.d.ts +22 -0
  20. package/dist/src/rules/api-led/ApikitConsoleProductionRule.d.ts.map +1 -0
  21. package/dist/src/rules/api-led/ApikitConsoleProductionRule.js +43 -0
  22. package/dist/src/rules/api-led/ApikitConsoleProductionRule.js.map +1 -0
  23. package/dist/src/rules/api-led/ApikitMainFlowStructureRule.d.ts +24 -0
  24. package/dist/src/rules/api-led/ApikitMainFlowStructureRule.d.ts.map +1 -0
  25. package/dist/src/rules/api-led/ApikitMainFlowStructureRule.js +53 -0
  26. package/dist/src/rules/api-led/ApikitMainFlowStructureRule.js.map +1 -0
  27. package/dist/src/rules/api-led/ApikitStatusCodeVariableRule.d.ts +25 -0
  28. package/dist/src/rules/api-led/ApikitStatusCodeVariableRule.d.ts.map +1 -0
  29. package/dist/src/rules/api-led/ApikitStatusCodeVariableRule.js +59 -0
  30. package/dist/src/rules/api-led/ApikitStatusCodeVariableRule.js.map +1 -0
  31. package/dist/src/rules/connector/EventListenerNullGuardRule.d.ts +24 -0
  32. package/dist/src/rules/connector/EventListenerNullGuardRule.d.ts.map +1 -0
  33. package/dist/src/rules/connector/EventListenerNullGuardRule.js +58 -0
  34. package/dist/src/rules/connector/EventListenerNullGuardRule.js.map +1 -0
  35. package/dist/src/rules/connector/ReplayChannelConfigRule.d.ts +23 -0
  36. package/dist/src/rules/connector/ReplayChannelConfigRule.d.ts.map +1 -0
  37. package/dist/src/rules/connector/ReplayChannelConfigRule.js +52 -0
  38. package/dist/src/rules/connector/ReplayChannelConfigRule.js.map +1 -0
  39. package/dist/src/rules/dataweave/DataWeaveRules.d.ts +11 -4
  40. package/dist/src/rules/dataweave/DataWeaveRules.d.ts.map +1 -1
  41. package/dist/src/rules/dataweave/DataWeaveRules.js +20 -20
  42. package/dist/src/rules/dataweave/DataWeaveRules.js.map +1 -1
  43. package/dist/src/rules/dataweave/DuplicateTransformLogicRule.d.ts +25 -0
  44. package/dist/src/rules/dataweave/DuplicateTransformLogicRule.d.ts.map +1 -0
  45. package/dist/src/rules/dataweave/DuplicateTransformLogicRule.js +63 -0
  46. package/dist/src/rules/dataweave/DuplicateTransformLogicRule.js.map +1 -0
  47. package/dist/src/rules/error-handling/CatchAllLastRule.d.ts +24 -0
  48. package/dist/src/rules/error-handling/CatchAllLastRule.d.ts.map +1 -0
  49. package/dist/src/rules/error-handling/CatchAllLastRule.js +65 -0
  50. package/dist/src/rules/error-handling/CatchAllLastRule.js.map +1 -0
  51. package/dist/src/rules/error-handling/ErrorHandlerTypeCoverageRule.d.ts +28 -0
  52. package/dist/src/rules/error-handling/ErrorHandlerTypeCoverageRule.d.ts.map +1 -0
  53. package/dist/src/rules/error-handling/ErrorHandlerTypeCoverageRule.js +70 -0
  54. package/dist/src/rules/error-handling/ErrorHandlerTypeCoverageRule.js.map +1 -0
  55. package/dist/src/rules/error-handling/ErrorResponseStructureRule.d.ts +23 -0
  56. package/dist/src/rules/error-handling/ErrorResponseStructureRule.d.ts.map +1 -0
  57. package/dist/src/rules/error-handling/ErrorResponseStructureRule.js +73 -0
  58. package/dist/src/rules/error-handling/ErrorResponseStructureRule.js.map +1 -0
  59. package/dist/src/rules/error-handling/GenericErrorRule.d.ts +15 -3
  60. package/dist/src/rules/error-handling/GenericErrorRule.d.ts.map +1 -1
  61. package/dist/src/rules/error-handling/GenericErrorRule.js +58 -18
  62. package/dist/src/rules/error-handling/GenericErrorRule.js.map +1 -1
  63. package/dist/src/rules/error-handling/GlobalErrorHandlerRule.d.ts +14 -15
  64. package/dist/src/rules/error-handling/GlobalErrorHandlerRule.d.ts.map +1 -1
  65. package/dist/src/rules/error-handling/GlobalErrorHandlerRule.js +59 -38
  66. package/dist/src/rules/error-handling/GlobalErrorHandlerRule.js.map +1 -1
  67. package/dist/src/rules/error-handling/TryScopeRule.d.ts +5 -0
  68. package/dist/src/rules/error-handling/TryScopeRule.d.ts.map +1 -1
  69. package/dist/src/rules/error-handling/TryScopeRule.js +30 -7
  70. package/dist/src/rules/error-handling/TryScopeRule.js.map +1 -1
  71. package/dist/src/rules/http/ConnectionIdleTimeoutRule.d.ts +27 -0
  72. package/dist/src/rules/http/ConnectionIdleTimeoutRule.d.ts.map +1 -0
  73. package/dist/src/rules/http/ConnectionIdleTimeoutRule.js +46 -0
  74. package/dist/src/rules/http/ConnectionIdleTimeoutRule.js.map +1 -0
  75. package/dist/src/rules/index.d.ts +1 -1
  76. package/dist/src/rules/index.d.ts.map +1 -1
  77. package/dist/src/rules/index.js +50 -8
  78. package/dist/src/rules/index.js.map +1 -1
  79. package/dist/src/rules/logging/LoggerPayloadRule.d.ts +15 -0
  80. package/dist/src/rules/logging/LoggerPayloadRule.d.ts.map +1 -1
  81. package/dist/src/rules/logging/LoggerPayloadRule.js +48 -4
  82. package/dist/src/rules/logging/LoggerPayloadRule.js.map +1 -1
  83. package/dist/src/rules/operations/FlowRefTargetExistsRule.d.ts +23 -0
  84. package/dist/src/rules/operations/FlowRefTargetExistsRule.d.ts.map +1 -0
  85. package/dist/src/rules/operations/FlowRefTargetExistsRule.js +58 -0
  86. package/dist/src/rules/operations/FlowRefTargetExistsRule.js.map +1 -0
  87. package/dist/src/rules/operations/UnusedFlowRule.d.ts +20 -0
  88. package/dist/src/rules/operations/UnusedFlowRule.d.ts.map +1 -1
  89. package/dist/src/rules/operations/UnusedFlowRule.js +73 -7
  90. package/dist/src/rules/operations/UnusedFlowRule.js.map +1 -1
  91. package/dist/src/rules/operations/UnusedVariableRule.d.ts +31 -0
  92. package/dist/src/rules/operations/UnusedVariableRule.d.ts.map +1 -0
  93. package/dist/src/rules/operations/UnusedVariableRule.js +103 -0
  94. package/dist/src/rules/operations/UnusedVariableRule.js.map +1 -0
  95. package/dist/src/rules/performance/ListenerReconnectForeverRule.d.ts +28 -0
  96. package/dist/src/rules/performance/ListenerReconnectForeverRule.d.ts.map +1 -0
  97. package/dist/src/rules/performance/ListenerReconnectForeverRule.js +56 -0
  98. package/dist/src/rules/performance/ListenerReconnectForeverRule.js.map +1 -0
  99. package/dist/src/rules/performance/ReconnectionStrategyRule.d.ts +7 -4
  100. package/dist/src/rules/performance/ReconnectionStrategyRule.d.ts.map +1 -1
  101. package/dist/src/rules/performance/ReconnectionStrategyRule.js +44 -24
  102. package/dist/src/rules/performance/ReconnectionStrategyRule.js.map +1 -1
  103. package/dist/src/rules/security/ConnectorCredentialsSecuredRule.d.ts +36 -0
  104. package/dist/src/rules/security/ConnectorCredentialsSecuredRule.d.ts.map +1 -0
  105. package/dist/src/rules/security/ConnectorCredentialsSecuredRule.js +124 -0
  106. package/dist/src/rules/security/ConnectorCredentialsSecuredRule.js.map +1 -0
  107. package/dist/src/rules/security/HardcodedCredentialsRule.d.ts +4 -0
  108. package/dist/src/rules/security/HardcodedCredentialsRule.d.ts.map +1 -1
  109. package/dist/src/rules/security/HardcodedCredentialsRule.js +15 -0
  110. package/dist/src/rules/security/HardcodedCredentialsRule.js.map +1 -1
  111. package/dist/src/rules/security/SecurePropertiesEncryptionRule.d.ts +25 -0
  112. package/dist/src/rules/security/SecurePropertiesEncryptionRule.d.ts.map +1 -0
  113. package/dist/src/rules/security/SecurePropertiesEncryptionRule.js +59 -0
  114. package/dist/src/rules/security/SecurePropertiesEncryptionRule.js.map +1 -0
  115. package/dist/src/rules/security/SecurePropertiesKeyRule.d.ts +23 -0
  116. package/dist/src/rules/security/SecurePropertiesKeyRule.d.ts.map +1 -0
  117. package/dist/src/rules/security/SecurePropertiesKeyRule.js +45 -0
  118. package/dist/src/rules/security/SecurePropertiesKeyRule.js.map +1 -0
  119. package/dist/src/rules/security/TlsKeystorePasswordRule.d.ts +25 -0
  120. package/dist/src/rules/security/TlsKeystorePasswordRule.d.ts.map +1 -0
  121. package/dist/src/rules/security/TlsKeystorePasswordRule.js +63 -0
  122. package/dist/src/rules/security/TlsKeystorePasswordRule.js.map +1 -0
  123. package/dist/src/rules/standards/ApikitRouteVariableConsistencyRule.d.ts +26 -0
  124. package/dist/src/rules/standards/ApikitRouteVariableConsistencyRule.d.ts.map +1 -0
  125. package/dist/src/rules/standards/ApikitRouteVariableConsistencyRule.js +61 -0
  126. package/dist/src/rules/standards/ApikitRouteVariableConsistencyRule.js.map +1 -0
  127. package/dist/src/rules/standards/ConfigPropertiesOrderingRule.d.ts +34 -0
  128. package/dist/src/rules/standards/ConfigPropertiesOrderingRule.d.ts.map +1 -0
  129. package/dist/src/rules/standards/ConfigPropertiesOrderingRule.js +76 -0
  130. package/dist/src/rules/standards/ConfigPropertiesOrderingRule.js.map +1 -0
  131. package/dist/src/rules/standards/MissingEnvPropertiesDeclarationRule.d.ts +25 -0
  132. package/dist/src/rules/standards/MissingEnvPropertiesDeclarationRule.d.ts.map +1 -0
  133. package/dist/src/rules/standards/MissingEnvPropertiesDeclarationRule.js +111 -0
  134. package/dist/src/rules/standards/MissingEnvPropertiesDeclarationRule.js.map +1 -0
  135. package/dist/src/rules/yaml/YamlRules.d.ts +6 -2
  136. package/dist/src/rules/yaml/YamlRules.d.ts.map +1 -1
  137. package/dist/src/rules/yaml/YamlRules.js +15 -11
  138. package/dist/src/rules/yaml/YamlRules.js.map +1 -1
  139. package/dist/src/types/Rule.d.ts +13 -0
  140. package/dist/src/types/Rule.d.ts.map +1 -1
  141. package/docs/README.md +87 -27
  142. package/docs/best-practices/ci-cd.md +135 -0
  143. package/docs/best-practices/connector-patterns.md +253 -0
  144. package/docs/best-practices/dataweave-patterns.md +370 -0
  145. package/docs/best-practices/deployment-2026.md +171 -0
  146. package/docs/best-practices/error-handling.md +277 -0
  147. package/docs/best-practices/event-driven-patterns.md +424 -0
  148. package/docs/best-practices/logging.md +163 -0
  149. package/docs/best-practices/mulesoft-best-practices.md +72 -865
  150. package/docs/best-practices/performance.md +273 -0
  151. package/docs/best-practices/rules-catalog.md +337 -29
  152. package/docs/best-practices/security.md +181 -0
  153. package/docs/best-practices/testing.md +190 -0
  154. package/docs/best-practices/variable-contracts.md +191 -0
  155. package/docs/linter/architecture.md +119 -64
  156. package/package.json +1 -1
@@ -0,0 +1,253 @@
1
+ # Connector Configuration Patterns
2
+
3
+ > **Applies to:** All (System APIs, Process APIs)
4
+ > **Related Rules:** `SEC-007` · `SEC-008` · `PERF-002` · `RES-001` · `RES-002` · `SF-001` · `SF-002`
5
+ > **Last Updated:** April 2026
6
+
7
+ ## When to Read This
8
+
9
+ Read this when configuring Salesforce, NetSuite, HTTP, or Database connectors in Mule 4. Covers connector attribute gotchas, the entity-config pattern, and protocol negotiation.
10
+
11
+ ---
12
+
13
+ ## Patterns
14
+
15
+ ### Pattern 1: Entity Configuration (YAML-Driven)
16
+
17
+ **Use when:** building a System API that supports multiple entity types (Account, Contact, Order, etc.) with consistent CRUD operations.
18
+
19
+ Instead of hardcoding entity-specific details in flow XML, externalize them to per-entity YAML files:
20
+
21
+ ```
22
+ src/main/resources/entity-config/
23
+ ├── account.yaml
24
+ ├── contact.yaml
25
+ ├── opportunity.yaml
26
+ └── order.yaml
27
+ ```
28
+
29
+ **Entity config structure:**
30
+
31
+ ```yaml
32
+ # entity-config/account.yaml
33
+ entity:
34
+ account:
35
+ sObjectType: 'Account'
36
+ enabled: true
37
+ externalIdField: 'NetSuite_ID__c'
38
+ queryFields: 'Id, Name, BillingStreet, BillingCity, Phone, Website'
39
+ queryTemplate: 'SELECT {fields} FROM Account WHERE {filter} LIMIT {limit}'
40
+ writeback:
41
+ netSuiteIdField: 'NetSuite_ID__c'
42
+ errorField: 'NetSuite_Error__c'
43
+ lastSyncField: 'Last_NetSuite_Sync__c'
44
+ ```
45
+
46
+ **Load in global-config.xml:**
47
+
48
+ ```xml
49
+ <configuration-properties doc:name="Account Config"
50
+ file="entity-config/account.yaml"/>
51
+ <configuration-properties doc:name="Contact Config"
52
+ file="entity-config/contact.yaml"/>
53
+ ```
54
+
55
+ **Benefits:**
56
+
57
+ - Add a new entity by creating a YAML file — no flow XML changes
58
+ - Entity behavior is visible, auditable, and environment-independent
59
+ - LLMs can read entity configs to understand available operations
60
+
61
+ ### Pattern 2: Salesforce JWT Connector
62
+
63
+ **Use when:** authenticating to Salesforce via OAuth 2.0 JWT Bearer flow.
64
+
65
+ ```xml
66
+ <salesforce:sfdc-config name="Salesforce_Config">
67
+ <salesforce:jwt-connection
68
+ consumerKey="${secure::salesforce.jwt.consumerKey}"
69
+ keyStore="${salesforce.jwt.keystorePath}"
70
+ storePassword="${secure::salesforce.jwt.storePassword}"
71
+ principal="${salesforce.jwt.principal}"
72
+ tokenEndpoint="${salesforce.jwt.tokenEndpoint}"
73
+ audienceUrl="${salesforce.jwt.audienceUrl}">
74
+ <reconnection>
75
+ <reconnect count="3" frequency="5000"/>
76
+ </reconnection>
77
+ </salesforce:jwt-connection>
78
+ </salesforce:sfdc-config>
79
+ ```
80
+
81
+ > ⚠️ **Gotchas** (common mistakes that cause runtime failures):
82
+ >
83
+ > | Incorrect | Correct | Notes |
84
+ > | ----------------------------------- | ------------------------ | ---------------------------------- |
85
+ > | `salesforce:jwt-connection-config` | `salesforce:sfdc-config` | Outer config element name |
86
+ > | `keyStorePath="..."` | `keyStore="..."` | JWT connection attribute |
87
+ > | `type="Account"` (on upsert) | `objectType="Account"` | Upsert operation attribute |
88
+ > | `type="Account"` (on create/update) | `type="Account"` | Create/Update use `type` (correct) |
89
+
90
+ ### Pattern 3: Protocol Negotiation (Dual-Protocol SAPI)
91
+
92
+ **Use when:** a System API must support both SOAP and REST protocols to the same backend (e.g., NetSuite).
93
+
94
+ The calling PAPI sends an `x-integration-protocol` header. The SAPI routes internally:
95
+
96
+ ```xml
97
+ <!-- common/netsuite-process-subflow.xml -->
98
+ <sub-flow name="netsuite-process-subflow">
99
+ <choice>
100
+ <when expression="#[vars.protocol == 'SOAP']">
101
+ <flow-ref name="netsuite-soap-upsert-subflow"/>
102
+ </when>
103
+ <when expression="#[vars.protocol == 'REST']">
104
+ <flow-ref name="netsuite-rest-upsert-subflow"/>
105
+ </when>
106
+ </choice>
107
+ </sub-flow>
108
+ ```
109
+
110
+ **Protocol support matrix:**
111
+
112
+ | Record Type | SOAP | REST |
113
+ | ----------- | ---- | -------------- |
114
+ | Customer | ✅ | ✅ |
115
+ | Contact | ✅ | ✅ |
116
+ | Sales Order | ✅ | ❌ (SOAP-only) |
117
+ | Credit Memo | ✅ | ❌ (SOAP-only) |
118
+
119
+ ### Pattern 4: HTTP Request Connector (with Pooling)
120
+
121
+ **Use when:** making outbound HTTP calls to downstream APIs.
122
+
123
+ ```xml
124
+ <http:request-config name="SAPI_HTTP_Config"
125
+ responseTimeout="${https.request.responseTimeout}">
126
+ <http:request-connection host="${https.request.host}"
127
+ port="${https.request.port}"
128
+ connectionIdleTimeout="${https.connection.idleTimeout}"
129
+ maxConnections="${https.connection.maxConnections}">
130
+ <reconnection>
131
+ <reconnect frequency="${reconnection.frequency}"
132
+ count="${reconnection.attempts}"/>
133
+ </reconnection>
134
+ </http:request-connection>
135
+ <!-- Set correlation ID once — applied to every request automatically -->
136
+ <http:default-headers>
137
+ <http:default-header key="X-Correlation-Id" value="#[correlationId]"/>
138
+ </http:default-headers>
139
+ </http:request-config>
140
+ ```
141
+
142
+ **Rules:**
143
+
144
+ - Always set `responseTimeout` (avoid hanging connections)
145
+ - Set `connectionIdleTimeout >= responseTimeout` (Grizzly kills connections if idle fires first)
146
+ - Configure connection pooling for production (`maxConnections`)
147
+
148
+ ### Pattern 5: Reconnection Strategies
149
+
150
+ **Use when:** configuring any connector that connects to an external system.
151
+
152
+ | Connector Type | Strategy | Example |
153
+ | -------------------------------------- | --------------------- | ---------------------------------- |
154
+ | **Event listeners** (SF, MQ) | `reconnect-forever` | Must auto-recover from disconnects |
155
+ | **Outbound connectors** (HTTP, SF, DB) | `reconnect count="3"` | Bounded retries with frequency |
156
+ | **HTTP Listener** | `reconnect-forever` | Server must always be up |
157
+
158
+ ```xml
159
+ <!-- Listener — always reconnect -->
160
+ <http:listener-config name="httpListenerConfig">
161
+ <http:listener-connection host="0.0.0.0" port="${http.port}">
162
+ <reconnection>
163
+ <reconnect-forever frequency="5000"/>
164
+ </reconnection>
165
+ </http:listener-connection>
166
+ </http:listener-config>
167
+
168
+ <!-- Outbound — bounded retries -->
169
+ <salesforce:sfdc-config name="Salesforce_Config">
170
+ <salesforce:jwt-connection ...>
171
+ <reconnection>
172
+ <reconnect count="3" frequency="5000"/>
173
+ </reconnection>
174
+ </salesforce:jwt-connection>
175
+ </salesforce:sfdc-config>
176
+ ```
177
+
178
+ ### Pattern 6: ObjectStore for Reference Data Caching
179
+
180
+ **Use when:** frequently-accessed reference data (customer records, config lookups) is fetched repeatedly during batch processing. Cache in ObjectStore with TTL to reduce API calls.
181
+
182
+ ```xml
183
+ <!-- global.xml — configure the cache store -->
184
+ <os:object-store name="customer-cache-store"
185
+ entryTtl="${customer.cache.ttl}"
186
+ expirationInterval="${customer.cache.expirationInterval}"
187
+ maxEntries="200"/>
188
+ ```
189
+
190
+ **Key rules:**
191
+
192
+ - Set `maxEntries` to prevent unbounded memory growth
193
+ - Set `entryTtl` appropriate to data volatility (e.g., 1 hour for customer data, 24 hours for country codes)
194
+ - Use `os:contains` + `os:retrieve` to check-then-get, not just `os:retrieve` with default (avoids computing defaults unnecessarily)
195
+
196
+ ---
197
+
198
+ ## DWL Utility Module: `parseAndConvert`
199
+
200
+ The Salesforce connector requires Java-typed values (DateTime, Date, Boolean) — not strings. Use a shared DWL module for type coercion:
201
+
202
+ ```dataweave
203
+ %dw 2.0
204
+ // dwl/modules/salesforceUtility.dwl
205
+
206
+ var dateTimePattern = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,3})?(Z|[+-]\d{2}:\d{2})?$/
207
+ var dateOnlyPattern = /^\d{4}-\d{2}-\d{2}$/
208
+
209
+ fun parseAndConvert(obj: Object) =
210
+ obj mapObject ((value, key) ->
211
+ if ((value is String) and ((value as String) matches dateTimePattern))
212
+ (key): (value as String) as DateTime
213
+ else if ((value is String) and ((value as String) matches dateOnlyPattern))
214
+ (key): (value as String) as Date
215
+ else if ((value is String) and ((value as String) == ""))
216
+ (key): null
217
+ else if (value is Object)
218
+ (key): parseAndConvert(value)
219
+ else
220
+ (key): value
221
+ )
222
+ ```
223
+
224
+ **Usage in flow XML:**
225
+
226
+ ```xml
227
+ <ee:transform>
228
+ <ee:set-payload><![CDATA[%dw 2.0
229
+ import dwl::modules::salesforceUtility
230
+ output application/java
231
+ ---
232
+ salesforceUtility::parseAndConvert(payload)
233
+ ]]></ee:set-payload>
234
+ </ee:transform>
235
+ ```
236
+
237
+ > ⚠️ **DWL import path rule:** Always use the full path prefix `dwl::modules::moduleName`. Short paths like `modules::moduleName` will fail at runtime.
238
+
239
+ ---
240
+
241
+ ## Checklist
242
+
243
+ - [ ] Entity configs externalized to `entity-config/{entity}.yaml`
244
+ - [ ] All connector credentials use `${secure::...}` property placeholders
245
+ - [ ] Reconnection strategies configured on all connectors
246
+ - [ ] `responseTimeout` explicitly set on HTTP request configs
247
+ - [ ] No hardcoded connector attribute values (hosts, ports, keys)
248
+ - [ ] DWL modules imported with full `dwl::modules::` path prefix
249
+ - [ ] Salesforce payloads pass through `parseAndConvert()` before connector call
250
+
251
+ ---
252
+
253
+ **See also:** [Security](security.md) · [Performance](performance.md) · [DataWeave Patterns](dataweave-patterns.md)
@@ -0,0 +1,370 @@
1
+ # DataWeave Best Practices
2
+
3
+ > **Applies to:** All
4
+ > **Related Rules:** `DW-001` · `DW-002` · `DW-003` · `DW-004` · `DW-005`
5
+ > **Last Updated:** April 2026
6
+
7
+ ## When to Read This
8
+
9
+ Read this when writing DataWeave transformations, creating reusable DWL modules, or debugging type coercion issues with connectors.
10
+
11
+ ---
12
+
13
+ ## Key Principles
14
+
15
+ 1. **Externalize complex transforms** — inline DWL > 5 lines should be in a `.dwl` file
16
+ 2. **Create reusable modules** — shared logic goes in `dwl/modules/`
17
+ 3. **Use full import paths** — `import X from dwl::modules::Y` (short paths fail at runtime)
18
+ 4. **Match connector type expectations** — SF connector needs Java types, not strings
19
+ 5. **Use kebab-case for file names** — `transform-order.dwl`, not `transformOrder.dwl`
20
+
21
+ ---
22
+
23
+ ## Project Structure
24
+
25
+ ```
26
+ src/main/resources/dwl/
27
+ ├── modules/ # Reusable DWL modules
28
+ │ ├── salesforceUtility.dwl # Type coercion for SF connector
29
+ │ ├── oauth1Header.dwl # OAuth 1.0 HMAC-SHA256 header generator
30
+ │ └── dateUtils.dwl # Shared date formatting functions
31
+ ├── transforms/ # Entity-specific transforms
32
+ │ ├── sf-account-to-ns-customer.dwl
33
+ │ ├── salesforce-upsert-response.dwl
34
+ │ ├── error-payload.dwl
35
+ │ └── ...
36
+ ├── lookups/ # Enum mapping modules
37
+ │ ├── countryMap.dwl # ISO country → target system enum
38
+ │ └── currencyMap.dwl # ISO currency → target system ID
39
+ └── dictionaries/ # Environment-specific lookup data
40
+ ├── customer-segment-dictionary-dev.json
41
+ └── customer-segment-dictionary-prod.json
42
+ ```
43
+
44
+ ---
45
+
46
+ ## Patterns
47
+
48
+ ### Pattern 1: External DWL Transform Files
49
+
50
+ **Use when:** any transform exceeds 5 lines of DataWeave.
51
+
52
+ ```xml
53
+ <!-- ❌ Bad — large inline transform -->
54
+ <ee:transform>
55
+ <ee:set-payload><![CDATA[%dw 2.0
56
+ <!-- 50+ lines of DataWeave -->
57
+ ]]></ee:set-payload>
58
+ </ee:transform>
59
+
60
+ <!-- ✅ Good — external file reference -->
61
+ <ee:transform doc:name="SF Account → NS Customer">
62
+ <ee:message>
63
+ <ee:set-payload resource="dwl/transforms/sf-account-to-ns-customer.dwl"/>
64
+ </ee:message>
65
+ </ee:transform>
66
+ ```
67
+
68
+ ### Pattern 2: Reusable DWL Modules
69
+
70
+ **Use when:** logic is shared across multiple transforms (date formatting, type coercion, enum lookups).
71
+
72
+ ```dataweave
73
+ // dwl/modules/dateUtils.dwl
74
+ %dw 2.0
75
+
76
+ fun formatDate(date: DateTime) =
77
+ date as String {format: "yyyy-MM-dd"}
78
+
79
+ fun formatDateTime(date: DateTime) =
80
+ date as String {format: "yyyy-MM-dd'T'HH:mm:ss.SSSZ"}
81
+
82
+ fun maskPII(value: String) =
83
+ value[0 to 2] ++ "****" ++ value[-2 to -1]
84
+
85
+ fun toErrorResponse(error, correlationId: String) = {
86
+ correlationId: correlationId,
87
+ timestamp: now(),
88
+ error: error.errorType.identifier,
89
+ message: error.description
90
+ }
91
+ ```
92
+
93
+ **Import with full path:**
94
+
95
+ ```dataweave
96
+ %dw 2.0
97
+ import dwl::modules::dateUtils
98
+ output application/json
99
+ ---
100
+ {
101
+ createdDate: dateUtils::formatDate(payload.CreatedDate)
102
+ }
103
+ ```
104
+
105
+ > ⚠️ **Critical:** Always use `dwl::modules::moduleName` as the import path. Short paths like `modules::moduleName` will compile but **fail at runtime**.
106
+
107
+ ### Pattern 3: Type Coercion for Connectors
108
+
109
+ **Use when:** passing data to the Salesforce connector (or any connector that expects Java types).
110
+
111
+ The SF connector rejects ISO datetime strings — they must be Java DateTime objects:
112
+
113
+ ```dataweave
114
+ // dwl/modules/salesforceUtility.dwl
115
+ %dw 2.0
116
+
117
+ var dateTimePattern = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,3})?(Z|[+-]\d{2}:\d{2})?$/
118
+ var dateOnlyPattern = /^\d{4}-\d{2}-\d{2}$/
119
+
120
+ fun parseAndConvert(obj: Object) =
121
+ obj mapObject ((value, key) ->
122
+ if ((value is String) and ((value as String) matches dateTimePattern))
123
+ (key): (value as String) as DateTime
124
+ else if ((value is String) and ((value as String) matches dateOnlyPattern))
125
+ (key): (value as String) as Date
126
+ else if ((value is String) and ((value as String) == ""))
127
+ (key): null
128
+ else if (value is Object)
129
+ (key): parseAndConvert(value)
130
+ else
131
+ (key): value
132
+ )
133
+ ```
134
+
135
+ **When to use `output application/java`:**
136
+
137
+ ```dataweave
138
+ %dw 2.0
139
+ import dwl::modules::salesforceUtility
140
+ output application/java // ← Required for SF connector inputs
141
+ ---
142
+ salesforceUtility::parseAndConvert(payload)
143
+ ```
144
+
145
+ Use `output application/java` for transforms that feed directly into connectors (SF, DB, NetSuite SOAP). Use `output application/json` for HTTP response payloads.
146
+
147
+ ### Pattern 4: Cross-System Value Mapping
148
+
149
+ **Use when:** translating enum/picklist values between systems (country codes, currency codes, payment terms, industry classifications). There are 4 strategies — choose based on your data characteristics.
150
+
151
+ #### Strategy A: Rich-Object DWL Lookup (Standardized Reference Data)
152
+
153
+ **Use when:** stable reference data (ISO countries, currencies), ≤ 300 entries, same across all environments, callers need multiple fields (`.name`, `.erp`).
154
+
155
+ ```dataweave
156
+ // dwl/lookups/countryMap.dwl
157
+ %dw 2.0
158
+
159
+ fun countryMap() = {
160
+ "US": { "name": "United States", "erp": "_unitedStates" },
161
+ "CA": { "name": "Canada", "erp": "_canada" },
162
+ "GB": { "name": "United Kingdom", "erp": "_unitedKingdom" }
163
+ // 250+ entries for full ISO 3166...
164
+ }
165
+ ```
166
+
167
+ **Usage — always null-guard before dereferencing:**
168
+
169
+ ```dataweave
170
+ import countryMap from dwl::lookups::countryMap
171
+ ---
172
+ {
173
+ (shippingCountry: countryMap()[payload.ShippingCountry].erp)
174
+ if (payload.ShippingCountry? and (countryMap()[payload.ShippingCountry] != null))
175
+ }
176
+ ```
177
+
178
+ #### Strategy B: Environment-Specific JSON Dictionary
179
+
180
+ **Use when:** target system IDs differ between dev/staging/prod (e.g., ERP sandbox has different internal IDs than production). Flat key → value mapping loaded via `readUrl`.
181
+
182
+ ```json
183
+ // dwl/dictionaries/customer-industry-dictionary-dev.json
184
+ {
185
+ "Education": "1",
186
+ "Financial Services": "13",
187
+ "Government": "14"
188
+ }
189
+ ```
190
+
191
+ ```json
192
+ // dwl/dictionaries/customer-industry-dictionary-prod.json
193
+ {
194
+ "Education": "101",
195
+ "Financial Services": "113",
196
+ "Government": "114"
197
+ }
198
+ ```
199
+
200
+ **Load dynamically using environment property:**
201
+
202
+ ```dataweave
203
+ var industry = readUrl(
204
+ "classpath://dwl/dictionaries/customer-industry-dictionary-$(p('mule.env')).json",
205
+ "application/json"
206
+ )
207
+ ---
208
+ {
209
+ (industryId: industry[payload.Industry]) if (payload.Industry?)
210
+ }
211
+ ```
212
+
213
+ #### Strategy C: Bidirectional DWL Module (Enum Translation)
214
+
215
+ **Use when:** business enums must translate in both directions (CRM → ERP and ERP → CRM), ≤ 50 values, stable values (payment terms, billing frequency).
216
+
217
+ **Option 1 — Computed inverse (recommended for 1:1 mappings):**
218
+
219
+ ```dataweave
220
+ // dwl/lookups/paymentTerms.dwl
221
+ %dw 2.0
222
+
223
+ // Single source of truth
224
+ var crmToErp = {
225
+ "Net 15": "1", "Net 30": "2", "Net 45": "8",
226
+ "Net 60": "3", "Due on receipt": "4"
227
+ }
228
+
229
+ // Auto-computed inverse — no dual maintenance
230
+ var erpToCrm = crmToErp pluck ((value, key) -> { (value): key as String })
231
+ reduce ((item, acc = {}) -> acc ++ item)
232
+
233
+ fun mapPaymentTermToERP(crmValue: String) = crmToErp[crmValue]
234
+ fun mapPaymentTermToCRM(erpId: String) = erpToCrm[erpId]
235
+ ```
236
+
237
+ **Option 2 — Explicit dual dictionary (required for many-to-one):**
238
+
239
+ When 3 CRM statuses map to 1 ERP status, the computed inverse is ambiguous. Maintain both directions explicitly:
240
+
241
+ ```dataweave
242
+ var statusMap = {
243
+ crmToErp: { "Open": "1", "In Progress": "1", "Closed": "2" },
244
+ erpToCrm: { "1": "Open", "2": "Closed" }
245
+ }
246
+ ```
247
+
248
+ #### Strategy D: External API / ObjectStore Cache (Dynamic Catalogs)
249
+
250
+ **Use when:** > 500 values, business users manage mappings at runtime, multiple apps share the same mapping, change frequency > quarterly.
251
+
252
+ ```xml
253
+ <os:object-store name="product-sku-cache"
254
+ entryTtl="3600000"
255
+ expirationInterval="300000"
256
+ maxEntries="500"/>
257
+ ```
258
+
259
+ #### Decision Tree
260
+
261
+ ```
262
+ Does the mapping differ between dev/staging/prod?
263
+ ├── YES → Strategy B (Environment-Specific JSON Dictionary)
264
+ └── NO
265
+ ├── Need lookup in both directions (CRM↔ERP)?
266
+ │ ├── YES, ≤ 50 values → Strategy C (Bidirectional DWL)
267
+ │ └── YES, > 50 values → Strategy D (External)
268
+ └── NO (one direction only)
269
+ ├── Need rich objects (name + ID)? → Strategy A (Rich-Object DWL)
270
+ └── Simple key→value? → Strategy A (flat DWL function)
271
+ ```
272
+
273
+ **Testing guidance:**
274
+
275
+ - **Completeness:** When CRM adds a new picklist value, the lookup silently returns `null`. Add MUnit tests that validate all known enum values have mappings.
276
+ - **Symmetry:** For 1:1 bidirectional mappings, `mapToERP(mapFromERP(x)) == x` should hold.
277
+ - **Environment parity:** For Strategy B, verify that dev and prod dictionary files have the **same keys** (only IDs should differ).
278
+
279
+ > ⚠️ **Always null-guard lookups** — `countryMap()[code]` returns `null` for unknown codes. Dereferencing `.erp` on `null` causes a runtime error.
280
+
281
+ ### Pattern 5: Array Normalization for Connectors
282
+
283
+ **Use when:** connector operations (upsert, create, update) always expect arrays, but input may be a single object.
284
+
285
+ ```dataweave
286
+ %dw 2.0
287
+ import dwl::modules::salesforceUtility
288
+ output application/java
289
+ ---
290
+ var parsed = salesforceUtility::parseAndConvert(payload)
291
+ ---
292
+ if (parsed is Array) parsed else [parsed]
293
+ ```
294
+
295
+ ### Pattern 6: Error Payload Template
296
+
297
+ **Use when:** building standardized error response payloads.
298
+
299
+ ```dataweave
300
+ // dwl/transforms/error-payload.dwl
301
+ %dw 2.0
302
+ output application/json
303
+ ---
304
+ {
305
+ timestamp: now() as String {format: "yyyy-MM-dd'T'HH:mm:ss.SSSZ"},
306
+ correlationId: vars.correlationId default "",
307
+ entity: vars.entity default "",
308
+ salesforceId: vars.salesforceId default "",
309
+ errorType: error.errorType.identifier default "UNKNOWN",
310
+ message: error.detailedDescription default error.description default "",
311
+ environment: p('mule.env')
312
+ }
313
+ ```
314
+
315
+ ---
316
+
317
+ ## Java 17 DataWeave Considerations
318
+
319
+ Since Mule 4.9+ mandates Java 17:
320
+
321
+ - `try()` requires explicit import: `import try from dw::Runtime`
322
+ - `error.errorType.identifier` — use `.identifier`, not `.asString` (doesn't exist)
323
+ - Batch Kryo serialization — don't use `output application/java` for HTTP payloads in batch steps
324
+ - `DateTime` coercion is stricter in Java 17 — always test date parsing patterns
325
+
326
+ **Safe error description extraction using `try()`:**
327
+
328
+ ```dataweave
329
+ %dw 2.0
330
+ import try from dw::Runtime
331
+ var errorDesc = try(() -> error.errorMessage.payload.errorDescription default "")
332
+ ---
333
+ {
334
+ "Subject": "Failed to sync record",
335
+ "Error": if (errorDesc.success and !isBlank(errorDesc.result))
336
+ errorDesc.result
337
+ else (error.detailedDescription default "")
338
+ }
339
+ ```
340
+
341
+ > ⚠️ Use `try()` when accessing deeply nested error properties that may not exist. Without it, accessing `error.errorMessage.payload.errorDescription` throws if `payload` is not an object.
342
+
343
+ ---
344
+
345
+ ## File Naming Convention
346
+
347
+ | Convention | Example | Use For |
348
+ | ------------ | ------------------------------ | -------------------------------------------- |
349
+ | `kebab-case` | `transform-order-response.dwl` | Transform files |
350
+ | `camelCase` | `salesforceUtility.dwl` | Module files (matches DWL import convention) |
351
+ | `kebab-case` | `country-map.dwl` | Lookup files |
352
+
353
+ > **Note:** Module filenames use `camelCase` because the DWL import path must match the filename exactly (`import dwl::modules::salesforceUtility`). Renaming to kebab-case would break imports.
354
+
355
+ ---
356
+
357
+ ## Checklist
358
+
359
+ - [ ] No inline DWL exceeding 5 lines — externalize to `.dwl` files
360
+ - [ ] Shared logic in `dwl/modules/` with full import path
361
+ - [ ] `output application/java` used for connector input transforms
362
+ - [ ] `output application/json` used for HTTP response transforms
363
+ - [ ] Type coercion applied before passing data to SF connector
364
+ - [ ] Lookup modules always null-guarded
365
+ - [ ] Array normalization before connector operations
366
+ - [ ] `.dwl` files have Javadoc-style header comments
367
+
368
+ ---
369
+
370
+ **See also:** [Connector Patterns](connector-patterns.md) · [Documentation Standards](documentation-standards.md)