# @customer-portal/domain

**Single Source of Truth for Types and Validation**

The `@customer-portal/domain` package is the **centralized domain layer** containing all types, Zod validation schemas, and provider-specific adapters for the customer portal application.

---

## 📦 Package Structure

```
packages/domain/
├── common/                      # Shared types and utilities
│   ├── types.ts                # Common types, API responses, pagination
│   ├── schema.ts               # Zod schemas for validation
│   └── index.ts
│
├── auth/                        # Authentication & authorization
│   ├── contract.ts             # User, AuthTokens, AuthResponse types
│   ├── schema.ts               # Login, signup, password validation
│   └── index.ts
│
├── billing/                     # Invoices and billing
│   ├── contract.ts             # Invoice, InvoiceItem, InvoiceList
│   ├── schema.ts               # Zod schemas + query params
│   ├── providers/whmcs/        # WHMCS adapter
│   └── index.ts
│
├── subscriptions/               # Service subscriptions
│   ├── contract.ts             # Subscription, SubscriptionStatus
│   ├── schema.ts               # Zod schemas + query params
│   ├── providers/whmcs/        # WHMCS adapter
│   └── index.ts
│
├── orders/                      # Order management
│   ├── contract.ts             # OrderSummary, OrderDetails
│   ├── schema.ts               # Zod schemas + query params
│   ├── providers/
│   │   ├── salesforce/         # Read orders from Salesforce
│   │   └── whmcs/              # Create orders in WHMCS
│   └── index.ts
│
├── payments/                    # Payment methods & gateways
│   ├── contract.ts             # PaymentMethod, PaymentGateway
│   ├── schema.ts               # Zod validation schemas
│   └── index.ts
│
├── sim/                         # SIM card management
│   ├── contract.ts             # SimDetails, SimUsage
│   ├── schema.ts               # Zod schemas + activation
│   ├── providers/freebit/      # Freebit adapter
│   └── index.ts
│
├── catalog/                     # Product catalog
│   ├── contract.ts             # CatalogProduct types
│   ├── schema.ts               # Product validation
│   └── index.ts
│
├── customer/                    # Customer data
│   ├── contract.ts             # Customer, CustomerAddress
│   ├── schema.ts               # Customer validation
│   └── index.ts
│
└── toolkit/                     # Utilities
    ├── formatting/             # Currency, date formatting
    ├── validation/             # Validation helpers
    └── index.ts
```

---

## 🎯 Design Principles

### 1. **Domain-First Organization**
Each business domain owns its:
- **`contract.ts`** - TypeScript interfaces (provider-agnostic)
- **`schema.ts`** - Zod validation schemas (runtime safety)
- **`providers/`** - Provider-specific adapters (WHMCS, Salesforce, Freebit)

### 2. **Single Source of Truth**
- ✅ All types defined in domain package
- ✅ All validation schemas in domain package
- ✅ No duplicate type definitions in apps
- ✅ Shared between frontend (Next.js) and backend (NestJS)

### 3. **Type Safety + Runtime Validation**
- TypeScript provides compile-time type checking
- Zod schemas provide runtime validation
- Use `z.infer<typeof schema>` to derive types from schemas

---

## 📚 Usage Guide

### **Basic Import Pattern**

```typescript
// Import domain types and schemas
import { Invoice, invoiceSchema, InvoiceQueryParams } from "@customer-portal/domain/billing";
import { Subscription, subscriptionSchema } from "@customer-portal/domain/subscriptions";
import { ApiResponse, PaginationParams } from "@customer-portal/domain/common";
```

### **API Response Handling**

```typescript
import { 
  ApiResponse, 
  ApiSuccessResponse, 
  ApiErrorResponse,
  apiResponseSchema 
} from "@customer-portal/domain/common";

// Type-safe API responses
const response: ApiResponse<Invoice> = {
  success: true,
  data: { /* invoice data */ }
};

// With validation
const validated = apiResponseSchema(invoiceSchema).parse(rawResponse);
```

