# Extended Workflow Schema Guide

The extended schema builds on the event-driven architecture to include everything needed for complete code generation.

## What's Required vs Optional

### ✅ REQUIRED Sections

These must be in every workflow YAML:

#### 1. Technology Stack
```yaml
technology:
  frontend:
    framework: react
    language: typescript
    styling: tailwind
  backend:
    framework: express
    language: typescript
    orm: prisma
  database:
    type: postgresql
```

**Why**: Generators need to know what language/framework to generate code for.

#### 2. Data Models
```yaml
models:
  User:
    fields:
      email:
        type: string
        required: true
        unique: true
      name:
        type: string
        required: true
      role:
        type: enum
        enum_values: [admin, user, guest]
        default: user
```

**Why**: Database schema and API need explicit data structure.

#### 3. Event-Driven Core
```yaml
events: { ... }
actors: { ... }
components: { ... }
workflows: { ... }
stack: { ... }
```

**Why**: These define the system architecture (from base schema).

---

### 🔧 OPTIONAL Sections

These can be omitted - generator will infer reasonable defaults:

#### API Endpoints
If not specified, generator infers from workflows and models.

```yaml
api:
  endpoints:
    - path: /products
      method: GET
      component: catalog
      response: Product[]
```

**When to specify**:
- Custom paths or methods
- Special auth requirements
- Non-standard behavior

#### UI Pages
If not specified, generator creates pages based on workflows.

```yaml
ui:
  pages:
    - path: /products
      title: Products
      layout: grid
      data_source: /api/products
```

**When to specify**:
- Custom layouts
- Specific page organization
- Non-standard navigation

#### Design Theme
If not specified, uses sensible defaults.

```yaml
design:
  theme:
    colors:
      primary: "#3B82F6"
      secondary: "#10B981"
```

**When to specify**:
- Brand colors
- Custom styling
- Specific design requirements

#### Cache Configuration
If not specified, no event-driven architecture is generated.

```yaml
technology:
  cache:
    provider: redis         # or 'upstash-redis' for serverless
    use: local             # or 'cloud' for Upstash
```

**When to specify**:
- Event-driven architecture required
- Real-time WebSocket updates needed
- Pub/sub messaging required

#### Events Definition
If not specified, no event infrastructure is generated.

```yaml
events:
  task_created:
    description: New task created
    payload:
      task_id: string
      title: string
      created_by_id: string
```

**When to specify**:
- Event-driven architecture
- Real-time notifications
- Async background processing
- System integrations

#### Relationships
If not specified, inferred from foreign keys in models.

```yaml
relationships:
  - type: one_to_many
    from: User
    to: Order
```

**When to specify**:
- Complex relationships
- Many-to-many relationships
- Custom join tables

---

## Complete Example: Task Manager

Here's a complete YAML for a simple task management app:

