Skip to Content

Cart

The cart lives entirely in a cookie while the customer shops — no database rows are written until checkout starts. Prices, tax, stock, and automatic promotions are always resolved server-side when the cart is read.

The codec lives in src/lib/server/cart-cookie.ts. The cookie is named cart and holds a compact JSON array of [version, [variantId, qty], ...]:

src/lib/server/cart-cookie.ts
export const CART_COOKIE = "cart"; // e.g. [1, [12, 2], [34, 1]] — version 1, variant 12 ×2, variant 34 ×1 export function parseCartCookie(raw: string | undefined | null): CartLine[]; export function serializeCartCookie(lines: CartLine[]): string;

The cookie is httpOnly, sameSite: "lax", and lives 30 days. It is intentionally unsigned — it only holds variant ids and quantities, both of which the customer controls through the UI anyway. Carts are capped at 50 different items and quantity 999 per line.

There is no cart merge on login. The cookie is the cart regardless of auth state — signing in just changes how prices (group pricing) and tax exemption are resolved when the cart is read.

Read Model

getCartView turns cookie lines into a priced view in a handful of batched queries. It never writes to the database.

src/lib/server/services/cart.ts
const view = await getCartView(cartLines, customerId, { skipPromotions }); // view: { lines, itemCount, subtotal, discount, taxTotal, total, isTaxExempt, promotions }

It resolves, per line: the featured image (variant image → product asset → featured/first variant image), group pricing for logged-in customers, available stock (variant stock minus active reservations), tax, and any qualifying automatic promotions.

Remote Functions

The UI talks to the cart through remote functions in src/lib/remote/cart.remote.ts. getCart is a query(); each mutating command() is valibot-validated, keyed by variantId, rewrites the cookie, then single-flight refreshes getCart:

src/lib/remote/cart.remote.ts
export const getCart = query(/* ... */); // → CartView export const addToCart = command(schema, /* ... */); // { variantId, quantity } export const setCartQuantity = command(schema, /* ... */); // quantity 0 removes the line export const removeCartLine = command(schema, /* ... */); // { variantId }

addToCart and setCartQuantity check available stock against reservations; addToCart errors when it would exceed stock, while setCartQuantity silently clamps to what is purchasable.

UI

src/lib/components/storefront/CartSheet.svelte renders getCart().current. The cartStore in src/lib/stores/cart.svelte.ts is now only sheet open/close state — the cart data itself comes from the remote query.

Checkout

The first database write happens when checkout starts, in orderService.startCheckout (src/lib/server/services/orders.ts). It snapshots the cookie lines into an order (active=true, state=created) with prices/names from getCartView and creates 15-minute stock reservations. The draft is identified by the checkout_token cookie. See Orders for the full lifecycle.

Last updated on