Skip to content

Project Structure ​

In traditional frameworks, you run an init command and are instantly handed a rigid folder structure that you have to force your application to fit into.

Carotene does the exact opposite. Your folder structure is a direct, dynamic reflection of your architectural blueprints. The monorepo is not pre-determined; it is Contract-Driven.

Let's look at how your project evolves from a blank canvas into a fully scaffolded microservice architecture.

Phase 1: The Blank Canvas (carrot init) ​

When you start a new project by running carrot init my-company, the CLI does not assume anything about your architecture. It simply gives you the minimal workspace needed to start designing:

my-company/
├── carotene.toml             # Global workspace configuration
└── system.carrot             # Your blank architectural blueprint

At this stage, there are no frontends, no backends, and no generated code. It is just you and the blueprint.

Phase 2: Defining the Topology ​

Inside your .carrot files, you define the physical blocks of your system. Carotene uses the names you assign to your backend and frontend blocks to determine exactly what the monorepo should look like.

Let's say you write the following in system.carrot:

dart
// Define a central backend
backend CoreAPI {
  function ProcessRefund(orderId: String) { ... }
}

// Define an internal dashboard
frontend AdminPanel {
  view RefundDashboard() { ... }
}

// Define a public-facing mobile app API
backend MobileGateway {
  function GetUserProfile(userId: String) { ... }
}

Phase 3: Contract-Driven Scaffolding (carrot build) ​

The moment you run carrot build (or carrot dev), the Carotene compiler reads your topology and dynamically generates the monorepo to match it perfectly.

The resulting project structure will look exactly like this:

my-company/
├── carotene.toml             
├── system.carrot             
│
└── apps/                     # 🚀 Dynamically generated based on your blocks
    │
    ├── AdminPanel/           # Matching: frontend AdminPanel {}
    │   ├── .generated/       # (Read-only) API clients, types, and routing
    │   └── src/              # Your standard frontend UI and components
    │
    ├── CoreAPI/              # Matching: backend CoreAPI {}
    │   ├── .generated/       # (Read-only) Routers and structural tests
    │   ├── src/              # Implementation stubs (ProcessRefund.ts)
    │   └── tests/            # AI-generated business logic tests
    │
    └── MobileGateway/        # Matching: backend MobileGateway {}
        ├── .generated/       
        ├── src/              # Implementation stubs (GetUserProfile.ts)
        └── tests/

The Boundary Rules ​

This dynamic generation creates incredibly clean boundaries between your services:

  1. The .generated/ Folders (Strictly Read-Only): Carotene isolates all the tedious networking, RPC clients, and strict TypeScript/Go Interfaces here. Do not edit these files. If your architecture changes, Carotene will instantly regenerate them, ensuring your native type-checker throws errors if implementations fall out of sync.
  2. The src/ Directory (The Persistent Workspace): When Carotene discovers a new function, it generates a stub here explicitly typed to match the .generated/ interface.
    • If the function is flagged as @ai in the contract, Carotene will overwrite this file to synthesise the logic.
    • If flagged as @manual, Carotene generates the stub once and permanently steps away, leaving the file entirely to you.

By making the folder structure an output of the compiler rather than a static starting point, Carotene ensures your monorepo never drifts from your architecture. Your codebase is always a mathematically perfect reflection of your .carrotblueprints.