{"_id":"@boring-stack-pkg/eslint-plugin-drizzle-conventions","_rev":"3-f570b21bf56030c8c67b5ca51c7c2df4","name":"@boring-stack-pkg/eslint-plugin-drizzle-conventions","dist-tags":{"latest":"0.2.0"},"versions":{"0.1.1":{"name":"@boring-stack-pkg/eslint-plugin-drizzle-conventions","version":"0.1.1","keywords":["eslint","eslintplugin","typescript","drizzle","drizzle-orm","schema"],"author":"","license":"MIT","_id":"@boring-stack-pkg/eslint-plugin-drizzle-conventions@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":"0d2642810f1971aeae41c34c43a52913bfe64c6a","tarball":"https://registry.npmjs.org/@boring-stack-pkg/eslint-plugin-drizzle-conventions/-/eslint-plugin-drizzle-conventions-0.1.1.tgz","fileCount":23,"integrity":"sha512-gat55bsUXhmVxnW+fqmcF/dEOP2/4aYs57lb15GvScVcxaHMrQcfc1fwxz3l4/TgXuT5xjWLGdoN9E1Oo71hXg==","signatures":[{"sig":"MEUCIQDFpDyV297xJr0wkrSu9nPco9lJ9b0GLQZsKmiICnnVWQIgBTfzcWbHmiYwabg5yzvWdth/Qzyk6NpaF61WlS+qnow=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@boring-stack-pkg%2feslint-plugin-drizzle-conventions@0.1.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":124312},"main":"./dist/index.cjs","type":"module","_from":"file:boring-stack-pkg-eslint-plugin-drizzle-conventions-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/1c65a1955be4d450b3bc560aecb99178/boring-stack-pkg-eslint-plugin-drizzle-conventions-0.1.1.tgz","_integrity":"sha512-gat55bsUXhmVxnW+fqmcF/dEOP2/4aYs57lb15GvScVcxaHMrQcfc1fwxz3l4/TgXuT5xjWLGdoN9E1Oo71hXg==","repository":{"url":"git+https://github.com/AI-Starter-Templates/eslint-plugins.git","type":"git","directory":"eslint-plugin-drizzle-conventions"},"_npmVersion":"10.9.7","description":"ESLint plugin enforcing schema-quality and organization conventions for Drizzle ORM.","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-drizzle-conventions_0.1.1_1779219581013_0.9134682094745943","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"@boring-stack-pkg/eslint-plugin-drizzle-conventions","version":"0.1.2","keywords":["eslint","eslintplugin","typescript","drizzle","drizzle-orm","schema"],"author":"","license":"MIT","_id":"@boring-stack-pkg/eslint-plugin-drizzle-conventions@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":"45213d45d0547416f71b80b36b13e71e7fed1cf1","tarball":"https://registry.npmjs.org/@boring-stack-pkg/eslint-plugin-drizzle-conventions/-/eslint-plugin-drizzle-conventions-0.1.2.tgz","fileCount":23,"integrity":"sha512-YrYXzUD1VkRgjEQ5ygV+euU/A6hl7wQl4Sgx0aDAXS0iQXJ/Imd3POkmd8bF5ZV2j4BLhbaDS5yAx1YTcQ6BxA==","signatures":[{"sig":"MEUCIBvmmTkek+nokusCi8a3Y/KcqElGLWp55qDEvQ/t6q9iAiEA+9+FuPcRBhfyBsT/EvW0doDKhQXIXRdq0StdEpL264Q=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":124882},"main":"./dist/index.cjs","type":"module","_from":"file:boring-stack-pkg-eslint-plugin-drizzle-conventions-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/25540e942d5b7a095baa685fd762a004/boring-stack-pkg-eslint-plugin-drizzle-conventions-0.1.2.tgz","_integrity":"sha512-YrYXzUD1VkRgjEQ5ygV+euU/A6hl7wQl4Sgx0aDAXS0iQXJ/Imd3POkmd8bF5ZV2j4BLhbaDS5yAx1YTcQ6BxA==","repository":{"url":"git+https://github.com/boringstack-xyz/eslint-plugins.git","type":"git","directory":"eslint-plugin-drizzle-conventions"},"_npmVersion":"11.15.0","description":"ESLint plugin enforcing schema-quality and organization conventions for Drizzle ORM.","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-drizzle-conventions_0.1.2_1779695634938_0.13950153535338128","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"_id":"@boring-stack-pkg/eslint-plugin-drizzle-conventions@0.2.0","bugs":{"url":"https://github.com/boringstack-xyz/eslint-plugins/issues"},"dist":{"shasum":"a7876043382f42d474dd3490d79e192a89358d63","tarball":"https://registry.npmjs.org/@boring-stack-pkg/eslint-plugin-drizzle-conventions/-/eslint-plugin-drizzle-conventions-0.2.0.tgz","fileCount":23,"integrity":"sha512-nnO9jLA4g5E4XzDbclkEFfnOpGYYc+7uhdke7n9V7PYp7UIcXvmPkUfQjnisoQ0wCfxmuByM8HM1uSQnpYjqDQ==","signatures":[{"sig":"MEQCIBfrii920OPvIqpjOtA8J4M8g+xyD5+xLvjsAUldVy43AiAI+vyeT8S4rkvGHqsDZAV0F1G0GaSOGU1i3n+55TFXGA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIAnWNwGUY6Pjktht3uh3vYACjhxiaHnhG+ucAX/6ybndAiBid9ip8dueltyTj04vn1smXhuDPoVD7UIj2IC1/ycirw=="}],"unpackedSize":127329},"main":"./dist/index.cjs","name":"@boring-stack-pkg/eslint-plugin-drizzle-conventions","type":"module","_from":"file:boring-stack-pkg-eslint-plugin-drizzle-conventions-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","drizzle","drizzle-orm","schema"],"_resolved":"/tmp/847e145d1ef3ccf31e1a110be63477ed/boring-stack-pkg-eslint-plugin-drizzle-conventions-0.2.0.tgz","_integrity":"sha512-nnO9jLA4g5E4XzDbclkEFfnOpGYYc+7uhdke7n9V7PYp7UIcXvmPkUfQjnisoQ0wCfxmuByM8HM1uSQnpYjqDQ==","repository":{"url":"git+https://github.com/boringstack-xyz/eslint-plugins.git","type":"git","directory":"eslint-plugin-drizzle-conventions"},"_npmVersion":"11.19.1","description":"ESLint plugin enforcing schema-quality and organization conventions for Drizzle ORM.","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-drizzle-conventions_0.2.0_1789409006467_0.8424391062558498"}}},"time":{"created":"2026-05-19T19:39:40.863Z","modified":"2026-09-14T18:03:27.074Z","0.1.1":"2026-05-19T19:39:41.174Z","0.1.2":"2026-05-25T07:53:55.097Z","0.2.0":"2026-09-14T18:03:26.596Z"},"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","drizzle","drizzle-orm","schema"],"repository":{"url":"git+https://github.com/boringstack-xyz/eslint-plugins.git","type":"git","directory":"eslint-plugin-drizzle-conventions"},"description":"ESLint plugin enforcing schema-quality and organization conventions for Drizzle ORM.","maintainers":[{"name":"agjs","email":"hi@aleksandar.xyz"}],"readme":"# eslint-plugin-drizzle-conventions\n\n[![npm](https://img.shields.io/npm/v/@boring-stack-pkg/eslint-plugin-drizzle-conventions?logo=npm)](https://www.npmjs.com/package/@boring-stack-pkg/eslint-plugin-drizzle-conventions) [![source](https://img.shields.io/badge/source-github-blue?logo=github)](https://github.com/boringstack-xyz/eslint-plugins/tree/main/eslint-plugin-drizzle-conventions)\n\nESLint plugin enforcing schema-quality and organization conventions for [Drizzle ORM](https://orm.drizzle.team/).\n\n## Why\n\nDrizzle gives you a typed schema, but nothing in the type system tells you that every table needs `createdAt`/`updatedAt`, that every `foreignKey(...)` needs a matching `relations(...)` call, that raw `sql\\`\\`` is a back door past your guarantees, or that schema files should not double as a service dumping ground. These four rules pin those invariants down at lint time so they don't drift.\n\n## Install\n\n```sh\npnpm add -D @boring-stack-pkg/eslint-plugin-drizzle-conventions\n```\n\n## Usage (flat config)\n\n```js\n// eslint.config.mjs\nimport tsParser from \"@typescript-eslint/parser\";\nimport drizzleConventions from \"@boring-stack-pkg/eslint-plugin-drizzle-conventions\";\n\nexport default [\n  {\n    files: [\"**/*.ts\"],\n    languageOptions: {\n      parser: tsParser,\n      parserOptions: { ecmaVersion: \"latest\", sourceType: \"module\" },\n    },\n    plugins: { \"drizzle-conventions\": drizzleConventions },\n    rules: drizzleConventions.configs.recommended.rules,\n  },\n];\n```\n\nThe recommended preset enables all four rules at `\"error\"`. To configure individual rules, replace the `rules:` block with explicit entries:\n\n```js\nrules: {\n  \"drizzle-conventions/tables-must-have-timestamps\": [\n    \"error\",\n    { requireColumns: [\"createdAt\", \"updatedAt\", \"deletedAt\"] }\n  ],\n  \"drizzle-conventions/no-raw-sql-outside-allowlist\": [\n    \"error\",\n    { allowFiles: [\"**/migrations/**\", \"**/raw/**\", \"**/reports/**\"] }\n  ]\n}\n```\n\n## Rules\n\n| Rule                                                                                         | Category    | Description                                                                                                                    |\n| -------------------------------------------------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------ |\n| [`tables-must-have-timestamps`](docs/rules/tables-must-have-timestamps.md)                   | Convention  | Tables must declare timestamp columns (`createdAt`, `updatedAt`); optional `requireOnUpdate` enforces `.$onUpdate(...)`.       |\n| [`timestamp-must-specify-mode`](docs/rules/timestamp-must-specify-mode.md)                   | Correctness | Every `timestamp(...)` must pin `mode: 'date'` or `mode: 'string'` so return types are deterministic across drivers.           |\n| [`relations-must-cover-fks`](docs/rules/relations-must-cover-fks.md)                         | Correctness | Every table that declares a `foreignKey(...)` needs a matching `relations(...)` call in the same file.                         |\n| [`no-raw-sql-outside-allowlist`](docs/rules/no-raw-sql-outside-allowlist.md)                 | Safety      | Disallow drizzle-orm `sql` tagged templates outside an allowlist (migrations, raw queries by default).                         |\n| [`no-nested-db-transaction`](docs/rules/no-nested-db-transaction.md)                         | Correctness | Inside a `db.transaction(async (tx) => …)` callback, forbid `db.transaction(...)`; use `tx.transaction(...)` instead.          |\n| [`schema-files-must-only-export-schema`](docs/rules/schema-files-must-only-export-schema.md) | Convention  | Schema files (`**/schema/**/*.schema.ts` by default) may only export schema artifacts and types.                               |\n| [`schema-files-must-not-import-driver`](docs/rules/schema-files-must-not-import-driver.md)   | Boundary    | Schema files must not import database driver packages (`pg`, `drizzle-orm/node-postgres`, etc.) — keeps them runtime-portable. |\n\n### tables-must-have-timestamps\n\nOptions: `{ requireColumns?: string[]; requireOnUpdate?: string[]; ignoreTablePattern?: string }`\n\n```ts\n// ❌\nexport const users = pgTable(\"users\", { id: uuid(\"id\").primaryKey() });\n\n// ✅\nexport const users = pgTable(\"users\", {\n  id: uuid(\"id\").primaryKey(),\n  createdAt: timestamp(\"created_at\", { mode: \"date\" }).notNull(),\n  updatedAt: timestamp(\"updated_at\", { mode: \"date\" })\n    .notNull()\n    .$onUpdate(() => new Date()),\n});\n```\n\n`requireOnUpdate` is opt-in. Without `.$onUpdate(...)`, `updatedAt` does not auto-update on row mutation. Recommended override:\n\n```js\n\"drizzle-conventions/tables-must-have-timestamps\": [\n  \"error\",\n  { requireOnUpdate: [\"updatedAt\"] }\n]\n```\n\n### timestamp-must-specify-mode\n\nOptions: `{ allowedModes?: (\"date\" | \"string\")[] }`\n\n```ts\n// ❌  return type drifts across drivers\ncreatedAt: timestamp(\"created_at\").notNull();\n\n// ✅\ncreatedAt: timestamp(\"created_at\", { mode: \"date\" }).notNull();\n```\n\n### relations-must-cover-fks\n\nOptions: `{ allowExternalFile?: boolean }` (default `false`).\n\n```ts\n// ❌\nexport const posts = pgTable(\"posts\", {\n  id: uuid(\"id\").primaryKey(),\n  authorFk: foreignKey({ columns: [], foreignColumns: [] }),\n});\n\n// ✅\nexport const posts = pgTable(\"posts\", {\n  /* ... */\n});\nexport const postsRelations = relations(posts, ({ one }) => ({}));\n```\n\nSingle-file detection only; cross-file relation detection is out of scope without parser services.\n\n### no-raw-sql-outside-allowlist\n\nOptions: `{ allowFiles?: string[] }` (default `[\"**/migrations/**\", \"**/raw/**\"]`).\n\n```ts\n// ❌  src/services/users.service.ts\nimport { sql } from \"drizzle-orm\";\nexport const ban = sql`UPDATE users SET banned = true`;\n\n// ✅  src/db/migrations/0001-init.ts\nimport { sql } from \"drizzle-orm\";\nexport const up = sql`CREATE TABLE users (id text)`;\n```\n\n### no-nested-db-transaction\n\nOptions: `{ transactionMethod?: string }` (default `\"transaction\"`).\n\n```ts\n// ❌\nawait db.transaction(async (tx) => {\n  await db.transaction(async (innerTx) => {}); // races, deadlocks on poolers\n});\n\n// ✅\nawait db.transaction(async (tx) => {\n  await tx.transaction(async (innerTx) => {}); // savepoint on the same connection\n});\n```\n\n### schema-files-must-not-import-driver\n\nOptions: `{ filePattern?: string; forbiddenSources?: string[]; forbiddenSourcePatterns?: string[] }`\n\nSchema files must remain runtime-agnostic — driver imports (`pg`, `drizzle-orm/node-postgres`, etc.) belong in the consuming application, not the schema package.\n\n```ts\n// ❌  src/schema/users/users.schema.ts\nimport { drizzle } from \"drizzle-orm/node-postgres\";\n\n// ✅  src/schema/users/users.schema.ts\nimport { pgTable } from \"drizzle-orm/pg-core\";\n```\n\n### schema-files-must-only-export-schema\n\nOptions: `{ filePattern?: string }` (default `**/schema/**/*.schema.ts`).\n\n```ts\n// ❌  src/schema/users/users.schema.ts\nexport class UsersService {} // class\nexport const helper = () => 1; // arrow const\n\n// ✅  src/schema/users/users.schema.ts\nexport const users = pgTable(\"users\", {\n  /* ... */\n});\nexport const usersRelations = relations(users, ({ one }) => ({}));\nexport type User = typeof users.$inferSelect;\n```\n\n## Development\n\n```sh\npnpm install\npnpm test\npnpm typecheck\npnpm build\n```\n\n## License\n\nMIT.\n","readmeFilename":"README.md"}