npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

eslint-plugin-code-architecture

v0.5.0

Published

Portable ESLint rules for vertical architecture, TigerStyle, Effect, and runtime safety

Readme

eslint-plugin-code-architecture

Portable ESLint rules that turn architectural decisions into fast, local feedback for humans and coding agents.

The plugin combines four ideas:

It is ESM-only, supports ESLint flat config, and does not require type-aware linting.

See References and attribution for the source material behind each policy. Rule pages link directly to the work they adapt.

Install

npm install --save-dev eslint eslint-plugin-code-architecture typescript typescript-eslint

Quick start

// eslint.config.js
import architecture from "eslint-plugin-code-architecture";
import tseslint from "typescript-eslint";

const integrations = [
  // Add only when Effect is installed and used:
  // ...architecture.configs.effect,
  // Add only when React is installed and used:
  // ...architecture.configs.react,
  // Add for compound components whose consumers should own layout:
  // ...architecture.configs.composition,
  // Add only to files that implement or consume strict LEGO object APIs:
  // ...architecture.configs.lego,
];

export default tseslint.config(
  ...architecture.configs.strict,
  ...integrations,
  {
    files: ["**/*.{ts,tsx}"],
    languageOptions: { parser: tseslint.parser },
  },
);

Presets are flat-config arrays and fall into two groups:

  • Library-agnostic: recommended, tigerstyle, and strict. The strict preset combines the other two.
  • Optional library and architecture integrations: effect, react, composition, and lego. These are deliberately excluded from strict; enable them only when the corresponding library and conventions are used.

Adopt incrementally in an existing codebase

The presets describe a target state and intentionally report every violation. On an established codebase, enable a small baseline first, repair its findings, and then promote additional rules one at a time. Library integrations remain opt-in throughout the rollout.

import architecture from "eslint-plugin-code-architecture";
import tseslint from "typescript-eslint";

export default tseslint.config({
  files: ["src/**/*.{ts,tsx}"],
  languageOptions: { parser: tseslint.parser },
  plugins: { "code-architecture": architecture },
  rules: {
    "code-architecture/no-unsafe-type-assertions": "error",
    "code-architecture/no-unvalidated-json-parse": "warn",
  },
});

Once warnings are resolved, change them to errors and add the next rule. Apply effect, react, or composition only to files that use the corresponding library or architecture. Project-specific rules such as enforce-module-boundaries, centralize-domain-literals, and no-raw-design-values still require explicit consumer configuration.

Using Biome and Oxlint alongside the plugin

Biome and Oxlint can handle formatting and broad, high-speed linting while ESLint runs only the architectural rules that remain specific to this plugin. Teams can use either tool or both; this example uses both:

npm install --save-dev --save-exact @biomejs/biome
npm install --save-dev oxlint eslint eslint-plugin-code-architecture typescript typescript-eslint

Create biome.json using the Biome configuration guide. Keep library domains opt-in as well; for example, Biome's React domain should only be enabled in a React project:

{
  "formatter": { "enabled": true },
  "linter": {
    "enabled": true,
    "domains": { "project": "recommended" }
  }
}

Oxlint works without configuration. If a committed config is preferred, initialize one with npx oxlint --init; see the official Oxlint configuration documentation.

{
  "$schema": "./node_modules/oxlint/configuration_schema.json"
}

Use separate commands so each tool has a clear responsibility:

{
  "scripts": {
    "lint": "npm run lint:biome && npm run lint:oxlint && npm run lint:architecture",
    "lint:biome": "biome check .",
    "lint:oxlint": "oxlint .",
    "lint:architecture": "eslint .",
    "format": "biome check --write ."
  }
}

Oxlint also has an alpha JavaScript-plugin compatibility layer. This example intentionally keeps eslint-plugin-code-architecture on ESLint's stable plugin API and follows Oxlint's documented incremental migration approach: run fast general checks first, then ESLint for custom rules.

Architecture boundaries

Verticals are deliberately configured by the consuming application because domain names and dependency direction are project-specific:

import architecture from "eslint-plugin-code-architecture";

export default [
  {
    files: ["src/**/*.{ts,tsx}"],
    plugins: { architecture },
    rules: {
      "architecture/enforce-module-boundaries": [
        "error",
        {
          modules: [
            {
              name: "checkout",
              pattern: "src/checkout/**",
              allow: ["catalog"],
            },
            {
              name: "catalog",
              pattern: "src/catalog/**",
              public: ["**/*.api.*", "**/*.service.*"],
            },
          ],
          allowPrivateImportsFrom: ["src/runtime/**"],
        },
      ],
    },
  },
];

This supports relative imports and configurable aliases. Cross-vertical imports must follow each source vertical's allow list and each target vertical's public patterns. Composition roots can be granted explicit private access.

Domain vocabulary

ESLint visits files independently, so a reliable “literal appears in two files” rule cannot aggregate whole-program state. centralize-domain-literals instead requires the project to declare its fixed vocabulary and approved constants files. This deterministically enforces the stronger rule from the first use.

"architecture/centralize-domain-literals": [
  "error",
  {
    constantsFiles: ["src/constants.ts"],
    literals: [
      { value: "completed", replacement: "JOB_STATUS.COMPLETED" },
      { value: "ollama", replacement: "AI_PROVIDERS.OLLAMA" },
    ],
  },
]

Design tokens

no-raw-design-values prohibits explicitly configured string or numeric values only when they appear in configured object properties. Consumers provide the semantic meaning: which values and properties belong together, their approved token replacements, token files, and narrow exceptions.

