Skip to content

Presets

Presets are pre-configured packages of boundaries and rules for common architecture patterns. They provide instant architecture enforcement without manual configuration.

What is a Preset?

A preset packages:

  • Boundary definitions - Layers and modules
  • Rule definitions - Allowed and forbidden dependencies
  • Architecture documentation - Best practices and examples
  • Diagrams - Visual representation of the architecture

Instead of defining 20+ boundaries and 50+ rules manually, use a preset:

.stricture/config.json
{
"preset": "@stricture/hexagonal"
}

This single line gives you complete hexagonal architecture enforcement!

Available Official Presets

Architecture Patterns

@stricture/hexagonal

Ports & Adapters - Isolate domain logic from infrastructure

Best for: DDD, microservices, complex business rules

Learn more →

@stricture/layered

N-Tier Architecture - Traditional layered approach

Best for: Enterprise apps, CRUD applications

Learn more →

@stricture/clean

Clean Architecture - Concentric dependency circles

Best for: Testability, framework independence

Learn more →

@stricture/modular

Feature Modules - Vertical slice architecture

Best for: Large apps, team independence

Learn more →

Framework-Specific

@stricture/nextjs

Next.js App Router - Server/client boundaries

Best for: Next.js 13+ applications

Learn more →

@stricture/nestjs

NestJS Modules - Module boundaries and DI

Best for: NestJS applications

Learn more →

Using Presets

The simplest way to use a preset:

eslint.config.js
import stricture from '@stricture/eslint-plugin'
export default [
stricture.configs.hexagonal() // Auto-loads @stricture/hexagonal
]

That’s it! No .stricture/config.json needed.

With Customization

Add overrides to the preset:

eslint.config.js
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
}
]
})
]

File-Based Configuration

For complex setups, use a separate file:

Install the plugin:

Terminal window
npm install -D @stricture/eslint-plugin
# or
pnpm add -D @stricture/eslint-plugin
# or
yarn add -D @stricture/eslint-plugin

Reference it in your config:

.stricture/config.json
{
"preset": "@stricture/hexagonal"
}

Configure ESLint:

eslint.config.js
export default [
stricture.configs.recommended()
]

Preset Requirements

Most presets expect a specific directory structure:

src/
├── core/
│ ├── domain/ # Pure business logic
│ ├── ports/ # Interfaces
│ └── application/ # Use cases
└── adapters/
├── driving/ # Entry points
└── driven/ # Implementations

Check each preset’s documentation for exact requirements.

Extending Presets

You can customize presets by adding boundaries, rules, or overrides.

Adding Boundaries

Add custom boundaries while keeping preset’s boundaries:

.stricture/config.json
{
"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.

Adding Rules

Add new rules to preset’s rules:

.stricture/config.json
{
"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.

Overriding Rules

Modify specific preset rules without redefining everything:

.stricture/config.json
{
"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 level
  • message - Custom error message
  • allowed - Flip allow/deny (use carefully!)
  • examples - Custom code examples

Multiple Presets (Extends)

Compose multiple presets for complex scenarios:

.stricture/config.json
{
"preset": "@stricture/hexagonal",
"extends": [
"@company/shared-rules",
"@company/security-policies",
"@company/api-standards"
]
}

Merge order:

  1. Base preset (preset)
  2. Extended presets (extends array, left to right)
  3. Your custom boundaries
  4. Your custom rules
  5. Overrides (applied last)

Use Cases for Multiple Presets

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"]
}

Creating Custom Presets

Create reusable presets for your organization.

Preset Structure

// @mycompany/architecture-preset/src/index.ts
import 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

Package.json

{
"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"
}
}

Publishing

Terminal window
# Build
pnpm build
# Publish to npm
npm publish --access public
# Or publish to private registry
npm publish --registry https://npm.mycompany.com

Using Custom Preset

Terminal window
pnpm add -D @mycompany/architecture-preset
.stricture/config.json
{
"preset": "@mycompany/architecture-preset"
}

Preset Best Practices

Start with Official Presets

// ✅ GOOD - Use proven pattern
{
"preset": "@stricture/hexagonal"
}
// ❌ AVOID - Building from scratch
{
"boundaries": [ /* 30 custom boundaries */ ],
"rules": [ /* 100 custom rules */ ]
}

Document Your Customizations

{
"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"
}
]
}

Version Your Custom Presets

{
"preset": "@mycompany/architecture-preset",
"metadata": {
"presetVersion": "2.1.0",
"migrationGuide": "https://wiki.company.com/architecture/v2-migration"
}
}

Follow semantic versioning for preset packages.

Test Your Presets

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.md

Common Preset Scenarios

Gradual Adoption

Start loose, tighten over time:

.stricture/config.json
{
"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"
}
]
}

Monorepo with Multiple Presets

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/modular

Team-Specific Rules

Add team standards to base preset:

.stricture/config.json
{
"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."
}
]
}

Migration Between Presets

Migrate from one architecture to another:

.stricture/config.json (During Migration)
{
"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 Comparison

When to Use Each Preset

PresetBest ForComplexityLearning Curve
HexagonalDDD, microservices, complex business logicHighMedium
LayeredTraditional apps, CRUD, enterpriseLowLow
CleanFramework independence, testabilityHighHigh
ModularLarge apps, feature teams, vertical slicesMediumMedium
Next.jsNext.js App Router, server/client splitMediumLow
NestJSNestJS applications, module boundariesMediumLow

Preset Feature Matrix

FeatureHexagonalLayeredCleanModular
Domain isolation
Ports & Adapters
Layer enforcement
Module independence
Framework agnostic
DDD support⚠️⚠️

Troubleshooting

Preset Not Found

Error: Could not resolve preset '@stricture/hexagonal'

Solution:

Terminal window
pnpm add -D @stricture/hexagonal

Ensure the preset package is installed in your project.

Preset Structure Mismatch

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' folder
src/adapters/** ❌ Missing 'driving'/'driven' split

Fix: Reorganize files or customize boundaries.

Override Not Working

Problem: Override seems ignored

Debug:

  1. Check rule id matches exactly (case-sensitive)
  2. Verify override is in overrides array, not rules
  3. Check if you’re overriding the right property
// ❌ 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!
}
]
}

Conflicting Presets

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
}
]
}

Next Steps

Configuration File

Master the complete configuration file format.

Read guide →