```yaml
workflow:
  id: task-manager
  version: 1.0.0
  name: Task Management Application
  description: Simple task manager with users and tasks

# ============================================================================
# TECHNOLOGY (REQUIRED)
# ============================================================================
technology:
  frontend:
    framework: react
    language: typescript
    styling: tailwind
  backend:
    framework: express
    language: typescript
    orm: prisma
  database:
    type: postgresql

# ============================================================================
# DATA MODELS (REQUIRED)
# ============================================================================
models:
  User:
    description: Application users
    fields:
      email:
        type: string
        required: true
        unique: true
        max_length: 255
      name:
        type: string
        required: true
        max_length: 255
      password_hash:
        type: string
        required: true
      role:
        type: enum
        enum_values: [admin, user]
        default: user
    indexes:
      - fields: [email]
        unique: true

  Task:
    description: User tasks
    fields:
      title:
        type: string
        required: true
        max_length: 255
      description:
        type: text
      status:
        type: enum
        enum_values: [todo, in_progress, done]
        default: todo
      priority:
        type: enum
        enum_values: [low, medium, high]
        default: medium
      user_id:
        type: uuid
        foreign_key: User
        required: true
      due_date:
        type: timestamp
    indexes:
      - fields: [user_id]
      - fields: [status]

# ============================================================================
# RELATIONSHIPS (OPTIONAL - but helpful for clarity)
# ============================================================================
relationships:
  - type: one_to_many
    from: User
    to: Task
    description: Users can have many tasks

# ============================================================================
# EVENTS
# ============================================================================
events:
  user_registered:
    description: New user account created
    payload:
      user_id: string
      email: string

  task_created:
    description: New task created
    payload:
      task_id: string
      user_id: string
      title: string

  task_status_changed:
    description: Task status updated
    payload:
      task_id: string
      old_status: string
      new_status: string

# ============================================================================
# ACTORS
# ============================================================================
actors:
  user:
    type: human
    name: Application User
    description: Person using the task manager

# ============================================================================
# COMPONENTS
# ============================================================================
components:
  auth:
    description: User authentication
    emits:
      - user_registered
    consumes: []

  tasks:
    description: Task management
    emits:
      - task_created
      - task_status_changed
    consumes:
      - user_registered

# ============================================================================
# WORKFLOWS
# ============================================================================
workflows:
  user_manage_tasks:
    actor: user
    description: User manages their tasks
    steps:
      - id: view_tasks
        action: list_tasks
        component: tasks

      - id: create_task
        action: add_task
        component: tasks
        emits: task_created

      - id: update_status
        action: change_status
        component: tasks
        emits: task_status_changed

# ============================================================================
# STACK
# ============================================================================
stack:
  auth:
    services:
      local:
        type: auth_provider
        config:
          strategy: jwt

  tasks:
    services:
      postgresql:
        type: database
        config:
          database_url: "{{ env.DATABASE_URL }}"

# ============================================================================
# STAGES (OPTIONAL)
# ============================================================================
stages:
  local:
    domain: localhost:3000
    database: postgresql://localhost/taskmanager_dev

  production:
    domain: taskmanager.app
    database: "{{ env.PROD_DATABASE_URL }}"

# ============================================================================
# DESIGN (OPTIONAL)
# ============================================================================
design:
  theme:
    colors:
      primary: "#3B82F6"
      secondary: "#10B981"
      error: "#EF4444"
```

---

## What Gets Generated From This

From the above YAML, Bangstack generates:

### Database
```sql
-- migrations/001_create_users.sql
CREATE TABLE users (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  email VARCHAR(255) NOT NULL UNIQUE,
  name VARCHAR(255) NOT NULL,
  password_hash VARCHAR(255) NOT NULL,
  role VARCHAR(50) DEFAULT 'user',
  created_at TIMESTAMP DEFAULT NOW(),
  updated_at TIMESTAMP DEFAULT NOW()
);

-- migrations/002_create_tasks.sql
CREATE TABLE tasks (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  title VARCHAR(255) NOT NULL,
  description TEXT,
  status VARCHAR(50) DEFAULT 'todo',
  priority VARCHAR(50) DEFAULT 'medium',
  user_id UUID NOT NULL REFERENCES users(id),
  due_date TIMESTAMP,
  created_at TIMESTAMP DEFAULT NOW(),
  updated_at TIMESTAMP DEFAULT NOW()
);

CREATE INDEX idx_tasks_user_id ON tasks(user_id);
CREATE INDEX idx_tasks_status ON tasks(status);
```

### Backend API
```typescript
// src/routes/tasks.ts
router.get('/tasks', authMiddleware, async (req, res) => {
  const tasks = await prisma.task.findMany({
    where: { user_id: req.user.id }
  });
  res.json(tasks);
});

router.post('/tasks', authMiddleware, async (req, res) => {
  const task = await prisma.task.create({
    data: {
      ...req.body,
      user_id: req.user.id
    }
  });
  eventBus.emit('task_created', {
    task_id: task.id,
    user_id: req.user.id,
    title: task.title
  });
  res.json(task);
});
```

### Frontend
```tsx
// src/pages/TasksPage.tsx
export default function TasksPage() {
  const { tasks, loading } = useTasks();

  return (
    <div className="container mx-auto p-4">
      <h1 className="text-2xl font-bold mb-4">My Tasks</h1>
      <TaskList tasks={tasks} loading={loading} />
      <CreateTaskButton />
    </div>
  );
}
```

### Tests
```typescript
// tests/e2e/tasks.spec.ts
test('user can create and complete task', async ({ page }) => {
  await page.goto('/tasks');
  await page.click('[data-testid="create-task"]');
  await page.fill('[name="title"]', 'Test task');
  await page.click('[data-testid="submit"]');

  await expect(page.locator('text=Test task')).toBeVisible();

  await page.click('[data-testid="task-status"]');
  await page.click('text=Done');

  await expect(page.locator('[data-status="done"]')).toBeVisible();
});
```

---

## Field Types Reference

### String Types
- `string` - VARCHAR (max_length recommended)
- `text` - TEXT (unlimited length)

