Architektura Storefront SDK
Storefront SDK (@doswiftly/storefront-sdk) to layered runtime SDK do budowy witryn sklepowych (storefrontów) na platformie DoSwiftly. Pakiet ma 0 runtime dependencies w core i dostarcza transport factory z middleware pipeline, React providers, Zustand stores, plain async clients (Cart, Auth) i strategie cache. Hooki React Query i server helpers są generowane lokalnie w szablonie storefront przez codegen.ts — wzorzec codegen-first (hooki i typy generowane lokalnie z operacji GraphQL).
SDK ma layered architecture (wprowadzona w 4.0.0) — logical split core/ (framework-agnostic) + react/ (React adapter):
@doswiftly/storefront-sdk(core) — transport factory, middleware pipeline (auth,currency,language,botProtection,retry,timeout,errors),CartClient,AuthClient,StorefrontError, format utilities,sanitizeHtml,normalizeConnection, auth/language/currency cookie config,matchesRoute, cache strategies. 0 runtime deps.@doswiftly/storefront-sdk/react—StorefrontProvider, Zustand stores (currency, auth, language, cart),useAuth(),useCartManager(),useCurrency(),useHydrated(),useDebouncedValue(),createStoreContext(),LanguageProvider,CurrencyProvider,CartProvider@doswiftly/storefront-operations— build-time: pliki.graphqlz operacjami, schemat GraphQL- Template (storefront) — lokalna generacja hooków React Query i server helpers via
codegen.ts
SDK obejmuje wyłącznie publiczne Storefront API (GraphQL) — operacje administracyjne panelu sklepu nie są częścią tego pakietu.
Hierarchia warstw
Architektura opiera się na trzech warstwach, od najbardziej fundamentalnej do najbardziej wyspecjalizowanej:
+--------------------------------------------------------------+
| 3. Custom operations (graphql/ w szablonie) |
| Własne zapytania GraphQL rozszerzające standardowe API |
+--------------------------------------------------------------+
| 2. Local codegen (codegen.ts → hooks.ts / server.ts) |
| Template generuje hooki React Query i server helpers |
+--------------------------------------------------------------+
| 1. Storefront SDK (transport, middleware, clients, stores, |
| format, auth handlers, helpers) — 0 deps core + React |
+--------------------------------------------------------------+
Warstwa 1: Storefront SDK (runtime)
Pakiet @doswiftly/storefront-sdk dostarcza fundament runtime jako jeden pakiet z wieloma export paths:
| Export path | Zawartość | Dependencies |
|---|---|---|
@doswiftly/storefront-sdk | Transport: createStorefrontClient, middleware (auth, currency, language, botProtection, retry, timeout, errors), CartClient, AuthClient, StorefrontError. Bot protection: createBotProtectionManager, FallbackBotProtectionManager. Format: formatPrice, formatPriceRange, formatAmount, formatDate, formatDateTime, formatNumber, formatPercentage, getCurrencySymbol. Helpers: sanitizeHtml, normalizeConnection, assertNoUserErrors. Auth: AUTH_COOKIE_NAME, AUTH_COOKIE_DEFAULTS, createSetTokenHandler, createClearTokenHandler, createWhoamiHandler, createAuthTokenClient, trustedForwardedHostValidator, originAllowlistValidator. Language: LANGUAGE_COOKIE_NAME, LANGUAGE_COOKIE_MAX_AGE, LANGUAGE_HEADER_NAME. Currency: CURRENCY_COOKIE_NAME, CURRENCY_COOKIE_MAX_AGE, CURRENCY_HEADER_NAME. Cart: CART_COOKIE_NAME, CART_COOKIE_MAX_AGE. Routes: matchesRoute. Image: thumbHashToDataURL. | 0 |
@doswiftly/storefront-sdk/react | StorefrontProvider, StorefrontClientProvider, CurrencyProvider, LanguageProvider, CartProvider. Hooks: useAuth(), useLogin(), useLogout(), useRefreshToken(), useCartManager(), useCart(cartId), useCurrency(), useStorefrontClient(), useBotProtection(), useHydrated(), useDebouncedValue(). Format hooks (Context-driven): useFormatPrice(), useFormatAmount(), useFormatPriceRange(), useFormatDate(), useFormatDateTime(), useFormatNumber(), useGetCurrencySymbol(). Stores: useAuthStore(), useAuthStoreApi(), useAuthHydrated(), useCurrencyStore(), useCurrencyStoreApi(), useLanguageStore(), useLanguageStoreApi(). Cart (DI): createCartStore(), useCartStore(), useCartStoreApi(). Selectors: selectCurrency, selectLanguage, selectCartId, etc. createStoreContext(). Headless components: <Money>, <Image>, <PriceDisplay>, <AddToCartButton>, więcej. | react, zustand |
@doswiftly/storefront-sdk/react/server | getStorefrontClient(), readCurrencyCookie(), readCartIdCookie() | react |
@doswiftly/storefront-sdk/cache | cacheLong(), cacheShort(), cacheNone(), cachePrivate(), cacheCustom() | 0 |
SDK nie eksportuje pre-built hooków React Query ani server helpers — te są generowane lokalnie w szablonie. SDK nie używa codegen wewnętrznie — operacje GraphQL i typy są ręczne.
Middleware pipeline
Transport factory createStorefrontClient obsługuje composable middleware pipeline (wzorzec reduceRight):
import {
createStorefrontClient,
authMiddleware,
currencyMiddleware,
retryMiddleware,
timeoutMiddleware,
errorMiddleware,
} from '@doswiftly/storefront-sdk';
// Middleware pipeline jest konfigurowany wewnątrz StorefrontProvider.
// Nie twórz StorefrontClient ręcznie w komponentach — użyj useStorefrontClient() z SDK.
const client = createStorefrontClient({
apiUrl: process.env.NEXT_PUBLIC_API_URL!,
shopSlug: process.env.NEXT_PUBLIC_SHOP_SLUG!,
middleware: [
authMiddleware(() => getToken()),
currencyMiddleware(() => getCurrency()),
retryMiddleware({ maxRetries: 2 }),
timeoutMiddleware({ timeout: 5000 }),
errorMiddleware(), // ZAWSZE OSTATNI
],
});
Kolejność middleware: auth → currency → language → bot-protection → [custom] → retry → timeout → errors (LAST).
Bot protection middleware jest automatycznie dodawany przez StorefrontProvider gdy backend zwraca shop.botProtection w shop query. Nie wymaga konfiguracji w szablonie.
Funkcje transportu:
- Lazy pipeline — middleware chain kompilowany przy pierwszym zapytaniu
- Request deduplication — identyczne zapytania w tym samym ticku = 1 fetch
- TypedDocumentString — wsparcie dla
client-presetcodegen - Custom fetch — injection custom fetch (polyfill, test mocks, edge)
CartClient i AuthClient
SDK dostarcza plain async API clients (bez React, bez React Query):
import { CartClient, AuthClient } from '@doswiftly/storefront-sdk';
const cartClient = new CartClient(client);
const cart = await cartClient.create();
await cartClient.addItems(cart.id, [{ variantId: 'var-123', quantity: 1 }]);
const authClient = new AuthClient(client);
const { accessToken } = await authClient.login('user@example.com', 'password');
CartClient ma 22 metody pokrywające pełny cart + checkout lifecycle: merchandise (get, create, addItems, updateItems, removeItems), buyer identity, discount codes, fulfillment (setShippingAddress, setBillingAddress, selectShippingMethod), payment (selectPaymentMethod, createPayment), gift cards, completion (complete), guest order recovery (getOrderByToken), checkout discovery (getAvailableShippingMethods, getAvailablePaymentMethods, validateDiscountCode). AuthClient ma 6 metod: login, logout, register, refreshToken, getCustomer(): Promise<Customer | null>, getAddresses(): Promise<MailingAddress[] | null>.
Mutacje (addItems, login itd.) auto-throwują StorefrontError z firstError.message (backend-translated per Accept-Language). Query metody zwracają T | null dla resource-not-found, lub raw payload z userErrors[] envelope dla business conditions (industry-standard pattern). Caller branchuje na result.userErrors[].code z backend-translated message.
Typy checkoutu
Pełne typy checkoutowe eksportowane z barrel @doswiftly/storefront-sdk — bez importu z subpath:
import type {
// Cart + nested output
Cart, CartLine, CartCost, CartLineCost,
CartBuyerIdentity, CartDiscountCode, CartDiscountAllocation,
CartShippingMethod, CartAppliedGiftCard, CartSelectedPaymentMethod,
Order, PaymentSession,
// Shipping / payment discovery
PaymentMethod, AvailablePaymentMethods,
AvailableShippingMethod, AvailableShippingMethodsPayload,
DeliveryEstimate, ShippingCarrier, FreeShippingProgress, PickupPoint,
// Input types
CartLineInput, CartLineUpdateInput,
CartCreateInput, CartBuyerIdentityInput,
CartAttributeInput, CartAttributeSelectionInput,
CartAddressInput, ShippingAddressInput, PickupPointInput,
CartSetShippingAddressInput, CartSetBillingAddressInput,
CartSelectShippingMethodInput, CartSelectPaymentMethodInput,
CartApplyGiftCardInput, CartRemoveGiftCardInput,
CartUpdateGiftCardRecipientInput,
CartCompleteInput, PaymentCreateInput,
// Enums
CartStatus, DeliveryType, PaymentMethodType, PaymentInitiationFlow,
CurrencyCode, CountryCode, LanguageCode, ProductTypeEnum,
StorefrontOrderStatus, OrderPaymentStatus, OrderFulfillmentStatus,
AttributeType, AttributeFillingMode, AttributeBillingMode, AttributeOptionSurchargeType,
// Discount validation preview
DiscountValidationResult, DiscountInfo, DiscountValidationError,
DiscountErrorCode, DiscountApplicationType,
} from '@doswiftly/storefront-sdk';
Wszystkie typy są aliasami wygenerowanych fragmentów GraphQL (codegen z operacji SDK przy pnpm codegen). Każde pole ma JSDoc opis widoczny w hoverze IDE — propagowany przez Pick<> z bazowych typów schematu.
Format utilities
Zestaw pure functions do formatowania cen, dat i liczb — 0 deps, działa w Node.js, Edge, Deno, Bun. Każdy formatter używa Intl.NumberFormat / Intl.DateTimeFormat runtime — wszystkie ISO 4217 waluty są wspierane, symbol jest locale-correct ('zł' w pl-PL, 'PLN' w en-US, '€' w de-DE).
import {
formatPrice, formatPriceRange, formatAmount,
formatDate, formatDateTime, formatNumber, formatPercentage,
getCurrencySymbol,
} from '@doswiftly/storefront-sdk';
formatPrice({ amount: '99.99', currencyCode: 'USD' }, 'en-US'); // "$99.99"
formatPriceRange(minPrice, maxPrice, 'pl-PL'); // "10,00 zł - 50,00 zł"
formatAmount('115.20', 'EUR', 'de-DE'); // "115,20 €"
formatDate(new Date(), 'pl-PL'); // "9 gru 2025"
formatPercentage(0.15); // "15%"
Locale handling. Każdy formatter cen i symboli (formatPrice, formatPriceRange, formatAmount, getCurrencySymbol) przyjmuje opcjonalny locale: string. Gdy pominięty, używany jest runtime default (Intl.NumberFormat().resolvedOptions().locale — w przeglądarce resolves z navigator.language, w Node z LANG / system settings). Formattery dat i liczb (formatDate, formatDateTime, formatNumber) wymagają explicit locale — typowy storefront z i18n czyta useLocale() (Client) lub getLocale() (Server) z next-intl.
Precyzja Money. Money.amount jest przekazywany do Intl.NumberFormat jako string (bez parseFloat) — pełna precyzja Decimal scalar zachowana, w tym dla walut z non-2-digit subunits (JPY/KRW, BHD/JOD, ISK).
Convenience hooks dla React. W obrębie <StorefrontProvider> użyj Context-driven hooków z @doswiftly/storefront-sdk/react, które same czytają język z useLanguageStore i forwardują do core formatters:
'use client';
import { useFormatPrice, useFormatDate } from '@doswiftly/storefront-sdk/react';
function ProductCard({ product }) {
const formatPrice = useFormatPrice();
const formatDate = useFormatDate();
return (
<>
<span>{formatPrice(product.priceRange.minVariantPrice)}</span>
<span>{formatDate(product.createdAt)}</span>
</>
);
}
Hooki zwracają memoised funkcje — bezpieczne do użycia w render bez useMemo. Każdy akceptuje opcjonalny final localeOverride argument, który wygrywa z store value (dla per-call override w jednym elemencie UI).
sanitizeHtml
Defense-in-depth HTML sanitizer — stripuje <script>, event handlery, javascript: URL-e:
import { sanitizeHtml } from '@doswiftly/storefront-sdk';
const safe = sanitizeHtml(userHtml);
normalizeConnection
Konwertuje Relay-style GraphQL connection na płaską tablicę:
import { normalizeConnection } from '@doswiftly/storefront-sdk';
const { items, pageInfo, totalCount } = normalizeConnection(data.products);
Auth cookie config (kontrakt platformy)
Stałe definiujące kontrakt auth cookie między SDK a backendem:
import { AUTH_COOKIE_NAME, AUTH_COOKIE_DEFAULTS } from '@doswiftly/storefront-sdk';
// AUTH_COOKIE_NAME = 'customerAccessToken'
// AUTH_COOKIE_DEFAULTS = { name, path, sameSite, httpOnly, secure, maxAge }
Auth cookie handlers (API route factories)
Fabryki handlerów dla API routes — pure Web API (Request/Response), 0 deps:
// app/api/auth/set-token/route.ts (2 linie!)
import { createSetTokenHandler } from '@doswiftly/storefront-sdk';
export const POST = createSetTokenHandler();
// app/api/auth/clear-token/route.ts
import { createClearTokenHandler } from '@doswiftly/storefront-sdk';
export const POST = createClearTokenHandler();
Auth token client (client-side)
Client-side fetch helpery do zarządzania tokenem auth via API routes:
import { createAuthTokenClient } from '@doswiftly/storefront-sdk';
const { setToken, clearToken } = createAuthTokenClient();
await setToken(accessToken); // POST /api/auth/set-token
await clearToken(); // POST /api/auth/clear-token
matchesRoute
Utility do dopasowywania ścieżek (exact + prefix matching):
import { matchesRoute } from '@doswiftly/storefront-sdk';
matchesRoute('/account/orders', ['/account']); // true
matchesRoute('/products', ['/account']); // false
StorefrontError
Zunifikowana klasa błędów ze structured data:
import { StorefrontError, ErrorCodes } from '@doswiftly/storefront-sdk';
try {
await client.query(ProductQuery, { handle: 'missing' });
} catch (err) {
if (err instanceof StorefrontError) {
err.code; // 'GRAPHQL_ERROR' | 'NETWORK_ERROR' | 'TIMEOUT' | 'USER_ERROR' | ...
err.status; // HTTP status (0 for network errors)
err.graphqlErrors; // GraphQL-level errors
err.userErrors; // Field-level validation errors
err.hasUserErrors; // boolean
err.isNetworkError; // boolean
err.isTimeout; // boolean
err.isNonJsonResponse; // boolean
}
}
NON_JSON_RESPONSE — odpowiedź nie sparsowała się jako JSON: zamiast danych GraphQL przyszła strona HTML (np. challenge ochrony przed botami albo strona błędu proxy/load-balancera) z realnym kodem HTTP — to NIE jest błąd sieciowy, odpowiedź dotarła. err.cause niesie wtedy diagnostykę: contentType (nagłówek Content-Type, null gdy brak), cfRay (nagłówek cf-ray, gdy obecny — przydatny do korelacji ze wsparciem) i bodySnippet (pierwsze ~200 znaków treści odpowiedzi). Statusy >= 500 są ponawiane jak każdy inny błąd 5xx (przejściowy błąd origin/load-balancera); statusy < 500 NIE są ponawiane (deterministyczny blok/challenge — ponowienie trafiłoby w tę samą stronę).
Throttlowane operacje
Część mutacji ma zaostrzony limit zapytań ze względów bezpieczeństwa (per IP+shop): cartComplete i paymentCreate — 5/min, cartApplyGiftCard — 5/min, cartDiscountCodesUpdate — 10/min, orderByToken — 5/min. Po przekroczeniu limitu backend zwraca StorefrontError z err.code === 'GRAPHQL_ERROR' — sygnał throttlingu jest dostępny w err.graphqlErrors[0]?.extensions?.code === 'THROTTLED' (nie przez err.code). Zastosuj exponential backoff przed ponowną próbą.
Warstwa 2: Lokalne hooki szablonu (codegen-first)
Template storefront generuje hooki lokalnie przez codegen.ts, który przetwarza operacje z @doswiftly/storefront-operations:
lib/graphql/hooks.ts— React Query hooki dla komponentów klienckich ("use client")lib/graphql/server.ts— async helpery dla Server Components z Reactcache()lib/graphql/client.ts—useExecute()hook używającyuseStorefrontClient()z SDK Context
// Hooki są lokalne w szablonie, nie z SDK
import { useProducts } from '@/lib/graphql/hooks';
const { data, isLoading } = useProducts({ first: 20, sortKey: 'price-low-to-high' });
// data.products to płaska tablica, klucz cache zawiera walutę
Nazewnictwo hooków: useProducts (nie useProductsQuery), useCartCreate (nie useCartCreateMutation).
Warstwa 3: Custom operations
Możesz rozszerzać API o własne zapytania GraphQL. Dodaj pliki .graphql w katalogu graphql/ — codegen wygeneruje *Document constant i typy.
Oprócz custom queries, możesz definiować kolokowane fragmenty (wzorzec kolokacji) — pliki .fragment.graphql umieszczone obok komponentu w components/. Fragment types importuj z @/lib/graphql/fragments:
import type { ProductCardFields } from '@/lib/graphql/fragments';
Custom queries:
# graphql/custom-queries.graphql
query MyCustomProduct($handle: String!) {
product(handle: $handle) {
id
title
myCustomField
}
}
Po pnpm run codegen, użyj wygenerowanego Document:
// Server Component
import { fetchMyCustomProduct } from '@/lib/graphql/server'; // drift-allow: codegen generuje fetch<NazwaTwojegoQuery>
const data = await fetchMyCustomProduct({ handle: 'koszulka' });
// Client Component — ad-hoc call (useExecute() requires component scope)
import { useExecute } from '@/lib/graphql/client';
import { MyCustomProductDocument, type MyCustomProductQuery } from '@/generated/graphql';
function MyComponent() {
const execute = useExecute();
// w useQuery, useCallback, event handler itp.
const data = await execute<MyCustomProductQuery>(MyCustomProductDocument.toString(), { handle: 'koszulka' });
}
Przepływ danych
storefront-operations storefront-sdk Template (codegen.ts) Storefront
(.graphql files) --> (transport + --> hooks.ts (React Query) --> "use client"
middleware + --> server.ts (cache()) --> Server Components
clients +
stores)
@doswiftly/storefront-operations— definiuje operacje GraphQL jako pliki.graphql@doswiftly/storefront-sdk— eksportuje runtime (transport, middleware, clients, providers, stores, cache)- Template
codegen.ts— generuje hooki i server helpers z operacji - Storefront importuje z
@/lib/graphql/hookslub rozszerza o własne operacje
Kluczowe domeny
Template generuje hooki dla następujących domen e-commerce:
| Domena | Queries | Mutations |
|---|---|---|
| Produkty | useProducts, useProduct, useProductSearch, useProductFilters | -- |
| Kolekcje | useCollections, useCollection | -- |
| Kategorie | useCategories, useCategory | -- |
| Koszyk | useCart (availability/walidacja kodu przez CartClient / useCartManager w SDK) | useCartCreate, useCartLinesAdd, useCartLinesUpdate, useCartLinesRemove, useCartDiscountCodesUpdate; pełny checkout lifecycle (buyer identity, adresy, shipping/payment, gift cards, complete, payment) przez useCartManager (SDK) |
| Klienci | useCustomer | useCustomerSignup, useCustomerLogin, useCustomerLogout, useCustomerRefreshToken, useCustomerUpdate, useCustomerAddAddress, useCustomerUpdateAddress, useCustomerRemoveAddress, useCustomerSetDefaultAddress, useCustomerRequestPasswordReset, useCustomerResetPassword |
| Przesyłki | useShipment, useShipmentByTrackingNumber | -- |
| Zwroty | useReturn, useReturnsByOrder, useReturnReasons | useReturnCreate, useReturnCancel |
| Karty podarunkowe | useGiftCard, useGiftCardValidate | -- |
| Program lojalnościowy | useLoyaltyMember, useLoyaltyTiers, useLoyaltyRewards, useLoyaltyTransactions, useLoyaltySettings, useEstimatePoints, useReferralStats | useRedeemLoyaltyReward, useGenerateReferralCode |
| Sklep | useShop | -- |
| Lista życzeń | useWishlists, useWishlist | useWishlistCreate, useWishlistAddItem, useWishlistRemoveItem, useWishlistDelete |
| Recenzje | useProductReviews, useReviewStats | useCreateReview, useVoteOnReview |
| Rekomendacje | useProductRecommendations, useRelatedProducts | -- |
| Blog | useBlogPosts, useBlogPost, useBlogCategories, useBlogTags | -- |
| Waluty | useCurrencies, useExchangeRate, useCurrencyConversion, useShopCurrencyConfig, usePaymentCurrencies | -- |
| Tłumaczenia | useAvailableLanguages, useTranslations | -- |
| Ceny B2B | useCustomerGroups, useB2BPriceDisplay | -- |
Dodatkowo SDK dostarcza CartClient i AuthClient jako plain async API (bez React Query) — dla scenariuszy edge, CLI, custom integrations.
Konfiguracja
Kontrakt SDK
createStorefrontClient i getStorefrontClient przyjmują explicit config — apiUrl i shopSlug są wymagane jako argumenty. SDK nie czyta process.env, nie sniffuje hostname'a, nie inspektuje request headers. Headery wstrzykiwane przez infrastrukturę DoSwiftly (X-Shop-Slug, X-Original-Host przez dispatch worker) są informational dla BFF route handlers i backend audit — SDK ich nie konsumuje przy budowaniu request'u.
Dlatego template Next.js scaffoldowany przez doswiftly init ma jeden punkt konfiguracji per environment: wartości apiUrl i shopSlug przekazywane do <StorefrontProvider config={{ apiUrl, shopSlug }}> w app/layout.tsx.
Kolejność rozwiązywania konfiguracji
Standardowy template czyta config w tej kolejności (highest priority first):
doswiftly.config.ts— typed plik commitowany do repo storefrontu, źródło prawdy dla CLI (doswiftly dev,doswiftly deploy) i runtime'u Next.js- Env vars — kanon
DOSWIFTLY_API_URL/DOSWIFTLY_SHOP_SLUG(dowolny framework) z aliasamiNEXT_PUBLIC_*o tych samych wartościach, inlinowanymi przez Next.js w build time - Defaults —
http://localhost:8000,demo-shop(tylko dla bootstrap nowego repo, NIE rekomendowane w produkcji)
Zmienne środowiskowe
| Kanon (dowolny framework) | Alias Next.js | Opis | Plik |
|---|---|---|---|
DOSWIFTLY_API_URL | NEXT_PUBLIC_API_URL | URL API backendu DoSwiftly | .env.local (commitowany) |
DOSWIFTLY_SHOP_SLUG | NEXT_PUBLIC_SHOP_SLUG | Slug sklepu dla multi-tenancy | .env.local (commitowany) |
Pełny kontrakt (w tym DOSWIFTLY_DEPLOYMENT_COMMIT i mapowanie na konwencje innych frameworków): Zmienne środowiskowe.
Plik .env.local w template scaffoldowanym przez doswiftly init jest commitowany (NIE w .gitignore) — wartości są dev-friendly, niewrażliwe (backend i tak waliduje shop slug w runtime). Sekrety (klucze API, webhook secrets) nie są wymagane po stronie storefrontu — całość kontraktu z backendem przechodzi przez X-Shop-Slug header i Bearer JWT klienta.
Plik doswiftly.config.ts
Główny plik konfiguracyjny storefrontu. Tworzony automatycznie przez CLI (@doswiftly/cli) podczas scaffoldingu projektu.
import type { DoswiftlyConfig } from './doswiftly.config';
const config: DoswiftlyConfig = {
// Identyfikator sklepu (wymagany dla Storefront API)
shop: {
slug: 'moj-sklep',
},
// Metadane projektu (kontekst CLI)
project: {
name: 'Mój Sklep',
},
// URL API backendu
api: {
url: 'https://api.doswiftly.pl',
},
// Opcjonalne ustawienia dev
dev: {
port: 3000,
openBrowser: true,
},
};
export default config;
Nagłówki HTTP
Każde żądanie do Storefront API zawiera następujące nagłówki, automatycznie wstrzykiwane przez middleware pipeline SDK:
| Nagłówek | Opis | Middleware |
|---|---|---|
X-Shop-Slug | Identyfikator sklepu dla routingu multi-tenant | transport (domyślnie) |
X-Preferred-Currency | Preferowana waluta klienta | currencyMiddleware |
Authorization | Token uwierzytelnienia klienta (Bearer) | authMiddleware |
X-Bot-Protection-Token | Token weryfikacji bot protection (chronione mutacje) | botProtectionMiddleware |
X-Operation-Name | Nazwa operacji GraphQL (debugging) | transport (auto-extract) |
Cache strategies
Strategie cache to funkcje z opcjonalnymi overrides:
import { cacheLong, cacheShort, cacheNone, cachePrivate } from '@doswiftly/storefront-sdk/cache';
// Domyślne wartości
cacheLong() // 1h + 23h stale-while-revalidate
cacheShort() // 1s + 9s swr
cacheNone() // no-store
cachePrivate() // private, 1s + 9s swr
// Z Next.js revalidation tags
cacheLong({ tags: ['product', slug] })
React adapter (/react)
Oprócz providers i store hooks, SDK react eksportuje:
useHydrated
SSR hydration guard — false podczas SSR i pierwszego renderingu, true po hydration:
import { useHydrated } from '@doswiftly/storefront-sdk/react';
const isHydrated = useHydrated();
// Użyj do guardowania browser-only state (localStorage, cookies, window)
useDebouncedValue
Standard debounce hook:
import { useDebouncedValue } from '@doswiftly/storefront-sdk/react';
const debouncedQuery = useDebouncedValue(query, 300);
createStoreContext
Fabryka Context+Zustand — eliminuje module-level singleton (Turbopack module duplication bug):
import { createStoreContext } from '@doswiftly/storefront-sdk/react';
import { createStore } from 'zustand/vanilla';
const { Provider: CartProvider, useStore: useCartStore, useApi: useCartStoreApi } =
createStoreContext<CartState>('CartStore');
// W layout:
const cartStore = useRef(createCartStore()).current;
<CartProvider store={cartStore}>{children}</CartProvider>
// W komponentach:
const isOpen = useCartStore((s) => s.isOpen);
const api = useCartStoreApi(); // dla .getState() w callbackach
Szybki start
// Server Component — import z lokalnych server helpers
import { fetchProducts } from '@/lib/graphql/server';
export default async function ProductsPage() {
const { products, pageInfo } = await fetchProducts({ first: 20 });
return (
<div>
{products.map(product => (
<div key={product.id}>{product.title}</div>
))}
</div>
);
}
// Client Component — import z lokalnych hooków React Query
'use client';
import { useProducts, useCartLinesAdd } from '@/lib/graphql/hooks';
export function ProductList() {
const { data, isLoading } = useProducts({ first: 10 });
const addToCart = useCartLinesAdd();
if (isLoading) return <div>Ładowanie...</div>;
return (
<div>
{data?.products.map(product => (
<button
key={product.id}
onClick={() => addToCart.mutateAsync({
id: 'cart-123',
// variants to Relay Connection (.nodes). Wymaga fragmentu z wariantami —
// domyślny ProductCard ich nie selektuje. Dla listy bez wariantów
// dociągnij produkt przez useProduct(handle) i czytaj variants.nodes[0].
lines: [{ variantId: product.variants.nodes[0].id, quantity: 1 }]
})}
>
{product.title}
</button>
))}
</div>
);
}
Product.variants to Relay Connection — odczyt przez .nodes. Lista produktów (useProducts) zwykle używa lekkiego fragmentu bez wariantów, więc product.variants.nodes[0] może być niedostępne. Gdy potrzebujesz add-to-cart z poziomu listy, dociągnij produkt przez useProduct(handle) z fragmentem zawierającym variants albo przenieś wybór wariantu na stronę produktu.
Następne kroki
- Produkty — Wyświetlanie i wyszukiwanie produktów
- Koszyk — Zarządzanie koszykiem zakupów
- SDK — Checkout — pełny przepływ finalizacji zamówienia
- Autoryzacja klienta — Rejestracja i logowanie
- Konto klienta — Profil, adresy, historia zamówień
- Więcej funkcji — Kolekcje, karty podarunkowe, program lojalnościowy