$npx -y skills add forcedotcom/sf-skills --skill experience-ui-bundle-salesforce-data-accessMUST activate when a uiBundles/*/src/ project does ANY Salesforce record operation — reading, creating, updating, deleting, or caching/refreshing query results. Triggers: code importing @salesforce/platform-sdk, calls to sdk.graphql.query / sdk.graphql.mutate / sdk.fetch, *.graph
| 1 | # Salesforce Data Access (UI bundles) |
| 2 | |
| 3 | All Salesforce data access in a UI bundle goes through the **`@salesforce/platform-sdk`** |
| 4 | data SDK. The SDK handles auth, CSRF, and base-URL resolution, and — on the WebApp |
| 5 | surface — caches every GraphQL query by default. |
| 6 | |
| 7 | This file is the **workflow + guardrail spine**. Depth lives in linked docs: |
| 8 | |
| 9 | - **[references/graphiti-cli.md](references/graphiti-cli.md)** — the **`graphiti` CLI** (`sf-gql-*` |
| 10 | commands) that compiles a small JSON spec into a schema-correct, guardrail-applied query + |
| 11 | variables + types. The preferred way to author the GraphQL in steps below; falls back to the |
| 12 | schema-grep script when unavailable. |
| 13 | - **[references/sdk-api.md](references/sdk-api.md)** — the new call API: `query`/`mutate`, |
| 14 | `QueryResult`, typing, error-handling stances. |
| 15 | - **[references/caching.md](references/caching.md)** — on-by-default cache + the **two refresh |
| 16 | modes** (`result.refresh`/`subscribe` vs per-call `cacheControl`). |
| 17 | - **[references/graphql-hand-authoring.md](references/graphql-hand-authoring.md)** — schema lookup, read / |
| 18 | mutation templates, every platform guardrail (`@optional`, pagination, limits, |
| 19 | semi-join, wrappers, error table…). |
| 20 | - **[references/rest-and-integration.md](references/rest-and-integration.md)** — `sdk.fetch`, |
| 21 | the supported-API allowlist, and the reactive/lifecycle integration patterns. |
| 22 | - **[references/migration.md](references/migration.md)** — old `@salesforce/sdk-data` callable code |
| 23 | → new namespace. The **only** place the dead API appears as usable code. |
| 24 | |
| 25 | ## The one-paragraph mental model |
| 26 | |
| 27 | `const sdk = await createDataSDK()`. Then `sdk.graphql` is a **namespace**, not a |
| 28 | function: **`sdk.graphql!.query({...})`** for reads, **`sdk.graphql!.mutate({...})`** |
| 29 | for writes. On WebApp, **every `query()` is cached by default** (300s). HTTP 200 never |
| 30 | means success — always check `result.errors`. Verify every entity and field against the |
| 31 | schema before you query it: one unverified field fails the *whole* query at runtime, and |
| 32 | `schema.graphql` is too large to eyeball — look it up. |
| 33 | |
| 34 | ```typescript |
| 35 | import { createDataSDK, gql } from "@salesforce/platform-sdk"; // gql tags the query string so codegen + eslint validate it |
| 36 | |
| 37 | const sdk = await createDataSDK(); |
| 38 | const result = await sdk.graphql!.query({ query: GET_ACCOUNTS, variables }); |
| 39 | if (result.errors?.length) throw new Error(result.errors.map((e) => e.message).join("; ")); |
| 40 | const rows = result.data?.uiapi?.query?.Account?.edges?.map((e) => e.node) ?? []; // unwrap edges/node; read field values via .value |
| 41 | ``` |
| 42 | |
| 43 | Typed call params (`query<GetAccountsQuery, GetAccountsQueryVariables>`), the `CacheControl` |
| 44 | type, and `NodeOfConnection<T>` (extracts a node type from a Connection for clean typing) all |
| 45 | live in [references/sdk-api.md](references/sdk-api.md). |
| 46 | |
| 47 | > **This changed (breaking — PR #502).** The previous callable `sdk.graphql(...)` form and the |
| 48 | > previous package name are **dead** — the code above is the only correct form. If you encounter |
| 49 | > the old API in existing code (or a stale `dist/` artifact), don't copy it; convert it per |
| 50 | > [Working on existing code](#working-on-existing-code-migration). |
| 51 | > |
| 52 | > **`sdk.graphql!` is WebApp-only.** The non-null assertion above is correct *only* if the |
| 53 | > bundle runs solely on WebApp. On other surfaces it can crash — decide before you write it. |
| 54 | > See **[Surfaces — `!` vs guard](#surfaces--sdkgraphql-vs-guard)** below. |
| 55 | |
| 56 | --- |
| 57 | |
| 58 | ## Surfaces — `sdk.graphql!` vs guard |
| 59 | |
| 60 | `createDataSDK()` runs on multiple surfaces, and **`sdk.graphql` / `sdk.fetch` are genuinely |
| 61 | optional** (typed `graphql?: …`). Whether you may assert them with `!` depends entirely on |
| 62 | where the bundle runs — this is the one surface decision that turns into a *runtime crash* if |
| 63 | you get it wrong, so make it explicitly before writing any `query`/`mutate` call: |
| 64 | |
| 65 | | Surface(s) | `sdk.graphql` | Write | |
| 66 | |---|---|---| |
| 67 | | **WebApp only** | always present | `sdk.graphql!.query({...})` — `!` is safe; every shipped WebApp consumer uses it | |
| 68 | | **Mosaic / OpenAI / MCPApps** (or any bundle that *might* run o |