# NestJS Service - AI Generator Guidelines

These guidelines are for generating NestJS microservices.

---

## 🚨 CRITICAL: Commands Block (REQUIRED - MUST BE FIRST IN YOUR RESPONSE)

Your response **MUST** start with a commands block that scaffolds the NestJS service structure:

```commands
pf new nest-micro <service-path> -y
```

**Example:** If user asks for "orders service", generate:
```commands
pf new nest-micro services/orders -y
```

This command creates: `package.json`, `tsconfig.json`, NestJS structure, and basic files.
**Without this command, the service cannot be built or run!**

- Services MUST be in `services/` directory
- User says "orders" → Generate path: `services/orders`
- Always include `-y` flag to skip prompts

---

## NestJS Event Consumer Architecture

For NestJS event consumers, we use NestJS's application context to bridge
`@crossdelta/cloudevents` handlers with NestJS's dependency injection system.

### Entry Point (src/main.ts)

```ts
import '@crossdelta/telemetry'

import { NestFactory } from '@nestjs/core'
import { AppModule } from './app.module'
import { EventsService } from './events/events.service'

async function bootstrap() {
  const app = await NestFactory.create(AppModule)
  const port = Number(process.env.MY_SERVICE_PORT) || 8080

  // Get EventsService from DI container and start consuming
  const eventsService = app.get(EventsService)
  await eventsService.startConsumers()

  await app.listen(port)
  console.log(`Service running on http://localhost:${port}`)
}

bootstrap()
```

---

## 🚨 CRITICAL: Events Module Pattern (MUST follow EXACTLY)

Create a dedicated Events module that manages NATS consumption with auto-discovery.

### Events Module (src/events/events.module.ts) - MUST be like this!

```ts
import { Module } from '@nestjs/common'
import { EventsService } from './events.service'

@Module({
  providers: [EventsService],
  exports: [EventsService],
})
export class EventsModule {}
```

**⚠️ CRITICAL RULES:**
- ✅ MUST: Only provide `EventsService`
- ❌ DO NOT: Create `src/events/handlers/` subdirectory
- ❌ DO NOT: Import handlers from `handlers/` directory
- ❌ DO NOT: Register handlers as module providers
- Handlers are auto-discovered via glob pattern, NOT manually imported

### Events Service (src/events/events.service.ts)

```ts
import { Injectable, Logger } from '@nestjs/common'
import { consumeJetStreamStreams, ensureJetStreamStreams } from '@crossdelta/cloudevents'

@Injectable()
export class EventsService {
  private readonly logger = new Logger(EventsService.name)

  async startConsumers(): Promise<void> {
    await ensureJetStreamStreams({
      streams: [
        { stream: 'ORDERS', subjects: ['orders.*'] },
      ],
    })

    consumeJetStreamStreams({
      streams: ['ORDERS'],
      consumer: 'my-service',
      discover: './src/events/**/*.event.ts',  // ✅ CORRECT: Auto-discovers all handlers
    })
  }
}
```

### Event Handler (src/events/orders-created.event.ts) - MUST be here!

Event handlers in `src/events/*.event.ts` use `@crossdelta/cloudevents` for handling.
Auto-discovery finds them automatically - NO manual imports needed.

```ts
import { handleEvent } from '@crossdelta/cloudevents'
import { OrdersCreatedContract, type OrdersCreatedData } from '{{scope}}/contracts'

// ✅ CORRECT: Default export, auto-discovered by cloudevents
export default handleEvent(OrdersCreatedContract, async (data: OrdersCreatedData) => {
  console.log('📦 Processing order:', data.orderId)

  // For simple handlers, process directly
  // For complex logic with NestJS DI, see "Accessing NestJS Services" below
})
```

**⚠️ CRITICAL RULES for Event Handlers:**
- ✅ MUST: Use `export default handleEvent(...)`
- ✅ MUST: Be in `src/events/*.event.ts` (e.g., `orders-created.event.ts`)
- ❌ DO NOT: Create `src/events/handlers/` subdirectory
- ❌ DO NOT: Use named exports like `export const OrdersCreatedHandler`
- ❌ DO NOT: Import handlers in events.module.ts
- ❌ DO NOT: Register handlers as providers
- Handlers are auto-discovered via `discover: './src/events/**/*.event.ts'`

---

## Accessing NestJS Services in Handlers

For handlers that need NestJS DI (database, external services), use the app context:

### App Context Singleton (src/app.context.ts)

```ts
import type { INestApplication, Type } from '@nestjs/common'

let app: INestApplication

export const setAppContext = (nestApp: INestApplication): void => {
  app = nestApp
}

export const getAppContext = (): INestApplication => {
  if (!app) throw new Error('App context not initialized')
  return app
}

export const getService = <T>(serviceClass: Type<T>): T => {
  return getAppContext().get(serviceClass)
}
```

