@salesforce/webapp-template-feature-react-authentication-experimental 1.111.0 → 1.112.1
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.
- package/dist/.a4drules/skills/creating-webapp/SKILL.md +1 -2
- package/dist/.a4drules/skills/deploying-to-salesforce/SKILL.md +1 -2
- package/dist/.a4drules/skills/using-salesforce-data/SKILL.md +268 -0
- package/dist/CHANGELOG.md +19 -0
- package/dist/force-app/main/default/webapplications/feature-react-authentication/package.json +3 -3
- package/dist/package-lock.json +2 -2
- package/dist/package.json +1 -1
- package/package.json +2 -2
- package/dist/.a4drules/skills/accessing-data/SKILL.md +0 -178
- package/dist/.a4drules/skills/exploring-graphql-schema/SKILL.md +0 -149
- package/dist/.a4drules/skills/fetching-rest-api/SKILL.md +0 -167
- package/dist/.a4drules/skills/generating-graphql-mutation-query/SKILL.md +0 -258
- package/dist/.a4drules/skills/generating-graphql-read-query/SKILL.md +0 -253
- package/dist/.a4drules/skills/using-graphql/SKILL.md +0 -324
- package/dist/.a4drules/skills/using-graphql/shared-schema.graphqls +0 -1150
|
@@ -1,253 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: generating-graphql-read-query
|
|
3
|
-
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.
|
|
4
|
-
paths:
|
|
5
|
-
- "**/*.ts"
|
|
6
|
-
- "**/*.tsx"
|
|
7
|
-
- "**/*.graphql"
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
# Salesforce GraphQL Read Query Generation
|
|
11
|
-
|
|
12
|
-
**Triggering conditions**
|
|
13
|
-
|
|
14
|
-
1. Only if the schema exploration phase completed successfully (invoke `exploring-graphql-schema` first)
|
|
15
|
-
2. Only if the query to generate is a read query
|
|
16
|
-
|
|
17
|
-
## Schema Access Policy
|
|
18
|
-
|
|
19
|
-
> ⚠️ **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.
|
|
20
|
-
|
|
21
|
-
## Field-Level Security and @optional
|
|
22
|
-
|
|
23
|
-
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+.
|
|
24
|
-
|
|
25
|
-
**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.
|
|
26
|
-
|
|
27
|
-
```ts
|
|
28
|
-
// ✅ Defend against missing fields
|
|
29
|
-
const name = node.Name?.value ?? '';
|
|
30
|
-
const relatedName = node.RelationshipName?.Name?.value ?? 'N/A';
|
|
31
|
-
|
|
32
|
-
// ❌ Unsafe — will throw if field omitted due to FLS
|
|
33
|
-
const name = node.Name.value;
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
## Your Role
|
|
37
|
-
|
|
38
|
-
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.
|
|
39
|
-
|
|
40
|
-
## Read Query Generation Workflow
|
|
41
|
-
|
|
42
|
-
Strictly follow the rules below when generating the GraphQL read query:
|
|
43
|
-
|
|
44
|
-
1. **No Proliferation** - Only generate for the explicitly requested fields, nothing else. Do NOT add fields the user did not ask for.
|
|
45
|
-
2. **Unique Query** - Leverage child relationships to query entities in one single query
|
|
46
|
-
3. **Navigate Entities** - Always use `relationshipName` to access reference fields and child entities
|
|
47
|
-
1. **Exception** - if the `relationshipName` field is null, you can't navigate the related entity, and will have to return the `Id` itself
|
|
48
|
-
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)
|
|
49
|
-
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
|
|
50
|
-
6. **Type Enforcement** - Make sure to leverage field type information from introspection and GraphQL schema to generate field access
|
|
51
|
-
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
|
|
52
|
-
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
|
|
53
|
-
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
|
|
54
|
-
10. **Semi and anti joins** - Use the semi-join or anti-join templates to filter an entity with conditions on child entities
|
|
55
|
-
11. **Query Generation** - Use the [template](#read-query-template) to generate the query
|
|
56
|
-
12. **Output Format** - Use the [standalone](#read-standalone-default-output-format---clean-code-only)
|
|
57
|
-
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
|
|
58
|
-
14. **Test the Query** - Use the [Generated Read Query Testing](#generated-read-query-testing) workflow to test the generated query
|
|
59
|
-
1. **Report First** - Always output the generated query in the proper output format BEFORE initiating any test
|
|
60
|
-
|
|
61
|
-
## Read Query Template
|
|
62
|
-
|
|
63
|
-
```graphql
|
|
64
|
-
query QueryName {
|
|
65
|
-
uiapi {
|
|
66
|
-
query {
|
|
67
|
-
EntityName(
|
|
68
|
-
# conditions here
|
|
69
|
-
) {
|
|
70
|
-
edges {
|
|
71
|
-
node {
|
|
72
|
-
# Direct fields — use @optional for FLS resilience
|
|
73
|
-
FieldName @optional { value }
|
|
74
|
-
|
|
75
|
-
# Non-polymorphic reference (single type)
|
|
76
|
-
RelationshipName @optional {
|
|
77
|
-
Id
|
|
78
|
-
Name { value }
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
# Polymorphic reference (multiple types)
|
|
82
|
-
PolymorphicRelationshipName @optional {
|
|
83
|
-
...TypeAInfo
|
|
84
|
-
...TypeBInfo
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
# Child relationship (subquery)
|
|
88
|
-
RelationshipName @optional (
|
|
89
|
-
# conditions here
|
|
90
|
-
) {
|
|
91
|
-
edges {
|
|
92
|
-
node {
|
|
93
|
-
# fields
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
fragment TypeAInfo on TypeA {
|
|
105
|
-
Id
|
|
106
|
-
SpecificFieldA @optional { value }
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
fragment TypeBInfo on TypeB {
|
|
110
|
-
Id
|
|
111
|
-
SpecificFieldB @optional { value }
|
|
112
|
-
}
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
## Semi-Join and Anti-Join Condition Template
|
|
116
|
-
|
|
117
|
-
Semi-joins (resp. anti-joins) condition leverage parent-child relationships and allow filtering the parent entity using a condition on child entities.
|
|
118
|
-
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:
|
|
119
|
-
|
|
120
|
-
- The child entity camelcase name to apply the condition on, with a value expressing the condition
|
|
121
|
-
- The field name on the child entity containing the parent entity `Id`, which is the `fieldName` from the `childRelationships` information for the child entity
|
|
122
|
-
- If the only condition is related child entity existence, you can use an `Id: { ne: null }` condition
|
|
123
|
-
|
|
124
|
-
### Semi-Join Example - ParentEntity with at least one Matching ChildEntity
|
|
125
|
-
|
|
126
|
-
```graphql
|
|
127
|
-
query testSemiJoin {
|
|
128
|
-
uiapi {
|
|
129
|
-
query {
|
|
130
|
-
ParentEntity(
|
|
131
|
-
where: {
|
|
132
|
-
Id: {
|
|
133
|
-
inq: {
|
|
134
|
-
ChildEntity: {
|
|
135
|
-
# standard conditions here
|
|
136
|
-
Name: { like: "test%" }
|
|
137
|
-
Type: { eq: "some value" }
|
|
138
|
-
}
|
|
139
|
-
ApiName: "parentIdFieldInChild"
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
) {
|
|
144
|
-
edges {
|
|
145
|
-
node {
|
|
146
|
-
Id
|
|
147
|
-
Name @optional {
|
|
148
|
-
value
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
### Anti-Join Example - ParentEntity with no Matching ChildEntity
|
|
159
|
-
|
|
160
|
-
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.
|
|
161
|
-
|
|
162
|
-
## Read Standalone (Default) Output Format - CLEAN CODE ONLY
|
|
163
|
-
|
|
164
|
-
```javascript
|
|
165
|
-
const QUERY_NAME = `
|
|
166
|
-
query GetData {
|
|
167
|
-
# query here
|
|
168
|
-
}
|
|
169
|
-
`;
|
|
170
|
-
|
|
171
|
-
const QUERY_VARIABLES = {
|
|
172
|
-
// variables here
|
|
173
|
-
};
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
**❌ FORBIDDEN — Do NOT include any of the following:**
|
|
177
|
-
|
|
178
|
-
- Explanatory comments about the query (inline or surrounding)
|
|
179
|
-
- Field descriptions or annotations
|
|
180
|
-
- Additional text about what the query does
|
|
181
|
-
- Workflow step descriptions or summaries
|
|
182
|
-
- Comments like `// fetches...`, `// returns...`, `/* ... */`
|
|
183
|
-
|
|
184
|
-
**✅ ONLY output:**
|
|
185
|
-
|
|
186
|
-
- The raw query string constant
|
|
187
|
-
- The variables object constant
|
|
188
|
-
- Nothing else — no imports, no exports, no wrapper functions
|
|
189
|
-
|
|
190
|
-
## Lint Validation
|
|
191
|
-
|
|
192
|
-
After writing the generated query into a source file, validate it against the schema using the project's GraphQL ESLint setup:
|
|
193
|
-
|
|
194
|
-
```bash
|
|
195
|
-
# Run from webapp dir (force-app/main/default/webapplications/<app-name>/)
|
|
196
|
-
npx eslint <path-to-file-containing-query>
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
**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`.
|
|
200
|
-
|
|
201
|
-
**Rules enforced:** `no-anonymous-operations`, `no-duplicate-fields`, `known-fragment-names`, `no-undefined-variables`, `no-unused-variables`
|
|
202
|
-
|
|
203
|
-
**On failure:** Fix the reported issues, re-run `npx eslint <file>` until clean, then proceed to testing.
|
|
204
|
-
|
|
205
|
-
> ⚠️ **Prerequisites**: The `schema.graphql` file must exist (invoke `exploring-graphql-schema` first) and project dependencies must be installed (`npm install`).
|
|
206
|
-
|
|
207
|
-
## Generated Read Query Testing
|
|
208
|
-
|
|
209
|
-
**Triggering conditions** — **ALL conditions must be true:**
|
|
210
|
-
|
|
211
|
-
1. The [Read Query Generation Workflow](#read-query-generation-workflow) completed with status `SUCCESS` and you have a generated query
|
|
212
|
-
2. The query is a read query
|
|
213
|
-
3. A non-manual method was used during schema exploration to retrieve introspection data
|
|
214
|
-
|
|
215
|
-
**Workflow**
|
|
216
|
-
|
|
217
|
-
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
|
|
218
|
-
2. **Interactive Step** - Ask the user whether they want you to test the query using the proposed method
|
|
219
|
-
1. **STOP and WAIT** for the user's answer. Do NOT proceed until the user responds. Do NOT assume consent.
|
|
220
|
-
3. **Test Query** - Only if the user explicitly agrees:
|
|
221
|
-
1. Use `sf api request rest` to POST the query to the GraphQL endpoint:
|
|
222
|
-
```bash
|
|
223
|
-
sf api request rest /services/data/v65.0/graphql \
|
|
224
|
-
--method POST \
|
|
225
|
-
--body '{"query":"query GetData { uiapi { query { EntityName { edges { node { Id } } } } } }"}'
|
|
226
|
-
```
|
|
227
|
-
2. Replace `v65.0` with the API version of the target org
|
|
228
|
-
3. Replace the `query` value with the generated read query string
|
|
229
|
-
4. If the query uses variables, include them in the JSON body as a `variables` key
|
|
230
|
-
5. Report the result as `SUCCESS` if the query executed without error, or `FAILED` if errors were returned
|
|
231
|
-
6. An empty result set with no errors is `SUCCESS` — the query is valid, the org simply has no matching data
|
|
232
|
-
4. **Remediation Step** - If status is `FAILED`, use the [`FAILED` status handling workflows](#failed-status-handling-workflow)
|
|
233
|
-
|
|
234
|
-
### `FAILED` Status Handling Workflow
|
|
235
|
-
|
|
236
|
-
The query is invalid:
|
|
237
|
-
|
|
238
|
-
1. **Error Analysis** - Parse and categorize the specific error messages
|
|
239
|
-
2. **Root Cause Identification** - Use error message to identify the root cause:
|
|
240
|
-
- **Syntax** - Error contains `invalid syntax`
|
|
241
|
-
- **Validation** - Error contains `validation error`
|
|
242
|
-
- **Type** - Error contains `VariableTypeMismatch` or `UnknownType`
|
|
243
|
-
3. **Targeted Resolution** - Depending on the root cause categorization
|
|
244
|
-
- **Syntax** - Update the query using the error message information to fix the syntax errors
|
|
245
|
-
- **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
|
|
246
|
-
- **Type** - Use the error details and re-verify the type via grep lookup in the schema. Correct the argument type and adjust variables accordingly
|
|
247
|
-
4. **Test Again** - Resume the [query testing workflow](#generated-read-query-testing) with the updated query (increment and track attempt counter)
|
|
248
|
-
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
|
|
249
|
-
|
|
250
|
-
## Related Skills
|
|
251
|
-
|
|
252
|
-
- Schema exploration: `exploring-graphql-schema` (must complete first)
|
|
253
|
-
- Mutation generation: `generating-graphql-mutation-query`
|
|
@@ -1,324 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: using-graphql
|
|
3
|
-
description: Salesforce GraphQL data access. Use when the user asks to fetch, query, or mutate Salesforce data, or add a GraphQL operation for an object like Account, Contact, or Opportunity.
|
|
4
|
-
paths:
|
|
5
|
-
- "**/*.ts"
|
|
6
|
-
- "**/*.tsx"
|
|
7
|
-
- "**/*.graphql"
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
# Salesforce GraphQL
|
|
11
|
-
|
|
12
|
-
Guidance for querying and mutating Salesforce data via the Salesforce GraphQL API. Use `createDataSDK()` + `sdk.graphql?.()` and codegen tooling.
|
|
13
|
-
|
|
14
|
-
## When to Use
|
|
15
|
-
|
|
16
|
-
- User asks to "fetch data from Salesforce"
|
|
17
|
-
- User asks to "query" or "mutate" Salesforce records
|
|
18
|
-
- User wants to add a new GraphQL operation (query or mutation)
|
|
19
|
-
- User asks to add data access for a Salesforce object (Account, Contact, Opportunity, etc.)
|
|
20
|
-
|
|
21
|
-
## Schema Access Policy (GREP ONLY)
|
|
22
|
-
|
|
23
|
-
> **GREP ONLY** — The `schema.graphql` file is very large (~265,000+ lines). 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.
|
|
24
|
-
|
|
25
|
-
## Directory Context
|
|
26
|
-
|
|
27
|
-
The generated app has a two-level directory structure. Commands must run from the correct directory.
|
|
28
|
-
|
|
29
|
-
```
|
|
30
|
-
<project-root>/ ← SFDX project root
|
|
31
|
-
├── schema.graphql ← grep target
|
|
32
|
-
├── sfdx-project.json
|
|
33
|
-
└── force-app/main/default/webapplications/<app-name>/ ← webapp dir
|
|
34
|
-
├── package.json (npm scripts: graphql:schema, graphql:codegen, lint)
|
|
35
|
-
├── eslint.config.js (schema ref: ../../../../../schema.graphql)
|
|
36
|
-
├── codegen.yml (schema ref: ../../../../../schema.graphql)
|
|
37
|
-
└── src/ (source code, .graphql query files)
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
| Command | Run from | Why |
|
|
41
|
-
| ------------------------- | ---------------- | -------------------------------------- |
|
|
42
|
-
| `npm run graphql:schema` | **webapp dir** | Script is in webapp's `package.json` |
|
|
43
|
-
| `npm run graphql:codegen` | **webapp dir** | Reads `codegen.yml` in webapp dir |
|
|
44
|
-
| `npx eslint <file>` | **webapp dir** | Reads `eslint.config.js` in webapp dir |
|
|
45
|
-
| `grep ... schema.graphql` | **project root** | `schema.graphql` lives at project root |
|
|
46
|
-
| `sf api request graphql` | **project root** | Needs `sfdx-project.json` |
|
|
47
|
-
|
|
48
|
-
> **Wrong directory = silent failures.** `npm run graphql:schema` from the project root will fail with "missing script." `grep ./schema.graphql` from the webapp dir will fail with "no such file."
|
|
49
|
-
|
|
50
|
-
## Prerequisites
|
|
51
|
-
|
|
52
|
-
The base React app (`base-react-app`) ships with all GraphQL dependencies and tooling pre-configured:
|
|
53
|
-
|
|
54
|
-
- `@salesforce/sdk-data` — runtime SDK for `createDataSDK` and `gql`
|
|
55
|
-
- `@graphql-codegen/cli` + plugins — type generation from `.graphql` files and inline `gql` queries
|
|
56
|
-
- `@graphql-eslint/eslint-plugin` — validates `.graphql` files and `gql` template literals against `schema.graphql` (used as a query validation gate — see Step 6)
|
|
57
|
-
- `graphql` — shared by codegen, ESLint, and schema introspection
|
|
58
|
-
|
|
59
|
-
Before using this skill, ensure:
|
|
60
|
-
|
|
61
|
-
1. The `@salesforce/sdk-data` package is available (provides `createDataSDK`, `gql`, `NodeOfConnection`)
|
|
62
|
-
2. **Deployment order**: Metadata must be deployed before schema fetch; schema must be refetched after any metadata deployment. Invoke the `deploying-to-salesforce` skill when deploying or syncing with the org.
|
|
63
|
-
3. A `schema.graphql` file exists at the project root. If missing, generate it:
|
|
64
|
-
```bash
|
|
65
|
-
# Run from webapp dir (force-app/main/default/webapplications/<app-name>/)
|
|
66
|
-
npm run graphql:schema
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
## npm Scripts
|
|
70
|
-
|
|
71
|
-
- **`npm run graphql:schema`** — _(run from webapp dir)_ Downloads the full GraphQL schema from a connected Salesforce org via introspection. Outputs `schema.graphql` to the project root.
|
|
72
|
-
- **`npm run graphql:codegen`** — _(run from webapp dir)_ Generates TypeScript types from `.graphql` files and inline `gql` queries. Outputs to `src/api/graphql-operations-types.ts`.
|
|
73
|
-
|
|
74
|
-
## Workflow
|
|
75
|
-
|
|
76
|
-
### Step 1: Download Schema
|
|
77
|
-
|
|
78
|
-
Ensure `schema.graphql` exists at the project root. If missing, run `npm run graphql:schema` from the webapp dir.
|
|
79
|
-
|
|
80
|
-
### Step 2: Explore the Schema (grep-only)
|
|
81
|
-
|
|
82
|
-
Before writing any query, verify the target object and its fields exist in the schema.
|
|
83
|
-
|
|
84
|
-
**Invoke the `exploring-graphql-schema` skill** for the full exploration workflow and **mandatory grep-only access policy**.
|
|
85
|
-
|
|
86
|
-
> **GREP ONLY** — All schema lookups MUST use the grep commands defined in the `exploring-graphql-schema` skill. Do NOT open, read, stream, or parse `./schema.graphql` with any tool other than grep.
|
|
87
|
-
|
|
88
|
-
Key actions (all via grep):
|
|
89
|
-
|
|
90
|
-
- `type <ObjectName> implements Record` — find available fields
|
|
91
|
-
- `input <ObjectName>_Filter` — find filter options
|
|
92
|
-
- `input <ObjectName>_OrderBy` — find sorting options
|
|
93
|
-
- `input <ObjectName>CreateInput` / `<ObjectName>UpdateInput` — find mutation input types
|
|
94
|
-
|
|
95
|
-
### Step 3: Choose the Query Pattern
|
|
96
|
-
|
|
97
|
-
**Pattern 1 — External `.graphql` file** (recommended for complex queries):
|
|
98
|
-
|
|
99
|
-
- Queries with variables, fragments, or shared across files
|
|
100
|
-
- Full codegen support, syntax highlighting, shareable
|
|
101
|
-
- Requires codegen step after changes
|
|
102
|
-
- See example: `api/utils/accounts.ts` + `api/utils/query/highRevenueAccountsQuery.graphql`
|
|
103
|
-
|
|
104
|
-
**Pattern 2 — Inline `gql` tag** (recommended for simple queries):
|
|
105
|
-
|
|
106
|
-
- Simple queries without variables; colocated with usage code
|
|
107
|
-
- Supports dynamic queries (field set varies at runtime)
|
|
108
|
-
- **MUST use `gql` tag** — plain template strings bypass `@graphql-eslint` validation
|
|
109
|
-
- See example: `api/utils/user.ts`
|
|
110
|
-
|
|
111
|
-
### Step 4: Write the Query
|
|
112
|
-
|
|
113
|
-
For **Pattern 1**:
|
|
114
|
-
|
|
115
|
-
1. Create a `.graphql` file under `src/api/utils/query/`
|
|
116
|
-
2. Follow UIAPI structure: `query { uiapi { query { ObjectName(...) { edges { node { ... } } } } } }`
|
|
117
|
-
3. For mutations, invoke the `generating-graphql-mutation-query` skill
|
|
118
|
-
4. For read queries, invoke the `generating-graphql-read-query` skill
|
|
119
|
-
|
|
120
|
-
For **Pattern 2**:
|
|
121
|
-
|
|
122
|
-
1. Define query inline using the `gql` template tag
|
|
123
|
-
2. Ensure the query name matches what codegen expects
|
|
124
|
-
|
|
125
|
-
### Step 5: Test Queries Against Live Org
|
|
126
|
-
|
|
127
|
-
Use the testing workflows in the `generating-graphql-read-query` and `generating-graphql-mutation-query` skills to validate queries against the connected org before integrating into the app.
|
|
128
|
-
|
|
129
|
-
### Step 6: Generate Types
|
|
130
|
-
|
|
131
|
-
```bash
|
|
132
|
-
# Run from webapp dir (force-app/main/default/webapplications/<app-name>/)
|
|
133
|
-
npm run graphql:codegen
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
This updates `src/api/graphql-operations-types.ts` with `<OperationName>Query`/`<OperationName>Mutation` and `<OperationName>QueryVariables`/`<OperationName>MutationVariables`.
|
|
137
|
-
|
|
138
|
-
### Step 7: Lint Validate
|
|
139
|
-
|
|
140
|
-
Run ESLint on the file containing the query to validate it against the schema **before** any live testing:
|
|
141
|
-
|
|
142
|
-
```bash
|
|
143
|
-
# Run from webapp dir
|
|
144
|
-
npx eslint <path-to-file>
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
The `@graphql-eslint/eslint-plugin` processor extracts GraphQL from `gql` template literals and validates them against `schema.graphql`. Fix all ESLint errors before proceeding.
|
|
148
|
-
|
|
149
|
-
### Step 8: Implement and Verify
|
|
150
|
-
|
|
151
|
-
Implement the data access function using the pattern below. Use the Quality Checklist before completing.
|
|
152
|
-
|
|
153
|
-
---
|
|
154
|
-
|
|
155
|
-
## Core Types & Function Signatures
|
|
156
|
-
|
|
157
|
-
### createDataSDK and graphql
|
|
158
|
-
|
|
159
|
-
```typescript
|
|
160
|
-
import { createDataSDK } from "@salesforce/sdk-data";
|
|
161
|
-
|
|
162
|
-
const sdk = await createDataSDK();
|
|
163
|
-
const response = await sdk.graphql?.<ResponseType, VariablesType>(query, variables);
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
`createDataSDK()` returns a `DataSDK` instance. The `graphql` method uses optional chaining (`?.`) because not all surfaces support GraphQL.
|
|
167
|
-
|
|
168
|
-
### gql Template Tag
|
|
169
|
-
|
|
170
|
-
```typescript
|
|
171
|
-
import { gql } from "@salesforce/sdk-data";
|
|
172
|
-
|
|
173
|
-
const MY_QUERY = gql`
|
|
174
|
-
query MyQuery {
|
|
175
|
-
uiapi { ... }
|
|
176
|
-
}
|
|
177
|
-
`;
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
The `gql` tag enables ESLint validation against the schema. Plain template strings bypass validation.
|
|
181
|
-
|
|
182
|
-
### Error Handling
|
|
183
|
-
|
|
184
|
-
Default: treat any errors as failure (Strategy A). For partial data tolerance, log errors but use data. For mutations where some return fields are inaccessible, use Strategy C (fail only when no data).
|
|
185
|
-
|
|
186
|
-
```typescript
|
|
187
|
-
// Default: strict
|
|
188
|
-
if (response?.errors?.length) {
|
|
189
|
-
throw new Error(response.errors.map((e) => e.message).join("; "));
|
|
190
|
-
}
|
|
191
|
-
const result = response?.data;
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
Responses follow `uiapi.query.ObjectName.edges[].node`; fields use `{ value }`.
|
|
195
|
-
|
|
196
|
-
### NodeOfConnection
|
|
197
|
-
|
|
198
|
-
```typescript
|
|
199
|
-
import { type NodeOfConnection } from "@salesforce/sdk-data";
|
|
200
|
-
|
|
201
|
-
type AccountNode = NodeOfConnection<GetHighRevenueAccountsQuery["uiapi"]["query"]["Account"]>;
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
---
|
|
205
|
-
|
|
206
|
-
## Pattern 1: External .graphql File
|
|
207
|
-
|
|
208
|
-
Create a `.graphql` file, run `npm run graphql:codegen`, import with `?raw` suffix, and use generated types.
|
|
209
|
-
|
|
210
|
-
**Required imports:**
|
|
211
|
-
|
|
212
|
-
```typescript
|
|
213
|
-
import { createDataSDK, type NodeOfConnection } from "@salesforce/sdk-data";
|
|
214
|
-
import MY_QUERY from "./query/myQuery.graphql?raw"; // ← ?raw suffix required
|
|
215
|
-
import type { GetMyDataQuery, GetMyDataQueryVariables } from "../graphql-operations-types";
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
**When to use:** Complex queries with variables, fragments, or shared across files. Does NOT support dynamic queries (field set varies at runtime).
|
|
219
|
-
|
|
220
|
-
---
|
|
221
|
-
|
|
222
|
-
## Pattern 2: Inline gql Tag
|
|
223
|
-
|
|
224
|
-
**Required imports:**
|
|
225
|
-
|
|
226
|
-
```typescript
|
|
227
|
-
import { createDataSDK, gql } from "@salesforce/sdk-data";
|
|
228
|
-
import { type CurrentUserQuery } from "../graphql-operations-types";
|
|
229
|
-
|
|
230
|
-
const MY_QUERY = gql`
|
|
231
|
-
query CurrentUser {
|
|
232
|
-
uiapi { ... }
|
|
233
|
-
}
|
|
234
|
-
`;
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
> **MUST use `gql` tag** — plain template strings bypass the `@graphql-eslint` processor entirely, meaning no lint validation against the schema.
|
|
238
|
-
|
|
239
|
-
**When to use:** Simple, colocated queries. Supports dynamic queries (field set varies at runtime).
|
|
240
|
-
|
|
241
|
-
---
|
|
242
|
-
|
|
243
|
-
## Conditional Field Selection
|
|
244
|
-
|
|
245
|
-
For dynamic fieldsets with **known** fields, use `@include(if: $condition)` and `@skip(if: $condition)` in `.graphql` files. See GraphQL spec for details.
|
|
246
|
-
|
|
247
|
-
---
|
|
248
|
-
|
|
249
|
-
## Anti-Patterns (Not Recommended)
|
|
250
|
-
|
|
251
|
-
### Direct API Calls
|
|
252
|
-
|
|
253
|
-
```typescript
|
|
254
|
-
// NOT RECOMMENDED: Direct axios/fetch calls for GraphQL
|
|
255
|
-
// PREFERRED: Use the Data SDK
|
|
256
|
-
const sdk = await createDataSDK();
|
|
257
|
-
const response = await sdk.graphql?.<ResponseType>(query, variables);
|
|
258
|
-
```
|
|
259
|
-
|
|
260
|
-
### Missing Type Definitions
|
|
261
|
-
|
|
262
|
-
```typescript
|
|
263
|
-
// NOT RECOMMENDED: Untyped GraphQL calls
|
|
264
|
-
// PREFERRED: Provide response type
|
|
265
|
-
const response = await sdk.graphql?.<GetMyDataQuery>(query);
|
|
266
|
-
```
|
|
267
|
-
|
|
268
|
-
### Plain String Queries (Without gql Tag)
|
|
269
|
-
|
|
270
|
-
```typescript
|
|
271
|
-
// NOT RECOMMENDED: Plain strings bypass ESLint validation
|
|
272
|
-
const query = `query { ... }`;
|
|
273
|
-
|
|
274
|
-
// PREFERRED: Use gql tag for inline queries
|
|
275
|
-
const QUERY = gql`query { ... }`;
|
|
276
|
-
```
|
|
277
|
-
|
|
278
|
-
---
|
|
279
|
-
|
|
280
|
-
## Quality Checklist
|
|
281
|
-
|
|
282
|
-
> If you have not completed the workflow above, **stop and complete it first**. Invoke the skill workflow before using this checklist.
|
|
283
|
-
|
|
284
|
-
Before completing GraphQL data access code:
|
|
285
|
-
|
|
286
|
-
### For Pattern 1 (.graphql files):
|
|
287
|
-
|
|
288
|
-
1. [ ] All field names verified via grep against `schema.graphql` (invoke `exploring-graphql-schema`)
|
|
289
|
-
2. [ ] Create `.graphql` file for the query/mutation
|
|
290
|
-
3. [ ] Run `npm run graphql:codegen` to generate types
|
|
291
|
-
4. [ ] Import query with `?raw` suffix
|
|
292
|
-
5. [ ] Import generated types from `graphql-operations-types.ts`
|
|
293
|
-
6. [ ] Use `sdk.graphql?.<ResponseType>()` with proper generic
|
|
294
|
-
7. [ ] Handle `response.errors` and destructure `response.data`
|
|
295
|
-
8. [ ] Use `NodeOfConnection` for cleaner node types when needed
|
|
296
|
-
9. [ ] Run `npx eslint <file>` from webapp dir — fix all GraphQL errors
|
|
297
|
-
|
|
298
|
-
### For Pattern 2 (inline with gql):
|
|
299
|
-
|
|
300
|
-
1. [ ] All field names verified via grep against `schema.graphql`
|
|
301
|
-
2. [ ] Define query using `gql` template tag (NOT a plain string)
|
|
302
|
-
3. [ ] Ensure query name matches generated types in `graphql-operations-types.ts`
|
|
303
|
-
4. [ ] Import generated types for the query
|
|
304
|
-
5. [ ] Use `sdk.graphql?.<ResponseType>()` with proper generic
|
|
305
|
-
6. [ ] Handle `response.errors` and destructure `response.data`
|
|
306
|
-
7. [ ] Run `npx eslint <file>` from webapp dir — fix all GraphQL errors
|
|
307
|
-
|
|
308
|
-
### General:
|
|
309
|
-
|
|
310
|
-
- [ ] Lint validation passes (`npx eslint <file>` reports no GraphQL errors)
|
|
311
|
-
- [ ] Query field names match the schema exactly (case-sensitive, confirmed via grep)
|
|
312
|
-
- [ ] Response type generic is provided to `sdk.graphql?.<T>()`
|
|
313
|
-
- [ ] Optional chaining is used for nested response data
|
|
314
|
-
|
|
315
|
-
---
|
|
316
|
-
|
|
317
|
-
## Reference
|
|
318
|
-
|
|
319
|
-
- Schema exploration: invoke the `exploring-graphql-schema` skill
|
|
320
|
-
- Read query generation: invoke the `generating-graphql-read-query` skill
|
|
321
|
-
- Mutation query generation: invoke the `generating-graphql-mutation-query` skill
|
|
322
|
-
- Shared GraphQL schema types: `shared-schema.graphqls` (in this skill directory)
|
|
323
|
-
- Schema download: `npm run graphql:schema` (run from webapp dir)
|
|
324
|
-
- Type generation: `npm run graphql:codegen` (run from webapp dir)
|