# Service Generator Instructions

**Generic guidelines for all service types. Framework-specific details are in separate files:**
- `hono-bun.md` - Hono with Bun runtime
- `hono-n```post-commands
pf event add orders.created --service services/my-notifications
pf event add customers.updated --service services/my-notifications
```md` - Hono with Node.js runtime (tsx + @hono/node-server)
- `nest.md` - NestJS framework

---

## 🚨 CRITICAL: @crossdelta/cloudevents API

**ONLY these exports exist in @crossdelta/cloudevents:**
- `ensureJetStreamStreams` - create/ensure multiple JetStream streams (preferred)
- `consumeJetStreamStreams` - consume from multiple streams (preferred)
- `ensureJetStreamStream` - create/ensure a single stream (legacy)
- `consumeJetStreamEvents` - consume from single stream (legacy)
- `handleEvent` - create event handler
- `publish` - publish events
- `createContract` - create shared event contract

**DO NOT USE (these do NOT exist):**
- ❌ `stream` - DOES NOT EXIST
- ❌ `consumer` - DOES NOT EXIST
- ❌ `subscribe` - DOES NOT EXIST
- ❌ Any other function not listed above

**EXACT function signatures (use batch versions for multiple streams):**
```ts
// Setup multiple streams at once (PREFERRED)
await ensureJetStreamStreams({
  streams: [
    { stream: 'ORDERS', subjects: ['orders.*'] },
    { stream: 'CUSTOMERS', subjects: ['customers.*'] },
  ]
})

// Consume from multiple streams (PREFERRED)
consumeJetStreamStreams({
  streams: ['ORDERS', 'CUSTOMERS'],
  consumer: 'my-service',
  discover: './src/events/**/*.event.ts',
})
```

**WRONG parameter names (DO NOT USE):**
- ❌ `name:` - use `stream:` instead
- ❌ `handler:` - use `discover:` instead (auto-discovery pattern)
- ❌ `topic:` - use `subjects:` instead

---

## 🚨 CRITICAL: @crossdelta/telemetry Import

**Telemetry is a side-effect import. Use EXACTLY:**
```ts
import '@crossdelta/telemetry'
```

**DO NOT USE:**
- ❌ `@crossdelta/telemetry/register` - DOES NOT EXIST
- ❌ `import { ... } from '@crossdelta/telemetry'` - No named exports needed
- ❌ Any subpath imports

---

## 🚨 CRITICAL: Event Consumer Workflow

**FOR EVENT CONSUMER SERVICES (services that "consume", "listen to", "react to" events):**

### What YOU (the AI) generate for Event Consumers:

1. **`packages/contracts/src/events/<event>.ts`** - Contract with **correct schema fields**
2. **`src/index.ts`** - Entry point with NATS stream/consumer setup
3. **`src/events/<event>.event.ts`** - Event handler that calls the use-case
4. **`src/use-cases/*.use-case.ts`** - Business logic (called by handlers)
5. **`src/use-cases/*.test.ts`** - Tests for use-cases
6. **`README.md`** - Documentation

The `pf event add` commands in `post-commands` will create:
- Mock data for testing
- Index exports (if not already present)

### Event Handler Template (YOU write this!)

```typescript
// src/events/orders-created.event.ts
import { handleEvent } from '@crossdelta/cloudevents'
import { OrdersCreatedContract, type OrdersCreatedData } from '{{scope}}/contracts'
import { sendOrderNotification } from '../use-cases/send-order-notification.use-case'

export default handleEvent(OrdersCreatedContract, async (data: OrdersCreatedData) => {
  await sendOrderNotification(data)
})
```

**Rules:**
- Handler imports the Contract from `{{scope}}/contracts`
- Handler calls the corresponding use-case function
- Handler is thin - NO business logic, just delegation
- Use-case name should match the handler purpose (e.g., `send-order-notification` for a notification service)

### Handler Location & Structure (CRITICAL!)

