mirror of
https://github.com/forcedotcom/afv-library.git
synced 2026-07-30 11:19:27 +08:00
feat(using-webapp-salesforce-data): align SKILL.md with using-salesforce-data
- Add GraphQL Non-Negotiable Rules section (6 critical platform rules) - Fix mutation template to include allOrNone wrapper - Add doc cross-references (schema introspection, query generation, testing, webapp integration) - Add deploying-webapp-to-salesforce skill cross-reference - Update script paths from .a4drules/... to scripts/graphql-search.sh - Expand checklist with allOrNone, pagination, and error handling items - Add two named webapp integration patterns (external .graphql, inline gql) Made-with: Cursor
This commit is contained in:
parent
c707d76b68
commit
316c36093f
@ -17,7 +17,7 @@ Use this skill when the user wants to:
|
||||
|
||||
## Data SDK Requirement
|
||||
|
||||
> **All Salesforce data access MUST use the Data SDK** (`@salesforce/sdk-data`). The SDK handles authentication, CSRF, and base URL resolution. Never use `fetch()` or `axios` directly.
|
||||
> **All Salesforce data access MUST use the Data SDK** (`@salesforce/sdk-data`). The SDK handles authentication, CSRF, and base URL resolution.
|
||||
|
||||
```typescript
|
||||
import { createDataSDK, gql } from "@salesforce/sdk-data";
|
||||
@ -67,6 +67,24 @@ const res = await sdk.fetch?.("/services/apexrest/my-resource");
|
||||
|
||||
---
|
||||
|
||||
## GraphQL Non-Negotiable Rules
|
||||
|
||||
These rules exist because Salesforce GraphQL has platform-specific behaviors that differ from standard GraphQL. Violations cause silent runtime failures.
|
||||
|
||||
1. **Schema is the single source of truth** — Every entity name, field name, and type must be confirmed via the schema search script before use in a query. Never guess — Salesforce field names are case-sensitive, relationships may be polymorphic, and custom objects use suffixes (`__c`, `__e`). See [Schema Introspection](docs/schema-introspection.md) for entity identification and iterative lookup procedures.
|
||||
|
||||
2. **`@optional` on all record fields** (read queries) — Salesforce field-level security (FLS) causes queries to fail entirely if the user lacks access to even one field. The `@optional` directive (v65+) tells the server to omit inaccessible fields instead of failing. Apply it to every scalar field, parent relationship, and child relationship. Consuming code must use optional chaining (`?.`) and nullish coalescing (`??`).
|
||||
|
||||
3. **Correct mutation syntax** — Mutations wrap under `uiapi(input: { allOrNone: true/false })`, not bare `uiapi { ... }`. Always set `allOrNone` explicitly. Output fields cannot include child relationships or navigated reference fields. See [Mutation Query Generation](docs/mutation-query-generation.md).
|
||||
|
||||
4. **Explicit pagination** — Always include `first:` in every query. If omitted, the server silently defaults to 10 records. Include `pageInfo { hasNextPage endCursor }` for any query that may need pagination.
|
||||
|
||||
5. **SOQL-derived execution limits** — Max 10 subqueries per request, max 5 levels of child-to-parent traversal, max 1 level of parent-to-child (no grandchildren), max 2,000 records per subquery. If a query would exceed these, split into multiple requests.
|
||||
|
||||
6. **HTTP 200 does not mean success** — Salesforce returns HTTP 200 even when operations fail. Always parse the `errors` array in the response body.
|
||||
|
||||
---
|
||||
|
||||
## GraphQL Workflow
|
||||
|
||||
### Step 1: Acquire Schema
|
||||
@ -75,18 +93,18 @@ The `schema.graphql` file (265K+ lines) is the source of truth. **Never open or
|
||||
|
||||
1. Check if `schema.graphql` exists at the SFDX project root
|
||||
2. If missing, run from the **webapp dir**: `npm run graphql:schema`
|
||||
3. Custom objects appear only after metadata is deployed
|
||||
3. Custom objects appear only after metadata is deployed — invoke the `deploying-webapp-to-salesforce` skill if deployment is needed
|
||||
|
||||
### Step 2: Look Up Entity Schema
|
||||
|
||||
Map user intent to PascalCase names ("accounts" → `Account`), then **run the search script from the project root**:
|
||||
|
||||
```bash
|
||||
# From project root — look up all relevant schema info for one or more entities
|
||||
bash .a4drules/skills/using-salesforce-data/graphql-search.sh Account
|
||||
# Look up all relevant schema info for one or more entities
|
||||
bash scripts/graphql-search.sh Account
|
||||
|
||||
# Multiple entities at once
|
||||
bash .a4drules/skills/using-salesforce-data/graphql-search.sh Account Contact Opportunity
|
||||
bash scripts/graphql-search.sh Account Contact Opportunity
|
||||
```
|
||||
|
||||
The script outputs five sections per entity:
|
||||
@ -96,11 +114,11 @@ The script outputs five sections per entity:
|
||||
4. **Create input** — fields accepted by create mutations
|
||||
5. **Update input** — fields accepted by update mutations
|
||||
|
||||
Use this output to determine exact field names before writing any query or mutation. **Maximum 2 script runs.** If the entity still can't be found, ask the user — the object may not be deployed.
|
||||
Use this output to determine exact field names before writing any query or mutation. **Maximum 2 script runs.** If the entity still can't be found, ask the user — the object may not be deployed. For entity identification procedures (`_Record` suffix, `__c` conventions) and iterative introspection cycles, see [Schema Introspection](docs/schema-introspection.md).
|
||||
|
||||
### Step 3: Generate Query
|
||||
|
||||
Use the templates below. Every field name **must** be verified from the script output in Step 2.
|
||||
Use the templates below. Every field name **must** be verified from the script output in Step 2. For detailed generation rules, filtering, pagination, ordering, semi-joins, and field value wrappers, see [Read Query Generation](docs/read-query-generation.md). For mutation chaining, input/output constraints, and transactional semantics, see [Mutation Query Generation](docs/mutation-query-generation.md).
|
||||
|
||||
#### Read Query Template
|
||||
|
||||
@ -138,7 +156,7 @@ const name = node.Name?.value ?? "";
|
||||
|
||||
```graphql
|
||||
mutation CreateAccount($input: AccountCreateInput!) {
|
||||
uiapi {
|
||||
uiapi(input: { allOrNone: true }) {
|
||||
AccountCreate(input: $input) {
|
||||
Record { Id Name { value } }
|
||||
}
|
||||
@ -222,15 +240,20 @@ const fields = response?.data?.uiapi?.objectInfos?.[0]?.fields ?? [];
|
||||
|
||||
```bash
|
||||
# From project root — re-check the entity that caused the error
|
||||
bash .a4drules/skills/using-salesforce-data/graphql-search.sh <EntityName>
|
||||
bash scripts/graphql-search.sh <EntityName>
|
||||
```
|
||||
|
||||
Then fix the query using the exact names from the script output.
|
||||
Then fix the query using the exact names from the script output. For detailed error categories, status handling, and retry strategy, see [Query Testing](docs/query-testing.md).
|
||||
|
||||
---
|
||||
|
||||
## Webapp Integration (React)
|
||||
|
||||
Two integration patterns are available:
|
||||
|
||||
- **Pattern 1 — External `.graphql` file** (recommended for complex queries): Create a `.graphql` file, run `npm run graphql:codegen`, import with `?raw` suffix
|
||||
- **Pattern 2 — Inline `gql` tag** (for simple queries): Use the `gql` template tag from `@salesforce/sdk-data`. **Must use `gql`** — plain template strings bypass ESLint schema validation.
|
||||
|
||||
```typescript
|
||||
import { createDataSDK, gql } from "@salesforce/sdk-data";
|
||||
|
||||
@ -239,13 +262,7 @@ const GET_ACCOUNTS = gql`
|
||||
uiapi {
|
||||
query {
|
||||
Account(first: 10) {
|
||||
edges {
|
||||
node {
|
||||
Id
|
||||
Name @optional { value }
|
||||
Industry @optional { value }
|
||||
}
|
||||
}
|
||||
edges { node { Id Name @optional { value } } }
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -254,14 +271,14 @@ const GET_ACCOUNTS = gql`
|
||||
|
||||
const sdk = await createDataSDK();
|
||||
const response = await sdk.graphql?.(GET_ACCOUNTS);
|
||||
|
||||
if (response?.errors?.length) {
|
||||
throw new Error(response.errors.map(e => e.message).join("; "));
|
||||
}
|
||||
|
||||
const accounts = response?.data?.uiapi?.query?.Account?.edges?.map(e => e.node) ?? [];
|
||||
```
|
||||
|
||||
For detailed patterns (external .graphql files, codegen, error handling strategies, quality checklists), see [Webapp Integration](docs/webapp-integration.md).
|
||||
|
||||
---
|
||||
|
||||
## REST API Patterns
|
||||
@ -320,7 +337,7 @@ const response = await sdk.graphql?.(GET_CURRENT_USER);
|
||||
|---------|----------|-----|
|
||||
| `npm run graphql:schema` | webapp dir | Script in webapp's package.json |
|
||||
| `npx eslint <file>` | webapp dir | Reads eslint.config.js |
|
||||
| `bash .a4drules/skills/using-salesforce-data/graphql-search.sh <Entity>` | project root | Schema lookup |
|
||||
| `bash scripts/graphql-search.sh <Entity>` | skill root | Schema lookup |
|
||||
| `sf api request rest` | project root | Needs sfdx-project.json |
|
||||
|
||||
---
|
||||
@ -332,7 +349,7 @@ const response = await sdk.graphql?.(GET_CURRENT_USER);
|
||||
Run the search script to get all relevant schema info in one step:
|
||||
|
||||
```bash
|
||||
bash .a4drules/skills/using-salesforce-data/graphql-search.sh <EntityName>
|
||||
bash scripts/graphql-search.sh <EntityName>
|
||||
```
|
||||
|
||||
| Script Output Section | Used For |
|
||||
@ -358,6 +375,9 @@ bash .a4drules/skills/using-salesforce-data/graphql-search.sh <EntityName>
|
||||
### Checklist
|
||||
|
||||
- [ ] All field names verified via search script (Step 2)
|
||||
- [ ] `@optional` applied to record fields (reads)
|
||||
- [ ] `@optional` applied to all record fields (reads)
|
||||
- [ ] Mutations use `uiapi(input: { allOrNone: ... })` wrapper
|
||||
- [ ] `first:` specified in every query
|
||||
- [ ] Optional chaining in consuming code
|
||||
- [ ] `errors` array checked in response handling
|
||||
- [ ] Lint passes: `npx eslint <file>`
|
||||
|
||||
Loading…
Reference in New Issue
Block a user