10 KiB
LDS Expert
Apply the LDS guidelines below when analyzing data requirements from PRDs, user asks, or LWC source files. Sections 1–3 produce structured requirement, validation, and recommendation entries from natural-language or code inputs; sections 4–5 detect and fix code-level anti-patterns in LWC source.
1 — Requirements Analysis for LDS data needs
For every data-related TODO, comment, or partial implementation in the LWC source, produce one requirement entry with these fields:
operation— exactly one ofREAD,CREATE,UPDATE,DELETE. If the source phrasing is ambiguous ("sync","refresh","manage"), setoperationto the single best fit and add a one-line note inassumptions.object— the Salesforce object API name (e.g.Account,Gym__c). Custom-object terms (Gym,Property,Course) require explicit__cresolution via the API-name validation rules below.fields— the list of field API names touched. Generic terms ("phone","address","name","status") must be disambiguated to a specific API name.scope— exactly one ofSINGLE_RECORD,RELATED_LIST,MULTI_OBJECT,QUERY. Drives adapter selection in section 3.trigger— exactly one ofMOUNT,USER_ACTION,EXTERNAL_EVENT. Drives wire-vs-imperative selection in section 3.assumptions— (optional) one-line note present only whenoperationwas inferred from ambiguous source phrasing (e.g."sync"→UPDATE — assumed based on verb context).
Source code with clear, specific LDS implementations or non-data TODOs produces no entry. Comments that already explicitly acknowledge the need for clarification (// TODO: confirm field API name with admin) are considered resolved by the comment and produce no new entry.
2 — API name validation
For each Salesforce object or field reference in the source, produce one validation entry with these fields:
reference— the object or field name as written (e.g.Account.Phone,Gym__c.Capacity__c).status— exactly one ofVERIFIED,UNVERIFIED,AMBIGUOUS.source— forVERIFIED, the source of truth (e.g.standard object — SObject describe,FieldDefinition row). ForUNVERIFIED/AMBIGUOUS, the missing input (e.g.no SObject describe for Gym__c in target org,caller must select between Phone and MobilePhone).resolution— the corrected API name with exact__csuffix and capitalization. ForUNVERIFIED, prefix withUNVERIFIED:followed by the best-guess name.
Auto-verified references: standard objects with exact casing — Account, Contact, Lead, Opportunity, Case, User, Task, Event, Product2, Pricebook2, Order, OrderItem, Asset, Contract, Campaign — and their standard fields per the precision mapping below.
Standard field precision mapping
Account:Name,Phone,Website,BillingAddress,ShippingAddress,Industry,Type,Description.Contact:FirstName,LastName,Name,Email,Phone,MobilePhone,MailingAddress,Department,Title.Opportunity:Name,StageName,CloseDate,Amount,Probability,AccountId,Type.Case:Subject,Status,Priority,Origin,Description,ContactId,AccountId.
Generic-term disambiguation
"phone"→Phone,MobilePhone,HomePhone,OtherPhone, orWorkPhone."address"→BillingAddress,ShippingAddress,MailingAddress, orOtherAddress."name"→Name(full),FirstName,LastName, orCompanyNameper object."status"→Status,StageName, or the custom status field.
Do not emit TODO comments — the validation entry above is the production output.
3 — API recommendation
Use this priority order when selecting the data-access API for each requirement entry produced in section 1:
- GraphQL wire adapter (
lightning/graphql) — top choice for all read scenarios it supports. - UI API / Lightning Data Service — second choice for CRUD writes, metadata, layouts, picklists, and simple reads.
- Apex — fallback only when GraphQL and UI API cannot meet the requirement.
Decision framework
| Requirement shape | Recommended API | Reason |
|---|---|---|
| Read-only on UI-API-supported object/fields | GraphQL | Adapter supports it; reduces round trips |
| Multi-object / parent-child read | GraphQL | Nested queries in one call |
| Filtering / sorting / pagination / aggregation | GraphQL | SOQL-like + native cursor support |
| Single-record CRUD on supported object | UI API | getRecord, createRecord, updateRecord, deleteRecord |
| Metadata (picklists, object info, layouts) | UI API | getPicklistValues, getObjectInfo, getLayout |
| Record-create with defaults | UI API | getRecordCreateDefaults + createRecord |
| List views | UI API | getListUi, getListInfoByName |
| Multi-record atomic transaction | Apex | UI API has no atomic batch |
| Object/field unsupported by UI API | Apex | UI API allowlist gap |
| Custom business logic / elevated permissions | Apex | System context required |
Output contract — for each requirement from section 1, produce one recommendation entry with:
dataNeed— one-sentence description referencing the source requirement.api— exactly one ofGraphQL,UIAPI,Apex.reason— one sentence pointing at the row above that selected the API.adapter— the specific adapter or method (e.g.@wire(graphql, ...),getRecord,@AuraEnabled Apex method <name>).
When the source contains no data needs, produce an empty list. Do not emit TODO comments in the LWC code or PRD — the recommendation entry is the production output.
4 — Wire adapter migration to state managers
When the source uses any of the legacy @wire adapters below, produce one migration entry per adapter occurrence — including the file location, the legacy adapter, the modern replacement, and the call-site code change required.
Legacy @wire adapter |
Replacement (lightning/stateManagersUiapi) |
|---|---|
graphql (from lightning/graphql) |
smGraphQL |
getLayout (from lightning/uiLayoutApi) |
smLayout |
getObjectInfo (from lightning/uiObjectInfoApi) |
smObjectInfo |
getObjectInfos (from lightning/uiObjectInfoApi) |
smObjectInfos |
getRecord (from lightning/uiRecordApi) |
smRecord |
getRelatedListInfo (from lightning/uiRelatedListApi) |
smRelatedListInfo |
getRelatedListRecords (from lightning/uiRelatedListApi) |
smRelatedListRecords |
getRelatedListsInfo (from lightning/uiRelatedListApi) |
smRelatedListsInfo |
Scope rules
- Only
@wireusages of the legacy adapters trigger an entry. Imperative usage of the same modules is out of scope. - Bare import statements without
@wireusage produce no entry. - Other
@wireadapters not in the table produce no entry.
Produce the migration entry directly in the output; do not emit TODO comments to defer the migration.
5 — GraphQL refresh usage (anti-pattern fixes)
When the source uses @wire(graphql, ...) from lightning/graphql and any of the patterns below occurs, produce one fix entry with the corrected code per the corresponding correct pattern below. The lightning/uiGraphQLApi refreshGraphQL standalone function is a different API and is out of scope.
Anti-patterns
// BAD — not destructuring refresh from the wire result
@wire(graphql, { query: myQuery })
wiredResult({ data, errors }) {
this.data = data;
}
handleRefresh() { /* refresh unavailable */ }
// BAD — not awaiting the refresh promise when downstream code depends on it
async handleRefresh() {
this._refresh();
this.processData(); // may run before refresh completes
}
// BAD — calling refresh without an existence check (wire may not have resolved)
handleRefresh() { this._refresh(); }
// BAD — mutation without refreshing the related GraphQL query
async handleCreateContact() {
await executeMutation({ query: createContactMutation });
// cache stale — must refresh
}
// BAD — external data change (Apex / CDC / LMS) without refreshing the GraphQL query
async handleUpdateViaApex() { await updateRecordsApex({ ... }); }
subscribe('/data/AccountChangeEvent', -1, (msg) => { /* no refresh */ });
handleRecordUpdate(msg) { if (msg.objectApiName === 'Contact') { /* no refresh */ } }
Correct patterns
@wire(graphql, { query: contactsQuery })
wiredContacts({ data, errors, refresh }) {
this.contacts = data?.uiapi?.query?.Contact?.edges?.map(e => e.node);
this.errors = errors;
this._refreshContacts = refresh;
}
async handleRefresh() {
if (this._refreshContacts) {
await this._refreshContacts();
}
}
async handleCreateContact() {
await executeMutation({ query: createContactMutation, variables: { /* ... */ } });
if (this._refreshContacts) {
await this._refreshContacts();
}
}
async handleUpdateViaApex() {
await updateRecordsApex({ recordIds: this.ids });
if (this._refreshRecords) {
await this._refreshRecords();
}
}
subscribeToChanges() {
subscribe('/data/AccountChangeEvent', -1, async (message) => {
if (this._refreshAccounts) {
await this._refreshAccounts();
}
});
}
async handleRecordUpdate(message) {
if (message.objectApiName === 'Contact' && this._refreshContacts) {
await this._refreshContacts();
}
}
For each occurrence, produce the corrected snippet directly; do not flag the issue with a TODO. When the source has no @wire(graphql, ...) usage, produce an empty list.