"architecture/no-raw-design-values": [
  "error",
  {
    allowedFiles: ["src/ui/tokens/**"],
    values: [
      {
        properties: ["color", "backgroundColor"],
        replacement: "tokens.color.surface",
        value: "#edf0eb",
      },
      {
        properties: ["gap", "padding"],
        replacement: "tokens.space.md",
        value: 16,
      },
    ],
    exceptions: [
      {
        files: ["src/charts/**"],
        properties: ["color"],
        values: ["#edf0eb"],
      },
    ],
  },
]

The rule is deliberately excluded from every preset. It does not assume React, React Native, CSS-in-JS, a token API, or that an arbitrary repeated number is a design value. See no-raw-design-values for its syntax-only limits.

Design-system adoption rules are also opt-in. Activate a rule only after the matching primitive, token family, interaction contract, dismissal pattern, or component variants exist and their intended consumers have migrated. Enabling them earlier would turn architectural feedback into suppressions rather than adoption.

Library-agnostic rules

| Rule | Purpose | Preset | | --- | --- | --- | | centralize-domain-literals | Keep fixed vocabulary in constants modules | Configure | | enforce-module-boundaries | Enforce vertical dependency direction and public surfaces | Configure | | imports-first | Keep dependencies at the top of a module | recommended | | max-function-lines | Enforce the 70-line TigerStyle limit for logic functions | recommended, tigerstyle | | max-function-parameters | Bound positional inputs | recommended, tigerstyle | | no-barrel-files | Disallow re-export barrels | recommended | | no-barrel-imports | Require concrete module imports | recommended, effect | | no-design-identity-overrides | Preserve component identity while permitting layout overrides | Configure | | no-raw-design-properties | Reject any static literal in configured design properties | Configure | | no-raw-design-values | Require configured values to use semantic tokens in configured properties | Configure | | no-unasserted-return | Disallow returning a call's result from an assertion-free function | Configure | | no-unsafe-type-assertions | Ban casts and non-null assertions | recommended | | no-unvalidated-json-parse | Require runtime validation around JSON parsing | recommended | | prefer-design-system-components | Replace configured platform primitives in consumer paths | Configure | | require-assertions | Require assertion density in functions | tigerstyle | | require-dismissible-modal-backdrop | Require close and outside-press paths for transparent surfaces | Configure | | require-interactive-component-contract | Require accessible shared-interaction contracts | Configure |

Optional integrations

These presets are never enabled by recommended, tigerstyle, or strict.

| Ecosystem | Preset behavior | Enable when | | --- | --- | --- | | Effect | effect-error-handling plus no-barrel-imports configured for effect and @effect/platform | The project installs and uses Effect | | React | declarative-components | The project installs and uses React with declarative component conventions | | Composition guardrails | prefer-composition-over-configuration, require-composable-root-children, and no-root-owned-compound-parts | Consumers should control the existence, order, repetition, and nesting of UI parts | | LEGO compound APIs | The composition rules plus require-compound-component-api and require-consumer-owned-compound-usage | Selected files use the public object-namespace Provider/Root-and-parts convention |

Consumer-owned composition

The composition preset provides portable architectural guardrails: a root may coordinate state and infrastructure, but the consumer owns the child hierarchy. It catches statically visible prop-driven hierarchy assembly, requires every top-level boundary return to reference children, and prevents a boundary from rendering its own public parts.

Invalid:

function Accordion({ items, showFooter, renderFooter }) {
  return (
    <section>
      {items.map((item) => <AccordionItem item={item} />)}
      {showFooter && renderFooter()}
    </section>
  );
}

Valid:

<Accordion.Root open={open} setOpen={setOpen}>
  {items.map((item) => (
    <Accordion.Item key={item.id}>
      <Accordion.Trigger>{item.title}</Accordion.Trigger>
      <Accordion.Content>{item.content}</Accordion.Content>
    </Accordion.Item>
  ))}
</Accordion.Root>

Passing composition does not prove a complete LEGO architecture. The preset deliberately does not mandate dot-notation object exports, barrel files, React useState, or a state/actions/meta context shape. Module namespace exports are equally composable, and context organization is a separate convention rather than proof that consumers control structure.

Strict LEGO compound APIs

The opt-in lego preset combines composition with positive, convention-oriented checks. Apply it only to files that define or consume compound APIs; ordinary screens and components are not required to become compounds.

const CounterProvider = ({ children }) => (
  <CounterContext.Provider value={actor}>{children}</CounterContext.Provider>
);
const CounterDisplay = () => <output />;
const CounterIncrement = () => <button />;

export const Counter = {
  Provider: CounterProvider,
  Display: CounterDisplay,
  Increment: CounterIncrement,
};

<Counter.Provider>
  <Counter.Display />
  <Counter.Increment />
</Counter.Provider>

By default, an identified compound object must be exported, expose Provider or Root, expose at least two additional component-valued parts, and avoid duplicate bindings. Imported or same-file compound boundaries must be open and contain a consumer-selected part from the same namespace. Configure boundaryMembers, minimumParts, compoundNamePattern, or headlessCompounds for other conventions and intentional actor/store-backed headless boundaries.

The rules use deterministic same-file syntax analysis. They can validate bindings declared or imported in the current file, but they do not resolve re-export graphs, prove that a component consumes a particular context across files, or prove shared state semantics. No shared-state rule is shipped because naming a hook is not reliable evidence that state is shared. Actor and store implementations are supported without requiring React local state; teams may scope the preset and configure their boundary names without adopting state/actions/meta.

Publishing

See PUBLISHING.md for first-release and automated publishing instructions.

Development

bun install
bun run check
bun run test:coverage
npm pack --dry-run

Every rule change should start with one observable failing test, followed by the smallest implementation and a refactor pass.

npm run smoke:package additionally packs the publishable tarball, installs it into a clean temporary project, and runs ESLint through the package's public export.

License

MIT