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

@aurelienbbn/oxlint-config

v0.8.0

Published

Opinionated strict reusable oxlint config for TypeScript projects.

Readme

@aurelienbbn/oxlint-config

npm downloads CI license node

Strict oxlint for TypeScript: every stable category at error, type-aware, warnings denied, every rule conflict settled.

pnpm add -D @aurelienbbn/oxlint-config oxlint oxlint-tsgolint
# oxlint >=1.82.0 <2.0.0 · oxlint-tsgolint ^7.0.2001 · Node ^22.19.0 || ^24.11.0
// oxlint.config.ts
import { defineStrictOxlintConfig } from "@aurelienbbn/oxlint-config";
import { defineConfig } from "oxlint";

export default defineConfig(
  defineStrictOxlintConfig({
    ignorePatterns: ["dist/**"],
  }),
);

Returns a plain OxlintConfig: works with oxlint directly or as Vite+'s lint config, no Vite+ dependency.

1 preset, 5 opt-ins

| Export | Adds | | ------------------------------------------------------- | ---------------------------------------------------- | | strictOxlintConfig | the preset object | | defineStrictOxlintConfig(overrides?, options?) | preset + your overrides, cloned | | withTanstackQueryLayer(config?, options?) | 🔌 official @tanstack/eslint-plugin-query, 7 rules | | withEffectTsgoLayer(config, { preset }) | 🔌 official @effect/tsgo preset + 4 owner rules | | withImportGraphLayer(config?, { entrypoints? }) | 🔌 import/no-cycle + import/no-self-import | | layerDirectionOverride({ files, forbidden, message }) | 🔌 one overrides entry banning cross-layer imports | | nurseryCandidateRules | 🔌 4 nursery rules to trial |

Also: testFileGlobs, vagueTestTitlePattern, tanstackQueryRules, tanstackQueryPluginSpecifier, effectTsgoPluginName, effectTsgoOwnerRules, effectTsgoSettledRules, effectIdiomRules, importGraphRules, types OxlintConfig, ImportGraphLayerOptions.

What the preset sets

correctness  ██████  error      style       ██████  error
pedantic     ██████  error      suspicious  ██████  error
perf         ██████  error      nursery     ░░░░░░  off (pending upgrade review)
restriction  ██████  error

vitest never runs on source files, only on testFileGlobs: vitest/require-hook reports ordinary top-level calls.