### **Query Parameters with Validation**

```typescript
import { 
  InvoiceQueryParams, 
  invoiceQueryParamsSchema 
} from "@customer-portal/domain/billing";

// In BFF controller
@Get()
@UsePipes(new ZodValidationPipe(invoiceQueryParamsSchema))
async getInvoices(@Query() query: InvoiceQueryParams) {
  // query is validated and typed
}

// In frontend
const params: InvoiceQueryParams = {
  page: 1,
  limit: 20,
  status: "Unpaid"
};
```

### **Form Validation (Frontend)**

```typescript
import { useZodForm } from "@customer-portal/validation";
import { loginRequestSchema, type LoginRequest } from "@customer-portal/domain/auth";

function LoginForm() {
  const form = useZodForm<LoginRequest>({
    schema: loginRequestSchema,
    initialValues: { email: "", password: "" },
    onSubmit: async (data) => {
      // data is validated and typed
      await login(data);
    },
  });

  return (
    <form onSubmit={form.handleSubmit}>
      {/* form fields */}
    </form>
  );
}
```

### **Backend Validation (BFF)**

```typescript
import { ZodValidationPipe } from "@bff/core/validation";
import { 
  createOrderRequestSchema, 
  type CreateOrderRequest 
} from "@customer-portal/domain/orders";

@Controller("orders")
export class OrdersController {
  @Post()
  @UsePipes(new ZodValidationPipe(createOrderRequestSchema))
  async create(@Body() body: CreateOrderRequest) {
    // body is validated by Zod before reaching here
    return this.orderService.create(body);
  }
}
```

---

## 🔧 Common Schemas Reference

### **API Responses**

| Schema | Description |
|--------|-------------|
| `apiSuccessResponseSchema(dataSchema)` | Successful API response wrapper |
| `apiErrorResponseSchema` | Error API response with code/message |
| `apiResponseSchema(dataSchema)` | Discriminated union of success/error |

### **Pagination & Queries**

| Schema | Description |
|--------|-------------|
| `paginationParamsSchema` | Page, limit, offset parameters |
| `paginatedResponseSchema(itemSchema)` | Paginated list response |
| `filterParamsSchema` | Search, sortBy, sortOrder |
| `queryParamsSchema` | Combined pagination + filters |

### **Domain-Specific Query Params**

| Schema | Description |
|--------|-------------|
| `invoiceQueryParamsSchema` | Invoice list filtering (status, dates) |
| `subscriptionQueryParamsSchema` | Subscription filtering (status, type) |
| `orderQueryParamsSchema` | Order filtering (status, orderType) |

### **Validation Primitives**

| Schema | Description |
|--------|-------------|
| `emailSchema` | Email validation (lowercase, trimmed) |
| `passwordSchema` | Strong password (8+ chars, mixed case, number, special) |
| `nameSchema` | Name validation (1-100 chars) |
| `phoneSchema` | Phone number validation |
| `timestampSchema` | ISO datetime string |
| `dateSchema` | ISO date string |

---

## 🚀 Adding New Domain Types

### 1. Create Domain Files

```typescript
// packages/domain/my-domain/contract.ts
export interface MyEntity {
  id: string;
  name: string;
  status: "active" | "inactive";
}

export interface MyEntityList {
  entities: MyEntity[];
  totalCount: number;
}

// packages/domain/my-domain/schema.ts
import { z } from "zod";

export const myEntityStatusSchema = z.enum(["active", "inactive"]);

export const myEntitySchema = z.object({
  id: z.string(),
  name: z.string().min(1),
  status: myEntityStatusSchema,
});

export const myEntityListSchema = z.object({
  entities: z.array(myEntitySchema),
  totalCount: z.number().int().nonnegative(),
});

// Query params
export const myEntityQueryParamsSchema = z.object({
  page: z.coerce.number().int().positive().optional(),
  limit: z.coerce.number().int().positive().max(100).optional(),
  status: myEntityStatusSchema.optional(),
});

export type MyEntityQueryParams = z.infer<typeof myEntityQueryParamsSchema>;

// packages/domain/my-domain/index.ts
export * from "./contract";
export * from "./schema";
```

