@stricture/hexagonal
Ports & Adapters - Isolate domain logic from infrastructure
Best for: DDD, microservices, complex business rules
Presets are pre-configured packages of boundaries and rules for common architecture patterns. They provide instant architecture enforcement without manual configuration.
A preset packages:
Instead of defining 20+ boundaries and 50+ rules manually, use a preset:
{ "preset": "@stricture/hexagonal"}This single line gives you complete hexagonal architecture enforcement!
@stricture/hexagonal
Ports & Adapters - Isolate domain logic from infrastructure
Best for: DDD, microservices, complex business rules
@stricture/layered
N-Tier Architecture - Traditional layered approach
Best for: Enterprise apps, CRUD applications
@stricture/clean
Clean Architecture - Concentric dependency circles
Best for: Testability, framework independence
@stricture/modular
@stricture/nextjs
@stricture/nestjs
The simplest way to use a preset:
import stricture from '@stricture/eslint-plugin'
export default [ stricture.configs.hexagonal() // Auto-loads @stricture/hexagonal]That’s it! No .stricture/config.json needed.
Add overrides to the preset:
export default [ stricture.configs.hexagonal({ ignorePatterns: ['**/*.test.ts'], boundaries: [ { name: 'shared', pattern: 'src/shared/**', mode: 'file', tags: ['shared'] } ], rules: [ { id: 'all-to-shared', from: { pattern: '**' }, to: { tag: 'shared' }, allowed: true } ] })]For complex setups, use a separate file:
Install the plugin:
npm install -D @stricture/eslint-plugin# orpnpm add -D @stricture/eslint-plugin# oryarn add -D @stricture/eslint-pluginReference it in your config:
{ "preset": "@stricture/hexagonal"}Configure ESLint:
export default [ stricture.configs.recommended()]Most presets expect a specific directory structure:
src/├── core/│ ├── domain/ # Pure business logic│ ├── ports/ # Interfaces│ └── application/ # Use cases└── adapters/ ├── driving/ # Entry points └── driven/ # Implementationssrc/├── presentation/ # Controllers, views├── business/ # Business logic├── data/ # Data access└── infrastructure/ # Cross-cuttingsrc/├── entities/ # Enterprise rules├── use-cases/ # Application logic├── interface-adapters/ # Controllers, gateways└── frameworks/ # External frameworksapp/ # App Router├── (pages)/ # Server components│ └── page.tsx├── api/ # API routes└── components/ # Client componentsCheck each preset’s documentation for exact requirements.
You can customize presets by adding boundaries, rules, or overrides.
Add custom boundaries while keeping preset’s boundaries:
{ "preset": "@stricture/hexagonal", "boundaries": [ { "name": "shared", "pattern": "src/shared/**", "mode": "file", "tags": ["shared"], "metadata": { "description": "Shared utilities accessible from all layers" } }, { "name": "test-utils", "pattern": "src/test-utils/**", "mode": "file", "tags": ["test"] } ], "rules": [ { "id": "all-to-shared", "name": "All Can Use Shared", "description": "Shared utilities are accessible everywhere", "severity": "error", "from": { "pattern": "**", "mode": "file" }, "to": { "tag": "shared", "mode": "file" }, "allowed": true } ]}Result: Your boundaries merge with preset’s boundaries. You now have preset’s boundaries + your custom ones.
Add new rules to preset’s rules:
{ "preset": "@stricture/hexagonal", "rules": [ { "id": "no-legacy-imports", "name": "No Legacy Code", "description": "New code cannot import legacy modules", "severity": "error", "from": { "pattern": "src/**", "exclude": ["src/legacy/**"], "mode": "file" }, "to": { "pattern": "src/legacy/**", "mode": "file" }, "allowed": false, "message": "Don't depend on legacy code. Refactor or duplicate needed logic." }, { "id": "enforce-barrel-exports", "name": "Use Barrel Exports", "description": "Import from index files, not internal files", "severity": "warn", "from": { "tag": "driving", "mode": "file" }, "to": { "pattern": "src/core/**/!(index).ts", "mode": "file" }, "allowed": false, "message": "Import from barrel exports (index.ts) for better encapsulation" } ]}Result: Your rules merge with preset’s rules.
Modify specific preset rules without redefining everything:
{ "preset": "@stricture/hexagonal", "overrides": [ { "id": "domain-isolation", "severity": "warn" // Change from error to warning }, { "id": "driving-independent", "severity": "off" // Disable this rule completely }, { "id": "application-not-adapters", "message": "CUSTOM: Application uses ports, not adapters. See wiki.company.com/di" } ]}Overridable properties:
severity - Change error levelmessage - Custom error messageallowed - Flip allow/deny (use carefully!)examples - Custom code examplesCompose multiple presets for complex scenarios:
{ "preset": "@stricture/hexagonal", "extends": [ "@company/shared-rules", "@company/security-policies", "@company/api-standards" ]}Merge order:
preset)extends array, left to right)Organizational Standards:
{ "preset": "@stricture/hexagonal", "extends": ["@mycompany/code-standards"]}Security Policies:
{ "preset": "@stricture/layered", "extends": ["@security/no-eval", "@security/secure-imports"]}Framework + Architecture:
{ "preset": "@stricture/nextjs", "extends": ["@stricture/hexagonal"]}Shared Team Rules:
{ "preset": "@stricture/clean", "extends": ["@team/backend-standards", "@team/api-patterns"]}Create reusable presets for your organization.
// @mycompany/architecture-preset/src/index.tsimport type { ArchPreset } from '@stricture/core'
export const preset: ArchPreset = { id: '@mycompany/architecture-preset', name: 'My Company Architecture', description: 'Standard architecture pattern for My Company applications',
boundaries: [ { name: 'api-layer', pattern: 'src/api/**', mode: 'file', tags: ['api', 'presentation'] }, { name: 'business-logic', pattern: 'src/business/**', mode: 'file', tags: ['business'] }, { name: 'data-access', pattern: 'src/data/**', mode: 'file', tags: ['data'] } ],
rules: [ { id: 'api-to-business', name: 'API Calls Business', description: 'API layer uses business layer', severity: 'error', from: { tag: 'api', mode: 'file' }, to: { tag: 'business', mode: 'file' }, allowed: true }, { id: 'business-to-data', name: 'Business Uses Data', description: 'Business layer uses data layer', severity: 'error', from: { tag: 'business', mode: 'file' }, to: { tag: 'data', mode: 'file' }, allowed: true }, { id: 'no-upward-deps', name: 'No Upward Dependencies', description: 'Lower layers cannot depend on higher layers', severity: 'error', from: { tag: 'data', mode: 'file' }, to: { tag: 'business', mode: 'file' }, allowed: false, message: 'Data layer cannot depend on business layer - this violates layered architecture' } ],
metadata: { version: '1.0.0', author: 'Architecture Team', url: 'https://wiki.mycompany.com/architecture' }}
export default preset{ "name": "@mycompany/architecture-preset", "version": "1.0.0", "main": "dist/index.js", "types": "dist/index.d.ts", "files": ["dist"], "scripts": { "build": "tsc", "prepublish": "pnpm build" }, "peerDependencies": { "@stricture/core": "^1.0.0" }, "devDependencies": { "@stricture/core": "^1.0.0", "typescript": "^5.0.0" }}# Buildpnpm build
# Publish to npmnpm publish --access public
# Or publish to private registrynpm publish --registry https://npm.mycompany.compnpm add -D @mycompany/architecture-preset{ "preset": "@mycompany/architecture-preset"}// ✅ GOOD - Use proven pattern{ "preset": "@stricture/hexagonal"}
// ❌ AVOID - Building from scratch{ "boundaries": [ /* 30 custom boundaries */ ], "rules": [ /* 100 custom rules */ ]}{ "preset": "@stricture/hexagonal", "boundaries": [ { "name": "legacy", "pattern": "src/legacy/**", "tags": ["legacy"], "metadata": { "description": "TEMPORARY: Legacy code being migrated", "deadline": "2024-Q3", "ticket": "ARCH-789" } } ], "rules": [ { "id": "isolate-legacy", "description": "Prevent new dependencies on legacy code", "severity": "error", "from": { "pattern": "src/**", "exclude": ["src/legacy/**"] }, "to": { "tag": "legacy" }, "allowed": false, "message": "Don't depend on legacy code - it's being replaced" } ]}{ "preset": "@mycompany/architecture-preset", "metadata": { "presetVersion": "2.1.0", "migrationGuide": "https://wiki.company.com/architecture/v2-migration" }}Follow semantic versioning for preset packages.
Create example projects that use your preset:
@mycompany/architecture-preset/├── src/│ └── index.ts # Preset definition├── examples/│ ├── basic/ # Basic usage│ │ └── .stricture/config.json│ └── advanced/ # Advanced usage│ └── .stricture/config.json├── tests/│ └── preset.test.ts # Preset validation tests└── README.mdStart loose, tighten over time:
{ "preset": "@stricture/hexagonal", "overrides": [ // Phase 1: Warnings only { "id": "domain-isolation", "severity": "warn" }, { "id": "application-not-adapters", "severity": "warn" } ], "ignorePatterns": [ "src/legacy/**" // Exclude legacy code ]}Later (Phase 2):
{ "preset": "@stricture/hexagonal", "overrides": [ // Upgrade to errors { "id": "domain-isolation", "severity": "error" } ]}Different packages use different presets:
packages/├── backend/│ └── .stricture/│ └── config.json # Uses @stricture/hexagonal├── frontend/│ └── .stricture/│ └── config.json # Uses @stricture/nextjs└── shared/ └── .stricture/ └── config.json # Uses @stricture/modularAdd team standards to base preset:
{ "preset": "@stricture/hexagonal", "extends": [ "@mycompany/backend-standards" // Shared team rules ], "rules": [ // Project-specific rules { "id": "feature-flags-isolation", "description": "Feature flags isolated to configuration layer", "severity": "error", "from": { "tag": "domain" }, "to": { "pattern": "src/config/feature-flags/**" }, "allowed": false, "message": "Domain cannot access feature flags directly. Use configuration ports." } ]}Migrate from one architecture to another:
{ "preset": "@stricture/hexagonal", // Target architecture "boundaries": [ { "name": "legacy-layered", "pattern": "src/old/**", "tags": ["legacy"] } ], "rules": [ { "id": "legacy-isolation", "description": "Isolate legacy code during migration", "severity": "error", "from": { "tag": "legacy" }, "to": { "tag": "core" }, "allowed": false, "message": "Migrate legacy code to hexagonal structure first" }, { "id": "new-not-legacy", "severity": "error", "from": { "tag": "core" }, "to": { "tag": "legacy" }, "allowed": false, "message": "Don't depend on legacy code - refactor it first" } ], "metadata": { "migration": { "from": "layered", "to": "hexagonal", "status": "in-progress", "completion": "45%" } }}| Preset | Best For | Complexity | Learning Curve |
|---|---|---|---|
| Hexagonal | DDD, microservices, complex business logic | High | Medium |
| Layered | Traditional apps, CRUD, enterprise | Low | Low |
| Clean | Framework independence, testability | High | High |
| Modular | Large apps, feature teams, vertical slices | Medium | Medium |
| Next.js | Next.js App Router, server/client split | Medium | Low |
| NestJS | NestJS applications, module boundaries | Medium | Low |
| Feature | Hexagonal | Layered | Clean | Modular |
|---|---|---|---|---|
| Domain isolation | ✅ | ❌ | ✅ | ❌ |
| Ports & Adapters | ✅ | ❌ | ✅ | ❌ |
| Layer enforcement | ✅ | ✅ | ✅ | ❌ |
| Module independence | ❌ | ❌ | ❌ | ✅ |
| Framework agnostic | ✅ | ✅ | ✅ | ✅ |
| DDD support | ✅ | ⚠️ | ✅ | ⚠️ |
Error: Could not resolve preset '@stricture/hexagonal'
Solution:
pnpm add -D @stricture/hexagonalEnsure the preset package is installed in your project.
Error: Multiple violations from preset rules
Solution: Check your directory structure matches preset expectations. Read preset documentation for required structure.
Example - Hexagonal expects:
src/core/domain/**src/core/ports/**src/core/application/**src/adapters/driving/**src/adapters/driven/**If you have:
src/domain/** ❌ Missing 'core' foldersrc/adapters/** ❌ Missing 'driving'/'driven' splitFix: Reorganize files or customize boundaries.
Problem: Override seems ignored
Debug:
id matches exactly (case-sensitive)overrides array, not rules// ❌ WRONG - putting override in rules{ "preset": "@stricture/hexagonal", "rules": [ { "id": "domain-isolation", "severity": "warn" // Won't work here! } ]}
// ✅ CORRECT - using overrides{ "preset": "@stricture/hexagonal", "overrides": [ { "id": "domain-isolation", "severity": "warn" // Works! } ]}Problem: Multiple presets have conflicting rules
Solution: Later presets override earlier ones. Adjust order:
{ "preset": "@stricture/hexagonal", "extends": [ "@company/general-rules", // Applied first "@company/specific-rules" // Overrides general rules ]}Or use overrides to resolve conflicts:
{ "preset": "@stricture/hexagonal", "extends": ["@company/conflicting-preset"], "overrides": [ { "id": "conflicting-rule", "severity": "off" // Disable problematic rule } ]}Configuration File
Master the complete configuration file format.
Explore Presets
Browse detailed preset documentation.
Custom Rules
Learn to write custom architecture rules.
Examples
See presets in action with real examples.