**⚠️ MUST be in `src/events/*.event.ts` - NEVER in `handlers/` subdirectory**

```
✅ CORRECT:
services/my-service/src/events/
├── orders-created.event.ts      # Handler here
├── customers-updated.event.ts   # Handler here
└── events.service.ts

❌ WRONG (DO NOT CREATE):
services/my-service/src/events/
├── handlers/                     # NEVER create this folder!
│   └── orders-created.handler.ts # Handlers NEVER here!
└── events.module.ts (with named imports)
```

**Handler Export Pattern:**

```typescript
// ✅ CORRECT: Default export
export default handleEvent(OrdersCreatedContract, async (data) => {
  // Handler code
})

// ❌ WRONG: Named export
export const OrdersCreatedHandler = handleEvent(...)
```

**Module Pattern:**

```typescript
// ✅ CORRECT: EventsModule only provides EventsService
import { Module } from '@nestjs/common'
import { EventsService } from './events.service'

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

// ❌ WRONG: Importing handlers and registering as providers
import { OrdersCreatedHandler } from './handlers/orders-created.handler'

@Module({
  providers: [EventsService, OrdersCreatedHandler],  // ❌ WRONG!
})
```

**Auto-Discovery Mechanism:**

Handlers are **automatically discovered** by the EventsService via glob pattern. Do NOT manually import them:

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

// ❌ WRONG: Manual handler imports not needed - discovery handles it!
import { OrdersCreatedHandler } from './events/orders-created.event'  // Don't do this
```

**Common Mistakes (from real failures):**

| Mistake | Why It Fails | Solution |
|---------|-------------|----------|
| Create `/handlers` subdirectory | Breaks auto-discovery path; creates duplicate files | Keep ALL handlers in `src/events/*.event.ts` |
| Named exports: `export const OrdersCreatedHandler` | CLI doesn't recognize as handler; causes import/export mismatches | Use `export default handleEvent(...)` ALWAYS |
| Import handlers in `events.module.ts` | Auto-discovery doesn't require imports; causes TS2614 errors | Remove handler imports; let discover glob find them |
| Register handlers as module providers | Not a NestJS injectable; breaks DI container | Only register EventsService as provider |

**Real-World Example of the Error:**

```
// ❌ What went wrong in test-ws-jack
src/events/events.module.ts:
  import { OrdersCreatedHandler } from './handlers/orders-created.handler'  ← File doesn't exist with named export

Error: TS2614 [ERROR]: Module "/services/my-nest-service/src/events/orders-created.event.ts" has no exported member "OrdersCreatedHandler"

Reason: Handler is `export default handleEvent(...)`, NOT `export const OrdersCreatedHandler`
        Folder structure should NOT have `/handlers` subdirectory
```

**Checklist Before Committing Handler Code:**

- [ ] All handlers in `src/events/*.event.ts` (NOT in subdirectories)
- [ ] Handler use `export default handleEvent(Contract, ...)`
- [ ] `events.module.ts` only provides `EventsService`
- [ ] `events.service.ts` uses `discover: './src/events/**/*.event.ts'`
- [ ] No `/handlers` subdirectory exists
- [ ] No handler imports in `events.module.ts`

### Contract Schema (YOU write this!)

**Create the contract with the EXACT fields your use-case needs.**

**⚠️ Zod 4 Syntax:** Use `z.string().email()` without parameters. For custom messages, use `.check()`:

```typescript
// packages/contracts/src/events/orders-created.ts
import { createContract } from '@crossdelta/cloudevents'
import { z } from 'zod'

export const OrdersCreatedSchema = z.object({
  orderId: z.string(),
  customerId: z.string(),
  email: z.string().email(),              // ✅ Zod 4: no params
  total: z.number(),
  items: z.array(z.object({
    productId: z.string(),
    quantity: z.number(),
  })),
  createdAt: z.string().datetime(),       // ✅ Zod 4: no params
})

export const OrdersCreatedContract = createContract({
  type: 'orders.created',
  schema: OrdersCreatedSchema,
})

export type OrdersCreatedData = z.infer<typeof OrdersCreatedContract.schema>
```

