afv-library/skills/generating-webapp-graphql-read-query/SKILL.md
k-j-kim 9ba064174a
feat: syncing webapp skills sync to afv @W-21338965@ (#57)
* feat: removing old webapp skills

* feat: adding sync of skills from webapps to afv

* feat: adding the first iteration of skills

* feat: pin template deps to latest npm versions and flatten skill folders

- Add pin-template-deps.js to resolve "*" deps to exact npm versions
- Integrate pinning into sync-template-skills npm script
- Remove check-template-skills-versions.js (no longer needed)
- Simplify workflow to single sync step
- Flatten skill output: one folder per skill with cleaned names

Made-with: Cursor

* fix: resolve skill validation errors

- Move .template-versions.json from skills/ to root
- Shorten skill names to meet 64-char limit:
  - salesforce-webapp-feature-micro-frontend-generating-micro-frontend-lwc → salesforce-webapp-micro-frontend-lwc
  - salesforce-webapp-feature-react-agentforce-conversation-client-integrating-agentforce-conversation-client → salesforce-webapp-agentforce-conversation-client
  - salesforce-webapp-feature-react-file-upload-implementing-file-upload → salesforce-webapp-react-file-upload
- Expand descriptions to meet 20-word minimum with trigger context

* Add webapp skills from template, sync script updates

- Rename skill folders from salesforce-webapp-* to *-webapp-* convention
- Update sync-template-skills.js: set SKILL.md front matter name to dest folder
- Remove sync-template-skills workflow and pin-template-deps script
- Add .synced-template-skills.json manifest, deploying-webapp-to-salesforce skill
- Replace salesforce-webapp-designing-webapp-ui-ux with designing-webapp-ui-ux

Made-with: Cursor

* Align SKILL.md front matter name with folder for all webapp skills

Made-with: Cursor

* Fix skill validation: description length and trigger context for configuring-webapp-metadata, creating-webapp

Made-with: Cursor

* Rename sync script to sync-webapp-skills, drop manifest file

- Rename sync-template-skills.js to sync-webapp-skills.js
- Update package.json script to sync-webapp-skills
- Remove .synced-template-skills.json creation and add to .gitignore

Made-with: Cursor

* Revert sync-react-b2e-sample and sync-react-b2x-sample to upstream version

Made-with: Cursor

* Sync script: pin b2e and b2x to latest, sync skills from template

- Pin both template packages to latest in sync-webapp-skills.js
- Update package.json / package-lock.json (b2x 1.109.0)
- Sync skills: managing-webapp-agentforce-conversation-client, bar-line-chart,
  remove building-webapp-analytics-charts and integrating-webapp-agentforce-conversation-client
- Minor skill content updates

Made-with: Cursor

* Remove interactive map, weather widget, and Unsplash skills (no longer in template)

Made-with: Cursor

---------

Co-authored-by: Hemant Singh Bisht <hsinghbisht@salesforce.com>
2026-03-19 22:46:27 +05:30

254 lines
12 KiB
Markdown

---
name: generating-webapp-graphql-read-query
description: Generate Salesforce GraphQL read queries. Use when the query to generate is a read query. Schema exploration must complete first — invoke exploring-graphql-schema first.
paths:
- "**/*.ts"
- "**/*.tsx"
- "**/*.graphql"
---
# Salesforce GraphQL Read Query Generation
**Triggering conditions**
1. Only if the schema exploration phase completed successfully (invoke `exploring-graphql-schema` first)
2. Only if the query to generate is a read query
## Schema Access Policy
> ⚠️ **GREP ONLY** — During query generation you may need to verify field names, types, or relationships. All schema lookups **MUST** use the grep-only commands defined in the `exploring-graphql-schema` skill. Do NOT open, read, stream, or parse `./schema.graphql` with any tool other than grep.
## Field-Level Security and @optional
Field-level security (FLS) restricts which fields different users can see. Use the `@optional` directive on Salesforce record fields when possible. The server omits the field when the user lacks access, allowing the query to succeed instead of failing. Apply `@optional` to scalar fields, value-type fields (e.g. `Name { value }`), parent relationships, and child relationships. Available in API v65.0+.
**Consuming code must defend against missing fields.** When a field is omitted due to FLS, it will be `undefined` (or absent) in the response. Use optional chaining (`?.`), nullish coalescing (`??`), and explicit null/undefined checks when reading query results. Never assume an optional field is present — otherwise the app may crash or behave incorrectly for users without field access.
```ts
// ✅ Defend against missing fields
const name = node.Name?.value ?? '';
const relatedName = node.RelationshipName?.Name?.value ?? 'N/A';
// ❌ Unsafe — will throw if field omitted due to FLS
const name = node.Name.value;
```
## Your Role
You are a GraphQL expert. Generate Salesforce-compatible read queries. Schema exploration must complete first. If the schema exploration has not been executed yet, you **MUST** run the full exploration workflow from the `exploring-graphql-schema` skill first, then return here for read query generation.
## Read Query Generation Workflow
Strictly follow the rules below when generating the GraphQL read query:
1. **No Proliferation** - Only generate for the explicitly requested fields, nothing else. Do NOT add fields the user did not ask for.
2. **Unique Query** - Leverage child relationships to query entities in one single query
3. **Navigate Entities** - Always use `relationshipName` to access reference fields and child entities
1. **Exception** - if the `relationshipName` field is null, you can't navigate the related entity, and will have to return the `Id` itself
4. **Leverage Fragments** - Generate one fragment per possible type on polymorphic fields (field with `dataType="REFERENCE"` and more than one entry in `referenceToInfos` introspection attribute)
5. **Type Consistency** - Make sure variables used as query arguments and their related fields share the same GraphQL type. Verify types against grep output from the schema — do not assume types
6. **Type Enforcement** - Make sure to leverage field type information from introspection and GraphQL schema to generate field access
7. **Field Name Validation** - Every field name in the generated query **MUST** match a field confirmed via grep lookup in the schema. Do NOT guess or assume field names exist
8. **@optional for FLS** - Apply `@optional` on all Salesforce record fields when possible (see [Field-Level Security and @optional](#field-level-security-and-optional)). This lets the query succeed when the user lacks field-level access; the server omits inaccessible fields instead of failing
9. **Consuming code defense** - When generating or modifying code that consumes read query results, defend against missing fields (see [Field-Level Security and @optional](#field-level-security-and-optional)). Use optional chaining, nullish coalescing, and null/undefined checks — never assume optional fields are present
10. **Semi and anti joins** - Use the semi-join or anti-join templates to filter an entity with conditions on child entities
11. **Query Generation** - Use the [template](#read-query-template) to generate the query
12. **Output Format** - Use the [standalone](#read-standalone-default-output-format---clean-code-only)
13. **Lint Validation** - After writing the query to a file, run `npx eslint <file>` from the webapp dir to validate it against the schema. Fix any reported errors before proceeding. See [Lint Validation](#lint-validation) for details
14. **Test the Query** - Use the [Generated Read Query Testing](#generated-read-query-testing) workflow to test the generated query
1. **Report First** - Always output the generated query in the proper output format BEFORE initiating any test
## Read Query Template
```graphql
query QueryName {
uiapi {
query {
EntityName(
# conditions here
) {
edges {
node {
# Direct fields — use @optional for FLS resilience
FieldName @optional { value }
# Non-polymorphic reference (single type)
RelationshipName @optional {
Id
Name { value }
}
# Polymorphic reference (multiple types)
PolymorphicRelationshipName @optional {
...TypeAInfo
...TypeBInfo
}
# Child relationship (subquery)
RelationshipName @optional (
# conditions here
) {
edges {
node {
# fields
}
}
}
}
}
}
}
}
}
fragment TypeAInfo on TypeA {
Id
SpecificFieldA @optional { value }
}
fragment TypeBInfo on TypeB {
Id
SpecificFieldB @optional { value }
}
```
## Semi-Join and Anti-Join Condition Template
Semi-joins (resp. anti-joins) condition leverage parent-child relationships and allow filtering the parent entity using a condition on child entities.
This is a standard `where` condition, on the parent entity's `Id`, expressed using the `inq` (resp. `ninq`, i.e. not `inq`) operator. This operator accepts two attributes:
- The child entity camelcase name to apply the condition on, with a value expressing the condition
- The field name on the child entity containing the parent entity `Id`, which is the `fieldName` from the `childRelationships` information for the child entity
- If the only condition is related child entity existence, you can use an `Id: { ne: null }` condition
### Semi-Join Example - ParentEntity with at least one Matching ChildEntity
```graphql
query testSemiJoin {
uiapi {
query {
ParentEntity(
where: {
Id: {
inq: {
ChildEntity: {
# standard conditions here
Name: { like: "test%" }
Type: { eq: "some value" }
}
ApiName: "parentIdFieldInChild"
}
}
}
) {
edges {
node {
Id
Name @optional {
value
}
}
}
}
}
}
}
```
### Anti-Join Example - ParentEntity with no Matching ChildEntity
Same example as the [Semi-Join Example](#semi-join-example---parententity-with-at-least-one-matching-childentity), but replacing the `inq` operator by the `ninq` one.
## Read Standalone (Default) Output Format - CLEAN CODE ONLY
```javascript
const QUERY_NAME = `
query GetData {
# query here
}
`;
const QUERY_VARIABLES = {
// variables here
};
```
**❌ FORBIDDEN — Do NOT include any of the following:**
- Explanatory comments about the query (inline or surrounding)
- Field descriptions or annotations
- Additional text about what the query does
- Workflow step descriptions or summaries
- Comments like `// fetches...`, `// returns...`, `/* ... */`
**✅ ONLY output:**
- The raw query string constant
- The variables object constant
- Nothing else — no imports, no exports, no wrapper functions
## Lint Validation
After writing the generated query into a source file, validate it against the schema using the project's GraphQL ESLint setup:
```bash
# Run from webapp dir (force-app/main/default/webapplications/<app-name>/)
npx eslint <path-to-file-containing-query>
```
**How it works:** The ESLint config uses `@graphql-eslint/eslint-plugin` with its `processor`, which extracts GraphQL operations from `gql` template literals in `.ts`/`.tsx` files and validates the extracted `.graphql` virtual files against `schema.graphql`.
**Rules enforced:** `no-anonymous-operations`, `no-duplicate-fields`, `known-fragment-names`, `no-undefined-variables`, `no-unused-variables`
**On failure:** Fix the reported issues, re-run `npx eslint <file>` until clean, then proceed to testing.
> ⚠️ **Prerequisites**: The `schema.graphql` file must exist (invoke `exploring-graphql-schema` first) and project dependencies must be installed (`npm install`).
## Generated Read Query Testing
**Triggering conditions****ALL conditions must be true:**
1. The [Read Query Generation Workflow](#read-query-generation-workflow) completed with status `SUCCESS` and you have a generated query
2. The query is a read query
3. A non-manual method was used during schema exploration to retrieve introspection data
**Workflow**
1. **Report Step** - State the exact method you will use to test (e.g., `sf api request graphql` from the **project root**, Connect API, etc.) — this **MUST** match the method used during schema exploration
2. **Interactive Step** - Ask the user whether they want you to test the query using the proposed method
1. **STOP and WAIT** for the user's answer. Do NOT proceed until the user responds. Do NOT assume consent.
3. **Test Query** - Only if the user explicitly agrees:
1. Use `sf api request rest` to POST the query to the GraphQL endpoint:
```bash
sf api request rest /services/data/v65.0/graphql \
--method POST \
--body '{"query":"query GetData { uiapi { query { EntityName { edges { node { Id } } } } } }"}'
```
2. Replace `v65.0` with the API version of the target org
3. Replace the `query` value with the generated read query string
4. If the query uses variables, include them in the JSON body as a `variables` key
5. Report the result as `SUCCESS` if the query executed without error, or `FAILED` if errors were returned
6. An empty result set with no errors is `SUCCESS` — the query is valid, the org simply has no matching data
4. **Remediation Step** - If status is `FAILED`, use the [`FAILED` status handling workflows](#failed-status-handling-workflow)
### `FAILED` Status Handling Workflow
The query is invalid:
1. **Error Analysis** - Parse and categorize the specific error messages
2. **Root Cause Identification** - Use error message to identify the root cause:
- **Syntax** - Error contains `invalid syntax`
- **Validation** - Error contains `validation error`
- **Type** - Error contains `VariableTypeMismatch` or `UnknownType`
3. **Targeted Resolution** - Depending on the root cause categorization
- **Syntax** - Update the query using the error message information to fix the syntax errors
- **Validation** - The field name is most probably invalid. Re-run the relevant grep command from the `exploring-graphql-schema` skill to verify the correct field name. If still unclear, ask the user for clarification and **STOP and WAIT** for their answer
- **Type** - Use the error details and re-verify the type via grep lookup in the schema. Correct the argument type and adjust variables accordingly
4. **Test Again** - Resume the [query testing workflow](#generated-read-query-testing) with the updated query (increment and track attempt counter)
5. **Escalation Path** - If targeted resolution fails after 2 attempts, ask for additional details and restart the entire GraphQL workflow from the `exploring-graphql-schema` skill
## Related Skills
- Schema exploration: `exploring-graphql-schema` (must complete first)
- Mutation generation: `generating-graphql-mutation-query`