{"_id":"@boring-stack-pkg/eslint-plugin-elysia","_rev":"3-faf4ea5ccc1e06649b223321d91246e1","name":"@boring-stack-pkg/eslint-plugin-elysia","dist-tags":{"latest":"0.2.0"},"versions":{"0.1.1":{"name":"@boring-stack-pkg/eslint-plugin-elysia","version":"0.1.1","keywords":["eslint","eslintplugin","typescript","elysia","elysiajs","bun"],"author":"","license":"MIT","_id":"@boring-stack-pkg/eslint-plugin-elysia@0.1.1","maintainers":[{"name":"agjs","email":"hi@aleksandar.xyz"}],"homepage":"https://github.com/AI-Starter-Templates/eslint-plugins#readme","bugs":{"url":"https://github.com/AI-Starter-Templates/eslint-plugins/issues"},"dist":{"shasum":"421ae7622e2446d1e556a092efa7f0b6eced8d8e","tarball":"https://registry.npmjs.org/@boring-stack-pkg/eslint-plugin-elysia/-/eslint-plugin-elysia-0.1.1.tgz","fileCount":44,"integrity":"sha512-P6A8s5HGN7E5zHTmUIgATk8gY5N4HZFwktO52tvGHYBR7S6HO+83tVAqCXkQu/AwEiE3KbtPCnyI+5D77kYAyw==","signatures":[{"sig":"MEUCIFabq2fG0FgbgIVG01av6zDNGDk5cDv1no75m8GRU3d0AiEA5R8HyqG+2QvL9M0ntdP1VcWq6CcezYiR1xR4z+eHlek=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@boring-stack-pkg%2feslint-plugin-elysia@0.1.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":162538},"main":"./dist/index.cjs","type":"module","_from":"file:boring-stack-pkg-eslint-plugin-elysia-0.1.1.tgz","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":"^20.0.0 || ^22.0.0 || >=24.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./package.json":"./package.json"},"scripts":{"test":"vitest run","build":"tsup src/index.ts --format esm,cjs --dts --clean","typecheck":"tsc --noEmit","test:watch":"vitest"},"_npmUser":{"name":"agjs","email":"hi@aleksandar.xyz"},"_resolved":"/tmp/4ce1f88f86188a33092ac0cdbe9bda1a/boring-stack-pkg-eslint-plugin-elysia-0.1.1.tgz","_integrity":"sha512-P6A8s5HGN7E5zHTmUIgATk8gY5N4HZFwktO52tvGHYBR7S6HO+83tVAqCXkQu/AwEiE3KbtPCnyI+5D77kYAyw==","repository":{"url":"git+https://github.com/AI-Starter-Templates/eslint-plugins.git","type":"git","directory":"eslint-plugin-elysia"},"_npmVersion":"10.9.7","description":"ESLint plugin enforcing architectural, type-safety, lifecycle, and performance patterns for Elysia.js applications.","directories":{},"sideEffects":false,"_nodeVersion":"22.22.2","dependencies":{"@typescript-eslint/utils":"8.0.0"},"publishConfig":{"access":"public","provenance":true},"_hasShrinkwrap":false,"devDependencies":{"tsup":"8.0.0","eslint":"9.0.0","vitest":"2.0.0","@eslint/js":"9.0.0","typescript":"6.0.3","@types/node":"22.0.0","@typescript-eslint/parser":"8.0.0","@typescript-eslint/rule-tester":"8.0.0"},"peerDependencies":{"eslint":"8.57.0 || ^9.0.0","typescript":">=5.0.0","@typescript-eslint/parser":">=8.0.0"},"_npmOperationalInternal":{"tmp":"tmp/eslint-plugin-elysia_0.1.1_1779219581868_0.7376517536143892","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"@boring-stack-pkg/eslint-plugin-elysia","version":"0.1.2","keywords":["eslint","eslintplugin","typescript","elysia","elysiajs","bun"],"author":"","license":"MIT","_id":"@boring-stack-pkg/eslint-plugin-elysia@0.1.2","maintainers":[{"name":"agjs","email":"hi@aleksandar.xyz"}],"homepage":"https://github.com/boringstack-xyz/eslint-plugins#readme","bugs":{"url":"https://github.com/boringstack-xyz/eslint-plugins/issues"},"dist":{"shasum":"5464ef734ccd91d908709414e51c66acaec629a7","tarball":"https://registry.npmjs.org/@boring-stack-pkg/eslint-plugin-elysia/-/eslint-plugin-elysia-0.1.2.tgz","fileCount":44,"integrity":"sha512-wbPoYC1E5gGnNRKplE6totDtNJxUElaDfCpfmS7ZHhqAPlDohKWHKmrnC2u2L7zgo1ILXPwrD+V5ss8uLc6poQ==","signatures":[{"sig":"MEYCIQDi/fLKhTuFBq8Dhuw5WTBRn2r6RBaYmLS+wDh9b832XQIhAMkq48U4XMJ4uaZpnl5CP39YfWe6ab3x4p3imb8/pnzc","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":163229},"main":"./dist/index.cjs","type":"module","_from":"file:boring-stack-pkg-eslint-plugin-elysia-0.1.2.tgz","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":"^20.0.0 || ^22.0.0 || >=24.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./package.json":"./package.json"},"scripts":{"test":"vitest run","build":"tsup src/index.ts --format esm,cjs --dts --clean","typecheck":"tsc --noEmit","test:watch":"vitest"},"_npmUser":{"name":"agjs","email":"hi@aleksandar.xyz"},"_resolved":"/tmp/279904b6af8e3772d7ed82e4ca6b76ab/boring-stack-pkg-eslint-plugin-elysia-0.1.2.tgz","_integrity":"sha512-wbPoYC1E5gGnNRKplE6totDtNJxUElaDfCpfmS7ZHhqAPlDohKWHKmrnC2u2L7zgo1ILXPwrD+V5ss8uLc6poQ==","repository":{"url":"git+https://github.com/boringstack-xyz/eslint-plugins.git","type":"git","directory":"eslint-plugin-elysia"},"_npmVersion":"11.15.0","description":"ESLint plugin enforcing architectural, type-safety, lifecycle, and performance patterns for Elysia.js applications.","directories":{},"sideEffects":false,"_nodeVersion":"22.22.3","dependencies":{"@typescript-eslint/utils":"8.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"8.0.0","eslint":"9.0.0","vitest":"2.0.0","@eslint/js":"9.0.0","typescript":"6.0.3","@types/node":"22.0.0","@typescript-eslint/parser":"8.0.0","@typescript-eslint/rule-tester":"8.0.0"},"peerDependencies":{"eslint":"8.57.0 || ^9.0.0","typescript":">=5.0.0","@typescript-eslint/parser":">=8.0.0"},"_npmOperationalInternal":{"tmp":"tmp/eslint-plugin-elysia_0.1.2_1779695635357_0.8958041198346189","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"_id":"@boring-stack-pkg/eslint-plugin-elysia@0.2.0","bugs":{"url":"https://github.com/boringstack-xyz/eslint-plugins/issues"},"dist":{"shasum":"3a9749bd2d38d44512633e0a38ccf8232ed7cd4d","tarball":"https://registry.npmjs.org/@boring-stack-pkg/eslint-plugin-elysia/-/eslint-plugin-elysia-0.2.0.tgz","fileCount":45,"integrity":"sha512-71JxEqwMlgJ9m5zbbP3a0MgTGWw87Pu/xw/rteOyoWKJ7HPd9Yy+CwD0qY766IuvajBvXGsdgqv0s0dkuN34tQ==","signatures":[{"sig":"MEUCIQCKXKtQ0DMu74+0FYC3FmipIWTJvVPJHNChjHk4N7wtlQIgVFZlgCSEyMb/0Y95n7pXb1kg1Mf06MGBnj8aw7V6RWY=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQCFrlkcY42tRY+UnDktpK2sbuh9XLaESgHy8EIsvws6OQIhAPCOSx2u82oOTkSTq3bB8iH8Y2LO2HkAY3FExklB0FGN"}],"unpackedSize":177064},"main":"./dist/index.cjs","name":"@boring-stack-pkg/eslint-plugin-elysia","type":"module","_from":"file:boring-stack-pkg-eslint-plugin-elysia-0.2.0.tgz","types":"./dist/index.d.ts","author":"","module":"./dist/index.js","engines":{"node":"^20.0.0 || ^22.0.0 || >=24.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./package.json":"./package.json"},"license":"MIT","scripts":{"test":"vitest run","build":"tsup src/index.ts --format esm,cjs --dts --clean","typecheck":"tsc --noEmit","test:watch":"vitest"},"version":"0.2.0","_npmUser":{"name":"agjs","email":"hi@aleksandar.xyz"},"homepage":"https://github.com/boringstack-xyz/eslint-plugins#readme","keywords":["eslint","eslintplugin","typescript","elysia","elysiajs","bun"],"_resolved":"/tmp/9578a0f5fabffa1b2694f04f2a244188/boring-stack-pkg-eslint-plugin-elysia-0.2.0.tgz","_integrity":"sha512-71JxEqwMlgJ9m5zbbP3a0MgTGWw87Pu/xw/rteOyoWKJ7HPd9Yy+CwD0qY766IuvajBvXGsdgqv0s0dkuN34tQ==","repository":{"url":"git+https://github.com/boringstack-xyz/eslint-plugins.git","type":"git","directory":"eslint-plugin-elysia"},"_npmVersion":"11.19.1","description":"ESLint plugin enforcing architectural, type-safety, lifecycle, and performance patterns for Elysia.js applications.","directories":{},"maintainers":[{"name":"agjs","email":"hi@aleksandar.xyz"}],"sideEffects":false,"_nodeVersion":"22.23.2","dependencies":{"@typescript-eslint/utils":"8.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"8.0.0","eslint":"9.0.0","vitest":"2.0.0","@eslint/js":"9.0.0","typescript":"6.0.3","@types/node":"22.0.0","@typescript-eslint/parser":"8.0.0","@typescript-eslint/rule-tester":"8.0.0"},"peerDependencies":{"eslint":"8.57.0 || ^9.0.0","typescript":">=5.0.0","@typescript-eslint/parser":">=8.0.0"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/eslint-plugin-elysia_0.2.0_1789412342693_0.8699543349214078"}}},"time":{"created":"2026-05-19T19:39:41.752Z","modified":"2026-09-14T18:59:03.011Z","0.1.1":"2026-05-19T19:39:42.026Z","0.1.2":"2026-05-25T07:53:55.506Z","0.2.0":"2026-09-14T18:59:02.797Z"},"bugs":{"url":"https://github.com/boringstack-xyz/eslint-plugins/issues"},"license":"MIT","homepage":"https://github.com/boringstack-xyz/eslint-plugins#readme","keywords":["eslint","eslintplugin","typescript","elysia","elysiajs","bun"],"repository":{"url":"git+https://github.com/boringstack-xyz/eslint-plugins.git","type":"git","directory":"eslint-plugin-elysia"},"description":"ESLint plugin enforcing architectural, type-safety, lifecycle, and performance patterns for Elysia.js applications.","maintainers":[{"name":"agjs","email":"hi@aleksandar.xyz"}],"readme":"# eslint-plugin-elysia\n\n[![npm](https://img.shields.io/npm/v/@boring-stack-pkg/eslint-plugin-elysia?logo=npm)](https://www.npmjs.com/package/@boring-stack-pkg/eslint-plugin-elysia) [![source](https://img.shields.io/badge/source-github-blue?logo=github)](https://github.com/boringstack-xyz/eslint-plugins/tree/main/eslint-plugin-elysia)\n\nESLint plugin enforcing architectural, type-safety, lifecycle, and performance patterns in [Elysia.js](https://elysiajs.com/) applications.\n\n## Why\n\nElysia is a fluent, chain-based framework whose correctness depends on **registration order**, **plugin naming**, **schema declaration**, and **idiomatic context destructuring**. Most of those constraints aren't expressible in the type system — a missing schema, an unnamed plugin, a hook registered after a route, or a `new Response()` returned where Elysia would have serialized natively all type-check fine and silently misbehave at runtime.\n\nThis plugin pins those invariants down at lint time.\n\n## Install\n\n```sh\npnpm add -D @boring-stack-pkg/eslint-plugin-elysia @typescript-eslint/parser\n```\n\n## Usage (flat config)\n\n```js\n// eslint.config.mjs\nimport tsParser from \"@typescript-eslint/parser\";\nimport elysia from \"@boring-stack-pkg/eslint-plugin-elysia\";\n\nexport default [\n  {\n    files: [\"**/*.ts\"],\n    languageOptions: {\n      parser: tsParser,\n      parserOptions: { ecmaVersion: \"latest\", sourceType: \"module\" },\n    },\n    plugins: { elysia },\n    rules: elysia.configs.recommended.rules,\n  },\n];\n```\n\nThe recommended preset enables all twelve rules at `\"error\"`. Override individual rules as needed:\n\n```js\nrules: {\n  ...elysia.configs.recommended.rules,\n  \"elysia/route-requires-tag\": \"warn\",\n  \"elysia/prefer-static-services\": \"off\",\n  \"elysia/no-direct-error-throw\": [\n    \"error\",\n    { factoryName: \"HttpErrors\", factoryMethod: \"make\" }\n  ]\n}\n```\n\n## Rules\n\n| Rule                                                                         | Category         | Description                                                                                                  |\n| ---------------------------------------------------------------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------ |\n| [`route-requires-schema`](docs/rules/route-requires-schema.md)               | Schema           | Every route must declare at least one of `body`/`query`/`params`/`response`/`headers`/`cookie`.              |\n| [`route-requires-tag`](docs/rules/route-requires-tag.md)                     | Convention       | Every route must declare `detail.tags` for Swagger grouping.                                                 |\n| [`no-direct-error-throw`](docs/rules/no-direct-error-throw.md)               | Safety (fixable) | Disallow `throw new Error(...)`; use a typed error factory.                                                  |\n| [`portable-schema-types`](docs/rules/portable-schema-types.md)               | Safety           | In `*.schemas.ts`, forbid `t.Integer()`, `t.Tuple()` and `t.Union(x.map(...))`, which the OpenAPI client cannot carry. |\n| [`consistent-status-via-set`](docs/rules/consistent-status-via-set.md)       | Convention       | Inside route handlers, set status via `set.status = N`, not `new Response(body, { status })`.                |\n| [`prefer-destructured-context`](docs/rules/prefer-destructured-context.md)   | Performance      | Don't pass the full Elysia `Context` to controllers/services — destructure at the boundary.                  |\n| [`require-plugin-name`](docs/rules/require-plugin-name.md)                   | Lifecycle        | Exported `new Elysia(...)` instances must declare `{ name: \"...\" }` for runtime deduplication.               |\n| [`no-separate-model-interfaces`](docs/rules/no-separate-model-interfaces.md) | Convention       | Disallow TS interfaces that duplicate a runtime schema's shape; use `typeof Schema.static` (or equivalent).  |\n| [`prefer-static-services`](docs/rules/prefer-static-services.md)             | Performance      | Don't `new Service()` inside route handlers when the class is stateless.                                     |\n| [`require-hooks-before-routes`](docs/rules/require-hooks-before-routes.md)   | Lifecycle        | Global hooks must register before any route on the same instance — Elysia's waterfall is order-sensitive.    |\n| [`prefer-throw-status`](docs/rules/prefer-throw-status.md)                   | Convention       | Inside route handlers, prefer `throw status(...)` over try/catch building manual responses.                  |\n| [`prefer-direct-return`](docs/rules/prefer-direct-return.md)                 | Performance      | Return values directly; let Elysia serialize. Reserve `new Response(...)` for streams and custom headers.    |\n| [`no-decorate-state-collision`](docs/rules/no-decorate-state-collision.md)   | Safety           | Disallow duplicate keys across `.decorate()` / `.state()` / `.derive()` / `.resolve()` on a single instance. |\n\n### route-requires-schema\n\n```ts\n// ❌\nnew Elysia().post(\"/users\", create);\n\n// ✅\nnew Elysia().post(\"/users\", create, {\n  body: t.Object({ email: t.String() }),\n  detail: { tags: [\"Users\"] },\n});\n```\n\n### require-plugin-name\n\n```ts\n// ❌\nexport const auth = new Elysia();\n\n// ✅\nexport const auth = new Elysia({ name: \"Auth.Plugin\" });\n```\n\n### require-hooks-before-routes\n\n```ts\n// ❌  onError will not fire for /health\nnew Elysia().get(\"/health\", () => \"ok\").onError(handleError);\n\n// ✅\nnew Elysia().onError(handleError).get(\"/health\", () => \"ok\");\n```\n\n### no-decorate-state-collision\n\n```ts\n// ❌  silent overwrite — second decorate(\"db\", ...) wins\nnew Elysia({ name: \"db\" }).decorate(\"db\", a).decorate(\"db\", b);\n\n// ✅\nnew Elysia({ name: \"db\" }).decorate(\"db\", buildDb()).state(\"requestId\", \"\");\n```\n\nFor the full list of options and ❌/✅ snippets per rule, see [`docs/rules/`](docs/rules/).\n\n## Design notes\n\n- **AST-only by design.** No `parserServices` / type-checker dependency. Consumers don't need a `parserOptions.project` setup. Rules degrade gracefully where types could give more info — this is documented per-rule under \"Limitations.\"\n- **Family-aligned conventions.** Mirrors [`eslint-plugin-module-boundaries`](https://github.com/agjs/eslint-plugin-module-boundaries) and [`eslint-plugin-drizzle-conventions`](https://github.com/agjs/eslint-plugin-drizzle-conventions): same plugin export shape, same `ESLintUtils.RuleCreator` factory, same flat-config preset structure, same kebab-case rule ids.\n- **Heuristic rules are explicit.** Rules that can't be made precise without parser services (`prefer-destructured-context`, `no-separate-model-interfaces`, `prefer-static-services`, `prefer-throw-status`) document their limits in the rule docs.\n\n## Development\n\n```sh\npnpm install\npnpm test\npnpm typecheck\npnpm build\n```\n\n## Release\n\nTag a `v*` version locally and push the tag — `.github/workflows/release.yml` runs `pnpm publish --access public` with `NPM_TOKEN`.\n\n## License\n\nMIT.\n","readmeFilename":"README.md"}