@salesforce/webapp-template-feature-react-chart-experimental 1.105.1 → 1.106.0
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/salesforce-data-access/SKILL.md +165 -0
- package/dist/.a4drules/skills/salesforce-graphql/SKILL.md +323 -0
- package/dist/.a4drules/skills/salesforce-graphql-explore-schema/SKILL.md +160 -0
- package/dist/.a4drules/skills/{implementing-graphql-data-access/docs/generate-mutation-query.md → salesforce-graphql-mutation-query/SKILL.md} +72 -42
- package/dist/.a4drules/skills/salesforce-graphql-read-query/SKILL.md +253 -0
- package/dist/.a4drules/skills/salesforce-rest-api-fetch/SKILL.md +167 -0
- package/dist/.a4drules/webapp-react.md +1 -49
- package/dist/AGENT.md +5 -0
- package/dist/CHANGELOG.md +11 -0
- package/dist/force-app/main/default/webapplications/feature-react-chart/eslint.config.js +42 -27
- package/dist/package.json +1 -1
- package/package.json +2 -2
- package/dist/.a4drules/features/feature-graphql-graphql-data-access-rule.md +0 -470
- package/dist/.a4drules/skills/implementing-graphql-data-access/SKILL.md +0 -155
- package/dist/.a4drules/skills/implementing-graphql-data-access/docs/explore-schema.md +0 -256
- package/dist/.a4drules/skills/implementing-graphql-data-access/docs/generate-read-query.md +0 -202
- /package/dist/.a4drules/skills/{implementing-graphql-data-access/docs → salesforce-graphql}/shared-schema.graphqls +0 -0
|
@@ -1,40 +1,47 @@
|
|
|
1
|
-
|
|
1
|
+
---
|
|
2
|
+
name: salesforce-graphql-mutation-query
|
|
3
|
+
description: Generate Salesforce GraphQL mutation queries. Use when the query to generate is a mutation query. Schema exploration must complete first — invoke salesforce-graphql-explore-schema first.
|
|
4
|
+
paths:
|
|
5
|
+
- "**/*.ts"
|
|
6
|
+
- "**/*.tsx"
|
|
7
|
+
- "**/*.graphql"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Salesforce GraphQL Mutation Query Generation
|
|
2
11
|
|
|
3
12
|
**Triggering conditions**
|
|
4
13
|
|
|
5
|
-
1. Only if the schema exploration phase completed successfully
|
|
14
|
+
1. Only if the schema exploration phase completed successfully (invoke `salesforce-graphql-explore-schema` first)
|
|
6
15
|
2. Only if the query to generate is a mutation query
|
|
7
16
|
|
|
8
|
-
##
|
|
17
|
+
## Schema Access Policy
|
|
9
18
|
|
|
10
|
-
|
|
19
|
+
> ⚠️ **GREP ONLY** — During mutation generation you may need to verify field names, input types, or representations. All schema lookups **MUST** use the grep-only commands defined in the `salesforce-graphql-explore-schema` skill. Do NOT open, read, stream, or parse `./schema.graphql` with any tool other than grep.
|
|
11
20
|
|
|
12
|
-
|
|
21
|
+
## Your Role
|
|
13
22
|
|
|
14
|
-
If the schema exploration has not been executed yet, you **MUST** run
|
|
23
|
+
You are a GraphQL expert. Generate Salesforce-compatible mutation queries. Schema exploration must complete first. If the schema exploration has not been executed yet, you **MUST** run the full exploration workflow from the `salesforce-graphql-explore-schema` skill first, then return here for mutation query generation.
|
|
15
24
|
|
|
16
25
|
## Mutation Queries General Information
|
|
17
26
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
1. **Mutation Types**: The GraphQL engine supports `Create`, `Update` and `Delete` operations
|
|
21
|
-
2. **Id Based Mutations**: `Update` and `Delete` operations operate on Id-based entity identification
|
|
22
|
-
3. **Mutation Schema**: Defined in the [mutation query schema](#mutation-query-schema) section
|
|
27
|
+
The GraphQL engine supports `Create`, `Update`, and `Delete` operations. `Update` and `Delete` operate on Id-based entity identification. See the [mutation query schema](#mutation-query-schema) section.
|
|
23
28
|
|
|
24
29
|
## Mutation Query Generation Workflow
|
|
25
30
|
|
|
26
31
|
Strictly follow the rules below when generating the GraphQL mutation query:
|
|
27
32
|
|
|
28
|
-
1. **Input Fields Validation** - Validate that the set of fields validate [input field constraints](#mutation-queries-input-field-constraints)
|
|
33
|
+
1. **Input Fields Validation** - Validate that the set of fields validate [input field constraints](#mutation-queries-input-field-constraints). Verify every field name and type against grep output from the schema — do NOT guess or assume
|
|
29
34
|
2. **Output Fields Validation** - Validate that the set of fields used in the select part of the query validate the [output fields constraints](#mutation-queries-output-field-constraints)
|
|
30
|
-
3. **Type Consistency** - Make sure variables used as query arguments and their related fields share the same GraphQL type
|
|
35
|
+
3. **Type Consistency** - Make sure variables used as query arguments and their related fields share the same GraphQL type. Verify types via grep lookup — do NOT assume types
|
|
31
36
|
4. **Report Phase** - Use the [Mutation Query Report Template](#mutation-query-report-template) below to report on the previous validation phases
|
|
32
37
|
5. **Input Arguments** - `input` is the default name for the argument, unless otherwise specified
|
|
33
38
|
6. **Output Field** - For `Create` and `Update` operations, the output field is always named `Record`, and is of type EntityName
|
|
34
|
-
7. **
|
|
35
|
-
8. **
|
|
36
|
-
9. **
|
|
37
|
-
|
|
39
|
+
7. **Field Name Validation** - Every field name in the generated mutation **MUST** match a field confirmed via grep lookup in the schema. Do NOT guess or assume field names exist
|
|
40
|
+
8. **Query Generation** - Use the [mutation query](#mutation-query-templates) template and adjust it based on the selected operation
|
|
41
|
+
9. **Output Format** - Use the [standalone](#mutation-standalone-default-output-format---clean-code-only)
|
|
42
|
+
10. **Lint Validation** - After writing the mutation 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
|
|
43
|
+
11. **Test the Query** - Use the [Generated Mutation Query Testing](#generated-mutation-query-testing) workflow to test the generated query
|
|
44
|
+
1. **Report First** - Always output the generated mutation in the proper output format BEFORE initiating any test
|
|
38
45
|
|
|
39
46
|
## Mutation Query Schema
|
|
40
47
|
|
|
@@ -127,7 +134,7 @@ mutation mutateEntityName(
|
|
|
127
134
|
## Mutation Standalone (Default) Output Format - CLEAN CODE ONLY
|
|
128
135
|
|
|
129
136
|
```javascript
|
|
130
|
-
import { gql } from '
|
|
137
|
+
import { gql } from '@salesforce/sdk-data';
|
|
131
138
|
const QUERY_NAME = gql`
|
|
132
139
|
mutation mutateEntity($input: EntityNameOperationInput!) {
|
|
133
140
|
uiapi {
|
|
@@ -150,36 +157,54 @@ const QUERY_VARIABLES = {
|
|
|
150
157
|
};
|
|
151
158
|
```
|
|
152
159
|
|
|
153
|
-
**❌
|
|
160
|
+
**❌ FORBIDDEN — Do NOT include any of the following:**
|
|
154
161
|
|
|
155
|
-
- Explanatory comments about the query
|
|
156
|
-
- Field descriptions
|
|
162
|
+
- Explanatory comments about the query (inline or surrounding)
|
|
163
|
+
- Field descriptions or annotations
|
|
157
164
|
- Additional text about what the query does
|
|
158
|
-
- Workflow step descriptions
|
|
165
|
+
- Workflow step descriptions or summaries
|
|
166
|
+
- Comments like `// fetches...`, `// creates...`, `/* ... */`
|
|
159
167
|
|
|
160
|
-
**✅ ONLY
|
|
168
|
+
**✅ ONLY output:**
|
|
161
169
|
|
|
162
|
-
-
|
|
163
|
-
-
|
|
164
|
-
- Nothing else
|
|
170
|
+
- The raw query string constant (using `gql` tagged template)
|
|
171
|
+
- The variables object constant
|
|
172
|
+
- Nothing else — no extra imports, no exports, no wrapper functions
|
|
173
|
+
|
|
174
|
+
## Lint Validation
|
|
175
|
+
|
|
176
|
+
After writing the generated mutation into a source file, validate it against the schema using the project's GraphQL ESLint setup:
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
# Run from webapp dir (force-app/main/default/webapplications/<app-name>/)
|
|
180
|
+
npx eslint <path-to-file-containing-mutation>
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
**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`.
|
|
184
|
+
|
|
185
|
+
**Rules enforced:** `no-anonymous-operations`, `no-duplicate-fields`, `known-fragment-names`, `no-undefined-variables`, `no-unused-variables`
|
|
186
|
+
|
|
187
|
+
**On failure:** Fix the reported issues, re-run `npx eslint <file>` until clean, then proceed to testing.
|
|
188
|
+
|
|
189
|
+
> ⚠️ **Prerequisites**: The `schema.graphql` file must exist (invoke `salesforce-graphql-explore-schema` first) and project dependencies must be installed (`npm install`).
|
|
165
190
|
|
|
166
191
|
## Generated Mutation Query Testing
|
|
167
192
|
|
|
168
|
-
**Triggering conditions**
|
|
193
|
+
**Triggering conditions** — **ALL conditions must be true:**
|
|
169
194
|
|
|
170
|
-
1.
|
|
171
|
-
2.
|
|
172
|
-
3.
|
|
195
|
+
1. The [Mutation Query Generation Workflow](#mutation-query-generation-workflow) completed with status `SUCCESS` and you have a generated query
|
|
196
|
+
2. The query is a mutation query
|
|
197
|
+
3. A non-manual method was used during schema exploration to retrieve introspection data
|
|
173
198
|
|
|
174
199
|
**Workflow**
|
|
175
200
|
|
|
176
|
-
1. **Report Step** -
|
|
177
|
-
2. **Interactive Step** - Ask the user whether they want you to test the query
|
|
178
|
-
1. **WAIT** for the user's answer.
|
|
179
|
-
3. **Input Arguments** - You **MUST** ask the user for the input
|
|
180
|
-
1. **WAIT** for the user's answer.
|
|
181
|
-
4. **Test Query** -
|
|
182
|
-
1.
|
|
201
|
+
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
|
|
202
|
+
2. **Interactive Step** - Ask the user whether they want you to test the query using the proposed method
|
|
203
|
+
1. **STOP and WAIT** for the user's answer. Do NOT proceed until the user responds. Do NOT assume consent.
|
|
204
|
+
3. **Input Arguments** - You **MUST** ask the user for the input argument values to use in the test
|
|
205
|
+
1. **STOP and WAIT** for the user's answer. Do NOT proceed until the user provides values. Do NOT fabricate test data.
|
|
206
|
+
4. **Test Query** - Only if the user explicitly agrees and has provided input values:
|
|
207
|
+
1. Execute the mutation using the reported method (e.g., `sf api request rest` to POST the query and variables to the GraphQL endpoint):
|
|
183
208
|
```bash
|
|
184
209
|
sf api request rest /services/data/v65.0/graphql \
|
|
185
210
|
--method POST \
|
|
@@ -207,14 +232,14 @@ The query is invalid:
|
|
|
207
232
|
- **Navigation** - Error contains `is not currently available in mutation results`
|
|
208
233
|
- **API Version** - Query deals with updates, you're testing with Connect API and error contains `Cannot invoke JsonElement.isJsonObject()`
|
|
209
234
|
3. **Targeted Resolution** - Depending on the root cause categorization
|
|
210
|
-
- **Execution** - You're trying to update or delete an unknown/no longer available entity: either create an entity first, if you have generated the related query, or ask for a valid entity id to use
|
|
235
|
+
- **Execution** - You're trying to update or delete an unknown/no longer available entity: either create an entity first, if you have generated the related query, or ask for a valid entity id to use. **STOP and WAIT** for the user to provide a valid Id
|
|
211
236
|
- **Syntax** - Update the query using the error message information to fix the syntax errors
|
|
212
|
-
- **Validation** -
|
|
213
|
-
- **Type** - Use the error details and
|
|
237
|
+
- **Validation** - The field name is most probably invalid. Re-run the relevant grep command from the `salesforce-graphql-explore-schema` skill to verify the correct field name. If still unclear, ask the user for clarification and **STOP and WAIT** for their answer
|
|
238
|
+
- **Type** - Use the error details and re-verify the type via grep lookup in the schema. Correct the argument type and adjust variables accordingly
|
|
214
239
|
- **Navigation** - Use the [`PARTIAL` status handling workflow](#partial-status-handling-workflow) below
|
|
215
240
|
- **API Version** - `Record` selection is only available with API version 64 and higher, **report** the issue, and try again with API version 64
|
|
216
241
|
4. **Test Again** - Resume the [query testing workflow](#generated-mutation-query-testing) with the updated query (increment and track attempt counter)
|
|
217
|
-
5. **Escalation Path** - If targeted resolution fails after 2 attempts, ask for additional details and restart the entire GraphQL workflow
|
|
242
|
+
5. **Escalation Path** - If targeted resolution fails after 2 attempts, ask for additional details and restart the entire GraphQL workflow from the `salesforce-graphql-explore-schema` skill
|
|
218
243
|
|
|
219
244
|
### `PARTIAL` Status Handling Workflow
|
|
220
245
|
|
|
@@ -224,5 +249,10 @@ The query can be improved:
|
|
|
224
249
|
2. Explain that these fields can't be queried as part of a mutation query
|
|
225
250
|
3. Explain that the query might be considered as failing, as it will report errors
|
|
226
251
|
4. Offer to remove the offending fields
|
|
227
|
-
5. **WAIT** for the user's answer
|
|
252
|
+
5. **STOP and WAIT** for the user's answer. Do NOT remove fields without explicit consent.
|
|
228
253
|
6. If they are OK with removing the fields restart the [generation workflow](#mutation-query-generation-workflow) with the new field list
|
|
254
|
+
|
|
255
|
+
## Related Skills
|
|
256
|
+
|
|
257
|
+
- Schema exploration: `salesforce-graphql-explore-schema` (must complete first)
|
|
258
|
+
- Read query generation: `salesforce-graphql-read-query`
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: salesforce-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 salesforce-graphql-explore-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 `salesforce-graphql-explore-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 `salesforce-graphql-explore-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 `salesforce-graphql-explore-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 `salesforce-graphql-explore-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 `salesforce-graphql-explore-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 `salesforce-graphql-explore-schema` skill
|
|
249
|
+
|
|
250
|
+
## Related Skills
|
|
251
|
+
|
|
252
|
+
- Schema exploration: `salesforce-graphql-explore-schema` (must complete first)
|
|
253
|
+
- Mutation generation: `salesforce-graphql-mutation-query`
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: salesforce-rest-api-fetch
|
|
3
|
+
description: REST API usage via the Data SDK fetch method. Use when implementing Chatter, Connect REST, Apex REST, UI API REST, or Einstein LLM calls — only when GraphQL is not sufficient.
|
|
4
|
+
paths:
|
|
5
|
+
- "**/*.ts"
|
|
6
|
+
- "**/*.tsx"
|
|
7
|
+
- "**/*.graphql"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Salesforce REST API via Data SDK Fetch
|
|
11
|
+
|
|
12
|
+
Use `sdk.fetch` from the Data SDK when GraphQL is not sufficient. The SDK applies authentication, CSRF handling, and base URL resolution. **Always use optional chaining** (`sdk.fetch?.()`) and handle the case where `fetch` is not available.
|
|
13
|
+
|
|
14
|
+
Invoke this skill when you need to call Chatter, Connect REST, Apex REST, UI API REST, or Einstein LLM endpoints.
|
|
15
|
+
|
|
16
|
+
## API Version
|
|
17
|
+
|
|
18
|
+
Use the project's API version. It is typically injected as `__SF_API_VERSION__`; fallback to `"65.0"`:
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
declare const __SF_API_VERSION__: string;
|
|
22
|
+
const API_VERSION = typeof __SF_API_VERSION__ !== "undefined" ? __SF_API_VERSION__ : "65.0";
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Base Path
|
|
26
|
+
|
|
27
|
+
URLs are relative to the Salesforce API base. The SDK prepends the correct base path. Use paths starting with `/services/...`.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Chatter API
|
|
32
|
+
|
|
33
|
+
User and collaboration data. No GraphQL equivalent.
|
|
34
|
+
|
|
35
|
+
| Endpoint | Method | Purpose |
|
|
36
|
+
| -------- | ------ | ------- |
|
|
37
|
+
| `/services/data/v{version}/chatter/users/me` | GET | Current user (id, name, email, username) |
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
const sdk = await createDataSDK();
|
|
41
|
+
const response = await sdk.fetch?.(`/services/data/v${API_VERSION}/chatter/users/me`);
|
|
42
|
+
|
|
43
|
+
if (!response?.ok) throw new Error(`HTTP ${response?.status}`);
|
|
44
|
+
const data = await response.json();
|
|
45
|
+
return { id: data.id, name: data.name };
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Connect REST API
|
|
51
|
+
|
|
52
|
+
File and content operations.
|
|
53
|
+
|
|
54
|
+
| Endpoint | Method | Purpose |
|
|
55
|
+
| -------- | ------ | ------- |
|
|
56
|
+
| `/services/data/v{version}/connect/file/upload/config` | GET | Upload config (token, uploadUrl) for file uploads |
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
const sdk = await createDataSDK();
|
|
60
|
+
const configRes = await sdk.fetch?.(`/services/data/v${API_VERSION}/connect/file/upload/config`, {
|
|
61
|
+
method: "GET",
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
if (!configRes?.ok) throw new Error(`Failed to get upload config: ${configRes?.status}`);
|
|
65
|
+
const config = await configRes.json();
|
|
66
|
+
const { token, uploadUrl } = config;
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Apex REST
|
|
72
|
+
|
|
73
|
+
Custom Apex REST resources. Requires corresponding Apex classes in the org. CSRF protection is applied automatically for `services/apexrest` URLs.
|
|
74
|
+
|
|
75
|
+
| Endpoint | Method | Purpose |
|
|
76
|
+
| -------- | ------ | ------- |
|
|
77
|
+
| `/services/apexrest/auth/login` | POST | User login |
|
|
78
|
+
| `/services/apexrest/auth/register` | POST | User registration |
|
|
79
|
+
| `/services/apexrest/auth/forgot-password` | POST | Request password reset |
|
|
80
|
+
| `/services/apexrest/auth/reset-password` | POST | Reset password with token |
|
|
81
|
+
| `/services/apexrest/auth/change-password` | POST | Change password (authenticated) |
|
|
82
|
+
| `/services/apexrest/{resource}` | GET/POST | Custom Apex REST resources |
|
|
83
|
+
|
|
84
|
+
**Example (login):**
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
const sdk = await createDataSDK();
|
|
88
|
+
const response = await sdk.fetch?.("/services/apexrest/auth/login", {
|
|
89
|
+
method: "POST",
|
|
90
|
+
body: JSON.stringify({ email, password, startUrl: "/" }),
|
|
91
|
+
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
|
92
|
+
});
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Apex REST paths do not include the API version.
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## UI API (REST)
|
|
100
|
+
|
|
101
|
+
When GraphQL cannot cover the use case. **Prefer GraphQL** when possible.
|
|
102
|
+
|
|
103
|
+
| Endpoint | Method | Purpose |
|
|
104
|
+
| -------- | ------ | ------- |
|
|
105
|
+
| `/services/data/v{version}/ui-api/records/{recordId}` | GET | Fetch a single record |
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
const sdk = await createDataSDK();
|
|
109
|
+
const response = await sdk.fetch?.(`/services/data/v${API_VERSION}/ui-api/records/${recordId}`);
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Einstein LLM Gateway
|
|
115
|
+
|
|
116
|
+
AI features. Requires Einstein API setup.
|
|
117
|
+
|
|
118
|
+
| Endpoint | Method | Purpose |
|
|
119
|
+
| -------- | ------ | ------- |
|
|
120
|
+
| `/services/data/v{version}/einstein/llm/prompt/generations` | POST | Generate text from Einstein LLM |
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
const sdk = await createDataSDK();
|
|
124
|
+
const response = await sdk.fetch?.(`/services/data/v${API_VERSION}/einstein/llm/prompt/generations`, {
|
|
125
|
+
method: "POST",
|
|
126
|
+
headers: { "Content-Type": "application/json" },
|
|
127
|
+
body: JSON.stringify({
|
|
128
|
+
additionalConfig: { applicationName: "PromptTemplateGenerationsInvocable" },
|
|
129
|
+
promptTextorId: prompt,
|
|
130
|
+
}),
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
if (!response?.ok) throw new Error(`Einstein LLM failed (${response?.status})`);
|
|
134
|
+
const data = await response.json();
|
|
135
|
+
return data?.generations?.[0]?.text ?? "";
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## General Pattern
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
import { createDataSDK } from "@salesforce/sdk-data";
|
|
144
|
+
|
|
145
|
+
const sdk = await createDataSDK();
|
|
146
|
+
|
|
147
|
+
if (!sdk.fetch) {
|
|
148
|
+
throw new Error("Data SDK fetch is not available in this context");
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const response = await sdk.fetch(url, {
|
|
152
|
+
method: "GET", // or POST, PUT, PATCH, DELETE
|
|
153
|
+
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
|
154
|
+
body: method !== "GET" ? JSON.stringify(payload) : undefined,
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
158
|
+
const data = await response.json();
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Reference
|
|
164
|
+
|
|
165
|
+
- Parent: `salesforce-data-access` — enforces Data SDK usage for all Salesforce data fetches
|
|
166
|
+
- GraphQL: `salesforce-graphql` — use for record queries and mutations when possible
|
|
167
|
+
- `createRecord` from `@salesforce/webapp-experimental/api` for UI API record creation (uses SDK internally)
|
|
@@ -90,55 +90,7 @@ Use standard web APIs and npm packages only.
|
|
|
90
90
|
|
|
91
91
|
## Data Access (CRITICAL)
|
|
92
92
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
### GraphQL (Preferred)
|
|
96
|
-
|
|
97
|
-
For queries and mutations, follow the **`implementing-graphql-data-access`** skill in `feature-graphql`. It covers schema exploration, query patterns, codegen, type generation, and guardrails.
|
|
98
|
-
|
|
99
|
-
### UI API (Fallback)
|
|
100
|
-
|
|
101
|
-
When GraphQL cannot cover the use case, use `sdk.fetch!()` for UI API endpoints:
|
|
102
|
-
|
|
103
|
-
```typescript
|
|
104
|
-
const sdk = await getDataSDK();
|
|
105
|
-
const resp = await sdk.fetch!('/services/data/v62.0/ui-api/records/{recordId}');
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
### Apex REST is NOT Available
|
|
109
|
-
|
|
110
|
-
Apex REST cannot be called from React applications. If Apex seems required:
|
|
111
|
-
1. Evaluate if GraphQL can accomplish the task
|
|
112
|
-
2. If not, inform the user the feature is not supported in React
|
|
113
|
-
|
|
114
|
-
### MCP Tool Integration
|
|
115
|
-
|
|
116
|
-
Before implementing data access:
|
|
117
|
-
1. Check if `orchestrate_lds_data_requirements` tool is available
|
|
118
|
-
2. Use it to get guidance on the appropriate pattern (GraphQL or UI API)
|
|
119
|
-
3. If it recommends Apex REST, ignore and use GraphQL instead
|
|
120
|
-
|
|
121
|
-
## Einstein LLM Gateway (AI Features)
|
|
122
|
-
|
|
123
|
-
```typescript
|
|
124
|
-
import { getDataSDK } from '@salesforce/sdk-data';
|
|
125
|
-
|
|
126
|
-
async function callEinsteinGenerations(prompt: string): Promise<string> {
|
|
127
|
-
const sdk = await getDataSDK();
|
|
128
|
-
const resp = await sdk.fetch!('/services/data/v62.0/einstein/llm/prompt/generations', {
|
|
129
|
-
method: 'POST',
|
|
130
|
-
headers: { 'Content-Type': 'application/json' },
|
|
131
|
-
body: JSON.stringify({
|
|
132
|
-
additionalConfig: { applicationName: 'PromptTemplateGenerationsInvocable' },
|
|
133
|
-
promptTextorId: prompt,
|
|
134
|
-
}),
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
if (!resp.ok) throw new Error(`Einstein LLM failed (${resp.status})`);
|
|
138
|
-
const data = await resp.json();
|
|
139
|
-
return data?.generations?.[0]?.text || '';
|
|
140
|
-
}
|
|
141
|
-
```
|
|
93
|
+
For all Salesforce data access (GraphQL, REST, Chatter, Connect, Apex REST, UI API, Einstein LLM), invoke the **`salesforce-data-access`** skill (`.a4drules/skills/salesforce-data-access/`). It enforces Data SDK usage, GraphQL-first preference, optional chaining, and documents when to use `sdk.fetch` via the `salesforce-rest-api-fetch` skill.
|
|
142
94
|
|
|
143
95
|
## Error Handling
|
|
144
96
|
|