Agent Skills by ALSEL
Anthropic Claudeソフトウェア開発⭐ リポ 0品質スコア 50/100

salesforce-development

Salesforce プラットフォーム開発に特化したスキルで、Lightning Web Components(LWC)、Apex トリガー・クラス、REST/Bulk API、Connected Apps、さらにスクラッチ org や第2世代パッケージ(2GP)を含む Salesforce DX の実践的なパターンを提供します。

description の原文を見る

Expert patterns for Salesforce platform development including Lightning Web Components (LWC), Apex triggers and classes, REST/Bulk APIs, Connected Apps, and Salesforce DX with scratch orgs and 2nd generation packages (2GP).

SKILL.md 本文

Salesforce Development

Salesforce プラットフォーム開発の専門的なパターン。Lightning Web Components (LWC)、Apex トリガーとクラス、REST/Bulk API、Connected Apps、Salesforce DX(scratch orgs と 2nd generation packages (2GP))に対応しています。

Patterns

Lightning Web Component with Wire Service

@wire デコレーターを使用して、Lightning Data Service または Apex メソッドでリアクティブなデータバインディングを実現します。@wire は LWC のリアクティブアーキテクチャに適合し、Salesforce のパフォーマンス最適化を実現します。

// myComponent.js
import { LightningElement, wire, api } from 'lwc';
import { getRecord, getFieldValue } from 'lightning/uiRecordApi';
import getRelatedRecords from '@salesforce/apex/MyController.getRelatedRecords';
import ACCOUNT_NAME from '@salesforce/schema/Account.Name';
import ACCOUNT_INDUSTRY from '@salesforce/schema/Account.Industry';

const FIELDS = [ACCOUNT_NAME, ACCOUNT_INDUSTRY];

export default class MyComponent extends LightningElement {
  @api recordId;  // Passed from parent or record page

  // Wire to Lightning Data Service (preferred for single records)
  @wire(getRecord, { recordId: '$recordId', fields: FIELDS })
  account;

  // Wire to Apex method (for complex queries)
  @wire(getRelatedRecords, { accountId: '$recordId' })
  wiredRecords({ error, data }) {
    if (data) {
      this.relatedRecords = data;
      this.error = undefined;
    } else if (error) {
      this.error = error;
      this.relatedRecords = undefined;
    }
  }

  get accountName() {
    return getFieldValue(this.account.data, ACCOUNT_NAME);
  }

  get isLoading() {
    return !this.account.data && !this.account.error;
  }

  // Reactive: changing recordId automatically re-fetches
}

// myComponent.html
<template>
  <lightning-card title={accountName}>
    <template if:true={isLoading}>
      <lightning-spinner alternative-text="Loading"></lightning-spinner>
    </template>

    <template if:true={account.data}>
      <p>Industry: {industry}</p>
    </template>

    <template if:true={error}>
      <p class="slds-text-color_error">{error.body.message}</p>
    </template>
  </lightning-card>
</template>

// MyController.cls
public with sharing class MyController {
  @AuraEnabled(cacheable=true)
  public static List<Contact> getRelatedRecords(Id accountId) {
    return [
      SELECT Id, Name, Email, Phone
      FROM Contact
      WHERE AccountId = :accountId
      WITH SECURITY_ENFORCED
      LIMIT 100
    ];
  }
}

Context

  • LWC コンポーネント構築
  • Salesforce データ取得
  • リアクティブ UI

Bulkified Apex Trigger with Handler Pattern

Apex トリガーはトランザクション当たり 200 件以上のレコードを処理するため、bulkify される必要があります。関心の分離、テスト可能性、再帰防止のためにハンドラーパターンを使用します。

// AccountTrigger.trigger
trigger AccountTrigger on Account (
  before insert, before update, before delete,
  after insert, after update, after delete, after undelete
) {
  new AccountTriggerHandler().run();
}

// TriggerHandler.cls (base class)
public virtual class TriggerHandler {
  // Recursion prevention
  private static Set<String> executedHandlers = new Set<String>();

  public void run() {
    String handlerName = String.valueOf(this).split(':')[0];

    // Prevent recursion
    String contextKey = handlerName + '_' + Trigger.operationType;
    if (executedHandlers.contains(contextKey)) {
      return;
    }
    executedHandlers.add(contextKey);

    switch on Trigger.operationType {
      when BEFORE_INSERT { this.beforeInsert(); }
      when BEFORE_UPDATE { this.beforeUpdate(); }
      when BEFORE_DELETE { this.beforeDelete(); }
      when AFTER_INSERT { this.afterInsert(); }
      when AFTER_UPDATE { this.afterUpdate(); }
      when AFTER_DELETE { this.afterDelete(); }
      when AFTER_UNDELETE { this.afterUndelete(); }
    }
  }

  // Override in child classes
  protected virtual void beforeInsert() {}
  protected virtual void beforeUpdate() {}
  protected virtual void beforeDelete() {}
  protected virtual void afterInsert() {}
  protected virtual void afterUpdate() {}
  protected virtual void afterDelete() {}
  protected virtual void afterUndelete() {}
}

// AccountTriggerHandler.cls
public class AccountTriggerHandler extends TriggerHandler {
  private List<Account> newAccounts;
  private List<Account> oldAccounts;
  private Map<Id, Account> newMap;
  private Map<Id, Account> oldMap;

  public AccountTriggerHandler() {
    this.newAccounts = (List<Account>) Trigger.new;
    this.oldAccounts = (List<Account>) Trigger.old;
    this.newMap = (Map<Id, Account>) Trigger.newMap;
    this.oldMap = (Map<Id, Account>) Trigger.oldMap;
  }

  protected override void afterInsert() {
    createDefaultContacts();
    notifySlack();
  }

  protected override void afterUpdate() {
    handleIndustryChange();
  }

  // BULKIFIED: Query once, update once
  private void createDefaultContacts() {
    List<Contact> contactsToInsert = new List<Contact>();

    for (Account acc : newAccounts) {
      if (acc.Type == 'Prospect') {
        contactsToInsert.add(new Contact(
          AccountId = acc.Id,
          LastName = 'Primary Contact',
          Email = 'contact@' + acc.Website
        ));
      }
    }

    if (!contactsToInsert.isEmpty()) {
      insert contactsToInsert;  // Single DML for all
    }
  }

  private void handleIndustryChange() {
    Set<Id> changedAccountIds = new Set<Id>();

    for (Account acc : newAccounts) {
      Account oldAcc = oldMap.get(acc.Id);
      if (acc.Industry != oldAcc.Industry) {
        changedAccountIds.add(acc.Id);
      }
    }

    if (!changedAccountIds.isEmpty()) {
      // Queue async processing for heavy work
      System.enqueueJob(new IndustryChangeQueueable(changedAccountIds));
    }
  }

  private void notifySlack() {
    // Offload callouts to async
    List<Id> accountIds = new List<Id>(newMap.keySet());
    System.enqueueJob(new SlackNotificationQueueable(accountIds));
  }
}

Context

  • Apex トリガー
  • データ操作
  • オートメーション

Queueable Apex for Async Processing

Queueable Apex を非同期処理に使用します。非プリミティブ型のサポート、AsyncApexJob による監視、ジョブチェーンに対応します。制限:トランザクション当たり 50 ジョブ、チェーン時に 1 つの子ジョブ。

// IndustryChangeQueueable.cls
public class IndustryChangeQueueable implements Queueable, Database.AllowsCallouts {
  private Set<Id> accountIds;
  private Integer retryCount;

  public IndustryChangeQueueable(Set<Id> accountIds) {
    this(accountIds, 0);
  }

  public IndustryChangeQueueable(Set<Id> accountIds, Integer retryCount) {
    this.accountIds = accountIds;
    this.retryCount = retryCount;
  }

  public void execute(QueueableContext context) {
    try {
      // Query with fresh data
      List<Account> accounts = [
        SELECT Id, Name, Industry, OwnerId
        FROM Account
        WHERE Id IN :accountIds
        WITH SECURITY_ENFORCED
      ];

      // Process and make callout
      for (Account acc : accounts) {
        syncToExternalSystem(acc);
      }

      // Update records
      updateRelatedOpportunities(accountIds);

    } catch (Exception e) {
      handleError(e);
    }
  }

  private void syncToExternalSystem(Account acc) {
    HttpRequest req = new HttpRequest();
    req.setEndpoint('callout:ExternalCRM/accounts');
    req.setMethod('POST');
    req.setHeader('Content-Type', 'application/json');
    req.setBody(JSON.serialize(new Map<String, Object>{
      'salesforceId' => acc.Id,
      'name' => acc.Name,
      'industry' => acc.Industry
    }));

    Http http = new Http();
    HttpResponse res = http.send(req);

    if (res.getStatusCode() != 200 && res.getStatusCode() != 201) {
      throw new CalloutException('Sync failed: ' + res.getBody());
    }
  }

