Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 35 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,10 +116,28 @@ The tool generates different Lambda structures based on trigger type:
- **Custom**: User-defined custom event sources with configurable type path and optional idempotency

### Domain Generation
When creating domains with `draft new:domain`, the tool prompts for:
When creating domains with `draft new:domain`, the tool supports both interactive and non-interactive modes:

**Interactive Mode** (default):
- Domain path (automatically prefixed with `domains/` if not present)
- Database type selection (Postgres or DynamoDB)
- Database-specific configuration (table names, prefixes, etc.)
- For Postgres: Database selection from `.local-migrate-config.yml`

**Non-Interactive Mode** (CLI flags):
- `--domain-path, -p`: Domain folder path
- `--db-type`: Database type (postgres or dynamo)
- `--table-name`: Database table name
- `--db-prefix`: ID prefix for Postgres (3 characters)
- `--db-name`: Database name for Postgres (loaded from config)

**Database Configuration**:
For Postgres domains, available databases are dynamically loaded from `.local-migrate-config.yml`:
- Reads `migrations.databases` configuration
- Filters out test databases (`group: 'test'`)
- Converts snake_case names to PascalCase for provider functions
- Example: `user_preferences` → `ProvideUserPreferences`
- Example: `games_core` → `ProvideGamesCore`

Domain structure varies by database type:
- **Postgres**: Full CRUD with service layer, repository layer, builders, DAOs, providers, and domain models with search/filter capabilities
Expand Down Expand Up @@ -189,7 +207,12 @@ Implementation in `internal/actions/mockery/`:
- Uses `select` on semaphore acquisition to respect cancellation
5. Cleans up temporary files and displays execution summary (or cancellation summary if interrupted)

The `new:domain` action uses a simpler mockery integration: it adds packages to `.mockery.yml` and runs `mockery` directly without the config merging system.
The `new:domain` action integrates with the mockery action layer:
- Creates `.mockery.pkg.yml` files for service and repository packages
- Calls the mockery action directly (reuses `internal/actions/mockery`)
- Passes context for cancellation support
- Runs with jobsNum=2 for concurrent service and repository mock generation
- Benefits from progress reporting and error handling of the main mockery action

## Configuration Files

Expand Down Expand Up @@ -230,6 +253,12 @@ Services use Pkl (configuration language) for app config:
- Supports rooted patterns (`/dist`), directory-only patterns (`node_modules/`), negation patterns (`!important.txt`), and glob patterns (`**/*.yml`)
- Optional `skipGitignore` parameter to disable `.gitignore` filtering
- Used by mockery command to automatically skip vendor, node_modules, and other ignored directories
- `internal/pkg/migrateconfig/` provides database configuration utilities:
- Reads and parses `.local-migrate-config.yml` from project root
- Extracts `migrations.databases` configuration
- Filters databases by group (excludes `group: 'test'`)
- Provides `ToPascalCase()` for converting snake_case to PascalCase
- Formats database names for display in forms
- `internal/dtos/` for data transfer objects passed between commands, forms, and actions
- `internal/data/` for global state (flags, metadata, placeholder tags)
- `cmd/commands/internal/common/` for command-level shared code
Expand Down Expand Up @@ -272,7 +301,10 @@ New lambdas are added to existing files via string replacement:

New domains have a different post-create flow:
1. `postgresModels()` (Postgres only) - Adds domain DAOs to provider test migrations using `NextDbModelTag`
2. `mockery()` - Adds domain service/repository packages to `.mockery.yml` and runs mockery to generate mocks
2. `mockery()` - Creates `.mockery.pkg.yml` files and runs mockery action to generate mocks
- Reuses `internal/actions/mockery` for concurrent execution
- Passes context for cancellation support
- Runs with jobsNum=2 for service and repository
3. `format()` - Runs goimports/gofmt on generated files

### Global State Usage
Expand Down
77 changes: 69 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,14 +150,38 @@ For an HTTP Lambda:

### Creating a Domain Layer

Generate a complete domain layer following Domain-Driven Design principles.
Generate a complete domain layer following Domain-Driven Design principles with support for both interactive and non-interactive modes.

#### Basic Usage

**Interactive Mode** (prompts for all values):
```bash
draft new:domain
```

**Non-Interactive Mode** (CI/CD friendly):
```bash
# Postgres domain
draft new:domain \
--domain-path users \
--db-type postgres \
--table-name public.users \
--db-prefix usr \
--db-name general

# DynamoDB domain
draft new:domain \
--domain-path products \
--db-type dynamo \
--table-name ProductsTable
```

**Mixed Mode** (some flags, some prompts):
```bash
draft new:domain --domain-path orders --db-type postgres
# Prompts only for table-name, db-prefix, and db-name
```

#### Database Support

