{"_id":"@akivastr/trpc-shield","_rev":"2-bbf19105b819adacd2cf74b5ae96d375","name":"@akivastr/trpc-shield","dist-tags":{"latest":"0.1.1"},"versions":{"0.1.0":{"name":"@akivastr/trpc-shield","version":"0.1.0","keywords":["trpc","authorization","permissions","middleware","rbac","shield"],"license":"MIT","_id":"@akivastr/trpc-shield@0.1.0","maintainers":[{"name":"akivastr","email":"strasser.akiva@gmail.com"}],"homepage":"https://github.com/akivastr/trpc-shield#readme","bugs":{"url":"https://github.com/akivastr/trpc-shield/issues"},"dist":{"shasum":"3f8e73f49b4d63c715e710af402e0837a35d390a","tarball":"https://registry.npmjs.org/@akivastr/trpc-shield/-/trpc-shield-0.1.0.tgz","fileCount":7,"integrity":"sha512-SIVzZe7pQGhFiGJYJ5SV5wurpbOElq0zUxRyUzbFY3gbDX/WaSOZqYEnNjVfQ//2/ocKlYCJsh9oDN15jwNHdQ==","signatures":[{"sig":"MEUCIEOrq5S/0kvU3q/RZHAQBW+lzym+cs4aitiG/zEaKHxPAiEA2WyQ7DiYrOaNF22ok0YAHLlg+q0tqn2foIkPsFEN4W0=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":14095},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=18"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"b83bb51b785f4eb5d3536561563835156795d551","scripts":{"lint":"eslint .","test":"vitest run","build":"tsc -p tsconfig.build.json","check":"eslint . && tsc --noEmit","lint:fix":"eslint . --fix","typecheck":"tsc --noEmit","test:watch":"vitest","format:check":"prettier --check \"**/*.{ts,md,json}\" --cache","format:write":"prettier --write \"**/*.{ts,md,json}\" --cache","prepublishOnly":"pnpm run check && pnpm run test && pnpm run build"},"_npmUser":{"name":"akivastr","email":"strasser.akiva@gmail.com"},"repository":{"url":"git+https://github.com/akivastr/trpc-shield.git","type":"git"},"_npmVersion":"11.6.2","description":"Lightweight, type-safe permission middleware for tRPC v11 routers — gate any procedure, at any nesting depth, by a rules object shaped like your router.","directories":{},"sideEffects":false,"_nodeVersion":"20.19.0","_hasShrinkwrap":false,"packageManager":"pnpm@10.34.1","devDependencies":{"eslint":"^9.39.5","vitest":"^4.1.6","prettier":"^3.5.3","typescript":"^5.8.2","@types/node":"^24","@trpc/server":"^11.0.0","typescript-eslint":"^8.65.0"},"peerDependencies":{"@trpc/server":"^11.0.0"},"_npmOperationalInternal":{"tmp":"tmp/trpc-shield_0.1.0_1786299907887_0.031219800769003436","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@akivastr/trpc-shield","version":"0.1.1","description":"Lightweight, type-safe permission middleware for tRPC v11 routers — gate any procedure, at any nesting depth, by a rules object shaped like your router.","license":"MIT","type":"module","main":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"sideEffects":false,"engines":{"node":">=18"},"repository":{"type":"git","url":"git+https://github.com/akivastr/trpc-shield.git"},"keywords":["trpc","authorization","permissions","middleware","rbac","shield"],"peerDependencies":{"@trpc/server":"^11.0.0"},"devDependencies":{"@trpc/server":"^11.0.0","@types/node":"^24","eslint":"^9.39.5","prettier":"^3.5.3","typescript":"^5.8.2","typescript-eslint":"^8.65.0","vitest":"^4.1.6"},"scripts":{"build":"tsc -p tsconfig.build.json","check":"eslint . && tsc --noEmit","lint":"eslint .","lint:fix":"eslint . --fix","typecheck":"tsc --noEmit","format:check":"prettier --check \"**/*.{ts,md,json}\" --cache","format:write":"prettier --write \"**/*.{ts,md,json}\" --cache","test":"vitest run","test:watch":"vitest"},"_id":"@akivastr/trpc-shield@0.1.1","bugs":{"url":"https://github.com/akivastr/trpc-shield/issues"},"homepage":"https://github.com/akivastr/trpc-shield#readme","_integrity":"sha512-ZEp+NoEB/1w5MNk0noRwGyPDoGS49F1wb6xi5uLh/yB0Mq9ieONnAnUh2JvF8jKfvofTGqpbEBPAHzS4l0sJKw==","_resolved":"/tmp/2b4bc9d3007874a3bed1be044691e260/akivastr-trpc-shield-0.1.1.tgz","_from":"file:akivastr-trpc-shield-0.1.1.tgz","_nodeVersion":"24.18.0","_npmVersion":"11.16.0","dist":{"integrity":"sha512-ZEp+NoEB/1w5MNk0noRwGyPDoGS49F1wb6xi5uLh/yB0Mq9ieONnAnUh2JvF8jKfvofTGqpbEBPAHzS4l0sJKw==","shasum":"8171732afa35f8566cf0ea6174bb2209b078e57d","tarball":"https://registry.npmjs.org/@akivastr/trpc-shield/-/trpc-shield-0.1.1.tgz","fileCount":7,"unpackedSize":13983,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@akivastr%2ftrpc-shield@0.1.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIEjHczJ52JqAgm7Ia3K9VILJg/Q5M+cn37uif6aXP+waAiEA5Qq6NAdj1GX6wTzGkleaBIMS9zSbt3KOiZn6PxiN03o="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:4fbca786-0398-463b-852a-990d2f1b4a8e"}},"directories":{},"maintainers":[{"name":"akivastr","email":"strasser.akiva@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/trpc-shield_0.1.1_1786300642975_0.8022943112442991"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-09T18:25:07.776Z","modified":"2026-08-09T18:37:23.678Z","0.1.0":"2026-08-09T18:25:08.013Z","0.1.1":"2026-08-09T18:37:23.126Z"},"bugs":{"url":"https://github.com/akivastr/trpc-shield/issues"},"license":"MIT","homepage":"https://github.com/akivastr/trpc-shield#readme","keywords":["trpc","authorization","permissions","middleware","rbac","shield"],"repository":{"type":"git","url":"git+https://github.com/akivastr/trpc-shield.git"},"description":"Lightweight, type-safe permission middleware for tRPC v11 routers — gate any procedure, at any nesting depth, by a rules object shaped like your router.","maintainers":[{"name":"akivastr","email":"strasser.akiva@gmail.com"}],"readme":"# trpc-shield\n\nLightweight, type-safe permission middleware for [tRPC](https://trpc.io) v11 routers — inspired by\n[`graphql-shield`](https://github.com/maticzav/graphql-shield). Define an object of rules shaped like your\nrouter, wire it into a single middleware, and every procedure gets a permission check with full type\ninference on `input` and `ctx`.\n\n## Install\n\n```bash\npnpm add @akivastr/trpc-shield\n```\n\n`@trpc/server` (`^11.0.0`) is a peer dependency.\n\n## Quick start\n\n```ts\nimport { initTRPC } from '@trpc/server'\nimport { createShield, type ShieldRules } from '@akivastr/trpc-shield'\n\nconst t = initTRPC.context<{ session: { user: { id: string; role: string } } | null }>().create()\nconst appRouter = t.router({\n  post: t.router({\n    delete: t.procedure.input((v: unknown) => v as { id: string; ownerId: string }).mutation(({ input }) => input)\n  })\n})\n\ntype Ctx = { session: { user: { id: string; role: string } } }\n\n// A partial map, keyed by router/procedure path (dot-separated for nested routers,\n// e.g. \"settings.department.update\"). Paths with no entry fall through per `default`.\nconst rules: ShieldRules<typeof appRouter._def.record, Ctx> = {\n  post: {\n    delete: ({ input, ctx }) => ctx.session.user.role === 'admin' || input.ownerId === ctx.session.user.id\n  }\n}\n\nconst shield = createShield(rules, { default: 'allow' })\n\nexport const protectedProcedure = t.procedure.use(async ({ ctx, path, getRawInput, next }) => {\n  await shield.enforce({ ctx, path, getRawInput })\n  return next()\n})\n```\n\nA rule returning `false` (or resolving to `false`) makes `enforce` throw a `TRPCError` with code\n`FORBIDDEN`. A rule that throws propagates as-is.\n\n## `$base` — gate a whole subtree\n\n`$base` is a reserved key that applies to every procedure nested under that point in the tree. It's\n**additive, not an override**: a procedure is gated by every `$base` rule from the root down to it,\nANDed together with its own leaf rule if one exists — short-circuiting on the first failure, so a more\nspecific leaf rule is never evaluated once an ancestor `$base` has failed.\n\n```ts\nconst rules: ShieldRules<typeof appRouter._def.record, Ctx> = {\n  $base: ({ ctx }) => Boolean(ctx.session.user),\n  settings: {\n    $base: ({ ctx }) => ctx.session.user.role === 'admin',\n    department: {\n      update: ({ input, ctx }) => input.departmentId === ctx.session.user.departmentId\n    }\n  }\n}\n```\n\n`$base`'s input is untyped (`unknown`), since sibling procedures under it can have different input\nshapes — reach for it when the check only needs `ctx`.\n\n## Combinators\n\n```ts\nimport { and, or, not, allow, deny } from '@akivastr/trpc-shield'\n\nupdate: and(\n  ({ ctx }) => Boolean(ctx.session.user),\n  ({ input, ctx }) => input.ownerId === ctx.session.user.id\n)\n\nupdate: or(\n  ({ ctx }) => ctx.session.user.role === 'admin',\n  ({ input, ctx }) => input.ownerId === ctx.session.user.id\n)\n\nupdate: not(({ ctx }) => ctx.session.user.isSuspended)\n```\n\n- `and(...rules)` — passes only if every rule passes; short-circuits on the first failure.\n- `or(...rules)` — passes if any rule passes; short-circuits on the first success.\n- `not(rule)` — inverts a rule's result.\n- `allow` / `deny` — constant rules that always pass / always fail.\n\n## Default policy\n\n```ts\ncreateShield(rules, { default: 'deny' })\n```\n\n- `'allow'` (default) — a procedure with no matching rule (and no applicable `$base`) is allowed.\n- `'deny'` — a procedure with no matching rule throws `FORBIDDEN`, forcing every procedure to have an\n  explicit rule (or an ancestor `$base`) before it's reachable.\n\n## API\n\n| Export                                       | Description                                                                                  |\n| -------------------------------------------- | -------------------------------------------------------------------------------------------- |\n| `createShield(rules, options?)`              | Builds a shield from a rules object. Returns `{ enforce }`.                                  |\n| `shield.enforce({ path, ctx, getRawInput })` | Runs the resolved rule for `path` and throws `TRPCError({ code: 'FORBIDDEN' })` if it fails. |\n| `ShieldRules<TRouterRecord, TCtx>`           | Type for a rules object shaped like a router's `TRPCRouterRecord`.                           |\n| `ShieldRule<TInput, TCtx>`                   | Type for a single rule: `(opts: { input, ctx }) => boolean \\| Promise<boolean>`.             |\n| `and`, `or`, `not`, `allow`, `deny`          | Rule combinators.                                                                            |\n\n## Development\n\n```bash\npnpm install\npnpm check   # eslint + tsc --noEmit\npnpm test\npnpm build\n```\n\n## License\n\nMIT\n","readmeFilename":"README.md"}