Compile-time Architecture Enforcement for TypeScript
ArchGuard uses TypeScript's type system to enforce Clean Architecture at compile time—preventing architectural violations before code even runs.
Large TypeScript codebases become architectural spaghetti:
- UI components calling database code directly
- Business logic mixed with HTTP handlers
- Data access scattered everywhere
- No way to enforce boundaries at compile time
Existing tools (Nx, Sheriff, Deptrac) check architecture after code is written. ArchGuard stops violations while you're writing code.
import { pure, domain, data, service, call } from 'archguard';
// ✅ Define functions with layer markers
const validateEmail = domain((email: string) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email));
const saveUser = data(async (user: User) => await db.users.insert(user));
const createUser = service(async (userData: UserInput) => {
if (!call(createUser, validateEmail, userData.email)) {
throw new Error('Invalid email');
}
return await call(createUser, saveUser, userData);
});
// ❌ This won't compile
const badUI = ui(() => {
const user = call(badUI, saveUser, data); // TypeScript error!
// Error: UI layer cannot call Data layer
});Result: Architectural violations become TypeScript compilation errors, not runtime bugs.
✅ Compile-Time Enforcement - Violations are TypeScript errors
✅ Function-Level Granularity - Not just module boundaries
✅ Clean Architecture - Enforces dependency rules automatically
✅ React Integration - Hooks for UI layer
✅ CLI Tools - Analyze and migrate existing code
✅ Testing Utilities - Mock databases, spy functions
✅ Performance Monitoring - Track layer performance
✅ Zero Runtime Overhead - Pure type-level checks
npm install archguard- Quick Start Guide - Get started in 5 minutes
- API Reference - Complete API documentation
- Advanced Patterns - CQRS, Saga, Circuit Breaker, etc.
- Next.js Integration - Full Next.js guide
- Express Integration - Full Express guide
// Pure: Calculations (no side effects)
const calculateTax = pure((amount: number, rate: number) => amount * rate);
// Domain: Business rules
const validateOrder = domain((order: Order) => {
return order.total > 0 && order.items.length > 0;
});
// Data: Database access
const saveOrder = data(async (order: Order) => {
return await db.orders.insert(order);
});
// Service: Orchestration
const createOrder = service(async (orderData: OrderInput) => {
// Validate
const valid = call(createOrder, validateOrder, orderData);
if (!valid) throw new Error('Invalid order');
// Calculate
const tax = call(createOrder, calculateTax, orderData.total, 0.08);
// Save
return await call(createOrder, saveOrder, { ...orderData, tax });
});
// API: HTTP handler
const handleCreateOrder = api(async (req, res) => {
try {
const order = await call(handleCreateOrder, createOrder, req.body);
res.json({ success: true, data: order });
} catch (error) {
res.status(400).json({ error: error.message });
}
});npx archguard src/Shows coverage, violations, and recommendations.
npx archguard-migrate src/Suggests layer markers for existing functions.
import { useServiceQuery, useServiceMutation } from 'archguard/react';
const UserProfile = ui((props) => {
const { data, loading, error } = useServiceQuery(
UserProfile,
getUser,
props.userId
);
if (loading) return <div>Loading...</div>;
return <div>{data.name}</div>;
});| Feature | Nx/Sheriff | ArchGuard |
|---|---|---|
| When Checked | Lint-time/CI | Compile-time |
| Granularity | Modules | Functions |
| Violations | Warnings | Errors |
| Can Bypass | Yes | No |
- Catches violations immediately - While you're writing code
- Can't be bypassed - Code won't compile with violations
- Function-level control - More precise than module boundaries
- Type-safe - Leverages TypeScript's type system
- Zero runtime cost - Pure compile-time checks
UI ──> Service ──> Data
│ │ │
└───────┴──────────┴──> Domain ──> Pure
Dependency Rules:
- Pure: No dependencies
- Domain: Can call Pure
- Data: Can call Pure, Domain
- Service: Can call Pure, Domain, Data
- API: Can call Pure, Domain, Data, Service
- UI: Can call Pure, Domain, Service (NOT Data)
import { MockDatabase, LayeredTestSuite, assert } from 'archguard/testing';
const suite = new LayeredTestSuite();
let mockDB: MockDatabase<User>;
suite.beforeEach(() => {
mockDB = new MockDatabase();
});
suite.test('creates user', async () => {
const user = await createUser({ email: 'test@example.com' });
assert.truthy(user.id);
});
await suite.run();Before ArchGuard:
// This compiles but violates architecture
const MyComponent = () => {
const user = database.users.findOne({ id: '123' }); // ❌ Bad!
return <div>{user.name}</div>;
};After ArchGuard:
const MyComponent = ui(() => {
// ❌ Won't compile - UI cannot call Data
const user = call(MyComponent, getUserFromDB, '123');
});
// ✅ Must use Service layer
const MyComponent = ui(() => {
const user = call(MyComponent, getUserService, '123'); // Compiles!
return <div>{user.name}</div>;
});Created after discovering that every TypeScript architecture tool checks violations AFTER code is written.
ArchGuard fills the gap identified by Opus 4.5's research: function-level architectural enforcement at compile time.
Inspired by Clean Architecture but enforced by TypeScript's type system.
MIT © ArchGuard Contributors
- Documentation: docs/
- Examples: examples/
- Contributing: CONTRIBUTING.md
- Changelog: CHANGELOG.md
Built with 💙 by the ArchGuard community
Making TypeScript codebases maintainable, one layer at a time.