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

stratifyjs

v3.0.0

Published

Enforce architectural layer boundaries in monorepos

Downloads

16

Readme

Stratify

Enforce architectural layer boundaries in monorepos. Catches invalid cross-layer dependencies at build time by analyzing internal dependency protocols (e.g. workspace:, link:, file:) in package.json files.

Installation

npm install stratifyjs
# or
yarn add stratifyjs

For CLI-only usage you can install globally:

npm install -g stratifyjs

Quick Start

  1. Add a "layer" field to each workspace package.json:
{
    "name": "my-feature",
    "layer": "features",
    "dependencies": {
        "my-core-lib": "workspace:*"
    }
}
  1. Create a stratify.config.json at your workspace root:
{
    "layers": {
        "features": {
            "description": "Feature packages",
            "allowedDependencies": ["core", "shared"]
        },
        "core": {
            "description": "Core business logic",
            "allowedDependencies": ["shared"]
        },
        "shared": {
            "description": "Shared utilities",
            "allowedDependencies": []
        }
    }
}
  1. Run:
stratify --config stratify.config.json

CLI Usage

stratify [options]

| Option | Default | Description | | --------------------- | ---------------------- | ---------------------------------------------------- | | -c, --config <path> | stratify.config.json | Path to the layer config file (relative to root) | | -r, --root <path> | process.cwd() | Workspace root directory | | -m, --mode <mode> | Config value or warn | Override enforcement mode: error, warn, or off | | --format <type> | console | Output format: console or json | | -V, --version | | Print version | | -h, --help | | Print help |

Examples

# Basic check with defaults
stratify

# Explicit config and root
stratify --config stratify.config.json --root /path/to/monorepo

# Fail CI on violations
stratify --mode error

# Machine-readable output
stratify --format json

# Combine options
stratify -c stratify.config.json -r ../.. -m error --format console

Exit Codes

| Code | Meaning | | ---- | --------------------------------------------------------------- | | 0 | No violations, or mode is warn/off | | 1 | Violations found and mode is error, or a fatal error occurred |

Programmatic API

The library exposes a single function for custom tooling, editor integrations, or CI pipelines.

validateLayers(options?)

Validate monorepo packages against architectural layer rules.

import { validateLayers, StratifyError } from 'stratifyjs';

try {
    const result = await validateLayers({
        workspaceRoot: '/path/to/monorepo',
        configPath: 'stratify.config.json',
        mode: 'error', // optional override
    });

    console.log(`Checked ${result.totalPackages} packages, found ${result.violations.length} violations`);

    for (const v of result.violations) {
        console.log(v.detailedMessage);
    }
} catch (error) {
    if (error instanceof StratifyError) {
        console.error(error.type, error.message);
    }
}

Options

| Field | Type | Default | Description | | --------------- | ---------------- | ------------------------ | ---------------------------------------------- | | workspaceRoot | string | process.cwd() | Workspace root directory | | configPath | string | 'stratify.config.json' | Path to config file, relative to workspaceRoot | | config | StratifyConfig | — | Pre-built config (skips file loading). workspaces and enforcement are optional — defaults are applied when omitted. | | mode | string | From config | Override: 'error', 'warn', or 'off' |

Result

| Field | Type | Description | | --------------- | ------------- | ---------------------------------- | | violations | Violation[] | All violations found | | totalPackages | number | Number of discovered packages | | duration | number | Elapsed time in milliseconds |

Each Violation has a short message for programmatic use and a rich detailedMessage with actionable context for human-readable output.

Pre-built config

You can pass a config object directly instead of loading from a file:

import { validateLayers } from 'stratifyjs';

const result = await validateLayers({
    workspaceRoot: '/path/to/monorepo',
    config: {
        layers: {
            features: { allowedDependencies: ['core'] },
            core: { allowedDependencies: [] },
        },
        // enforcement and workspaces are optional — defaults applied automatically
    },
});

Error handling

Infrastructure failures (missing config, bad JSON, discovery errors) throw a StratifyError:

import { validateLayers, StratifyError } from 'stratifyjs';

try {
    const result = await validateLayers();
} catch (error) {
    if (error instanceof StratifyError) {
        // error.type: 'config-not-found' | 'config-parse-error' | 'glob-failed' | ...
        console.error(error.message);
    }
}

Config File Format

The config file (default: stratify.config.json) is a JSON object with three sections:

{
  "layers": { ... },
  "enforcement": { ... },
  "workspaces": { ... }
}

layers (required)

A map of layer names to their definitions. Each layer must specify which other layers it is allowed to depend on.

{
    "layers": {
        "adapters": {
            "description": "I/O and external integrations",
            "allowedDependencies": ["core", "types"]
        },
        "core": {
            "description": "Pure business logic",
            "allowedDependencies": ["types"]
        },
        "types": {
            "description": "Shared type definitions",
            "allowedDependencies": []
        }
    }
}

| Field | Type | Required | Description | | --------------------- | ---------- | -------- | -------------------------------------------------------------------- | | description | string | No | Human-readable description of the layer's purpose | | allowedDependencies | string[] | Yes | Layer names this layer may depend on. Use "*" to allow all layers. | | allowedPackages | string[] | No | Inline list of package names allowed to declare this layer. Mutually exclusive with allowedPackagesFile. | | allowedPackagesFile | string | No | Path to a JSON file (relative to workspace root) containing an array of allowed package names. Mutually exclusive with allowedPackages. |

enforcement (optional)

Controls how violations are reported.

{
    "enforcement": {
        "mode": "error"
    }
}

