Skip to content

Saurav0989/archguard-v1

Repository files navigation

ArchGuard

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.

TypeScript License: MIT

The Problem

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.

The Solution

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.


Features

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


Installation

npm install archguard

Documentation


Quick Example

// 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 });
  }
});

CLI Tools

Analyze Your Codebase

npx archguard src/

Shows coverage, violations, and recommendations.

Migration Helper

npx archguard-migrate src/

Suggests layer markers for existing functions.


React Hooks

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>;
});

Why ArchGuard?

Comparison

Feature Nx/Sheriff ArchGuard
When Checked Lint-time/CI Compile-time
Granularity Modules Functions
Violations Warnings Errors
Can Bypass Yes No

Key Advantages

  1. Catches violations immediately - While you're writing code
  2. Can't be bypassed - Code won't compile with violations
  3. Function-level control - More precise than module boundaries
  4. Type-safe - Leverages TypeScript's type system
  5. Zero runtime cost - Pure compile-time checks

Architecture Layers

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)

Testing

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();

Real-World Impact

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>;
});

Origin Story

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.


License

MIT © ArchGuard Contributors


Links


Built with 💙 by the ArchGuard community

Making TypeScript codebases maintainable, one layer at a time.

About

enterprise-grade architecture enforcement for TypeScript (v1) #typescript

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors