Menu
Examples
Commerce
A full Kovo storefront — product grid, cart badge, and order history — running live next to the authored components, queries, and derived optimism that drive it.
Live appOpen in new tab ↗
/** @jsxImportSource @kovojs/server */
import { component, FieldError, form, FormError } from '@kovojs/core';
import { Badge } from '@kovojs/ui/badge';
import { Button } from '@kovojs/ui/button';
import { Card } from '@kovojs/ui/card';
import { tokens } from '@kovojs/style';
import * as style from '@kovojs/style';
import { addToCart, type ProductGridResult } from '../domain.js';
import { productGridQuery } from '../queries.js';
const addToCartForm = form('cart/add');
const productGridStyles = style.create({
errorText: {
color: tokens.sys.color.error,
fontSize: 14,
},
field: {
backgroundColor: tokens.sys.color.surfaceContainerLowest,
borderColor: tokens.sys.color.outline,
borderRadius: tokens.sys.shape.cornerMedium,
borderStyle: 'solid',
borderWidth: 1,
boxSizing: 'border-box',
color: tokens.sys.color.onSurface,
paddingBlock: 6,
paddingInline: 10,
},
formLabel: {
color: tokens.sys.color.onSurfaceVariant,
display: 'grid',
fontSize: 12,
fontWeight: 500,
gap: 4,
},
link: {
color: tokens.sys.color.primary,
fontSize: 14,
fontWeight: 500,
textDecoration: 'none',
},
panelError: {
backgroundColor: tokens.sys.color.errorContainer,
borderColor: tokens.sys.color.error,
borderRadius: tokens.sys.shape.cornerMedium,
borderStyle: 'solid',
borderWidth: 1,
color: tokens.sys.color.onErrorContainer,
fontSize: 14,
padding: 16,
},
productEmoji: {
backgroundColor: tokens.sys.color.surfaceContainer,
borderRadius: tokens.sys.shape.cornerMedium,
display: 'grid',
fontSize: 24,
height: 48,
placeItems: 'center',
width: 48,
},
productForm: {
alignItems: 'end',
display: 'flex',
flexWrap: 'wrap',
gap: 8,
},
row: {
alignItems: 'center',
display: 'flex',
gap: 16,
},
rowBetween: {
alignItems: 'center',
display: 'flex',
justifyContent: 'space-between',
},
stack: {
display: 'grid',
gap: 16,
},
stackSm: {
display: 'grid',
gap: 4,
},
tabularStrong: {
fontVariantNumeric: 'tabular-nums',
fontWeight: 600,
},
title: {
color: tokens.sys.color.onSurface,
fontWeight: 600,
letterSpacing: 0,
margin: 0,
},
});
export const productGridStyleCss = style.emitAtomicCss(
Object.values(productGridStyles).flatMap((entry) => entry.__rules ?? []),
);
export interface OutOfStockFailure {
code: 'OUT_OF_STOCK';
payload: { availableQuantity: number };
}
export const ProductGrid = component({
errorBoundary: {
fallback: renderProductGridError,
target: 'product-grid',
},
mutations: { addToCart: addToCartForm },
queries: { productGrid: productGridQuery },
render: ({ productGrid }: { productGrid: ProductGridResult }) => {
const { nextCursor } = productGrid;
return (
<section data-page-cursor={nextCursor ?? ''}>{renderProductGridItems(productGrid)}</section>
);
},
});
export function ProductGridError(): string {
return renderProductGridError();
}
function renderProductGridError(): string {
return (
<section style={productGridStyles.panelError}>Products are temporarily unavailable.</section>
);
}
export function renderProductGridItems(result: ProductGridResult): string {
const cards = result.items.map((item) => renderProductCard(item));
const cursor = result.nextCursor;
return (
<>
{cards}
{cursor ? (
<a style={productGridStyles.link} href={`/products?after=${cursor}`} data-cursor={cursor}>
More
</a>
) : (
''
)}
</>
);
}
export interface ProductItem {
id: string;
name: string;
category: string;
emoji: string;
stock: number;
unitPrice: number;
}
/** Format an integer cent amount as `$25.99`. */
export function priceLabel(cents: number): string {
return `$${(cents / 100).toFixed(2)}`;
}
/** Low stock reads as a warning badge; healthy stock as success. */
function stockBadge(stock: number): string {
if (stock === 0) return Badge.definition.render({ variant: 'warning', children: 'Sold out' });
if (stock <= 2)
return Badge.definition.render({ variant: 'warning', children: `Only ${stock} left` });
return Badge.definition.render({ variant: 'success', children: `${stock} in stock` });
}
function renderProductCard(item: ProductItem): string {
const body = (
<div style={productGridStyles.stack}>
<div style={productGridStyles.row}>
<span style={productGridStyles.productEmoji}>{item.emoji}</span>
<div style={productGridStyles.stackSm}>
<h2 style={productGridStyles.title}>{item.name}</h2>
{Badge.definition.render({ variant: 'neutral', children: item.category })}
</div>
</div>
<div style={productGridStyles.rowBetween}>
<span style={productGridStyles.tabularStrong}>{priceLabel(item.unitPrice)}</span>
{stockBadge(item.stock)}
</div>
{renderAddToCartForm(item)}
</div>
);
return <article kovo-key={item.id}>{Card.definition.render({ children: body })}</article>;
}
export function renderAddToCartForm(item: { id: string; stock: number }): string {
const soldOut = item.stock === 0;
return (
<form enhance mutation={addToCart} key={item.id} style={productGridStyles.productForm}>
<input type="hidden" name="productId" value={item.id} />
<label style={productGridStyles.formLabel}>
<span>Qty</span>
<input
style={productGridStyles.field}
name="quantity"
type="number"
min="1"
max={item.stock}
value="1"
/>
<FieldError name="quantity" style={productGridStyles.errorText} />
</label>
{Button.definition.render({
children: soldOut ? 'Sold out' : 'Add to cart',
disabled: soldOut,
type: 'submit',
variant: 'primary',
})}
<FormError
code="OUT_OF_STOCK"
style={productGridStyles.errorText}
message={(failure: OutOfStockFailure) =>
`Only ${failure.payload.availableQuantity} available.`
}
/>
</form>
);
}
/** @jsxImportSource @kovojs/server */
import { component } from '@kovojs/core';
import { t } from '@kovojs/server';
import { tokens } from '@kovojs/style';
import * as style from '@kovojs/style';
import { commerceMessages, type CartQueryResult } from '../domain.js';
import { cartQuery } from '../queries.js';
const cartBadgeStyles = style.create({
badge: {
alignItems: 'center',
backgroundColor: tokens.sys.color.surfaceContainerLowest,
borderColor: tokens.sys.color.outlineVariant,
borderRadius: tokens.sys.shape.cornerMedium,
borderStyle: 'solid',
borderWidth: 1,
color: tokens.sys.color.onSurface,
display: 'inline-flex',
fontSize: 14,
fontWeight: 500,
gap: 8,
paddingBlock: 8,
paddingInline: 12,
},
count: {
alignItems: 'center',
backgroundColor: tokens.sys.color.primary,
borderRadius: tokens.sys.shape.cornerFull,
color: tokens.sys.color.onPrimary,
display: 'inline-flex',
fontSize: 12,
fontVariantNumeric: 'tabular-nums',
fontWeight: 600,
height: 20,
justifyContent: 'center',
minWidth: 20,
paddingInline: 6,
},
});
export const cartBadgeStyleCss = style.emitAtomicCss(
Object.values(cartBadgeStyles).flatMap((entry) => entry.__rules ?? []),
);
export const CartBadge = component({
queries: { cart: cartQuery },
render: ({ cart }: { cart: CartQueryResult }) => (
<cart-badge style={cartBadgeStyles.badge}>
<span>{t(commerceMessages, 'cartLabel')}</span>
<span style={cartBadgeStyles.count}>{cart.count}</span>
</cart-badge>
),
});
/** @jsxImportSource @kovojs/server */
import { component } from '@kovojs/core';
import { Badge } from '@kovojs/ui/badge';
import { tokens } from '@kovojs/style';
import * as style from '@kovojs/style';
import type { OrderHistoryResult } from '../domain.js';
import { orderHistoryQuery } from '../queries.js';
import { priceLabel } from './product-grid.js';
const orderHistoryStyles = style.create({
item: {
alignItems: 'center',
backgroundColor: tokens.sys.color.surfaceContainerLowest,
borderColor: tokens.sys.color.outlineVariant,
borderRadius: tokens.sys.shape.cornerMedium,
borderStyle: 'solid',
borderWidth: 1,
display: 'flex',
justifyContent: 'space-between',
paddingBlock: 12,
paddingInline: 16,
},
mutedText: {
color: tokens.sys.color.onSurfaceVariant,
fontSize: 12,
},
row: {
alignItems: 'center',
display: 'flex',
gap: 16,
},
stack: {
display: 'grid',
gap: 16,
},
stackSm: {
display: 'grid',
gap: 4,
},
tabularStrong: {
fontVariantNumeric: 'tabular-nums',
fontWeight: 600,
},
title: {
color: tokens.sys.color.onSurface,
fontWeight: 600,
letterSpacing: 0,
margin: 0,
},
});
export const orderHistoryStyleCss = style.emitAtomicCss(
Object.values(orderHistoryStyles).flatMap((entry) => entry.__rules ?? []),
);
export const OrderHistory = component({
queries: { orderHistory: orderHistoryQuery },
render: ({ orderHistory }: { orderHistory: OrderHistoryResult }) => (
<ol style={orderHistoryStyles.stack}>{renderOrderHistoryItems(orderHistory)}</ol>
),
});
interface OrderHistoryItem {
id: string;
productId: string;
qty: number;
total: number;
}
export function renderOrderHistoryItems(result: OrderHistoryResult): string {
return (
<>
{result.items.map((item: OrderHistoryItem) => (
<li kovo-key={item.id} style={orderHistoryStyles.item}>
<div style={orderHistoryStyles.stackSm}>
<span style={orderHistoryStyles.title}>{item.productId}</span>
<span style={orderHistoryStyles.mutedText}>Order {item.id}</span>
</div>
<div style={orderHistoryStyles.row}>
{Badge.definition.render({ children: `×${item.qty}`, variant: 'neutral' })}
<span style={orderHistoryStyles.tabularStrong}>{priceLabel(item.total)}</span>
</div>
</li>
))}
</>
);
}
import { guards, query, type QueryLoadContext } from '@kovojs/server';
import { eq, gt, sum } from 'drizzle-orm';
import { domain } from '@kovojs/server';
import type { CommerceDb } from './db.js';
import { cartItems, orders, products } from './schema.js';
export interface CartQueryResult {
count: number;
}
export interface ProductGridInput {
after?: string;
limit?: number;
}
export interface ProductGridResult {
items: {
id: string;
name: string;
category: string;
emoji: string;
stock: number;
unitPrice: number;
}[];
nextCursor: string | null;
}
export interface OrderHistoryResult {
items: { id: string; productId: string; qty: number; total: number; userId: string }[];
}
export interface CommerceQueryRequest {
db: CommerceDb;
// SECURITY (SECURITY_FINDINGS.md M9): order-history reads are per-user, so the
// query request must be able to carry the authenticated session whose user id
// scopes the rows. Cart/product reads remain global (no session needed).
session?: { id?: string; user?: { id?: string } | null } | null;
}
export const cart = domain('cart');
export const order = domain('order');
export const product = domain('product');
type CommerceQueryLoadContext = QueryLoadContext<CommerceQueryRequest> & {
db?: CommerceDb;
session?: CommerceQueryRequest['session'];
};
export const cartQuery = query('cart', {
async load(_input: unknown, context?: CommerceQueryLoadContext): Promise<CartQueryResult> {
const db = requireCommerceQueryDb(context);
const rows = await db.select({ value: sum(cartItems.qty) }).from(cartItems);
return { count: Number(rows[0]?.value ?? 0) };
},
reads: [cart],
});
export const productGridQuery = query('productGrid', {
async load(input: unknown, context?: CommerceQueryLoadContext): Promise<ProductGridResult> {
const db = requireCommerceQueryDb(context);
const { after, limit } = (input ?? {}) as ProductGridInput;
const pageSize = limit ?? 2;
const items = await db
.select({
id: products.id,
name: products.name,
category: products.category,
emoji: products.emoji,
stock: products.stock,
unitPrice: products.unitPrice,
})
.from(products)
.where(after ? gt(products.id, after) : undefined)
.orderBy(products.id)
.limit(pageSize);
const last = items.at(-1);
const more = last
? await db.select({ id: products.id }).from(products).where(gt(products.id, last.id)).limit(1)
: [];
const nextCursor = more.length > 0 ? (last?.id ?? null) : null;
return { items: items, nextCursor: nextCursor };
},
reads: [product],
});
export const orderHistoryQuery = query('orderHistory', {
// SECURITY (SECURITY_FINDINGS.md M9): order history is per-user, so this read must
// require an authenticated session — the endpoint guard rejects unauthenticated
// callers, and the `load` below additionally scopes the rowset to that user's id
// so no caller can ever observe another user's orders.
guard: guards.authed<CommerceQueryRequest>(),
async load(_input: unknown, context?: CommerceQueryLoadContext): Promise<OrderHistoryResult> {
const db = requireCommerceQueryDb(context);
const userId = requireCommerceQueryUserId(context);
// Orders are an append-only log. The user filter keeps the rowset scoped to
// the authenticated session.
const items = await db
.select({
id: orders.id,
productId: orders.productId,
qty: orders.qty,
total: orders.total,
userId: orders.userId,
})
.from(orders)
.where(eq(orders.userId, userId));
return { items: items };
},
// SPEC §9.1.1: the `items` collection is keyed by order `id` and scoped by the
// `order` domain, so an `order`-touching mutation that carries the changed
// order id ships only the new order row instead of the whole history.
// (Compiler-derived delta meta is the deferred zero-config piece; this
// declares it explicitly today.)
delta: [{ domain: 'order', key: 'id', path: 'items' }],
reads: [order],
});
export async function loadCartQuery(db: CommerceDb): Promise<CartQueryResult> {
return cartQuery.load(undefined, { db, request: { db } });
}
export async function loadProductGrid(
db: CommerceDb,
input: ProductGridInput = {},
): Promise<ProductGridResult> {
return productGridQuery.load(input, { db, request: { db } });
}
// SECURITY (SECURITY_FINDINGS.md M9): the order-history loader now requires the
// authenticated user id so it can scope the read; callers thread the session user
// id from the request.
export async function loadOrderHistory(
db: CommerceDb,
userId: string,
): Promise<OrderHistoryResult> {
const session = { id: userId, user: { id: userId } };
return orderHistoryQuery.load(undefined, { db, request: { db, session }, session });
}
function requireCommerceQueryDb(context?: CommerceQueryLoadContext): CommerceDb {
const db = context?.db ?? context?.request?.db;
if (!db) {
throw new Error('commerce query loaders require context.db or request.db');
}
return db;
}
function requireCommerceQueryUserId(context?: CommerceQueryLoadContext): string {
const userId = context?.session?.user?.id ?? context?.request?.session?.user?.id;
if (!userId) {
// Default-deny: order history is per-user and must never fall back to an
// unscoped read. A missing user id means the caller is unauthenticated.
throw new Error('orderHistory query requires an authenticated session user id');
}
return userId;
}
import '../live-targets.js';
// DO NOT EDIT — generated by @kovojs/drizzle derived optimism (SPEC.md §10.5).
// Override a transform by declaring it in the mutation module; deleting the
// override lets derivation take this pair back over, pair by pair (SPEC.md §10.4).
import { tempId, type OptimisticFor } from '@kovojs/runtime';
import type { addToCartForm } from '../../domain.js';
export const cartAddDerivedOptimistic = {
queue: 'cart',
transforms: {
cart: (current, $input) => {
const next = structuredClone(current);
next.count = (next.count ?? 0) + $input.quantity;
return next;
},
orderHistory: (current, $input) => {
const next = structuredClone(current);
next.items.push({ id: tempId(), productId: $input.productId, qty: $input.quantity, total: 0, userId: tempId() });
return next;
},
productGrid: (current, $input) => {
const next = structuredClone(current);
{
const target = next.items.find((entry) => entry.id === $input.productId);
if (target) {
target.stock = (target.stock - $input.quantity);
}
}
return next;
},
},
} satisfies OptimisticFor<typeof addToCartForm>;