@salesforce/afv-skills 1.5.1 → 1.5.2

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.
Files changed (68) hide show
  1. package/README.md +16 -415
  2. package/package.json +5 -3
  3. package/skills/building-ui-bundle-app/SKILL.md +325 -0
  4. package/skills/building-ui-bundle-frontend/SKILL.md +122 -0
  5. package/skills/{building-webapp-react-components → building-ui-bundle-frontend}/implementation/component.md +1 -1
  6. package/skills/creating-b2b-commerce-store/SKILL.md +169 -0
  7. package/skills/creating-b2b-commerce-store/references/store-vs-storefront.md +169 -0
  8. package/skills/deploying-ui-bundle/SKILL.md +77 -0
  9. package/skills/generating-apex/CREDITS.md +30 -0
  10. package/skills/generating-apex/SKILL.md +335 -189
  11. package/skills/generating-apex/assets/abstract.cls +12 -8
  12. package/skills/generating-apex/assets/batch.cls +7 -7
  13. package/skills/generating-apex/assets/domain.cls +5 -5
  14. package/skills/generating-apex/assets/dto.cls +11 -11
  15. package/skills/generating-apex/assets/exception.cls +1 -1
  16. package/skills/generating-apex/assets/interface.cls +2 -2
  17. package/skills/generating-apex/assets/invocable.cls +115 -0
  18. package/skills/generating-apex/assets/queueable.cls +6 -6
  19. package/skills/generating-apex/assets/rest-resource.cls +300 -0
  20. package/skills/generating-apex/assets/schedulable.cls +7 -7
  21. package/skills/generating-apex/assets/selector.cls +7 -7
  22. package/skills/generating-apex/assets/service.cls +4 -4
  23. package/skills/generating-apex/assets/trigger.cls +45 -0
  24. package/skills/generating-apex/assets/utility.cls +5 -5
  25. package/skills/generating-apex/references/AccountDeduplicationBatch.cls +7 -7
  26. package/skills/generating-apex/references/AccountSelector.cls +10 -10
  27. package/skills/generating-apex/references/AccountService.cls +9 -9
  28. package/skills/generating-apex-test/CREDITS.md +30 -0
  29. package/skills/generating-apex-test/SKILL.md +165 -74
  30. package/skills/generating-apex-test/assets/test-class-template.cls +23 -54
  31. package/skills/generating-apex-test/assets/test-data-factory-template.cls +0 -1
  32. package/skills/generating-apex-test/references/assertion-patterns.md +38 -95
  33. package/skills/generating-apex-test/references/async-testing.md +59 -142
  34. package/skills/generating-apex-test/references/mocking-patterns.md +77 -76
  35. package/skills/generating-apex-test/references/test-data-factory.md +29 -130
  36. package/skills/generating-experience-react-site/SKILL.md +9 -9
  37. package/skills/generating-experience-react-site/docs/configure-metadata-digital-experience.md +1 -1
  38. package/skills/generating-flexipage/SKILL.md +28 -12
  39. package/skills/generating-ui-bundle-features/SKILL.md +45 -0
  40. package/skills/generating-ui-bundle-metadata/SKILL.md +106 -0
  41. package/skills/{managing-webapp-agentforce-conversation-client → implementing-ui-bundle-agentforce-conversation-client}/SKILL.md +5 -5
  42. package/skills/{managing-webapp-agentforce-conversation-client → implementing-ui-bundle-agentforce-conversation-client}/references/constraints.md +2 -2
  43. package/skills/{managing-webapp-agentforce-conversation-client → implementing-ui-bundle-agentforce-conversation-client}/references/examples.md +1 -1
  44. package/skills/{implementing-webapp-file-upload → implementing-ui-bundle-file-upload}/SKILL.md +11 -11
  45. package/skills/searching-media/SKILL.md +1 -1
  46. package/skills/{using-webapp-salesforce-data → using-ui-bundle-salesforce-data}/SKILL.md +52 -25
  47. package/skills/using-ui-bundle-salesforce-data/references/mutation-query-generation.md +140 -0
  48. package/skills/using-ui-bundle-salesforce-data/references/query-testing.md +78 -0
  49. package/skills/using-ui-bundle-salesforce-data/references/read-query-generation.md +307 -0
  50. package/skills/using-ui-bundle-salesforce-data/references/schema-introspection.md +53 -0
  51. package/skills/using-ui-bundle-salesforce-data/references/ui-bundle-integration.md +221 -0
  52. package/skills/{using-webapp-salesforce-data → using-ui-bundle-salesforce-data/scripts}/graphql-search.sh +75 -23
  53. package/skills/building-webapp-data-visualization/SKILL.md +0 -72
  54. package/skills/building-webapp-data-visualization/implementation/bar-line-chart.md +0 -316
  55. package/skills/building-webapp-data-visualization/implementation/dashboard-layout.md +0 -189
  56. package/skills/building-webapp-data-visualization/implementation/donut-chart.md +0 -181
  57. package/skills/building-webapp-data-visualization/implementation/stat-card.md +0 -150
  58. package/skills/building-webapp-react-components/SKILL.md +0 -96
  59. package/skills/configuring-webapp-csp-trusted-sites/SKILL.md +0 -90
  60. package/skills/configuring-webapp-metadata/SKILL.md +0 -158
  61. package/skills/creating-webapp/SKILL.md +0 -138
  62. package/skills/deploying-webapp-to-salesforce/SKILL.md +0 -226
  63. package/skills/installing-webapp-features/SKILL.md +0 -210
  64. /package/skills/{building-webapp-react-components → building-ui-bundle-frontend}/implementation/header-footer.md +0 -0
  65. /package/skills/{building-webapp-react-components → building-ui-bundle-frontend}/implementation/page.md +0 -0
  66. /package/skills/{configuring-webapp-csp-trusted-sites/implementation/metadata-format.md → generating-ui-bundle-metadata/implementation/csp-metadata-format.md} +0 -0
  67. /package/skills/{managing-webapp-agentforce-conversation-client → implementing-ui-bundle-agentforce-conversation-client}/references/style-tokens.md +0 -0
  68. /package/skills/{managing-webapp-agentforce-conversation-client → implementing-ui-bundle-agentforce-conversation-client}/references/troubleshooting.md +0 -0
