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.yamlThen execute wave by wave (or use --mode auto for continuous execution):
Execute the orchestrationExpected 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: 7Step 5: Verify the Results
Check the created files:
tree src testsExpected structure:
src/
├── types/
│ └── auth.ts
├── utils/
│ ├── hash.ts
│ └── jwt.ts
├── services/
│ └── auth.ts
├── middleware/
│ └── auth.ts
└── routes/
└── auth.ts
tests/
└── auth.test.tsRun the tests:
npm testUnderstanding 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:
- Identify independent work — Types, configs, and utilities often have no dependencies
- Minimize dependency chains — Deep chains (A→B→C→D) limit parallelization
- Group related work — Multiple independent files can be in one stream
- 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 unchangedNew 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-testsThis 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
- Self-Healing Tutorial — Handle failures automatically
- Context Optimization Tutorial — Reduce costs and improve quality
- Wave Planning Guide — Advanced parallelization strategies
Great work building a complete feature with multi-wave execution!