Skip to content

CheckerBoundaries ​

Checks architectural import boundaries: scans the project’s TypeScript files, resolves imports through tsconfig.json, and reports forbidden imports, grouping violations by file.

ts
tsconfigPath: string

The path to tsconfig for resolving imports and types

ts
extensions: Array<string>

An array of file extensions to check

ts
rules: Array<{
  entries: Array<string>;
  exclude?: RegExp;
  allow: {
    imports: Array<string>;
    importsInsideScope: 
      'none' | 'all' | 'sameTopLevel';
    node?: boolean;
    external?: boolean;
  };
}>

An array of rules, each containing:

entries — glob patterns for finding files relative to rootDir, such as features/**
exclude — a regular expression for excluding files from processing

allow — an object with the following fields:

imports — glob patterns for allowed imports relative to rootDir
importsInsideScope — the policy for imports within the scope
node — allows imports with the node: prefix
external — allows modules identified by TypeScript as external libraries

importsInsideScope ​

  • none — forbids imports within the same scope unless explicitly allowed through allow.imports;
  • all — allows any cross-imports within the scope;
  • sameTopLevel — allows imports within the same module or feature, but forbids imports from a neighboring module or feature.

Example: Imports between the API and models layers

API files can import models, but not neighboring API files. Models can only import each other; test files are excluded from the check

ts
new CheckerBoundaries({
  tsconfigPath: path.resolve(rootDir, 'tsconfig.json'),
  extensions: ['.ts'],
  rules: [
    {
      entries: ['api/**'],
      exclude: /\.test\.ts$/,
      allow: {
        imports: ['models/**'],
        importsInsideScope: 'none',
      },
    },
    {
      entries: ['models/**'],
      allow: {
        imports: [],
        importsInsideScope: 'all',
      },
    },
  ],
}),
Run Sandbox
USER: tsconfig.json was created with content:
json
{
  "include": ["**/*.ts"],
  "compilerOptions": {
    "target": "ES2023",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "strict": true
  }
}
USER: models/status.ts was created with content:
ts
export type TypeStatus = 'draft' | 'confirmed';
USER: models/order.ts was created with content:
ts
import type { TypeStatus } from './status';

export type TypeOrder = { id: string; status: TypeStatus };
USER: api/orders.ts was created with content:
ts
import type { TypeOrder } from '../models/order';

export type TypeResponse = { orders: Array<TypeOrder> };
USER: api/orders.test.ts was created with content:
ts
import type { TypeResponse } from './orders';

export type TypeTestResponse = TypeResponse;
ARFA Generator shows logs:
txt
[ARFA-GEN] finished in 0ms

Example: Feature module isolation

sameTopLevel allows imports within the same feature module and from shared, but blocks direct imports from neighboring modules, even through aliases and with broad allow.imports patterns

ts
new CheckerBoundaries({
  tsconfigPath: path.resolve(rootDir, 'tsconfig.json'),
  extensions: ['.ts'],
  rules: [
    {
      entries: ['features/**'],
      allow: {
        imports: ['shared/**', 'features/**'],
        importsInsideScope: 'sameTopLevel',
      },
    },
  ],
}),
Run Sandbox
USER: tsconfig.json was created with content:
json
{
  "include": ["**/*.ts"],
  "compilerOptions": {
    "target": "ES2023",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "strict": true,
    "paths": {
      "features/*": ["./features/*"],
      "shared/*": ["./shared/*"]
    }
  }
}
USER: shared/formatId.ts was created with content:
ts
export const formatId = (id: string) => id.toUpperCase();
USER: features/orders/model.ts was created with content:
ts
export type TypeOrder = { id: string };
USER: features/customers/model.ts was created with content:
ts
export type TypeCustomer = { id: string };
USER: features/orders/view.ts was created with content:
ts
import type { TypeOrder } from 'features/orders/model';
import { formatId } from 'shared/formatId';

export const orderLabel = (order: TypeOrder) => formatId(order.id);
ARFA Generator shows logs:
txt
[ARFA-GEN] finished in 0ms

Example: External packages and Node.js

Server files are allowed to use node:* imports, while client files are allowed to import external packages. Each permission applies independently

ts
new CheckerBoundaries({
  tsconfigPath: path.resolve(rootDir, 'tsconfig.json'),
  extensions: ['.ts'],
  rules: [
    {
      entries: ['server/**'],
      allow: {
        imports: [],
        importsInsideScope: 'none',
        node: true,
      },
    },
    {
      entries: ['client/**'],
      allow: {
        imports: [],
        importsInsideScope: 'none',
        external: true,
      },
    },
  ],
}),
Run Sandbox
USER: tsconfig.json was created with content:
json
{
  "include": ["**/*.ts"],
  "compilerOptions": {
    "target": "ES2023",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "strict": true
  }
}
USER: server/report.ts was created with content:
ts
import path from 'node:path';

export const reportPath = path.join('reports', 'today');
USER: client/value.ts was created with content:
ts
import type { Ref } from 'vue';

export type TypeValue = Ref<string>;
ARFA Generator shows logs:
txt
[ARFA-GEN] finished in 0ms

MIT License