# Module Folder Structure Reference

Complete visual reference for how to organize files in new domain modules.

---

## Full Directory Tree (Purchases Example)

```
lib/modules/
├── purchases/                          # Module root (snake_case)
│   ├── domain/                         # Pure business logic (no I/O)
│   │   ├── purchase.model.ts           # Commands, types, enums
│   │   └── purchase.domain.ts          # Validators, calculators, helpers
│   │
│   ├── application/                    # Orchestration & contracts
│   │   ├── purchase-order.repository.ts       # Repository interface + DTO type
│   │   ├── purchase-accounting.port.ts        # Accounting port interface
│   │   ├── purchase-side-effects.port.ts      # Side effects port interface
│   │   ├── purchase-reference-data.port.ts    # Reference data port interface
│   │   ├── purchase-id-generator.port.ts      # ID generator port interface
│   │   ├── purchase-application-service.ts    # Main orchestrator (5-port DI)
│   │   └── create-purchase-order.mapper.ts    # DTO ↔ Command mapping
│   │
│   └── infrastructure/                 # Adapters (swappable implementations)
│       ├── json-purchase-order.repository.ts
│       ├── postgres-purchase-order.repository.ts
│       ├── purchase-order.repository.factory.ts
│       ├── default-purchase-accounting.port.ts
│       ├── default-purchase-side-effects.port.ts
│       ├── default-purchase-reference-data.port.ts
│       ├── default-purchase-id-generator.port.ts
│       └── legacy-purchase-order-journal.ts        # Optional: extracted legacy
│
├── sales/                              # New module follows same structure
│   ├── domain/
│   │   ├── sales-invoice.model.ts
│   │   └── sales-invoice.domain.ts
│   ├── application/
│   │   ├── sales-invoice.repository.ts
│   │   ├── sales-accounting.port.ts
│   │   ├── sales-side-effects.port.ts
│   │   ├── sales-reference-data.port.ts
│   │   ├── sales-id-generator.port.ts
│   │   ├── sales-invoice-application-service.ts
│   │   └── create-sales-invoice.mapper.ts
│   └── infrastructure/
│       ├── json-sales-invoice.repository.ts
│       ├── postgres-sales-invoice.repository.ts
│       ├── sales-invoice.repository.factory.ts
│       ├── default-sales-accounting.port.ts
│       ├── default-sales-side-effects.port.ts
│       ├── default-sales-reference-data.port.ts
│       └── default-sales-id-generator.port.ts
│
└── MODULE_ARCHITECTURE.md              # This file (reference template)

tests/
├── purchases/
│   ├── purchase-domain.test.ts
│   ├── purchase-repository.contract.test.ts
│   └── purchase-application-service.test.ts
│
└── sales/
    ├── sales-invoice-domain.test.ts
    ├── sales-invoice-repository.contract.test.ts
    └── sales-invoice-application-service.test.ts
```

---

## File Naming Conventions

### Domain Layer
| File | Pattern | Example |
|------|---------|---------|
| Model/Types | `{entity}.model.ts` | `purchase.model.ts`, `sales-invoice.model.ts` |
| Functions | `{entity}.domain.ts` | `purchase.domain.ts`, `sales-invoice.domain.ts` |

### Application Layer
| File | Pattern | Example |
|------|---------|---------|
| Repository | `{entity}.repository.ts` | `purchase-order.repository.ts` |
| Port | `{entity}-{purpose}.port.ts` | `purchase-accounting.port.ts` |
| Service | `{entity}-application-service.ts` | `purchase-application-service.ts` |
| Mapper | `create-{entity}.mapper.ts` | `create-purchase-order.mapper.ts` |

### Infrastructure Layer
| File | Pattern | Example |
|------|---------|---------|
| JSON Repo | `json-{entity}.repository.ts` | `json-purchase-order.repository.ts` |
| Postgres Repo | `postgres-{entity}.repository.ts` | `postgres-purchase-order.repository.ts` |
| Factory | `{entity}.repository.factory.ts` | `purchase-order.repository.factory.ts` |
| Port Adapter | `default-{entity}-{purpose}.port.ts` | `default-purchase-accounting.port.ts` |
| Legacy Helper | `legacy-{entity}-{function}.ts` | `legacy-purchase-order-journal.ts` |