| Field | Type | Default | Description | | ------ | ---------------------------- | -------- | ------------------------------------------------------------------------- | | mode | "error" \| "warn" \| "off" | "warn" | error = non-zero exit on violations; warn = report only; off = skip |

workspaces (optional)

Controls which packages are discovered and how internal dependencies are detected.

{
    "workspaces": {
        "patterns": ["packages/**/*", "shared/**/*"],
        "protocols": ["workspace:", "link:"],
        "ignore": ["**/node_modules/**", "**/lib/**", "**/dist/**", "**/build/**"],
        "dependencyTypes": ["dependencies"]
    }
}

| Field | Type | Default | Description | | ------------------ | ---------- | ---------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | | patterns | string[] | ["packages/**/*"] | Glob patterns to locate workspace packages (each must contain a package.json) | | protocols | string[] | ["workspace:"] | Version-string prefixes that identify internal dependencies. Common values: "workspace:", "link:", "portal:", "file:" | | ignore | string[] | ["**/node_modules/**", "**/lib/**", "**/dist/**"] | Glob patterns to exclude from package discovery | | dependencyTypes | string[] | ["dependencies"] | Which package.json dependency fields to check. Valid values: "dependencies", "devDependencies", "peerDependencies" |

Layer Definition Reference

Package Assignment

Each workspace package declares its layer via the "layer" field in package.json:

{
    "name": "my-package",
    "version": "1.0.0",
    "layer": "core"
}

Packages missing the "layer" field produce a missing-layer violation.

Dependency Detection

By default, only workspace: protocol dependencies are checked. You can configure additional protocols (e.g. link:, portal:, file:) via the workspaces.protocols config field.

By default, only the dependencies field is scanned — this represents the true runtime/production boundary between packages. If you also want to enforce layer rules on build-time or test-time dependencies, add "devDependencies" and/or "peerDependencies" to workspaces.dependencyTypes:

{
    "workspaces": {
        "dependencyTypes": ["dependencies", "devDependencies"]
    }
}

| Value | Meaning | | -------------------- | ------------------------------------------------------------- | | "dependencies" | Production/runtime dependencies (default, always recommended) | | "devDependencies" | Build tools, test helpers, linters | | "peerDependencies" | Peer contracts provided by the consumer |

External (npm registry) dependencies are ignored — layers only govern internal monorepo boundaries.

Violation Types

| Type | Description | | -------------------- | ------------------------------------------------------------------------------ | | missing-layer | Package has no "layer" field in its package.json | | unknown-layer | Package declares a layer not defined in the config | | invalid-dependency | Package depends on another package whose layer is not in allowedDependencies | | unauthorized-layer-member | Package declares a layer that has an allowlist, but the package is not in it |

Layer Membership Control

You can restrict which packages are allowed to belong to a specific layer. This is useful for preventing growth of tech-debt layers, limiting entry points to approved app shells, or requiring approval before packages join privileged layers.

Inline allowlist — for layers with a small, stable set of members:

{
    "layers": {
        "entry": {
            "description": "App bootstraps — only approved entry points",
            "allowedDependencies": ["features", "core", "shared"],
            "allowedPackages": ["@myapp/web-main", "@myapp/web-admin"]
        }
    }
}

External JSON file — for layers with many members (keeps the config readable):

{
    "layers": {
        "legacy": {
            "description": "Existing packages not yet migrated",
            "allowedDependencies": ["*"],
            "allowedPackagesFile": "legacy-packages.json"
        }
    }
}

Where legacy-packages.json is a sorted JSON array checked into source control:

[
    "@myapp/old-auth",
    "@myapp/old-cart",
    "@myapp/old-checkout"
]

The two fields are mutually exclusive — specifying both on the same layer is a config validation error. Layers without either field remain unrestricted.

Wildcard Dependencies

Use "*" to allow a layer to depend on any other layer:

{
    "layers": {
        "app": {
            "description": "Application entry points — can use anything",
            "allowedDependencies": ["*"]
        }
    }
}

Full Config Example

{
    "layers": {
        "app": {
            "description": "Application entry points",
            "allowedDependencies": ["features", "core", "shared"]
        },
        "features": {
            "description": "Feature modules",
            "allowedDependencies": ["core", "shared"]
        },
        "core": {
            "description": "Core business logic",
            "allowedDependencies": ["shared"]
        },
        "shared": {
            "description": "Shared utilities and types",
            "allowedDependencies": []
        }
    },
    "enforcement": {
        "mode": "error"
    },
    "workspaces": {
        "patterns": ["packages/**/*", "libs/**/*"],
        "protocols": ["workspace:"],
        "ignore": ["**/node_modules/**", "**/lib/**", "**/dist/**"],
        "dependencyTypes": ["dependencies"]
    }
}

CI Integration

Add to your CI pipeline to enforce boundaries on every PR:

# GitHub Actions
- name: Enforce layers
  run: npx stratify --config stratify.config.json --mode error
# Azure Pipelines
- script: npx stratify --config stratify.config.json --mode error
  displayName: 'Enforce layer boundaries'

Development

# Install dependencies
yarn install

# Run tests
yarn test

# Run tests in watch mode
yarn test:watch

# Build (compile TypeScript → lib/)
yarn build

# Type-check without emitting
yarn typecheck

Documenting Changes

This project uses Beachball to manage versioning and changelogs. Every PR that affects the published package must include a change file. Run yarn change before submitting your PR and follow the prompts. CI will fail if the change file is missing.

PRs that only touch tests, documentation, or configuration files are excluded from this requirement.

License

MIT