@salesforce/webapp-template-feature-graphql-experimental 1.56.0 → 1.56.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/README.md CHANGED
@@ -15,7 +15,7 @@ The base React app (`@salesforce/webapp-template-base-react-app-experimental`) i
15
15
  - `@salesforce/sdk-data` dependency
16
16
  - GraphQL ESLint plugin and rules
17
17
  - GraphQL codegen dependencies and configuration (`codegen.yml`)
18
- - `executeGraphQL<T>()` utility and `gql` template tag
18
+ - `getDataSDK()` + `data.graphql?.()` pattern and `gql` template tag
19
19
  - Example components: `AccountsTable`, `AccountsPage`
20
20
  - Type-safe GraphQL patterns with codegen
21
21
  - `npm run graphql:schema` — download schema from a connected Salesforce org
@@ -54,7 +54,7 @@ The skill references the base app's `npm run graphql:codegen` and `npm run graph
54
54
 
55
55
  This package includes **rules** that define conventions for GraphQL data access:
56
56
 
57
- - Use `executeGraphQL<T>()` (never raw fetch/axios)
57
+ - Use `getDataSDK()` + `data.graphql?.()` (never raw fetch/axios)
58
58
  - Always provide response type generics
59
59
  - Use `gql` tag or `.graphql` files (never plain strings)
60
60
  - Follow the two-pattern workflow (external file vs. inline)
@@ -74,7 +74,7 @@ cp rules/graphql-data-access-rule.md ~/.clinerules/
74
74
 
75
75
  The base React app includes reference examples:
76
76
 
77
- - `src/api/graphql.ts` — Core `executeGraphQL()` utility
77
+ - `src/api/graphql.ts` — Core `getDataSDK()` singleton and GraphQL utilities
78
78
  - `src/api/utils/accounts.ts` — Pattern 1 (external `.graphql` file + codegen types)
79
79
  - `src/api/utils/user.ts` — Pattern 2 (inline `gql` tag)
80
80
  - `src/components/AccountsTable.tsx` — Component using GraphQL to fetch and render Accounts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/webapp-template-feature-graphql-experimental",
3
- "version": "1.56.0",
3
+ "version": "1.56.1",
4
4
  "description": "GraphQL documentation, agent skills, and rules for Salesforce web applications",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "author": "",
@@ -16,5 +16,5 @@
16
16
  "scripts": {
17
17
  "clean": "echo 'No build artifacts to clean'"
18
18
  },
19
- "gitHead": "580bc4db5f0190f3b7345056a9f16454455f3652"
19
+ "gitHead": "c900d757a03250a770c5312dbef2013de91e7bb3"
20
20
  }
@@ -34,26 +34,25 @@ For a GraphQL operation named `GetHighRevenueAccounts`:
34
34
 
35
35
  ## Core Types & Function Signatures
36
36
 
37
- ### executeGraphQL Function
37
+ ### getDataSDK Function
38
38
 
39
- Located in `src/api/graphql.ts`:
39
+ Available from `@salesforce/webapp-experimental/api`:
40
40
 
41
41
  ```typescript
42
- function executeGraphQL<T, InputVariables = Record<string, unknown>>(
43
- query: string,
44
- variables?: InputVariables,
45
- ): Promise<T>;
42
+ import { getDataSDK } from "@salesforce/webapp-experimental/api";
43
+
44
+ const data = await getDataSDK();
45
+ const response = await data.graphql?.<ResponseType, VariablesType>(query, variables);
46
46
  ```
47
47
 
48
- - `T` - The response type (e.g., `GetHighRevenueAccountsQuery`)
49
- - `InputVariables` - The variables type (e.g., `GetHighRevenueAccountsQueryVariables`)
48
+ `getDataSDK()` returns a lazily-initialized `DataSDK` singleton. The `graphql` method uses optional chaining (`?.`) because not all surfaces support GraphQL.
50
49
 
51
50
  ### gql Template Tag
52
51
 
53
- Also exported from `graphql.ts` for inline query definitions:
52
+ Also available from `@salesforce/webapp-experimental/api` for inline query definitions:
54
53
 
55
54
  ```typescript
56
- import { gql } from "../api/graphql";
55
+ import { gql } from "@salesforce/webapp-experimental/api";
57
56
 
58
57
  const MY_QUERY = gql`