  private void updateRelatedOpportunities(Set<Id> accIds) {
    List<Opportunity> oppsToUpdate = [
      SELECT Id, Industry__c, AccountId
      FROM Opportunity
      WHERE AccountId IN :accIds
      WITH SECURITY_ENFORCED
    ];

    Map<Id, Account> accountMap = new Map<Id, Account>([
      SELECT Id, Industry FROM Account WHERE Id IN :accIds
    ]);

    for (Opportunity opp : oppsToUpdate) {
      opp.Industry__c = accountMap.get(opp.AccountId).Industry;
    }

    if (!oppsToUpdate.isEmpty()) {
      update oppsToUpdate;
    }
  }

  private void handleError(Exception e) {
    // Log error
    System.debug(LoggingLevel.ERROR, 'Queueable failed: ' + e.getMessage());

    // Retry with exponential backoff (max 3 retries)
    if (retryCount < 3) {
      // Chain new job for retry
      System.enqueueJob(new IndustryChangeQueueable(accountIds, retryCount + 1));
    } else {
      // Create error record for monitoring
      insert new Integration_Error__c(
        Type__c = 'Industry Sync',
        Message__c = e.getMessage(),
        Stack_Trace__c = e.getStackTraceString(),
        Record_Ids__c = String.join(new List<Id>(accountIds), ',')
      );
    }
  }
}

Context

  • 非同期処理
  • 長時間実行操作
  • トリガーからのコールアウト

REST API Integration with Connected App

外部統合では Connected App と OAuth 2.0 を使用します。サーバー間通信では JWT Bearer フロー、ユーザー向けアプリでは Web Server フロー を使用します。常に Named Credentials を使用して安全なコールアウト設定を行います。

// Node.js - JWT Bearer Flow (server-to-server)
import jwt from 'jsonwebtoken';
import fs from 'fs';

class SalesforceClient {
  private accessToken: string | null = null;
  private instanceUrl: string | null = null;
  private tokenExpiry: number = 0;

  constructor(
    private clientId: string,
    private username: string,
    private privateKeyPath: string,
    private loginUrl: string = 'https://login.salesforce.com'
  ) {}

  async authenticate(): Promise<void> {
    // Check if token is still valid (5 min buffer)
    if (this.accessToken && Date.now() < this.tokenExpiry - 300000) {
      return;
    }

    const privateKey = fs.readFileSync(this.privateKeyPath, 'utf8');

    // Create JWT assertion
    const claim = {
      iss: this.clientId,
      sub: this.username,
      aud: this.loginUrl,
      exp: Math.floor(Date.now() / 1000) + 300  // 5 minutes
    };

    const assertion = jwt.sign(claim, privateKey, { algorithm: 'RS256' });

    // Exchange JWT for access token
    const response = await fetch(`${this.loginUrl}/services/oauth2/token`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      body: new URLSearchParams({
        grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
        assertion
      })
    });

    if (!response.ok) {
      const error = await response.json();
      throw new Error(`Auth failed: ${error.error_description}`);
    }

    const data = await response.json();
    this.accessToken = data.access_token;
    this.instanceUrl = data.instance_url;
    this.tokenExpiry = Date.now() + 7200000;  // 2 hours
  }

  async query(soql: string): Promise<any> {
    await this.authenticate();

    const response = await fetch(
      `${this.instanceUrl}/services/data/v59.0/query?q=${encodeURIComponent(soql)}`,
      {
        headers: {
          'Authorization': `Bearer ${this.accessToken}`,
          'Content-Type': 'application/json'
        }
      }
    );

    if (!response.ok) {
      await this.handleError(response);
    }

    return response.json();
  }

  async createRecord(sobject: string, data: object): Promise<any> {
    await this.authenticate();

    const response = await fetch(
      `${this.instanceUrl}/services/data/v59.0/sobjects/${sobject}`,
      {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${this.accessToken}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(data)
      }
    );

    if (!response.ok) {
      await this.handleError(response);
    }

    return response.json();
  }

  private async handleError(response: Response): Promise<never> {
    const error = await response.json();

    if (response.status === 401) {
      // Token expired, clear and retry
      this.accessToken = null;
      throw new Error('Session expired, retry required');
    }

    throw new Error(`API Error: ${JSON.stringify(error)}`);
  }
}