### Numeric Types
- `integer` - INT
- `decimal` - DECIMAL (precision, scale recommended)

### Date/Time Types
- `timestamp` - TIMESTAMP (date + time)
- `date` - DATE (date only)
- `time` - TIME (time only)

### Other Types
- `uuid` - UUID (recommended for IDs)
- `boolean` - BOOLEAN
- `json` - JSON/JSONB
- `array` - ARRAY (specify with type parameter)
- `enum` - ENUM (requires enum_values)

---

## Event-Driven Architecture

Bangstack v0.4.0+ supports automatic generation of event-driven infrastructure with Redis pub/sub and WebSocket real-time updates.

### Cache Configuration (Required for Events)

Events require a cache provider (Redis) for pub/sub messaging:

```yaml
technology:
  cache:
    provider: redis         # Local Redis
    use: local

  # OR for serverless/cloud:
  cache:
    provider: upstash-redis  # Upstash Redis
    use: cloud
```

**Options:**
- **provider**: `redis` (local Redis) or `upstash-redis` (serverless)
- **use**: `local` (self-hosted) or `cloud` (managed service)

### Events Definition

Define events that occur in your system:

```yaml
events:
  # Convention: {model}_{action}
  task_created:
    description: New task created in the system
    payload:
      task_id: string
      title: string
      created_by_id: string
      created_at: string

  task_updated:
    description: Task details or status updated
    payload:
      task_id: string
      old_status: string
      new_status: string
      updated_by_id: string

  task_assigned:
    description: Task assigned to a user
    payload:
      task_id: string
      assigned_to_id: string
      assigned_by_id: string

  user_registered:
    description: New user account created
    payload:
      user_id: string
      email: string
      name: string
```

**Event Naming Conventions:**
- Use snake_case: `task_created`, not `TaskCreated`
- Format: `{model}_{action}` (e.g., `user_created`, `order_updated`)
- Be specific: `order_payment_failed` not just `order_failed`

**Payload Field Types:**
- `string` - String values (IDs, names, emails)
- `number` - Numeric values
- `boolean` - True/false values
- `array` - Arrays of values
- `object` - Nested objects
- `date` - Date/time values

### What Gets Generated

When you define events and cache, Bangstack automatically generates:

#### Backend Infrastructure

1. **Event Types** (`backend/src/types/events.ts`):
```typescript
// TypeScript interfaces for type-safe events
export interface TaskCreatedPayload {
  task_id: string;
  title: string;
  created_by_id: string;
}

export interface TaskCreatedEvent {
  type: 'task_created';
  payload: TaskCreatedPayload;
  timestamp: Date | string;
  correlationId?: string;
  userId?: string;
}

export type Event = TaskCreatedEvent | TaskUpdatedEvent | ...;
```

2. **Event Bus** (`backend/src/events/event-bus.ts`):
```typescript
// Redis pub/sub event bus
const eventBus = getEventBus({
  redis: {
    host: process.env.REDIS_HOST,
    port: parseInt(process.env.REDIS_PORT),
  },
});

// Publish events
await eventBus.publish('task_created', payload, metadata);

// Subscribe to events
await eventBus.subscribe('task_created', handler);
```

3. **Event Handlers** (`backend/src/events/handlers/`):
```typescript
// One handler file per event
export async function handleTaskCreatedHandler(event: TaskCreatedEvent) {
  // TODO: Add your business logic
  console.log('Task created:', event.payload);
}
```

4. **Automatic Event Emission** (in CRUD endpoints):
```typescript
// POST /tasks automatically emits task_created
router.post('/tasks', async (req, res) => {
  const task = await prisma.task.create({ data: req.body });

  // Auto-generated by Bangstack!
  await emitEventWithContext(req, 'task_created', {
    task_id: task.id,
    title: task.title,
    created_by_id: req.userId,
  });

  res.json(task);
});
```

5. **WebSocket Server** (`backend/src/websocket/server.ts`):
```typescript
// WebSocket server for real-time updates
const realtimeServer = initializeRealtimeServer(httpServer);

// Broadcasts events to connected clients
realtimeServer.broadcast('task_created', payload);
```

6. **Event Bridge** (`backend/src/websocket/event-bridge.ts`):
```typescript
// Bridges Redis events to WebSocket clients
eventBus.subscribe('task_created', (event) => {
  realtimeServer.broadcast('task_created', event.payload);
});
```

#### Frontend Infrastructure

