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

@noctcore/eslint-plugin-contracts

v0.7.2

Published

ESLint rules for IO contracts and config: checked fetch responses, parsed boundary data, error cause and taxonomy, zod schema and wire naming, env access, decimal money and translation keys.

Readme

@noctcore/eslint-plugin-contracts

Rules for shared contract conventions: zod schema naming, wire-message discriminants, error stringification, direct process.env access and money precision, where drift between producer and consumer breaks at runtime.

Docs: noctcore.github.io/eslint-plugins/packages/contracts

Not a good fit for a codebase that does not use zod, or whose error handling is intentionally untyped.

Requirements

  • ESLint 9 or newer, flat config (eslint.config.js) only.
  • configs.recommended registers the plugin and sets rule severities, nothing else. It sets no files and no parser, so it applies to whatever files the rest of your config lints. To lint TypeScript, add a files pattern and @typescript-eslint/parser, as in the quick start.

Install

bun add -D @noctcore/eslint-plugin-contracts @typescript-eslint/parser   # or npm i -D / pnpm add -D

Quick start

// eslint.config.js
import tsParser from '@typescript-eslint/parser';
import contracts from '@noctcore/eslint-plugin-contracts';

export default [
  {
    ...contracts.configs.recommended,
    files: ['**/*.{ts,tsx}'],
    languageOptions: { parser: tsParser },
  },
  // Optional: tune a preset rule's options.
  {
    files: ['**/*.{ts,tsx}'],
    rules: {
      'noctcore-contracts/zod-schema-naming': ['error', { roleSuffixes: ['Event', 'Command', 'Query'] }],
      'noctcore-contracts/no-direct-process-env': ['error', { configModule: '@acme/config' }],
    },
  },
];

Opt-in rules

Four rules ship off: require-registered-keys, env-var-schema-parity and translation-key-exists do nothing until their sinks / schema / catalogs options are set, and require-schema-parse-at-boundary is a conservative syntactic slice of a type-aware concern.

// eslint.config.js
export default [
  // ...the quick start's entries, then:
  {
    files: ['**/*.{ts,tsx}'],
    languageOptions: { parser: tsParser },
    rules: {
      'noctcore-contracts/require-registered-keys': ['error', {
        sinks: [{ callee: 'localStorage.getItem', argIndex: 0 }],
      }],
      'noctcore-contracts/env-var-schema-parity': ['error', { schema: '.env.example' }],
      'noctcore-contracts/translation-key-exists': ['error', {
        catalogs: [{ file: 'locales/en/common.json', namespace: 'common' }],
      }],
      'noctcore-contracts/require-schema-parse-at-boundary': 'error',
    },
  },
];

Rules

✅ on in recommended (error) · 🔘 opt-in: not on in recommended, enable it yourself · ⚙️ needs options · 🔧 --fix · 💡 suggestions · 💭 needs type info · ❌ deprecated

| Rule | Description | Preset | ⚙️ | 🔧 | 💡 | 💭 | | --- | --- | :-: | :-: | :-: | :-: | :-: | | env-var-schema-parity | Require every process.env.FOO / import.meta.env.FOO key to be declared in a schema file (.env.example or a zod-env module), so config access and config declaration cannot drift apart. | 🔘 | ⚙️ | | | | | fetch-must-check-ok | Require a fetch response to be checked with .ok or a status comparison before .json() parses its body. | ✅ | | | | | | money-must-be-decimal | Disallow monetary values typed as the JS primitive number. Money-named fields explicitly typed : number lose precision to float rounding; use a Decimal money type instead. | ✅ | | | | | | no-direct-process-env | Disallow direct process.env access. Force every consumer through a typed, validated config accessor so a missing variable fails at boot, not at use. | ✅ | | | | | | no-error-stringify | Disallow stringifying an error with bare ${error} interpolation, error.toString(), or error + "". These drop the cause chain. Use error instanceof Error ? error.message : String(error) instead. | ✅ | | | | | | require-error-cause | Require re-thrown errors inside a catch to forward the caught error as { cause }. A throw new SomeError(...) that omits the cause severs the chain to the original failure. | ✅ | | 🔧 | | | | require-registered-keys | Require the key/name argument of configured sink APIs (storage, event channels, cache keys) to be an imported constant from a registry module, not a raw string literal. | 🔘 | ⚙️ | | | | | require-schema-parse-at-boundary | Disallow asserting external boundary data with as T instead of parsing it at runtime. Flags casts of JSON.parse, res.json(), web storage, URL search params, message-event data and LLM tool input, directly or through a const; use a zod/valibot parse. | 🔘 | | | | | | restrict-throw-to-taxonomy | Restrict throw to an approved error taxonomy. Flags throwing a non-allowlisted error class and throwing a non-Error value (string, object, number, ...). | ✅ | | | | | | schema-enum-field-consistency | Disallow a zod field that is an enum in one object schema of a module from being z.string() in another, which widens the wire type every consumer then narrows by hand. | ✅ | | | | | | translation-key-exists | Require every static i18next / react-i18next translation key (t(...), i18n.t(...), <Trans i18nKey>) to exist in the catalog of the namespace in scope. | 🔘 | ⚙️ | | | | | wire-message-naming | A message-schema const ending in a role suffix (default Event/Command/Query) whose zod object declares type: z.literal(...) must set that literal to kebab-case(const name minus its role suffix). | ✅ | | 🔧 | | | | zod-schema-naming | Every exported zod schema is a PascalCase const suffixed Schema, paired with a same-named inferred type (export type Foo = z.infer<typeof FooSchema>). | ✅ | | | | |

Severity policy

Every rule is error or off, never warn: a warning is a rule nobody obeys. See the severity policy.