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,377 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sf-flow-development
|
|
3
|
+
description: "Salesforce Flow development — flow types, patterns, bulkification, error handling, testing, subflows, Flow vs Apex. Use when building or reviewing Flows. Do NOT use for pure Apex or Platform Event architecture."
|
|
4
|
+
origin: SCC
|
|
5
|
+
user-invocable: false
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Salesforce Flow Development
|
|
9
|
+
|
|
10
|
+
Procedures for building, testing, and maintaining Salesforce Flows. Flow type details, governor limits, bulkification rules, and the Flow vs Apex decision matrix live in the reference file.
|
|
11
|
+
|
|
12
|
+
@../_reference/FLOW_PATTERNS.md
|
|
13
|
+
|
|
14
|
+
## When to Use
|
|
15
|
+
|
|
16
|
+
- Building or debugging Salesforce Flows (record-triggered, screen, scheduled, autolaunched, orchestration)
|
|
17
|
+
- Deciding between Flow and Apex for automation
|
|
18
|
+
- Configuring fault paths, bulkification, or governor-safe flow patterns
|
|
19
|
+
- Implementing subflows, versioning, or modular flow design
|
|
20
|
+
- Migrating from Process Builder to Record-Triggered Flows
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Before Save: Field Updates (Recommended Pattern)
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
Flow: ACC_SetAccountPriority (Before Save, Account, before insert OR before update)
|
|
28
|
+
Entry Condition: {!$Record.AnnualRevenue} >= 1000000
|
|
29
|
+
|
|
30
|
+
Steps:
|
|
31
|
+
1. Assignment: {!$Record.Priority__c} = "High"
|
|
32
|
+
2. Assignment: {!$Record.Tier__c} = "Enterprise"
|
|
33
|
+
|
|
34
|
+
Notes:
|
|
35
|
+
- No Get Records, Update Records, or Create Records elements allowed
|
|
36
|
+
- No DML of any kind
|
|
37
|
+
- Very fast — runs in memory before DB write
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## After Save: Related Record Creation
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
Flow: OPP_CreateNegotiationTask (After Save, Opportunity, after update)
|
|
44
|
+
Entry Condition: {!$Record.StageName} = "Negotiation"
|
|
45
|
+
AND {!$Record.StageName} <> {!$Record__Prior.StageName}
|
|
46
|
+
|
|
47
|
+
Steps:
|
|
48
|
+
1. Get Records: User WHERE Id = {!$Record.OwnerId}
|
|
49
|
+
2. Create Records: Task (
|
|
50
|
+
Subject = "Review negotiation checklist"
|
|
51
|
+
WhatId = {!$Record.Id}
|
|
52
|
+
OwnerId = {!$Record.OwnerId}
|
|
53
|
+
ActivityDate = TODAY() + 3
|
|
54
|
+
Priority = "High"
|
|
55
|
+
)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Before Save vs After Save Decision
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
Need to update the SAME record's fields?
|
|
64
|
+
-> Before Save (no DML counted, faster)
|
|
65
|
+
Note: Before Save flows cannot use Get Records or DML elements,
|
|
66
|
+
but CAN access parent fields via cross-object formula references
|
|
67
|
+
(e.g., {!$Record.Account.Name}).
|
|
68
|
+
|
|
69
|
+
Need to create/update OTHER records?
|
|
70
|
+
-> After Save (can do DML on other objects)
|
|
71
|
+
|
|
72
|
+
Need to send emails or call external services?
|
|
73
|
+
-> After Save (outbound actions need committed record)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Bulkification Patterns
|
|
79
|
+
|
|
80
|
+
### DML Outside the Loop (Critical)
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
BAD:
|
|
84
|
+
Loop: For each Contact in {!Contacts}
|
|
85
|
+
|
|
|
86
|
+
+-> Update Records: Contact <- DML inside loop = 1 DML per record
|
|
87
|
+
|
|
88
|
+
GOOD:
|
|
89
|
+
Loop: For each Contact in {!Contacts}
|
|
90
|
+
|
|
|
91
|
+
+-> Assignment: Add to contactsToUpdate collection
|
|
92
|
+
|
|
93
|
+
Update Records: {!contactsToUpdate} <- Single DML for entire collection
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### SOQL Outside the Loop
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
BAD:
|
|
100
|
+
Loop: For each Opportunity in {!Opportunities}
|
|
101
|
+
|
|
|
102
|
+
+-> Get Records: Account WHERE Id = {!loopVar.AccountId} <- SOQL per record
|
|
103
|
+
|
|
104
|
+
GOOD:
|
|
105
|
+
Get Records: Account WHERE Id IN {!opportunityAccountIds} <- single query
|
|
106
|
+
|
|
107
|
+
Loop: For each Opportunity in {!Opportunities}
|
|
108
|
+
|
|
|
109
|
+
+-> (Use data from pre-fetched collection, no SOQL)
|
|
110
|
+
|
|
111
|
+
Heap warning: If pre-fetch returns 10K+ records, the collection may
|
|
112
|
+
exceed the heap size limit (see @../_reference/GOVERNOR_LIMITS.md). Filter aggressively, or move to Batch Apex.
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## Subflows: Modular Design
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
Parent Flow: SCR_CustomerOnboarding (Screen Flow)
|
|
121
|
+
|
|
|
122
|
+
+-- Subflow: VAL_ValidateAddress (Autolaunched)
|
|
123
|
+
| Input: {!streetAddress}, {!city}, {!state}
|
|
124
|
+
| Output: {!isValidAddress}, {!normalizedAddress}
|
|
125
|
+
|
|
|
126
|
+
+-- Subflow: INT_CreateCRMAccount (Autolaunched)
|
|
127
|
+
| Input: {!accountData}
|
|
128
|
+
| Output: {!newAccountId}
|
|
129
|
+
|
|
|
130
|
+
+-- Subflow: NOT_SendWelcomeEmail (Autolaunched)
|
|
131
|
+
Input: {!accountId}, {!contactEmail}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Variables passed between parent and subflow must be marked as available for input/output in the subflow definition.
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Error Handling: Fault Connectors
|
|
139
|
+
|
|
140
|
+
Every element that can fail should have a fault connector.
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
[Update Records: Update Account]
|
|
144
|
+
|
|
|
145
|
+
+-(SUCCESS)-> [Next Step]
|
|
146
|
+
|
|
|
147
|
+
+-(FAULT)-> [Assignment: errorMessage =
|
|
148
|
+
"Failed: " + {!$Flow.FaultMessage}]
|
|
149
|
+
|
|
|
150
|
+
+-> [Screen: Display Error to User]
|
|
151
|
+
OR
|
|
152
|
+
+-> [Create Records: Error_Log__c]
|
|
153
|
+
OR
|
|
154
|
+
+-> [Custom Notification: Notify Admin]
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Get Records Best Practices
|
|
160
|
+
|
|
161
|
+
```
|
|
162
|
+
Element: Get Records
|
|
163
|
+
Object: Contact
|
|
164
|
+
Filter: AccountId = {!$Record.Id} AND IsActive__c = true
|
|
165
|
+
Store All Records: No (when you need just one) / Yes (for a collection)
|
|
166
|
+
|
|
167
|
+
Tips:
|
|
168
|
+
- Add filter conditions to reduce records returned
|
|
169
|
+
- Select only the fields you need
|
|
170
|
+
- Use "Only the first record" when you need a single result
|
|
171
|
+
- Filter on indexed fields when possible (Id, OwnerId, ExternalId__c)
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## Custom Labels and Custom Metadata in Flows
|
|
177
|
+
|
|
178
|
+
### Custom Labels
|
|
179
|
+
|
|
180
|
+
```
|
|
181
|
+
Formula Resource:
|
|
182
|
+
Name: FormattedMessage
|
|
183
|
+
Data Type: Text
|
|
184
|
+
Value: {!$Label.Welcome_Message} & " " & {!ContactRecord.FirstName}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Custom Metadata Types
|
|
188
|
+
|
|
189
|
+
```
|
|
190
|
+
Get Records:
|
|
191
|
+
Object: Service_Config__mdt
|
|
192
|
+
Filter: DeveloperName = "Production"
|
|
193
|
+
Fields: Endpoint_URL__c, Timeout_Ms__c
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## Testing Flows
|
|
199
|
+
|
|
200
|
+
### Apex Test Coverage for Record-Triggered Flows
|
|
201
|
+
|
|
202
|
+
```apex
|
|
203
|
+
@IsTest
|
|
204
|
+
public class OppNegotiationFlowTest {
|
|
205
|
+
|
|
206
|
+
@TestSetup
|
|
207
|
+
static void setup() {
|
|
208
|
+
Account acc = TestDataFactory.createAccount('Test Account');
|
|
209
|
+
insert acc;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
@IsTest
|
|
213
|
+
static void testNegotiationTask_stageChanged_createsTask() {
|
|
214
|
+
Account acc = [SELECT Id FROM Account LIMIT 1];
|
|
215
|
+
|
|
216
|
+
Opportunity opp = new Opportunity(
|
|
217
|
+
Name = 'Test Opp',
|
|
218
|
+
AccountId = acc.Id,
|
|
219
|
+
StageName = 'Prospecting',
|
|
220
|
+
CloseDate = Date.today().addDays(30),
|
|
221
|
+
Amount = 50000
|
|
222
|
+
);
|
|
223
|
+
insert opp;
|
|
224
|
+
|
|
225
|
+
Test.startTest();
|
|
226
|
+
opp.StageName = 'Negotiation';
|
|
227
|
+
update opp;
|
|
228
|
+
Test.stopTest();
|
|
229
|
+
|
|
230
|
+
List<Task> tasks = [SELECT Subject, Priority
|
|
231
|
+
FROM Task WHERE WhatId = :opp.Id];
|
|
232
|
+
System.assertEquals(1, tasks.size());
|
|
233
|
+
System.assertEquals('Review negotiation checklist', tasks[0].Subject);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
@IsTest
|
|
237
|
+
static void testNegotiationTask_bulk_createsTasksForAll() {
|
|
238
|
+
Account acc = [SELECT Id FROM Account LIMIT 1];
|
|
239
|
+
|
|
240
|
+
List<Opportunity> opps = new List<Opportunity>();
|
|
241
|
+
for (Integer i = 0; i < 200; i++) {
|
|
242
|
+
opps.add(new Opportunity(
|
|
243
|
+
Name = 'Bulk Opp ' + i,
|
|
244
|
+
AccountId = acc.Id,
|
|
245
|
+
StageName = 'Prospecting',
|
|
246
|
+
CloseDate = Date.today().addDays(30),
|
|
247
|
+
Amount = 1000 * i
|
|
248
|
+
));
|
|
249
|
+
}
|
|
250
|
+
insert opps;
|
|
251
|
+
|
|
252
|
+
Test.startTest();
|
|
253
|
+
for (Opportunity opp : opps) { opp.StageName = 'Negotiation'; }
|
|
254
|
+
update opps;
|
|
255
|
+
Test.stopTest();
|
|
256
|
+
|
|
257
|
+
Integer taskCount = [SELECT COUNT() FROM Task WHERE WhatId IN :opps];
|
|
258
|
+
System.assertEquals(200, taskCount);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
## New Screen Flow Components (Spring '26)
|
|
266
|
+
|
|
267
|
+
Five native Screen Flow components that eliminate the need for custom LWC in common patterns:
|
|
268
|
+
|
|
269
|
+
| Component | Best For |
|
|
270
|
+
|-----------|----------|
|
|
271
|
+
| **Kanban Board** | Stage/status reassignment wizards, visual prioritization |
|
|
272
|
+
| **Message** | Confirmation prompts, warnings before destructive steps |
|
|
273
|
+
| **File Preview** | Document review steps in approval flows |
|
|
274
|
+
| **Integrated Approval** | Wizard-based approval flows where reps submit and track |
|
|
275
|
+
| **Editable Data Table** | Mass-edit child records within a guided wizard |
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
## Record-Triggered Flows on Files (Spring '26)
|
|
280
|
+
|
|
281
|
+
Record-Triggered Flows now support `ContentVersion` as a triggering object.
|
|
282
|
+
|
|
283
|
+
```
|
|
284
|
+
Object: ContentVersion
|
|
285
|
+
Trigger: A record is created or updated
|
|
286
|
+
Entry Conditions:
|
|
287
|
+
- {!$Record.FileExtension} = 'pdf'
|
|
288
|
+
- {!$Record.Title} Does NOT Contain 'DRAFT'
|
|
289
|
+
|
|
290
|
+
Use cases:
|
|
291
|
+
- Auto-tag documents based on filename patterns
|
|
292
|
+
- Notify a team when a contract document is uploaded
|
|
293
|
+
- Create audit log when a sensitive file type is uploaded
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
---
|
|
297
|
+
|
|
298
|
+
## Orchestration Flow Design
|
|
299
|
+
|
|
300
|
+
Orchestration Flows manage long-running, multi-step processes spanning hours or days.
|
|
301
|
+
|
|
302
|
+
```
|
|
303
|
+
Orchestration Flow: Employee_Onboarding
|
|
304
|
+
+-- Stage 1: "System Setup"
|
|
305
|
+
| +-- Background Step: Create user account (Autolaunched Flow)
|
|
306
|
+
| +-- Background Step: Provision email (Autolaunched Flow)
|
|
307
|
+
+-- Stage 2: "HR Review"
|
|
308
|
+
| +-- Interactive Step: Complete onboarding form (Screen Flow -> HR Manager)
|
|
309
|
+
+-- Stage 3: "Equipment & Access"
|
|
310
|
+
| +-- Interactive Step: Order equipment (Screen Flow -> IT Team queue)
|
|
311
|
+
| +-- Background Step: Grant system permissions (Autolaunched Flow)
|
|
312
|
+
+-- Stage 4: "Orientation"
|
|
313
|
+
+-- Interactive Step: Schedule orientation (Screen Flow -> Employee)
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### Key Design Principles
|
|
317
|
+
|
|
318
|
+
- Stages execute sequentially -- Stage 2 waits for all Stage 1 steps
|
|
319
|
+
- Steps within a stage can run in parallel
|
|
320
|
+
- Interactive steps pause the orchestration until the assigned user completes the Screen Flow
|
|
321
|
+
- Background steps run automatically using Autolaunched Flows
|
|
322
|
+
- Define fault paths on background steps to prevent stalled orchestrations
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
## Flow Versioning and Activation
|
|
327
|
+
|
|
328
|
+
```
|
|
329
|
+
Each Flow has:
|
|
330
|
+
- Multiple versions (v1, v2, v3...)
|
|
331
|
+
- Only ONE active version at a time
|
|
332
|
+
- Inactive versions can be tested without activating
|
|
333
|
+
|
|
334
|
+
Deployment:
|
|
335
|
+
- Deploying creates a new version
|
|
336
|
+
- Set status: Active in Flow metadata to auto-activate:
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
```xml
|
|
340
|
+
<status>Active</status>
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
```bash
|
|
344
|
+
sf project deploy start \
|
|
345
|
+
--metadata "Flow:OPP_CreateNegotiationTask" \
|
|
346
|
+
--target-org myOrg
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
---
|
|
350
|
+
|
|
351
|
+
## Process Builder Migration
|
|
352
|
+
|
|
353
|
+
```
|
|
354
|
+
1. IDENTIFY — List all active Process Builders
|
|
355
|
+
sf data query -q "SELECT Id, MasterLabel, ProcessType FROM Flow
|
|
356
|
+
WHERE ProcessType = 'Workflow' AND Status = 'Active'" --json
|
|
357
|
+
|
|
358
|
+
2. ANALYZE — Document trigger object, criteria, and actions
|
|
359
|
+
|
|
360
|
+
3. CREATE — Build equivalent Record-Triggered Flow:
|
|
361
|
+
- Before Save Flow for field updates
|
|
362
|
+
- After Save Flow for create records, email alerts
|
|
363
|
+
|
|
364
|
+
4. TEST — Deploy to sandbox, test with bulk data (200+ records)
|
|
365
|
+
|
|
366
|
+
5. DEACTIVATE — Turn off Process Builder, monitor 1-2 weeks
|
|
367
|
+
|
|
368
|
+
6. CLEANUP — Delete via destructiveChanges.xml
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
---
|
|
372
|
+
|
|
373
|
+
## Related
|
|
374
|
+
|
|
375
|
+
- Agent: `sf-flow-agent` -- for interactive, in-depth guidance
|
|
376
|
+
- Constraints: sf-apex-constraints (for Apex-invoked flows)
|
|
377
|
+
- Reference: @../_reference/FLOW_PATTERNS.md
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sf-governor-limits
|
|
3
|
+
description: >-
|
|
4
|
+
Use when hitting Salesforce governor limits in Apex — SOQL, DML, heap,
|
|
5
|
+
CPU optimization, async offloading, bulk processing strategies.
|
|
6
|
+
Do NOT use for general Apex or LWC patterns.
|
|
7
|
+
origin: SCC
|
|
8
|
+
user-invocable: false
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Governor Limits — Working Within Limits
|
|
12
|
+
|
|
13
|
+
Salesforce governor limits prevent any single transaction from monopolizing shared infrastructure. Hitting a limit throws `System.LimitException`, which cannot be caught. This skill covers strategies and optimization procedures. See @../_reference/GOVERNOR_LIMITS.md for the complete limits reference table.
|
|
14
|
+
|
|
15
|
+
@../_reference/GOVERNOR_LIMITS.md
|
|
16
|
+
|
|
17
|
+
## When to Use
|
|
18
|
+
|
|
19
|
+
- A transaction throws `System.LimitException` in production or tests
|
|
20
|
+
- Reviewing Apex code for SOQL/DML-in-loop anti-patterns before deployment
|
|
21
|
+
- Batch or trigger jobs intermittently hitting CPU or heap limits
|
|
22
|
+
- Profiling a large data operation processing 200+ records
|
|
23
|
+
- Optimizing a slow trigger handler or service class for bulk safety
|
|
24
|
+
- Preparing for an ISV security review that checks governor limit compliance
|
|
25
|
+
|
|
26
|
+
## Checking Limits Programmatically
|
|
27
|
+
|
|
28
|
+
The `Limits` class provides real-time limit consumption. Use it defensively before expensive operations.
|
|
29
|
+
|
|
30
|
+
```apex
|
|
31
|
+
public class LimitAwareProcessor {
|
|
32
|
+
public void processIfSafe(List<Account> accounts) {
|
|
33
|
+
Integer soqlRemaining = Limits.getLimitQueries() - Limits.getQueries();
|
|
34
|
+
if (soqlRemaining < 5) {
|
|
35
|
+
System.debug(LoggingLevel.WARN,
|
|
36
|
+
'Low SOQL budget: ' + Limits.getQueries() + '/' +
|
|
37
|
+
Limits.getLimitQueries() + '. Deferring to async.');
|
|
38
|
+
if (Limits.getQueueableJobs() < Limits.getLimitQueueableJobs()
|
|
39
|
+
&& !System.isBatch() && !System.isFuture()) {
|
|
40
|
+
System.enqueueJob(new AccountProcessorJob(extractIds(accounts)));
|
|
41
|
+
}
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
Integer dmlRemaining = Limits.getLimitDmlStatements() - Limits.getDmlStatements();
|
|
46
|
+
if (dmlRemaining < 3) {
|
|
47
|
+
throw new LimitSafetyException(
|
|
48
|
+
'Insufficient DML budget. ' +
|
|
49
|
+
Limits.getDmlStatements() + '/' + Limits.getLimitDmlStatements() + ' used.'
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
Integer heapUsed = Limits.getHeapSize();
|
|
54
|
+
Integer heapLimit = Limits.getLimitHeapSize();
|
|
55
|
+
if (heapUsed > heapLimit * 0.75) {
|
|
56
|
+
System.debug(LoggingLevel.WARN, 'Heap at 75% — skipping optional enrichment.');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
processInternal(accounts);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
public class LimitSafetyException extends Exception {}
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## SOQL Limit Strategies
|
|
69
|
+
|
|
70
|
+
### Query Once, Store in Map
|
|
71
|
+
|
|
72
|
+
The most impactful single optimization in Salesforce development.
|
|
73
|
+
|
|
74
|
+
```apex
|
|
75
|
+
public void processAccounts(List<Account> accounts) {
|
|
76
|
+
Set<Id> accountIds = new Set<Id>();
|
|
77
|
+
for (Account acc : accounts) accountIds.add(acc.Id);
|
|
78
|
+
|
|
79
|
+
Map<Id, List<Contact>> contactsByAccountId = new Map<Id, List<Contact>>();
|
|
80
|
+
for (Contact con : [SELECT Id, Email, AccountId FROM Contact WHERE AccountId IN :accountIds]) {
|
|
81
|
+
if (!contactsByAccountId.containsKey(con.AccountId)) {
|
|
82
|
+
contactsByAccountId.put(con.AccountId, new List<Contact>());
|
|
83
|
+
}
|
|
84
|
+
contactsByAccountId.get(con.AccountId).add(con);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
for (Account acc : accounts) {
|
|
88
|
+
List<Contact> contacts = contactsByAccountId.get(acc.Id);
|
|
89
|
+
if (contacts != null) sendEmailsToContacts(contacts);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Use Aggregate Queries
|
|
95
|
+
|
|
96
|
+
```apex
|
|
97
|
+
// 1 query instead of 3
|
|
98
|
+
Map<String, Integer> countsByType = new Map<String, Integer>();
|
|
99
|
+
for (AggregateResult ar : [
|
|
100
|
+
SELECT Type, COUNT(Id) cnt FROM Account WHERE Type != null GROUP BY Type
|
|
101
|
+
]) {
|
|
102
|
+
countsByType.put((String) ar.get('Type'), (Integer) ar.get('cnt'));
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## DML Limit Strategies
|
|
109
|
+
|
|
110
|
+
### Collect Records, Single DML After Loop
|
|
111
|
+
|
|
112
|
+
```apex
|
|
113
|
+
public void setDefaultTitle(List<Contact> contacts) {
|
|
114
|
+
List<Contact> toUpdate = new List<Contact>();
|
|
115
|
+
for (Contact con : contacts) {
|
|
116
|
+
if (String.isBlank(con.Title)) {
|
|
117
|
+
toUpdate.add(new Contact(Id = con.Id, Title = 'Business Contact'));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (!toUpdate.isEmpty()) {
|
|
121
|
+
update toUpdate; // 1 DML regardless of list size
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Partial Success DML
|
|
127
|
+
|
|
128
|
+
```apex
|
|
129
|
+
List<Database.SaveResult> results = Database.insert(accounts, false);
|
|
130
|
+
List<String> errors = new List<String>();
|
|
131
|
+
for (Integer i = 0; i < results.size(); i++) {
|
|
132
|
+
if (!results[i].isSuccess()) {
|
|
133
|
+
for (Database.Error err : results[i].getErrors()) {
|
|
134
|
+
errors.add(accounts[i].Name + ': ' + err.getMessage());
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (!errors.isEmpty()) ErrorLogger.log(errors);
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Unit of Work Pattern
|
|
142
|
+
|
|
143
|
+
For complex transactions creating related records across multiple objects, collect all records and commit once to minimize DML statements.
|
|
144
|
+
|
|
145
|
+
```apex
|
|
146
|
+
SimpleUnitOfWork uow = new SimpleUnitOfWork();
|
|
147
|
+
Account acc = new Account(Name = 'New Customer');
|
|
148
|
+
uow.registerNew(acc);
|
|
149
|
+
Contact primary = new Contact(LastName = 'Primary');
|
|
150
|
+
uow.registerNew(primary, Contact.AccountId, acc);
|
|
151
|
+
uow.commitWork(); // Minimal DML: inserts parent first, resolves IDs, then children
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Heap Limit Strategies
|
|
157
|
+
|
|
158
|
+
### Select Minimal Fields
|
|
159
|
+
|
|
160
|
+
```apex
|
|
161
|
+
// Use aggregate for count — do not load full sObjects just to count
|
|
162
|
+
Integer count = [SELECT COUNT() FROM Account];
|
|
163
|
+
|
|
164
|
+
// Select only fields the calling code needs
|
|
165
|
+
List<Account> accounts = [SELECT Id, Name FROM Account WHERE Id IN :accountIds];
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Use Maps Instead of Parallel Lists
|
|
169
|
+
|
|
170
|
+
```apex
|
|
171
|
+
// Single data structure instead of two synchronized lists
|
|
172
|
+
Map<Id, String> accountNameById = new Map<Id, String>();
|
|
173
|
+
for (Account acc : accounts) {
|
|
174
|
+
accountNameById.put(acc.Id, acc.Name);
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Nullify Large References When Done
|
|
179
|
+
|
|
180
|
+
```apex
|
|
181
|
+
List<SObject> largeDataSet = loadLargeDataSet();
|
|
182
|
+
List<String> results = extractResults(largeDataSet);
|
|
183
|
+
largeDataSet = null; // Eligible for garbage collection
|
|
184
|
+
saveResults(results);
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## CPU Time Strategies
|
|
190
|
+
|
|
191
|
+
### Use Maps Instead of Nested Loops
|
|
192
|
+
|
|
193
|
+
```apex
|
|
194
|
+
// O(n) using Set lookup instead of O(n^2) nested loop
|
|
195
|
+
Set<Id> validAccountIds = new Set<Id>(new Map<Id, Account>(validAccounts).keySet());
|
|
196
|
+
List<Contact> orphaned = new List<Contact>();
|
|
197
|
+
for (Contact con : contacts) {
|
|
198
|
+
if (!validAccountIds.contains(con.AccountId)) {
|
|
199
|
+
orphaned.add(con);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Use String.join Instead of Concatenation in Loops
|
|
205
|
+
|
|
206
|
+
```apex
|
|
207
|
+
List<String> names = new List<String>();
|
|
208
|
+
for (Account acc : accounts) names.add(acc.Name);
|
|
209
|
+
String output = String.join(names, ', '); // One allocation
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Offload to Async When CPU Is High
|
|
213
|
+
|
|
214
|
+
```apex
|
|
215
|
+
if (Limits.getCpuTime() > 8000) { // 8 of 10 seconds used
|
|
216
|
+
System.enqueueJob(new AccountProcessorJob(
|
|
217
|
+
new List<Id>(new Map<Id, Account>(accounts).keySet())
|
|
218
|
+
));
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
performExpensiveProcessing(accounts);
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## Callout Limit Strategies
|
|
227
|
+
|
|
228
|
+
### @future(callout=true) from Triggers
|
|
229
|
+
|
|
230
|
+
Triggers cannot make synchronous callouts. Use @future to defer.
|
|
231
|
+
|
|
232
|
+
```apex
|
|
233
|
+
public class AccountERPSyncService {
|
|
234
|
+
@future(callout=true)
|
|
235
|
+
public static void syncToERP(List<Id> accountIds) {
|
|
236
|
+
List<Account> accounts = [
|
|
237
|
+
SELECT Id, Name, External_Id__c FROM Account WHERE Id IN :accountIds
|
|
238
|
+
];
|
|
239
|
+
for (Account acc : accounts) ERPClient.syncAccount(acc);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### Queueable for Callout Chains
|
|
245
|
+
|
|
246
|
+
```apex
|
|
247
|
+
public class SequentialCalloutJob implements Queueable, Database.AllowsCallouts {
|
|
248
|
+
private final List<Id> accountIds;
|
|
249
|
+
private final Integer currentIndex;
|
|
250
|
+
|
|
251
|
+
public SequentialCalloutJob(List<Id> accountIds) { this(accountIds, 0); }
|
|
252
|
+
private SequentialCalloutJob(List<Id> accountIds, Integer startIndex) {
|
|
253
|
+
this.accountIds = accountIds;
|
|
254
|
+
this.currentIndex = startIndex;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
public void execute(QueueableContext ctx) {
|
|
258
|
+
final Integer CALLOUTS_PER_JOB = 90;
|
|
259
|
+
Integer end = Math.min(currentIndex + CALLOUTS_PER_JOB, accountIds.size());
|
|
260
|
+
for (Integer i = currentIndex; i < end; i++) {
|
|
261
|
+
ERPClient.syncAccount(accountIds[i]);
|
|
262
|
+
}
|
|
263
|
+
if (end < accountIds.size()) {
|
|
264
|
+
System.enqueueJob(new SequentialCalloutJob(accountIds, end));
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
## Async Decision Tree
|
|
273
|
+
|
|
274
|
+
```
|
|
275
|
+
User action that can complete in < 5s? → Synchronous
|
|
276
|
+
Processing > 200 records? → Batch Apex
|
|
277
|
+
Callouts from a trigger? → @future(callout=true) or Queueable
|
|
278
|
+
CPU exceeding 8000ms regularly? → Profile first; then Queueable
|
|
279
|
+
Recurring scheduled operation? → Schedulable wrapping Batch/Queueable
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
## Testing at Limits
|
|
285
|
+
|
|
286
|
+
```apex
|
|
287
|
+
@isTest
|
|
288
|
+
static void testTrigger_200RecordBulkInsert_noLimitException() {
|
|
289
|
+
List<Account> accounts = new List<Account>();
|
|
290
|
+
for (Integer i = 0; i < 200; i++) {
|
|
291
|
+
accounts.add(new Account(Name = 'Bulk Test ' + i, Type = 'Customer'));
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
Test.startTest(); // Resets governor limit counters
|
|
295
|
+
insert accounts;
|
|
296
|
+
Test.stopTest();
|
|
297
|
+
|
|
298
|
+
System.assertEquals(200,
|
|
299
|
+
[SELECT COUNT() FROM Account WHERE Type = 'Customer'],
|
|
300
|
+
'All 200 accounts should be inserted');
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
@isTest
|
|
304
|
+
static void testService_queriesStayWithinLimits() {
|
|
305
|
+
List<Account> accounts = TestDataFactory.createAccounts(50);
|
|
306
|
+
|
|
307
|
+
Test.startTest();
|
|
308
|
+
Integer queriesBefore = Limits.getQueries();
|
|
309
|
+
AccountService.processAll(new Map<Id, Account>(accounts).keySet());
|
|
310
|
+
Integer queriesUsed = Limits.getQueries() - queriesBefore;
|
|
311
|
+
Test.stopTest();
|
|
312
|
+
|
|
313
|
+
System.assert(queriesUsed <= 5,
|
|
314
|
+
'processAll() should use at most 5 SOQL queries. Actual: ' + queriesUsed);
|
|
315
|
+
}
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
---
|
|
319
|
+
|
|
320
|
+
## Related
|
|
321
|
+
|
|
322
|
+
- **Agent**: `sf-apex-agent` — For interactive, in-depth guidance
|
|
323
|
+
- **Constraints**: `sf-apex-constraints` — Hard rules for Apex governor compliance
|