1. **WebSocket Client** (`frontend/src/lib/websocket-client.ts`):
```typescript
// WebSocket client with reconnection
export class WebSocketClient {
  connect(token?: string): Promise<void>
  subscribe(eventType: string, callback: Function): () => void
  disconnect(): void
}
```

2. **React Hooks** (`frontend/src/hooks/useEvents.ts`):
```typescript
// Easy-to-use React hooks
export function useEvent<T>(eventType: T, callback: Function): void
export function useEvents(): { [eventType]: Function }
export function useAllEvents(callback: Function): void
```

3. **WebSocket Context** (`frontend/src/lib/websocket-context.tsx`):
```typescript
// Context provider for WebSocket connection
export function WebSocketProvider({
  children,
  url,
  autoConnect,
  token,
}): JSX.Element
```

### Example: Real-time Task Updates

```yaml
technology:
  frontend:
    framework: react
    language: typescript
  backend:
    framework: express
    language: typescript
  cache:
    provider: redis
    use: local

models:
  Task:
    fields:
      title: { type: string, required: true }
      status: { type: enum, enum_values: [todo, done] }

events:
  task_created:
    description: New task created
    payload:
      task_id: string
      title: string

  task_status_changed:
    description: Task status updated
    payload:
      task_id: string
      new_status: string
```

**Generated React Component:**
```typescript
import { useEvent } from '../hooks/useEvents';

function TaskList() {
  const [tasks, setTasks] = useState([]);

  // Subscribe to real-time events
  useEvent('task_created', (payload) => {
    setTasks(prev => [...prev, payload]);
  });

  useEvent('task_status_changed', (payload) => {
    setTasks(prev => prev.map(t =>
      t.task_id === payload.task_id
        ? { ...t, status: payload.new_status }
        : t
    ));
  });

  return <div>{/* Render tasks */}</div>;
}
```

### Environment Variables

Generated `.env` files include:

```bash
# Redis Configuration
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=

# WebSocket Configuration
WS_PORT=3000
```

### Best Practices for Events

1. **Keep payloads minimal** - Only include essential data:
```yaml
# ✅ Good
task_created:
  payload:
    task_id: string
    title: string

# ❌ Avoid - too much data
task_created:
  payload:
    task_id: string
    title: string
    description: string        # Can fetch if needed
    all_comments: array        # Too large
    entire_user_object: object # Redundant
```

2. **Use past tense for event names**:
```yaml
# ✅ Good
task_created:        # Something that happened
user_registered:     # Past tense
order_completed:     # Past tense

# ❌ Avoid
create_task:         # Command, not event
registering_user:    # Present tense
complete_order:      # Command
```

3. **Include context in payload**:
```yaml
task_assigned:
  payload:
    task_id: string
    assigned_to_id: string
    assigned_by_id: string  # ✅ Include who did the action
    timestamp: string       # ✅ Include when it happened
```

4. **One event per action**:
```yaml
# ✅ Good - specific events
task_created:
  payload: { task_id: string }

task_updated:
  payload: { task_id: string, changes: object }

# ❌ Avoid - generic event
task_changed:  # What kind of change?
  payload: { task_id: string, action: string }
```

---

## Custom Pages and Navigation

### Custom Pages

You can define custom pages with specific layouts and authentication requirements:

```yaml
ui:
  pages:
    # Dashboard page (protected)
    - path: /dashboard
      title: Dashboard
      layout: dashboard
      auth: required
      data_source: /api/dashboard/stats

    # Public articles list
    - path: /articles
      title: Articles
      layout: list
      auth: optional
      data_source: /api/articles

    # User profile page (protected)
    - path: /profile
      title: My Profile
      layout: detail
      auth: required
      data_source: /api/users/me
```

**Available layouts**:
- `grid` - Grid layout for displaying cards
- `list` - List layout for displaying items
- `dashboard` - Dashboard layout with stats and metrics
- `detail` - Detail view for single items
- `form` - Form layout
- `custom` - Custom layout (you provide the structure)

**Auth options**:
- `required` - Page requires authentication (redirects to login)
- `optional` - Page accessible to all users
- `none` - No authentication required

### Custom Navigation

Define a custom navigation menu instead of auto-generated menu from models:

```yaml
ui:
  navigation:
    - label: Home
      path: /
      icon: 🏠
      auth: optional

    - label: Dashboard
      path: /dashboard
      icon: 📊
      auth: required

    - label: Articles
      path: /articles
      icon: 📝
      auth: optional

    - label: Profile
      path: /profile
      icon: 👤
      auth: required
```

