Tutorial: Building a Multi-Wave Feature

Learn to build a realistic feature using Orchex's multi-wave execution. You'll create a user authentication system with types, implementation, middleware, and tests — all orchestrated with optimal parallelization.

Time: 15-20 minutes Prerequisites: Completed First Orchestration Tutorial


Introduction

Why Multi-Wave Matters

Real features have natural dependencies:

  • Types must exist before implementation
  • Implementation must exist before tests
  • Some components are independent and can run in parallel

Orchex automatically calculates the optimal wave structure, maximizing parallelism while respecting dependencies.

What You'll Build

A user authentication feature with:

  • Wave 1: Type definitions + utility functions (parallel)
  • Wave 2: Auth service + validation middleware (parallel)
  • Wave 3: API routes (depends on service and middleware)
  • Wave 4: Comprehensive tests (depends on everything)
Wave 1: auth-types, utils ──────────┐
                                    │
Wave 2: auth-service, middleware ───┼── (parallel groups)
                                    │
Wave 3: auth-routes ────────────────┤
                                    │
Wave 4: auth-tests ─────────────────┘

Step 1: Plan the Feature

Before writing streams, map out the dependencies:

Stream Creates Depends On
auth-types src/types/auth.ts
utils src/utils/hash.ts, src/utils/jwt.ts
auth-service src/services/auth.ts auth-types, utils
middleware src/middleware/auth.ts auth-types
auth-routes src/routes/auth.ts auth-service, middleware
auth-tests tests/auth.test.ts auth-routes

Why this structure?

  • Types and utils have no dependencies → Wave 1
  • Service needs types + utils; middleware needs only types → Wave 2
  • Routes need both service and middleware → Wave 3
  • Tests need routes (which include everything) → Wave 4

Step 2: Create the Manifest

Create auth-feature.yaml:

# auth-feature.yaml
# Multi-wave authentication feature orchestration

