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

mm-linter

v1.2.0

Published

Custom ESLint rules for Miris projects

Readme

mm-linter

Custom ESLint rules for Miris projects.

Installation

npm install --save-dev mm-linter eslint

Usage

Flat config example:

const mmLinter = require("mm-linter");

module.exports = [
  {
    plugins: {
      mm: mmLinter,
    },
    rules: {
      "mm/async-safety": "error",
      "mm/barrel-boundary": ["error", {
        boundaries: [{ directoryName: "domain" }],
      }],
      "mm/comment-contract": ["error", {
        targets: [
          {
            directories: ["usecases", "repositories"],
            minDescriptionLength: 12,
          },
        ],
      }],
      "mm/dangerous-api": ["error", {
        rootDirs: ["src"],
        layers: {
          domain: {
            deny: ["Date.now", "Math.random", "fetch", "process.env"],
          },
          application: {
            deny: ["console.log"],
          },
        },
      }],
      "mm/dependency-injection-enforcement": ["error", {
        rootDirs: ["src"],
        layers: {
          application: {
            denyNew: ["*Repository", "*RepositoryImpl", "*Client", "*Service"],
          },
          presentation: {
            denyNew: ["*Usecase", "*Repository", "*Client"],
          },
        },
      }],
      "mm/duplicate-role-detection": "error",
      "mm/directory-export-name": ["error", {
        targets: [
          {
            directoryName: "factory",
            pattern: "Factory$",
            format: "XxxFactory",
          },
        ],
      }],
      "mm/dto-export-name": "error",
      "mm/explicit-public-surface": "error",
      "mm/forbidden-relative-depth": ["error", { maxDepth: 2 }],
      "mm/framework-leakage": ["error", {
        rootDirs: ["src"],
        layers: {
          domain: {
            denyImports: ["react", "express", "@prisma/client", "@nestjs/*"],
          },
          application: {
            denyImports: ["react", "@nestjs/*"],
          },
        },
      }],
      "mm/hooks-export-name": "error",
      "mm/layer-import-direction": ["error", {
        rootDirs: ["src"],
        aliases: ["@"],
        layers: {
          domain: {},
          application: {},
          infrastructure: {},
          presentation: {},
        },
        forbidden: {
          domain: ["application", "infrastructure", "presentation"],
          application: ["infrastructure", "presentation"],
        },
      }],
      "mm/mutation-guard": "error",
      "mm/naming-consistency-between-file-and-symbol": "error",
      "mm/name-responsibility-match": ["error", {
        targets: [
          {
            suffix: "Usecase",
            allowedDirectories: ["usecases"],
            requiredMethods: ["execute"],
          },
        ],
      }],
      "mm/no-cross-layer-instantiation": ["error", {
        rootDirs: ["src"],
        aliases: ["@"],
        layers: {
          application: {},
          presentation: {},
        },
        forbidden: {
          application: ["presentation"],
        },
      }],
      "mm/no-empty-catch-or-swallow": "error",
      "mm/no-inline-implementation-in-interface-layer": ["error", {
        layers: {
          presentation: {
            maxStatements: 2,
          },
        },
      }],
      "mm/no-todo-shipping": "error",
      "mm/presentation-export-name": "error",
      "mm/query-command-separation": ["error", {
        targets: [
          {
            directories: ["usecases"],
            queryNamePatterns: ["*Query", "get*", "find*"],
            commandNamePatterns: ["*Command", "execute*", "handle*"],
            mutationCallPatterns: ["repository.save*", "repository.update*", "repository.delete*"],
          },
        ],
      }],
      "mm/result-error-handling": ["error", {
        targets: [
          {
            directories: ["usecases"],
            functionNamePatterns: ["execute*", "handle*"],
            resultCallPatterns: ["Result.*", "ok", "err"],
          },
        ],
      }],
      "mm/serialization-boundary": ["error", {
        rootDirs: ["src"],
        layers: {
          domain: {
            deny: ["JSON.stringify", "JSON.parse", "structuredClone"],
          },
          application: {
            deny: ["JSON.stringify", "JSON.parse"],
          },
        },
      }],
      "mm/test-coupling-guard": ["error", {
        testFilePatterns: [".test.", ".spec."],
        maxInternalDepth: 3,
        denyImportSegments: ["internal", "private", "impl"],
      }],
      "mm/structured-log-contract": ["error", {
        targets: ["console.*", "logger.*", "log.*"],
        requiredKeys: ["timestamp", "applicationName", "level", "description"],
      }],
      "mm/side-effect-boundary": ["error", {
        rootDirs: ["src"],
        layers: {
          domain: {
            deny: ["fetch", "console.log", "process.env", "Date.now"],
          },
          application: {
            deny: ["console.log"],
          },
        },
      }],
      "mm/testability-guard": "error",
      "mm/usecase-export-name": "error",
    },
  },
];

Rules

mm/async-safety

Disallows floating async or promise-returning calls unless they are awaited, returned, assigned, or explicitly ignored with void.

mm/dependency-injection-enforcement

Disallows direct new of configured dependency roles in selected layers, so DI or factory/provider paths are used instead.

mm/duplicate-role-detection

Detects duplicate exported role names such as repeated *Usecase or *Repository implementations within the same lint run.

mm/comment-contract

Requires a minimal JSDoc contract comment on exported APIs in selected directories.

mm/test-coupling-guard

Restricts tests from importing deep internal implementation paths instead of stable public surfaces.

mm/structured-log-contract

Requires log calls to emit an object payload containing at least timestamp, applicationName, level, and description.

mm/dangerous-api