// Usage
const sf = new SalesforceClient(
  process.env.SF_CLIENT_ID!,
  process.env.SF_USERNAME!,
  './certificates/server.key'
);

const accounts = await sf.query(
  "SELECT Id, Name FROM Account WHERE CreatedDate = TODAY"
);

Context

  • 外部統合
  • REST API アクセス
  • Connected Apps

Bulk API 2.0 for Large Data Operations

10K 件以上のレコードに対する操作には Bulk API 2.0 を使用します。ジョブベースのワークフローによる非同期処理です。REST API の一部で、オリジナルの Bulk API と比較してストリーミングラインインターフェースです。

// Node.js - Bulk API 2.0 insert
class SalesforceBulkClient extends SalesforceClient {

  async bulkInsert(sobject: string, records: object[]): Promise<any> {
    await this.authenticate();

    // Step 1: Create job
    const job = await this.createBulkJob(sobject, 'insert');

    try {
      // Step 2: Upload data (CSV format)
      await this.uploadJobData(job.id, records);

      // Step 3: Close job to start processing
      await this.closeJob(job.id);

      // Step 4: Poll for completion
      return await this.waitForJobCompletion(job.id);

    } catch (error) {
      // Abort job on error
      await this.abortJob(job.id);
      throw error;
    }
  }

  private async createBulkJob(sobject: string, operation: string): Promise<any> {
    const response = await fetch(
      `${this.instanceUrl}/services/data/v59.0/jobs/ingest`,
      {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${this.accessToken}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          object: sobject,
          operation,
          contentType: 'CSV',
          lineEnding: 'LF'
        })
      }
    );

    return response.json();
  }

  private async uploadJobData(jobId: string, records: object[]): Promise<void> {
    // Convert to CSV
    const csv = this.recordsToCSV(records);

    await fetch(
      `${this.instanceUrl}/services/data/v59.0/jobs/ingest/${jobId}/batches`,
      {
        method: 'PUT',
        headers: {
          'Authorization': `Bearer ${this.accessToken}`,
          'Content-Type': 'text/csv'
        },
        body: csv
      }
    );
  }

  private async closeJob(jobId: string): Promise<void> {
    await fetch(
      `${this.instanceUrl}/services/data/v59.0/jobs/ingest/${jobId}`,
      {
        method: 'PATCH',
        headers: {
          'Authorization': `Bearer ${this.accessToken}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({ state: 'UploadComplete' })
      }
    );
  }

  private async waitForJobCompletion(jobId: string): Promise<any> {
    const maxWaitTime = 10 * 60 * 1000;  // 10 minutes
    const pollInterval = 5000;  // 5 seconds
    const startTime = Date.now();

    while (Date.now() - startTime < maxWaitTime) {
      const response = await fetch(
        `${this.instanceUrl}/services/data/v59.0/jobs/ingest/${jobId}`,
        {
          headers: { 'Authorization': `Bearer ${this.accessToken}` }
        }
      );

      const job = await response.json();

      if (job.state === 'JobComplete') {
        // Get results
        return {
          success: job.numberRecordsProcessed - job.numberRecordsFailed,
          failed: job.numberRecordsFailed,
          failedResults: job.numberRecordsFailed > 0
            ? await this.getFailedResults(jobId)
            : []
        };
      }

      if (job.state === 'Failed' || job.state === 'Aborted') {
        throw new Error(`Bulk job failed: ${job.state}`);
      }

      await new Promise(r => setTimeout(r, pollInterval));
    }

    throw new Error('Bulk job timeout');
  }

  private async getFailedResults(jobId: string): Promise<any[]> {
    const response = await fetch(
      `${this.instanceUrl}/services/data/v59.0/jobs/ingest/${jobId}/failedResults`,
      {
        headers: { 'Authorization': `Bearer ${this.accessToken}` }
      }
    );

    const csv = await response.text();
    return this.parseCSV(csv);
  }

  private recordsToCSV(records: object[]): string {
    if (records.length === 0) return '';

    const headers = Object.keys(records[0]);
    const rows = records.map(r =>
      headers.map(h => this.escapeCSV(r[h])).join(',')
    );

    return [headers.join(','), ...rows].join('\n');
  }

  private escapeCSV(value: any): string {
    if (value === null || value === undefined) return '';
    const str = String(value);
    if (str.includes(',') || str.includes('"') || str.includes('\n')) {
      return `"${str.replace(/"/g, '""')}"`;
    }
    return str;
  }
}

