CtrlK
BlogDocsLog inGet started
Tessl Logo
PluginPublic

workout/ddd-functional-nextjs

Enforces full DDD conventions for TypeScript/Next.js: folder structure, layer boundaries, functional patterns, and ubiquitous language.

80

Quality

100%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide
SecuritybySnyk

Passed

No findings from the security scan

Overview
Quality
Evals
Security
Files

EXAMPLES.mdskills/ddd-functional-nextjs/docs/

Layer Examples — Full Code Samples

Domain Layer

Value Objects

// 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 });
};

Aggregates

// 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 })]);
};

Repository Interfaces

// 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>;
};

Infrastructure Layer

// 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));
    });
  },
});

API Route

// 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 });
}

Components — Query Handler + View Model + Component

// 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

docs

EXAMPLES.md

PRIMITIVES.md

SKILL.md

tile.json