Enforces full DDD conventions for TypeScript/Next.js: folder structure, layer boundaries, functional patterns, and ubiquitous language.
80
100%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Passed
No findings from the security scan
// modules/ordering/domain/money.ts
export type Money = { readonly amount: number; readonly currency: string };
export const createMoney = (amount: number, currency: string): Result<Money> => {
if (amount < 0) return err('Amount cannot be negative');
if (!currency.match(/^[A-Z]{3}$/)) return err('Currency must be a 3-letter ISO code');
return ok({ amount, currency });
};
export const addMoney = (a: Money, b: Money): Result<Money> => {
if (a.currency !== b.currency) return err('Cannot add amounts in different currencies');
return ok({ amount: a.amount + b.amount, currency: a.currency });
};// modules/ordering/domain/order.ts
export const generateOrderId = createId<'OrderId'>('OrderId');
export const createOrder = (props: { customerId: CustomerId; lines: readonly OrderLine[] }): Result<Order> => {
if (props.lines.length === 0) return err('An order must have at least one line');
return ok({ id: generateOrderId(), status: 'draft', ...props });
};
export const placeOrder = (order: Order): Result<[Order, OrderPlaced]> => {
if (order.status !== 'draft') return err('Only draft orders can be placed');
return ok([{ ...order, status: 'placed' }, createOrderPlacedEvent({ orderId: order.id })]);
};// modules/ordering/domain/repositories/orderRepository.ts
export type OrderRepository = {
findById: (id: OrderId) => Promise<Order | null>;
findByCustomerId: (customerId: CustomerId) => Promise<readonly Order[]>;
save: (order: Order, events: readonly DomainEvent<string, Record<string, unknown>>[]) => Promise<void>;
};// modules/ordering/infrastructure/persistence/orderRepository.ts
export const createOrderRepository = (): OrderRepository => ({
findById: async (id) => {
const row = await db.query.orders.findFirst({ where: eq(orders.id, id) });
return row ? OrderMapper.toDomain(row) : null;
},
save: async (order, events) => {
await db.transaction(async (tx) => {
await tx.insert(orders).values(OrderMapper.toPersistence(order)).onConflictDoUpdate(...);
await tx.insert(outbox).values(events.map(toOutboxRow));
});
},
});// app/api/orders/[id]/place/route.ts
export async function POST(req: NextRequest, { params }: { params: { id: string } }) {
const { placeOrder } = createOrderingHandlers();
const result = await placeOrder({ orderId: params.id, customerId: req.headers.get('x-customer-id') ?? '' });
if (!result.ok) return NextResponse.json({ error: result.error }, { status: 422 });
return NextResponse.json({ success: true }, { status: 200 });
}// modules/ordering/application/queries/getOrder.ts
export type OrderViewModel = {
id: string;
status: string;
lineCount: number;
totalDisplay: string; // Formatting happens here, not in the component
};
export const createGetOrderHandler =
(orders: OrderRepository) =>
async (id: string): Promise<OrderViewModel | null> => {
const order = await orders.findById(castId('OrderId')(id));
if (!order) return null;
return { id: order.id, status: order.status, lineCount: order.lines.length, totalDisplay: formatMoney(order.total) };
};// components/ordering/OrderSummary.tsx
export const OrderSummary = ({ order }: { order: OrderViewModel }) => (
<div><span>{order.status}</span><span>{order.totalDisplay}</span></div>
);skills
ddd-functional-nextjs