Context

  • 大規模データボリューム
  • データマイグレーション
  • バルク操作

Salesforce DX with Scratch Orgs

ソースドリブン開発で、分離されたテストのための使い捨て scratch orgs を使用します。Scratch orgs は 7-30 日間存在し、sandbox リフレッシュの制限と異なり、一日を通して作成できます。

// project-scratch-def.json - Scratch org definition
{
  "orgName": "MyApp Dev Org",
  "edition": "Developer",
  "features": ["EnableSetPasswordInApi", "Communities"],
  "settings": {
    "lightningExperienceSettings": {
      "enableS1DesktopEnabled": true
    },
    "mobileSettings": {
      "enableS1EncryptedStoragePref2": false
    },
    "securitySettings": {
      "passwordPolicies": {
        "enableSetPasswordInApi": true
      }
    }
  }
}

// sfdx-project.json - Project configuration
{
  "packageDirectories": [
    {
      "path": "force-app",
      "default": true,
      "package": "MyPackage",
      "versionName": "ver 1.0",
      "versionNumber": "1.0.0.NEXT",
      "dependencies": [
        {
          "package": "SomePackage@2.0.0"
        }
      ]
    }
  ],
  "namespace": "myns",
  "sfdcLoginUrl": "https://login.salesforce.com",
  "sourceApiVersion": "59.0"
}

開発ワークフローコマンド

# 1. Create scratch org
sf org create scratch \
  --definition-file config/project-scratch-def.json \
  --alias myapp-dev \
  --duration-days 7 \
  --set-default

# 2. Push source to scratch org
sf project deploy start --target-org myapp-dev

# 3. Assign permission set
sf org assign permset --name MyApp_Admin --target-org myapp-dev

# 4. Import sample data
sf data import tree --plan data/sample-data-plan.json --target-org myapp-dev

# 5. Open org
sf org open --target-org myapp-dev

# 6. Run tests
sf apex run test \
  --code-coverage \
  --result-format human \
  --wait 10 \
  --target-org myapp-dev

# 7. Pull changes back
sf project retrieve start --target-org myapp-dev

Context

  • 開発ワークフロー
  • CI/CD
  • テスト

2nd Generation Package (2GP) Development

2GP はソースドリブンでモジュラーなパッケージングで 1GP に置き換わります。Dev Hub と 2GP が有効化され、namespace がリンクされ、プロモートされたパッケージに対して 75% のコードカバレッジが必要です。

# Enable Dev Hub and 2GP in Setup:
# Setup > Dev Hub > Enable Dev Hub
# Setup > Dev Hub > Enable Unlocked Packages and 2GP

# Link namespace (required for managed packages)
sf package create \
  --name "MyManagedPackage" \
  --package-type Managed \
  --path force-app \
  --target-dev-hub DevHub

# Create package version (beta)
sf package version create \
  --package "MyManagedPackage" \
  --installation-key-bypass \
  --wait 30 \
  --code-coverage \
  --target-dev-hub DevHub

# Check version status
sf package version list --packages "MyManagedPackage" --target-dev-hub DevHub

# Promote to released (requires 75% coverage)
sf package version promote \
  --package "MyManagedPackage@1.0.0-1" \
  --target-dev-hub DevHub

# Install in sandbox for testing
sf package install \
  --package "MyManagedPackage@1.0.0-1" \
  --target-org MySandbox \
  --wait 20

CI/CD パイプライン (GitHub Actions)

# .github/workflows/salesforce-ci.yml
name: Salesforce CI

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install Salesforce CLI
        run: npm install -g @salesforce/cli

      - name: Authenticate Dev Hub
        run: |
          echo "${{ secrets.SFDX_AUTH_URL }}" > auth.txt
          sf org login sfdx-url --sfdx-url-file auth.txt --alias DevHub --set-default-dev-hub

      - name: Create Scratch Org
        run: |
          sf org create scratch \
            --definition-file config/project-scratch-def.json \
            --alias ci-scratch \
            --duration-days 1 \
            --set-default

      - name: Deploy Source
        run: sf project deploy start --target-org ci-scratch

      - name: Run Tests
        run: |
          sf apex run test \
            --code-coverage \
            --result-format human \
            --wait 20 \
            --target-org ci-scratch

      - name: Delete Scratch Org
        if: always()
        run: sf org delete scratch --target-org ci-scratch --no-prompt

