Architecture
EdgeMaster is built with a layered architecture specifically designed for edge computing environments. This guide explores the internal design, request flow, and key architectural decisions.
Design Philosophy
EdgeMaster follows these core principles:
1. Zero Dependencies
- No runtime dependencies for minimal bundle size (~14 KB)
- Only platform APIs (Web APIs) for maximum compatibility
- Dev dependencies only for testing and building
2. Edge-First Design
- Optimized for Cloudflare Workers' execution model
- Fast cold starts (<1ms)
- Minimal memory footprint
- CPU-time optimized operations
3. Type Safety First
- Full TypeScript support with strict typing
- Generic types for extensibility
- Complete IntelliSense support
4. Composability
- Small, focused components that work together
- Reusable tasks, interceptors, and matchers
- Functional composition patterns
5. Production Ready
- Built-in authentication, rate limiting, caching
- Error handling and recovery
- Observability hooks
System Architecture
EdgeMaster uses a layered architecture with clear separation of concerns:
┌─────────────────────────────────────────┐
│ Application Layer │
│ (Your Routes & Business Logic) │
└───────────────┬─────────────────────────┘
│
┌───────────────▼─────────────────────────┐
│ EdgeController │
│ (Routing Engine & Orchestrator) │
└───────────────┬─────────────────────────┘
│
┌───────┴────────┐
│ │
┌───────▼─────┐ ┌──────▼────────┐
│ Interceptors│ │ Route Matcher │
│ (Middleware)│ │ (Patterns) │
└───────┬─────┘ └──────┬────────┘
│ │
│ ┌───────▼─────┐
│ │RouteHandler │
│ └───────┬─────┘
│ │
│ ┌───────▼─────┐
│ │ Tasks │
│ │ (Execution) │
│ └─────────────┘
│
┌───────▼─────────────────────────────────┐
│ Helper Layer │
│ (Request/Response/Context Helpers) │
└─────────────────────────────────────────┘
Core Components
1. EdgeController
Location: src/EdgeController.ts:11
The EdgeController is the heart of EdgeMaster - it's the main orchestrator that manages routing, interceptors, and request lifecycle.
Responsibilities
- Route Management: Add, organize, and match routes
- Request Orchestration: Coordinate request/response flow
- Interceptor Management: Execute middleware pipeline
- Error Handling: Global error and 404 handling
- Context Management: Maintain request context and state
Key Methods
class EdgeController {
// Route management
addRoute(matcher: IMatcher, handler: IRouteHandler, priority?: number): EdgeController
GET(path: string, handler: IRouteHandler): EdgeController
POST(path: string, handler: IRouteHandler): EdgeController
PUT(path: string, handler: IRouteHandler): EdgeController
DELETE(path: string, handler: IRouteHandler): EdgeController
// Interceptors (middleware)
addInterceptor(interceptor: IInterceptor): EdgeController
// Error handlers
onNotFound(handler: (ctx: ContextWithReq) => Promise<Response>): EdgeController
onError(handler: (error: Error, ctx: Context) => Promise<Response>): EdgeController
// Route grouping
group(prefix: string, callback: (controller: EdgeController) => void): EdgeController
// Request handling
handleRequest(args: RequestHandlerArgs): Promise<Response>
}
Internal State
class EdgeController {
private _reqInterceptors: IRequestInterceptor[] // Request middleware
private _resInterceptors: IResponseInterceptor[] // Response middleware
private _routes: Route[] // Registered routes
private _notFoundHandler?: Function // Custom 404 handler
private _errorHandler?: Function // Custom error handler
}
Request Lifecycle
The request flows through multiple stages in EdgeMaster:
┌─────────────────────────────────────────────┐
│ 1. Incoming Request │
│ fetch(request, env, ctx) │
└──────────────┬──────────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ 2. Context Initialization │
│ • Create Context object │
│ • Initialize state Map() │
│ • Set req, env, ctx │
└──────────────┬──────────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ 3. Request Interceptors (Sequential) │
│ • JWT/API Key Auth │
│ • Cache Check (may short-circuit) │
│ • Rate Limiting (may short-circuit) │
│ • Request Logging │
│ • Custom interceptors │
└──────────────┬──────────────────────────────┘
│
┌─────────┴──────────┐
│ Responder Set? │
│ (Short-circuit) │
└────┬──────────┬────┘
│ Yes │ No
│ │
▼ ▼
┌────────┐ ┌─────────────────────────────┐
│ Return │ │ 4. Route Matching │
│Response│ │ • Priority-based matching │
└────────┘ │ • Custom matchers │
│ • Pattern matching │
└──────────┬──────────────────┘
│
┌─────────┴──────────┐
│ Route Found? │
└────┬─────────┬─────┘
│ Yes │ No
│ │
▼ ▼
┌─────────┐ ┌────────────────┐
│Execute │ │ 404 Handler │
│Handler │ │ (or default) │
└────┬────┘ └────┬───────────┘
│ │
└─────┬─────┘
│
▼
┌─────────────────────────────┐
│ 5. RouteHandler Execution │
│ • Execute task chain │
│ • Conditional execution │
│ • Task composition │
└──────────┬──────────────────┘
│
▼
┌─────────────────────────────┐
│ 6. Task Chain Execution │
│ • when() condition check │
│ • do() main logic │
│ • doThen() post-processing│
└──────────┬──────────────────┘
│
▼
┌─────────────────────────────┐
│ 7. Response Generated │
└──────────┬──────────────────┘
│
▼
┌─────────────────────────────┐
│ 8. Response Interceptors │
│ • Cache Store │
│ • CORS Headers │
│ • Response Logging │
│ • Custom interceptors │
└──────────┬──────────────────┘
│
▼
┌─────────────────────────────┐
│ 9. Final Response │
│ Return to Worker │
└─────────────────────────────┘
Detailed Flow
Stage 1-2: Initialization
export default {
fetch: (request: Request, env: any, ctx: ExecutionContext) => {
return app.handleRequest({
req: request,
env: env,
ctx: ctx
});
}
};
Stage 3: Request Interceptors
// Sequential execution, each interceptor can:
// 1. Modify the request
// 2. Set a responder to short-circuit
// 3. Add data to context.state
for (const interceptor of this._reqInterceptors) {
req = await interceptor.intercept({ ...ctx, reqCtx: { req } });
}
Stage 4-5: Route Matching & Handler Execution
// Find first matching route (priority-based)
const route = this._routes.find(r => r.matcher(ctx.req));
if (route) {
return route.routeHandler.execute(ctx);
}
Stage 6: Task Execution
// Tasks execute in sequence with conditional logic
for (const task of tasks) {
if (!task.when || await task.when(ctx)) {
const result = await task.do(ctx);
if (task.doThen) await task.doThen(result, ctx);
}
}
Stage 8: Response Interceptors
// Transform the response
for (const interceptor of this._resInterceptors) {
res = await interceptor.intercept({ ...ctx, res });
}
Interceptor Pattern (Middleware)
Interceptors implement the Chain of Responsibility pattern with two types:
Request Interceptors
Execute before route handling:
interface IRequestInterceptor {
type: InterceptorType.Request;
intercept(ctx: Context): Promise<Request>;
responder?: (ctx: Context) => Promise<Response>; // Short-circuit
}
Use Cases:
- Authentication (JWT, API keys)
- Rate limiting
- Cache checking
- Request logging
- Request validation