| Key | Value | | -------------- | ---------------------------------------------------------------------------------------------------- | | plugins | eslint, typescript, unicorn, oxc, node, promise | | options | typeAware: true, typeCheck: true, denyWarnings: true, reportUnusedDisableDirectives: "error" | | env | builtin: true, node: true | | overrides[0] | vitest plugin on testFileGlobs = **/*.{test,spec}.{ts,tsx,mts,cts,js,jsx,mjs,cjs} |

plugins replaces oxlint's default set, so the four defaults (eslint, typescript, unicorn, oxc) are re-listed before node and promise. Other test layout: add an overrides entry with plugins: ["vitest"].

| Rule | Setting | Why | | --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | | typescript/ban-ts-comment | ts-ignore banned, ts-expect-error needs description | a suppression says why | | typescript/no-explicit-any, typescript/no-non-null-assertion | error | pinned by name | | unicorn/filename-case | kebabCase | one naming scheme | | eslint/no-underscore-dangle | allow _tag, _tree, __dirname | tagged unions, Node globals | | eslint/max-lines | 300, blank and comment lines skipped | measures code, not docs or formatter reflow | | eslint/sort-keys | off | key order carries meaning: schema fields, table columns, read-in-order config | | eslint/one-var | never | grouping makes later edits noisier | | oxc/no-async-await, oxc/no-optional-chaining, oxc/no-rest-spread-properties | off | ban modern syntax, not defects | | vitest/no-standalone-expect | off (test files) | | | typescript/no-unnecessary-condition | error by name (nursery off) | needs strictNullChecks; noUncheckedIndexedAccess keeps indexed access quiet | | typescript/switch-exhaustiveness-check | allowDefaultCaseForExhaustiveSwitch: false, requireDefaultForNonUnion: true | union switch: every member, no default; other switch: default required |

nurseryCandidateRules (not in the preset; spread into rules to trial): typescript/prefer-optional-chain, eslint/no-useless-assignment, eslint/no-unreachable-loop, promise/no-return-in-finally.

Every conflict is settled once

| ❌ Off | ✅ Kept | Why | | --------------------------------------------------------- | ---------------------------------------------------- | ----------------------------------------------------------------------------- | | eslint/no-ternary | unicorn/prefer-ternary, eslint/no-nested-ternary | no-let code needs expressions; nesting stays banned | | node/no-top-level-await | unicorn/prefer-top-level-await | app entry points; libraries re-enable the node rule | | vitest/no-importing-vitest-globals | vitest/prefer-importing-vitest-globals | imports stay visible to import-keyed rules | | vitest/prefer-to-be-truthy, vitest/prefer-to-be-falsy | vitest/prefer-strict-boolean-matchers | toBe(true) is stricter | | eslint/no-undefined | unicorn/no-null, unicorn/no-useless-undefined | banning both null and undefined leaves no way to express absence | | eslint/no-warning-comments | — | rejects tracked TODO(#123):; core/no-dead-comments reports untracked only | | eslint/max-lines-per-function | eslint/max-statements | counts physical lines, so a narrower print width fails it; statements don't | | eslint/default-case | typescript/switch-exhaustiveness-check | default hides new union members | | eslint/require-await | typescript/require-await | typed extension | | eslint/no-implied-eval | typescript/no-implied-eval | typed extension | | eslint/prefer-promise-reject-errors | typescript/prefer-promise-reject-errors | typed extension | | eslint/no-throw-literal | typescript/only-throw-error | typed extension | | unicorn/prefer-includes | typescript/prefer-includes | typed duplicate | | unicorn/prefer-string-starts-ends-with | typescript/prefer-string-starts-ends-with | typed duplicate | | unicorn/prefer-array-find | typescript/prefer-find | typed duplicate | | eslint/no-magic-numbers (test files only) | vitest/prefer-expect-assertions | expected values are the oracle and stay literal; expect.assertions(n) too |

[!WARNING] The typed side wins only because typeAware: true. Turn it off: re-enable the untyped rules.

Vague test titles fail

it("works", …)                            // ❌ bare "works" / "should work", any case
it("adds items correctly", …)             // ❌ correctly / properly / as expected
it("rejects improperly signed tokens", …) // ✅

Pinned so an upstream category move can't drop them:

| Rule | Owns | | --------------------------------- | --------------------------------------------------------------------------------------------------- | | vitest/require-to-throw-message | bare toThrow() / rejects.toThrow() (core/no-weak-test-assertions skips this) | | vitest/prefer-called-with | bare toHaveBeenCalled(): makes existing interaction assertions exact, doesn't create them | | vitest/valid-title | mustNotMatch: vagueTestTitlePattern | | vitest/require-hook | top-level setup; allowedFunctionCalls admits the four Harness *Conformance({ root }) registrars |

Whether to assert a call at all: agentlint core/test-behavior-coverage. toHaveBeenCalledWith(expect.anything()): core/no-weak-test-assertions.

| mustNotMatch form (oxlint 1.82.0) | Fires? | | -------------------------------------- | ---------------- | | plain string | ✅ | | { it: "<regex>" } | ✅ | | [regex, message] tuple | ✅ | | any form + non-empty disallowedWords | ❌ ignored | | pattern with \b or \W | ❌ never matches |

So filler words share one pattern with [^a-z] word edges. Overriding: keep one mustNotMatch, leave disallowedWords unset. src/index.test.ts runs the installed oxlint against these cases.

Opt-in layers

import {
  defineStrictOxlintConfig,
  layerDirectionOverride,
  withImportGraphLayer,
  withTanstackQueryLayer,
} from "@aurelienbbn/oxlint-config";
import { defineConfig } from "oxlint";

export default defineConfig(
  withImportGraphLayer(
    withTanstackQueryLayer(
      defineStrictOxlintConfig({
        overrides: [
          layerDirectionOverride({
            files: ["**/domain/**"],
            forbidden: ["**/infra/**", "express"],
            message: "Domain never imports infrastructure.",
          }),
        ],
      }),
    ),
  ),
);

Rules already set on config win over any layer.

[!WARNING] oxlint replaces rule options, it doesn't merge them. Two overrides matching one file: the later eslint/no-restricted-imports wins, and either replaces a root-level one. One entry per layer glob, listing everything it must not reach.

pnpm add -D @tanstack/eslint-plugin-query   # not a dependency of this package

Appends the plugin to jsPlugins, sets tanstackQueryRules to error.

| @tanstack/query/… | Reported in probe¹ | | ------------------------------- | ------------------------------------------------------------------- | | exhaustive-deps | ✅ | | mutation-property-order | ✅ | | no-rest-destructuring | ✅ (needs type info under oxlint? unconfirmed) | | prefer-query-options | ✅ | | stable-query-client | ✅ | | infinite-query-property-order | ⚠️ not exercised | | no-unstable-deps | ⚠️ not exercised | | no-void-query-fn | ❌ left out: needs the TS checker, not exposed to oxlint JS plugins |

¹ @tanstack/eslint-plugin-query 5.103.1 under oxlint 1.82.0, one-off. Wiring, not verified coverage: no repo fixture installs the official plugin; oxlint JS plugins are alpha. Re-check after upgrading either.

options.companionPlugins: { specifier, rules }[]. Specifier appended to jsPlugins after the official plugin; rules merge after the official rules.

pnpm add -D @effect/[email protected] [email protected] [email protected] [email protected]   # not dependencies of this package
// package.json: registers the native `effecttsgo` plugin in oxlint-tsgolint
{ "scripts": { "prepare": "effect-tsgo patch --oxlint" } }
// oxlint.config.ts
import { recommended } from "@effect/tsgo/oxlint-presets";
import { defineStrictOxlintConfig, withEffectTsgoLayer } from "@aurelienbbn/oxlint-config";
import { defineConfig } from "oxlint";

export default defineConfig(withEffectTsgoLayer(defineStrictOxlintConfig(), { preset: recommended }));

Merges the preset's rules, adds effecttsgo to plugins, then effectTsgoOwnerRules, effectTsgoSettledRules and effectIdiomRules. Config rules and options win. The preset reports at warn; the strict preset's denyWarnings makes those blocking. Editor LSP (@effect/language-service in tsconfig.json plugins): set "diagnostics": false, or every finding shows twice.

[!WARNING] Version lock. @effect/tsgo 0.45.0 supports oxlint 1.81.0 / 1.82.0, oxlint-tsgolint 7.0.2001, TypeScript 7.0.2, and effect-tsgo patch refuses anything else. That is the compatibility baseline row; the current row (oxlint 1.83.0, oxlint-tsgolint 7.0.2002) is outside it. Pin the baseline row until a tsgo release widens its matrix.

effectTsgoOwnerRules: off in recommended, on here because they own checks @aurelienbbn/oxlint-plugin-effect removed.

| effecttsgo/… | Owns the removed rule | | ------------------------------ | ------------------------------------------------------------------------------------------ | | any-unknown-in-error-context | no-unsafe-error-channel | | unsafe-effect-type-assertion | no-effect-type-assertion | | deterministic-keys | matching-identifier (with class-self-mismatch, already in recommended) | | strict-effect-provide | ⚠️ partial: Effect.provide outside entry points, not the removed Layer.provide nesting |

Every overlap with @aurelienbbn/oxlint-plugin-effect, settled once:

| effecttsgo/… | Harness rule | Setting | Why | | -------------------------------------------------------- | -------------------------------------------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | catch-die-to-or-die | no-effect-ordie | off | its rewrite is Effect.orDie, which the Harness rule bans; the dying catch already reports | | redundant-or-die | no-effect-ordie | off | hoists an orDie the Harness rule already reports | | catch-to-or-else-succeed | no-swallowed-failure | off | its rewrite Effect.orElseSucceed(() => placeholder) is reported too | | catch-to-ignore | no-swallowed-failure, no-catch-all-cause | off | suggests bare Effect.ignore (needs { log }) or Effect.ignoreCause (banned) | | global-fetch, global-fetch-in-effect | require-abort-signal | both on, tsgo first | replace fetch with HttpClient (interruption built in) and both go quiet; forwarding signal alone leaves tsgo firing. Keeping fetch: turn the tsgo pair off | | global-error-in-effect-failure, extends-native-error | require-tagged-effect-fail | both on | same fix (a tagged error); Effect.fail(new Error(…)) reports twice. Literals and strings: Harness only | | try-catch-in-effect-gen, global-timers-in-effect | no-unsafe-effect-body | tsgo owns | the Harness rule keeps only its throw check |

Not running @aurelienbbn/oxlint-plugin-effect? The four off rules lose their reason: set them back in rules.

effectIdiomRules: strict-preset rules that contradict @effect/tsgo or an idiom Effect's API requires. The Effect side wins; each rule still reports outside that idiom.

| Strict rule | Setting | Why | | ------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | typescript/promise-function-async | off | its autofix adds async, which effecttsgo/async-function reports | | eslint/new-cap | capIsNew: false | Effect constructors are PascalCase functions: Schema.Struct(…) | | eslint/func-names | generators: "never" | Effect.gen(function* () { … }) takes an anonymous generator | | node/no-sync | ignores: ["runSync"] | Effect.runSync runs an Effect at an edge; fs.*Sync still reports | | eslint/max-classes-per-file | off | tagged errors, Schema.Class models and services are classes; a domain module holds several | | eslint/no-redeclare | off | a Schema and its type share a name (const User + type User); TypeScript rejects real redeclarations | | unicorn/throw-new-error | off | reports the Schema.TaggedError<Self>()(…) factory; its autofix emits code that does not compile | | typescript/prefer-readonly-parameter-types | methods readonly, inferred types ignored, Effect's immutable types allowed | the deep check rejects every Effect, Option, DateTime.Utc, Redacted, SQL Fragment and Migrator.Loader parameter; mutable arrays and objects still report | | unicorn/no-array-callback-reference, unicorn/no-array-for-each, unicorn/no-array-method-this-argument, unicorn/no-array-sort | off | with namespace imports they take Effect modules for arrays (Option.some(value), Effect.forEach, Arr.filter(xs, f), Arr.sort(order)); the effect/no-array-* rules of @aurelienbbn/oxlint-plugin-effect report the same misuse on real arrays. Not running that plugin: set the four back in rules |

Credit: preset shape, the effecttsgo plugin name, and every diagnostic name come from Effect-TS/tsgo (MIT).

One overrides entry using eslint/no-restricted-imports patterns. No default layer map: no layers, no rule.

| Field | Takes | | ----------- | -------------------------------------------------------------------------------------------------- | | files | globs of the protected layer | | forbidden | gitignore-style globs for paths, bare names for packages; relative and package imports both report | | message | the fix direction: the rule can't know which of the three moves is right |

Cycles creep in through convenience imports between siblings, then surface as undefined at module init and untestable modules.

import rules in oxlint 1.82.0   33
  error  ██                      2   import/no-cycle, import/no-self-import
  off    ███████████████████████ 31  pinned off in importGraphRules

Loading the import plugin also builds the module graph, which lets oxc/no-barrel-file count what a barrel loads: a package's src/index.ts over Effect modules loads hundreds. Pass its entry points as entrypoints (withImportGraphLayer(config, { entrypoints: ["packages/*/src/index.ts"] })) to exempt them.