**Features**:
- **Icons**: Optional icons displayed next to labels
- **Auth-aware**: Navigation items with `auth: required` are automatically hidden when not authenticated
- **Order**: Menu items appear in the order defined

### Protected Routes

Pages marked with `auth: required` are automatically wrapped with a `ProtectedRoute` component that:
- Checks for authentication (looks for `auth_token` in localStorage)
- Redirects unauthenticated users to `/login`
- Allows authenticated users to access the page

```yaml
ui:
  pages:
    - path: /admin
      title: Admin Panel
      auth: required  # ← Automatically protected
      layout: dashboard
```

Generated code:
```tsx
// src/main.tsx
<Routes>
  <Route path="/admin" element={
    <ProtectedRoute>
      <AdminPanel />
    </ProtectedRoute>
  } />
</Routes>
```

### Custom Forms

Create reusable forms with validation:

```yaml
ui:
  forms:
    - id: contact-form
      title: Contact Us
      fields:
        - name: name
          label: Your Name
          type: text
          placeholder: Enter your name
          validation: required

        - name: email
          label: Email Address
          type: email
          placeholder: your@email.com
          validation: required,email

        - name: subject
          label: Subject
          type: select
          validation: required
          options:
            - value: general
              label: General Inquiry
            - value: support
              label: Technical Support

        - name: message
          label: Message
          type: textarea
          placeholder: Your message...
          validation: required

      submit_label: Send Message
      on_submit: contact.submit
```

**Field types**:
- `text` - Single-line text input
- `textarea` - Multi-line text input
- `email` - Email input with validation
- `password` - Password input (hidden)
- `number` - Number input
- `date` - Date picker
- `select` - Dropdown select
- `checkbox` - Checkbox
- `file` - File upload

**Validation**:
- `required` - Field must have a value
- `email` - Must be valid email format
- `min:n` - Minimum length/value
- `max:n` - Maximum length/value

### Complete Example

See `examples/extended-ui-features.workflow.yaml` for a complete example showcasing:
- Custom pages with different layouts
- Custom navigation menu
- Protected routes
- Custom forms with validation
- Integration with data models

---

## Best Practices

### 1. Always Define Models Explicitly
Even if you can infer from events, explicit models are clearer:

```yaml
# ✅ Good - explicit
models:
  Product:
    fields:
      name: { type: string, required: true }
      price: { type: decimal, precision: 10, scale: 2 }

# ❌ Avoid - relying on inference
events:
  product_created:
    payload:
      name: string
      price: number  # Loses precision info
```

### 2. Use Descriptive Field Names
```yaml
# ✅ Good
fields:
  email_address: { type: string }
  created_at: { type: timestamp }

# ❌ Avoid
fields:
  email: { type: string }  # Could be ambiguous
  date: { type: timestamp }  # Which date?
```

### 3. Add Indexes for Foreign Keys
```yaml
Task:
  fields:
    user_id: { type: uuid, foreign_key: User }
  indexes:
    - fields: [user_id]  # ✅ Performance boost
```

### 4. Use Enums for Fixed Options
```yaml
# ✅ Good - type-safe
status:
  type: enum
  enum_values: [todo, in_progress, done]

# ❌ Avoid - no validation
status:
  type: string
```

### 5. Set Reasonable Defaults
```yaml
fields:
  role:
    type: enum
    enum_values: [admin, user, guest]
    default: user  # ✅ Safe default
```

---

## Migration from Event-Only Schema

If you have an existing event-driven workflow, here's how to extend it:

### Before (Event-Driven Only)
```yaml
events:
  product_created:
    payload:
      product_id: string
      name: string
      price: number
```

### After (Extended)
```yaml
# Add technology
technology:
  frontend: { framework: react, language: typescript, styling: tailwind }
  backend: { framework: express, language: typescript, orm: prisma }
  database: { type: postgresql }

# Add explicit models
models:
  Product:
    fields:
      name: { type: string, required: true, max_length: 255 }
      price: { type: decimal, required: true, precision: 10, scale: 2 }
      description: { type: text }
      in_stock: { type: boolean, default: true }

# Keep existing events, actors, components, workflows
events: { ... }
actors: { ... }
components: { ... }
workflows: { ... }
```

---

## Next Steps

1. **Review this guide** - Understand required vs optional sections
2. **Look at the example** - See a complete working YAML
3. **Start simple** - Begin with minimal required fields
4. **Iterate** - Add more details as needed

When you're ready, we can work together to create the YAML for your first app!