streams:
  # ═══════════════════════════════════════════════════════════════
  # Wave 1: Foundation (no dependencies - run in parallel)
  # ═══════════════════════════════════════════════════════════════

  - id: auth-types
    prompt: |
      Create `src/types/auth.ts` with TypeScript interfaces for authentication:

      ```typescript
      export interface User {
        id: string;
        email: string;
        passwordHash: string;
        createdAt: Date;
      }

      export interface Session {
        userId: string;
        token: string;
        expiresAt: Date;
      }

      export interface LoginRequest {
        email: string;
        password: string;
      }

      export interface LoginResponse {
        user: Omit<User, 'passwordHash'>;
        token: string;
        expiresAt: string;
      }

      export interface AuthError {
        code: 'INVALID_CREDENTIALS' | 'SESSION_EXPIRED' | 'UNAUTHORIZED';
        message: string;
      }
      ```

      Create the directory structure if it doesn't exist.
    dependencies: []

  - id: utils
    prompt: |
      Create utility functions for authentication:

      1. `src/utils/hash.ts`:
         - `hashPassword(password: string): Promise<string>` using bcrypt
         - `verifyPassword(password: string, hash: string): Promise<boolean>`
         - Use cost factor of 10

      2. `src/utils/jwt.ts`:
         - `signToken(payload: object): string` with 1-hour expiry
         - `verifyToken(token: string): object | null`
         - Use HS256 algorithm
         - Get secret from `process.env.JWT_SECRET`

      Include proper error handling and TypeScript types.
      Create directories if they don't exist.
    dependencies: []

  # ═══════════════════════════════════════════════════════════════
  # Wave 2: Core implementation (depends on Wave 1)
  # ═══════════════════════════════════════════════════════════════

  - id: auth-service
    prompt: |
      Create `src/services/auth.ts` implementing the authentication service.

      Import types from `src/types/auth.ts` and utils from `src/utils/`.

      Implement:
      - `register(email: string, password: string): Promise<User>`
        - Validate email format
        - Check for existing user (throw if exists)
        - Hash password and create user
        - Return user without password hash

      - `login(request: LoginRequest): Promise<LoginResponse>`
        - Find user by email
        - Verify password
        - Generate JWT token
        - Return user info and token

      - `validateSession(token: string): Promise<User | null>`
        - Verify JWT token
        - Return user if valid, null if invalid/expired

      Use an in-memory Map for user storage (for demo purposes).
      Include proper error handling with AuthError types.
    dependencies: [auth-types, utils]

  - id: middleware
    prompt: |
      Create `src/middleware/auth.ts` with Express authentication middleware.

      Import types from `src/types/auth.ts`.

      Implement:
      - `requireAuth`: Middleware that checks Authorization header
        - Extract Bearer token
        - Validate token (call auth service)
        - Attach user to `req.user`
        - Return 401 with AuthError if invalid

      - `optionalAuth`: Middleware that attaches user if token present
        - Don't fail if no token
        - Attach user if valid token exists

      Export both middlewares and extend Express Request type to include `user`.
    dependencies: [auth-types]

  # ═══════════════════════════════════════════════════════════════
  # Wave 3: API layer (depends on Wave 2)
  # ═══════════════════════════════════════════════════════════════

  - id: auth-routes
    prompt: |
      Create `src/routes/auth.ts` with Express routes for authentication.

      Import auth service and middleware from previous streams.

      Implement routes:
      - `POST /auth/register`
        - Accept email and password in body
        - Call auth service register
        - Return 201 with user data

      - `POST /auth/login`
        - Accept email and password in body
        - Call auth service login
        - Return 200 with token and user

      - `POST /auth/logout`
        - Require authentication (use middleware)
        - Invalidate session (for demo, just return success)
        - Return 200

      - `GET /auth/me`
        - Require authentication
        - Return current user info

      Include proper error handling:
      - 400 for validation errors
      - 401 for auth errors
      - 500 for server errors

      Export the router as default.
    dependencies: [auth-service, middleware]

  # ═══════════════════════════════════════════════════════════════
  # Wave 4: Tests (depends on everything)
  # ═══════════════════════════════════════════════════════════════

  - id: auth-tests
    prompt: |
      Create comprehensive tests in `tests/auth.test.ts`.

      Import and test all authentication components.

      Test categories:
      1. **Utils tests** (5+ tests)
         - Password hashing and verification
         - JWT signing and verification
         - Edge cases (empty strings, invalid tokens)

      2. **Service tests** (8+ tests)
         - User registration (success, duplicate email)
         - Login (success, wrong password, unknown user)
         - Session validation (valid, expired, invalid)

      3. **Middleware tests** (4+ tests)
         - requireAuth with valid token
         - requireAuth with invalid/missing token
         - optionalAuth with and without token

      4. **Route integration tests** (6+ tests)
         - Full registration  login  me flow
         - Error responses for invalid inputs
         - Unauthorized access attempts

      Use vitest or jest syntax. Include setup/teardown as needed.
      Total: 20+ test cases.
    dependencies: [auth-routes]

Step 3: Visualize the Wave Structure

Before executing, understand the parallelization:

┌─────────────────────────────────────────────────────────────┐
│                        WAVE 1                               │
│  ┌────────────────────┐    ┌────────────────────┐          │
│  │    auth-types      │    │       utils        │          │
│  │ src/types/auth.ts  │    │ src/utils/hash.ts  │          │
│  │                    │    │ src/utils/jwt.ts   │          │
│  └─────────┬──────────┘    └─────────┬──────────┘          │
│            │                         │                      │
└────────────│─────────────────────────│──────────────────────┘
             │                         │
             └───────────┬─────────────┘
                         │
┌────────────────────────│────────────────────────────────────┐
│                        │       WAVE 2                       │
│            ┌───────────┴───────────┐                        │
│            │                       │                        │
│  ┌─────────▼──────────┐  ┌────────▼─────────────┐          │
│  │    auth-service    │  │     middleware       │          │
│  │ src/services/auth  │  │ src/middleware/auth  │          │
│  └─────────┬──────────┘  └─────────┬────────────┘          │
│            │                       │                        │
└────────────│───────────────────────│────────────────────────┘
             │                       │
             └───────────┬───────────┘
                         │
