scc-universal 1.1.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.
- package/.claude-plugin/plugin.json +44 -0
- package/.cursor/agents/deep-researcher.md +142 -0
- package/.cursor/agents/doc-updater.md +219 -0
- package/.cursor/agents/eval-runner.md +335 -0
- package/.cursor/agents/learning-engine.md +210 -0
- package/.cursor/agents/loop-operator.md +245 -0
- package/.cursor/agents/refactor-cleaner.md +119 -0
- package/.cursor/agents/sf-admin-agent.md +127 -0
- package/.cursor/agents/sf-agentforce-agent.md +126 -0
- package/.cursor/agents/sf-apex-agent.md +117 -0
- package/.cursor/agents/sf-architect.md +426 -0
- package/.cursor/agents/sf-aura-reviewer.md +369 -0
- package/.cursor/agents/sf-bugfix-agent.md +101 -0
- package/.cursor/agents/sf-flow-agent.md +155 -0
- package/.cursor/agents/sf-integration-agent.md +141 -0
- package/.cursor/agents/sf-lwc-agent.md +123 -0
- package/.cursor/agents/sf-review-agent.md +357 -0
- package/.cursor/agents/sf-visualforce-reviewer.md +465 -0
- package/.cursor/hooks/adapter.js +81 -0
- package/.cursor/hooks/after-file-edit.js +26 -0
- package/.cursor/hooks/after-mcp-execution.js +12 -0
- package/.cursor/hooks/after-shell-execution.js +30 -0
- package/.cursor/hooks/after-tab-file-edit.js +12 -0
- package/.cursor/hooks/before-mcp-execution.js +11 -0
- package/.cursor/hooks/before-read-file.js +13 -0
- package/.cursor/hooks/before-shell-execution.js +29 -0
- package/.cursor/hooks/before-submit-prompt.js +23 -0
- package/.cursor/hooks/pre-compact.js +7 -0
- package/.cursor/hooks/session-end.js +10 -0
- package/.cursor/hooks/session-start.js +10 -0
- package/.cursor/hooks/stop.js +18 -0
- package/.cursor/hooks/subagent-start.js +10 -0
- package/.cursor/hooks/subagent-stop.js +10 -0
- package/.cursor/hooks.json +107 -0
- package/.cursor/skills/aside/SKILL.md +115 -0
- package/.cursor/skills/checkpoint/SKILL.md +50 -0
- package/.cursor/skills/configure-scc/SKILL.md +160 -0
- package/.cursor/skills/continuous-agent-loop/SKILL.md +260 -0
- package/.cursor/skills/mcp-server-patterns/SKILL.md +142 -0
- package/.cursor/skills/model-route/SKILL.md +81 -0
- package/.cursor/skills/prompt-optimizer/SKILL.md +366 -0
- package/.cursor/skills/refactor-clean/SKILL.md +133 -0
- package/.cursor/skills/resume-session/SKILL.md +111 -0
- package/.cursor/skills/save-session/SKILL.md +183 -0
- package/.cursor/skills/search-first/SKILL.md +140 -0
- package/.cursor/skills/security-scan/SKILL.md +142 -0
- package/.cursor/skills/sessions/SKILL.md +124 -0
- package/.cursor/skills/sf-agentforce-development/SKILL.md +449 -0
- package/.cursor/skills/sf-apex-async-patterns/SKILL.md +324 -0
- package/.cursor/skills/sf-apex-best-practices/SKILL.md +421 -0
- package/.cursor/skills/sf-apex-constraints/SKILL.md +79 -0
- package/.cursor/skills/sf-apex-cursor/SKILL.md +336 -0
- package/.cursor/skills/sf-apex-enterprise-patterns/SKILL.md +344 -0
- package/.cursor/skills/sf-apex-testing/SKILL.md +407 -0
- package/.cursor/skills/sf-api-design/SKILL.md +237 -0
- package/.cursor/skills/sf-approval-processes/SKILL.md +312 -0
- package/.cursor/skills/sf-aura-development/SKILL.md +260 -0
- package/.cursor/skills/sf-build-fix/SKILL.md +120 -0
- package/.cursor/skills/sf-data-modeling/SKILL.md +274 -0
- package/.cursor/skills/sf-debugging/SKILL.md +362 -0
- package/.cursor/skills/sf-deployment/SKILL.md +291 -0
- package/.cursor/skills/sf-deployment-constraints/SKILL.md +153 -0
- package/.cursor/skills/sf-devops-ci-cd/SKILL.md +322 -0
- package/.cursor/skills/sf-docs-lookup/SKILL.md +100 -0
- package/.cursor/skills/sf-e2e-testing/SKILL.md +321 -0
- package/.cursor/skills/sf-experience-cloud/SKILL.md +248 -0
- package/.cursor/skills/sf-flow-development/SKILL.md +376 -0
- package/.cursor/skills/sf-governor-limits/SKILL.md +319 -0
- package/.cursor/skills/sf-harness-audit/SKILL.md +139 -0
- package/.cursor/skills/sf-help/SKILL.md +156 -0
- package/.cursor/skills/sf-integration/SKILL.md +479 -0
- package/.cursor/skills/sf-lwc-constraints/SKILL.md +128 -0
- package/.cursor/skills/sf-lwc-development/SKILL.md +302 -0
- package/.cursor/skills/sf-lwc-testing/SKILL.md +387 -0
- package/.cursor/skills/sf-metadata-management/SKILL.md +285 -0
- package/.cursor/skills/sf-platform-events-cdc/SKILL.md +372 -0
- package/.cursor/skills/sf-quickstart/SKILL.md +170 -0
- package/.cursor/skills/sf-security/SKILL.md +330 -0
- package/.cursor/skills/sf-security-constraints/SKILL.md +125 -0
- package/.cursor/skills/sf-soql-constraints/SKILL.md +129 -0
- package/.cursor/skills/sf-soql-optimization/SKILL.md +353 -0
- package/.cursor/skills/sf-tdd-workflow/SKILL.md +332 -0
- package/.cursor/skills/sf-testing-constraints/SKILL.md +198 -0
- package/.cursor/skills/sf-trigger-constraints/SKILL.md +88 -0
- package/.cursor/skills/sf-trigger-frameworks/SKILL.md +343 -0
- package/.cursor/skills/sf-visualforce-development/SKILL.md +259 -0
- package/.cursor/skills/strategic-compact/SKILL.md +205 -0
- package/.cursor/skills/update-docs/SKILL.md +162 -0
- package/.cursor/skills/update-platform-docs/SKILL.md +86 -0
- package/.cursor-plugin/plugin.json +26 -0
- package/LICENSE +21 -0
- package/README.md +522 -0
- package/agents/deep-researcher.md +145 -0
- package/agents/doc-updater.md +222 -0
- package/agents/eval-runner.md +340 -0
- package/agents/learning-engine.md +211 -0
- package/agents/loop-operator.md +247 -0
- package/agents/refactor-cleaner.md +122 -0
- package/agents/sf-admin-agent.md +131 -0
- package/agents/sf-agentforce-agent.md +132 -0
- package/agents/sf-apex-agent.md +124 -0
- package/agents/sf-architect.md +435 -0
- package/agents/sf-aura-reviewer.md +372 -0
- package/agents/sf-bugfix-agent.md +105 -0
- package/agents/sf-flow-agent.md +159 -0
- package/agents/sf-integration-agent.md +146 -0
- package/agents/sf-lwc-agent.md +127 -0
- package/agents/sf-review-agent.md +366 -0
- package/agents/sf-visualforce-reviewer.md +468 -0
- package/assets/logo.svg +18 -0
- package/docs/ARCHITECTURE.md +133 -0
- package/docs/authoring-guide.md +373 -0
- package/docs/hook-development.md +578 -0
- package/docs/token-optimization.md +139 -0
- package/docs/workflow-examples.md +645 -0
- package/examples/agentforce-action/README.md +227 -0
- package/examples/apex-trigger-handler/README.md +114 -0
- package/examples/devops-pipeline/README.md +325 -0
- package/examples/flow-automation/README.md +188 -0
- package/examples/integration-pattern/README.md +416 -0
- package/examples/lwc-component/README.md +180 -0
- package/examples/platform-events/README.md +492 -0
- package/examples/scratch-org-setup/README.md +138 -0
- package/examples/security-audit/README.md +244 -0
- package/examples/visualforce-migration/README.md +314 -0
- package/hooks/hooks.json +338 -0
- package/hooks/memory-persistence/README.md +73 -0
- package/manifests/install-modules.json +217 -0
- package/manifests/install-profiles.json +17 -0
- package/mcp-configs/mcp-servers.json +19 -0
- package/package.json +89 -0
- package/schemas/hooks.schema.json +123 -0
- package/schemas/install-modules.schema.json +76 -0
- package/schemas/install-profiles.schema.json +28 -0
- package/schemas/install-state.schema.json +73 -0
- package/schemas/package-manager.schema.json +18 -0
- package/schemas/plugin.schema.json +112 -0
- package/schemas/scc-install-config.schema.json +29 -0
- package/schemas/state-store.schema.json +111 -0
- package/scripts/cli/install-apply.js +170 -0
- package/scripts/cli/uninstall.js +193 -0
- package/scripts/hooks/check-console-log.js +101 -0
- package/scripts/hooks/check-hook-enabled.js +17 -0
- package/scripts/hooks/check-platform-docs-age.js +48 -0
- package/scripts/hooks/cost-tracker.js +78 -0
- package/scripts/hooks/doc-file-warning.js +63 -0
- package/scripts/hooks/evaluate-session.js +98 -0
- package/scripts/hooks/governor-check.js +220 -0
- package/scripts/hooks/learning-observe.sh +206 -0
- package/scripts/hooks/mcp-health-check.js +588 -0
- package/scripts/hooks/post-bash-build-complete.js +34 -0
- package/scripts/hooks/post-bash-pr-created.js +43 -0
- package/scripts/hooks/post-edit-console-warn.js +61 -0
- package/scripts/hooks/post-edit-format.js +79 -0
- package/scripts/hooks/post-edit-typecheck.js +98 -0
- package/scripts/hooks/post-write.js +168 -0
- package/scripts/hooks/pre-bash-git-push-reminder.js +35 -0
- package/scripts/hooks/pre-bash-tmux-reminder.js +47 -0
- package/scripts/hooks/pre-compact.js +51 -0
- package/scripts/hooks/pre-tool-use.js +163 -0
- package/scripts/hooks/pre-write-doc-warn.js +9 -0
- package/scripts/hooks/quality-gate.js +251 -0
- package/scripts/hooks/run-with-flags-shell.sh +32 -0
- package/scripts/hooks/run-with-flags.js +135 -0
- package/scripts/hooks/session-end-marker.js +29 -0
- package/scripts/hooks/session-end.js +311 -0
- package/scripts/hooks/session-start.js +202 -0
- package/scripts/hooks/sfdx-scanner-check.js +142 -0
- package/scripts/hooks/sfdx-validate.js +119 -0
- package/scripts/hooks/stop-hook.js +170 -0
- package/scripts/hooks/suggest-compact.js +67 -0
- package/scripts/lib/agent-adapter.js +82 -0
- package/scripts/lib/apex-analysis.js +194 -0
- package/scripts/lib/hook-flags.js +74 -0
- package/scripts/lib/install-config.js +73 -0
- package/scripts/lib/install-executor.js +363 -0
- package/scripts/lib/install-state.js +121 -0
- package/scripts/lib/orchestration-session.js +299 -0
- package/scripts/lib/package-manager.js +124 -0
- package/scripts/lib/project-detect.js +228 -0
- package/scripts/lib/schema-validator.js +190 -0
- package/scripts/lib/skill-adapter.js +100 -0
- package/scripts/lib/state-store.js +376 -0
- package/scripts/lib/tmux-worktree-orchestrator.js +598 -0
- package/scripts/lib/utils.js +313 -0
- package/scripts/scc.js +164 -0
- package/skills/_reference/AGENTFORCE_PATTERNS.md +112 -0
- package/skills/_reference/APEX_CURSOR.md +159 -0
- package/skills/_reference/API_VERSIONS.md +78 -0
- package/skills/_reference/APPROVAL_PROCESSES.md +105 -0
- package/skills/_reference/ASYNC_PATTERNS.md +163 -0
- package/skills/_reference/AURA_COMPONENTS.md +146 -0
- package/skills/_reference/DATA_MIGRATION_PATTERNS.md +151 -0
- package/skills/_reference/DATA_MODELING.md +124 -0
- package/skills/_reference/DEBUGGING_TOOLS.md +140 -0
- package/skills/_reference/DEPLOYMENT_CHECKLIST.md +87 -0
- package/skills/_reference/DEPRECATIONS.md +79 -0
- package/skills/_reference/DOCKER_CI_PATTERNS.md +138 -0
- package/skills/_reference/ENTERPRISE_PATTERNS.md +122 -0
- package/skills/_reference/EXPERIENCE_CLOUD.md +143 -0
- package/skills/_reference/FLOW_PATTERNS.md +113 -0
- package/skills/_reference/GOVERNOR_LIMITS.md +77 -0
- package/skills/_reference/INTEGRATION_PATTERNS.md +105 -0
- package/skills/_reference/LWC_PATTERNS.md +79 -0
- package/skills/_reference/METADATA_TYPES.md +115 -0
- package/skills/_reference/NAMING_CONVENTIONS.md +84 -0
- package/skills/_reference/PACKAGE_DEVELOPMENT.md +150 -0
- package/skills/_reference/PLATFORM_EVENTS.md +121 -0
- package/skills/_reference/REPORTING_API.md +143 -0
- package/skills/_reference/SCRATCH_ORG_PATTERNS.md +126 -0
- package/skills/_reference/SECURITY_PATTERNS.md +127 -0
- package/skills/_reference/SHARING_MODEL.md +120 -0
- package/skills/_reference/SOQL_PATTERNS.md +119 -0
- package/skills/_reference/TESTING_STANDARDS.md +96 -0
- package/skills/_reference/TRIGGER_PATTERNS.md +114 -0
- package/skills/_reference/VISUALFORCE_PATTERNS.md +121 -0
- package/skills/aside/SKILL.md +118 -0
- package/skills/checkpoint/SKILL.md +53 -0
- package/skills/configure-scc/SKILL.md +163 -0
- package/skills/continuous-agent-loop/SKILL.md +264 -0
- package/skills/mcp-server-patterns/SKILL.md +146 -0
- package/skills/model-route/SKILL.md +84 -0
- package/skills/prompt-optimizer/SKILL.md +369 -0
- package/skills/refactor-clean/SKILL.md +136 -0
- package/skills/resume-session/SKILL.md +114 -0
- package/skills/save-session/SKILL.md +186 -0
- package/skills/search-first/SKILL.md +144 -0
- package/skills/security-scan/SKILL.md +146 -0
- package/skills/sessions/SKILL.md +127 -0
- package/skills/sf-agentforce-development/SKILL.md +450 -0
- package/skills/sf-apex-async-patterns/SKILL.md +326 -0
- package/skills/sf-apex-best-practices/SKILL.md +425 -0
- package/skills/sf-apex-constraints/SKILL.md +81 -0
- package/skills/sf-apex-cursor/SKILL.md +338 -0
- package/skills/sf-apex-enterprise-patterns/SKILL.md +348 -0
- package/skills/sf-apex-testing/SKILL.md +409 -0
- package/skills/sf-api-design/SKILL.md +238 -0
- package/skills/sf-approval-processes/SKILL.md +315 -0
- package/skills/sf-aura-development/SKILL.md +263 -0
- package/skills/sf-build-fix/SKILL.md +121 -0
- package/skills/sf-data-modeling/SKILL.md +278 -0
- package/skills/sf-debugging/SKILL.md +363 -0
- package/skills/sf-deployment/SKILL.md +295 -0
- package/skills/sf-deployment-constraints/SKILL.md +155 -0
- package/skills/sf-devops-ci-cd/SKILL.md +325 -0
- package/skills/sf-docs-lookup/SKILL.md +103 -0
- package/skills/sf-e2e-testing/SKILL.md +324 -0
- package/skills/sf-experience-cloud/SKILL.md +249 -0
- package/skills/sf-flow-development/SKILL.md +377 -0
- package/skills/sf-governor-limits/SKILL.md +323 -0
- package/skills/sf-harness-audit/SKILL.md +142 -0
- package/skills/sf-help/SKILL.md +159 -0
- package/skills/sf-integration/SKILL.md +483 -0
- package/skills/sf-lwc-constraints/SKILL.md +130 -0
- package/skills/sf-lwc-development/SKILL.md +303 -0
- package/skills/sf-lwc-testing/SKILL.md +388 -0
- package/skills/sf-metadata-management/SKILL.md +288 -0
- package/skills/sf-platform-events-cdc/SKILL.md +375 -0
- package/skills/sf-quickstart/SKILL.md +173 -0
- package/skills/sf-security/SKILL.md +334 -0
- package/skills/sf-security-constraints/SKILL.md +127 -0
- package/skills/sf-soql-constraints/SKILL.md +131 -0
- package/skills/sf-soql-optimization/SKILL.md +354 -0
- package/skills/sf-tdd-workflow/SKILL.md +336 -0
- package/skills/sf-testing-constraints/SKILL.md +200 -0
- package/skills/sf-trigger-constraints/SKILL.md +90 -0
- package/skills/sf-trigger-frameworks/SKILL.md +347 -0
- package/skills/sf-visualforce-development/SKILL.md +260 -0
- package/skills/strategic-compact/SKILL.md +208 -0
- package/skills/update-docs/SKILL.md +165 -0
- package/skills/update-platform-docs/SKILL.md +90 -0
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sf-integration
|
|
3
|
+
description: >-
|
|
4
|
+
Use when building Salesforce Apex integrations — REST/SOAP callouts,
|
|
5
|
+
Named Credentials, inbound API, External Services.
|
|
6
|
+
Do NOT use for Platform Events or Flow-only automation.
|
|
7
|
+
origin: SCC
|
|
8
|
+
user-invocable: false
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Salesforce Integration Patterns
|
|
12
|
+
|
|
13
|
+
Procedures for building integrations between Salesforce and external systems. Limits, auth protocols, and pattern decision matrices live in the reference file.
|
|
14
|
+
|
|
15
|
+
@../_reference/INTEGRATION_PATTERNS.md
|
|
16
|
+
|
|
17
|
+
## When to Use
|
|
18
|
+
|
|
19
|
+
- Designing a new integration between Salesforce and an external system
|
|
20
|
+
- Choosing between REST callout, SOAP callout, Bulk API, or Composite API
|
|
21
|
+
- Implementing an inbound REST API endpoint in Salesforce
|
|
22
|
+
- Configuring Named Credentials and External Credentials for authentication
|
|
23
|
+
- Adding retry logic to callout classes for resilience
|
|
24
|
+
- Migrating from Connected Apps to External Client Apps (Spring '26+)
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Outbound REST Callout — Complete Pattern
|
|
29
|
+
|
|
30
|
+
```apex
|
|
31
|
+
public with sharing class OrderManagementIntegration {
|
|
32
|
+
|
|
33
|
+
private static final String NAMED_CREDENTIAL = 'OrderManagementAPI';
|
|
34
|
+
private static final Integer TIMEOUT_MS = 10000;
|
|
35
|
+
private static final Integer MAX_RETRIES = 2;
|
|
36
|
+
|
|
37
|
+
public static OrderResponse createExternalOrder(OrderRequest orderData) {
|
|
38
|
+
HttpRequest req = new HttpRequest();
|
|
39
|
+
req.setEndpoint('callout:' + NAMED_CREDENTIAL + '/api/v2/orders');
|
|
40
|
+
req.setMethod('POST');
|
|
41
|
+
req.setHeader('Content-Type', 'application/json');
|
|
42
|
+
req.setHeader('Accept', 'application/json');
|
|
43
|
+
req.setTimeout(TIMEOUT_MS);
|
|
44
|
+
req.setBody(JSON.serialize(orderData));
|
|
45
|
+
|
|
46
|
+
return executeWithRetry(req, MAX_RETRIES);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public static OrderResponse getOrderStatus(String externalOrderId) {
|
|
50
|
+
HttpRequest req = new HttpRequest();
|
|
51
|
+
req.setEndpoint('callout:' + NAMED_CREDENTIAL +
|
|
52
|
+
'/api/v2/orders/' + EncodingUtil.urlEncode(externalOrderId, 'UTF-8'));
|
|
53
|
+
req.setMethod('GET');
|
|
54
|
+
req.setHeader('Accept', 'application/json');
|
|
55
|
+
req.setTimeout(TIMEOUT_MS);
|
|
56
|
+
|
|
57
|
+
return executeWithRetry(req, MAX_RETRIES);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* In-transaction retry for transient network glitches.
|
|
62
|
+
* For true backoff, use Queueable chaining with
|
|
63
|
+
* AsyncOptions.minimumQueueableDelayInMinutes between attempts.
|
|
64
|
+
*/
|
|
65
|
+
private static OrderResponse executeWithRetry(HttpRequest req, Integer retries) {
|
|
66
|
+
Http http = new Http();
|
|
67
|
+
HttpResponse res;
|
|
68
|
+
Exception lastException;
|
|
69
|
+
|
|
70
|
+
for (Integer attempt = 0; attempt <= retries; attempt++) {
|
|
71
|
+
try {
|
|
72
|
+
res = http.send(req);
|
|
73
|
+
|
|
74
|
+
if (res.getStatusCode() == 200 || res.getStatusCode() == 201) {
|
|
75
|
+
return (OrderResponse) JSON.deserialize(
|
|
76
|
+
res.getBody(), OrderResponse.class);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (res.getStatusCode() == 429) {
|
|
80
|
+
if (attempt == retries) {
|
|
81
|
+
throw new IntegrationException(
|
|
82
|
+
'Rate limited (429) after ' + (retries + 1) + ' attempts.');
|
|
83
|
+
}
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (res.getStatusCode() >= 500 && attempt < retries) continue;
|
|
88
|
+
|
|
89
|
+
throw new IntegrationException(
|
|
90
|
+
'HTTP ' + res.getStatusCode() + ': ' + res.getBody());
|
|
91
|
+
|
|
92
|
+
} catch (System.CalloutException e) {
|
|
93
|
+
lastException = e;
|
|
94
|
+
if (attempt == retries) {
|
|
95
|
+
throw new IntegrationException(
|
|
96
|
+
'Callout failed after ' + (retries + 1) +
|
|
97
|
+
' attempts: ' + e.getMessage(), e);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
throw new IntegrationException('Unexpected retry loop exit');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
public class OrderRequest {
|
|
105
|
+
public String externalAccountId;
|
|
106
|
+
public String productCode;
|
|
107
|
+
public Integer quantity;
|
|
108
|
+
public Decimal unitPrice;
|
|
109
|
+
public String currency_x;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
public class OrderResponse {
|
|
113
|
+
public String orderId;
|
|
114
|
+
public String status;
|
|
115
|
+
public String message;
|
|
116
|
+
public String createdAt;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
public class IntegrationException extends Exception {}
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Named Credentials Setup
|
|
126
|
+
|
|
127
|
+
### Modern Model: External Credentials + Named Credentials (API 54.0+)
|
|
128
|
+
|
|
129
|
+
**Step 1 -- Create External Credential** (Setup > Security > Named Credentials > External Credentials)
|
|
130
|
+
|
|
131
|
+
- Principal type: `Named Principal` (shared credential) or `Per User` (each user authenticates separately)
|
|
132
|
+
- Authentication Protocol: OAuth 2.0, JWT Token, Basic, Custom Header, etc.
|
|
133
|
+
|
|
134
|
+
**Step 2 -- Create Named Credential** referencing the External Credential
|
|
135
|
+
|
|
136
|
+
- Name: `OrderManagementAPI`
|
|
137
|
+
- URL: `https://erp.example.com`
|
|
138
|
+
- External Credential: select from Step 1
|
|
139
|
+
|
|
140
|
+
**Step 3 -- Grant permission** via Permission Set on the External Credential Principal
|
|
141
|
+
|
|
142
|
+
```apex
|
|
143
|
+
// Usage in Apex — identical to legacy Named Credentials
|
|
144
|
+
req.setEndpoint('callout:OrderManagementAPI/api/v2/orders');
|
|
145
|
+
// Auth headers injected automatically
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Legacy Named Credentials
|
|
149
|
+
|
|
150
|
+
Still valid for simpler use cases. Combine URL + auth in one config.
|
|
151
|
+
|
|
152
|
+
```apex
|
|
153
|
+
req.setEndpoint('callout:LegacyNamedCred/endpoint');
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## SOAP Callout via WSDL2Apex
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
# Generate Apex stub from WSDL
|
|
162
|
+
sf generate apex from-wsdl \
|
|
163
|
+
--file path/to/service.wsdl \
|
|
164
|
+
--output-dir force-app/main/default/classes
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Manual SOAP Callout (when WSDL2Apex is impractical)
|
|
168
|
+
|
|
169
|
+
```apex
|
|
170
|
+
public with sharing class SoapIntegration {
|
|
171
|
+
|
|
172
|
+
private static final String SOAP_ENDPOINT = 'callout:LegacySoapService/service';
|
|
173
|
+
|
|
174
|
+
public static String invokeMethod(String accountNumber) {
|
|
175
|
+
String safeAccountNumber = escapeXml(accountNumber);
|
|
176
|
+
|
|
177
|
+
String soapBody =
|
|
178
|
+
'<?xml version="1.0" encoding="UTF-8"?>' +
|
|
179
|
+
'<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"' +
|
|
180
|
+
' xmlns:leg="http://legacy.example.com/service">' +
|
|
181
|
+
' <soapenv:Header/>' +
|
|
182
|
+
' <soapenv:Body>' +
|
|
183
|
+
' <leg:GetAccountDetails>' +
|
|
184
|
+
' <leg:AccountNumber>' + safeAccountNumber + '</leg:AccountNumber>' +
|
|
185
|
+
' </leg:GetAccountDetails>' +
|
|
186
|
+
' </soapenv:Body>' +
|
|
187
|
+
'</soapenv:Envelope>';
|
|
188
|
+
|
|
189
|
+
HttpRequest req = new HttpRequest();
|
|
190
|
+
req.setEndpoint(SOAP_ENDPOINT);
|
|
191
|
+
req.setMethod('POST');
|
|
192
|
+
req.setHeader('Content-Type', 'text/xml; charset=UTF-8');
|
|
193
|
+
req.setHeader('SOAPAction', '"GetAccountDetails"');
|
|
194
|
+
req.setBody(soapBody);
|
|
195
|
+
req.setTimeout(15000);
|
|
196
|
+
|
|
197
|
+
HttpResponse res = new Http().send(req);
|
|
198
|
+
|
|
199
|
+
if (res.getStatusCode() != 200) {
|
|
200
|
+
throw new CalloutException('SOAP error: ' + res.getStatus());
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
Dom.Document doc = res.getBodyDocument();
|
|
204
|
+
Dom.XmlNode root = doc.getRootElement();
|
|
205
|
+
Dom.XmlNode body = root.getChildElement(
|
|
206
|
+
'Body', 'http://schemas.xmlsoap.org/soap/envelope/');
|
|
207
|
+
Dom.XmlNode responseEl = body.getChildElements()[0];
|
|
208
|
+
Dom.XmlNode result = responseEl.getChildElement(
|
|
209
|
+
'Result', 'http://legacy.example.com/service');
|
|
210
|
+
|
|
211
|
+
return result != null ? result.getText() : null;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
private static String escapeXml(String input) {
|
|
215
|
+
if (String.isBlank(input)) return input;
|
|
216
|
+
return input
|
|
217
|
+
.replace('&', '&')
|
|
218
|
+
.replace('<', '<')
|
|
219
|
+
.replace('>', '>')
|
|
220
|
+
.replace('"', '"')
|
|
221
|
+
.replace('\'', ''');
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## Inbound REST API
|
|
229
|
+
|
|
230
|
+
```apex
|
|
231
|
+
@RestResource(urlMapping='/v1/accounts/*')
|
|
232
|
+
global with sharing class AccountRestService {
|
|
233
|
+
|
|
234
|
+
@HttpGet
|
|
235
|
+
global static AccountDTO getAccount() {
|
|
236
|
+
RestRequest req = RestContext.request;
|
|
237
|
+
RestResponse res = RestContext.response;
|
|
238
|
+
|
|
239
|
+
String accountId = req.requestURI.substring(
|
|
240
|
+
req.requestURI.lastIndexOf('/') + 1);
|
|
241
|
+
|
|
242
|
+
if (String.isBlank(accountId)) {
|
|
243
|
+
res.statusCode = 400;
|
|
244
|
+
return new AccountDTO(null, null, 'Invalid Account ID');
|
|
245
|
+
}
|
|
246
|
+
try {
|
|
247
|
+
Id.valueOf(accountId);
|
|
248
|
+
} catch (StringException e) {
|
|
249
|
+
res.statusCode = 400;
|
|
250
|
+
return new AccountDTO(null, null, 'Invalid Account ID');
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
List<Account> accounts = [
|
|
254
|
+
SELECT Id, Name, Phone, BillingCity, BillingCountry
|
|
255
|
+
FROM Account WHERE Id = :accountId
|
|
256
|
+
WITH USER_MODE LIMIT 1
|
|
257
|
+
];
|
|
258
|
+
|
|
259
|
+
if (accounts.isEmpty()) {
|
|
260
|
+
res.statusCode = 404;
|
|
261
|
+
return new AccountDTO(null, null, 'Account not found');
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
res.statusCode = 200;
|
|
265
|
+
return new AccountDTO(accounts[0].Id, accounts[0].Name, null);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
@HttpPost
|
|
269
|
+
global static AccountDTO createAccount() {
|
|
270
|
+
RestRequest req = RestContext.request;
|
|
271
|
+
RestResponse res = RestContext.response;
|
|
272
|
+
|
|
273
|
+
try {
|
|
274
|
+
Map<String, Object> body =
|
|
275
|
+
(Map<String, Object>) JSON.deserializeUntyped(
|
|
276
|
+
req.requestBody.toString());
|
|
277
|
+
|
|
278
|
+
Account acc = new Account();
|
|
279
|
+
acc.Name = (String) body.get('name');
|
|
280
|
+
acc.Phone = (String) body.get('phone');
|
|
281
|
+
acc.BillingCity = (String) body.get('billingCity');
|
|
282
|
+
|
|
283
|
+
if (String.isBlank(acc.Name)) {
|
|
284
|
+
res.statusCode = 400;
|
|
285
|
+
return new AccountDTO(null, null, 'Account name is required');
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
Database.insert(acc, AccessLevel.USER_MODE);
|
|
289
|
+
res.statusCode = 201;
|
|
290
|
+
return new AccountDTO(acc.Id, acc.Name, 'Account created');
|
|
291
|
+
|
|
292
|
+
} catch (DmlException e) {
|
|
293
|
+
res.statusCode = 400;
|
|
294
|
+
return new AccountDTO(null, null, 'DML error: ' + e.getDmlMessage(0));
|
|
295
|
+
} catch (JSONException e) {
|
|
296
|
+
res.statusCode = 400;
|
|
297
|
+
return new AccountDTO(null, null, 'Invalid JSON: ' + e.getMessage());
|
|
298
|
+
} catch (System.SecurityException e) {
|
|
299
|
+
res.statusCode = 403;
|
|
300
|
+
return new AccountDTO(null, null, 'Insufficient access: ' + e.getMessage());
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
global class AccountDTO {
|
|
305
|
+
global String id;
|
|
306
|
+
global String name;
|
|
307
|
+
global String message;
|
|
308
|
+
|
|
309
|
+
global AccountDTO(String id, String name, String message) {
|
|
310
|
+
this.id = id;
|
|
311
|
+
this.name = name;
|
|
312
|
+
this.message = message;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
---
|
|
319
|
+
|
|
320
|
+
## HttpCalloutMock -- Testing Callouts
|
|
321
|
+
|
|
322
|
+
### Single Endpoint Mock
|
|
323
|
+
|
|
324
|
+
```apex
|
|
325
|
+
@IsTest
|
|
326
|
+
public class OrderIntegrationMock implements HttpCalloutMock {
|
|
327
|
+
|
|
328
|
+
private Integer statusCode;
|
|
329
|
+
private String body;
|
|
330
|
+
|
|
331
|
+
public OrderIntegrationMock(Integer statusCode, String body) {
|
|
332
|
+
this.statusCode = statusCode;
|
|
333
|
+
this.body = body;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
public HttpResponse respond(HttpRequest req) {
|
|
337
|
+
HttpResponse res = new HttpResponse();
|
|
338
|
+
res.setStatusCode(statusCode);
|
|
339
|
+
res.setHeader('Content-Type', 'application/json');
|
|
340
|
+
res.setBody(body);
|
|
341
|
+
return res;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
### Multi-Endpoint Mock
|
|
347
|
+
|
|
348
|
+
```apex
|
|
349
|
+
@IsTest
|
|
350
|
+
public class MultiRequestMock implements HttpCalloutMock {
|
|
351
|
+
|
|
352
|
+
private Map<String, HttpCalloutMock> mocks = new Map<String, HttpCalloutMock>();
|
|
353
|
+
|
|
354
|
+
public void addMock(String endpoint, HttpCalloutMock mock) {
|
|
355
|
+
mocks.put(endpoint, mock);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
public HttpResponse respond(HttpRequest req) {
|
|
359
|
+
String endpoint = req.getEndpoint();
|
|
360
|
+
for (String key : mocks.keySet()) {
|
|
361
|
+
if (endpoint.contains(key)) {
|
|
362
|
+
return mocks.get(key).respond(req);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
throw new IllegalArgumentException('Unexpected callout to: ' + endpoint);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
### Using Mocks in Tests
|
|
371
|
+
|
|
372
|
+
```apex
|
|
373
|
+
@IsTest
|
|
374
|
+
static void testCreateOrder_success_returnsOrderId() {
|
|
375
|
+
String mockResponse =
|
|
376
|
+
'{"orderId":"ERP-001","status":"PENDING","message":"Order created"}';
|
|
377
|
+
Test.setMock(HttpCalloutMock.class,
|
|
378
|
+
new OrderIntegrationMock(201, mockResponse));
|
|
379
|
+
|
|
380
|
+
Test.startTest();
|
|
381
|
+
OrderManagementIntegration.OrderRequest req =
|
|
382
|
+
new OrderManagementIntegration.OrderRequest();
|
|
383
|
+
req.productCode = 'PROD-001';
|
|
384
|
+
req.quantity = 5;
|
|
385
|
+
req.unitPrice = 99.99;
|
|
386
|
+
|
|
387
|
+
OrderManagementIntegration.OrderResponse res =
|
|
388
|
+
OrderManagementIntegration.createExternalOrder(req);
|
|
389
|
+
Test.stopTest();
|
|
390
|
+
|
|
391
|
+
System.assertEquals('ERP-001', res.orderId);
|
|
392
|
+
System.assertEquals('PENDING', res.status);
|
|
393
|
+
}
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
---
|
|
397
|
+
|
|
398
|
+
## External Client Apps (Spring '26 GA)
|
|
399
|
+
|
|
400
|
+
External Client Apps replace Connected Apps for new OAuth-based integrations.
|
|
401
|
+
|
|
402
|
+
| Feature | Connected App | External Client App |
|
|
403
|
+
|---------|--------------|---------------------|
|
|
404
|
+
| Status | Existing -- maintain as-is | New standard for Spring '26+ |
|
|
405
|
+
| Location | Setup > App Manager | Setup > External Client App Manager |
|
|
406
|
+
| Metadata type | `ConnectedApp` | `ExternalClientApplication` |
|
|
407
|
+
| Recommendation | Keep existing; do not migrate | Use for all new integrations |
|
|
408
|
+
|
|
409
|
+
### Creating an External Client App (Metadata)
|
|
410
|
+
|
|
411
|
+
```xml
|
|
412
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
413
|
+
<ExternalClientApplication xmlns="http://soap.sforce.com/2006/04/metadata">
|
|
414
|
+
<contactEmail>integrations@example.com</contactEmail>
|
|
415
|
+
<description>OAuth integration with Warehouse Management System</description>
|
|
416
|
+
<distributionState>Global</distributionState>
|
|
417
|
+
<label>Warehouse Integration</label>
|
|
418
|
+
<name>WarehouseIntegration</name>
|
|
419
|
+
</ExternalClientApplication>
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
```bash
|
|
423
|
+
sf project deploy start \
|
|
424
|
+
--metadata ExternalClientApplication:WarehouseIntegration \
|
|
425
|
+
--target-org Production
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
### Key Steps for New Integrations
|
|
429
|
+
|
|
430
|
+
1. Create External Client App in Setup > External Client Apps
|
|
431
|
+
2. Configure OAuth scopes and callback URLs
|
|
432
|
+
3. Reference the External Client App in your Named Credential's External Credential
|
|
433
|
+
4. Test OAuth flow in sandbox before production deployment
|
|
434
|
+
|
|
435
|
+
---
|
|
436
|
+
|
|
437
|
+
## Retry Pattern with Queueable
|
|
438
|
+
|
|
439
|
+
For callouts with transient failures, implement a retry Queueable:
|
|
440
|
+
|
|
441
|
+
```apex
|
|
442
|
+
public class RetryCalloutQueueable implements Queueable, Database.AllowsCallouts {
|
|
443
|
+
|
|
444
|
+
private Id recordId;
|
|
445
|
+
private Integer attemptNumber;
|
|
446
|
+
private static final Integer MAX_ATTEMPTS = 3;
|
|
447
|
+
|
|
448
|
+
public RetryCalloutQueueable(Id recordId, Integer attemptNumber) {
|
|
449
|
+
this.recordId = recordId;
|
|
450
|
+
this.attemptNumber = attemptNumber;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
public void execute(QueueableContext ctx) {
|
|
454
|
+
try {
|
|
455
|
+
ExternalIntegration.sync(recordId);
|
|
456
|
+
update new MyRecord__c(
|
|
457
|
+
Id = recordId,
|
|
458
|
+
Sync_Status__c = 'Success',
|
|
459
|
+
Last_Sync__c = Datetime.now()
|
|
460
|
+
);
|
|
461
|
+
} catch (Exception e) {
|
|
462
|
+
if (attemptNumber < MAX_ATTEMPTS) {
|
|
463
|
+
System.enqueueJob(
|
|
464
|
+
new RetryCalloutQueueable(recordId, attemptNumber + 1));
|
|
465
|
+
} else {
|
|
466
|
+
update new MyRecord__c(
|
|
467
|
+
Id = recordId,
|
|
468
|
+
Sync_Status__c = 'Failed',
|
|
469
|
+
Sync_Error__c = e.getMessage()
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
---
|
|
478
|
+
|
|
479
|
+
## Related
|
|
480
|
+
|
|
481
|
+
- Agent: `sf-integration-agent` -- for interactive, in-depth guidance
|
|
482
|
+
- Constraints: sf-apex-constraints, sf-security-constraints
|
|
483
|
+
- Reference: @../_reference/INTEGRATION_PATTERNS.md
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sf-lwc-constraints
|
|
3
|
+
description: "Enforce LWC naming, security, accessibility, and performance rules. Use when writing or reviewing ANY LWC component, template, or JS controller. Do NOT use for Apex, Aura, Visualforce, or Flow."
|
|
4
|
+
origin: SCC
|
|
5
|
+
user-invocable: false
|
|
6
|
+
allowed-tools: Read, Grep, Glob
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# LWC Constraints
|
|
10
|
+
|
|
11
|
+
## When to Use
|
|
12
|
+
|
|
13
|
+
This skill auto-activates when writing, reviewing, or modifying any Lightning Web Component, template, or JavaScript controller. It enforces naming, security, accessibility, and performance rules for all LWC artifacts.
|
|
14
|
+
|
|
15
|
+
Hard rules for Lightning Web Component development. Violations must be flagged
|
|
16
|
+
or fixed before code is considered complete.
|
|
17
|
+
|
|
18
|
+
For lifecycle hooks, reactive property rules, wire service rules, event
|
|
19
|
+
propagation, and slot rules see @../_reference/LWC_PATTERNS.md.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Naming Conventions
|
|
24
|
+
|
|
25
|
+
| Element | Convention | Example |
|
|
26
|
+
|---------|-----------|---------|
|
|
27
|
+
| Component folder & files | camelCase | `accountList/accountList.js` |
|
|
28
|
+
| HTML tag in markup | kebab-case with namespace | `<c-account-list>` |
|
|
29
|
+
| Public properties (`@api`) | camelCase in JS, kebab-case in HTML | JS: `@api maxRecords` / HTML: `max-records="10"` |
|
|
30
|
+
| Private fields | camelCase, prefix `_` for backing fields | `_wiredResult`, `isLoading` |
|
|
31
|
+
| Custom events | lowercase, no spaces, hyphens allowed | `opportunityselect`, `row-action` |
|
|
32
|
+
| CSS classes | SLDS utilities or BEM for custom | `slds-p-around_medium`, `card__title` |
|
|
33
|
+
|
|
34
|
+
**Never** use PascalCase or UPPER_CASE for component folder names or file names.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Never Do
|
|
39
|
+
|
|
40
|
+
| Rule | Reason |
|
|
41
|
+
|------|--------|
|
|
42
|
+
| Direct DOM manipulation (`document.createElement`, `innerHTML`, `appendChild`) | Breaks Shadow DOM encapsulation and LWS security model. Use template directives (`lwc:if`, `for:each`) and reactive state instead. |
|
|
43
|
+
| Imperative Apex when `@wire` works | Wire provides caching via LDS, automatic re-provisioning on param change, and offline support. Use imperative calls only for DML or non-cacheable operations. |
|
|
44
|
+
| Inline styles (`style="color:red"`) | Violates SLDS theming, breaks dark mode (SLDS 2.0), and bypasses component-scoped CSS. Use SLDS utility classes or CSS custom properties. |
|
|
45
|
+
| `querySelector('#id')` for shadow or slotted elements | Element IDs are transformed at render time. Use `this.template.querySelector('.class')` for shadow elements, `this.querySelector('.class')` for light DOM. |
|
|
46
|
+
| Update `@wire` config inside `renderedCallback()` | Creates an infinite re-render loop: render -> renderedCallback -> wire config change -> re-provision -> re-render. |
|
|
47
|
+
| Mutate `@wire` result data directly | Wire data is immutable (read-only). Shallow-copy (`{...data}` or `[...data]`) before mutating. |
|
|
48
|
+
| Use deprecated `if:true` / `if:false` directives | Replaced by `lwc:if` / `lwc:elseif` / `lwc:else` (GA). Deprecated directives will be removed. |
|
|
49
|
+
| Skip `super()` in `constructor()` | Required by the Custom Elements spec. Omitting it throws a runtime error. |
|
|
50
|
+
| Access DOM in `constructor()` | Shadow DOM is not attached yet. No `this.template`, no child elements, no attributes. |
|
|
51
|
+
| Dispatch events with `bubbles:true, composed:true` without namespacing | Composed events cross all shadow boundaries and become public API. Namespace the event name to avoid collisions. |
|
|
52
|
+
| Use `localStorage`/`sessionStorage` without namespace awareness | Under LWS, storage is namespace-scoped. Keys that worked under Locker may behave differently. |
|
|
53
|
+
| Heavy logic in `renderedCallback()` without a guard | Fires on every render cycle. Unguarded work causes performance degradation or infinite loops if it updates reactive state. |
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Always Do
|
|
58
|
+
|
|
59
|
+
| Rule | Detail |
|
|
60
|
+
|------|--------|
|
|
61
|
+
| Use `@api` for all public properties and methods | Defines the component's contract. Without `@api`, properties are private and invisible to parent components and App Builder. |
|
|
62
|
+
| Use kebab-case for component tags in HTML | `<c-account-list>`, never `<c-accountList>`. The framework maps kebab-case tags to camelCase folder names automatically. |
|
|
63
|
+
| Use camelCase for JS properties | `@api maxRecords`, not `@api max_records`. Kebab-to-camelCase mapping applies when attributes are set in HTML. |
|
|
64
|
+
| Provide `alternative-text` on `<lightning-spinner>` | Required for screen-reader accessibility. Omitting it is a WCAG violation. |
|
|
65
|
+
| Provide `label` on all `<lightning-input>` elements | Required for accessibility. Use `variant="label-hidden"` if visual label must be hidden, but never omit `label` entirely. |
|
|
66
|
+
| Set `key` on iterated elements | `for:each` requires a unique, stable `key` attribute (use record `Id`, not array index). Missing keys cause rendering bugs. |
|
|
67
|
+
| Use `lwc:if` / `lwc:elseif` / `lwc:else` for conditionals | Modern directive (GA). Always prefer over deprecated `if:true`/`if:false`. |
|
|
68
|
+
| Implement `errorCallback(error, stack)` or wrap with error boundary | Catches descendant lifecycle and render errors. Without it, a child error crashes the entire component tree. |
|
|
69
|
+
| Clean up in `disconnectedCallback()` | Remove `window`/`document` event listeners, `unsubscribe()` from message channels, clear timers. Prevents memory leaks. |
|
|
70
|
+
| Store bound references for event listeners | `this._boundHandler = this.handler.bind(this)` in constructor, so `removeEventListener` gets the same reference as `addEventListener`. |
|
|
71
|
+
| Set `<apiVersion>66.0</apiVersion>` in `.js-meta.xml` | Target Spring '26. Older API versions miss LWS, SLDS 2.0 styling hooks, and new wire adapters. |
|
|
72
|
+
| Use `WITH USER_MODE` in backing Apex SOQL | Enforces FLS/CRUD. Required for security review. The LWC itself does not bypass object permissions, but its Apex must not either. |
|
|
73
|
+
| Prefer SLDS utility classes over custom CSS | Ensures dark mode compatibility (SLDS 2.0), consistent spacing, and responsive layout. |
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Anti-Pattern Table
|
|
78
|
+
|
|
79
|
+
| Anti-Pattern | Correct Pattern | Why |
|
|
80
|
+
|-------------|----------------|-----|
|
|
81
|
+
| `document.querySelector('.my-class')` | `this.template.querySelector('.my-class')` | Shadow DOM scoping. `document` queries cannot reach inside shadow roots. |
|
|
82
|
+
| `@track` on primitives or simple reassignments | Remove `@track`; all fields are reactive since Spring '20 | `@track` is only needed for deep observation of object/array mutations in place. Overuse adds confusion. |
|
|
83
|
+
| Imperative Apex in `connectedCallback` for cacheable reads | `@wire(getRecord, ...)` or `@wire(apexMethod, ...)` | Wire caches via LDS, re-provisions automatically, and works offline. Imperative calls bypass the cache. |
|
|
84
|
+
| `setTimeout` to wait for DOM | `renderedCallback()` with a guard flag | The DOM is guaranteed available in `renderedCallback`. `setTimeout` is fragile and race-prone. |
|
|
85
|
+
| `event.target` inside shadow boundary listeners | `event.currentTarget` or `event.target.dataset.*` | `event.target` is retargeted at shadow boundaries. Use `currentTarget` for the element the listener is attached to. |
|
|
86
|
+
| Hardcoded field API names as strings | Import from `@salesforce/schema` | Schema imports give compile-time validation, refactoring safety, and dependency tracking. |
|
|
87
|
+
| `console.log` left in production code | Remove or guard with `IS_DEBUG` flag | Console output is visible to end users in browser dev tools and degrades performance at scale. |
|
|
88
|
+
| Fetching all records then paginating client-side | Server-side pagination via `OFFSET`/`LIMIT` or SOQL cursors (v66.0+) | Client-side pagination loads all data upfront; breaks on large datasets. |
|
|
89
|
+
| `if:true={property}` | `lwc:if={property}` | `if:true`/`if:false` are deprecated. `lwc:if` supports `elseif`/`else` chains and will be the only option going forward. |
|
|
90
|
+
| Inline `style="width:100px"` | CSS class or SLDS utility `slds-size_full` | Inline styles bypass theming, break dark mode, and cannot be overridden by styling hooks. |
|
|
91
|
+
| Missing `.js-meta.xml` `<targets>` | Declare all intended surfaces (`RecordPage`, `AppPage`, etc.) | Component will not appear in App Builder or Flow without explicit target declarations. |
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Wire Service Constraints
|
|
96
|
+
|
|
97
|
+
1. **All** `$`-prefixed reactive params must be defined (non-`undefined`) before the wire adapter fires.
|
|
98
|
+
2. Wire data arrives **non-deterministically** -- never assume it is available in `connectedCallback`.
|
|
99
|
+
3. Wire chains (`$record.data.fieldName`) are valid but add latency; keep chains to two hops max.
|
|
100
|
+
4. Use `refreshApex(wiredResult)` after imperative DML -- store the full wire result reference (`_wiredResult`) for this purpose.
|
|
101
|
+
5. For `@wire` with Apex, the Apex method must be annotated `@AuraEnabled(cacheable=true)`.
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Event Constraints
|
|
106
|
+
|
|
107
|
+
1. Default to `{ bubbles: false, composed: false }` for parent-child communication.
|
|
108
|
+
2. Always use `CustomEvent` with a `detail` property for data payload -- never custom properties on the event object.
|
|
109
|
+
3. Parent listens with `on{eventname}` (all lowercase, no hyphens in the `on` prefix).
|
|
110
|
+
4. If `composed: true` is required, namespace the event name (e.g., `myns__recordupdate`) to prevent collisions.
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Accessibility Constraints
|
|
115
|
+
|
|
116
|
+
1. Every interactive element must be keyboard-operable (focusable, activatable via Enter/Space).
|
|
117
|
+
2. Every `<lightning-spinner>` must have `alternative-text`.
|
|
118
|
+
3. Every `<lightning-input>` must have `label` (use `variant="label-hidden"` to visually hide).
|
|
119
|
+
4. Every `<lightning-icon>` used as a meaningful indicator must have `alternative-text`; decorative icons should set `aria-hidden="true"`.
|
|
120
|
+
5. Color must never be the sole indicator of state -- pair with text, icon, or ARIA attributes.
|
|
121
|
+
6. Custom components exposing interactive regions should set appropriate `role` and `aria-*` attributes.
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Meta XML Constraints
|
|
126
|
+
|
|
127
|
+
1. Always include `<apiVersion>66.0</apiVersion>` (Spring '26).
|
|
128
|
+
2. Set `<isExposed>true</isExposed>` for any component intended for App Builder, Flow, or Experience Cloud.
|
|
129
|
+
3. Declare every intended surface in `<targets>`.
|
|
130
|
+
4. Expose configurable `@api` properties via `<targetConfigs>` with `label`, `type`, and `description`.
|