Context

  • パッケージング
  • ISV 開発
  • AppExchange

Sharp Edges

Governor Limits Apply Per Transaction, Not Per Record

重大度:CRITICAL

ガバナー制限はトランザクション単位で適用され、レコード単位ではありません。

@wire Results Are Cached and May Be Stale

重大度:HIGH

@wire の結果はキャッシュされており、古い可能性があります。

LWC Properties Are Case-Sensitive

重大度:MEDIUM

LWC プロパティは大文字と小文字を区別します。

Null Pointer Exceptions in Apex Collections

重大度:HIGH

Apex コレクションの null ポインター例外。

Trigger Recursion Causes Infinite Loops

重大度:CRITICAL

トリガーの再帰は無限ループを引き起こします。

Cannot Make Callouts from Synchronous Triggers

重大度:HIGH

同期トリガーからコールアウトを行うことはできません。

Cannot Mix Setup and Non-Setup DML

重大度:HIGH

Setup と Non-Setup DML を混在させることはできません。

Dynamic SOQL Is Vulnerable to Injection

重大度:CRITICAL

動的 SOQL はインジェクション攻撃に脆弱です。

Scratch Orgs Expire and Lose All Data

重大度:MEDIUM

Scratch orgs は有効期限が切れ、すべてのデータが失われます。

API Version Mismatches Cause Silent Failures

重大度:MEDIUM

API バージョンの不一致はサイレント失敗を引き起こします。

Validation Checks

SOQL Query Inside Loop

重大度:ERROR

ループ内の SOQL はバルクデータでガバナー制限例外を引き起こします。

メッセージ:SOQL query inside loop. Query once outside the loop and use a Map.

DML Operation Inside Loop

重大度:ERROR

ループ内の DML は 150 ステートメント制限に達します。

メッセージ:DML operation inside loop. Collect records and perform single DML outside loop.

HTTP Callout in Trigger

重大度:ERROR

同期トリガーはコールアウトを行うことができません。

メッセージ:Callout in trigger. Use @future(callout=true) or Queueable with Database.AllowsCallouts.

Potential SOQL Injection

重大度:ERROR

文字列連結による動的 SOQL は脆弱です。

メッセージ:Dynamic SOQL with concatenation. Use bind variables or String.escapeSingleQuotes().

Missing WITH SECURITY_ENFORCED

重大度:WARNING

SOQL は FLS/CRUD 権限を強制すべきです。

メッセージ:SOQL without security enforcement. Add WITH SECURITY_ENFORCED.

Hardcoded Salesforce ID

重大度:WARNING

レコード ID は orgs 間で異なります。

メッセージ:Hardcoded Salesforce ID. Query by DeveloperName or ExternalId instead.

Hardcoded Credentials

重大度:ERROR

認証情報は Named Credentials またはカスタムメタデータを使用する必要があります。

メッセージ:Hardcoded credentials. Use Named Credentials or Custom Metadata.

Direct DOM Manipulation in LWC

重大度:WARNING

LWC はシャドウ DOM を使用し、直接操作はカプセル化を破壊します。

メッセージ:Direct DOM access in LWC. Use this.template.querySelector() or data binding.

Reactive Property Without @track

重大度:INFO

複雑なオブジェクトプロパティはリアクティブ性のために @track が必要です。

