mirror of
https://github.com/forcedotcom/afv-library.git
synced 2026-08-03 05:41:08 +08:00
* 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>
254 lines
12 KiB
Markdown
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`
|