### Tests
| File | Pattern | Example |
|------|---------|---------|
| Domain | `{entity}-domain.test.ts` | `purchase-domain.test.ts` |
| Contract | `{entity}-repository.contract.test.ts` | `purchase-repository.contract.test.ts` |
| Service | `{entity}-application-service.test.ts` | `purchase-application-service.test.ts` |

---

## File Count Reference

### Per-Module Baseline

| Layer | Count | Details |
|-------|-------|---------|
| **Domain** | 2 | model.ts + domain.ts |
| **Application** | 7 | repository.ts + 4 ports + service + mapper |
| **Infrastructure** | 8 | 2 adapters + factory + 4 port adapters + optional legacy |
| **Tests** | 3 | domain + contract + service |
| **Config** | 1 | package.json scripts |
| **Docs** | 1 | MODULE_TEMPLATE markdown (optional) |
| **TOTAL** | ~22 files per module | Scales linearly; complexity is local |

### Growth Path
- **MVP (Create only):** 12 files (min repos + service + 1 test suite)
- **Standard (Create + Update):** 18 files (full CRUD + two methods)
- **Mature (Complex domain):** 25+ files (advanced queries + multiple endpoints)

---

## File Size Guidelines

| File | Lines | Rationale |
|------|-------|-----------|
| Domain model | 50-100 | Type definitions, enums |
| Domain logic | 100-200 | Pure functions, validators, calculators |
| Repository interface | 20-40 | Just method signatures |
| Port interface | 10-30 | Minimal, focused contract |
| Application service | 150-300 | Orchestration of validation → persist → effects |
| Mapper | 30-60 | Simple transformations |
| Repo adapter (JSON) | 50-100 | Wraps legacy helpers |
| Repo adapter (Postgres) | 100-150 | PG-specific queries + generation |
| Port adapter (default) | 15-30 | Thin wrapper, error swallowing |
| Test file | 100-150 | 3-5 test cases |

**Rule:** If a file exceeds 300 lines, consider splitting concerns.

---

## Import Organization

### Within-Module Imports
```typescript
// ✅ Correct (hierarchical, no circular)
// Application layer imports
import { calculateTotals } from '../domain/purchase.domain';            // From domain
import type { PurchaseOrderRepository } from './purchase-order.repository'; // From app
import { createPurchaseOrderRepository } from '../infrastructure/purchase-order.repository.factory'; // From infra

// Infrastructure layer imports
import { addPurchaseOrder } from '@/lib/data';                         // From legacy lib
import type { PurchaseOrderRepository } from '../application/purchase-order.repository'; // From app contracts
```

### Cross-Module Imports
```typescript
// ✅ OK (minimal, at action layer)
// In actions.ts
import { PurchaseApplicationService } from '@/lib/modules/purchases/application/purchase-application-service';
import { SalesInvoiceApplicationService } from '@/lib/modules/sales/application/sales-invoice-application-service';

// ❌ NOT OK (direct cross-module)
// In purchase-service.ts
import { SalesInvoiceApplicationService } from '@/lib/modules/sales/...'; // Violation!
```

**Rule:** Modules only talk to each other through action layer.

---

## Directory Structure Decision Matrix

### When to Split Domain into Sub-Domains

| Indicator | Single Module | Multiple Modules |
|-----------|---|---|
| Entity count | 1-3 entities | 4+ entities |
| Aggregate size | <20 files | >25 files |
| Distinct workflows | 1-2 | 3+ different flows |
| Independent testing | Test together | Test separately |
| Deployment cadence | Same release | Different releases |
| Domain expert | 1 person | 2+ people |

**Examples:**
- ✅ Single: Purchase (PO + Lines + Log)
- ✅ Single: Sales Invoice (Invoice + Lines + Payments + Log)
- ⚠️ Consider Split: Accounting (Journals + Ledgers + Reconciliation + Reporting)
- ⚠️ Consider Split: Payroll (Master Data + Calculation + Tax + Posting)

---

## Configuration & Serialization

