@noctcore/eslint-plugin-contracts
IO boundaries (checked fetch responses, parsed boundary data), error taxonomy, schema and wire naming, env access, money precision and translation keys.
What it solves
Section titled “What it solves”The places a system talks to the outside world are where types stop being true: a fetch whose body is parsed without checking .ok, a JSON.parse(...) as User, an error re-thrown without its cause, process.env.FOO read in forty places, money typed as a float. These rules hold the contract layer to conventions that make those boundaries explicit and checkable.
What it will yell at you about
Section titled “What it will yell at you about”It will yell at you about naming and boundary discipline: zod schemas must be PascalCaseSchema with a paired inferred type, wire messages need a literal type discriminant, process.env goes through one config module, thrown errors come from an approved taxonomy. Several conventions are opinionated, and the naming rules assume zod. It is a bad fit if you do not use zod, or if your error handling is intentionally untyped. Four rules ship off: three are inert until you give them a sink list, schema file or translation catalogs, and require-schema-parse-at-boundary is a conservative syntactic slice of a concern a type-aware setup does better.
Install and configure
Section titled “Install and configure”npm install --save-dev @noctcore/eslint-plugin-contracts @typescript-eslint/parser# or: bun add -D / pnpm add -D @noctcore/eslint-plugin-contracts @typescript-eslint/parser// eslint.config.jsimport tsParser from '@typescript-eslint/parser';import contracts from '@noctcore/eslint-plugin-contracts';
export default [ { ...contracts.configs.recommended, files: ['**/*.{ts,tsx}'], languageOptions: { parser: tsParser }, },];
// Rules outside the preset are enabled one by one, e.g.// { rules: { 'noctcore-contracts/require-schema-parse-at-boundary': 'error' } }The recommended preset enables 9 of 13 rules.
Each rule links to its page, with the options it takes and Incorrect and Correct examples. A good first
read is fetch-must-check-ok.
| Rule | What it reports | Preset | Fix | Types |
|---|---|---|---|---|
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. | off | ||
fetch-must-check-ok | Require a fetch response to be checked with .ok or a status comparison before .json() parses its body. | error | ||
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. | error | ||
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. | error | ||
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. | error | ||
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. | error | autofix | |
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. | off | ||
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. | off | ||
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, ...). | error | ||
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. | error | ||
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. | off | optional | |
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). | error | autofix | |
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>). | error |
Preset: severity in configs.recommended; off means the preset registers the rule switched off, not listed means it leaves the rule out; both are opt-in, so you turn the rule on yourself. Fix: whether the rule ships an autofix or an editor suggestion. Types: whether the rule needs a type-checked program (parserOptions.projectService).