Adding "import" to plugins alone switches on all 33 (whole categories are enabled), including contradicting pairs: import/no-default-export vs import/prefer-default-export, import/no-named-export, import/group-exports. Want another: set it in rules.

| Behavior | Detail | | ---------------- | ------------------------------------------------------------- | | type-only cycles | ignored (ignoreTypes): gone at runtime | | cost | resolves files project-wide, the plugin's most expensive rule | | path aliases | resolve only when oxlint finds the declaring tsconfig |

After every oxlint upgrade, a new import/* rule missing from importGraphRules turns on via its category:

  • [ ] pnpm exec oxlint --rules (or --rules -f json)
  • [ ] compare import rows with importGraphRules
  • [ ] pin each new rule off

Here src/index.test.ts fails until the list matches the installed oxlint. Consumers on another oxlint check by hand.

Contract & migration

| Topic | Contract | | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------- | | target | Node/TypeScript. Browser: override env.node, enable the browser env | | nursery | off pending upgrade review, except typescript/no-unnecessary-condition | | Jest-namespace disables | ⚠️ removed | | lists | plugins, jsPlugins, ignorePatterns, overrides merge deduplicated; { replaceLists: true } replaces any list you supply | | objects | categories, env, options, rules, settings merge key by key | | isolation | nested rule options are independent clones | | types | from oxlint; Vite+ re-exports the same contract | | compatibility | matrix tests the packed config on baseline and current runners, type-aware included |