Disallows risky APIs by layer.

Example:

"mm/dangerous-api": ["error", {
  rootDirs: ["src"],
  layers: {
    domain: {
      deny: ["Date.now", "Math.random", "fetch", "process.env"],
    },
    application: {
      deny: ["console.log"],
    },
  },
}]

This rule checks:

  • function calls like fetch()
  • member calls like Date.now()
  • member access like process.env
  • constructor calls like new Date()

mm/barrel-boundary

Requires imports to go through configured barrel directories.

mm/forbidden-relative-depth

Disallows overly deep relative imports like ../../../x.

mm/framework-leakage

Disallows framework and library imports in restricted layers such as domain or application.

mm/side-effect-boundary

Disallows top-level side effects such as fetch(), console.log(), process.env, or Date.now() in restricted layers.

mm/result-error-handling

Requires selected exported functions to return a Result-style value and avoid direct throw.

mm/serialization-boundary

Disallows direct JSON.stringify, JSON.parse, and similar serialization APIs in restricted layers.

mm/query-command-separation

Ensures queries stay read-only and commands do not directly return object or array read models.

mm/mutation-guard

Disallows public setters on *ValueObject and prevents this.x = ... reassignment outside constructors for selected class roles.

mm/naming-consistency-between-file-and-symbol

Ensures exported symbol names stay aligned with the file basename.

mm/name-responsibility-match

Checks that names like *Usecase or *Repository are placed in the right directory and define required methods.

mm/no-inline-implementation-in-interface-layer

Limits inline statement count in interface-layer handlers such as presentation or controllers.

mm/no-cross-layer-instantiation

Disallows new on classes imported from forbidden layers.

mm/testability-guard

Disallows test-hostile APIs such as Date.now() or Math.random() in core layers.

mm/no-todo-shipping

Disallows shipping comments like TODO, FIXME, or temporary.

mm/no-empty-catch-or-swallow

Disallows empty catch blocks and obvious swallowed errors.

mm/explicit-public-surface

Restricts exports to explicit entry files such as index.ts.

mm/directory-export-name

Configurable rule for exported function names in arbitrary directories.

Example:

"mm/directory-export-name": ["error", {
  targets: [
    {
      directoryName: "factory",
      pattern: "Factory$",
      format: "XxxFactory",
    },
    {
      directoryName: "presenter",
      pattern: "Presenter$",
      format: "XxxPresenter",
      subject: "presenters",
    },
  ],
}]

mm/dto-export-name

Requires exported functions in dto directories to use the XxxDto naming convention.

Checked targets:

  • export function ...
  • export const ...

Only files whose path includes /dto/ are checked.

Valid:

export function UserDto() {
  return null;
}

export const SessionDto = () => null;

Invalid:

export function user() {
  return null;
}

export const session = () => null;

Non-exported helper functions are allowed.

mm/presentation-export-name

Requires exported functions in presentation directories to use the XxxPresentation naming convention.

Checked targets:

  • export function ...
  • export const ...

Only files whose path includes /presentation/ are checked.

mm/usecase-export-name

Requires exported functions in usecase directories to use the XxxUsecase naming convention.

Checked targets:

  • export function ...
  • export const ...

Only files whose path includes /usecase/ are checked.

mm/hooks-export-name

Requires exported functions in hooks directories to use the useXxx naming convention.

Checked targets:

  • export function ...
  • export const ...

Only files whose path includes /hooks/ are checked.

Valid:

export function useAuth() {
  return null;
}

export const useSession = () => null;

Invalid:

export function auth() {
  return null;
}

export const session = () => null;

Non-exported helper functions are allowed.

mm/layer-structure

Enforces directory names, file names, and function names for each architecture layer.

Example:

const mmLinter = require("mm-linter");

module.exports = [
  {
    plugins: {
      mm: mmLinter,
    },
    rules: {
      "mm/layer-structure": ["error", {
        rootDirs: ["src"],
        denyLayers: ["presentation"],
        layers: {
          domain: {
            directories: {
              allow: ["entities", "services", "shared"],
              deny: ["controllers", "hooks"],
            },
            files: {
              allow: ["*.entity.ts", "*.service.ts", "index.ts"],
              deny: ["*.controller.ts", "*.hook.ts"],
            },
            functions: {
              allow: ["create*", "rebuild*", "validate*"],
              deny: ["use*", "handle*", "render*"],
            },
          },
          infrastructure: {
            enabled: false,
          },
        },
      }],
    },
  },
];

To lock the base policy and only allow selected overrides, use the config helper:

const mmLinter = require("mm-linter");

const layerOptions = mmLinter.configs.defineLayerStructureOptions(
  {
    locked: true,
    customization: {
      allowNewLayers: false,
      allowAllowRules: false,
      allowDenyRules: true,
    },
    layers: {
      domain: {
        files: {
          allow: ["*.entity.ts"],
          deny: ["*.controller.ts"],
        },
      },
    },
  },
  {
    denyLayers: ["presentation"],
  }
);

mm/layer-import-direction

Enforces forbidden import directions between layers.

Example:

"mm/layer-import-direction": ["error", {
  rootDirs: ["src"],
  aliases: ["@"],
  layers: {
    domain: {},
    application: {},
    infrastructure: {},
    presentation: {},
  },
  forbidden: {
    domain: ["application", "infrastructure", "presentation"],
    application: ["infrastructure", "presentation"],
  },
}]

This rule checks:

  • import ... from "..."
  • export ... from "..."
  • require("...")

Development

npm test

To preview the published tarball contents:

npm pack --dry-run