59
58
  query MyQuery {
@@ -68,7 +67,7 @@ The `gql` tag is a template literal that allows defining GraphQL queries inline
68
67
 
69
68
  ### GraphQLResponse Shape
70
69
 
71
- The raw response wrapper (handled internally by `executeGraphQL`):
70
+ `data.graphql?.()` returns `GraphQLResponse<T>`. Callers destructure `{ data, errors }` and handle errors themselves:
72
71
 
73
72
  ```typescript
74
73
  interface GraphQLResponse<T> {
@@ -81,12 +80,47 @@ interface GraphQLResponse<T> {
81
80
  }
82
81
  ```
83
82
 
83
+ ### Handling Mixed Responses (Partial Success)
84
+
85
+ GraphQL can return **both `data` and `errors`** in the same response (partial success). For example, some fields may resolve while others fail due to field-level security. Choose a strategy per use case:
86
+
87
+ ```typescript
88
+ // Strategy A: Strict — treat any errors as failure (default for most queries)
89
+ if (response?.errors?.length) {
90
+ throw new Error(response.errors.map((e) => e.message).join("; "));
91
+ }
92
+ const result = response?.data;
93
+
94
+ // Strategy B: Tolerant — log errors but use partial data
95
+ if (response?.errors?.length) {
96
+ console.warn("GraphQL partial errors:", response.errors);
97
+ }
98
+ const result = response?.data;
99
+
100
+ // Strategy C: Discriminated — fail only when no data came back
101
+ if (response?.errors?.length && !response?.data) {
102
+ throw new Error(response.errors.map((e) => e.message).join("; "));
103
+ }
104
+ if (response?.errors?.length) {
105
+ console.warn("Partial success with errors:", response.errors);
106
+ }
107
+ const result = response?.data;
108
+ ```
109
+
110
+ **When to use each:**
111
+
112
+ - **Strategy A** — Default. Use for queries where incomplete data would be misleading (e.g., financial summaries, approval workflows).
113
+ - **Strategy B** — Use when partial data is still useful and the UI can degrade gracefully (e.g., dashboard tiles, optional fields).
114
+ - **Strategy C** — Use for mutations where the operation may succeed but some return fields are inaccessible.
115
+
116
+ For mutation-specific partial responses, see `docs/generate-mutation-query.md` which covers `PARTIAL` and `FAILED` status handling workflows.
117
+
84
118
  ### NodeOfConnection Utility Type
85
119
 
86
120
  Extract the node type from a connection (edges/node pattern):
87
121
 
88
122
  ```typescript
89
- import { type NodeOfConnection } from "../api/graphql";
123
+ import { type NodeOfConnection } from "@salesforce/webapp-experimental/api";
90
124
 
91
125
  // Extract Account node type from the query response
92
126
  type AccountNode = NodeOfConnection<GetHighRevenueAccountsQuery["uiapi"]["query"]["Account"]>;
@@ -162,22 +196,31 @@ This generates types in `graphql-operations-types.ts`:
162
196
  #### Step 3: Import and Use
163
197
 
164
198
  ```typescript
165
- import { executeGraphQL, type NodeOfConnection } from "../api/graphql";
199
+ import { getDataSDK, type NodeOfConnection } from "@salesforce/webapp-experimental/api";
166
200
  import MY_QUERY from "./query/myQuery.graphql?raw";
167
201
  import type { GetMyDataQuery, GetMyDataQueryVariables } from "../graphql-operations-types";
168
202
 
169
- // Extract node type for cleaner return types
170
203
  type MyNode = NodeOfConnection<GetMyDataQuery["uiapi"]["query"]["MyObject"]>;
171
204
 
172
205
  export async function getMyData(variables: GetMyDataQueryVariables): Promise<MyNode[]> {
173
- const response = await executeGraphQL<GetMyDataQuery>(MY_QUERY, variables);
174
- return response.uiapi?.query?.MyObject?.edges?.map((edge) => edge?.node) || [];
206
+ const data = await getDataSDK();
207
+ const response = await data.graphql?.<GetMyDataQuery, GetMyDataQueryVariables>(
208
+ MY_QUERY,
209
+ variables,
210
+ );
211
+
212
+ if (response?.errors?.length) {
213
+ const errorMessages = response.errors.map((e) => e.message).join("; ");
214
+ throw new Error(`GraphQL Error: ${errorMessages}`);
215
+ }
216
+
217
+ return response?.data?.uiapi?.query?.MyObject?.edges?.map((edge) => edge?.node) || [];
175
218
  }
176
219
  ```
177
220
 
178
221
  **Key imports for Pattern 1:**
179
222
 
180
- - `executeGraphQL` - Execute the query
223
+ - `getDataSDK` - Get the DataSDK singleton
181
224
  - `NodeOfConnection` - Extract node types from connection responses
182
225
  - Query from `.graphql` file with `?raw` suffix
183
226
  - Generated types from `graphql-operations-types.ts`
@@ -206,7 +249,7 @@ export async function getMyData(variables: GetMyDataQueryVariables): Promise<MyN
206
249
  For simpler queries without variables or when colocation is preferred:
207
250
 
208
251
  ```typescript
209
- import { executeGraphQL, gql } from "../api/graphql";
252
+ import { getDataSDK, gql } from "@salesforce/webapp-experimental/api";
210
253
  import { type CurrentUserQuery } from "../graphql-operations-types";
211
254
 
212
255
  const CURRENT_USER_QUERY = gql`
@@ -229,9 +272,14 @@ interface User {
229
272
 
230
273
  export async function getCurrentUser(): Promise<User | null> {
231
274
  try {
232
- const response = await executeGraphQL<CurrentUserQuery>(CURRENT_USER_QUERY);
275
+ const data = await getDataSDK();
276
+ const response = await data.graphql?.<CurrentUserQuery>(CURRENT_USER_QUERY);
277
+
278
+ if (response?.errors?.length) {
279
+ throw new Error(response.errors.map((e) => e.message).join("; "));
280
+ }
233
281
 
234
- const userData = response.uiapi.currentUser;
282
+ const userData = response?.data?.uiapi.currentUser;
235
283
 
236
284
  if (!userData) {
237
285
  throw new Error("No user data found");
@@ -250,7 +298,7 @@ export async function getCurrentUser(): Promise<User | null> {
250
298
 
251
299
  **Key imports for Pattern 2:**
252
300
 
253
- - `executeGraphQL` - Execute the query
301
+ - `getDataSDK` - Get the DataSDK singleton
254
302
  - `gql` - Template tag for inline query definition
255
303
  - Generated types from `graphql-operations-types.ts`
256
304
 
@@ -321,18 +369,22 @@ fragment ContactFields on Account {
321
369
  ### Usage
322
370
 
323
371
  ```typescript
324
- import { executeGraphQL } from "../api/graphql";
372
+ import { getDataSDK } from "@salesforce/webapp-experimental/api";
325
373
  import QUERY from "./query/getAccountDetails.graphql?raw";
326
374
  import type {
327
375
  GetAccountDetailsQuery,
328
376
  GetAccountDetailsQueryVariables,
329
377
  } from "../graphql-operations-types";
330
378
 
331
- const data = await executeGraphQL<GetAccountDetailsQuery>(QUERY, {
332
- id: accountId,
333
- includeFinancials: userWantsFinancials,
334
- includeContacts: userWantsContacts,
335
- });
379
+ const data = await getDataSDK();
380
+ const response = await data.graphql?.<GetAccountDetailsQuery, GetAccountDetailsQueryVariables>(
381
+ QUERY,
382
+ {
383
+ id: accountId,
384
+ includeFinancials: userWantsFinancials,
385
+ includeContacts: userWantsContacts,
386
+ },
387
+ );
336
388
  ```
337
389
 
338
390
  ## Anti-Patterns (Not Recommended)
@@ -343,18 +395,21 @@ const data = await executeGraphQL<GetAccountDetailsQuery>(QUERY, {
343
395
  // NOT RECOMMENDED: Direct axios/fetch calls for GraphQL
344
396
  const response = await axios.post("/graphql", { query });
345
397
 
346
- // PREFERRED: Use executeGraphQL
347
- const data = await executeGraphQL<ResponseType>(query, variables);
398
+ // PREFERRED: Use the DataSDK
399
+ const data = await getDataSDK();
400
+ const response = await data.graphql?.<ResponseType>(query, variables);
348
401
  ```
349
402
 
350
403
  ### Missing Type Definitions
351
404
 
352
405
  ```typescript
353
406
  // NOT RECOMMENDED: Untyped GraphQL calls
354
- const data = await executeGraphQL(query);
407
+ const data = await getDataSDK();
408
+ await data.graphql?.(query);
355
409
 
356
410
  // PREFERRED: Provide response type
357
- const data = await executeGraphQL<GetMyDataQuery>(query);
411
+ const data = await getDataSDK();
412
+ const response = await data.graphql?.<GetMyDataQuery>(query);
358
413
  ```
359
414
 
360
415
  ### Plain String Queries (Without gql Tag)
@@ -362,23 +417,26 @@ const data = await executeGraphQL<GetMyDataQuery>(query);
362
417
  ```typescript
363
418
  // NOT RECOMMENDED: Plain string queries without gql tag
364
419
  const query = `query { ... }`;
365
- const data = await executeGraphQL(query);
420
+ const data = await getDataSDK();
421
+ await data.graphql?.(query);
366
422
 
367
423
  // PREFERRED: Use gql tag for inline queries
368
424
  const QUERY = gql`query { ... }`;
369
- const data = await executeGraphQL<ResponseType>(QUERY);
425
+ const data = await getDataSDK();
426
+ const response = await data.graphql?.<ResponseType>(QUERY);
370
427
 
371
428
  // OR: Use .graphql file for complex queries
372
429
  import QUERY from "./query/myQuery.graphql?raw";
373
- const data = await executeGraphQL<ResponseType>(QUERY);
430
+ const data = await getDataSDK();
431
+ const response = await data.graphql?.<ResponseType>(QUERY);
374
432
  ```
375
433
 
376
- ## Benefits of executeGraphQL
434
+ ## Benefits of the DataSDK GraphQL API
377
435
 
378
- - Centralized error handling for GraphQL errors
436
+ - Uses the DataSDK with proper authentication and CSRF token handling
379
437
  - Consistent typing with `GraphQLResponse<T>` interface
380
- - Uses the configured `baseDataClient` with proper authentication
381
- - Automatic extraction of `data` from response envelope
438
+ - Optional chaining (`?.`) safely handles surfaces where GraphQL is unavailable
439
+ - Callers get full `GraphQLResponse<T>` for flexible error handling
382
440
 
383
441
  ## Quality Checklist
384
442
 
@@ -390,18 +448,19 @@ Before completing GraphQL data access code:
390
448
  2. [ ] Run `npm run graphql:codegen` to generate types
391
449
  3. [ ] Import query with `?raw` suffix
392
450
  4. [ ] Import generated types from `graphql-operations-types.ts`
393
- 5. [ ] Use `executeGraphQL<ResponseType>()` with proper generic
394
- 6. [ ] Use `NodeOfConnection` for cleaner node types when needed
395
- 7. [ ] Handle optional chaining for nested response data
396
- 8. [ ] Follow the pattern in `accounts.ts`
451
+ 5. [ ] Use `data.graphql?.<ResponseType>()` with proper generic
452
+ 6. [ ] Handle `response.errors` and destructure `response.data`
453
+ 7. [ ] Use `NodeOfConnection` for cleaner node types when needed
454
+ 8. [ ] Handle optional chaining for nested response data
455
+ 9. [ ] Follow the pattern in `accounts.ts`
397
456
 
398
457
  ### For Pattern 2 (inline with gql):
399
458
 
400
459
  1. [ ] Define query using `gql` template tag
401
460
  2. [ ] Ensure query name matches generated types in `graphql-operations-types.ts`
402
461
  3. [ ] Import generated types for the query
403
- 4. [ ] Use `executeGraphQL<ResponseType>()` with proper generic
404
- 5. [ ] Handle errors with try/catch when appropriate
462
+ 4. [ ] Use `data.graphql?.<ResponseType>()` with proper generic
463
+ 5. [ ] Handle `response.errors` and destructure `response.data`
405
464
  6. [ ] Handle optional chaining for nested response data
406
465
  7. [ ] Follow the pattern in `user.ts`
407
466
 
@@ -5,7 +5,7 @@ description: Add or modify Salesforce GraphQL data access code. Use when the use
5
5
 
6
6
  # GraphQL Data Access
7
7
 
8
- Add or modify Salesforce GraphQL data access code using the established `executeGraphQL` utilities and codegen tooling.
8
+ Add or modify Salesforce GraphQL data access code using `getDataSDK()` + `data.graphql?.()` and codegen tooling.
9
9
 
10
10
  ## When to Use
11
11
 
@@ -18,14 +18,14 @@ Add or modify Salesforce GraphQL data access code using the established `execute
18
18
 
19
19
  The base React app (`base-react-app`) ships with all GraphQL dependencies and tooling pre-configured:
20
20
 
21
- - `@salesforce/sdk-data` — runtime SDK for `executeGraphQL` and `gql`
21
+ - `@salesforce/sdk-data` — runtime SDK for `getDataSDK` and `gql`
22
22
  - `@graphql-codegen/cli` + plugins — type generation from `.graphql` files and inline `gql` queries
23
23
  - `@graphql-eslint/eslint-plugin` — linting for `.graphql` files and `gql` template literals
24
24
  - `graphql` — shared by codegen, ESLint, and schema introspection
25
25
 
26
26
  Before using this skill, ensure:
27
27
 
28
- 1. The `api/graphql.ts` utility exists in the project (provided by the base app)
28
+ 1. The `@salesforce/webapp-experimental/api` package is available (provides `getDataSDK`, `gql`, `NodeOfConnection`)
29
29
  2. A `schema.graphql` file exists at the project root. If missing, generate it:
30
30
  ```bash
31
31
  npm run graphql:schema
@@ -96,21 +96,31 @@ This updates `src/api/graphql-operations-types.ts` with:
96
96
 
97
97
  ```typescript
98
98
  // Pattern 1
99
- import { executeGraphQL, type NodeOfConnection } from "../api/graphql";
99
+ import { getDataSDK, type NodeOfConnection } from "@salesforce/webapp-experimental/api";
100
100
  import MY_QUERY from "./query/myQuery.graphql?raw";
101
101
  import type { GetMyDataQuery, GetMyDataQueryVariables } from "../graphql-operations-types";
102
102
 
103
103
  type MyNode = NodeOfConnection<GetMyDataQuery["uiapi"]["query"]["MyObject"]>;
104
104
 
105
105
  export async function getMyData(variables: GetMyDataQueryVariables): Promise<MyNode[]> {
106
- const response = await executeGraphQL<GetMyDataQuery>(MY_QUERY, variables);
107
- return response.uiapi?.query?.MyObject?.edges?.map((edge) => edge?.node) || [];
106
+ const data = await getDataSDK();
107
+ const response = await data.graphql?.<GetMyDataQuery, GetMyDataQueryVariables>(
108
+ MY_QUERY,
109
+ variables,
110
+ );
111
+
112
+ if (response?.errors?.length) {
113
+ const errorMessages = response.errors.map((e) => e.message).join("; ");
114
+ throw new Error(`GraphQL Error: ${errorMessages}`);
115
+ }
116
+
117
+ return response?.data?.uiapi?.query?.MyObject?.edges?.map((edge) => edge?.node) || [];
108
118
  }
109
119
  ```
110
120
 
111
121
  ```typescript
112
122
  // Pattern 2
113
- import { executeGraphQL, gql } from "../api/graphql";
123
+ import { getDataSDK, gql } from "@salesforce/webapp-experimental/api";
114
124
  import type { MySimpleQuery } from "../graphql-operations-types";
115
125
 
116
126
  const MY_QUERY = gql`
@@ -120,15 +130,16 @@ const MY_QUERY = gql`
120
130
  `;
121
131
 
122
132
  export async function getSimpleData(): Promise<SomeType> {
123
- const response = await executeGraphQL<MySimpleQuery>(MY_QUERY);
124
- // extract and return data
133
+ const data = await getDataSDK();
134
+ const response = await data.graphql?.<MySimpleQuery>(MY_QUERY);
135
+ // check response.errors, then extract response.data
125
136
  }
126
137
  ```
127
138
 
128
139
  ### Step 6: Verify
129
140
 
130
141
  - [ ] Query field names match the schema exactly (case-sensitive)
131
- - [ ] Response type generic is provided to `executeGraphQL<T>()`
142
+ - [ ] Response type generic is provided to `data.graphql?.<T>()`
132
143
  - [ ] Optional chaining is used for nested response data
133
144
  - [ ] Pattern 1: `.graphql` file imported with `?raw` suffix
134
145
  - [ ] Pattern 2: Query uses `gql` tag (not plain string)