opencode-skills-collection 1.0.186 → 1.0.187

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 (71) hide show
  1. package/bundled-skills/.antigravity-install-manifest.json +5 -1
  2. package/bundled-skills/3d-web-experience/SKILL.md +152 -37
  3. package/bundled-skills/agent-evaluation/SKILL.md +1088 -26
  4. package/bundled-skills/agent-memory-systems/SKILL.md +1037 -25
  5. package/bundled-skills/agent-tool-builder/SKILL.md +668 -16
  6. package/bundled-skills/ai-agents-architect/SKILL.md +271 -31
  7. package/bundled-skills/ai-product/SKILL.md +716 -26
  8. package/bundled-skills/ai-wrapper-product/SKILL.md +450 -44
  9. package/bundled-skills/algolia-search/SKILL.md +867 -15
  10. package/bundled-skills/autonomous-agents/SKILL.md +1033 -26
  11. package/bundled-skills/aws-serverless/SKILL.md +1046 -35
  12. package/bundled-skills/azure-functions/SKILL.md +1318 -19
  13. package/bundled-skills/browser-automation/SKILL.md +1065 -28
  14. package/bundled-skills/browser-extension-builder/SKILL.md +159 -32
  15. package/bundled-skills/bullmq-specialist/SKILL.md +347 -16
  16. package/bundled-skills/clerk-auth/SKILL.md +796 -15
  17. package/bundled-skills/computer-use-agents/SKILL.md +1870 -28
  18. package/bundled-skills/context-window-management/SKILL.md +271 -18
  19. package/bundled-skills/conversation-memory/SKILL.md +453 -24
  20. package/bundled-skills/crewai/SKILL.md +252 -46
  21. package/bundled-skills/discord-bot-architect/SKILL.md +1207 -34
  22. package/bundled-skills/docs/integrations/jetski-cortex.md +3 -3
  23. package/bundled-skills/docs/integrations/jetski-gemini-loader/README.md +1 -1
  24. package/bundled-skills/docs/maintainers/repo-growth-seo.md +3 -3
  25. package/bundled-skills/docs/maintainers/skills-update-guide.md +1 -1
  26. package/bundled-skills/docs/users/bundles.md +1 -1
  27. package/bundled-skills/docs/users/claude-code-skills.md +1 -1
  28. package/bundled-skills/docs/users/gemini-cli-skills.md +1 -1
  29. package/bundled-skills/docs/users/getting-started.md +1 -1
  30. package/bundled-skills/docs/users/kiro-integration.md +1 -1
  31. package/bundled-skills/docs/users/usage.md +4 -4
  32. package/bundled-skills/docs/users/visual-guide.md +4 -4
  33. package/bundled-skills/email-systems/SKILL.md +646 -26
  34. package/bundled-skills/faf-expert/SKILL.md +221 -0
  35. package/bundled-skills/faf-wizard/SKILL.md +252 -0
  36. package/bundled-skills/file-uploads/SKILL.md +212 -11
  37. package/bundled-skills/firebase/SKILL.md +646 -16
  38. package/bundled-skills/gcp-cloud-run/SKILL.md +1117 -32
  39. package/bundled-skills/graphql/SKILL.md +1026 -27
  40. package/bundled-skills/hubspot-integration/SKILL.md +804 -19
  41. package/bundled-skills/idea-darwin/SKILL.md +120 -0
  42. package/bundled-skills/inngest/SKILL.md +431 -16
  43. package/bundled-skills/interactive-portfolio/SKILL.md +342 -44
  44. package/bundled-skills/langfuse/SKILL.md +296 -41
  45. package/bundled-skills/langgraph/SKILL.md +259 -50
  46. package/bundled-skills/micro-saas-launcher/SKILL.md +343 -44
  47. package/bundled-skills/neon-postgres/SKILL.md +572 -15
  48. package/bundled-skills/nextjs-supabase-auth/SKILL.md +269 -21
  49. package/bundled-skills/notion-template-business/SKILL.md +371 -44
  50. package/bundled-skills/personal-tool-builder/SKILL.md +537 -44
  51. package/bundled-skills/plaid-fintech/SKILL.md +825 -19
  52. package/bundled-skills/prompt-caching/SKILL.md +438 -25
  53. package/bundled-skills/rag-engineer/SKILL.md +271 -29
  54. package/bundled-skills/salesforce-development/SKILL.md +912 -19
  55. package/bundled-skills/satori/SKILL.md +54 -0
  56. package/bundled-skills/scroll-experience/SKILL.md +381 -44
  57. package/bundled-skills/segment-cdp/SKILL.md +817 -19
  58. package/bundled-skills/shopify-apps/SKILL.md +1475 -19
  59. package/bundled-skills/slack-bot-builder/SKILL.md +1162 -28
  60. package/bundled-skills/telegram-bot-builder/SKILL.md +152 -37
  61. package/bundled-skills/telegram-mini-app/SKILL.md +445 -44
  62. package/bundled-skills/trigger-dev/SKILL.md +916 -27
  63. package/bundled-skills/twilio-communications/SKILL.md +1310 -28
  64. package/bundled-skills/upstash-qstash/SKILL.md +898 -27
  65. package/bundled-skills/vercel-deployment/SKILL.md +637 -39
  66. package/bundled-skills/viral-generator-builder/SKILL.md +132 -37
  67. package/bundled-skills/voice-agents/SKILL.md +937 -27
  68. package/bundled-skills/voice-ai-development/SKILL.md +375 -46
  69. package/bundled-skills/workflow-automation/SKILL.md +982 -29
  70. package/bundled-skills/zapier-make-patterns/SKILL.md +772 -27
  71. package/package.json +1 -1