┌────────────────────────│────────────────────────────────────┐
│                        │       WAVE 3                       │
│              ┌─────────▼──────────┐                         │
│              │    auth-routes     │                         │
│              │  src/routes/auth   │                         │
│              └─────────┬──────────┘                         │
│                        │                                    │
└────────────────────────│────────────────────────────────────┘
                         │
┌────────────────────────│────────────────────────────────────┐
│                        │       WAVE 4                       │
│              ┌─────────▼──────────┐                         │
│              │    auth-tests      │                         │
│              │  tests/auth.test   │                         │
│              └────────────────────┘                         │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Time Savings:

Without parallelization (sequential): ~6 stream executions in sequence With waves: 4 waves, with Wave 1 and Wave 2 running 2 streams each in parallel

Effective speedup: ~33% faster (4 waves instead of 6 sequential steps)

Step 4: Execute the Orchestration

Initialize and execute:

Initialize orchestration from auth-feature.yaml

Then execute wave by wave (or use --mode auto for continuous execution):

Execute the orchestration

Expected output:

🚀 Orchex Orchestration
Feature: auth-feature

⚡ Wave 1/4 (2 streams)
├─ auth-types ... ✓ complete (12.3s)
└─ utils      ... ✓ complete (15.1s)

⚡ Saved 12.3s (45%) with parallel execution

⚡ Wave 2/4 (2 streams)
├─ auth-service ... ✓ complete (18.2s)
└─ middleware   ... ✓ complete (10.5s)

⚡ Saved 10.5s (37%) with parallel execution

⚡ Wave 3/4 (1 stream)
└─ auth-routes ... ✓ complete (14.7s)

⚡ Wave 4/4 (1 stream)
└─ auth-tests ... ✓ complete (22.1s)

✅ Orchestration complete!
Total time: 82.6s (sequential would be ~113s)
Files created: 7

Step 5: Verify the Results

Check the created files:

tree src tests

Expected structure:

src/
├── types/
│   └── auth.ts
├── utils/
│   ├── hash.ts
│   └── jwt.ts
├── services/
│   └── auth.ts
├── middleware/
│   └── auth.ts
└── routes/
    └── auth.ts
tests/
└── auth.test.ts

Run the tests:

npm test

Understanding Wave Efficiency

Calculating Parallelization Benefit

Wave Streams Parallel Time Sequential Time
1 2 15.1s 27.4s
2 2 18.2s 28.7s
3 1 14.7s 14.7s
4 1 22.1s 22.1s
Total 6 70.1s 92.9s

Time saved: 22.8 seconds (25% faster)

Maximizing Parallelization

Tips:

  1. Identify independent work — Types, configs, and utilities often have no dependencies
  2. Minimize dependency chains — Deep chains (A→B→C→D) limit parallelization
  3. Group related work — Multiple independent files can be in one stream
  4. Split large streams — Breaking big tasks into smaller parallel streams

Advanced: Adding More Parallelism

What if we split the utils stream?

streams:
  - id: auth-types
    dependencies: []

  - id: hash-utils
    prompt: Create src/utils/hash.ts with password hashing
    dependencies: []

  - id: jwt-utils
    prompt: Create src/utils/jwt.ts with JWT functions
    dependencies: []

  - id: auth-service
    dependencies: [auth-types, hash-utils, jwt-utils]

  # ... rest unchanged

New structure:

Wave 1: auth-types, hash-utils, jwt-utils (3 parallel!)
Wave 2: auth-service, middleware (2 parallel)
Wave 3: auth-routes
Wave 4: auth-tests

This adds more parallelism to Wave 1, potentially saving more time.


What You've Learned

✅ How to plan features with dependency mapping

✅ How Orchex calculates optimal wave structure

✅ How parallel execution saves time

✅ Strategies for maximizing parallelization


Next Steps


Great work building a complete feature with multi-wave execution!