Mechanical TypeScript conventions for testable code: keep IO behind explicit boundaries and make exported free functions composable.
code-arch checks two things:
- IO APIs and packages declared in your catalog only appear in their designated areas.
- Exported free functions with input accept one object parameter.
The IO catalog is explicit. No static tool can reliably infer whether every arbitrary package performs IO, so code-arch guarantees confinement for the APIs and packages you declare.
npm install --save-dev code-arch
npx code-arch init
npx code-arch checkRun npx code-arch readme to print the documentation shipped with the installed package.
init creates code-contracts.config.ts and a short CODE_CONTRACTS.md. A config is required: designated IO areas are application-specific, and the checker will not silently invent them.
import { defineCodeContracts } from "code-arch";
export default defineCodeContracts({
io: {
time: {
apis: ["Date.now", "new Date"],
allowedIn: ["src/infrastructure/time/**"]
},
database: {
packages: ["drizzle-orm", "drizzle-orm/**"],
allowedIn: ["src/infrastructure/database/**"]
},
network: {
apis: ["fetch"],
packages: ["axios", "axios/**"],
allowedIn: ["src/infrastructure/http/**"]
}
},
functions: {
objectParams: true
}
});Top-level fields:
| Field | Type | Default | Description |
|---|---|---|---|
rootDir |
string |
config directory | Root for discovery and reporting |
include |
string[] |
["src/**/*.{ts,tsx}"] |
Source globs to check |
exclude |
string[] |
build, dependency, declaration, fixture, and test files | Globs to skip |
io |
Record<string, IoCapability> |
required | Named IO capabilities and their owners |
functions.objectParams |
boolean |
true |
Enforce object input for exported free functions |
Each IO capability accepts:
| Field | Type | Description |
|---|---|---|
apis |
string[] |
API expressions such as Date.now, new Date, process.env, or console.* |
packages |
string[] |
Exact or globbed module specifiers such as drizzle-orm/** |
allowedIn |
string[] |
Required file globs designating where this IO belongs |
A capability must declare at least one API or package. Package checks cover imports, side-effect imports, re-exports, literal dynamic imports, and literal CommonJS require() calls.
Exported top-level functions may take no input or one object input:
export function healthcheck() {}
interface CreateOrderParams {
customerId: string;
productId: string;
}
export function createOrder(params: CreateOrderParams) {}Primitive, positional, array, tuple, callable, rest, any, unknown, and unconstrained generic inputs fail. Methods and non-exported helpers are outside this convention.
Layer assignment and dependency direction are deliberately not duplicated here. Use ArchUnitTS for those architecture tests:
const rule = projectFiles()
.inFolder("src/domain/**")
.shouldNot()
.dependOnFiles()
.inFolder("src/infrastructure/**");This keeps responsibilities clear: code-arch owns IO confinement and function composition conventions; ArchUnitTS owns dependency graphs, layers, slices, and cycles.
Usage: code-arch <command> [options]
Commands:
init Create a starter config
check Run code contract checks
readme Print this README
rules List available rule IDs
prompt Generate a focused AI refactor prompt
Options for check:
--json Print machine-readable JSON
--rule <rule> Run restrict-io or require-object-params only
Options for prompt:
--rule <rule> Rule ID (required)
--file <file> File to target (otherwise discover all violations)
import { loadConfig, runCheck } from "code-arch";
const config = await loadConfig(process.cwd());
const result = await runCheck(config, process.cwd());- Node.js 20 or newer
- TypeScript project
MIT