# Stack and Services Guide

## Overview

The **stack** section maps abstract **components** to concrete **services**. This allows you to:

1. Use multiple service providers for the same component
2. Route requests to different services based on context
3. Configure fallback services for resilience
4. Switch providers without changing component logic

---

## Core Concepts

### Components vs Services

**Components** are **logical/abstract** parts of your system:
```yaml
components:
  auth:
    description: "Authentication and authorization"
    emits: ["user_authenticated"]
  payment:
    description: "Payment processing"
    emits: ["payment_completed"]
```

**Services** are **concrete implementations**:
```yaml
stack:
  auth:
    services:
      clerk:
        type: auth_provider
        config:
          publishable_key: "pk_..."
```

### Validation

All component references in `stack` must exist in `components`:

```yaml
# ✅ Valid
components:
  auth:
    description: "Authentication"

stack:
  auth:  # References defined component
    services: {...}

# ❌ Invalid
stack:
  payment:  # Error: payment not defined in components
    services: {...}
```

---

## Single Service per Component

### Basic Configuration

```yaml
components:
  auth:
    description: "User authentication"
    emits: ["user_authenticated"]

stack:
  auth:
    services:
      clerk:
        type: auth_provider
        config:
          publishable_key: "pk_test_..."
          secret_key: "sk_test_..."
```

**Generated code**:
```typescript
// src/services/auth.ts
import { Clerk } from '@clerk/clerk-sdk-node';

export const authService = new Clerk({
  publishableKey: process.env.CLERK_PUBLISHABLE_KEY,
  secretKey: process.env.CLERK_SECRET_KEY,
});

// Usage in components
export async function authenticateUser(token: string) {
  const user = await authService.verifyToken(token);
  return user;
}
```

---

## Multiple Services: Primary Service

Use one service as the default with explicit primary designation:

```yaml
stack:
  payment:
    primary: stripe  # Use Stripe as default
    services:
      stripe:
        type: payment_processor
        config:
          api_key: "{{ env.STRIPE_API_KEY }}"

      paypal:
        type: payment_processor
        config:
          client_id: "{{ env.PAYPAL_CLIENT_ID }}"
          client_secret: "{{ env.PAYPAL_CLIENT_SECRET }}"
```

**Generated code**:
```typescript
// src/services/payment.ts
import Stripe from 'stripe';
import paypal from '@paypal/checkout-server-sdk';

// Primary service
export const stripe = new Stripe(process.env.STRIPE_API_KEY);

// Alternative service
export const paypalClient = new paypal.core.PayPalHttpClient(
  new paypal.core.SandboxEnvironment(
    process.env.PAYPAL_CLIENT_ID,
    process.env.PAYPAL_CLIENT_SECRET
  )
);

// Default payment method uses primary
export async function processPayment(amount: number, currency: string) {
  // Uses Stripe (primary)
  return await stripe.paymentIntents.create({
    amount,
    currency,
  });
}

// Explicit service selection
export async function processPaymentWithPayPal(amount: number, currency: string) {
  const request = new paypal.orders.OrdersCreateRequest();
  request.requestBody({ /* ... */ });
  return await paypalClient.execute(request);
}
```

---

## Context-Based Routing

Route to different services based on context (e.g., user type, feature flags):

### Example: Auth with Clerk + Custom JWT

```yaml
stack:
  auth:
    primary: clerk
    routing: context_based  # Enable context-based routing

    services:
      clerk:
        type: auth_provider
        use_for: ["signup", "user_login", "social_auth"]  # Customer-facing
        config:
          publishable_key: "{{ env.CLERK_PUBLISHABLE_KEY }}"

      custom_jwt:
        type: auth_provider
        use_for: ["admin_login", "api_key_auth"]  # Internal/admin
        config:
          secret: "{{ env.JWT_SECRET }}"
          issuer: "myapp"
```

**Generated code**:
```typescript
// src/services/auth.ts
import { Clerk } from '@clerk/clerk-sdk-node';
import jwt from 'jsonwebtoken';

// Service instances
const clerk = new Clerk({
  publishableKey: process.env.CLERK_PUBLISHABLE_KEY,
});

const jwtConfig = {
  secret: process.env.JWT_SECRET,
  issuer: 'myapp',
};

// Context-based routing
type AuthContext = 'signup' | 'user_login' | 'social_auth' | 'admin_login' | 'api_key_auth';

export async function authenticate(token: string, context: AuthContext) {
  switch (context) {
    case 'signup':
    case 'user_login':
    case 'social_auth':
      // Use Clerk for customer-facing auth
      return await clerk.verifyToken(token);

    case 'admin_login':
    case 'api_key_auth':
      // Use custom JWT for internal/admin
      return jwt.verify(token, jwtConfig.secret, { issuer: jwtConfig.issuer });

    default:
      throw new Error(`Unknown auth context: ${context}`);
  }
}
```