@@ -1,13 +1,20 @@
1
1
  ---
2
2
  name: salesforce-development
3
- description: "Use @wire decorator for reactive data binding with Lightning Data Service or Apex methods. @wire fits LWC's reactive architecture and enables Salesforce performance optimizations."
3
+ description: Expert patterns for Salesforce platform development including
4
+ Lightning Web Components (LWC), Apex triggers and classes, REST/Bulk APIs,
5
+ Connected Apps, and Salesforce DX with scratch orgs and 2nd generation
6
+ packages (2GP).
4
7
  risk: safe
5
- source: "vibeship-spawner-skills (Apache 2.0)"
6
- date_added: "2026-02-27"
8
+ source: vibeship-spawner-skills (Apache 2.0)
9
+ date_added: 2026-02-27
7
10
  ---
8
11
 
9
12
  # Salesforce Development
10
13
 
14
+ Expert patterns for Salesforce platform development including Lightning Web
15
+ Components (LWC), Apex triggers and classes, REST/Bulk APIs, Connected Apps,
16
+ and Salesforce DX with scratch orgs and 2nd generation packages (2GP).
17
+
11
18
  ## Patterns
12
19
 
13
20
  ### Lightning Web Component with Wire Service
@@ -16,38 +23,924 @@ Use @wire decorator for reactive data binding with Lightning Data Service
16
23
  or Apex methods. @wire fits LWC's reactive architecture and enables
17
24
  Salesforce performance optimizations.
18
25
 
26
+ // myComponent.js
27
+ import { LightningElement, wire, api } from 'lwc';
28
+ import { getRecord, getFieldValue } from 'lightning/uiRecordApi';
29
+ import getRelatedRecords from '@salesforce/apex/MyController.getRelatedRecords';
30
+ import ACCOUNT_NAME from '@salesforce/schema/Account.Name';
31
+ import ACCOUNT_INDUSTRY from '@salesforce/schema/Account.Industry';
32
+
33
+ const FIELDS = [ACCOUNT_NAME, ACCOUNT_INDUSTRY];
34
+
35
+ export default class MyComponent extends LightningElement {
36
+ @api recordId; // Passed from parent or record page
37
+
38
+ // Wire to Lightning Data Service (preferred for single records)
39
+ @wire(getRecord, { recordId: '$recordId', fields: FIELDS })
40
+ account;
41
+
42
+ // Wire to Apex method (for complex queries)
43
+ @wire(getRelatedRecords, { accountId: '$recordId' })
44
+ wiredRecords({ error, data }) {
45
+ if (data) {
46
+ this.relatedRecords = data;
47
+ this.error = undefined;
48
+ } else if (error) {
49
+ this.error = error;
50
+ this.relatedRecords = undefined;
51
+ }
52
+ }
53
+
54
+ get accountName() {
55
+ return getFieldValue(this.account.data, ACCOUNT_NAME);
56
+ }
57
+
58
+ get isLoading() {
59
+ return !this.account.data && !this.account.error;
60
+ }
61
+
62
+ // Reactive: changing recordId automatically re-fetches
63
+ }
64
+
65
+ // myComponent.html
66
+ <template>
67
+ <lightning-card title={accountName}>
68
+ <template if:true={isLoading}>
69
+ <lightning-spinner alternative-text="Loading"></lightning-spinner>
70
+ </template>
71
+
72
+ <template if:true={account.data}>
73
+ <p>Industry: {industry}</p>
74
+ </template>
75
+
76
+ <template if:true={error}>
77
+ <p class="slds-text-color_error">{error.body.message}</p>
78
+ </template>
79
+ </lightning-card>
80
+ </template>
81
+
82
+ // MyController.cls
83
+ public with sharing class MyController {
84
+ @AuraEnabled(cacheable=true)
85
+ public static List<Contact> getRelatedRecords(Id accountId) {
86
+ return [
87
+ SELECT Id, Name, Email, Phone
88
+ FROM Contact
89
+ WHERE AccountId = :accountId
90
+ WITH SECURITY_ENFORCED
91
+ LIMIT 100
92
+ ];
93
+ }
94
+ }
95
+
96
+ ### Context
97
+
98
+ - building LWC components
99
+ - fetching Salesforce data
100
+ - reactive UI
101
+
19
102
  ### Bulkified Apex Trigger with Handler Pattern
20
103
 
21
104
  Apex triggers must be bulkified to handle 200+ records per transaction.
22
105
  Use handler pattern for separation of concerns, testability, and
