@techsio/storefront-data
Version:
Medusa-first storefront data layer built on TanStack Query.
209 lines (160 loc) • 5.91 kB
Markdown
# Storefront Data (`@techsio/storefront-data`)
Medusa-first storefront data layer built on TanStack Query.
The library is intentionally opinionated:
- use explicit file-level imports, not package-root barrels
- compose one preset per storefront
- keep Medusa quirks inside Medusa services and preset wiring
- keep app DTOs and read models in the app unless reused by a second storefront
## Recommended integration path
For Medusa storefronts, the primary entrypoint is `createMedusaStorefrontPreset`.
A preset owns:
- query keys
- services
- hooks
- flows
- shared cache semantics
- cart persistence wiring
That lets each storefront keep a thin local composition module for:
- SDK instance
- localized error mapping
- product field defaults
- address adapters
- customer-specific policy
## Install
```bash
pnpm add @techsio/storefront-data @medusajs/js-sdk @medusajs/types @tanstack/react-query react react-dom
```
If you use Next.js, transpile the package:
```js
const nextConfig = {
transpilePackages: ["@techsio/storefront-data"],
}
```
## Client provider
```tsx
import { StorefrontDataProvider } from "@techsio/storefront-data/client/provider"
export function Providers({ children }) {
return <StorefrontDataProvider>{children}</StorefrontDataProvider>
}
```
## Preset-first example
```ts
import { createLocalStorageValueStore } from "@techsio/storefront-data/shared/storage-value-store"
import { createMedusaStorefrontPreset } from "@techsio/storefront-data/medusa/preset"
import { sdk } from "@/lib/medusa-client"
const cartStorage = createLocalStorageValueStore({
key: "shop-cart-id",
})
export const storefront = createMedusaStorefrontPreset({
sdk,
queryKeyNamespace: "shop",
cart: {
hooks: {
cartStorage,
},
},
})
```
Use the preset from thin app wrappers or directly in app code:
```ts
const { products, cart, checkout } = storefront.hooks
const { cart: cartFlow, checkout: checkoutFlow } = storefront.flows
```
## Sane defaults vs custom adapters
The library exports shared address contracts from `shared/address` and a small opinionated default in `checkout/address`.
Use the default when your storefront shape is close to the built-in checkout address model:
```ts
import {
createCheckoutCartAddressAdapter,
createCheckoutCustomerAddressAdapter,
} from "@techsio/storefront-data/checkout/address"
```
If your storefront has its own form DTO, formatting, localization, or patch semantics, keep the adapter local to the app and implement the shared adapter contract instead.
## Query options
The stable public query-options surface is intentionally small.
Use domain hooks for most reads, and use query-option helpers when you need SSR, loader, or manual prefetch integration.
```ts
import { createOrderHooks } from "@techsio/storefront-data/orders/hooks"
const orderHooks = createOrderHooks({
service,
buildListParams: (input) => ({ ...input }),
})
const listQuery = orderHooks.getListQueryOptions({ limit: 20, offset: 0 })
const detailQuery = orderHooks.getDetailQueryOptions({ id: "order_1" })
```
```ts
import { createProductHooks } from "@techsio/storefront-data/products/hooks"
const productHooks = createProductHooks({
service,
buildListParams: (input) => ({ ...input }),
buildDetailParams: (input) => ({ ...input }),
})
const listQuery = productHooks.getListQueryOptions({
limit: 20,
region_id: "reg_1",
})
```
## SSR example
```tsx
import { dehydrate, HydrationBoundary } from "@tanstack/react-query"
import { getServerQueryClient } from "@techsio/storefront-data/server/get-query-client"
export default async function Page() {
const queryClient = getServerQueryClient()
await queryClient.prefetchQuery(productHooks.getListQueryOptions({ limit: 20 }))
return (
<HydrationBoundary state={dehydrate(queryClient)}>
<ProductsPage />
</HydrationBoundary>
)
}
```
## Stable public subpaths
Use explicit file-level imports. There is no supported package-root import.
Core subpaths:
- `@techsio/storefront-data/client/provider`
- `@techsio/storefront-data/server/get-query-client`
- `@techsio/storefront-data/shared/address`
- `@techsio/storefront-data/shared/cache-config`
- `@techsio/storefront-data/shared/local-storage`
- `@techsio/storefront-data/shared/medusa-client`
- `@techsio/storefront-data/shared/query-client`
- `@techsio/storefront-data/shared/query-keys`
- `@techsio/storefront-data/shared/region`
- `@techsio/storefront-data/shared/region-context`
- `@techsio/storefront-data/shared/storage-value-store`
- `@techsio/storefront-data/medusa/preset`
Domain subpaths:
- `@techsio/storefront-data/<domain>/hooks`
- `@techsio/storefront-data/<domain>/medusa-service`
- `@techsio/storefront-data/<domain>/query-keys`
- `@techsio/storefront-data/<domain>/query-options` where the domain exposes SSR/query-option helpers
- `@techsio/storefront-data/<domain>/types`
Product-list utilities:
- `@techsio/storefront-data/product-lists/utils`
Supported domains:
- `auth`
- `cart`
- `catalog`
- `categories`
- `checkout`
- `collections`
- `customers`
- `orders`
- `product-lists`
- `products`
- `regions`
## Notes
- Prefetch helpers default to skipping only fresh cache entries (`skipMode: "fresh"`).
- Suspense queries are not cancellable through TanStack Query.
- Query keys normalize plain-object params and strip `enabled` from query-key inputs.
- The default checkout helpers are optional. The shared contract is the stable part.
- App-level read models such as category registries should stay in the app until reused by another storefront.
## Development
```bash
pnpm -C libs/storefront-data build
pnpm -C libs/storefront-data lint
pnpm -C libs/storefront-data test
pnpm -C libs/storefront-data validate:release
```
Releases are tag-gated. A tag such as `storefront-data-v0.1.0` must exactly
match the package version before the trusted-publishing workflow can publish it.