### Example: Payment Routing by Region

```yaml
stack:
  payment:
    routing: context_based

    services:
      stripe:
        type: payment_processor
        use_for: ["us", "ca", "eu"]  # North America and Europe
        config:
          api_key: "{{ env.STRIPE_API_KEY }}"

      razorpay:
        type: payment_processor
        use_for: ["in", "bd", "pk"]  # South Asia
        config:
          key_id: "{{ env.RAZORPAY_KEY_ID }}"
          key_secret: "{{ env.RAZORPAY_KEY_SECRET }}"

      adyen:
        type: payment_processor
        use_for: ["*"]  # Fallback for all other regions
        fallback: true
        config:
          api_key: "{{ env.ADYEN_API_KEY }}"
```

**Generated code**:
```typescript
// src/services/payment.ts
import Stripe from 'stripe';
import Razorpay from 'razorpay';
import { Client as AdyenClient } from '@adyen/api-library';

const stripe = new Stripe(process.env.STRIPE_API_KEY);
const razorpay = new Razorpay({
  key_id: process.env.RAZORPAY_KEY_ID,
  key_secret: process.env.RAZORPAY_KEY_SECRET,
});
const adyen = new AdyenClient({
  apiKey: process.env.ADYEN_API_KEY,
});

// Region-based routing
const REGION_ROUTING = {
  stripe: ['us', 'ca', 'eu'],
  razorpay: ['in', 'bd', 'pk'],
};

export async function processPayment(
  amount: number,
  currency: string,
  region: string
) {
  // Route by region
  if (REGION_ROUTING.stripe.includes(region)) {
    return await stripe.paymentIntents.create({ amount, currency });
  }

  if (REGION_ROUTING.razorpay.includes(region)) {
    return await razorpay.orders.create({
      amount: amount * 100, // Razorpay uses paise
      currency,
    });
  }

  // Fallback to Adyen
  return await adyen.checkout.payments({
    amount: { value: amount, currency },
  });
}
```

---

## Fallback Services

Configure backup services for resilience:

```yaml
stack:
  email:
    primary: sendgrid
    routing: primary_only

    services:
      sendgrid:
        type: email_provider
        config:
          api_key: "{{ env.SENDGRID_API_KEY }}"

      ses:
        type: email_provider
        fallback: true  # Use if SendGrid fails
        config:
          region: "us-east-1"
          access_key: "{{ env.AWS_ACCESS_KEY }}"
          secret_key: "{{ env.AWS_SECRET_KEY }}"
```

**Generated code**:
```typescript
// src/services/email.ts
import sgMail from '@sendgrid/mail';
import { SESClient, SendEmailCommand } from '@aws-sdk/client-ses';

sgMail.setApiKey(process.env.SENDGRID_API_KEY);

const ses = new SESClient({
  region: 'us-east-1',
  credentials: {
    accessKeyId: process.env.AWS_ACCESS_KEY,
    secretAccessKey: process.env.AWS_SECRET_KEY,
  },
});

export async function sendEmail(to: string, subject: string, html: string) {
  try {
    // Try primary service (SendGrid)
    await sgMail.send({
      to,
      from: 'noreply@myapp.com',
      subject,
      html,
    });
  } catch (error) {
    console.error('SendGrid failed, falling back to SES:', error);

    // Fallback to SES
    await ses.send(new SendEmailCommand({
      Source: 'noreply@myapp.com',
      Destination: { ToAddresses: [to] },
      Message: {
        Subject: { Data: subject },
        Body: { Html: { Data: html } },
      },
    }));
  }
}
```

---

## Load-Balanced Services

Distribute load across multiple service instances:

```yaml
stack:
  storage:
    routing: load_balanced

    services:
      s3_us_east:
        type: object_storage
        config:
          provider: "s3"
          region: "us-east-1"
          bucket: "myapp-storage-us-east"

      s3_us_west:
        type: object_storage
        config:
          provider: "s3"
          region: "us-west-2"
          bucket: "myapp-storage-us-west"

      s3_eu:
        type: object_storage
        config:
          provider: "s3"
          region: "eu-west-1"
          bucket: "myapp-storage-eu"
```

**Generated code**:
```typescript
// src/services/storage.ts
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';

const s3Clients = {
  us_east: new S3Client({ region: 'us-east-1' }),
  us_west: new S3Client({ region: 'us-west-2' }),
  eu: new S3Client({ region: 'eu-west-1' }),
};

const buckets = {
  us_east: 'myapp-storage-us-east',
  us_west: 'myapp-storage-us-west',
  eu: 'myapp-storage-eu',
};

// Round-robin load balancing
let currentIndex = 0;
const regions = ['us_east', 'us_west', 'eu'];

export async function uploadFile(key: string, body: Buffer) {
  // Get next region in rotation
  const region = regions[currentIndex];
  currentIndex = (currentIndex + 1) % regions.length;

  // Upload to selected region
  await s3Clients[region].send(new PutObjectCommand({
    Bucket: buckets[region],
    Key: key,
    Body: body,
  }));

  return { region, key };
}
```