23
106
  recursion prevention.
24
107
 
108
+ // AccountTrigger.trigger
109
+ trigger AccountTrigger on Account (
110
+ before insert, before update, before delete,
111
+ after insert, after update, after delete, after undelete
112
+ ) {
113
+ new AccountTriggerHandler().run();
114
+ }
115
+
116
+ // TriggerHandler.cls (base class)
117
+ public virtual class TriggerHandler {
118
+ // Recursion prevention
119
+ private static Set<String> executedHandlers = new Set<String>();
120
+
121
+ public void run() {
122
+ String handlerName = String.valueOf(this).split(':')[0];
123
+
124
+ // Prevent recursion
125
+ String contextKey = handlerName + '_' + Trigger.operationType;
126
+ if (executedHandlers.contains(contextKey)) {
127
+ return;
128
+ }
129
+ executedHandlers.add(contextKey);
130
+
131
+ switch on Trigger.operationType {
132
+ when BEFORE_INSERT { this.beforeInsert(); }
133
+ when BEFORE_UPDATE { this.beforeUpdate(); }
134
+ when BEFORE_DELETE { this.beforeDelete(); }
135
+ when AFTER_INSERT { this.afterInsert(); }
136
+ when AFTER_UPDATE { this.afterUpdate(); }
137
+ when AFTER_DELETE { this.afterDelete(); }
138
+ when AFTER_UNDELETE { this.afterUndelete(); }
139
+ }
140
+ }
141
+
142
+ // Override in child classes
143
+ protected virtual void beforeInsert() {}
144
+ protected virtual void beforeUpdate() {}
145
+ protected virtual void beforeDelete() {}
146
+ protected virtual void afterInsert() {}
147
+ protected virtual void afterUpdate() {}
148
+ protected virtual void afterDelete() {}
149
+ protected virtual void afterUndelete() {}
150
+ }
151
+
152
+ // AccountTriggerHandler.cls
153
+ public class AccountTriggerHandler extends TriggerHandler {
154
+ private List<Account> newAccounts;
155
+ private List<Account> oldAccounts;
156
+ private Map<Id, Account> newMap;
157
+ private Map<Id, Account> oldMap;
158
+
159
+ public AccountTriggerHandler() {
160
+ this.newAccounts = (List<Account>) Trigger.new;
161
+ this.oldAccounts = (List<Account>) Trigger.old;
162
+ this.newMap = (Map<Id, Account>) Trigger.newMap;
163
+ this.oldMap = (Map<Id, Account>) Trigger.oldMap;
164
+ }
165
+
166
+ protected override void afterInsert() {
167
+ createDefaultContacts();
168
+ notifySlack();
169
+ }
170
+
171
+ protected override void afterUpdate() {
172
+ handleIndustryChange();
173
+ }
174
+
175
+ // BULKIFIED: Query once, update once
176
+ private void createDefaultContacts() {
177
+ List<Contact> contactsToInsert = new List<Contact>();
178
+
179
+ for (Account acc : newAccounts) {
180
+ if (acc.Type == 'Prospect') {
181
+ contactsToInsert.add(new Contact(
182
+ AccountId = acc.Id,
183
+ LastName = 'Primary Contact',
184
+ Email = 'contact@' + acc.Website
185
+ ));
186
+ }
187
+ }
188
+
189
+ if (!contactsToInsert.isEmpty()) {
190
+ insert contactsToInsert; // Single DML for all
191
+ }
192
+ }
193
+
194
+ private void handleIndustryChange() {
195
+ Set<Id> changedAccountIds = new Set<Id>();
196
+
197
+ for (Account acc : newAccounts) {
198
+ Account oldAcc = oldMap.get(acc.Id);
199
+ if (acc.Industry != oldAcc.Industry) {
200
+ changedAccountIds.add(acc.Id);
201
+ }
202
+ }
203
+
204
+ if (!changedAccountIds.isEmpty()) {
205
+ // Queue async processing for heavy work
206
+ System.enqueueJob(new IndustryChangeQueueable(changedAccountIds));
207
+ }
208
+ }
209
+
210
+ private void notifySlack() {
211
+ // Offload callouts to async
212
+ List<Id> accountIds = new List<Id>(newMap.keySet());
213
+ System.enqueueJob(new SlackNotificationQueueable(accountIds));
214
+ }
215
+ }
216
+
217
+ ### Context
218
+
219
+ - apex triggers
220
+ - data operations
221
+ - automation
222
+
25
223
  ### Queueable Apex for Async Processing
26
224
 
27
225
  Use Queueable Apex for async processing with support for non-primitive
28
226
  types, monitoring via AsyncApexJob, and job chaining. Limit: 50 jobs
29
227
  per transaction, 1 child job when chaining.
30
228
 
