# Package Organization & "Lib" Files Strategy

**Status**: ✅ Implemented  
**Date**: October 2025

---

## 🎯 Core Principle: Domain Package = Pure Domain Logic

The `@customer-portal/domain` package should contain **ONLY** pure domain logic that is:
- ✅ Framework-agnostic
- ✅ Reusable across frontend and backend
- ✅ Pure TypeScript (no React, no NestJS, no Next.js)
- ✅ No external infrastructure dependencies

---

## 📦 Package Structure Matrix

| Type | Location | Examples | Reasoning |
|------|----------|----------|-----------|
| **Domain Types** | `packages/domain/*/contract.ts` | `Invoice`, `Order`, `Customer` | Pure business entities |
| **Validation Schemas** | `packages/domain/*/schema.ts` | `invoiceSchema`, `orderQueryParamsSchema` | Runtime validation |
| **Pure Utilities** | `packages/domain/toolkit/` | `formatCurrency()`, `parseDate()` | No framework dependencies |
| **Provider Mappers** | `packages/domain/*/providers/` | `transformWhmcsInvoice()` | Data transformation logic |
| **Framework Utils** | `apps/*/src/lib/` or `apps/*/src/core/` | API clients, React hooks | Framework-specific code |
| **Shared Infra** | `packages/validation`, `packages/logging` | `ZodPipe`, `useZodForm` | Framework bridges |

---

## 📂 Detailed Breakdown

### ✅ **What Belongs in `packages/domain/`**

#### 1. Domain Types & Contracts
```typescript
// packages/domain/billing/contract.ts
export interface Invoice {
  id: number;
  status: InvoiceStatus;
  total: number;
}
```

#### 2. Validation Schemas (Zod)
```typescript
// packages/domain/billing/schema.ts
export const invoiceSchema = z.object({
  id: z.number(),
  status: invoiceStatusSchema,
  total: z.number(),
});

// Domain-specific query params
export const invoiceQueryParamsSchema = z.object({
  page: z.coerce.number().int().positive().optional(),
  status: invoiceStatusSchema.optional(),
  dateFrom: z.string().datetime().optional(),
});
```

#### 3. Pure Utility Functions
```typescript
// packages/domain/toolkit/formatting/currency.ts
export function formatCurrency(
  amount: number,
  currency: SupportedCurrency
): string {
  return new Intl.NumberFormat("en-US", {
    style: "currency",
    currency,
  }).format(amount);
}
```

✅ **Pure function** - no React, no DOM, no framework
✅ **Business logic** - directly related to domain entities
✅ **Reusable** - both frontend and backend can use it

#### 4. Provider Mappers
```typescript
// packages/domain/billing/providers/whmcs/mapper.ts
export function transformWhmcsInvoice(raw: WhmcsInvoiceRaw): Invoice {
  return {
    id: raw.invoiceid,
    status: mapStatus(raw.status),
    total: parseFloat(raw.total),
  };
}
```

---

### ❌ **What Should NOT Be in `packages/domain/`**

#### 1. Framework-Specific API Clients
```typescript
// ❌ DO NOT put in domain
// apps/portal/src/lib/api/client.ts
import { ApiClient } from "@hey-api/client-fetch";  // ← Framework dependency

export const apiClient = new ApiClient({
  baseUrl: process.env.NEXT_PUBLIC_API_URL,  // ← Next.js specific
});
```

**Why?** 
- Depends on `@hey-api/client-fetch` (external library)
- Uses Next.js environment variables
- Runtime infrastructure code

#### 2. React Hooks
```typescript
// ❌ DO NOT put in domain
// apps/portal/src/lib/hooks/useInvoices.ts
import { useQuery } from "@tanstack/react-query";  // ← React dependency

export function useInvoices() {
  return useQuery({ ... });  // ← React-specific
}
```

**Why?** React-specific - backend can't use this

#### 3. Error Handling with Framework Dependencies
```typescript
// ❌ DO NOT put in domain
// apps/portal/src/lib/utils/error-handling.ts
import { ApiError as ClientApiError } from "@/lib/api";  // ← Framework client

export function getErrorInfo(error: unknown): ApiErrorInfo {
  if (error instanceof ClientApiError) {  // ← Framework-specific error type
    // ...
  }
}
```

**Why?** Depends on the API client implementation

#### 4. NestJS-Specific Utilities
```typescript
// ❌ DO NOT put in domain
// apps/bff/src/core/utils/error.util.ts
export function getErrorMessage(error: unknown): string {
  // Generic error extraction - could be in domain
  if (error instanceof Error) {
    return error.message;
  }
  return String(error);
}
```

**This one is borderline** - it's generic enough it COULD be in domain, but:
- Only used by backend
- Not needed for type definitions
- Better to keep application-specific utils in apps

---

## 🎨 Current Architecture (Correct!)