**Zod 4 Quick Reference:**
- ✅ `z.string().email()` - no params
- ✅ `z.string().url()` - no params
- ✅ `z.string().uuid()` - no params
- ✅ `z.string().datetime()` - no params
- ❌ `z.string().email('Invalid')` - DEPRECATED

### Naming Convention (CRITICAL!)

**Event Type → Contract Name → File Name:**

| Event Type | Contract Name | File Name |
|------------|---------------|-----------|
| `orders.created` | `OrdersCreatedContract` | `orders-created.ts` |
| `users.registered` | `UsersRegisteredContract` | `users-registered.ts` |
| `payments.completed` | `PaymentsCompletedContract` | `payments-completed.ts` |
| `customers.updated` | `CustomersUpdatedContract` | `customers-updated.ts` |

**⚠️ ALWAYS use PLURAL namespace:**
- ✅ `orders.created`, `customers.updated`, `payments.completed`
- ❌ `order.created`, `customer.updated`, `payment.completed`

This ensures JetStream stream names match: `ORDERS`, `CUSTOMERS`, `PAYMENTS`

---

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

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

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

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

This command creates: `package.json`, `tsconfig.json`, `Dockerfile`, and basic structure.
**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

---

## 🚨 CRITICAL: Post-Generation Commands (REQUIRED for Event Consumers)

**For Event Consumer services, you MUST include a `post-commands` block with ALL events the service listens to.**

Your response MUST include this block right after the files:

```post-commands
pf event add <event.type> --service <service-path>
```

**Example:** Service that listens to `orders.created` AND `customers.updated`:
```post-commands
pf event add orders.created --service services/notifications
pf event add customers.updated --service services/notifications
```

**What `pf event add` creates:**
- `src/events/<event>.event.ts` - Handler file that imports your contract
- `packages/contracts/src/events/<event>.mock.json` - Test mock data
- Adds export to `packages/contracts/src/index.ts`

**⚠️ IMPORTANT:** `pf event add` skips contract creation if it already exists - so YOUR contract with the correct schema is preserved!

---

## 🚨 CRITICAL: Response Structure for Event Consumers

Your response MUST follow this exact structure:

1. **`commands` block** (scaffold service)
2. **Contract files** (`packages/contracts/src/events/<event>.ts` with correct schema)
3. **Service files** (src/index.ts, use-cases, tests, README)
4. **`post-commands` block** (wire event handlers via CLI)
5. **`dependencies` block** (if needed)

**Example complete response structure:**

\`\`\`commands
pf new hono-micro services/my-notifications -y
\`\`\`

#### \`packages/contracts/src/events/orders-created.ts\`
\`\`\`typescript
import { createContract } from '@crossdelta/cloudevents'
import { z } from 'zod'

export const OrdersCreatedSchema = z.object({
  orderId: z.string(),
  customerId: z.string(),
  total: z.number(),
})

export const OrdersCreatedContract = createContract({
  type: 'orders.created',
  schema: OrdersCreatedSchema,
})

export type OrdersCreatedData = z.infer<typeof OrdersCreatedContract.schema>
\`\`\`

#### \`src/index.ts\`
\`\`\`typescript
// Entry point code...
\`\`\`

#### \`src/use-cases/send-notification.use-case.ts\`
\`\`\`typescript
// Use-case code using OrdersCreatedData type...
\`\`\`

\`\`\`post-commands
pf event add orders.created --service services/my-notifications
pf event add customer.updated --service services/my-notifications
\`\`\`

\`\`\`dependencies
@pusher/push-notifications-server
\`\`\`

---

## Dependencies Block

Only include packages **not already in the workspace root**:

```dependencies
@pusher/push-notifications-server
drizzle-orm
```

**Note:** `{{scope}}/contracts`, `@crossdelta/cloudevents`, and `@crossdelta/telemetry` are already available via workspace root – do NOT add them here.

---

## Output Format

**CRITICAL: Response MUST use Markdown format with specific block markers!**

### Commands Block (REQUIRED - at start)

```commands
pf new hono-micro services/my-service -y
```

### File Blocks

Each file uses a header with backtick-wrapped path, followed by a code block:

#### `packages/contracts/src/events/orders-created.ts`
```typescript
import { createContract } from '@crossdelta/cloudevents'
import { z } from 'zod'