Draft supports multiple database backends:
Expand All @@ -167,13 +191,46 @@ Draft supports multiple database backends:
| **Postgres** | Full CRUD, search with filters/pagination, repository builders, DAOs, domain models |
| **DynamoDB** | Simplified repository pattern, optimized for NoSQL |

#### Flags
#### Database Configuration

```bash
# Specify working directory
draft new:domain -w path/to/project
For Postgres domains, available databases are **dynamically loaded** from `.local-migrate-config.yml`:

1. Reads `migrations.databases` from project root configuration
2. Filters out test databases (`group: 'test'`)
3. Presents remaining databases as options
4. Converts database names to PascalCase for provider functions

**Example**: If `.local-migrate-config.yml` contains:
```yaml
migrations:
databases:
general:
folder: 'postgres'
general_test:
group: 'test' # This will be filtered out
user_preferences:
folder: 'user-preferences'
games_core:
folder: 'games-core'
```

The command will:
- Show options: `General`, `User Preferences`, `Games Core`
- Generate provider calls: `ProvideGeneral`, `ProvideUserPreferences`, `ProvideGamesCore`

#### Flags

| Flag | Short | Description | Example |
|------|-------|-------------|---------|
| `--domain-path` | `-p` | Path to domain folder | `users`, `auth/sessions` |
| `--db-type` | | Database type | `postgres`, `dynamo` |
| `--table-name` | | Database table name | `public.users`, `ProductsTable` |
| `--db-prefix` | | ID prefix for Postgres (3 chars) | `usr`, `ord`, `prd` |
| `--db-name` | | Database name (from config) | `general`, `user_preferences` |
| `--working-dir` | `-w` | Working directory | `path/to/project` |

**Note**: The `--db-name` flag accepts snake_case database names as they appear in `.local-migrate-config.yml`. The tool automatically converts them to PascalCase for provider function names.

#### What Gets Created (Postgres)

```
Expand Down Expand Up @@ -312,11 +369,14 @@ Failed packages:
#### Integration with `new:domain`

When creating domains with `draft new:domain`, mocks are automatically generated:
- Domain service and repository packages are added to `.mockery.yml`
- Mockery runs to generate mock implementations
- Creates `.mockery.pkg.yml` files for service and repository packages
- Calls the mockery action directly (reuses `internal/actions/mockery`)
- Runs concurrently with `jobsNum=2` for service and repository
- Provides progress reporting and error handling
- Supports cancellation with Ctrl+C
- Mocks are placed in `domain/service/mocks` and `domain/repository/mocks`

**Note**: The `new:domain` action uses a simpler approach by directly updating `.mockery.yml` rather than the base/package config merging system.
The domain command benefits from the same mockery infrastructure used by the standalone `draft mockery` command, including concurrent execution, progress tracking, and proper error handling.

---

Expand Down Expand Up @@ -474,6 +534,7 @@ draft/
│ ├── log/ # Logging utilities
│ ├── format/ # Code formatting
│ ├── constants/ # Shared constants
│ ├── migrateconfig/ # Database config utilities
│ └── ... # Other utilities
├── Taskfile.yml # Task runner configuration
├── go.mod # Go module definition
Expand Down
86 changes: 81 additions & 5 deletions cmd/commands/newdomain/newdomain.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,101 @@ import (
"github.com/Drafteame/draft/internal/pkg/log"
)

var (
domainPath string
dbType string
tableName string
dbPrefix string
dbName string
)

var newDomainCmd = &cobra.Command{
Use: "new:domain",
Short: "Create a new domain",
Long: "Create a new configurable domain, creates models, services, repositories and any other needed config to work",
Run: run,
Short: "Create a new domain with models, services, and repositories",
Long: `Create a new configurable domain with complete domain-driven design structure.

This command scaffolds a domain layer including:
- Domain models and business logic (Postgres only)
- Service layer with CRUD operations
- Repository layer with database interactions
- Provider functions for dependency injection
- Mock configurations for testing

The command supports both interactive mode (prompts for values) and non-interactive
mode (uses flags) for CI/CD automation.

Database Types:
postgres - Creates full domain structure with Postgres support
Includes: search, filters, pagination, DAOs, and builders
dynamo - Creates simplified structure optimized for DynamoDB
Includes: basic CRUD operations

Examples:
# Interactive mode - prompts for all values
draft new:domain

# Non-interactive mode with Postgres
draft new:domain \
--domain-path users \
--db-type postgres \
--table-name public.users \
--db-prefix usr \
--db-name general

# Non-interactive mode with DynamoDB
draft new:domain \
--domain-path products \
--db-type dynamo \
--table-name ProductsTable

# Mixed mode - provide some flags, prompt for others
draft new:domain --domain-path orders --db-type postgres

# With custom working directory
draft new:domain -w /path/to/project --domain-path inventory --db-type postgres

Database Configuration:
For Postgres domains, the list of available databases is dynamically loaded from
the .local-migrate-config.yml file in the project root. The command will:

1. Read migrations.databases from .local-migrate-config.yml
2. Filter out any databases with group: 'test'
3. Present the remaining databases as options
4. Convert database names to PascalCase for provider functions
Example: user_preferences -> ProvideUserPreferences
games_core -> ProvideGamesCore

If the .local-migrate-config.yml file is not found, the command will fail with
an error. Ensure this file exists in your project root before running the command.`,
Run: run,
}