### 2. Use in Backend (BFF)

```typescript
import { ZodValidationPipe } from "@bff/core/validation";
import { 
  myEntitySchema, 
  myEntityQueryParamsSchema,
  type MyEntity,
  type MyEntityQueryParams
} from "@customer-portal/domain/my-domain";

@Controller("my-entities")
export class MyEntitiesController {
  @Get()
  @UsePipes(new ZodValidationPipe(myEntityQueryParamsSchema))
  async list(@Query() query: MyEntityQueryParams): Promise<MyEntityList> {
    return this.service.list(query);
  }
}
```

### 3. Use in Frontend

```typescript
import { useQuery } from "@tanstack/react-query";
import { myEntitySchema, type MyEntity } from "@customer-portal/domain/my-domain";

function useMyEntities() {
  return useQuery({
    queryKey: ["my-entities"],
    queryFn: async () => {
      const response = await apiClient.get("/my-entities");
      return myEntitySchema.array().parse(response.data);
    },
  });
}
```

---

## ✅ Validation Best Practices

### 1. **Always Define Both Type and Schema**

```typescript
// ✅ Good - Type and schema together
export const userSchema = z.object({
  id: z.string(),
  email: emailSchema,
});
export type User = z.infer<typeof userSchema>;

// ❌ Bad - Type only (no runtime validation)
export interface User {
  id: string;
  email: string;
}
```

### 2. **Use Zod Schema Composition**

```typescript
// Base schema
const baseProductSchema = z.object({
  id: z.string(),
  name: z.string(),
});

// Extended schema
export const fullProductSchema = baseProductSchema.extend({
  description: z.string(),
  price: z.number().positive(),
});
```

### 3. **Query Params Use `z.coerce` for URL Strings**

```typescript
// ✅ Good - coerce string params to numbers
export const paginationSchema = z.object({
  page: z.coerce.number().int().positive().optional(),
  limit: z.coerce.number().int().positive().optional(),
});

// ❌ Bad - will fail on URL query strings
export const paginationSchema = z.object({
  page: z.number().int().positive().optional(), // "1" !== 1
});
```

### 4. **Use Refinements for Complex Validation**

```typescript
export const simActivationSchema = z.object({
  simType: z.enum(["eSIM", "Physical SIM"]),
  eid: z.string().optional(),
}).refine(
  (data) => data.simType !== "eSIM" || (data.eid && data.eid.length >= 15),
  { message: "EID required for eSIM", path: ["eid"] }
);
```

---

## 🔄 Migration from Local Types

If you find types defined locally in apps, migrate them to domain:

```typescript
// ❌ Before: apps/bff/src/modules/invoices/types.ts
export interface InvoiceQuery {
  status?: string;
  page?: number;
}

// ✅ After: packages/domain/billing/schema.ts
export const invoiceQueryParamsSchema = z.object({
  status: invoiceStatusSchema.optional(),
  page: z.coerce.number().int().positive().optional(),
});
export type InvoiceQueryParams = z.infer<typeof invoiceQueryParamsSchema>;

// Update imports
import { InvoiceQueryParams } from "@customer-portal/domain/billing";
```

---

## 📖 Additional Resources

- **Zod Documentation**: https://zod.dev/
- **Provider-Aware Architecture**: See `docs/DOMAIN-STRUCTURE.md`
- **Type System**: See `docs/CONSOLIDATED-TYPE-SYSTEM.md`

---

## 🤝 Contributing

When adding new types or schemas:

1. ✅ Define types in `contract.ts`
2. ✅ Add Zod schemas in `schema.ts`
3. ✅ Export from `index.ts`
4. ✅ Update this README if adding new patterns
5. ✅ Remove any duplicate types from apps
6. ✅ Update imports to use domain package

---

**Maintained by**: Customer Portal Team  
**Last Updated**: October 2025