メッセージ:Object assignment may need @track for reactivity (post-Spring '20 objects are auto-tracked).

Wire Without Refresh After DML

重大度:WARNING

キャッシュされた wire データは更新後に古くなります。

メッセージ:DML after @wire without refreshApex. Data may be stale.

Collaboration

Delegation Triggers

  • ユーザーが外部 API 統合が必要 -> backend (REST API 設計、外部システム同期)
  • ユーザーが LWC を超えた複雑な UI が必要 -> frontend (React/Next.js を使用したカスタムポータル)
  • ユーザーが HubSpot 統合が必要 -> hubspot-integration (Salesforce-HubSpot 同期パターン)
  • ユーザーがデータウェアハウス同期が必要 -> data-engineer (Salesforce から warehouse への ETL)
  • ユーザーが決済処理が必要 -> stripe-integration (Salesforce Billing を超えて)
  • ユーザーが高度な認証が必要 -> auth-specialist (SSO、SAML、カスタムポータル)

When to Use

  • ユーザーが言及または暗示:salesforce
  • ユーザーが言及または暗示:sfdc
  • ユーザーが言及または暗示:apex
  • ユーザーが言及または暗示:lwc
  • ユーザーが言及または暗示:lightning web components
  • ユーザーが言及または暗示:sfdx
  • ユーザーが言及または暗示:scratch org
  • ユーザーが言及または暗示:visualforce
  • ユーザーが言及または暗示:soql
  • ユーザーが言及または暗示:governor limits
  • ユーザーが言及または暗示:connected app

Limitations

  • このスキルは、上記で説明されているスコープと明確に一致するタスクの場合のみ使用してください。
  • 出力を環境固有の検証、テスト、またはエキスパートレビューの代替として扱わないでください。
  • 必要な入力、権限、安全境界、または成功基準が不足している場合は、停止して説明を求めてください。

ライセンス: MIT(寛容ライセンスのため全文を引用しています) · 原本リポジトリ

詳細情報

作者
sickn33
リポジトリ
sickn33/antigravity-awesome-skills
ライセンス
MIT
最終更新
不明

Source: https://github.com/sickn33/antigravity-awesome-skills / ライセンス: MIT

関連スキル

汎用ソフトウェア開発⭐ リポ 39,967

doubt-driven-development

重要な判断はすべて、本番環境への展開前に新しい視点から対抗的レビューを実施します。速度より正確性が重要な場合、不慣れなコードを扱う場合、本番環境・セキュリティに関わるロジック・取り消し不可の操作など影響度が高い場合、または後でバグを修正するよりも今検証する方が効率的な場合に活用してください。

by addyosmani
汎用ソフトウェア開発⭐ リポ 1,175

apprun-skills

TypeScriptを使用したAppRunアプリケーションのMVU設計に関する総合的なガイダンスが得られます。コンポーネントパターン、イベントハンドリング、状態管理(非同期ジェネレータを含む)、パラメータと保護機能を備えたルーティング・ナビゲーション、vistestを使用したテストに対応しています。AppRunコンポーネントの設計・レビュー、ルートの配線、状態フローの管理、AppRunテストの作成時に活用してください。

by yysun
OpenAIソフトウェア開発⭐ リポ 797

desloppify

コードベースのヘルスチェックと技術負債の追跡ツールです。コード品質、技術負債、デッドコード、大規模ファイル、ゴッドクラス、重複関数、コードスメル、命名規則の問題、インポートサイクル、結合度の問題についてユーザーが質問した場合に使用してください。また、ヘルススコアの確認、次の改善項目の提案、クリーンアップ計画の作成をリクエストされた際にも対応します。29言語に対応しています。

by Git-on-my-level
汎用ソフトウェア開発⭐ リポ 39,967

debugging-and-error-recovery

テストが失敗したり、ビルドが壊れたり、動作が期待と異なったり、予期しないエラーが発生したりした場合に、体系的な根本原因デバッグをガイドします。推測ではなく、根本原因を見つけて修正するための体系的なアプローチが必要な場合に使用してください。

by addyosmani
汎用ソフトウェア開発⭐ リポ 39,967

test-driven-development

テスト駆動開発により実装を進めます。ロジックの実装、バグの修正、動作の変更など、あらゆる場面で活用できます。コードが正常に動作することを証明する必要がある場合、バグ報告を受けた場合、既存機能を修正する予定がある場合に使用してください。

by addyosmani
汎用ソフトウェア開発⭐ リポ 39,967

incremental-implementation

変更を段階的に実施します。複数のファイルに影響する機能や変更を実装する場合に使用してください。大量のコードを一度に書こうとしている場合や、タスクが一度では完結できないほど大きい場合に活用します。

by addyosmani
本サイトは GitHub 上で公開されているオープンソースの SKILL.md ファイルをクロール・インデックス化したものです。 各スキルの著作権は原作者に帰属します。掲載に問題がある場合は info@alsel.co.jp または /takedown フォームよりご連絡ください。
原作者: sickn33 · sickn33/antigravity-awesome-skills · ライセンス: MIT