@techsio/storefront-data
Version:
Medusa-first storefront data layer built on TanStack Query.
192 lines (141 loc) • 5.23 kB
Markdown
---
name: extend-storefront-data-for-new-backend-use-cases
description: >
Load this skill when adding a new shared backend-facing capability to
@techsio/storefront-data through preset wiring, normalized query keys,
service-layer cancellation, and test-backed invariants. Use it when app code
has proven a backend concern is no longer customer-specific and should become
part of the shared storefront platform.
type: core
library: "@techsio/storefront-data"
library_version: "0.1.0"
requires:
- decide-app-specific-overrides-vs-shared-platform
sources:
- "TechsioCZ/new-engine:libs/storefront-data/AGENTS.md"
- "TechsioCZ/new-engine:libs/storefront-data/src/medusa/preset.ts"
- "TechsioCZ/new-engine:libs/storefront-data/src/shared/query-keys.ts"
- "TechsioCZ/new-engine:libs/storefront-data/src/orders/medusa-service.ts"
- "TechsioCZ/new-engine:libs/storefront-data/tests/medusa.preset.test.tsx"
- "TechsioCZ/new-engine:libs/storefront-data/tests/medusa.flow.test.tsx"
---
This skill builds on `decide-app-specific-overrides-vs-shared-platform`. Use it only after deciding the behavior should become shared.
# Extend storefront-data for new backend use cases
## Setup
Treat new shared behavior as a coordinated change across types, query keys, service wiring, preset composition, and tests.
```ts
// src/inventory/query-keys.ts
import { createQueryKey, type QueryNamespace } from "../shared/query-keys"
export const createInventoryQueryKeys = (namespace: QueryNamespace) => ({
all: () => createQueryKey(namespace, "inventory"),
list: (input: { sku?: string }) => createQueryKey(namespace, "inventory", "list", input),
})
```
```ts
// src/inventory/medusa-service.ts
import type { MedusaSdk } from "../shared/medusa-client"
export function createMedusaInventoryService(sdk: MedusaSdk) {
return {
async getInventory(input: { sku?: string }, signal?: AbortSignal) {
return sdk.client.fetch({
method: "GET",
path: "/store/inventory",
query: input,
signal,
})
},
}
}
```
## Core Patterns
### Wire new shared behavior through the preset
```ts
// src/medusa/preset.ts
const services = {
...existingServices,
inventory: config.inventory?.service ?? createMedusaInventoryService(config.sdk),
}
```
The preset is the canonical composition root. If the new domain never reaches the preset, it does not reach the intended integration path.
### Reuse normalized query-key helpers
```ts
// src/inventory/hooks.ts
const queryKeys =
config.queryKeys ?? createInventoryQueryKeys(config.queryKeyNamespace ?? "storefront-data")
```
Treat `createQueryKey()` and the existing key factories as mandatory infrastructure, not optional style.
### Pin the new invariant with tests in `libs/storefront-data/tests`
```ts
// tests/inventory.smoke.test.ts
import { describe, expect, it } from "vitest"
describe("inventory preset wiring", () => {
it("exposes the inventory surface from the preset", () => {
expect(true).toBe(true)
})
})
```
Use the test file as the place where the new shared contract becomes explicit.
## Common Mistakes
### HIGH New domain outside the preset
Wrong:
```ts
export const useInventory = createInventoryHooks({
service,
queryKeyNamespace: "shop",
})
```
Correct:
```ts
export const storefront = createMedusaStorefrontPreset({
sdk,
inventory: {
service,
},
})
```
The preset is the main composition root. A new capability that lives outside it immediately diverges from the intended integration path.
Source: `libs/storefront-data/src/medusa/preset.ts`, `libs/storefront-data/AGENTS.md`, `libs/storefront-data/README.md`
### CRITICAL Hardcoded query keys
Wrong:
```ts
const queryKey = ["storefront", "inventory", params]
```
Correct:
```ts
const queryKey = createQueryKey(namespace, "inventory", "list", params)
```
Shared domains must participate in normalized key generation or they drift from invalidation and cache-matching behavior everywhere else.
Source: `libs/storefront-data/src/shared/query-keys.ts`, `libs/storefront-data/AGENTS.md`
### HIGH `AbortSignal` accepted but ignored
Wrong:
```ts
async function getInventory(params: InventoryInput, signal?: AbortSignal) {
return sdk.store.product.list(params)
}
```
Correct:
```ts
async function getInventory(params: InventoryInput, signal?: AbortSignal) {
return sdk.client.fetch({
method: "GET",
path: "/store/inventory",
query: params,
signal,
})
}
```
TanStack Query passes cancellation signals into query functions. Dropping them creates fake cancellation support.
Source: `libs/storefront-data/src/orders/medusa-service.ts`, TanStack Query query cancellation docs
### HIGH Shared behavior without shared tests
Wrong:
```ts
// add new flow logic with no regression coverage
```
Correct:
```ts
// add the new logic and pin the invariant in libs/storefront-data/tests/*
```
The library now treats tests as part of the consumer-facing contract. Missing regression coverage pushes risk back into each storefront.
Source: `libs/storefront-data/tests/medusa.preset.test.tsx`, `libs/storefront-data/tests/medusa.flow.test.tsx`
## References
- [Extension recipe](references/extension-recipe.md)