export const OrdersCreatedSchema = z.object({
  orderId: z.string(),
  total: z.number(),
})

export const OrdersCreatedContract = createContract({
  type: 'orders.created',
  schema: OrdersCreatedSchema,
})

export type OrdersCreatedData = z.infer<typeof OrdersCreatedContract.schema>
```

#### `src/index.ts`
```typescript
import '@crossdelta/telemetry'

import { Hono } from 'hono'
// ... rest of code
```

### Post-Commands Block (after files)

```post-commands
pf event add orders.created --service services/my-service
```

### Dependencies Block (if needed)

```dependencies
@pusher/push-notifications-server
lodash
```

### Complete Example Response

```commands
pf new hono-micro services/my-service -y
```

#### `packages/contracts/src/events/orders-created.ts`
```typescript
import { createContract } from '@crossdelta/cloudevents'
import { z } from 'zod'

export const OrdersCreatedSchema = z.object({
  orderId: z.string(),
  total: z.number(),
})

export const OrdersCreatedContract = createContract({
  type: 'orders.created',
  schema: OrdersCreatedSchema,
})

export type OrdersCreatedData = z.infer<typeof OrdersCreatedContract.schema>
```

#### `src/index.ts`
```typescript
import '@crossdelta/telemetry'

import { Hono } from 'hono'
import { consumeJetStreamStreams, ensureJetStreamStreams } from '@crossdelta/cloudevents'

const app = new Hono()
const port = Number(process.env.PORT || process.env.MY_SERVICE_PORT) || 8080

const start = async () => {
  await ensureJetStreamStreams({
    streams: [{ stream: 'ORDERS', subjects: ['orders.*'] }],
  })

  consumeJetStreamStreams({
    streams: ['ORDERS'],
    consumer: 'my-service',
    discover: './src/events/**/*.event.ts',
  })

  app.get('/health', (c) => c.json({ status: 'ok' }))

  return app.listen(port, () => {
    console.log(`Server running on http://localhost:${port}`)
  })
}

start().catch(console.error)
```

#### `src/events/orders-created.event.ts`
```typescript
import { handleEvent } from '@crossdelta/cloudevents'
import { OrdersCreatedContract, type OrdersCreatedData } from '@my-scope/contracts'
import { processOrder } from '../use-cases/process-order.use-case'

export default handleEvent(OrdersCreatedContract, async (data: OrdersCreatedData) => {
  console.log('📦 Processing order:', data.orderId)
  await processOrder(data)
})
```

#### `src/use-cases/process-order.use-case.ts`
```typescript
import type { OrdersCreatedData } from '@my-scope/contracts'

export const processOrder = async (data: OrdersCreatedData): Promise<void> => {
  console.log('Order processed:', data.orderId)
  // Add your business logic here
}
```

#### `src/use-cases/process-order.test.ts`
```typescript
import { describe, expect, it } from 'bun:test'
import { processOrder } from './process-order.use-case'

describe('processOrder', () => {
  it('should process order', async () => {
    const data = { orderId: '123', total: 100 }
    await expect(processOrder(data)).resolves.toBeUndefined()
  })
})
```

#### `README.md`
```markdown
# My Service

Event consumer that listens to `orders.created`.

## Events

- `orders.created` → processes order
```

```post-commands
pf event add orders.created --service services/my-service
```

```dependencies
```

### Format Rules

1. **Commands block** MUST come first with `pf new` command
2. **File headers** use `#### \`path/to/file.ts\`` format
3. **Code blocks** use triple backticks with language (typescript, markdown, etc.)
4. **Post-commands** block after all files
5. **Dependencies** block at end (can be empty)
6. **Paths**: Use relative paths for service files, full paths for contracts

