Subchapter 1.112
references/dashboard-page/DATA_SOURCES.mdMarkdown9 KBView on GitHub
Scope. DRAFT_TEMPLATE.md gives you the page; this file finds the method and for its data. The gives the namespace, the method and the entity’s ; this file checks the fields there. The installed package is the version your code compiles against.
file:linePackage layout, the hashed entity file, and why not to cat the barrel are in
SDK_PACKAGE_LAYOUT.md.
The SDK lookup lists every method of the namespace (other methods: outside the intent). Prefer
its list to recall: listTransactionsForMultipleOrders(orderIds: string[]) and
listTransactionsForSingleOrder differ by exactly the shape a collection page needs, and only one
of them avoids an N+1 across the visible page.
Once you have the entity, read the declaration for every field you map into a column, not just the uncertain ones, in one pass. List the types and fields first, then print them with one call per file. A grep per question is a round trip each:
E=node_modules/@wix/auto_sdk_ecom_order-transactions/build/es
awk '/^(interface|type) (Payment|RegularPaymentDetails)[ <{=]/,/^}/' $E/index.typings.d.mtsFailure modes that recur:
Deprecated fields still compile. On RegularPaymentDetails, paymentMethod carries
@deprecated Use paymentMethodName.buyerLanguageName instead — the deprecated one is a bare code,
the replacement is the localised name a person expects to read. Grep before you map:
grep -B4 "paymentMethod?:" $E/index.typings.d.mts # shows the @deprecated banner above itA column bound to a deprecated field renders something plausible and wrong, which is the hardest kind of defect to catch in review.
Aggregates hide one level down. Money on a refund is not on the refund — it is
refund.summary.refunded.amount (AggregatedRefundSummary, with pendingRefund, failedRefundAmount
and requestedRefund beside it), while per-transaction status is
refund.transactions[].refundStatus. Summing the wrong one silently under-reports.
A generic-sounding field is not the related entity. Treat .title, .name and .summary as
unverified until you read the declaration: an Extended* / *WithDetails shape exists precisely to
attach the real related entity, and a summary field on the base item is not a substitute. A column
must show what its header promises.
A mapper must cover every shape the response returns. Responses often carry a oneof — Bookings’
bookedEntity is slot for an appointment, schedule for a class — and a mapper reading one
variant renders blank cells for the other while passing tsc.
A collection page usually needs one query plus one batch lookup, never a lookup per row:
const { orders: found } = await orders.searchOrders({ filter, sort, cursorPaging: { limit, cursor } });
const ids = found.map((o) => o._id).filter((id): id is string => Boolean(id));
const { orderTransactions: txns } = await orderTransactions.listTransactionsForMultipleOrders(ids);
const byId = new Map(txns.map((t) => [t.orderId, t]));Build the Map once and index it in the row mapper. Guard the empty case before the second call —
a batch endpoint given [] is a wasted round trip at best.
Keep the page limit at or below what the batch method accepts. listTransactionsForMultipleOrders
takes at most 100 ids. It leaves out orders the caller isn’t permitted to access instead of failing, so an order
missing from the Map is not proof that it has no payments. searchOrders skips PENDING and
REJECTED orders unless the filter asks for that status, and never returns INITIALIZED ones.
searchOrders takes { cursorPaging, filter, sort }. filter is Record<string, any>, so a
wrong field or operator still compiles: take both from the method’s supported-filters table. An
exact order number is { number: { $eq: orderNumber } }. A date range is two $and clauses, not
$gte and $lte on one field (QUERY_AND_PAGING.md).
Read enums from the declaration.
Docs search returns REST pages, and a dashboard page calls the SDK. Append ?apiView=SDK to the
URL of whatever page you landed on and read that view — it answers two of the four differences.
1 and 2 — namespace and id naming. The SDK view states both. It prints the package and namespace
outright (SDK Package: @wix/members, SDK Namespace: customFields), and uses _id in its own
parameter list. Infer either from the REST page and you get them wrong: REST paths say services/v2,
so servicesV2 looks right, and the SDK exports services; REST responses show "id", and the SDK
type declares _id — same for _createdDate / _updatedDate. Offline, the barrel is the fallback
for the namespace list:
grep -oE "as [a-zA-Z][a-zA-Z0-9]* }" node_modules/@wix/<pkg>/build/es/index.d.mts | sed 's/as //; s/ }//' | sort -uThe next two are codegen artifacts that no docs page mentions — read them off the declaration.
3. Don’t derive types from ReturnType. Most SDK functions are overloaded — a direct call
and an httpClient form — so Awaited<ReturnType<typeof ns.method>>['items'][number] resolves
against the wrong overload and fails. Import the entity type the package exports instead:
type Row = <namespace>.<Entity>; // e.g. extendedBookings.ExtendedBooking — not ReturnType<typeof …>4. Paging metadata changes key and type per method. The key may be pagingMetadata,
metadata or cursorPagingMetadata; the type may be CursorPagingMetadata, PagingMetadataV2,
PagingMetadata or PlatformPagingMetadata. Only CursorPagingMetadata declares hasNext. searchOrders, for example,
returns metadata: CursorPagingMetadata. Read the response interface of the method you call before
you take the next-page signal from it. What the collection wants you to return is a separate
question — see TABLE_STATE.md.
Is the package even installed? A vertical’s SDK is not a default dependency. @wix/bookings,
@wix/ecom and the rest have to be present before the import resolves:
node -e "const p=require('./package.json');console.log(!!({...p.dependencies,...p.devDependencies})['@wix/<pkg>'])"
npm install @wix/<pkg> # if falseDoes the app hold the scope? This is the one that produces a page which builds, mounts, renders its shell, and shows nothing — the hardest failure to read, because an empty table looks like empty data. Reads of a vertical’s data need that vertical’s permission scope, granted in Dev Center → Permissions; it cannot be declared in the repo, and the app’s install consent screen names what it actually has.
Don’t guess the scope name — the method’s own docs page prints it. In SDK view every method
carries Method Permissions and Method Permissions Scopes IDs, for example
Manage Members: SCOPE.DC-MEMBERS.MANAGE-MEMBERS. Look it up for the exact method you are calling,
assume it is missing on a fresh app, and report it under
Manual Steps Required by name. Wiring errorState on the
table (the draft template does) is what turns this from silent skeletons into a message you can read.
The two checks above are easy to run once, at Step 3, for the vertical the page is obviously about — and then to skip for the next one, because by then you are deep in Step 4b adding a detail column or resolving a search term. That second package needs the same two things, and it fails differently: the page already worked, so the regression looks like something you broke in the UI.
A measured run added @wix/crm mid-implementation to resolve a client-name search. The app held
Bookings scopes and not SCOPE.DC-CONTACTS.READ-CONTACTS, the contacts call answered 403, and
because the search awaited it unconditionally, a page that had been loading fine stopped loading at
all.
Run the package-and-scope check for every @wix/* import you add, whenever you add it — and
then decide what its failure should cost:
errorState reports it.TableTopNotification is the patterns component for it) rather than degrading silently.Ask which one you are adding before you write the await. An unwrapped secondary call is a page
whose availability is the intersection of every scope it touches.
Filter field paths, the one-operator-per-field rule, and the two cursor-paging traps are in
QUERY_AND_PAGING.md. Read it before writing fetchData.