### package.json Test Scripts
```json
{
  "scripts": {
    "test:{domain}": "tsx --test tests/{domain}/**/*.test.ts",
    "test:{domain}:domain": "tsx --test tests/{domain}/{entity}-domain.test.ts",
    "test:{domain}:contracts": "tsx --test tests/{domain}/{entity}-repository.contract.test.ts",
    "test:{domain}:service": "tsx --test tests/{domain}/{entity}-application-service.test.ts"
  }
}
```

**Example:**
```json
{
  "scripts": {
    "test:purchases": "tsx --test tests/purchases/**/*.test.ts",
    "test:purchases:domain": "tsx --test tests/purchases/purchase-domain.test.ts",
    "test:purchases:contracts": "tsx --test tests/purchases/purchase-repository.contract.test.ts",
    "test:purchases:service": "tsx --test tests/purchases/purchase-application-service.test.ts",
    "test:sales": "tsx --test tests/sales/**/*.test.ts",
    "test:sales:domain": "tsx --test tests/sales/sales-invoice-domain.test.ts"
  }
}
```

---

## Module Dependency Graph

```
┌─────────────────────────────────┐
│      lib/modules/{domain}       │ (Isolated module)
├─────────────────────────────────┤
│  domain/                        │ ← Pure logic (no dependencies)
│  ├─ *.model.ts                  │
│  └─ *.domain.ts                 │
├─────────────────────────────────┤
│  application/                   │ ← Orchestration
│  ├─ *.repository.ts (interface) │
│  ├─ *-*.port.ts (interfaces)    │
│  ├─ *-application-service.ts    │ ← Imports: domain + infra (factory)
│  └─ *.mapper.ts                 │
├─────────────────────────────────┤
│  infrastructure/                │ ← Implementations
│  ├─ *-repository.ts (adapters)  │ ← Imports: @/lib/data, @/lib/postgres
│  ├─ default-*-port.ts (adapters)│ ← Imports: @/lib/data, legacy helpers
│  └─ *-repository.factory.ts     │ ← Imports: adapters above
├─────────────────────────────────┤
│ Tests (mirror structure)        │
│  ├─ *-domain.test.ts            │ ← No mocks needed
│  ├─ *-repository.contract.test  │ ← Both adapters, no service
│  └─ *-application-service.test  │ ← All mocks, no real I/O
└─────────────────────────────────┘
        ↑ No circular deps
        ↑ Tests isolated
        ↓
    action layer (binds modules via service factories)
```

---

## Initialization Checklist

New module bootstrap:

```bash
# 1. Create directory structure
mkdir -p lib/modules/{domain}/{domain,application,infrastructure}
mkdir -p tests/{domain}

# 2. Create minimal files (in order)
touch lib/modules/{domain}/domain/{entity}.model.ts
touch lib/modules/{domain}/domain/{entity}.domain.ts
touch lib/modules/{domain}/application/{entity}.repository.ts
touch lib/modules/{domain}/application/{entity}-*.port.ts  # 4 ports
touch lib/modules/{domain}/application/{entity}-application-service.ts
touch lib/modules/{domain}/infrastructure/json-{entity}.repository.ts
touch lib/modules/{domain}/infrastructure/postgres-{entity}.repository.ts
touch lib/modules/{domain}/infrastructure/{entity}.repository.factory.ts
touch lib/modules/{domain}/infrastructure/default-{entity}-*.port.ts  # 4 adapters

# 3. Create tests
touch tests/{domain}/{entity}-domain.test.ts
touch tests/{domain}/{entity}-repository.contract.test.ts
touch tests/{domain}/{entity}-application-service.test.ts

# 4. Add package.json scripts
# (edit package.json to add test:{domain} suite)

# 5. Verify structure
find lib/modules/{domain} -type f | wc -l  # Should be ~12-14 files
find tests/{domain} -type f | wc -l        # Should be 3 files
```

---

## Visual Module Lifecycle