```
packages/
├── domain/                          # ✅ Pure domain logic
│   ├── billing/
│   │   ├── contract.ts             # ✅ Types
│   │   ├── schema.ts               # ✅ Zod schemas + domain query params
│   │   └── providers/whmcs/        # ✅ Data mappers
│   ├── common/
│   │   ├── types.ts                # ✅ Truly generic types (ApiResponse, PaginationParams)
│   │   └── schema.ts               # ✅ Truly generic schemas (paginationParamsSchema, emailSchema)
│   └── toolkit/
│       ├── formatting/             # ✅ Pure functions (formatCurrency, formatDate)
│       └── validation/             # ✅ Pure validation helpers
│
├── validation/                      # ✅ Framework bridges
│   ├── src/zod-pipe.ts             # NestJS Zod pipe
│   └── src/zod-form.ts             # React Zod form hook
│
└── logging/                         # ✅ Infrastructure
    └── src/logger.ts                # Logging utilities

apps/
├── portal/ (Next.js)
│   └── src/lib/
│       ├── api/                    # ❌ Framework-specific
│       │   ├── client.ts           # API client instance
│       │   └── runtime/            # Generated client code
│       ├── hooks/                  # ❌ React-specific
│       │   └── useAuth.ts          # React Query hooks
│       └── utils/                  # ❌ App-specific
│           ├── error-handling.ts   # Portal error handling
│           └── cn.ts               # Tailwind utility
│
└── bff/ (NestJS)
    └── src/core/
        ├── validation/             # ❌ NestJS-specific
        │   └── zod-validation.filter.ts
        └── utils/                  # ❌ App-specific
            ├── error.util.ts       # BFF error utilities
            └── validation.util.ts  # BFF validation helpers
```

---

## 🔄 Decision Framework: "Should This Be in Domain?"

Ask these questions:

### ✅ Move to Domain If:
1. Is it a **pure function** with no framework dependencies?
2. Does it work with **domain types**?
3. Could **both frontend and backend** use it?
4. Is it **business logic** (not infrastructure)?

### ❌ Keep in Apps If:
1. Does it import **React**, **Next.js**, or **NestJS**?
2. Does it use **environment variables**?
3. Does it depend on **external libraries** (API clients, HTTP libs)?
4. Is it **UI-specific** or **framework-specific**?
5. Is it only used in **one app**?

---

## 📋 Examples with Decisions

| Utility | Decision | Location | Why |
|---------|----------|----------|-----|
| `formatCurrency(amount, currency)` | ✅ Domain | `domain/toolkit/formatting/` | Pure function, no deps |
| `invoiceQueryParamsSchema` | ✅ Domain | `domain/billing/schema.ts` | Domain-specific validation |
| `paginationParamsSchema` | ✅ Domain | `domain/common/schema.ts` | Truly generic |
| `useInvoices()` React hook | ❌ Portal App | `portal/lib/hooks/` | React-specific |
| `apiClient` instance | ❌ Portal App | `portal/lib/api/` | Framework-specific |
| `ZodValidationPipe` | ✅ Validation Package | `packages/validation/` | Reusable bridge |
| `getErrorMessage(error)` | ❌ BFF App | `bff/core/utils/` | App-specific utility |
| `transformWhmcsInvoice()` | ✅ Domain | `domain/billing/providers/whmcs/` | Data transformation |

---

## 🚀 What About Query Parameters?

### Current Structure (✅ Correct!)

**Generic building blocks** in `domain/common/`:
```typescript
// packages/domain/common/schema.ts
export const paginationParamsSchema = z.object({
  page: z.coerce.number().int().positive().optional(),
  limit: z.coerce.number().int().positive().max(100).optional(),
});

export const filterParamsSchema = z.object({
  search: z.string().optional(),
  sortBy: z.string().optional(),
  sortOrder: z.enum(["asc", "desc"]).optional(),
});
```

**Domain-specific query params** in their own domains:
```typescript
// packages/domain/billing/schema.ts
export const invoiceQueryParamsSchema = z.object({
  page: z.coerce.number().int().positive().optional(),
  limit: z.coerce.number().int().positive().max(100).optional(),
  status: invoiceStatusSchema.optional(),        // ← Domain-specific
  dateFrom: z.string().datetime().optional(),    // ← Domain-specific
  dateTo: z.string().datetime().optional(),      // ← Domain-specific
});

// packages/domain/subscriptions/schema.ts
export const subscriptionQueryParamsSchema = z.object({
  page: z.coerce.number().int().positive().optional(),
  limit: z.coerce.number().int().positive().max(100).optional(),
  status: subscriptionStatusSchema.optional(),   // ← Domain-specific
  type: z.string().optional(),                   // ← Domain-specific
});
```

**Why this works:**
- `common` has truly generic utilities
- Each domain owns its specific query parameters
- No duplication of business logic

---

## 📖 Summary

### Domain Package (`packages/domain/`)
**Contains:**
- ✅ Domain types & interfaces
- ✅ Zod validation schemas
- ✅ Provider mappers (WHMCS, Salesforce, Freebit)
- ✅ Pure utility functions (formatting, parsing)
- ✅ Domain-specific query parameter schemas

**Does NOT contain:**
- ❌ React hooks
- ❌ API client instances
- ❌ Framework-specific code
- ❌ Infrastructure code

### App Lib/Core Directories
**Contains:**
- ✅ Framework-specific utilities
- ✅ API clients & HTTP interceptors
- ✅ React hooks & custom hooks
- ✅ Error handling with framework dependencies
- ✅ Application-specific helpers

---

**Key Takeaway**: The domain package is your **single source of truth for types and validation**. Everything else stays in apps where it belongs!

