8 headless packages,
each with clear responsibility
Import only what you need. Swap an adapter without rewriting the rest. Each package is a class or set of pure functions with injected dependencies.
@kmee/shop-types Foundation
Shared types (Product, ProductDetail, Cart, Brand, Category) and pure helpers. AuthProvider and Logger contracts. Imported by every other package.
Main exports
ProductProductDetailCartAuthProviderLoggerformatPriceapplyFilters import { formatPrice, applyFilters } from "@kmee/shop-types"
formatPrice({ value: 49.9 }, { locale: "pt-BR", currency: "BRL" })
// → "R$ 49,90"
applyFilters(products, { categories: ["Films"] }, { min: 10, max: 100 }) @kmee/shopinvader-client Backend client
Headless client for Odoo via Shopinvader. CartService, SalesService, AddressService, DeliveryCarriersService, LeadsService on top of OdooHttpClient with retry, timeout and injectable auth.
Main exports
OdooHttpClientCartServiceSalesServiceAddressServiceDeliveryCarriersServiceLeadsService import { OdooHttpClient, SalesService } from "@kmee/shopinvader-client"
const sales = new SalesService(
new OdooHttpClient({
baseUrl,
auth,
getExtraHeaders: async () => ({ apikey: SUPABASE_ANON_KEY }),
}),
)
const orders = await sales.list({ limit: 10 }) @kmee/elasticsearch-shop Search & catalog
Elasticsearch client for product listings, search and detail, categories and brands. ProductService collapses by model.name, supports tag filters, slug fallback by segment wildcards, and Levenshtein for picking the best hit.
Main exports
ProductServicesearchCategoriessearchBrands import { ProductService } from "@kmee/elasticsearch-shop"
const products = new ProductService(esConfig)
await products.search({ searchTerm: "vinyl", page: 1, pageSize: 12 })
await products.getByURLKey("oratape-mt80p")
await products.getRelatedProducts([{ name: "ThermoFlex Plus" }]) @kmee/supabase-auth-shop Auth adapter
Adapter implementing AuthProvider on top of @supabase/supabase-js. Plug it into an OdooHttpClient and the Supabase token flows through to Odoo automatically.
Main exports
createSupabaseAuthProvider import { createSupabaseAuthProvider } from "@kmee/supabase-auth-shop"
const auth = createSupabaseAuthProvider(supabase)
// auth implements AuthProvider — plug into OdooHttpClient.
await auth.getToken() // → access_token or null
await auth.isAuthenticated() // → false for anonymous by default @kmee/shop-storage Local persistence
Local persistence: CartStorage (UUID + cart data with expiry) and CartItemsMetadataStorage (min_order_qty per item). Configurable storage keys.
Main exports
CartStorageCartItemsMetadataStorage import { CartStorage } from "@kmee/shop-storage"
const cartStorage = new CartStorage({
cartKey: "myshop_cart",
uuidKey: "myshop_cart_uuid",
expiryDays: 60,
})
cartStorage.getOrCreateCartUuid()
cartStorage.saveCartData({ uuid, items: [], lastUpdated: 0 }) @kmee/admin-session Admin auth
Admin session: HttpOnly cookie JWT (jose), Next.js cookie helpers, runtime-agnostic getClientIp, and AdminRateLimiter with pluggable store (MemoryRateLimitStore by default; swap for Redis/KV in production).
Main exports
signAdminSessionJwtverifyAdminSessionTokenAdminRateLimitergetClientIp import {
signAdminSessionJwt,
AdminRateLimiter,
getClientIp,
} from "@kmee/admin-session"
const limiter = new AdminRateLimiter()
const ip = getClientIp(request.headers)
const decision = await limiter.assertAllowed(ip)
if (!decision.ok) return new Response("rate limited", { status: 429 })
const token = await signAdminSessionJwt({ secret: SECRET }) @kmee/shop-react React layer
React providers and hooks to wire it all together: <CartProvider>, <WishlistProvider>, <SupabaseAuthProvider>, useProductsPage, useCategoriesPage, useBrandsAndCategories.
Main exports
CartProviderWishlistProviderSupabaseAuthProvideruseCartuseWishlistuseAuthuseProductsPage import { useCart, useWishlist, useAuth } from "@kmee/shop-react"
const { cart, addToCart, removeItem, loading } = useCart()
const { items, add, isInWishlist } = useWishlist()
const { user, signInWithPassword, signOut } = useAuth() @kmee/shop-ui UI components
Opinionated components (Tailwind + Radix): primitives (Button, Badge, Skeleton, Sheet, DropdownMenu, Select, Input, Tabs, Table) and ~20 domain components. Theme via CSS vars + Tailwind preset compatible with shadcn.
Main exports
ProductCardAddToCartButtonCartDrawerProductGalleryProductFiltersHeroCarouselGlobalHeaderFooter // client tailwind.config.ts
import shopUiPreset from "@kmee/shop-ui/tailwind-preset"
export default {
presets: [shopUiPreset],
content: [
"./app/**/*.{ts,tsx}",
"./node_modules/@kmee/shop-ui/src/**/*.{ts,tsx}",
],
} How to compose
You only consume what makes sense for your app.
// lib/clients.ts — typical composition in a Next.js app
import { createClient } from "@supabase/supabase-js"
import { createSupabaseAuthProvider } from "@kmee/supabase-auth-shop"
import { OdooHttpClient, CartService, SalesService } from "@kmee/shopinvader-client"
import { ProductService } from "@kmee/elasticsearch-shop"
import { CartStorage } from "@kmee/shop-storage"
export const supabase = createClient(URL, ANON_KEY)
const auth = createSupabaseAuthProvider(supabase)
const odoo = new OdooHttpClient({ baseUrl, auth })
export const cart = new CartService(odoo)
export const sales = new SalesService(odoo)
export const products = new ProductService(esConfig)
export const cartStorage = new CartStorage()
Each class is a composition point. The OdooHttpClient receives the auth provider; CartService receives the client; CartProvider (React side) receives the service. Nothing tightly coupled.
Need a custom package?
Have a use case that doesn't fit the 8 libs? KMEE develops specific adapters (payment gateway, third-party ERP, marketplace).