```
Week 1: Bootstrap
├─ Create folder structure
├─ Define domain types + commands
├─ Write 3-5 pure domain functions
├─ Add domain tests (100% coverage)
└─ Commit: "Module: {domain} domain layer"

Week 2: Infrastructure
├─ Define repository interface
├─ Implement JSON adapter (wrap legacy)
├─ Implement Postgres adapter (pg queries)
├─ Add contract tests (both adapters)
└─ Commit: "Module: {domain} persistence adapters"

Week 3: Service & Ports
├─ Define 4 port interfaces
├─ Implement 4 port adapters (thin wrappers)
├─ Build application service (orchestrator)
├─ Add service integration tests
└─ Commit: "Module: {domain} application service"

Week 4: Integration
├─ Create mapper (DTO → Command)
├─ Wire action layer to service
├─ Add end-to-end tests (via actions)
├─ Documentation + rollout plan
└─ Commit: "Module: {domain} action integration"

Ongoing:
├─ Add new commands (e.g., update, delete)
├─ Extend side effects (new capabilities)
├─ Add repository queries (performance)
└─ Keep tests at 80%+ coverage
```

---

## File Template Repository

Quick-copy templates for each file type:

### Domain Model Template
```typescript
// lib/modules/{domain}/domain/{entity}.model.ts

export type {Entity}PostingStatus = 'draft' | 'saved' | 'posted';
export type {Entity}WorkflowStatus = 'active' | 'cancelled' | 'archived';

export type {Entity}CreateCommand = {
  // User input
  field1: string;
  field2: number;
  items: Array<{ item fields }>;
  // Meta
  currencyCode: string;
  postingStatus: {Entity}PostingStatus;
};

export type {Entity}CalculatedTotals = {
  processedItems: any[];
  subTotal: number;
  grandTotal: number;
  amountDue: number;
};
```

### Port Interface Template
```typescript
// lib/modules/{domain}/application/{entity}-{purpose}.port.ts

export interface I{Domain}{Purpose}Port {
  async method1(input: InputType): Promise<OutputType>;
  async method2(input: InputType): Promise<void>;
}
```

### Port Adapter Template
```typescript
// lib/modules/{domain}/infrastructure/default-{entity}-{purpose}.port.ts

import type { I{Domain}{Purpose}Port } from '../application/{entity}-{purpose}.port';
import { legacyHelper } from '@/lib/data';

export class Default{Domain}{Purpose}Port implements I{Domain}{Purpose}Port {
  async method1(input: InputType): Promise<OutputType> {
    try {
      return legacyHelper(input.field1, input.field2);
    } catch (error) {
      console.error('Failed to ...:', error);
      // Return default or rethrow as needed
    }
  }
}

export function createDefault{Domain}{Purpose}Port(): I{Domain}{Purpose}Port {
  return new Default{Domain}{Purpose}Port();
}
```

---

## Audit Checklist (Before Shipping Module)

- [ ] All files follow naming convention (no camelCase in filenames except legacy)
- [ ] Domain layer has 0 imports from infrastructure or application
- [ ] Application service has 5 injected ports (no fewer, no fewer exceptions)
- [ ] Repository interface is read-only at application boundary (no methods return internal state)
- [ ] All ports have default adapters
- [ ] All tests pass: `npm run test:{domain}`
- [ ] Domain tests: 100% coverage (no mocks, no DB)
- [ ] Contract tests: Both JSON and Postgres pass identically
- [ ] Service tests: All scenarios covered (draft, posted, error, duplicate)
- [ ] Action layer: Service used via `createDefault()` factory only
- [ ] No cross-module imports (except in action layer or tests)
- [ ] TypeScript errors: 0 (`npm run lint` or equivalent)
- [ ] Documentation: Updated MODULE_ARCHITECTURE.md or STANDARDIZATION_GUIDE.md if patterns changed

---

## When to Create a New Module vs. Extend

| Scenario | Action |
|---|---|
| Add new command to existing entity | Extend service method, add tests |
| Add new entity to existing domain | New file in domain/, new service method, new tests |
| Completely new business domain | New module (replicate structure) |
| Cross-domain workflow | Add in action layer, not in module |
| Shared utilities/helpers | Add to `lib/utils/` or `lib/helpers/` |
| Shared types | Add to `lib/types.ts` |

**Principle:** One module = one aggregate root + related entities. If you need orchestration across roots, that's the action layer.
