بنقرة واحدة
feature-generator
// Generate full-stack features. Backend = hand-written bounded-context aggregates (DDD); frontend = FSD slices with HydrationBoundary. Use when creating new features, adding CRUD operations, or scaffolding new pages.
// Generate full-stack features. Backend = hand-written bounded-context aggregates (DDD); frontend = FSD slices with HydrationBoundary. Use when creating new features, adding CRUD operations, or scaffolding new pages.
Generate TypeScript API client from Swagger/Go comments. Use when updating API endpoints, adding new routes, or regenerating the frontend API client after backend changes.
Manage database migrations and Better Auth schema. Use when adding tables, modifying schema, running migrations, or resetting the database.
Access local technical documentation before searching the internet. Use FIRST when researching any tech stack question.
Create and manage authentication pages with server-side session handling. Use when adding login, register, or protected pages WITHOUT flicker/skeleton.
Server-Side + Client-Side Data Fetching with Orval + TanStack Query HydrationBoundary Pattern. ALWAYS use Orval - NEVER manual fetch()!
Feature-Sliced Design architecture for frontend. Use when creating new features, slices, or understanding the FSD layer structure.
| name | feature-generator |
| description | Generate full-stack features. Backend = hand-written bounded-context aggregates (DDD); frontend = FSD slices with HydrationBoundary. Use when creating new features, adding CRUD operations, or scaffolding new pages. |
| allowed-tools | Read, Edit, Write, Bash, Glob, Grep |
Create complete full-stack features. Backend follows bounded contexts (DDD-strategic) with Clean-Architecture layers (DDD-tactical) inside each context. Frontend follows Feature-Sliced Design.
No code generator is used. The backend used to be scaffolded with Goca; it was removed in refactor/backend-clean-architecture because its flat-layer output is structurally incompatible with this repo's bounded-context layout. The five backend files per aggregate are short — copy the existing internal/stats/ context as the canonical template and modify.
Each aggregate lives inside one bounded context (e.g. stats/, auth/, notifications/, exports/, or a brand-new one). Five files plus a wiring step:
backend/internal/<ctx>/domain/<aggregate>.go
gorm.io/gorm import. No I/O.shared.AggregateBase if it raises events.func NewMoney(...) (Money, error)).EventName() string.backend/internal/<ctx>/application/
ports.go declares interfaces (Repository, JobEnqueuer, ...).<aggregate>_usecases.go holds use-case structs with Execute(ctx, ...).agg.PullEvents() before repo.Save(...).backend/internal/<ctx>/infrastructure/persistence/
gorm_models.go (unexported GORM-tagged twin) + <aggregate>_mapper.go + <aggregate>_repo.go + registry.go exposing Entities() []any.var _ <ctx>app.Repository = (*Repository)(nil).Save must mutate only DB-owned fields back into *agg, never replace it whole (would wipe pending events).backend/internal/<ctx>/interfaces/http/handler.go
application/ package. Never imports gorm or another bounded context.backend/internal/composition/composition.go
<ctx>persist.Entities() to runAutoMigrations.statsToExportsReader / authToNotificationsDirectory).# Look at the canonical example
ls backend/internal/stats/
# domain/ application/ infrastructure/ interfaces/
# Create your context by mirroring its layout
mkdir -p backend/internal/products/{domain,application,infrastructure/persistence,interfaces/http}
# Write your five files, then:
cd .. && just api # regenerate Swagger + Orval TS client
just dev-backend # AutoMigrate runs on startup
There is no central registry. Each bounded context owns its own Entities() function under internal/<ctx>/infrastructure/persistence/registry.go. The composition root aggregates them:
// internal/<ctx>/infrastructure/persistence/registry.go
func Entities() []any {
return []any{&gormProduct{}}
}
// internal/composition/composition.go (runAutoMigrations)
entities = append(entities, productspersist.Entities()...)
// app/(protected)/products/page.tsx (Server Component)
import { dehydrate, HydrationBoundary } from "@tanstack/react-query"
import { cookies } from "next/headers"
import { redirect } from "next/navigation"
import { getGetProductsQueryKey, getProducts } from "@shared/api/endpoints/products/products"
import { getQueryClient } from "@shared/lib/query-client"
import { getSession } from "@shared/lib/auth-server"
import { ProductList } from "./product-list"
export default async function ProductsPage() {
const session = await getSession()
if (!session) redirect("/login")
const cookieStore = await cookies()
const cookieHeader = cookieStore.getAll().map((c) => `${c.name}=${c.value}`).join("; ")
const queryClient = getQueryClient()
await queryClient.prefetchQuery({
queryKey: getGetProductsQueryKey(),
queryFn: () => getProducts({ headers: { Cookie: cookieHeader }, cache: "no-store" }),
})
return (
<HydrationBoundary state={dehydrate(queryClient)}>
<div className="container py-8">
<h1 className="text-2xl font-bold mb-4">Products</h1>
<ProductList />
</div>
</HydrationBoundary>
)
}
// app/(protected)/products/product-list.tsx
"use client"
import { useGetProducts } from "@shared/api/endpoints/products/products"
import { useSSE } from "@features/stats"
export function ProductList() {
useSSE()
const { data: productsResponse } = useGetProducts()
const products = productsResponse?.status === 200 ? productsResponse.data : null
return (
<div className="grid gap-4">
{products?.map((product) => (
<div key={product.id}>{product.name} - {product.price}€</div>
))}
</div>
)
}
// internal/<ctx>/interfaces/http/handler.go
// GetProducts godoc
// @Summary List all products
// @Description Get all products for the authenticated user
// @Tags products
// @Accept json
// @Produce json
// @Success 200 {array} ProductResponse
// @Failure 401 {object} ErrorResponse
// @Security BearerAuth
// @Router /products [get]
func (h *Handler) GetProducts(w http.ResponseWriter, r *http.Request) {
// Implementation depends only on this context's application use cases.
}
just api # always run after handler changes
snake_case.gopage.tsx in route folderkebab-case.tsxfrontend/src/app/frontend/src/app/(protected)/frontend/src/app/(auth)/initialData prop needed)