---

## Complete Example: Multi-Service SaaS Stack

```yaml
components:
  auth:
    description: "Authentication and authorization"
    emits: ["user_authenticated", "user_logged_out"]

  payment:
    description: "Payment processing"
    emits: ["payment_completed", "payment_failed"]

  email:
    description: "Email notifications"
    consumes: ["user_authenticated", "payment_completed"]

  storage:
    description: "File storage"
    emits: ["file_uploaded"]

stack:
  # Auth: Context-based (Clerk for users, JWT for admins)
  auth:
    primary: clerk
    routing: context_based
    services:
      clerk:
        type: auth_provider
        use_for: ["signup", "user_login", "social_auth"]
        config:
          publishable_key: "{{ env.CLERK_PUBLISHABLE_KEY }}"

      custom_jwt:
        type: auth_provider
        use_for: ["admin_login", "api_key_auth"]
        config:
          secret: "{{ env.JWT_SECRET }}"

  # Payment: Primary with fallback
  payment:
    primary: stripe
    routing: primary_only
    services:
      stripe:
        type: payment_processor
        config:
          api_key: "{{ env.STRIPE_API_KEY }}"

      paypal:
        type: payment_processor
        fallback: true
        config:
          client_id: "{{ env.PAYPAL_CLIENT_ID }}"

  # Email: Primary with fallback
  email:
    primary: sendgrid
    routing: primary_only
    services:
      sendgrid:
        type: email_provider
        config:
          api_key: "{{ env.SENDGRID_API_KEY }}"

      ses:
        type: email_provider
        fallback: true
        config:
          region: "us-east-1"

  # Storage: Load-balanced across regions
  storage:
    routing: load_balanced
    services:
      s3_us:
        type: object_storage
        config:
          region: "us-east-1"
          bucket: "myapp-storage-us"

      s3_eu:
        type: object_storage
        config:
          region: "eu-west-1"
          bucket: "myapp-storage-eu"

      s3_asia:
        type: object_storage
        config:
          region: "ap-southeast-1"
          bucket: "myapp-storage-asia"
```

---

## Inferring Services from YAML

The code generator can infer services from multiple locations:

### 1. Technology Stack

```yaml
technology:
  backend:
    framework: express
    orm: prisma
  database:
    type: postgresql
```

**Inferred**: Use Prisma ORM with PostgreSQL database

### 2. Stage Infrastructure

```yaml
stages:
  production:
    infrastructure:
      services:
        redis:
          provider: "upstash"
        queue:
          provider: "bullmq"
```

**Inferred**: Use Upstash Redis and BullMQ for queues

### 3. Stack Services

```yaml
stack:
  auth:
    services:
      clerk:
        type: auth_provider
```

**Inferred**: Use Clerk for authentication component

---

## Routing Modes Summary

| Mode | Use Case | Example |
|------|----------|---------|
| `primary_only` | Single service with optional fallback | Stripe (primary) + PayPal (fallback) |
| `context_based` | Different services for different contexts | Clerk (users) + JWT (admins) |
| `load_balanced` | Distribute load across instances | Multi-region S3 buckets |

---

## Best Practices

### 1. Always Define Primary Service

```yaml
# ✅ Good
stack:
  payment:
    primary: stripe
    services:
      stripe: {...}
      paypal: {...}

# ❌ Ambiguous (which is default?)
stack:
  payment:
    services:
      stripe: {...}
      paypal: {...}
```

### 2. Use Context-Based for Different User Types

```yaml
# ✅ Good: Clear separation
stack:
  auth:
    routing: context_based
    services:
      clerk:
        use_for: ["user_login"]
      custom_jwt:
        use_for: ["admin_login"]
```

### 3. Configure Fallbacks for Critical Services

```yaml
# ✅ Good: Resilient
stack:
  email:
    primary: sendgrid
    services:
      sendgrid: {...}
      ses:
        fallback: true
```

### 4. Load Balance Across Regions

```yaml
# ✅ Good: Geographic distribution
stack:
  storage:
    routing: load_balanced
    services:
      s3_us: {...}
      s3_eu: {...}
      s3_asia: {...}
```

---

## Summary

**Key Features**:
- ✅ Multiple services per component
- ✅ Primary service designation
- ✅ Context-based routing (user type, region, feature flags)
- ✅ Fallback services for resilience
- ✅ Load balancing across instances
- ✅ All stack components must reference defined components (validated)

**Generated Code**:
- Service clients for all configured services
- Routing logic based on configuration
- Fallback handling with automatic retry
- Load balancing with round-robin or custom strategies

This system allows **flexible service composition** while maintaining **type safety** and **referential integrity**.