@@ -0,0 +1,78 @@
1
+ # Query Testing
2
+
3
+ ## Testing Method
4
+
5
+ Use `sf api request rest` to POST the query to the GraphQL endpoint. Run from the **SFDX project root** (where `sfdx-project.json` lives).
6
+
7
+ ```bash
8
+ sf api request rest /services/data/v66.0/graphql \
9
+ --method POST \
10
+ --body '{"query":"query GetData { uiapi { query { EntityName { edges { node { Id } } } } } }"}'
11
+ ```
12
+
13
+ - Use the API version of the target org (v66.0+ for mutation support, v65.0+ for `@optional`)
14
+ - Replace the `query` value with the generated query string
15
+ - If the query uses variables, include them in the JSON body as a `variables` key
16
+
17
+ ## Critical: HTTP 200 Does Not Mean Success
18
+
19
+ Salesforce returns HTTP 200 even when the GraphQL operation has errors (e.g., invalid fields, permission failures, invalid IDs). **Always parse the `errors` array in the response body regardless of HTTP status code.** Do not treat HTTP 200 as confirmation that the query succeeded.
20
+
21
+ ## Testing Workflow
22
+
23
+ This workflow applies to both read and mutation queries:
24
+
25
+ 1. **Report method** — State the exact method: `sf api request rest` POST to `/services/data/vXX.0/graphql` from the project root
26
+ 2. **Ask user** — Ask the user whether they want to test the query. For mutations, also ask for input argument values — mutations modify real data, so explicit consent is essential. Wait for the user's answer before proceeding. Do not fabricate test data.
27
+ 3. **Execute test** — Only if the user explicitly agrees. Run `sf api request rest` with the query, variables, and correct API version
28
+ 4. **Report result** — Classify the result using the status definitions below. Always check the `errors` array in the response, even on HTTP 200.
29
+
30
+ ## Result Status Definitions
31
+
32
+ | Status | Condition | Meaning |
33
+ | --------- | ----------------------------------------------- | --------------------------------------------- |
34
+ | `SUCCESS` | `errors` is absent or empty | Query is valid (even if no data is returned) |
35
+ | `FAILED` | `data` is empty or null | Query is invalid |
36
+ | `PARTIAL` | `data` is present **and** `errors` is not empty | Some fields are inaccessible (mutations only) |
37
+
38
+ ## FAILED Status Handling
39
+
40
+ The query is invalid. Follow this sequence:
41
+
42
+ ### 1. Error Analysis
43
+
44
+ Parse the `errors` array and check `errors[].extensions.ErrorType` for Salesforce-specific error classification. Categorize into:
45
+
46
+ | Category | ErrorType / Message Contains | Resolution |
47
+ | --------------- | ---------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
48
+ | **Syntax** | `InvalidSyntax` | Fix syntax errors using the error message details |
49
+ | **Validation** | `ValidationError` | Field name is likely invalid — re-run the schema search script, ask user if still unclear |
50
+ | **Type** | `VariableTypeMismatch` or `UnknownType` | Use error details and schema to correct the argument type; adjust variables |
51
+ | **Execution** | `DataFetchingException`, `invalid cross reference id` | Entity is unknown/deleted — create entity first if possible, or ask for a valid Id |
52
+ | **Navigation** | `is not currently available in mutation results` | Field cannot be in mutation output — apply PARTIAL status handling |
53
+ | **Unsupported** | `OperationNotSupported` | The operation is not supported — check object availability and API version |
54
+ | **API Version** | `Cannot invoke JsonElement.isJsonObject()` (on update mutations) | `Record` selection requires API version 64+ — report and retry with version 64 |
55
+
56
+ ### 2. Targeted Resolution
57
+
58
+ Apply the resolution from the table above based on the error category. Update the query accordingly.
59
+
60
+ ### 3. Test Again
61
+
62
+ Re-run the testing workflow with the updated query. Increment and track the attempt counter.
63
+
64
+ ## PARTIAL Status Handling
65
+
66
+ The query executed but some fields are inaccessible (mutations only):
67
+
68
+ 1. Report the fields listed in the `errors` attribute
69
+ 2. Explain that these fields cannot be queried as part of a mutation
70
+ 3. Explain that the query will report errors if these fields remain
71
+ 4. Offer to remove the offending fields
72
+ 5. **STOP and WAIT** for the user's answer. Do NOT remove fields without explicit consent.
73
+ 6. If the user agrees, restart the mutation generation workflow with the updated field list
74
+
75
+ ## Retry and Escalation
76
+
77
+ - **Maximum 2 test attempts** per generated query
78
+ - If targeted resolution fails after 2 attempts, ask the user for additional details and **restart the entire workflow from Step 1 (Acquire Schema)** to re-validate entity and field information
@@ -0,0 +1,307 @@
1
+ # Read Query Generation
2
+
3
+ ## Generation Rules
4
+
5
+ 1. **No proliferation** — Only generate for explicitly requested fields, nothing else. Do NOT add fields the user did not ask for.
6
+ 2. **Unique query** — Leverage child relationships to query entities in one single query
7
+ 3. **Navigate entities** — Always use `relationshipName` to access reference fields and child entities. Exception: if `relationshipName` is null, return the `Id` itself
8
+ 4. **Leverage fragments** — Generate one fragment per possible type on polymorphic fields (fields with `dataType="REFERENCE"` and more than one entry in `referenceToInfos`)
9
+ 5. **Type consistency** — Variables used as query arguments and their related fields must share the same GraphQL type. Verify types against the schema search script output — do not assume types
10
+ 6. **Type enforcement** — Use field type information from introspection and the GraphQL schema to generate correct field access
11
+ 7. **Field name validation** — Every field name in the generated query **MUST** match a field confirmed via the schema search script. Do NOT guess or assume field names exist
12
+ 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
13
+ 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
14
+ 10. **Semi and anti joins** — Use the semi-join or anti-join templates to filter an entity with conditions on child entities
15
+ 11. **Explicit pagination** — Always include `first:` in every query to control page size (see [Pagination](#pagination)). Default is 10 if omitted.
16
+ 12. **Respect execution limits** — Stay within SOQL-derived limits: max 10 subqueries per request, max 5 child-to-parent relationship levels, max 1 parent-to-child level (no grandchildren), max 55 child-to-parent relationships, max 20 parent-to-child relationships per query
17
+ 13. **Compound fields** — When filtering, ordering, or aggregating, use constituent fields (e.g., `BillingCity`, `BillingCountry`) not the compound wrapper (`BillingAddress`). The compound wrapper is only for selection.
18
+ 14. **`_Record` suffix awareness** — Objects added to UI API in v60+ may use a `_Record` suffix for their type name (e.g., `FeedItem_Record` instead of `FeedItem`). Always verify type names via schema lookup — do not assume type name equals sObject API name.
19
+ 15. **Query generation** — Use the read query template below
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. Available in API v65.0+.
24
+
25
+ Apply `@optional` to:
26
+ - Scalar fields and value-type fields (e.g. `Name { value }`)
27
+ - Parent relationships
28
+ - Child relationships
29
+
30
+ **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.
31
+
32
+ ```ts
33
+ // Defend against missing fields
34
+ const name = node.Name?.value ?? '';
35
+ const relatedName = node.RelationshipName?.Name?.value ?? 'N/A';
36
+
37
+ // Unsafe — will throw if field omitted due to FLS
38
+ const name = node.Name.value;
39
+ ```
40
+
41
+ ## Pagination
42
+
43
+ Salesforce GraphQL uses Relay Cursor Connections with **forward-only pagination**. There is no backward pagination (`last`/`before` are not supported).
44
+
45
+ ### Core Rules
46
+
47
+ - **Always specify `first:`** — If omitted, the server defaults to 10 records. Be explicit.
48
+ - **Forward-only** — Use `first` and `after` only. Do **not** use `last` or `before` — they are unsupported and will fail.
49
+ - **Maximum without upperBound** — Standard pagination allows up to 4,000 total records across pages.
50
+ - **Use `pageInfo`** — Select `pageInfo { hasNextPage endCursor }` for any query that may need pagination.
51
+
52
+ ### UpperBound Pagination (v59+)
53
+
54
+ When you need more than 200 records per page or more than 4,000 total records, switch to upperBound mode:
55
+
56
+ - **`first` must be 200–2000** when `upperBound` is set. Values below 200 are invalid.
57
+ - **`upperBound`** declares the estimated total record count and enables extended pagination.
58
+
59
+ ```graphql
60
+ # Standard pagination
61
+ Account(first: 50, after: $cursor) {
62
+ edges { node { Id Name @optional { value } } }
63
+ pageInfo { hasNextPage endCursor }
64
+ }
65
+
66
+ # UpperBound pagination for large result sets
67
+ Account(first: 2000, after: $cursor, upperBound: 10000) {
68
+ edges { node { Id Name @optional { value } } }
69
+ pageInfo { hasNextPage endCursor }
70
+ }
71
+ ```
72
+
73
+ ## Ordering
74
+
75
+ Use the `orderBy:` argument with generated `<Object>_OrderBy` input types. Run the schema search script to verify sortable fields.
76
+
77
+ ### Rules
78
+
79
+ - Use `orderBy:` with the generated OrderBy type: `orderBy: { FieldName: { order: ASC } }`
80
+ - **Multi-column sorting** is supported by combining fields in the orderBy input
81
+ - **Unsupported field types** for ordering: multi-select picklist, rich text, long text area, encrypted fields. Do not order by these.
82
+ - **Locale sensitivity** — Sort order depends on user locale. For deterministic ordering, add `Id` as a tie-breaker field.
83
+ - **Compound fields** — Use constituent fields for ordering (e.g., `BillingCity`), not the compound wrapper.
84
+
85
+ ```graphql
86
+ Account(
87
+ first: 10,
88
+ orderBy: { Name: { order: ASC }, CreatedDate: { order: DESC } }
89
+ ) { ... }
90
+ ```
91
+
92
+ ## Filtering
93
+
94
+ ### Boolean Filter Composition
95
+
96
+ Filter types include `AND`, `OR`, and `NOT` fields for combining conditions. Multiple filter fields at the same level combine with implicit AND.
97
+
98
+ ```graphql
99
+ # Implicit AND — both conditions must match
100
+ Account(where: { Industry: { eq: "Technology" }, AnnualRevenue: { gt: 1000000 } })
101
+
102
+ # Explicit OR
103
+ Account(where: { OR: [
104
+ { Industry: { eq: "Technology" } },
105
+ { Industry: { eq: "Finance" } }
106
+ ] })
107
+
108
+ # NOT
109
+ Account(where: { NOT: { Industry: { eq: "Technology" } } })
110
+ ```
111
+
112
+ ### Date and DateTime Filtering
113
+
114
+ Date and DateTime fields use special input objects (`DateInput`/`DateTimeInput`) that support both literal values and SOQL-style relative date semantics.
115
+
116
+ ```graphql
117
+ # Literal date
118
+ Opportunity(where: { CloseDate: { eq: { value: "2024-12-31" } } })
119
+
120
+ # Relative date literal
121
+ Opportunity(where: { CloseDate: { gte: { literal: TODAY } } })
122
+ ```
123
+
124
+ Verify exact literal enum values (e.g., `TODAY`, `THIS_MONTH`) via the schema search script.
125
+
126
+ ### String Equality Is Case-Insensitive
127
+
128
+ String comparisons with `eq` are case-insensitive in Salesforce GraphQL. Do not rely on case sensitivity for string equality filters.
129
+
130
+ ### Relationship Filters
131
+
132
+ Filter through parent relationships using nested filter objects (not dot notation):
133
+
134
+ ```graphql
135
+ # Correct — nested filter objects
136
+ Contact(where: { Account: { Name: { like: "Acme%" } } })
137
+
138
+ # Wrong — dot notation is not supported
139
+ Contact(where: { "Account.Name": { like: "Acme%" } })
140
+ ```
141
+
142
+ ### Polymorphic Relationship Filters
143
+
144
+ Polymorphic relationships use union-aware filter input types named `<Object>_<RelationshipName>_Filters`. Filter by specific concrete types within the union:
145
+
146
+ ```graphql
147
+ # Filter by polymorphic Owner (which is a union of User, Group, etc.)
148
+ Account(where: { Owner: { User: { Username: { like: "admin%" } } } })
149
+ ```
150
+
151
+ Verify exact filter input type names and available concrete types via the schema search script.
152
+
153
+ ### ID Filtering
154
+
155
+ Salesforce accepts both 15-character and 18-character record IDs for `Id` filtering. Do not reject or "correct" either form.
156
+
157
+ ## Semi-Join and Anti-Join Templates
158
+
159
+ Semi-joins and anti-joins filter a parent entity using conditions on child entities. They use `inq` (semi-join) and `ninq` (anti-join) operators on the parent entity's `Id`.
160
+
161
+ The operator accepts:
162
+
163
+ - The child entity camelCase name with conditions
164
+ - The `ApiName` field containing the parent entity `Id` (`fieldName` from `childRelationships`)
165
+
166
+ If the only condition is child entity existence, use `Id: { ne: null }`.
167
+
168
+ ### Restrictions
169
+
170
+ Semi-join and anti-join queries have SOQL-derived restrictions:
171
+ - **Limited count** — There are limits on the number of `inq`/`ninq` operators per query
172
+ - **No `ne` with joins** — Cannot use `ne` operator in combination with join operators
173
+ - **No `or` in subquery** — The join subquery conditions cannot use `OR`
174
+ - **No `orderBy` in subquery** — Join subqueries do not support ordering
175
+ - **Nesting restrictions** — Semi/anti-joins cannot be nested within each other
176
+
177
+ ### Semi-Join Example
178
+
179
+ Filter `ParentEntity` to include only those with at least one matching `ChildEntity`:
180
+
181
+ ```graphql
182
+ query testSemiJoin {
183
+ uiapi {
184
+ query {
185
+ ParentEntity(
186
+ where: {
187
+ Id: {
188
+ inq: {
189
+ ChildEntity: {
190
+ Name: { like: "test%" }
191
+ Type: { eq: "some value" }
192
+ }
193
+ ApiName: "parentIdFieldInChild"
194
+ }
195
+ }
196
+ }
197
+ ) {
198
+ edges {
199
+ node {
200
+ Id
201
+ Name @optional {
202
+ value
203
+ }
204
+ }
205
+ }
206
+ }
207
+ }
208
+ }
209
+ }
210
+ ```
211
+
212
+ ### Anti-Join Example
213
+
214
+ Same as the semi-join example, but replace `inq` with `ninq` to filter `ParentEntity` with **no** matching `ChildEntity`.
215
+
216
+ ## Current User Exception
217
+
218
+ To retrieve **current user**, **connected user**, or **authenticated user** information, use `uiapi.currentUser` instead of the standard query pattern. This field takes **no arguments** and returns a `User` type.
219
+
220
+ ## Conditional Field Selection
221
+
222
+ For dynamic fieldsets with **known** fields, use `@include(if: $condition)` and `@skip(if: $condition)` directives in `.graphql` files. See GraphQL spec for details.
223
+
224
+ ## Read Query Template
225
+
226
+ ```graphql
227
+ query QueryName($after: String) {
228
+ uiapi {
229
+ query {
230
+ EntityName(
231
+ first: 10 # Always specify — default is 10 if omitted
232
+ after: $after # For pagination
233
+ where: { ... } # Filter conditions
234
+ orderBy: { ... } # Sort order
235
+ ) {
236
+ edges {
237
+ node {
238
+ # Direct fields — use @optional for FLS resilience
239
+ FieldName @optional { value }
240
+
241
+ # Non-polymorphic reference (single type)
242
+ RelationshipName @optional {
243
+ Id
244
+ Name { value }
245
+ }
246
+
247
+ # Polymorphic reference (multiple types)
248
+ PolymorphicRelationshipName @optional {
249
+ ...TypeAInfo
250
+ ...TypeBInfo
251
+ }
252
+
253
+ # Child relationship (subquery) — max 1 level deep, no grandchildren
254
+ RelationshipName @optional (
255
+ first: 10 # Always specify
256
+ ) {
257
+ edges {
258
+ node {
259
+ # fields
260
+ }
261
+ }
262
+ }
263
+ }
264
+ }
265
+ pageInfo {
266
+ hasNextPage
267
+ endCursor
268
+ }
269
+ }
270
+ }
271
+ }
272
+ }
273
+
274
+ fragment TypeAInfo on TypeA {
275
+ Id
276
+ SpecificFieldA @optional { value }
277
+ }
278
+
279
+ fragment TypeBInfo on TypeB {
280
+ Id
281
+ SpecificFieldB @optional { value }
282
+ }
283
+ ```
284
+
285
+ ## Field Value Wrappers
286
+
287
+ Schema fields use typed wrappers. Access the underlying value via `.value`:
288
+
289
+ | Wrapper Type | Underlying Type | Access Pattern |
290
+ | ----------------- | --------------- | --------------------- |
291
+ | `StringValue` | `String` | `field { value }` |
292
+ | `IntValue` | `Int` | `field { value }` |
293
+ | `CurrencyValue` | `Currency` | `field { value }` |
294
+ | `DateTimeValue` | `DateTime` | `field { value }` |
295
+ | `PicklistValue` | `Picklist` | `field { value }` |
296
+ | `BooleanValue` | `Boolean` | `field { value }` |
297
+ | `DoubleValue` | `Double` | `field { value }` |
298
+ | `PercentValue` | `Percent` | `field { value }` |
299
+ | `IDValue` | `ID` | `field { value }` |
300
+ | `EmailValue` | `Email` | `field { value }` |
301
+ | `PhoneNumberValue`| `PhoneNumber` | `field { value }` |
302
+ | `UrlValue` | `Url` | `field { value }` |
303
+ | `DateValue` | `Date` | `field { value }` |
304
+ | `LongValue` | `Long` | `field { value }` |
305
+ | `TextAreaValue` | `TextArea` | `field { value }` |
306
+
307
+ All wrappers also expose `displayValue: String` for formatted display. `displayValue` is server-rendered using SOQL `toLabel()` or `format()` depending on field type — use it for UI display instead of formatting values client-side.
@@ -0,0 +1,53 @@
1
+ # Schema Introspection
2
+
3
+ ## Schema Access Policy
4
+
5
+ The `schema.graphql` file is **265,000+ lines**. Loading it into context or opening it in an editor will overwhelm the context window or crash tools.
6
+
7
+ Do not use cat, less, more, head, tail, editors (VS Code, vim, nano), or programmatic parsers (node, python, awk, sed, jq) on `schema.graphql`. Use the schema search script or targeted grep calls only.
8
+
9
+ ## Schema Lookup
10
+
11
+ Run the search script from the **SFDX project root** to get all relevant schema info in one step:
12
+
13
+ ```bash
14
+ bash scripts/graphql-search.sh <EntityName>
15
+ # Multiple entities:
16
+ bash scripts/graphql-search.sh Account Contact Opportunity
17
+ ```
18
+
19
+ **Maximum 2 script runs.** If the entity still can't be found after checking naming variations, ask the user.
20
+
21
+ ## Entity Identification
22
+
23
+ Map user intent to PascalCase entity names:
24
+
25
+ 1. Convert natural language to PascalCase (e.g., "accounts" → `Account`, "case comments" → `CaseComment`, "custom objects" → `CustomObject__c`)
26
+ 2. Run the schema search script to validate the entity exists
27
+ 3. If a candidate does not match, try:
28
+ - `__c` suffix for custom objects, `__e` for platform events
29
+ - **`_Record` suffix** — Objects added to UI API in API v60+ may use `<EntityName>_Record` as their type name (e.g., `FeedItem_Record` instead of `FeedItem`)
30
+ 4. If an entity cannot be resolved, **ask the user** for the correct name — do not guess
31
+
32
+ ## Iterative Introspection
33
+
34
+ Use a maximum of **3 introspection cycles** to resolve all entities and their dependencies:
35
+
36
+ 1. **Introspect** — Run the schema search script for each unresolved entity
37
+ 2. **Fields** — Extract requested field names and types from the type definition output
38
+ 3. **References** — Identify reference fields. If a reference resolves to multiple types, mark it as **polymorphic** (use inline fragments in the generated query). Add newly discovered entity types to the working list.
39
+ 4. **Child relationships** — Identify Connection types (e.g., `Contacts: ContactConnection`). Add child entity types to the working list.
40
+ 5. **Next cycle** — If unresolved entities remain and the cycle limit hasn't been reached, repeat from step 1
41
+
42
+ ### Hard Stop Rules
43
+
44
+ - If no introspection data is returned for an entity, **stop** — the entity may not be deployed
45
+ - If unknown entities remain after 3 cycles, **stop** — ask the user for clarification
46
+ - Do not proceed with query generation until all entities and requested fields are confirmed in the schema
47
+
48
+ ## Deployment Prerequisites
49
+
50
+ The schema reflects the **current org state**. Custom objects and fields appear only after metadata is deployed.
51
+
52
+ - **Before** running `npm run graphql:schema`: Deploy all metadata and assign permission sets. Invoke the `deploying-ui-bundle` skill for the full sequence.
53
+ - **After** any metadata deployment: Re-run `npm run graphql:schema` and `npm run graphql:codegen` so types and queries stay in sync.
@@ -0,0 +1,221 @@
1
+ # UI Bundle Integration
2
+
3
+ ## When to Use
4
+
5
+ This guide applies when integrating GraphQL queries into a React UI bundle using `createDataSDK` + codegen from `@salesforce/sdk-data`.
6
+
7
+ ## Core Types & Function Signatures
8
+
9
+ ### createDataSDK and graphql
10
+
11
+ ```typescript
12
+ import { createDataSDK } from "@salesforce/sdk-data";
13
+
14
+ const sdk = await createDataSDK();
15
+ const response = await sdk.graphql?.<ResponseType, VariablesType>(query, variables);
16
+ ```
17
+
18
+ `createDataSDK()` returns a `DataSDK` instance. The `graphql` method uses optional chaining (`?.`) because not all surfaces support GraphQL.
19
+
20
+ ### gql Template Tag
21
+
22
+ ```typescript
23
+ import { gql } from "@salesforce/sdk-data";
24
+
25
+ const MY_QUERY = gql`
26
+ query MyQuery {
27
+ uiapi { ... }
28
+ }
29
+ `;
30
+ ```
31
+
32
+ The `gql` tag enables ESLint validation against the schema. Plain template strings bypass validation.
33
+
34
+ ### NodeOfConnection
35
+
36
+ ```typescript
37
+ import { type NodeOfConnection } from "@salesforce/sdk-data";
38
+
39
+ type AccountNode = NodeOfConnection<GetHighRevenueAccountsQuery["uiapi"]["query"]["Account"]>;
40
+ ```
41
+
42
+ Use `NodeOfConnection` to extract the node type from a Connection type for cleaner typing.
43
+
44
+ ## Query Patterns
45
+
46
+ Choose the pattern based on query complexity:
47
+
48
+ - **Pattern 1 — External `.graphql` file**: Recommended for complex queries with variables, fragments, or shared across files. Full codegen support, syntax highlighting, shareable. Requires codegen step after changes. Does NOT support dynamic queries.
49
+ - **Pattern 2 — Inline `gql` tag**: Recommended for simple queries. Supports dynamic queries (field set varies at runtime). **MUST use `gql` tag** — plain template strings bypass `@graphql-eslint` validation.
50
+
51
+ ## Pattern 1: External .graphql File
52
+
53
+ Create a `.graphql` file, run `npm run graphql:codegen`, import with `?raw` suffix, and use generated types.
54
+
55
+ **Required imports:**
56
+
57
+ ```typescript
58
+ import { createDataSDK, type NodeOfConnection } from "@salesforce/sdk-data";
59
+ import MY_QUERY from "./query/myQuery.graphql?raw"; // ?raw suffix required
60
+ import type { GetMyDataQuery, GetMyDataQueryVariables } from "../graphql-operations-types";
61
+ ```
62
+
63
+ **Example usage:**
64
+
65
+ ```typescript
66
+ const sdk = await createDataSDK();
67
+ const response = await sdk.graphql?.<GetMyDataQuery, GetMyDataQueryVariables>(
68
+ MY_QUERY,
69
+ variables
70
+ );
71
+
72
+ if (response?.errors?.length) {
73
+ throw new Error(response.errors.map((e) => e.message).join("; "));
74
+ }
75
+
76
+ const nodes = response?.data?.uiapi?.query?.EntityName?.edges?.map((e) => e.node) ?? [];
77
+ ```
78
+
79
+ ## Pattern 2: Inline gql Tag
80
+
81
+ **Required imports:**
82
+
83
+ ```typescript
84
+ import { createDataSDK, gql } from "@salesforce/sdk-data";
85
+ import { type CurrentUserQuery } from "../graphql-operations-types";
86
+
87
+ const MY_QUERY = gql`
88
+ query CurrentUser {
89
+ uiapi { ... }
90
+ }
91
+ `;
92
+ ```
93
+
94
+ > **MUST use `gql` tag** — plain template strings bypass the `@graphql-eslint` processor entirely, meaning no lint validation against the schema.
95
+
96
+ ## Error Handling Strategies
97
+
98
+ **Strategy A — Strict (default):** Treat any errors as failure.
99
+
100
+ ```typescript
101
+ if (response?.errors?.length) {
102
+ throw new Error(response.errors.map((e) => e.message).join("; "));
103
+ }
104
+ const result = response?.data;
105
+ ```
106
+
107
+ **Strategy B — Tolerant:** Log errors but use available data.
108
+
109
+ ```typescript
110
+ if (response?.errors?.length) {
111
+ console.warn("GraphQL partial errors:", response.errors);
112
+ }
113
+ const result = response?.data;
114
+ ```
115
+
116
+ **Strategy C — Discriminated:** Fail only when no data is returned. Useful for mutations where some return fields may be inaccessible.
117
+
118
+ ```typescript
119
+ if (!response?.data && response?.errors?.length) {
120
+ throw new Error(response.errors.map((e) => e.message).join("; "));
121
+ }
122
+ const result = response?.data;
123
+ ```
124
+
125
+ Responses follow `uiapi.query.ObjectName.edges[].node`; fields use `{ value }`.
126
+
127
+ ## Conditional Field Selection
128
+
129
+ For dynamic fieldsets with **known** fields, use `@include(if: $condition)` and `@skip(if: $condition)` directives in `.graphql` files. See GraphQL spec for details.
130
+
131
+ ## ESLint Validation
132
+
133
+ After writing the query into a source file, validate it against the schema:
134
+
135
+ ```bash
136
+ # Run from UI bundle dir (force-app/main/default/uiBundles/<app-name>/)
137
+ npx eslint <path-to-file-containing-query>
138
+ ```
139
+
140
+ **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`.
141
+
142
+ **Rules enforced:** `no-anonymous-operations`, `no-duplicate-fields`, `known-fragment-names`, `no-undefined-variables`, `no-unused-variables`
143
+
144
+ **On failure:** Fix the reported issues, re-run `npx eslint <file>` until clean, then proceed to testing.
145
+
146
+ > **Prerequisites**: The `schema.graphql` file must exist and project dependencies must be installed (`npm install`).
147
+
148
+ ## Codegen
149
+
150
+ Generate TypeScript types from `.graphql` files and inline `gql` queries:
151
+
152
+ ```bash
153
+ # Run from UI bundle dir (force-app/main/default/uiBundles/<app-name>/)
154
+ npm run graphql:codegen
155
+ ```
156
+
157
+ Output: `src/api/graphql-operations-types.ts`
158
+
159
+ Naming conventions:
160
+ - `<OperationName>Query` / `<OperationName>Mutation` — response types
161
+ - `<OperationName>QueryVariables` / `<OperationName>MutationVariables` — variable types
162
+
163
+ ## Anti-Patterns
164
+
165
+ ### Direct API Calls
166
+
167
+ ```typescript
168
+ // NOT RECOMMENDED: Direct axios/fetch calls for GraphQL
169
+ // PREFERRED: Use the Data SDK
170
+ const sdk = await createDataSDK();
171
+ const response = await sdk.graphql?.<ResponseType>(query, variables);
172
+ ```
173
+
174
+ ### Missing Type Definitions
175
+
176
+ ```typescript
177
+ // NOT RECOMMENDED: Untyped GraphQL calls
178
+ // PREFERRED: Provide response type
179
+ const response = await sdk.graphql?.<GetMyDataQuery>(query);
180
+ ```
181
+
182
+ ### Plain String Queries (Without gql Tag)
183
+
184
+ ```typescript
185
+ // NOT RECOMMENDED: Plain strings bypass ESLint validation
186
+ const query = `query { ... }`;
187
+
188
+ // PREFERRED: Use gql tag for inline queries
189
+ const QUERY = gql`query { ... }`;
190
+ ```
191
+
192
+ ## Quality Checklists
193
+
194
+ ### For Pattern 1 (.graphql files):
195
+
196
+ 1. [ ] All field names verified via schema search script
197
+ 2. [ ] Create `.graphql` file for the query/mutation
198
+ 3. [ ] Run `npm run graphql:codegen` to generate types
199
+ 4. [ ] Import query with `?raw` suffix
200
+ 5. [ ] Import generated types from `graphql-operations-types.ts`
201
+ 6. [ ] Use `sdk.graphql?.<ResponseType>()` with proper generic
202
+ 7. [ ] Handle `response.errors` and destructure `response.data`
203
+ 8. [ ] Use `NodeOfConnection` for cleaner node types when needed
204
+ 9. [ ] Run `npx eslint <file>` from UI bundle dir — fix all GraphQL errors
205
+
206
+ ### For Pattern 2 (inline with gql):
207
+
208
+ 1. [ ] All field names verified via schema search script
209
+ 2. [ ] Define query using `gql` template tag (NOT a plain string)
210
+ 3. [ ] Ensure query name matches generated types in `graphql-operations-types.ts`
211
+ 4. [ ] Import generated types for the query
212
+ 5. [ ] Use `sdk.graphql?.<ResponseType>()` with proper generic
213
+ 6. [ ] Handle `response.errors` and destructure `response.data`
214
+ 7. [ ] Run `npx eslint <file>` from UI bundle dir — fix all GraphQL errors
215
+
216
+ ### General:
217
+
218
+ - [ ] Lint validation passes (`npx eslint <file>` reports no GraphQL errors)
219
+ - [ ] Query field names match the schema exactly (case-sensitive)
220
+ - [ ] Response type generic is provided to `sdk.graphql?.<T>()`
221
+ - [ ] Optional chaining is used for nested response data