---

## Required Files

**Event Consumer (what YOU generate):**
```
services/my-service/
├── src/
│   ├── index.ts                       # Entry point (YOU create this)
│   ├── events/
│   │   └── orders-created.event.ts    # Handler calling use-case (YOU create this)
│   └── use-cases/
│       ├── process-order.use-case.ts  # Business logic (YOU create this)
│       └── process-order.test.ts      # Tests (YOU create this)
└── README.md                          # Documentation (YOU create this)

packages/contracts/src/events/
└── orders-created.ts                  # Contract with schema (YOU create this)
```

**Created by `pf event add` (mock data only):**
```
packages/contracts/src/events/
└── orders-created.mock.json           # Created by CLI
```

**Event Publisher:**
```
services/my-service/
├── src/
│   ├── [entry-point]              # Framework-specific entry point
│   └── use-cases/
│       ├── create-order.use-case.ts
│       └── create-order.test.ts
└── README.md
```

**IMPORTANT:** Do NOT create `src/types/` directories. All types come from contracts:
- `OrdersCreatedData` → import from `@scope/contracts`
- Custom request/response types → define inline or in use-case files

---

## Service Types

### 📤 Event Publisher (REST API)

**Keywords:** "publishes", "creates", "manages", "REST API"

Services that expose HTTP endpoints and publish events to NATS.

### 📥 Event Consumer (NATS)

**Keywords:** "consumes", "listens to", "reacts to", "handles events"

Services that listen to NATS events and process them. See 3-Step Workflow above.

### 🔄 Hybrid (Both)

Combines REST endpoints + NATS consumer.

---

## Use-Case Rules

- Live in `src/use-cases/*.use-case.ts`
- Pure functions, no framework imports
- Import types from `@scope/contracts`
- Inject dependencies as parameters (Map, services, etc.)
- NO manual validation (adapters handle that)
- **Event handlers:** Always log the event type and a key identifier (e.g., `console.log('📦 Processing order:', data.orderId)`) at the start of the use-case for debugging visibility

```ts
import type { OrdersCreatedData } from '{{scope}}/contracts'

export const processOrder = async (
  data: OrdersCreatedData,
  orderStore: Map<string, any>
) => {
  console.log('📦 Processing order:', data.orderId)

  orderStore.set(data.orderId, { ...data, processedAt: new Date() })
  return { success: true }
}
```

---

## Testing Rules

- Test ONLY use-cases
- Use appropriate test framework for the runtime
- Focus on error handling and validation
- No mocking - prefer simple direct tests

Example:
```ts
import { describe, expect, it } from 'bun:test'
import { processOrder } from './process-order.use-case'

describe('Process Order', () => {
  it('should store order', async () => {
    const store = new Map()
    const result = await processOrder({ orderId: '123', customerId: 'c1', total: 100 }, store)
    expect(store.has('123')).toBe(true)
  })
})
```

---

## Code Quality Guidelines

- ✅ **Verify package names** on npmjs.com before using
- ✅ Use exact package names: `@crossdelta/cloudevents`, `@crossdelta/telemetry`
- ❌ **DO NOT hallucinate** APIs or package names

---

## Absolute Rules

**DO NOT:**
- Generate full rewrites
- Use raw NATS clients
- Put business logic in handlers or entry points
- Use `src/handlers/` (use `src/events/`)
- Insert semicolons
- Edit `packages/contracts/src/index.ts` directly (CLI handles exports)

**DO:**
- Follow naming convention (plural event types)
- Create contracts in `packages/contracts/src/events/`
- Create event handlers in `src/events/` that call use-cases
- Export schema separately from contract
- Keep handlers thin - delegate to use-cases
- Keep code minimal and clean
- Run `pf audit` after adding new dependencies to check for vulnerabilities
