The product is a Centra-powered fashion storefront in a Next.js monorepo. Shoppers see product cards, basket drawers, and checkout steps built from shared shop components. Those components used to import Centra commerce types and Sanity CMS view types directly. A Centra field rename then broke a product card at runtime on a Friday deploy. A Sanity block type leaked into a checkout molecule the week after. Shop UI should not know vendor field names, but autocomplete made it easy to reach for them.
Vendor shapes in the UI layer mean every API drift becomes a JSX chase. Basket and CheckoutDraft were especially painful: half the props were Centra selection types with names I did not want on a storefront contract. A DTO here is a small mapped object with only the fields the storefront needs, not the vendor's full record. Once a selection type sits on a card prop, every Centra release note becomes your problem.
Mappers, then a lint wall
Code review did not catch a convenient SelectionLine import at the end of a slice. Thin shop mappers expose Basket, CheckoutDraft, and settings as storefront types via ReturnType inference. No hand-rolled field bags, and no re-exporting vendor types from shop. Compositions only see what the storefront needs.
Biome noRestrictedImports bans @repo/centra/** and @repo/sanity/view/** under packages/shop/src/components. Mappers and services still import vendors. Live preview and image adapters stay on an allowlist because they are adapters, not product UI.
export function mapBasket(...args: Parameters<typeof mapCartSelection>) {
return mapCartSelection(...args);
}
export type BasketDto = ReturnType<typeof mapBasket>;
// biome.jsonc on packages/shop/src/components:
// noRestrictedImports: @repo/centra/**, @repo/sanity/view/**The linter stops the next developer from importing SelectionLine into a card component because it was convenient for five minutes. Convenience is how vendor APIs become your public API.
Allowlists and ReturnType have teeth
The ban is on packages/shop/src/components. Mappers and services still talk to Centra and Sanity. That is the point. Live preview and image adapters stay allowlisted because they are adapters. Copy that allowlist onto a product card and the wall is gone.
ReturnType inference means the mapper return shape is the DTO. If the mapper forwards a Centra selection object untouched, the card still sees vendor fields, just with a friendlier alias. I pick the fields the storefront needs. Wrapping mapCartSelection and calling the leak a contract does not help.
I am not going to type every basket line by hand. Inference stays honest only if the mapper is honest. Settings go through the same door as Basket and CheckoutDraft. One leaky settings type and checkout molecules start importing Sanity view unions again.
Mappers sit outside the banned folder on purpose. Live preview and image adapters are the exception because they adapt vendor media, they do not render a product card. I keep the exception small so nobody treats "adapter" as a free pass for every molecule.
When Centra changes a cart field, I still want one file to touch. That file is the mapper, not ProductCard.tsx. The lint rule is there for the day I am tired and the import autocomplete offers @repo/centra. Review catches intent. Biome catches the shortcut at save time. The storefront contract stays stable even when the vendor SDK shuffles names again next quarter.
Happy coding! Sander