Technical documentation

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
@kmee/shop-types
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
@kmee/shopinvader-client
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
@kmee/elasticsearch-shop
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
@kmee/supabase-auth-shop
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
@kmee/shop-storage
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
@kmee/admin-session
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
@kmee/shop-react
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
@kmee/shop-ui
// 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).