31
- ## Anti-Patterns
229
+ // IndustryChangeQueueable.cls
230
+ public class IndustryChangeQueueable implements Queueable, Database.AllowsCallouts {
231
+ private Set<Id> accountIds;
232
+ private Integer retryCount;
233
+
234
+ public IndustryChangeQueueable(Set<Id> accountIds) {
235
+ this(accountIds, 0);
236
+ }
237
+
238
+ public IndustryChangeQueueable(Set<Id> accountIds, Integer retryCount) {
239
+ this.accountIds = accountIds;
240
+ this.retryCount = retryCount;
241
+ }
242
+
243
+ public void execute(QueueableContext context) {
244
+ try {
245
+ // Query with fresh data
246
+ List<Account> accounts = [
247
+ SELECT Id, Name, Industry, OwnerId
248
+ FROM Account
249
+ WHERE Id IN :accountIds
250
+ WITH SECURITY_ENFORCED
251
+ ];
252
+
253
+ // Process and make callout
254
+ for (Account acc : accounts) {
255
+ syncToExternalSystem(acc);
256
+ }
257
+
258
+ // Update records
259
+ updateRelatedOpportunities(accountIds);
260
+
261
+ } catch (Exception e) {
262
+ handleError(e);
263
+ }
264
+ }
265
+
266
+ private void syncToExternalSystem(Account acc) {
267
+ HttpRequest req = new HttpRequest();
268
+ req.setEndpoint('callout:ExternalCRM/accounts');
269
+ req.setMethod('POST');
270
+ req.setHeader('Content-Type', 'application/json');
271
+ req.setBody(JSON.serialize(new Map<String, Object>{
272
+ 'salesforceId' => acc.Id,
273
+ 'name' => acc.Name,
274
+ 'industry' => acc.Industry
275
+ }));
276
+
277
+ Http http = new Http();
278
+ HttpResponse res = http.send(req);
279
+
280
+ if (res.getStatusCode() != 200 && res.getStatusCode() != 201) {
281
+ throw new CalloutException('Sync failed: ' + res.getBody());
282
+ }
283
+ }
284
+
285
+ private void updateRelatedOpportunities(Set<Id> accIds) {
286
+ List<Opportunity> oppsToUpdate = [
287
+ SELECT Id, Industry__c, AccountId
288
+ FROM Opportunity
289
+ WHERE AccountId IN :accIds
290
+ WITH SECURITY_ENFORCED
291
+ ];
292
+
293
+ Map<Id, Account> accountMap = new Map<Id, Account>([
294
+ SELECT Id, Industry FROM Account WHERE Id IN :accIds
295
+ ]);
296
+
297
+ for (Opportunity opp : oppsToUpdate) {
298
+ opp.Industry__c = accountMap.get(opp.AccountId).Industry;
299
+ }
300
+
301
+ if (!oppsToUpdate.isEmpty()) {
302
+ update oppsToUpdate;
303
+ }
304
+ }
305
+
306
+ private void handleError(Exception e) {
307
+ // Log error
308
+ System.debug(LoggingLevel.ERROR, 'Queueable failed: ' + e.getMessage());
309
+
310
+ // Retry with exponential backoff (max 3 retries)
311
+ if (retryCount < 3) {
312
+ // Chain new job for retry
313
+ System.enqueueJob(new IndustryChangeQueueable(accountIds, retryCount + 1));
314
+ } else {
315
+ // Create error record for monitoring
316
+ insert new Integration_Error__c(
317
+ Type__c = 'Industry Sync',
318
+ Message__c = e.getMessage(),
319
+ Stack_Trace__c = e.getStackTraceString(),
320
+ Record_Ids__c = String.join(new List<Id>(accountIds), ',')
321
+ );
322
+ }
323
+ }
324
+ }
325
+
326
+ ### Context
327
+
328
+ - async processing
329
+ - long-running operations
330
+ - callouts from triggers
331
+
332
+ ### REST API Integration with Connected App
333
+
334
+ External integrations use Connected Apps with OAuth 2.0. JWT Bearer flow
335
+ for server-to-server, Web Server flow for user-facing apps. Always use
336
+ Named Credentials for secure callout configuration.
337
+
338
+ // Node.js - JWT Bearer Flow (server-to-server)
339
+ import jwt from 'jsonwebtoken';
340
+ import fs from 'fs';
341
+
342
+ class SalesforceClient {
343
+ private accessToken: string | null = null;
344
+ private instanceUrl: string | null = null;
345
+ private tokenExpiry: number = 0;
346
+
347
+ constructor(
348
+ private clientId: string,
349
+ private username: string,
350
+ private privateKeyPath: string,
351
+ private loginUrl: string = 'https://login.salesforce.com'
352
+ ) {}
353
+
354
+ async authenticate(): Promise<void> {
355
+ // Check if token is still valid (5 min buffer)
356
+ if (this.accessToken && Date.now() < this.tokenExpiry - 300000) {
357
+ return;
358
+ }
359
+
360
+ const privateKey = fs.readFileSync(this.privateKeyPath, 'utf8');
361
+
362
+ // Create JWT assertion
363
+ const claim = {
364
+ iss: this.clientId,
365
+ sub: this.username,
366
+ aud: this.loginUrl,
367
+ exp: Math.floor(Date.now() / 1000) + 300 // 5 minutes
368
+ };
369
+
370
+ const assertion = jwt.sign(claim, privateKey, { algorithm: 'RS256' });
371
+
372
+ // Exchange JWT for access token
373
+ const response = await fetch(`${this.loginUrl}/services/oauth2/token`, {
374
+ method: 'POST',
375
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
376
+ body: new URLSearchParams({
377
+ grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
378
+ assertion
379
+ })
380
+ });
381
+
382
+ if (!response.ok) {
383
+ const error = await response.json();
384
+ throw new Error(`Auth failed: ${error.error_description}`);
385
+ }
386
+
387
+ const data = await response.json();
388
+ this.accessToken = data.access_token;
389
+ this.instanceUrl = data.instance_url;
390
+ this.tokenExpiry = Date.now() + 7200000; // 2 hours
391
+ }
392
+
393
+ async query(soql: string): Promise<any> {
394
+ await this.authenticate();
395
+
396
+ const response = await fetch(
397
+ `${this.instanceUrl}/services/data/v59.0/query?q=${encodeURIComponent(soql)}`,
398
+ {
399
+ headers: {
400
+ 'Authorization': `Bearer ${this.accessToken}`,
401
+ 'Content-Type': 'application/json'
402
+ }
403
+ }
404
+ );
405
+
406
+ if (!response.ok) {
407
+ await this.handleError(response);
408
+ }
409
+
410
+ return response.json();
411
+ }
412
+
413
+ async createRecord(sobject: string, data: object): Promise<any> {
414
+ await this.authenticate();
415
+
416
+ const response = await fetch(
417
+ `${this.instanceUrl}/services/data/v59.0/sobjects/${sobject}`,
418
+ {
419
+ method: 'POST',
420
+ headers: {
421
+ 'Authorization': `Bearer ${this.accessToken}`,
422
+ 'Content-Type': 'application/json'
423
+ },
424
+ body: JSON.stringify(data)
425
+ }
426
+ );
427
+
428
+ if (!response.ok) {
429
+ await this.handleError(response);
430
+ }
431
+
432
+ return response.json();
433
+ }
434
+
435
+ private async handleError(response: Response): Promise<never> {
436
+ const error = await response.json();
437
+
438
+ if (response.status === 401) {
439
+ // Token expired, clear and retry
440
+ this.accessToken = null;
441
+ throw new Error('Session expired, retry required');
442
+ }
443
+
444
+ throw new Error(`API Error: ${JSON.stringify(error)}`);
445
+ }
446
+ }
447
+
448
+ // Usage
449
+ const sf = new SalesforceClient(
450
+ process.env.SF_CLIENT_ID!,
451
+ process.env.SF_USERNAME!,
452
+ './certificates/server.key'
453
+ );
454
+
455
+ const accounts = await sf.query(
456
+ "SELECT Id, Name FROM Account WHERE CreatedDate = TODAY"
457
+ );
458
+
459
+ ### Context
460
+
461
+ - external integration
462
+ - REST API access
463
+ - connected apps
464
+
465
+ ### Bulk API 2.0 for Large Data Operations
466
+
467
+ Use Bulk API 2.0 for operations on 10K+ records. Asynchronous processing
468
+ with job-based workflow. Part of REST API with streamlined interface
469
+ compared to original Bulk API.
470
+
471
+ // Node.js - Bulk API 2.0 insert
472
+ class SalesforceBulkClient extends SalesforceClient {
473
+
474
+ async bulkInsert(sobject: string, records: object[]): Promise<any> {
475
+ await this.authenticate();
476
+
477
+ // Step 1: Create job
478
+ const job = await this.createBulkJob(sobject, 'insert');
479
+
480
+ try {
481
+ // Step 2: Upload data (CSV format)
482
+ await this.uploadJobData(job.id, records);
483
+
484
+ // Step 3: Close job to start processing
485
+ await this.closeJob(job.id);
486
+
487
+ // Step 4: Poll for completion
488
+ return await this.waitForJobCompletion(job.id);
489
+
490
+ } catch (error) {
491
+ // Abort job on error
492
+ await this.abortJob(job.id);
493
+ throw error;
494
+ }
495
+ }
496
+
497
+ private async createBulkJob(sobject: string, operation: string): Promise<any> {
498
+ const response = await fetch(
499
+ `${this.instanceUrl}/services/data/v59.0/jobs/ingest`,
500
+ {
501
+ method: 'POST',
502
+ headers: {
503
+ 'Authorization': `Bearer ${this.accessToken}`,
504
+ 'Content-Type': 'application/json'
505
+ },
506
+ body: JSON.stringify({
507
+ object: sobject,
508
+ operation,
509
+ contentType: 'CSV',
510
+ lineEnding: 'LF'
511
+ })
512
+ }
513
+ );
514
+
515
+ return response.json();
516
+ }
517
+
518
+ private async uploadJobData(jobId: string, records: object[]): Promise<void> {
519
+ // Convert to CSV
520
+ const csv = this.recordsToCSV(records);
521
+
522
+ await fetch(
523
+ `${this.instanceUrl}/services/data/v59.0/jobs/ingest/${jobId}/batches`,
524
+ {
525
+ method: 'PUT',
526
+ headers: {
527
+ 'Authorization': `Bearer ${this.accessToken}`,
528
+ 'Content-Type': 'text/csv'
529
+ },
530
+ body: csv
531
+ }
532
+ );
533
+ }
534
+
535
+ private async closeJob(jobId: string): Promise<void> {
536
+ await fetch(
537
+ `${this.instanceUrl}/services/data/v59.0/jobs/ingest/${jobId}`,
538
+ {
539
+ method: 'PATCH',
540
+ headers: {
541
+ 'Authorization': `Bearer ${this.accessToken}`,
542
+ 'Content-Type': 'application/json'
543
+ },
544
+ body: JSON.stringify({ state: 'UploadComplete' })
545
+ }
546
+ );
547
+ }
548
+
549
+ private async waitForJobCompletion(jobId: string): Promise<any> {
550
+ const maxWaitTime = 10 * 60 * 1000; // 10 minutes
551
+ const pollInterval = 5000; // 5 seconds
552
+ const startTime = Date.now();
553
+
554
+ while (Date.now() - startTime < maxWaitTime) {
555
+ const response = await fetch(
556
+ `${this.instanceUrl}/services/data/v59.0/jobs/ingest/${jobId}`,
557
+ {
558
+ headers: { 'Authorization': `Bearer ${this.accessToken}` }
559
+ }
560
+ );
561
+
562
+ const job = await response.json();
563
+
564
+ if (job.state === 'JobComplete') {
565
+ // Get results
566
+ return {
567
+ success: job.numberRecordsProcessed - job.numberRecordsFailed,
568
+ failed: job.numberRecordsFailed,
569
+ failedResults: job.numberRecordsFailed > 0
570
+ ? await this.getFailedResults(jobId)
571
+ : []
572
+ };
573
+ }
574
+
575
+ if (job.state === 'Failed' || job.state === 'Aborted') {
576
+ throw new Error(`Bulk job failed: ${job.state}`);
577
+ }
578
+
579
+ await new Promise(r => setTimeout(r, pollInterval));
580
+ }
581
+
582
+ throw new Error('Bulk job timeout');
583
+ }
584
+
585
+ private async getFailedResults(jobId: string): Promise<any[]> {
586
+ const response = await fetch(
587
+ `${this.instanceUrl}/services/data/v59.0/jobs/ingest/${jobId}/failedResults`,
588
+ {
589
+ headers: { 'Authorization': `Bearer ${this.accessToken}` }
590
+ }
591
+ );
32
592
 
33
- ### SOQL Inside Loops
593
+ const csv = await response.text();
594
+ return this.parseCSV(csv);
595
+ }
34
596
 
35
- ### DML Inside Loops
597
+ private recordsToCSV(records: object[]): string {
598
+ if (records.length === 0) return '';
36
599
 
37
- ### Hardcoding IDs
600
+ const headers = Object.keys(records[0]);
601
+ const rows = records.map(r =>
602
+ headers.map(h => this.escapeCSV(r[h])).join(',')
603
+ );
38
604
 
39
- ## ⚠️ Sharp Edges
605
+ return [headers.join(','), ...rows].join('\n');
606
+ }
40
607
 
41
- | Issue | Severity | Solution |
42
- |-------|----------|----------|
43
- | Issue | critical | See docs |
44
- | Issue | high | See docs |
45
- | Issue | medium | See docs |
46
- | Issue | high | See docs |
47
- | Issue | critical | See docs |
48
- | Issue | high | See docs |
49
- | Issue | high | See docs |
50
- | Issue | critical | See docs |
608
+ private escapeCSV(value: any): string {
609
+ if (value === null || value === undefined) return '';
610
+ const str = String(value);
611
+ if (str.includes(',') || str.includes('"') || str.includes('\n')) {
612
+ return `"${str.replace(/"/g, '""')}"`;
613
+ }
614
+ return str;
615
+ }
616
+ }
617
+
618
+ ### Context
619
+
620
+ - large data volumes
621
+ - data migration
622
+ - bulk operations
623
+
624
+ ### Salesforce DX with Scratch Orgs
625
+
626
+ Source-driven development with disposable scratch orgs for isolated
627
+ testing. Scratch orgs exist 7-30 days and can be created throughout
628
+ the day, unlike sandbox refresh limits.
629
+
630
+ // project-scratch-def.json - Scratch org definition
631
+ {
632
+ "orgName": "MyApp Dev Org",
633
+ "edition": "Developer",
634
+ "features": ["EnableSetPasswordInApi", "Communities"],
635
+ "settings": {
636
+ "lightningExperienceSettings": {
637
+ "enableS1DesktopEnabled": true
638
+ },
639
+ "mobileSettings": {
640
+ "enableS1EncryptedStoragePref2": false
641
+ },
642
+ "securitySettings": {
643
+ "passwordPolicies": {
644
+ "enableSetPasswordInApi": true
645
+ }
646
+ }
647
+ }
648
+ }
649
+
650
+ // sfdx-project.json - Project configuration
651
+ {
652
+ "packageDirectories": [
653
+ {
654
+ "path": "force-app",
655
+ "default": true,
656
+ "package": "MyPackage",
657
+ "versionName": "ver 1.0",
658
+ "versionNumber": "1.0.0.NEXT",
659
+ "dependencies": [
660
+ {
661
+ "package": "SomePackage@2.0.0"
662
+ }
663
+ ]
664
+ }
665
+ ],
666
+ "namespace": "myns",
667
+ "sfdcLoginUrl": "https://login.salesforce.com",
668
+ "sourceApiVersion": "59.0"
669
+ }
670
+
671
+ # Development workflow commands
672
+ # 1. Create scratch org
673
+ sf org create scratch \
674
+ --definition-file config/project-scratch-def.json \
675
+ --alias myapp-dev \
676
+ --duration-days 7 \
677
+ --set-default
678
+
679
+ # 2. Push source to scratch org
680
+ sf project deploy start --target-org myapp-dev
681
+
682
+ # 3. Assign permission set
683
+ sf org assign permset --name MyApp_Admin --target-org myapp-dev
684
+
685
+ # 4. Import sample data
686
+ sf data import tree --plan data/sample-data-plan.json --target-org myapp-dev
687
+
688
+ # 5. Open org
689
+ sf org open --target-org myapp-dev
690
+
691
+ # 6. Run tests
692
+ sf apex run test \
693
+ --code-coverage \
694
+ --result-format human \
695
+ --wait 10 \
696
+ --target-org myapp-dev
697
+
698
+ # 7. Pull changes back
699
+ sf project retrieve start --target-org myapp-dev
700
+
701
+ ### Context
702
+
703
+ - development workflow
704
+ - CI/CD
705
+ - testing
706
+
707
+ ### 2nd Generation Package (2GP) Development
708
+
709
+ 2GP replaces 1GP with source-driven, modular packaging. Requires Dev Hub
710
+ with 2GP enabled, namespace linked, and 75% code coverage for promoted
711
+ packages.
712
+
713
+ # Enable Dev Hub and 2GP in Setup:
714
+ # Setup > Dev Hub > Enable Dev Hub
715
+ # Setup > Dev Hub > Enable Unlocked Packages and 2GP
716
+
717
+ # Link namespace (required for managed packages)
718
+ sf package create \
719
+ --name "MyManagedPackage" \
720
+ --package-type Managed \
721
+ --path force-app \
722
+ --target-dev-hub DevHub
723
+
724
+ # Create package version (beta)
725
+ sf package version create \
726
+ --package "MyManagedPackage" \
727
+ --installation-key-bypass \
728
+ --wait 30 \
729
+ --code-coverage \
730
+ --target-dev-hub DevHub
731
+
732
+ # Check version status
733
+ sf package version list --packages "MyManagedPackage" --target-dev-hub DevHub
734
+
735
+ # Promote to released (requires 75% coverage)
736
+ sf package version promote \
737
+ --package "MyManagedPackage@1.0.0-1" \
738
+ --target-dev-hub DevHub
739
+
740
+ # Install in sandbox for testing
741
+ sf package install \
742
+ --package "MyManagedPackage@1.0.0-1" \
743
+ --target-org MySandbox \
744
+ --wait 20
745
+
746
+ # CI/CD Pipeline (GitHub Actions)
747
+ # .github/workflows/salesforce-ci.yml
748
+ name: Salesforce CI
749
+
750
+ on:
751
+ push:
752
+ branches: [main, develop]
753
+ pull_request:
754
+ branches: [main]
755
+
756
+ jobs:
757
+ validate:
758
+ runs-on: ubuntu-latest
759
+ steps:
760
+ - uses: actions/checkout@v4
761
+
762
+ - name: Install Salesforce CLI
763
+ run: npm install -g @salesforce/cli
764
+
765
+ - name: Authenticate Dev Hub
766
+ run: |
767
+ echo "${{ secrets.SFDX_AUTH_URL }}" > auth.txt
768
+ sf org login sfdx-url --sfdx-url-file auth.txt --alias DevHub --set-default-dev-hub
769
+
770
+ - name: Create Scratch Org
771
+ run: |
772
+ sf org create scratch \
773
+ --definition-file config/project-scratch-def.json \
774
+ --alias ci-scratch \
775
+ --duration-days 1 \
776
+ --set-default
777
+
778
+ - name: Deploy Source
779
+ run: sf project deploy start --target-org ci-scratch
780
+
781
+ - name: Run Tests
782
+ run: |
783
+ sf apex run test \
784
+ --code-coverage \
785
+ --result-format human \
786
+ --wait 20 \
787
+ --target-org ci-scratch
788
+
789
+ - name: Delete Scratch Org
790
+ if: always()
791
+ run: sf org delete scratch --target-org ci-scratch --no-prompt
792
+
793
+ ### Context
794
+
795
+ - packaging
796
+ - ISV development
797
+ - AppExchange
798
+
799
+ ## Sharp Edges
800
+
801
+ ### Governor Limits Apply Per Transaction, Not Per Record
802
+
803
+ Severity: CRITICAL
804
+
805
+ ### @wire Results Are Cached and May Be Stale
806
+
807
+ Severity: HIGH
808
+
809
+ ### LWC Properties Are Case-Sensitive
810
+
811
+ Severity: MEDIUM
812
+
813
+ ### Null Pointer Exceptions in Apex Collections
814
+
815
+ Severity: HIGH
816
+
817
+ ### Trigger Recursion Causes Infinite Loops
818
+
819
+ Severity: CRITICAL
820
+
821
+ ### Cannot Make Callouts from Synchronous Triggers
822
+
823
+ Severity: HIGH
824
+
825
+ ### Cannot Mix Setup and Non-Setup DML
826
+
827
+ Severity: HIGH
828
+
829
+ ### Dynamic SOQL Is Vulnerable to Injection
830
+
831
+ Severity: CRITICAL
832
+
833
+ ### Scratch Orgs Expire and Lose All Data
834
+
835
+ Severity: MEDIUM
836
+
837
+ ### API Version Mismatches Cause Silent Failures
838
+
839
+ Severity: MEDIUM
840
+
841
+ ## Validation Checks
842
+
843
+ ### SOQL Query Inside Loop
844
+
845
+ Severity: ERROR
846
+
847
+ SOQL in loops causes governor limit exceptions with bulk data
848
+
849
+ Message: SOQL query inside loop. Query once outside the loop and use a Map.
850
+
851
+ ### DML Operation Inside Loop
852
+
853
+ Severity: ERROR
854
+
855
+ DML in loops hits 150 statement limit
856
+
857
+ Message: DML operation inside loop. Collect records and perform single DML outside loop.
858
+
859
+ ### HTTP Callout in Trigger
860
+
861
+ Severity: ERROR
862
+
863
+ Synchronous triggers cannot make callouts
864
+
865
+ Message: Callout in trigger. Use @future(callout=true) or Queueable with Database.AllowsCallouts.
866
+
867
+ ### Potential SOQL Injection
868
+
869
+ Severity: ERROR
870
+
871
+ Dynamic SOQL with string concatenation is vulnerable
872
+
873
+ Message: Dynamic SOQL with concatenation. Use bind variables or String.escapeSingleQuotes().
874
+
875
+ ### Missing WITH SECURITY_ENFORCED
876
+
877
+ Severity: WARNING
878
+
879
+ SOQL should enforce FLS/CRUD permissions
880
+
881
+ Message: SOQL without security enforcement. Add WITH SECURITY_ENFORCED.
882
+
883
+ ### Hardcoded Salesforce ID
884
+
885
+ Severity: WARNING
886
+
887
+ Record IDs differ between orgs
888
+
889
+ Message: Hardcoded Salesforce ID. Query by DeveloperName or ExternalId instead.
890
+
891
+ ### Hardcoded Credentials
892
+
893
+ Severity: ERROR
894
+
895
+ Credentials must use Named Credentials or Custom Metadata
896
+
897
+ Message: Hardcoded credentials. Use Named Credentials or Custom Metadata.
898
+
899
+ ### Direct DOM Manipulation in LWC
900
+
901
+ Severity: WARNING
902
+
903
+ LWC uses shadow DOM, direct manipulation breaks encapsulation
904
+
905
+ Message: Direct DOM access in LWC. Use this.template.querySelector() or data binding.
906
+
907
+ ### Reactive Property Without @track
908
+
909
+ Severity: INFO
910
+
911
+ Complex object properties need @track for reactivity
912
+
913
+ Message: Object assignment may need @track for reactivity (post-Spring '20 objects are auto-tracked).
914
+
915
+ ### Wire Without Refresh After DML
916
+
917
+ Severity: WARNING
918
+
919
+ Cached wire data becomes stale after updates
920
+
921
+ Message: DML after @wire without refreshApex. Data may be stale.
922
+
923
+ ## Collaboration
924
+
925
+ ### Delegation Triggers
926
+
927
+ - user needs external API integration -> backend (REST API design, external system sync)
928
+ - user needs complex UI beyond LWC -> frontend (Custom portal with React/Next.js)
929
+ - user needs HubSpot integration -> hubspot-integration (Salesforce-HubSpot sync patterns)
930
+ - user needs data warehouse sync -> data-engineer (ETL from Salesforce to warehouse)
931
+ - user needs payment processing -> stripe-integration (Beyond Salesforce Billing)
932
+ - user needs advanced auth -> auth-specialist (SSO, SAML, custom portals)
51
933
 
52
934
  ## When to Use
53
- This skill is applicable to execute the workflow or actions described in the overview.
935
+
936
+ - User mentions or implies: salesforce
937
+ - User mentions or implies: sfdc
938
+ - User mentions or implies: apex
939
+ - User mentions or implies: lwc
940
+ - User mentions or implies: lightning web components
941
+ - User mentions or implies: sfdx
942
+ - User mentions or implies: scratch org
943
+ - User mentions or implies: visualforce
944
+ - User mentions or implies: soql
945
+ - User mentions or implies: governor limits
946
+ - User mentions or implies: connected app