### Updated main.ts

```ts
import '@crossdelta/telemetry'

import { NestFactory } from '@nestjs/core'
import { AppModule } from './app.module'
import { setAppContext } from './app.context'
import { EventsService } from './events/events.service'

async function bootstrap() {
  const app = await NestFactory.create(AppModule)
  const port = Number(process.env.MY_SERVICE_PORT) || 8080

  // Make app context available to event handlers
  setAppContext(app)

  // Start event consumers
  const eventsService = app.get(EventsService)
  await eventsService.startConsumers()

  await app.listen(port)
  console.log(`Service running on http://localhost:${port}`)
}

bootstrap()
```

### Handler with DI Access (src/events/orders-created.event.ts)

```ts
import { handleEvent } from '@crossdelta/cloudevents'
import { OrdersCreatedContract, type OrdersCreatedData } from '{{scope}}/contracts'
import { getService } from '../app.context'
import { OrdersService } from '../orders/orders.service'

// ✅ CORRECT: Default export, auto-discovered
export default handleEvent(OrdersCreatedContract, async (data: OrdersCreatedData) => {
  // Get service from NestJS DI container
  const ordersService = getService(OrdersService)

  await ordersService.processOrder(data)
})
```

### Orders Service (src/orders/orders.service.ts)

```ts
import { Injectable, Logger } from '@nestjs/common'
import type { OrdersCreatedData } from '{{scope}}/contracts'

@Injectable()
export class OrdersService {
  private readonly logger = new Logger(OrdersService.name)

  async processOrder(data: OrdersCreatedData): Promise<void> {
    this.logger.log(`Processing order: ${data.orderId}`)

    // Business logic with full DI access
    // Can inject repositories, HTTP services, etc.
  }
}
```

---

## App Module Structure

```ts
// src/app.module.ts
import { Module } from '@nestjs/common'
import { AppController } from './app.controller'
import { EventsModule } from './events/events.module'
import { OrdersModule } from './orders/orders.module'

@Module({
  imports: [EventsModule, OrdersModule],
  controllers: [AppController],
})
export class AppModule {}
```

```ts
// src/app.controller.ts
import { Controller, Get } from '@nestjs/common'

@Controller()
export class AppController {
  @Get('health')
  health() {
    return { status: 'ok' }
  }
}
```

---

## Required File Structure for Event Consumer

```
✅ CORRECT:
services/my-service/
├── src/
│   ├── main.ts                          # Entry point
│   ├── app.module.ts                    # Root module
│   ├── app.controller.ts                # Health endpoint
│   ├── app.context.ts                   # App context for DI bridge
│   ├── events/
│   │   ├── events.module.ts             # Events module (ONLY EventsService)
│   │   ├── events.service.ts            # NATS consumer setup
│   │   ├── orders-created.event.ts      # Handler in src/events/*.event.ts
│   │   └── customers-updated.event.ts   # Handler in src/events/*.event.ts
│   └── orders/
│       ├── orders.module.ts
│       └── orders.service.ts
└── README.md

❌ WRONG (DO NOT CREATE):
services/my-service/
├── src/events/
│   ├── handlers/                        # ❌ NEVER create handlers/ subfolder
│   │   ├── orders-created.handler.ts    # ❌ NEVER put handlers here
│   │   └── customers-updated.handler.ts # ❌ NEVER put handlers here
│   └── events.module.ts (with imports)  # ❌ NEVER import handlers

packages/contracts/src/events/
├── orders-created.ts                    # Contract with schema
└── customers-updated.ts                 # Contract with schema
```

---

## Testing

### Unit Test for Service

```ts
// src/orders/orders.service.spec.ts
import { Test, TestingModule } from '@nestjs/testing'
import { OrdersService } from './orders.service'

describe('OrdersService', () => {
  let service: OrdersService

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [OrdersService],
    }).compile()

    service = module.get<OrdersService>(OrdersService)
  })

  it('should process order', async () => {
    const mockData = {
      orderId: 'ord_123',
      customerId: 'cust_456',
      total: 99.99,
    }

    await expect(service.processOrder(mockData)).resolves.not.toThrow()
  })
})
```

---

## Key Differences from Hono

| Aspect | Hono | NestJS |
|--------|------|--------|
| Entry point | `src/index.ts` | `src/main.ts` |
| HTTP server | Hono + Bun.serve | NestFactory.create |
| Event handlers | `handleEvent()` in `src/events/*.event.ts` | `handleEvent()` in `src/events/*.event.ts` |
| Business logic | Pure use-cases | `@Injectable()` Services |
| DI | Not used | Full NestJS DI via `getService()` |
| Testing | Direct function tests | `@nestjs/testing` utilities |

---

## Dev Command

```json
{
  "scripts": {
    "start:dev": "nest start --watch"
  }
}
```