func init() {
newDomainCmd.Flags().StringVarP(&domainPath, "domain-path", "p", "", "Path to the domain folder")
newDomainCmd.Flags().StringVar(&dbType, "db-type", "", "Database type (postgres or dynamo)")
newDomainCmd.Flags().StringVar(&tableName, "table-name", "", "Name of the database table")
newDomainCmd.Flags().StringVar(&dbPrefix, "db-prefix", "", "ID prefix for Postgres (3 characters)")
newDomainCmd.Flags().StringVar(&dbName, "db-name", "", "Database name for Postgres (loaded from .local-migrate-config.yml). Use the snake_case database name (e.g., 'general', 'user_preferences')")
}

func run(cmd *cobra.Command, _ []string) {
common.ChDir(cmd)

data.LoadMeta()

input := dtos.DomainInput{}
input := dtos.DomainInput{
DomainPath: domainPath,
DBType: dbType,
TableName: tableName,
DBPrefix: dbPrefix,
DBName: dbName,
}

if err := forms.NewDomain(&input); err != nil {
log.Exitf(1, "Failed to collect domain info: %v", err)
}

if errExec := newdomain.New(input).Exec(); errExec != nil {
if errExec := newdomain.New(cmd.Context(), input).Exec(); errExec != nil {
log.Exitf(1, "Failed to create domain: %v", errExec)
}

Expand Down
4 changes: 4 additions & 0 deletions internal/actions/newdomain/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ func (nd *NewDomain) createFiles(fileList []dtos.FileEntry) error {

func (nd *NewDomain) createPostgresService() error {
fileList := []dtos.FileEntry{
{Path: nd.input.DomainPath + "/service/.mockery.pkg.yml", Data: nd.tmpl.Service.Postgres.DotMockeryPkgYml},
{Path: nd.input.DomainPath + "/service/create.go", Data: nd.tmpl.Service.Postgres.CreateGo},
{Path: nd.input.DomainPath + "/service/create_test.go", Data: nd.tmpl.Service.Postgres.CreateTestGo},
{Path: nd.input.DomainPath + "/service/delete.go", Data: nd.tmpl.Service.Postgres.DeleteGo},
Expand All @@ -114,6 +115,7 @@ func (nd *NewDomain) createPostgresService() error {

func (nd *NewDomain) createDynamoService() error {
fileList := []dtos.FileEntry{
{Path: nd.input.DomainPath + "/service/.mockery.pkg.yml", Data: nd.tmpl.Service.Dynamo.DotMockeryPkgYml},
{Path: nd.input.DomainPath + "/service/interfaces.go", Data: nd.tmpl.Service.Dynamo.InterfacesGo},
{Path: nd.input.DomainPath + "/service/service.go", Data: nd.tmpl.Service.Dynamo.ServiceGo},
{Path: nd.input.DomainPath + "/service/provider.go", Data: nd.tmpl.Service.Dynamo.ProviderGo},
Expand All @@ -135,6 +137,7 @@ func (nd *NewDomain) createRepository() error {

func (nd *NewDomain) createPostgresRepository() error {
fileList := []dtos.FileEntry{
{Path: nd.input.DomainPath + "/repository/.mockery.pkg.yml", Data: nd.tmpl.Repository.Postgres.DotMockeryPkgYml},
{Path: nd.input.DomainPath + "/repository/create.go", Data: nd.tmpl.Repository.Postgres.CreateGo},
{Path: nd.input.DomainPath + "/repository/create_test.go", Data: nd.tmpl.Repository.Postgres.CreateTestGo},
{Path: nd.input.DomainPath + "/repository/delete.go", Data: nd.tmpl.Repository.Postgres.DeleteGo},
Expand Down Expand Up @@ -165,6 +168,7 @@ func (nd *NewDomain) createPostgresRepository() error {

func (nd *NewDomain) createDynamoRepository() error {
fileList := []dtos.FileEntry{
{Path: nd.input.DomainPath + "/repository/.mockery.pkg.yml", Data: nd.tmpl.Repository.Dynamo.DotMockeryPkgYml},
{Path: nd.input.DomainPath + "/repository/interfaces.go", Data: nd.tmpl.Repository.Dynamo.InterfacesGo},
{Path: nd.input.DomainPath + "/repository/repository.go", Data: nd.tmpl.Repository.Dynamo.RepositoryGo},
{Path: nd.input.DomainPath + "/repository/provider.go", Data: nd.tmpl.Repository.Dynamo.ProviderGo},
Expand Down
Loading