{"_id":"@djpanda/convex-authz","_rev":"13-68a9e4955ecf8803b623d7ffbb454feb","name":"@djpanda/convex-authz","dist-tags":{"latest":"2.4.1"},"versions":{"0.1.1":{"name":"@djpanda/convex-authz","version":"0.1.1","keywords":["convex","component","authorization","rbac","abac","rebac","zanzibar","permissions","access-control"],"author":{"name":"djpanda"},"license":"MIT","_id":"@djpanda/convex-authz@0.1.1","maintainers":[{"name":"djpanda","email":"dbjpanda@live.com"}],"homepage":"https://github.com/dbjpanda/convex-authz#readme","bugs":{"url":"https://github.com/dbjpanda/convex-authz/issues"},"dist":{"shasum":"bf8d6fe3a1a0d0a723900d71d92234950980e908","tarball":"https://registry.npmjs.org/@djpanda/convex-authz/-/convex-authz-0.1.1.tgz","fileCount":79,"integrity":"sha512-DFPlguotB9kRr/IQAj8ZbUN5bxDtXuUGKftgRP6k25GYj1Sjgj1s/QVkJ2KX41irpCRseF7f3RSq3OpeM59Qmg==","signatures":[{"sig":"MEUCIHmIeE6KofIHSEpX2Cb0EC8OkE+ZvoTDykzJiPAK+0KDAiEAr5FG/JQXWF0v/MoDUEpUrO1XjFrsi5vdb9XlWd/zp/s=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":464254},"type":"module","types":"./dist/client/index.d.ts","module":"./dist/client/index.js","exports":{".":{"types":"./dist/client/index.d.ts","default":"./dist/client/index.js"},"./test":"./src/test.ts","./react":{"types":"./dist/react/index.d.ts","default":"./dist/react/index.js"},"./package.json":"./package.json","./convex.config":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./convex.config.js":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./_generated/component":{"types":"./dist/component/_generated/component.d.ts"},"./_generated/component.js":{"types":"./dist/component/_generated/component.d.ts"}},"gitHead":"5abff6e001398f6d7e77f06a6c9aee75d9914956","scripts":{"all":"run-p -r 'dev:*' 'test:watch'","dev":"run-p -r 'dev:*'","lint":"eslint .","test":"vitest run --typecheck","alpha":"npm version prerelease --preid alpha && npm publish --tag alpha && git push --follow-tags","build":"tsc --project ./tsconfig.build.json","predev":"path-exists .env.local dist || (npm run build && convex dev --once)","release":"npm version patch && npm publish && git push --follow-tags","version":"vim -c 'normal o' -c 'normal o## '$npm_package_version CHANGELOG.md && prettier -w CHANGELOG.md && git add CHANGELOG.md","dev:build":"chokidar 'tsconfig*.json' 'src/**/*.ts' -i '**/*.test.ts' -c 'npm run build:codegen' --initial","typecheck":"tsc --noEmit && tsc -p example && tsc -p example/convex","preversion":"npm ci && npm run build:clean && run-p test lint typecheck","test:debug":"vitest --inspect-brk --no-file-parallelism","test:watch":"vitest --typecheck --clearScreen false","build:clean":"rm -rf dist *.tsbuildinfo && npm run build:codegen","dev:backend":"convex dev --typecheck-components","dev:frontend":"cd example && vite --clearScreen false","build:codegen":"npx convex codegen --component-dir ./src/component && npm run build","test:coverage":"vitest run --coverage --coverage.reporter=text"},"_npmUser":{"name":"djpanda","email":"dbjpanda@live.com"},"repository":{"url":"git+https://github.com/dbjpanda/convex-authz.git","type":"git"},"_npmVersion":"11.6.2","description":"A comprehensive RBAC/ABAC/ReBAC authorization component for Convex with O(1) indexed lookups, inspired by Google Zanzibar","directories":{},"_nodeVersion":"25.1.0","_hasShrinkwrap":false,"devDependencies":{"vite":"7.2.6","react":"^19.2.1","convex":"1.31.0","eslint":"9.39.1","vitest":"3.2.4","cpy-cli":"^6.0.0","globals":"^16.5.0","prettier":"3.6.2","react-dom":"^19.2.1","@eslint/js":"9.39.1","pkg-pr-new":"^0.0.60","typescript":"5.9.3","@types/node":"^24.10.4","convex-test":"0.0.40","@types/react":"^19.2.7","chokidar-cli":"3.0.0","npm-run-all2":"8.0.4","path-exists-cli":"2.0.0","@edge-runtime/vm":"^5.0.0","@eslint/eslintrc":"^3.3.1","@types/react-dom":"^19.2.3","typescript-eslint":"8.47.0","eslint-plugin-react":"^7.37.5","@vitejs/plugin-react":"^5.1.1","@convex-dev/eslint-plugin":"^1.1.1","eslint-plugin-react-hooks":"^7.0.1","eslint-plugin-react-refresh":"^0.4.24"},"peerDependencies":{"react":"^18.3.1 || ^19.0.0","convex":"^1.29.3"},"_npmOperationalInternal":{"tmp":"tmp/convex-authz_0.1.1_1767900995618_0.16527442467169018","host":"s3://npm-registry-packages-npm-production"}},"0.1.3":{"name":"@djpanda/convex-authz","version":"0.1.3","keywords":["convex","component","authorization","rbac","abac","rebac","zanzibar","permissions","access-control"],"author":{"name":"djpanda"},"license":"MIT","_id":"@djpanda/convex-authz@0.1.3","maintainers":[{"name":"djpanda","email":"dbjpanda@live.com"}],"homepage":"https://github.com/dbjpanda/convex-authz#readme","bugs":{"url":"https://github.com/dbjpanda/convex-authz/issues"},"dist":{"shasum":"cefc82ecd19a143b031835d930ee0ce0cca3f30c","tarball":"https://registry.npmjs.org/@djpanda/convex-authz/-/convex-authz-0.1.3.tgz","fileCount":79,"integrity":"sha512-T8QB43uNHPSo0AoVrAJiZb4umq0nofB3WlYKeiqL/TDRYEVaYIEt4ChIPWEFSiAeEI0NWnXeDR0wvuMgGrq1OA==","signatures":[{"sig":"MEUCIFegf/5mP9ptUODDURQ1K/e7eA7VYlqWecL06AGQC9F7AiEAqIqL/iNMCHnvHmpajxwaJ+J5fDKIoG/Tc+KHuKKa4SM=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":464239},"type":"module","types":"./dist/client/index.d.ts","module":"./dist/client/index.js","exports":{".":{"types":"./dist/client/index.d.ts","default":"./dist/client/index.js"},"./test":"./src/test.ts","./react":{"types":"./dist/react/index.d.ts","default":"./dist/react/index.js"},"./package.json":"./package.json","./convex.config":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./convex.config.js":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./_generated/component":{"types":"./dist/component/_generated/component.d.ts"},"./_generated/component.js":{"types":"./dist/component/_generated/component.d.ts"}},"gitHead":"69dbf0dfe26087502ca08107fde505fcfbcc2351","scripts":{"all":"run-p -r 'dev:*' 'test:watch'","dev":"run-p -r 'dev:*'","lint":"eslint .","test":"vitest run --typecheck","alpha":"npm version prerelease --preid alpha && npm publish --tag alpha && git push --follow-tags","build":"tsc --project ./tsconfig.build.json","predev":"path-exists .env.local dist || (npm run build && convex dev --once)","release":"npm version patch && npm publish && git push --follow-tags","version":"vim -c 'normal o' -c 'normal o## '$npm_package_version CHANGELOG.md && prettier -w CHANGELOG.md && git add CHANGELOG.md","dev:build":"chokidar 'tsconfig*.json' 'src/**/*.ts' -i '**/*.test.ts' -c 'npm run build:codegen' --initial","typecheck":"tsc --noEmit && tsc -p example && tsc -p example/convex","preversion":"npm ci && npm run build:clean && run-p test lint typecheck","test:debug":"vitest --inspect-brk --no-file-parallelism","test:watch":"vitest --typecheck --clearScreen false","build:clean":"rm -rf dist *.tsbuildinfo && npm run build:codegen","dev:backend":"convex dev --typecheck-components","dev:frontend":"cd example && vite --clearScreen false","build:codegen":"npx convex codegen --component-dir ./src/component && npm run build","test:coverage":"vitest run --coverage --coverage.reporter=text"},"_npmUser":{"name":"djpanda","email":"dbjpanda@live.com"},"repository":{"url":"git+https://github.com/dbjpanda/convex-authz.git","type":"git"},"_npmVersion":"11.6.2","description":"A comprehensive RBAC/ABAC/ReBAC authorization component for Convex with O(1) indexed lookups, inspired by Google Zanzibar","directories":{},"_nodeVersion":"25.1.0","_hasShrinkwrap":false,"devDependencies":{"vite":"7.2.6","react":"^19.2.1","convex":"1.31.0","eslint":"9.39.1","vitest":"3.2.4","cpy-cli":"^6.0.0","globals":"^16.5.0","prettier":"3.6.2","react-dom":"^19.2.1","@eslint/js":"9.39.1","pkg-pr-new":"^0.0.60","typescript":"5.9.3","@types/node":"^24.10.4","convex-test":"0.0.40","@types/react":"^19.2.7","chokidar-cli":"3.0.0","npm-run-all2":"8.0.4","path-exists-cli":"2.0.0","@edge-runtime/vm":"^5.0.0","@eslint/eslintrc":"^3.3.1","@types/react-dom":"^19.2.3","typescript-eslint":"8.47.0","eslint-plugin-react":"^7.37.5","@vitejs/plugin-react":"^5.1.1","@convex-dev/eslint-plugin":"^1.1.1","eslint-plugin-react-hooks":"^7.0.1","eslint-plugin-react-refresh":"^0.4.24"},"peerDependencies":{"react":"^18.3.1 || ^19.0.0","convex":"^1.29.3"},"_npmOperationalInternal":{"tmp":"tmp/convex-authz_0.1.3_1767902046375_0.9353491777085148","host":"s3://npm-registry-packages-npm-production"}},"0.1.4":{"name":"@djpanda/convex-authz","version":"0.1.4","keywords":["convex","component","authorization","rbac","abac","rebac","zanzibar","permissions","access-control"],"author":{"name":"djpanda"},"license":"MIT","_id":"@djpanda/convex-authz@0.1.4","maintainers":[{"name":"djpanda","email":"dbjpanda@live.com"}],"homepage":"https://github.com/dbjpanda/convex-authz#readme","bugs":{"url":"https://github.com/dbjpanda/convex-authz/issues"},"dist":{"shasum":"f6a033f82336e151453fcc7e5688afe15e61daef","tarball":"https://registry.npmjs.org/@djpanda/convex-authz/-/convex-authz-0.1.4.tgz","fileCount":80,"integrity":"sha512-kAhd5BpFuI4vyegF2tbp0A5lIDoGnamxyCcT2SrsKPpeycmm6on1tI1Nlb/uvGrZ0SCDl1svof+itDyAsraoDg==","signatures":[{"sig":"MEUCIQCi216hX3hjPkCmgRGndsudB80NYrYreiPLm0Ka+kanrwIgNGKYzN7+0oa/PC9td2318Jwttv8RbJ0omnKetjyXwx0=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":502843},"type":"module","types":"./dist/client/index.d.ts","module":"./dist/client/index.js","exports":{".":{"types":"./dist/client/index.d.ts","default":"./dist/client/index.js"},"./test":"./src/test.ts","./react":{"types":"./dist/react/index.d.ts","default":"./dist/react/index.js"},"./package.json":"./package.json","./convex.config":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./convex.config.js":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./_generated/component":{"types":"./dist/component/_generated/component.d.ts"},"./_generated/component.js":{"types":"./dist/component/_generated/component.d.ts"}},"gitHead":"7740053809b62d437d7e70111c5330e07c527633","scripts":{"all":"run-p -r 'dev:*' 'test:watch'","dev":"run-p -r 'dev:*'","lint":"eslint .","test":"vitest run --typecheck","alpha":"npm version prerelease --preid alpha && npm publish --tag alpha && git push --follow-tags","build":"tsc --project ./tsconfig.build.json","predev":"path-exists .env.local dist || (npm run build && convex dev --once)","release":"npm version patch && npm publish && git push --follow-tags","version":"vim -c 'normal o' -c 'normal o## '$npm_package_version CHANGELOG.md && prettier -w CHANGELOG.md && git add CHANGELOG.md","dev:build":"chokidar 'tsconfig*.json' 'src/**/*.ts' -i '**/*.test.ts' -c 'npm run build:codegen' --initial","typecheck":"tsc --noEmit && tsc -p example && tsc -p example/convex","preversion":"npm ci && npm run build:clean && run-p test lint typecheck","test:debug":"vitest --inspect-brk --no-file-parallelism","test:watch":"vitest --typecheck --clearScreen false","build:clean":"rm -rf dist *.tsbuildinfo && npm run build:codegen","dev:backend":"convex dev --typecheck-components","dev:frontend":"cd example && vite --clearScreen false","build:codegen":"npx convex codegen --component-dir ./src/component && npm run build","test:coverage":"vitest run --coverage --coverage.reporter=text"},"_npmUser":{"name":"djpanda","email":"dbjpanda@live.com"},"repository":{"url":"git+https://github.com/dbjpanda/convex-authz.git","type":"git"},"_npmVersion":"11.6.2","description":"A comprehensive RBAC/ABAC/ReBAC authorization component for Convex with O(1) indexed lookups, inspired by Google Zanzibar","directories":{},"_nodeVersion":"25.1.0","_hasShrinkwrap":false,"devDependencies":{"clsx":"^2.1.1","vite":"7.2.6","react":"^19.2.1","convex":"1.31.0","eslint":"9.39.1","vitest":"3.2.4","cpy-cli":"^6.0.0","globals":"^16.5.0","prettier":"3.6.2","react-dom":"^19.2.1","@eslint/js":"9.39.1","pkg-pr-new":"^0.0.60","typescript":"5.9.3","@types/node":"^24.10.4","convex-test":"0.0.40","tailwindcss":"^4.1.17","@types/react":"^19.2.7","chokidar-cli":"3.0.0","lucide-react":"^0.555.0","npm-run-all2":"8.0.4","tailwind-merge":"^3.4.0","path-exists-cli":"2.0.0","@edge-runtime/vm":"^5.0.0","@eslint/eslintrc":"^3.3.1","@types/react-dom":"^19.2.3","@tailwindcss/vite":"^4.1.17","typescript-eslint":"8.47.0","eslint-plugin-react":"^7.37.5","@radix-ui/react-slot":"^1.2.4","@vitejs/plugin-react":"^5.1.1","class-variance-authority":"^0.7.1","@convex-dev/eslint-plugin":"^1.1.1","eslint-plugin-react-hooks":"^7.0.1","eslint-plugin-react-refresh":"^0.4.24"},"peerDependencies":{"react":"^18.3.1 || ^19.0.0","convex":"^1.29.3"},"_npmOperationalInternal":{"tmp":"tmp/convex-authz_0.1.4_1769634742277_0.5176941728379534","host":"s3://npm-registry-packages-npm-production"}},"0.1.7":{"name":"@djpanda/convex-authz","version":"0.1.7","keywords":["convex","component","authorization","rbac","abac","rebac","zanzibar","permissions","access-control"],"author":{"name":"djpanda"},"license":"MIT","_id":"@djpanda/convex-authz@0.1.7","maintainers":[{"name":"djpanda","email":"dbjpanda@live.com"}],"homepage":"https://github.com/dbjpanda/convex-authz#readme","bugs":{"url":"https://github.com/dbjpanda/convex-authz/issues"},"dist":{"shasum":"63ed413216680f1f8c23f0a74290fd1d59953f81","tarball":"https://registry.npmjs.org/@djpanda/convex-authz/-/convex-authz-0.1.7.tgz","fileCount":84,"integrity":"sha512-gI6x2xyfnsmft/B3wbEcCJ2pVICwlOuedyt6NibZC1/Tt1u6yOzTs4XOKa0S5wbrtUCa87pqPJF8BIzGaQl8sA==","signatures":[{"sig":"MEUCIDk1z8fo5DxXeqUsmGjvQn14z6xsa0Ptsg8mmgZ3G6/bAiEAt8xwiaXJ1NMCe7g4+mHnl1RivFfjagk4UkXZvv1A+yI=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":637307},"type":"module","types":"./dist/client/index.d.ts","module":"./dist/client/index.js","exports":{".":{"types":"./dist/client/index.d.ts","default":"./dist/client/index.js"},"./test":"./src/test.ts","./react":{"types":"./dist/react/index.d.ts","default":"./dist/react/index.js"},"./package.json":"./package.json","./convex.config":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./convex.config.js":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./_generated/component":{"types":"./dist/component/_generated/component.d.ts"},"./_generated/component.js":{"types":"./dist/component/_generated/component.d.ts"}},"gitHead":"ac411fb2ead1a6ba496ee9224e55e7407906c6e0","scripts":{"all":"run-p -r 'dev:*' 'test:watch'","dev":"run-p -r 'dev:*'","lint":"eslint .","test":"vitest run --typecheck","alpha":"npm version prerelease --preid alpha && npm publish --tag alpha && git push --follow-tags","build":"tsc --project ./tsconfig.build.json","predev":"path-exists .env.local dist || (npm run build && convex dev --once)","release":"npm version patch && npm publish && git push --follow-tags","version":"bash scripts/changelog.sh && prettier -w CHANGELOG.md && git add CHANGELOG.md","dev:build":"chokidar 'tsconfig*.json' 'src/**/*.ts' -i '**/*.test.ts' -c 'npm run build:codegen' --initial","typecheck":"tsc --noEmit && tsc -p example && tsc -p example/convex","preversion":"npm ci && npm run build:clean && run-p test lint typecheck","test:debug":"vitest --inspect-brk --no-file-parallelism","test:watch":"vitest --typecheck --clearScreen false","build:clean":"rm -rf dist *.tsbuildinfo && npm run build:codegen","dev:backend":"convex dev --typecheck-components","dev:frontend":"cd example && vite --clearScreen false","build:codegen":"npx convex codegen --component-dir ./src/component && npm run build","test:coverage":"vitest run --coverage --coverage.reporter=text"},"_npmUser":{"name":"djpanda","email":"dbjpanda@live.com"},"repository":{"url":"git+https://github.com/dbjpanda/convex-authz.git","type":"git"},"_npmVersion":"11.6.2","description":"A comprehensive RBAC/ABAC/ReBAC authorization component for Convex with O(1) indexed lookups, inspired by Google Zanzibar","directories":{},"_nodeVersion":"25.1.0","_hasShrinkwrap":false,"devDependencies":{"clsx":"^2.1.1","vite":"7.2.6","react":"^19.2.1","convex":"1.31.0","eslint":"9.39.1","vitest":"3.2.4","cpy-cli":"^6.0.0","globals":"^16.5.0","prettier":"3.6.2","react-dom":"^19.2.1","@eslint/js":"9.39.1","pkg-pr-new":"^0.0.60","typescript":"5.9.3","@types/node":"^24.10.4","convex-test":"0.0.40","tailwindcss":"^4.1.17","@types/react":"^19.2.7","chokidar-cli":"3.0.0","lucide-react":"^0.555.0","npm-run-all2":"8.0.4","tailwind-merge":"^3.4.0","path-exists-cli":"2.0.0","@edge-runtime/vm":"^5.0.0","@eslint/eslintrc":"^3.3.1","@types/react-dom":"^19.2.3","@tailwindcss/vite":"^4.1.17","typescript-eslint":"8.47.0","@vitest/coverage-v8":"^3.2.4","eslint-plugin-react":"^7.37.5","@radix-ui/react-slot":"^1.2.4","@vitejs/plugin-react":"^5.1.1","class-variance-authority":"^0.7.1","@convex-dev/eslint-plugin":"^1.1.1","eslint-plugin-react-hooks":"^7.0.1","eslint-plugin-react-refresh":"^0.4.24"},"peerDependencies":{"react":"^18.3.1 || ^19.0.0","convex":"^1.29.3"},"_npmOperationalInternal":{"tmp":"tmp/convex-authz_0.1.7_1770670266002_0.002181668725487862","host":"s3://npm-registry-packages-npm-production"}},"2.0.0":{"name":"@djpanda/convex-authz","version":"2.0.0","keywords":["convex","component","authorization","rbac","abac","rebac","zanzibar","permissions","access-control"],"author":{"name":"djpanda"},"license":"Apache-2.0","_id":"@djpanda/convex-authz@2.0.0","maintainers":[{"name":"djpanda","email":"dbjpanda@live.com"}],"homepage":"https://github.com/dbjpanda/convex-authz#readme","bugs":{"url":"https://github.com/dbjpanda/convex-authz/issues"},"dist":{"shasum":"d598c8d564b0eba1105e6c84c60e0d7c4cdf0266","tarball":"https://registry.npmjs.org/@djpanda/convex-authz/-/convex-authz-2.0.0.tgz","fileCount":112,"integrity":"sha512-HX/MSf2H46l+0fkmwFSxSmP1s0r3kjKZ4aQcDJUVTX3vj3NsGRJktMKc0MimL7gRTkV9/7HHCzbVdI0zH7X4gg==","signatures":[{"sig":"MEUCIG6SIePG4D1Y+4I0ytzCW4EFfCR/z+ZdydZx/IYhRf8TAiEA9dx1WHWqDMI1gowDNl6Dx1lz8Mc2DS4NMZAgS0JYZDA=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":1301819},"type":"module","types":"./dist/client/index.d.ts","module":"./dist/client/index.js","exports":{".":{"types":"./dist/client/index.d.ts","default":"./dist/client/index.js"},"./test":"./src/test.ts","./react":{"types":"./dist/react/index.d.ts","default":"./dist/react/index.js"},"./package.json":"./package.json","./convex.config":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./convex.config.js":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./_generated/component":{"types":"./dist/component/_generated/component.d.ts"},"./_generated/component.js":{"types":"./dist/component/_generated/component.d.ts"}},"gitHead":"36b11a496b2010af790bc9ec48558e8dedac4e99","scripts":{"all":"run-p -r 'dev:*' 'test:watch'","dev":"run-p -r 'dev:*'","lint":"eslint .","test":"vitest run --typecheck","alpha":"npm version prerelease --preid alpha && npm publish --tag alpha && git push --follow-tags","build":"tsc --project ./tsconfig.build.json","predev":"path-exists .env.local dist || (npm run build && convex dev --once)","release":"npm version patch && npm publish && git push --follow-tags","version":"bash scripts/changelog.sh && prettier -w CHANGELOG.md && git add CHANGELOG.md","dev:build":"chokidar 'tsconfig*.json' 'src/**/*.ts' -i '**/*.test.ts' -c 'npm run build:codegen' --initial","typecheck":"tsc --noEmit && tsc -p example && tsc -p example/convex","preversion":"npm ci && npm run build:clean && run-p test lint typecheck","test:debug":"vitest --inspect-brk --no-file-parallelism","test:watch":"vitest --typecheck --clearScreen false","build:clean":"rm -rf dist *.tsbuildinfo && npm run build:codegen","dev:backend":"convex dev --typecheck-components","dev:frontend":"cd example && vite --clearScreen false","build:codegen":"npx convex codegen --component-dir ./src/component && npm run build","test:coverage":"vitest run --coverage --coverage.reporter=text"},"_npmUser":{"name":"djpanda","email":"dbjpanda@live.com"},"repository":{"url":"git+https://github.com/dbjpanda/convex-authz.git","type":"git"},"_npmVersion":"11.6.2","description":"A comprehensive RBAC/ABAC/ReBAC authorization component for Convex with O(1) indexed lookups, inspired by Google Zanzibar","directories":{},"_nodeVersion":"25.1.0","dependencies":{"@convex-dev/crons":"^0.2.0"},"_hasShrinkwrap":false,"devDependencies":{"clsx":"^2.1.1","vite":"7.2.6","jsdom":"^25.0.0","react":"^19.2.1","convex":"1.31.0","eslint":"9.39.1","vitest":"3.2.4","cpy-cli":"^6.0.0","globals":"^16.5.0","prettier":"3.6.2","react-dom":"^19.2.1","@eslint/js":"9.39.1","pkg-pr-new":"^0.0.60","typescript":"5.9.3","@types/node":"^24.10.4","convex-test":"0.0.40","tailwindcss":"^4.1.17","@types/react":"^19.2.7","chokidar-cli":"3.0.0","lucide-react":"^0.555.0","npm-run-all2":"8.0.4","tailwind-merge":"^3.4.0","path-exists-cli":"2.0.0","@edge-runtime/vm":"^5.0.0","@eslint/eslintrc":"^3.3.1","@types/react-dom":"^19.2.3","@tailwindcss/vite":"^4.1.17","typescript-eslint":"8.47.0","@vitest/coverage-v8":"^3.2.4","eslint-plugin-react":"^7.37.5","@radix-ui/react-slot":"^1.2.4","@vitejs/plugin-react":"^5.1.1","@testing-library/react":"^16.0.0","class-variance-authority":"^0.7.1","@convex-dev/eslint-plugin":"^1.1.1","eslint-plugin-react-hooks":"^7.0.1","eslint-plugin-react-refresh":"^0.4.24"},"peerDependencies":{"react":"^18.3.1 || ^19.0.0","convex":"^1.29.3"},"_npmOperationalInternal":{"tmp":"tmp/convex-authz_2.0.0_1774365115399_0.43524713183992625","host":"s3://npm-registry-packages-npm-production"}},"2.1.0":{"name":"@djpanda/convex-authz","version":"2.1.0","keywords":["convex","component","authorization","rbac","abac","rebac","zanzibar","permissions","access-control"],"author":{"name":"djpanda"},"license":"Apache-2.0","_id":"@djpanda/convex-authz@2.1.0","maintainers":[{"name":"djpanda","email":"dbjpanda@live.com"}],"homepage":"https://github.com/dbjpanda/convex-authz#readme","bugs":{"url":"https://github.com/dbjpanda/convex-authz/issues"},"dist":{"shasum":"71fac407676303516ec9b38b474097080d5d6caf","tarball":"https://registry.npmjs.org/@djpanda/convex-authz/-/convex-authz-2.1.0.tgz","fileCount":113,"integrity":"sha512-CFIYXPPouSwXLqd0qj1HxCZtUGC3eEjufa90imMeFoPmP7MSzyC9Sq15HZzcgPHnj8glWm/kVklMdrhS1LseJQ==","signatures":[{"sig":"MEUCIBOhJaZL7n9fKsOIW5Jl5WwLV7RBnRDqBbIn0WNouaAlAiEAiWMq4P0Mspn5qoMZWiDO+cn0kP7Aw4PdxCffwUulUjc=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":1310879},"type":"module","types":"./dist/client/index.d.ts","module":"./dist/client/index.js","exports":{".":{"types":"./dist/client/index.d.ts","default":"./dist/client/index.js"},"./test":"./src/test.ts","./react":{"types":"./dist/react/index.d.ts","default":"./dist/react/index.js"},"./package.json":"./package.json","./convex.config":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./convex.config.js":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./_generated/component":{"types":"./dist/component/_generated/component.d.ts"},"./_generated/component.js":{"types":"./dist/component/_generated/component.d.ts"}},"gitHead":"2e554dc5c51246c17d0997bbe44504f728afc9fe","scripts":{"all":"run-p -r 'dev:*' 'test:watch'","dev":"run-p -r 'dev:*'","lint":"eslint .","test":"vitest run --typecheck","alpha":"npm version prerelease --preid alpha && npm publish --tag alpha && git push --follow-tags","build":"tsc --project ./tsconfig.build.json","predev":"path-exists .env.local dist || (npm run build && convex dev --once)","release":"npm version patch && npm publish && git push --follow-tags","version":"bash scripts/changelog.sh && prettier -w CHANGELOG.md && git add CHANGELOG.md","dev:build":"chokidar 'tsconfig*.json' 'src/**/*.ts' -i '**/*.test.ts' -c 'npm run build:codegen' --initial","typecheck":"tsc --noEmit && tsc -p example && tsc -p example/convex","preversion":"npm ci && npm run build:clean && run-p test lint typecheck","test:debug":"vitest --inspect-brk --no-file-parallelism","test:watch":"vitest --typecheck --clearScreen false","build:clean":"rm -rf dist *.tsbuildinfo && npm run build:codegen","dev:backend":"convex dev --typecheck-components","dev:frontend":"cd example && vite --clearScreen false","build:codegen":"npx convex codegen --component-dir ./src/component && npm run build","test:coverage":"vitest run --coverage --coverage.reporter=text"},"_npmUser":{"name":"djpanda","email":"dbjpanda@live.com"},"repository":{"url":"git+https://github.com/dbjpanda/convex-authz.git","type":"git"},"_npmVersion":"11.6.2","description":"A comprehensive RBAC/ABAC/ReBAC authorization component for Convex with O(1) indexed lookups, inspired by Google Zanzibar","directories":{},"_nodeVersion":"25.1.0","dependencies":{"@convex-dev/crons":"^0.2.0"},"_hasShrinkwrap":false,"devDependencies":{"clsx":"^2.1.1","vite":"7.2.6","jsdom":"^25.0.0","react":"^19.2.1","convex":"1.31.0","eslint":"9.39.1","vitest":"3.2.4","cpy-cli":"^6.0.0","globals":"^16.5.0","prettier":"3.6.2","react-dom":"^19.2.1","@eslint/js":"9.39.1","pkg-pr-new":"^0.0.60","typescript":"5.9.3","@types/node":"^24.10.4","convex-test":"0.0.40","tailwindcss":"^4.1.17","@types/react":"^19.2.7","chokidar-cli":"3.0.0","lucide-react":"^0.555.0","npm-run-all2":"8.0.4","tailwind-merge":"^3.4.0","path-exists-cli":"2.0.0","@edge-runtime/vm":"^5.0.0","@eslint/eslintrc":"^3.3.1","@types/react-dom":"^19.2.3","@tailwindcss/vite":"^4.1.17","typescript-eslint":"8.47.0","@vitest/coverage-v8":"^3.2.4","eslint-plugin-react":"^7.37.5","@radix-ui/react-slot":"^1.2.4","@vitejs/plugin-react":"^5.1.1","@testing-library/react":"^16.0.0","class-variance-authority":"^0.7.1","@convex-dev/eslint-plugin":"^1.1.1","eslint-plugin-react-hooks":"^7.0.1","eslint-plugin-react-refresh":"^0.4.24"},"peerDependencies":{"react":"^18.3.1 || ^19.0.0","convex":"^1.29.3"},"_npmOperationalInternal":{"tmp":"tmp/convex-authz_2.1.0_1774372149661_0.4397841880219233","host":"s3://npm-registry-packages-npm-production"}},"2.1.1":{"name":"@djpanda/convex-authz","version":"2.1.1","keywords":["convex","component","authorization","rbac","abac","rebac","zanzibar","permissions","access-control"],"author":{"name":"djpanda"},"license":"Apache-2.0","_id":"@djpanda/convex-authz@2.1.1","maintainers":[{"name":"djpanda","email":"dbjpanda@live.com"}],"homepage":"https://github.com/dbjpanda/convex-authz#readme","bugs":{"url":"https://github.com/dbjpanda/convex-authz/issues"},"dist":{"shasum":"770e6be619aaf0753541d5eb2c52cf03ec45bf4a","tarball":"https://registry.npmjs.org/@djpanda/convex-authz/-/convex-authz-2.1.1.tgz","fileCount":113,"integrity":"sha512-/sVWdl6Xf3bfTJhC/YP8B45THSKXnmPuT1nGDvAlvcqpGpb1PQvoiZYnHVeWPY+C4mT8OIQmb7f2xrELdRkPug==","signatures":[{"sig":"MEYCIQC3476KUgjog4wcHAABUP+HFrP3sAusT7XnJiZRZRFkbQIhANibuVT6VUhsIA+qay5efAxxihPvshCsslZnULyXtYuv","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":1310727},"type":"module","types":"./dist/client/index.d.ts","module":"./dist/client/index.js","exports":{".":{"types":"./dist/client/index.d.ts","default":"./dist/client/index.js"},"./test":"./src/test.ts","./react":{"types":"./dist/react/index.d.ts","default":"./dist/react/index.js"},"./package.json":"./package.json","./convex.config":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./convex.config.js":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./_generated/component":{"types":"./dist/component/_generated/component.d.ts"},"./_generated/component.js":{"types":"./dist/component/_generated/component.d.ts"}},"gitHead":"49bb65b3f4da1fb4600878bb687cac3c6ab3f231","scripts":{"all":"run-p -r 'dev:*' 'test:watch'","dev":"run-p -r 'dev:*'","lint":"eslint .","test":"vitest run --typecheck","alpha":"npm version prerelease --preid alpha && npm publish --tag alpha && git push --follow-tags","build":"tsc --project ./tsconfig.build.json","predev":"path-exists .env.local dist || (npm run build && convex dev --once)","release":"npm version patch && npm publish && git push --follow-tags","version":"bash scripts/changelog.sh && prettier -w CHANGELOG.md && git add CHANGELOG.md","dev:build":"chokidar 'tsconfig*.json' 'src/**/*.ts' -i '**/*.test.ts' -c 'npm run build:codegen' --initial","typecheck":"tsc --noEmit && tsc -p example && tsc -p example/convex","preversion":"npm ci && npm run build:clean && run-p test lint typecheck","test:debug":"vitest --inspect-brk --no-file-parallelism","test:watch":"vitest --typecheck --clearScreen false","build:clean":"rm -rf dist *.tsbuildinfo && npm run build:codegen","dev:backend":"convex dev --typecheck-components","dev:frontend":"cd example && vite --clearScreen false","build:codegen":"npx convex codegen --component-dir ./src/component && npm run build","test:coverage":"vitest run --coverage --coverage.reporter=text"},"_npmUser":{"name":"djpanda","email":"dbjpanda@live.com"},"repository":{"url":"git+https://github.com/dbjpanda/convex-authz.git","type":"git"},"_npmVersion":"11.6.2","description":"A comprehensive RBAC/ABAC/ReBAC authorization component for Convex with O(1) indexed lookups, inspired by Google Zanzibar","directories":{},"_nodeVersion":"25.1.0","dependencies":{"@convex-dev/crons":"^0.2.0"},"_hasShrinkwrap":false,"devDependencies":{"clsx":"^2.1.1","vite":"7.2.6","jsdom":"^25.0.0","react":"^19.2.1","convex":"1.31.0","eslint":"9.39.1","vitest":"3.2.4","cpy-cli":"^6.0.0","globals":"^16.5.0","prettier":"3.6.2","react-dom":"^19.2.1","@eslint/js":"9.39.1","pkg-pr-new":"^0.0.60","typescript":"5.9.3","@types/node":"^24.10.4","convex-test":"0.0.40","tailwindcss":"^4.1.17","@types/react":"^19.2.7","chokidar-cli":"3.0.0","lucide-react":"^0.555.0","npm-run-all2":"8.0.4","tailwind-merge":"^3.4.0","path-exists-cli":"2.0.0","@edge-runtime/vm":"^5.0.0","@eslint/eslintrc":"^3.3.1","@types/react-dom":"^19.2.3","@tailwindcss/vite":"^4.1.17","typescript-eslint":"8.47.0","@vitest/coverage-v8":"^3.2.4","eslint-plugin-react":"^7.37.5","@radix-ui/react-slot":"^1.2.4","@vitejs/plugin-react":"^5.1.1","@testing-library/react":"^16.0.0","class-variance-authority":"^0.7.1","@convex-dev/eslint-plugin":"^1.1.1","eslint-plugin-react-hooks":"^7.0.1","eslint-plugin-react-refresh":"^0.4.24"},"peerDependencies":{"react":"^18.3.1 || ^19.0.0","convex":"^1.29.3"},"_npmOperationalInternal":{"tmp":"tmp/convex-authz_2.1.1_1774373485515_0.9848117745410641","host":"s3://npm-registry-packages-npm-production"}},"2.2.0":{"name":"@djpanda/convex-authz","version":"2.2.0","keywords":["convex","component","authorization","rbac","abac","rebac","zanzibar","permissions","access-control"],"author":{"name":"djpanda"},"license":"Apache-2.0","_id":"@djpanda/convex-authz@2.2.0","maintainers":[{"name":"djpanda","email":"dbjpanda@live.com"}],"homepage":"https://github.com/dbjpanda/convex-authz#readme","bugs":{"url":"https://github.com/dbjpanda/convex-authz/issues"},"dist":{"shasum":"bc226a802be3185c01c412a40b4d9d9ed7f23d07","tarball":"https://registry.npmjs.org/@djpanda/convex-authz/-/convex-authz-2.2.0.tgz","fileCount":113,"integrity":"sha512-qQK3akn1JFWAsMwRP6RTBR8UJLccYnPJqZ2uHK2Nbawt4cn7qslPnEsXG0Wt3O2BMo1RbRifirz5FOT8zChZMw==","signatures":[{"sig":"MEUCIQCkdJXQHPsX2nbYSQmrKox0OUvs+HVwkb0jeZcNpQvVwQIgMZJ/dfpHBCecllSsv4ne1E426y6a0Lqmt1Qc1i1VXsY=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@djpanda%2fconvex-authz@2.2.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":1311233},"type":"module","types":"./dist/client/index.d.ts","module":"./dist/client/index.js","exports":{".":{"types":"./dist/client/index.d.ts","default":"./dist/client/index.js"},"./test":"./src/test.ts","./react":{"types":"./dist/react/index.d.ts","default":"./dist/react/index.js"},"./package.json":"./package.json","./convex.config":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./convex.config.js":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./_generated/component":{"types":"./dist/component/_generated/component.d.ts"},"./_generated/component.js":{"types":"./dist/component/_generated/component.d.ts"}},"gitHead":"7ce63b332bb04f671bb6727882f80222f1d444a3","scripts":{"all":"run-p -r 'dev:*' 'test:watch'","dev":"run-p -r 'dev:*'","lint":"eslint .","test":"vitest run --typecheck","build":"tsc --project ./tsconfig.build.json","predev":"path-exists .env.local dist || (npm run build && convex dev --once)","dev:build":"chokidar 'tsconfig*.json' 'src/**/*.ts' -i '**/*.test.ts' -c 'npm run build:codegen' --initial","typecheck":"tsc --noEmit && tsc -p example && tsc -p example/convex","test:debug":"vitest --inspect-brk --no-file-parallelism","test:watch":"vitest --typecheck --clearScreen false","build:clean":"rm -rf dist *.tsbuildinfo && npm run build:codegen","dev:backend":"convex dev --typecheck-components","dev:frontend":"cd example && vite --clearScreen false","build:codegen":"npx convex codegen --component-dir ./src/component && npm run build","test:coverage":"vitest run --coverage --coverage.reporter=text"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:f59aede5-a33f-444c-8f09-83b76551c53a"}},"repository":{"url":"git+https://github.com/dbjpanda/convex-authz.git","type":"git"},"_npmVersion":"11.11.0","description":"A comprehensive RBAC/ABAC/ReBAC authorization component for Convex with O(1) indexed lookups, inspired by Google Zanzibar","directories":{},"_nodeVersion":"24.14.1","dependencies":{"@convex-dev/crons":"^0.2.0"},"_hasShrinkwrap":false,"devDependencies":{"clsx":"^2.1.1","vite":"7.2.6","jsdom":"^25.0.0","react":"^19.2.1","convex":"1.31.0","eslint":"9.39.1","vitest":"3.2.4","cpy-cli":"^6.0.0","globals":"^16.5.0","prettier":"3.6.2","react-dom":"^19.2.1","@eslint/js":"9.39.1","pkg-pr-new":"^0.0.60","typescript":"5.9.3","@types/node":"^24.10.4","convex-test":"0.0.40","tailwindcss":"^4.1.17","@types/react":"^19.2.7","chokidar-cli":"3.0.0","lucide-react":"^0.555.0","npm-run-all2":"8.0.4","tailwind-merge":"^3.4.0","path-exists-cli":"2.0.0","@edge-runtime/vm":"^5.0.0","@eslint/eslintrc":"^3.3.1","@types/react-dom":"^19.2.3","@tailwindcss/vite":"^4.1.17","typescript-eslint":"8.47.0","@vitest/coverage-v8":"^3.2.4","eslint-plugin-react":"^7.37.5","@radix-ui/react-slot":"^1.2.4","@vitejs/plugin-react":"^5.1.1","@testing-library/react":"^16.0.0","class-variance-authority":"^0.7.1","@convex-dev/eslint-plugin":"^1.1.1","eslint-plugin-react-hooks":"^7.0.1","eslint-plugin-react-refresh":"^0.4.24"},"peerDependencies":{"react":"^18.3.1 || ^19.0.0","convex":"^1.29.3"},"_npmOperationalInternal":{"tmp":"tmp/convex-authz_2.2.0_1777848147169_0.17063588461595502","host":"s3://npm-registry-packages-npm-production"}},"2.3.0":{"name":"@djpanda/convex-authz","version":"2.3.0","keywords":["convex","component","authorization","rbac","abac","rebac","zanzibar","permissions","access-control"],"author":{"name":"djpanda"},"license":"Apache-2.0","_id":"@djpanda/convex-authz@2.3.0","maintainers":[{"name":"djpanda","email":"dbjpanda@live.com"}],"homepage":"https://github.com/dbjpanda/convex-authz#readme","bugs":{"url":"https://github.com/dbjpanda/convex-authz/issues"},"dist":{"shasum":"525e629e9bf34354d0f48355d1bcd8c9b9cae68d","tarball":"https://registry.npmjs.org/@djpanda/convex-authz/-/convex-authz-2.3.0.tgz","fileCount":114,"integrity":"sha512-88ox5uodDQUTuV10r40wtle8Ys3OxQbvzYqJFH2gh+pHX6UyPAovO0EobvlGMnl3jgPwPzdlKBacKIItZAh2tw==","signatures":[{"sig":"MEYCIQDlvTXNGkDCx4ze8lskN5MNw7TXaKKQplLVPDKk5dRvqAIhAPt0Llj8zF7OU/3+E+kEXpKCPpij81JwzpOGD6XlxdvQ","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@djpanda%2fconvex-authz@2.3.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":1345879},"type":"module","types":"./dist/client/index.d.ts","module":"./dist/client/index.js","exports":{".":{"types":"./dist/client/index.d.ts","default":"./dist/client/index.js"},"./test":"./src/test.ts","./react":{"types":"./dist/react/index.d.ts","default":"./dist/react/index.js"},"./package.json":"./package.json","./convex.config":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./convex.config.js":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./_generated/component":{"types":"./dist/component/_generated/component.d.ts"},"./_generated/component.js":{"types":"./dist/component/_generated/component.d.ts"}},"gitHead":"a83c062fc263c597bd550b578136c65d2d37591f","scripts":{"all":"run-p -r 'dev:*' 'test:watch'","dev":"run-p -r 'dev:*'","lint":"eslint .","test":"vitest run --typecheck","build":"tsc --project ./tsconfig.build.json","predev":"path-exists .env.local dist || (npm run build && convex dev --once)","dev:build":"chokidar 'tsconfig*.json' 'src/**/*.ts' -i '**/*.test.ts' -c 'npm run build:codegen' --initial","typecheck":"tsc --noEmit && tsc -p example && tsc -p example/convex","test:debug":"vitest --inspect-brk --no-file-parallelism","test:watch":"vitest --typecheck --clearScreen false","build:clean":"rm -rf dist *.tsbuildinfo && npm run build:codegen","dev:backend":"convex dev --typecheck-components","dev:frontend":"cd example && vite --clearScreen false","build:codegen":"npx convex codegen --component-dir ./src/component && npm run build","test:coverage":"vitest run --coverage --coverage.reporter=text"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:f59aede5-a33f-444c-8f09-83b76551c53a"}},"repository":{"url":"git+https://github.com/dbjpanda/convex-authz.git","type":"git"},"_npmVersion":"11.11.0","description":"A comprehensive RBAC/ABAC/ReBAC authorization component for Convex with O(1) indexed lookups, inspired by Google Zanzibar","directories":{},"_nodeVersion":"24.14.1","dependencies":{"@convex-dev/crons":"^0.2.0","@testing-library/dom":"^10.4.1"},"_hasShrinkwrap":false,"devDependencies":{"clsx":"^2.1.1","vite":"8.0.8","jsdom":"^29.0.2","react":"^19.2.5","convex":"1.35.1","eslint":"10.2.0","vitest":"4.1.4","cpy-cli":"^7.0.0","globals":"^17.5.0","prettier":"3.8.2","react-dom":"^19.2.5","@eslint/js":"10.0.1","pkg-pr-new":"^0.0.66","typescript":"6.0.2","@types/node":"^25.6.0","convex-test":"0.0.47","tailwindcss":"^4.2.2","@types/react":"^19.2.14","chokidar-cli":"3.0.0","lucide-react":"^1.8.0","npm-run-all2":"8.0.4","tailwind-merge":"^3.5.0","path-exists-cli":"2.0.0","@edge-runtime/vm":"^5.0.0","@eslint/eslintrc":"^3.3.5","@types/react-dom":"^19.2.3","@tailwindcss/vite":"^4.2.2","typescript-eslint":"8.58.1","@vitest/coverage-v8":"^4.1.4","eslint-plugin-react":"^7.37.5","@radix-ui/react-slot":"^1.2.4","@vitejs/plugin-react":"^6.0.1","@testing-library/react":"^16.0.0","class-variance-authority":"^0.7.1","@convex-dev/eslint-plugin":"^2.0.0","eslint-plugin-react-hooks":"^7.0.1","eslint-plugin-react-refresh":"^0.5.2"},"peerDependencies":{"react":"^18.3.1 || ^19.0.0","convex":"^1.29.3"},"_npmOperationalInternal":{"tmp":"tmp/convex-authz_2.3.0_1777918642244_0.4995210825543541","host":"s3://npm-registry-packages-npm-production"}},"2.3.1":{"name":"@djpanda/convex-authz","version":"2.3.1","keywords":["convex","component","authorization","rbac","abac","rebac","zanzibar","permissions","access-control"],"author":{"name":"djpanda"},"license":"Apache-2.0","_id":"@djpanda/convex-authz@2.3.1","maintainers":[{"name":"djpanda","email":"dbjpanda@live.com"}],"homepage":"https://github.com/dbjpanda/convex-authz#readme","bugs":{"url":"https://github.com/dbjpanda/convex-authz/issues"},"dist":{"shasum":"2053a8f2296805a17c71b67d04f3ea8e9c3d777b","tarball":"https://registry.npmjs.org/@djpanda/convex-authz/-/convex-authz-2.3.1.tgz","fileCount":114,"integrity":"sha512-A9PLyuySVta80KJE62XXaIAb8XjyPiAgAP79DQQZvTBRNVQD9A9qSWbzG1qMr0I1gk7uuODWNDTLpiHGGBRVGw==","signatures":[{"sig":"MEQCIDnl6PtApL/vhMcF/JS92/cl83p/8fjPT8aCeqH5KRrLAiACXHi0mzo62J8H8WgHg1wOWByxSadg39JXnpiGRf3Xgg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@djpanda%2fconvex-authz@2.3.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":1347242},"type":"module","types":"./dist/client/index.d.ts","module":"./dist/client/index.js","exports":{".":{"types":"./dist/client/index.d.ts","default":"./dist/client/index.js"},"./test":"./src/test.ts","./react":{"types":"./dist/react/index.d.ts","default":"./dist/react/index.js"},"./package.json":"./package.json","./convex.config":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./convex.config.js":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./_generated/component":{"types":"./dist/component/_generated/component.d.ts"},"./_generated/component.js":{"types":"./dist/component/_generated/component.d.ts"}},"gitHead":"cd7d1ab06c37a1e81e397ff734d437e5db6be758","scripts":{"all":"run-p -r 'dev:*' 'test:watch'","dev":"run-p -r 'dev:*'","lint":"eslint .","test":"vitest run --typecheck","build":"tsc --project ./tsconfig.build.json","predev":"path-exists .env.local dist || (npm run build && convex dev --once)","dev:build":"chokidar 'tsconfig*.json' 'src/**/*.ts' -i '**/*.test.ts' -c 'npm run build:codegen' --initial","typecheck":"tsc --noEmit && tsc -p example && tsc -p example/convex","test:debug":"vitest --inspect-brk --no-file-parallelism","test:watch":"vitest --typecheck --clearScreen false","build:clean":"rm -rf dist *.tsbuildinfo && npm run build:codegen","dev:backend":"convex dev --typecheck-components","dev:frontend":"cd example && vite --clearScreen false","build:codegen":"npx convex codegen --component-dir ./src/component && npm run build","test:coverage":"vitest run --coverage --coverage.reporter=text"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:f59aede5-a33f-444c-8f09-83b76551c53a"}},"repository":{"url":"git+https://github.com/dbjpanda/convex-authz.git","type":"git"},"_npmVersion":"11.11.0","description":"A comprehensive RBAC/ABAC/ReBAC authorization component for Convex with O(1) indexed lookups, inspired by Google Zanzibar","directories":{},"_nodeVersion":"24.14.1","dependencies":{"@convex-dev/crons":"^0.2.0","@testing-library/dom":"^10.4.1"},"_hasShrinkwrap":false,"devDependencies":{"clsx":"^2.1.1","vite":"8.0.8","jsdom":"^29.0.2","react":"^19.2.5","convex":"1.35.1","eslint":"10.2.0","vitest":"4.1.4","cpy-cli":"^7.0.0","globals":"^17.5.0","prettier":"3.8.2","react-dom":"^19.2.5","@eslint/js":"10.0.1","pkg-pr-new":"^0.0.66","typescript":"6.0.2","@types/node":"^25.6.0","convex-test":"0.0.47","tailwindcss":"^4.2.2","@types/react":"^19.2.14","chokidar-cli":"3.0.0","lucide-react":"^1.8.0","npm-run-all2":"8.0.4","tailwind-merge":"^3.5.0","path-exists-cli":"2.0.0","@edge-runtime/vm":"^5.0.0","@eslint/eslintrc":"^3.3.5","@types/react-dom":"^19.2.3","@tailwindcss/vite":"^4.2.2","typescript-eslint":"8.58.1","@vitest/coverage-v8":"^4.1.4","eslint-plugin-react":"^7.37.5","@radix-ui/react-slot":"^1.2.4","@vitejs/plugin-react":"^6.0.1","@testing-library/react":"^16.0.0","class-variance-authority":"^0.7.1","@convex-dev/eslint-plugin":"^2.0.0","eslint-plugin-react-hooks":"^7.0.1","eslint-plugin-react-refresh":"^0.5.2"},"peerDependencies":{"react":"^18.3.1 || ^19.0.0","convex":"^1.29.3"},"_npmOperationalInternal":{"tmp":"tmp/convex-authz_2.3.1_1777983188377_0.10170515155273208","host":"s3://npm-registry-packages-npm-production"}},"2.3.2":{"name":"@djpanda/convex-authz","version":"2.3.2","keywords":["convex","component","authorization","rbac","abac","rebac","zanzibar","permissions","access-control"],"author":{"name":"djpanda"},"license":"Apache-2.0","_id":"@djpanda/convex-authz@2.3.2","maintainers":[{"name":"djpanda","email":"dbjpanda@live.com"}],"homepage":"https://github.com/dbjpanda/convex-authz#readme","bugs":{"url":"https://github.com/dbjpanda/convex-authz/issues"},"dist":{"shasum":"2ed5c2d0f56b13eebc2b7aa8e934416961e2e9aa","tarball":"https://registry.npmjs.org/@djpanda/convex-authz/-/convex-authz-2.3.2.tgz","fileCount":114,"integrity":"sha512-rQKN0gK7sBh0PVufY32aozfzmg9jhrBNX27tjyI9xUxEKq6mLjmFwt70YSby7uGv1xqwYmJ2GYNYeHCgxvxhgw==","signatures":[{"sig":"MEQCIG5viC5AnMlJnK/VR7pjDLHWuCb6p46uLvD/Rq6QgWHYAiBtHMdL0gGDxmuK4JUL/M5Klgmie8a24ccOaKCou1RN2Q==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@djpanda%2fconvex-authz@2.3.2","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":1348093},"type":"module","types":"./dist/client/index.d.ts","module":"./dist/client/index.js","exports":{".":{"types":"./dist/client/index.d.ts","default":"./dist/client/index.js"},"./test":"./src/test.ts","./react":{"types":"./dist/react/index.d.ts","default":"./dist/react/index.js"},"./package.json":"./package.json","./convex.config":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./convex.config.js":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./_generated/component":{"types":"./dist/component/_generated/component.d.ts"},"./_generated/component.js":{"types":"./dist/component/_generated/component.d.ts"}},"gitHead":"b23e2a719f5882b2094d773588394508929258ad","scripts":{"all":"run-p -r 'dev:*' 'test:watch'","dev":"run-p -r 'dev:*'","lint":"eslint .","test":"vitest run --typecheck","build":"tsc --project ./tsconfig.build.json","predev":"path-exists .env.local dist || (npm run build && convex dev --once)","dev:build":"chokidar 'tsconfig*.json' 'src/**/*.ts' -i '**/*.test.ts' -c 'npm run build:codegen' --initial","typecheck":"tsc --noEmit && tsc -p example && tsc -p example/convex","test:debug":"vitest --inspect-brk --no-file-parallelism","test:watch":"vitest --typecheck --clearScreen false","build:clean":"rm -rf dist *.tsbuildinfo && npm run build:codegen","dev:backend":"convex dev --typecheck-components","dev:frontend":"cd example && vite --clearScreen false","build:codegen":"npx convex codegen --component-dir ./src/component && npm run build","test:coverage":"vitest run --coverage --coverage.reporter=text"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:f59aede5-a33f-444c-8f09-83b76551c53a"}},"repository":{"url":"git+https://github.com/dbjpanda/convex-authz.git","type":"git"},"_npmVersion":"11.11.0","description":"A comprehensive RBAC/ABAC/ReBAC authorization component for Convex with O(1) indexed lookups, inspired by Google Zanzibar","directories":{},"_nodeVersion":"24.14.1","dependencies":{"convex-helpers":"^0.1.116","@convex-dev/crons":"^0.2.0","@testing-library/dom":"^10.4.1"},"_hasShrinkwrap":false,"devDependencies":{"clsx":"^2.1.1","vite":"8.0.8","jsdom":"^29.0.2","react":"^19.2.5","convex":"1.35.1","eslint":"10.2.0","vitest":"4.1.4","cpy-cli":"^7.0.0","globals":"^17.5.0","prettier":"3.8.2","react-dom":"^19.2.5","@eslint/js":"10.0.1","pkg-pr-new":"^0.0.66","typescript":"6.0.2","@types/node":"^25.6.0","convex-test":"0.0.47","tailwindcss":"^4.2.2","@types/react":"^19.2.14","chokidar-cli":"3.0.0","lucide-react":"^1.8.0","npm-run-all2":"8.0.4","tailwind-merge":"^3.5.0","path-exists-cli":"2.0.0","@edge-runtime/vm":"^5.0.0","@eslint/eslintrc":"^3.3.5","@types/react-dom":"^19.2.3","@tailwindcss/vite":"^4.2.2","typescript-eslint":"8.58.1","@vitest/coverage-v8":"^4.1.4","eslint-plugin-react":"^7.37.5","@radix-ui/react-slot":"^1.2.4","@vitejs/plugin-react":"^6.0.1","@testing-library/react":"^16.0.0","class-variance-authority":"^0.7.1","@convex-dev/eslint-plugin":"^2.0.0","eslint-plugin-react-hooks":"^7.0.1","eslint-plugin-react-refresh":"^0.5.2"},"peerDependencies":{"react":"^18.3.1 || ^19.0.0","convex":"^1.29.3"},"_npmOperationalInternal":{"tmp":"tmp/convex-authz_2.3.2_1778264420819_0.1850542004836575","host":"s3://npm-registry-packages-npm-production"}},"2.4.0":{"name":"@djpanda/convex-authz","version":"2.4.0","keywords":["convex","component","authorization","rbac","abac","rebac","zanzibar","permissions","access-control"],"author":{"name":"djpanda"},"license":"Apache-2.0","_id":"@djpanda/convex-authz@2.4.0","maintainers":[{"name":"djpanda","email":"dbjpanda@live.com"}],"homepage":"https://github.com/dbjpanda/convex-authz#readme","bugs":{"url":"https://github.com/dbjpanda/convex-authz/issues"},"dist":{"shasum":"259cf10d06d32b6dbf5e061ce508ee87fb2047a7","tarball":"https://registry.npmjs.org/@djpanda/convex-authz/-/convex-authz-2.4.0.tgz","fileCount":125,"integrity":"sha512-aJCHBS4QUlza0XplvKpYhUawWoq0WIE3CKACae79S4W+3Y17hBwV5oUXRVKq6lJ5oB49EFsdKKvuAykWcrDsmg==","signatures":[{"sig":"MEQCIEepyCqHJFDdgKaLHG9vQ5xabiLj3wQpexuvUMyr8cj0AiBVTlmOFJLUvw0jLBfs8F6lIrNlBjgnstk2ni0UIm3aUA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@djpanda%2fconvex-authz@2.4.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":1569903},"type":"module","types":"./dist/client/index.d.ts","module":"./dist/client/index.js","exports":{".":{"types":"./dist/client/index.d.ts","default":"./dist/client/index.js"},"./test":"./src/test.ts","./react":{"types":"./dist/react/index.d.ts","default":"./dist/react/index.js"},"./package.json":"./package.json","./convex.config":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./convex.config.js":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./_generated/component":{"types":"./dist/component/_generated/component.d.ts"},"./_generated/component.js":{"types":"./dist/component/_generated/component.d.ts"}},"gitHead":"026709965638787115261029b8be2c89e880b959","scripts":{"all":"run-p -r 'dev:*' 'test:watch'","dev":"run-p -r 'dev:*'","lint":"eslint .","test":"vitest run --typecheck","build":"tsc --project ./tsconfig.build.json","predev":"path-exists .env.local dist || (npm run build && convex dev --once)","dev:build":"chokidar 'tsconfig*.json' 'src/**/*.ts' -i '**/*.test.ts' -c 'npm run build:codegen' --initial","typecheck":"tsc --noEmit && tsc -p example && tsc -p example/convex","test:debug":"vitest --inspect-brk --no-file-parallelism","test:watch":"vitest --typecheck --clearScreen false","build:clean":"rm -rf dist *.tsbuildinfo && npm run build:codegen","dev:backend":"convex dev --typecheck-components","dev:frontend":"cd example && vite --clearScreen false","build:codegen":"npx convex codegen --component-dir ./src/component && npm run build","test:coverage":"vitest run --coverage --coverage.reporter=text"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:f59aede5-a33f-444c-8f09-83b76551c53a"}},"repository":{"url":"git+https://github.com/dbjpanda/convex-authz.git","type":"git"},"_npmVersion":"11.11.0","description":"A comprehensive RBAC/ABAC/ReBAC authorization component for Convex with O(1) indexed lookups, inspired by Google Zanzibar","directories":{},"_nodeVersion":"24.14.1","dependencies":{"convex-helpers":"^0.1.116","@convex-dev/crons":"^0.2.0","@testing-library/dom":"^10.4.1"},"_hasShrinkwrap":false,"devDependencies":{"clsx":"^2.1.1","vite":"8.0.8","jsdom":"^29.0.2","react":"^19.2.5","convex":"1.35.1","eslint":"10.2.0","vitest":"4.1.4","cpy-cli":"^7.0.0","globals":"^17.5.0","prettier":"3.8.2","react-dom":"^19.2.5","@eslint/js":"10.0.1","pkg-pr-new":"^0.0.66","typescript":"6.0.2","@types/node":"^25.6.0","convex-test":"0.0.47","tailwindcss":"^4.2.2","@types/react":"^19.2.14","chokidar-cli":"3.0.0","lucide-react":"^1.8.0","npm-run-all2":"8.0.4","tailwind-merge":"^3.5.0","path-exists-cli":"2.0.0","@edge-runtime/vm":"^5.0.0","@eslint/eslintrc":"^3.3.5","@types/react-dom":"^19.2.3","@tailwindcss/vite":"^4.2.2","typescript-eslint":"8.58.1","@vitest/coverage-v8":"^4.1.4","eslint-plugin-react":"^7.37.5","@radix-ui/react-slot":"^1.2.4","@vitejs/plugin-react":"^6.0.1","@testing-library/react":"^16.0.0","class-variance-authority":"^0.7.1","@convex-dev/eslint-plugin":"^2.0.0","eslint-plugin-react-hooks":"^7.0.1","eslint-plugin-react-refresh":"^0.5.2"},"peerDependencies":{"react":"^18.3.1 || ^19.0.0","convex":"^1.29.3"},"_npmOperationalInternal":{"tmp":"tmp/convex-authz_2.4.0_1778358939623_0.1703797336093893","host":"s3://npm-registry-packages-npm-production"}},"2.4.1":{"name":"@djpanda/convex-authz","description":"A comprehensive RBAC/ABAC/ReBAC authorization component for Convex with O(1) indexed lookups, inspired by Google Zanzibar","repository":{"type":"git","url":"git+https://github.com/dbjpanda/convex-authz.git"},"homepage":"https://github.com/dbjpanda/convex-authz#readme","bugs":{"url":"https://github.com/dbjpanda/convex-authz/issues"},"version":"2.4.1","license":"Apache-2.0","author":{"name":"djpanda"},"keywords":["convex","component","authorization","rbac","abac","rebac","zanzibar","permissions","access-control"],"type":"module","scripts":{"dev":"run-p -r 'dev:*'","dev:backend":"convex dev --typecheck-components","dev:frontend":"cd example && vite --clearScreen false","dev:build":"chokidar 'tsconfig*.json' 'src/**/*.ts' -i '**/*.test.ts' -c 'npm run build:codegen' --initial","predev":"path-exists .env.local dist || (npm run build && convex dev --once)","build":"tsc --project ./tsconfig.build.json","build:codegen":"npx convex codegen --component-dir ./src/component && npm run build","build:clean":"rm -rf dist *.tsbuildinfo && npm run build:codegen","typecheck":"tsc --noEmit && tsc -p example && tsc -p example/convex","lint":"eslint .","all":"run-p -r 'dev:*' 'test:watch'","test":"vitest run --typecheck","test:watch":"vitest --typecheck --clearScreen false","test:debug":"vitest --inspect-brk --no-file-parallelism","test:coverage":"vitest run --coverage --coverage.reporter=text"},"exports":{"./package.json":"./package.json",".":{"types":"./dist/client/index.d.ts","default":"./dist/client/index.js"},"./react":{"types":"./dist/react/index.d.ts","default":"./dist/react/index.js"},"./test":"./src/test.ts","./_generated/component.js":{"types":"./dist/component/_generated/component.d.ts"},"./_generated/component":{"types":"./dist/component/_generated/component.d.ts"},"./convex.config.js":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./convex.config":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"}},"peerDependencies":{"convex":"^1.29.3","react":"^18.3.1 || ^19.0.0"},"devDependencies":{"@convex-dev/eslint-plugin":"^2.0.0","@edge-runtime/vm":"^5.0.0","@eslint/eslintrc":"^3.3.5","@eslint/js":"10.0.1","@radix-ui/react-slot":"^1.2.4","@tailwindcss/vite":"^4.2.2","@testing-library/react":"^16.0.0","@types/node":"^25.6.0","@types/react":"^19.2.14","@types/react-dom":"^19.2.3","@vitejs/plugin-react":"^6.0.1","@vitest/coverage-v8":"^4.1.4","chokidar-cli":"3.0.0","class-variance-authority":"^0.7.1","clsx":"^2.1.1","convex":"1.35.1","convex-test":"0.0.47","cpy-cli":"^7.0.0","eslint":"10.2.0","eslint-plugin-react":"^7.37.5","eslint-plugin-react-hooks":"^7.0.1","eslint-plugin-react-refresh":"^0.5.2","globals":"^17.5.0","jsdom":"^29.0.2","lucide-react":"^1.8.0","npm-run-all2":"8.0.4","path-exists-cli":"2.0.0","pkg-pr-new":"^0.0.66","prettier":"3.8.2","react":"^19.2.5","react-dom":"^19.2.5","tailwind-merge":"^3.5.0","tailwindcss":"^4.2.2","typescript":"6.0.2","typescript-eslint":"8.58.1","vite":"8.0.8","vitest":"4.1.4"},"types":"./dist/client/index.d.ts","module":"./dist/client/index.js","dependencies":{"@convex-dev/crons":"^0.2.0","@testing-library/dom":"^10.4.1","convex-helpers":"^0.1.116"},"gitHead":"0717b7fbc0c0a6760261f454ba8000124a670019","_id":"@djpanda/convex-authz@2.4.1","_nodeVersion":"24.16.0","_npmVersion":"11.13.0","dist":{"integrity":"sha512-+nqEztMuTBBdCgDTxGo6f4/d/9/WUqvY1INU6gzn3nK1B8LmmyaTqIlrj1FtmMdG2kwRtNxnQordDAQLhuRtxw==","shasum":"28c035ac3d307f95d9e689d6f24b9f1f8e73e401","tarball":"https://registry.npmjs.org/@djpanda/convex-authz/-/convex-authz-2.4.1.tgz","fileCount":125,"unpackedSize":1571234,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@djpanda%2fconvex-authz@2.4.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIGL0ic609WPdSsdVEi/MRFhvfYPNKH8tedBavHWxTpd+AiEAkekjvctkrPmkkYTUDLZG7vIm3bcg75+13ZFaoKB9qvQ="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:f59aede5-a33f-444c-8f09-83b76551c53a"}},"directories":{},"maintainers":[{"name":"djpanda","email":"dbjpanda@live.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/convex-authz_2.4.1_1781886126752_0.8609678894925676"},"_hasShrinkwrap":false}},"time":{"created":"2026-01-08T19:36:35.509Z","modified":"2026-06-19T16:22:07.217Z","0.1.1":"2026-01-08T19:36:35.771Z","0.1.3":"2026-01-08T19:54:06.603Z","0.1.4":"2026-01-28T21:12:22.434Z","0.1.7":"2026-02-09T20:51:06.177Z","2.0.0":"2026-03-24T15:11:55.581Z","2.1.0":"2026-03-24T17:09:09.921Z","2.1.1":"2026-03-24T17:31:25.732Z","2.2.0":"2026-05-03T22:42:27.370Z","2.3.0":"2026-05-04T18:17:22.550Z","2.3.1":"2026-05-05T12:13:08.594Z","2.3.2":"2026-05-08T18:20:21.023Z","2.4.0":"2026-05-09T20:35:39.847Z","2.4.1":"2026-06-19T16:22:06.937Z"},"bugs":{"url":"https://github.com/dbjpanda/convex-authz/issues"},"author":{"name":"djpanda"},"license":"Apache-2.0","homepage":"https://github.com/dbjpanda/convex-authz#readme","keywords":["convex","component","authorization","rbac","abac","rebac","zanzibar","permissions","access-control"],"repository":{"type":"git","url":"git+https://github.com/dbjpanda/convex-authz.git"},"description":"A comprehensive RBAC/ABAC/ReBAC authorization component for Convex with O(1) indexed lookups, inspired by Google Zanzibar","maintainers":[{"name":"djpanda","email":"dbjpanda@live.com"}],"readme":"# @djpanda/convex-authz\n\nA comprehensive, production-ready authorization component for [Convex](https://convex.dev) featuring **RBAC**, **ABAC**, and **ReBAC** with **O(1) indexed lookups**, inspired by [Google Zanzibar](https://research.google/pubs/pub48190/).\n\n[![npm version](https://badge.fury.io/js/@djpanda%2Fconvex-authz.svg)](https://www.npmjs.com/package/@djpanda/convex-authz)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![Convex Approved](https://www.convex.dev/components/badge/djpanda/convex-tenants)](https://www.convex.dev/components/djpanda/convex-tenants)\n\n## Features\n\n\n| Feature                | Description                                            |\n| ---------------------- | ------------------------------------------------------ |\n| **RBAC**               | Role-Based Access Control with scoped roles            |\n| **ABAC**               | Attribute-Based Access Control with custom policies    |\n| **ReBAC**              | Relationship-Based Access Control with graph traversal |\n| **O(1) Lookups**       | Pre-computed permissions for instant checks            |\n| **Type Safety**        | Full TypeScript support with type-safe permissions     |\n| **Audit Logging**      | Track all permission changes and checks                |\n| **Scoped Permissions** | Resource-level access control                          |\n| **Expiring Grants**    | Time-limited role assignments and permissions          |\n| **Convex Native**      | Built specifically for Convex, with real-time updates  |\n\n\n## Terminology\n\n\n| Term                    | Definition                                                                                                     |\n| ----------------------- | -------------------------------------------------------------------------------------------------------------- |\n| **RBAC**                | Role-Based Access Control - permissions assigned via roles (admin, editor, viewer)                             |\n| **ABAC**                | Attribute-Based Access Control - permissions based on user/resource attributes (department=engineering)        |\n| **ReBAC**               | Relationship-Based Access Control - permissions derived from relationships (member of team that owns resource) |\n| **Zanzibar**            | Google's global authorization system, inspiration for OpenFGA and this component                               |\n| **Tuple**               | A relationship triple: `(subject, relation, object)` e.g., `(user:alice, member, team:sales)`                  |\n| **Scope**               | Resource-level permission context, e.g., \"admin of team:123\" vs global \"admin\"                                 |\n| **Traversal**           | Following relationship chains to determine inherited access                                                    |\n| **O(1) Lookup**         | Constant-time permission check via pre-computed indexes                                                        |\n| **Permission Override** | Direct grant/deny that bypasses role-based permissions                                                         |\n\n\n---\n\n## Installation\n\n```bash\nnpm install @djpanda/convex-authz\n```\n\n---\n\n## Quick Start\n\n### 1. Register the Component\n\n```typescript\n// convex/convex.config.ts\nimport { defineApp } from \"convex/server\";\nimport authz from \"@djpanda/convex-authz/convex.config\";\n\nconst app = defineApp();\napp.use(authz);\n\nexport default app;\n```\n\n### 2. Define Your Permissions and Roles\n\n```typescript\n// convex/authz.ts\nimport { Authz, definePermissions, defineRoles } from \"@djpanda/convex-authz\";\nimport { components } from \"./_generated/api\";\n\n// Step 1: Define permissions\nconst permissions = definePermissions({\n  documents: {\n    create: true,\n    read: true,\n    update: true,\n    delete: true,\n  },\n  settings: {\n    view: true,\n    manage: true,\n  },\n});\n\n// Step 2: Define roles\nconst roles = defineRoles(permissions, {\n  admin: {\n    documents: [\"create\", \"read\", \"update\", \"delete\"],\n    settings: [\"view\", \"manage\"],\n  },\n  editor: {\n    documents: [\"create\", \"read\", \"update\"],\n    settings: [\"view\"],\n  },\n  viewer: {\n    documents: [\"read\"],\n  },\n});\n\n// Step 3: Create the authz client\nexport const authz = new Authz(components.authz, { permissions, roles, tenantId: \"my-app\" });\n```\n\n#### Role inheritance and composition\n\nRoles can be defined in terms of other roles to avoid repeating permission lists:\n\n- `**inherits**` – one parent role; effective permissions = parent’s permissions ∪ this role’s direct permissions.\n- `**includes**` – multiple roles; effective permissions = union of all included roles’ permissions ∪ this role’s direct permissions.\n\nExample with inheritance (admin > editor > viewer):\n\n```typescript\nconst roles = defineRoles(permissions, {\n  viewer: { documents: [\"read\"] },\n  editor: { inherits: \"viewer\", documents: [\"create\", \"update\"] },\n  admin: { inherits: \"editor\", documents: [\"delete\"], settings: [\"manage\"] },\n});\n```\n\nExample with composition (combine roles):\n\n```typescript\nconst roles = defineRoles(permissions, {\n  editor: { documents: [\"create\", \"read\", \"update\"] },\n  billing_admin: { billing: [\"view\", \"manage\"] },\n  billing_manager: { includes: [\"editor\", \"billing_admin\"], settings: [\"view\"] },\n});\n```\n\n**Note:** `inherits` and `includes` are reserved keys in role definitions; do not use them as permission resource names.\n\n### 3. Use in Your Functions\n\n```typescript\n// convex/documents.ts\nimport { mutation, query } from \"./_generated/server\";\nimport { v } from \"convex/values\";\nimport { authz } from \"./authz\";\nimport { getAuthUserId } from \"@convex-dev/auth/server\";\n\nexport const updateDocument = mutation({\n  args: { docId: v.id(\"documents\"), content: v.string() },\n  handler: async (ctx, args) => {\n    const userId = await getAuthUserId(ctx);\n    \n    // Check permission (throws if denied)\n    await authz.require(ctx, userId, \"documents:update\");\n    \n    // Or with scope\n    await authz.require(ctx, userId, \"documents:update\", {\n      type: \"document\",\n      id: args.docId,\n    });\n    \n    // Proceed with update...\n  },\n});\n```\n\n---\n\n## Unified Authz v2\n\nv2 consolidates everything into a single `Authz` class. If you previously used `IndexedAuthz`, just rename it — the constructor signature is identical.\n\n### What changed\n\n- **One class**: `Authz` replaces both the original `Authz` and `IndexedAuthz`. O(1) reads via pre-computed effective tables are now the default.\n- **ReBAC on `Authz`**: `hasRelation`, `addRelation`, and `removeRelation` are available directly on every `Authz` instance.\n- **ABAC policy types**: Policies accept a `type` field (`\"static\"` or `\"deferred\"`). In the current implementation, both types are evaluated at read-time when `can()` is called — Convex mutations cannot call queries, so write-time evaluation is not possible. The `type` field is reserved for future optimization but currently has no behavioral difference.\n- `**canWithContext()`**: Check deferred ABAC policies that need runtime context (e.g. IP address, time of day).\n- `**recomputeUser()**`: Rebuild a user's effective-permissions table on demand — useful after a schema change or post-deploy migration.\n- `**withTenant()**`: Return a scoped copy of the client bound to a specific tenantId. This is the primary tenant-routing primitive when a single `Authz` is shared across tenants (e.g. a module-scope instance in an integration package) — call it on every operation that targets a particular tenant. Also covers cross-tenant admin operations.\n\n### ReBAC example\n\n```typescript\n// Add a relationship\nawait authz.addRelation(ctx, { type: \"user\", id: userId }, \"member\", { type: \"team\", id: teamId });\n\n// Check a relationship\nconst isMember = await authz.hasRelation(ctx, { type: \"user\", id: userId }, \"member\", { type: \"team\", id: teamId });\n\n// Remove a relationship\nawait authz.removeRelation(ctx, { type: \"user\", id: userId }, \"member\", { type: \"team\", id: teamId });\n```\n\n### ReBAC → Permission Bridge\n\nUse `defineRelationPermissions` to automatically grant permissions when relationships are created:\n\n```typescript\nimport { defineRelationPermissions } from \"@djpanda/convex-authz\";\n\nconst authz = new Authz(components.authz, {\n  permissions, roles, tenantId: \"my-app\",\n  relationPermissions: defineRelationPermissions({\n    \"document:viewer\": [\"documents:read\"],\n    \"document:editor\": [\"documents:read\", \"documents:update\"],\n    \"document:owner\": [\"documents:read\", \"documents:update\", \"documents:delete\"],\n  }),\n});\n\n// Adding a relation automatically grants scoped permissions\nawait authz.addRelation(ctx, { type: \"user\", id: userId }, \"editor\", { type: \"document\", id: docId });\n\n// can() now checks relation-derived permissions — no separate hasRelation() needed\nconst canUpdate = await authz.can(ctx, userId, \"documents:update\", { type: \"document\", id: docId }); // true\n\n// Removing the relation revokes the permissions\nawait authz.removeRelation(ctx, { type: \"user\", id: userId }, \"editor\", { type: \"document\", id: docId });\n```\n\n### ABAC example\n\n```typescript\n// Policies are always evaluated at read-time when can() is called.\n// The optional type field (\"static\" | \"deferred\") is reserved for future use\n// and currently has no behavioral effect.\nconst policies = definePolicies({\n  \"documents:read\": {\n    condition: (ctx) => ctx.getAttribute(\"verified\") === true,\n    message: \"Only verified users can read documents\",\n  },\n  \"billing:export\": {\n    condition: (ctx) => {\n      const hour = new Date().getUTCHours();\n      return hour >= 9 && hour <= 17;\n    },\n    message: \"Billing exports only during business hours\",\n  },\n});\n\n// Use canWithContext() when request context is available\nconst allowed = await authz.canWithContext(ctx, userId, \"documents:read\", undefined, {\n  ipAllowlisted: true,\n});\n```\n\n### Post-deploy rebuild\n\n```typescript\n// Rebuild a single user's effective permissions after upgrading\nawait authz.recomputeUser(ctx, userId);\n```\n\n### Cross-tenant operations\n\n```typescript\nconst otherTenantAuthz = authz.withTenant(\"other-tenant-id\");\nconst allowed = await otherTenantAuthz.can(ctx, userId, \"documents:read\");\n```\n\n### Migration guide: `IndexedAuthz` → `Authz`\n\n> **Note:** `IndexedAuthz` is no longer exported in v2. The import below will fail — just replace it with `Authz`.\n\n```typescript\n// Before (v1) — this import no longer works in v2\n// import { IndexedAuthz } from \"@djpanda/convex-authz\";\n// const authz = new IndexedAuthz(components.authz, { permissions, roles, tenantId: \"my-app\" });\n\n// After (v2) — same constructor, just rename the class\nimport { Authz } from \"@djpanda/convex-authz\";\nconst authz = new Authz(components.authz, { permissions, roles, tenantId: \"my-app\" });\n```\n\nAfter upgrading, run `recomputeUser()` for each existing user to backfill the effective-permissions table:\n\n```typescript\n// one-time migration mutation\nexport const backfillEffectivePermissions = mutation({\n  args: {},\n  handler: async (ctx) => {\n    const users = await ctx.db.query(\"users\").collect();\n    for (const user of users) {\n      await authz.recomputeUser(ctx, String(user._id));\n    }\n  },\n});\n```\n\n---\n\n## React integration\n\nThe package provides React hooks and a `PermissionGate` component so your UI can check permissions and roles reactively. Your app must expose Convex queries that wrap the Authz component (e.g. `checkPermission`, `getUserRoles`). The hooks call those queries via Convex’s `useQuery`, so permission and role changes stay up to date without polling.\n\n### 1. Expose Convex queries\n\nDefine queries that delegate to your authz client, for example:\n\n```typescript\n// convex/app.ts (or similar)\nimport { query } from \"./_generated/server\";\nimport { v } from \"convex/values\";\nimport { authz } from \"./authz\";\n\nexport const checkPermission = query({\n  args: {\n    userId: v.string(),\n    permission: v.string(),\n    scope: v.optional(v.object({ type: v.string(), id: v.string() })),\n  },\n  handler: async (ctx, args) => {\n    return authz.can(ctx, args.userId, args.permission, args.scope);\n  },\n});\n\nexport const getUserRoles = query({\n  args: {\n    userId: v.string(),\n    scope: v.optional(v.object({ type: v.string(), id: v.string() })),\n  },\n  handler: async (ctx, args) => {\n    return authz.getUserRoles(ctx, args.userId, args.scope);\n  },\n});\n```\n\n### 2. Wrap your app with AuthzProvider\n\nPass your Convex query refs (and optionally a default user id) to the provider:\n\n```tsx\nimport { AuthzProvider } from \"@djpanda/convex-authz/react\";\nimport { api } from \"./convex/_generated/api\";\n\n<AuthzProvider\n  queryRefs={{\n    checkPermission: api.app.checkPermission,\n    getUserRoles: api.app.getUserRoles,\n  }}\n  defaultUserId={currentUserId}  // optional; hooks can pass userId in options\n>\n  <App />\n</AuthzProvider>\n```\n\n### 3. Use hooks and PermissionGate\n\n- **useCanUser(permission, options?)** — Returns `{ allowed, isLoading, error }`. Options: `{ userId?, scope? }`. Uses `defaultUserId` from the provider when `userId` is omitted.\n- **useUserRoles(options?)** — Returns `{ roles, isLoading, error }`. Options: `{ userId?, scope? }`.\n- **useRequirePermission(permission, options?)** — Throws when the user is not allowed (use an error boundary to show a denied state).\n- **PermissionGate** — Renders `children` when allowed, `fallback` when denied, and `loadingFallback` (optional) while loading.\n\n```tsx\nimport {\n  useCanUser,\n  useUserRoles,\n  useRequirePermission,\n  PermissionGate,\n} from \"@djpanda/convex-authz/react\";\n\nfunction DocumentList() {\n  const { allowed, isLoading } = useCanUser(\"documents:read\");\n\n  if (isLoading) return <Spinner />;\n  if (!allowed) return <p>You cannot view documents.</p>;\n  return <div>{/* list */}</div>;\n}\n\nfunction AdminPanel() {\n  useRequirePermission(\"settings:manage\"); // throws if denied; wrap in error boundary\n  return <div>Admin content</div>;\n}\n\nfunction EditButton({ docId }: { docId: string }) {\n  return (\n    <PermissionGate\n      permission=\"documents:update\"\n      scope={{ type: \"document\", id: docId }}\n      fallback={<span>No access</span>}\n      loadingFallback={<span>Checking…</span>}\n    >\n      <button>Edit</button>\n    </PermissionGate>\n  );\n}\n```\n\nConvex’s reactivity ensures that when permissions or roles change on the backend, the hooks and `PermissionGate` re-run and the UI updates automatically.\n\n---\n\n## Architecture\n\n```\n┌─────────────────────────────────────────────────────────────────────────────┐\n│                           @djpanda/convex-authz                             │\n├─────────────────────────────────────────────────────────────────────────────┤\n│                                                                             │\n│  ┌──────────────────┐  ┌──────────────────┐  ┌──────────────────────────┐   │\n│  │      RBAC        │  │      ABAC        │  │         ReBAC            │   │\n│  │  Role-Based      │  │  Attribute-Based │  │  Relationship-Based      │   │\n│  │  Access Control  │  │  Access Control  │  │  Access Control          │   │\n│  │                  │  │                  │  │                          │   │\n│  │  • Roles         │  │  • User attrs    │  │  • Tuples (S, R, O)      │   │\n│  │  • Permissions   │  │  • Policies      │  │  • Graph traversal       │   │\n│  │  • Scopes        │  │  • Conditions    │  │  • Inheritance           │   │\n│  └──────────────────┘  └──────────────────┘  └──────────────────────────┘   │\n│           │                    │                         │                  │\n│           ▼                    ▼                         ▼                  │\n│  ┌──────────────────────────────────────────────────────────────────────┐   │\n│  │                    O(1) Indexed Permission Cache                     │   │\n│  │                                                                      │   │\n│  │   effectivePermissions  │  effectiveRoles  │  effectiveRelationships │   │\n│  │   [user, perm, scope]   │  [user, role]    │  [subject, rel, object] │   │\n│  └──────────────────────────────────────────────────────────────────────┘   │\n│                                                                             │\n└─────────────────────────────────────────────────────────────────────────────┘\n```\n\n---\n\n## RBAC (Role-Based Access Control)\n\n### Assigning Roles\n\n```typescript\n// Global role\nawait authz.assignRole(ctx, userId, \"admin\");\n\n// Scoped role (e.g., admin of a specific team)\nawait authz.assignRole(ctx, userId, \"admin\", {\n  type: \"team\",\n  id: \"team_123\",\n});\n\n// With expiration (24 hours)\nawait authz.assignRole(ctx, userId, \"admin\", undefined, Date.now() + 86400000);\n```\n\n### Revoking Roles\n\n```typescript\nawait authz.revokeRole(ctx, userId, \"admin\");\n\n// Scoped\nawait authz.revokeRole(ctx, userId, \"admin\", { type: \"team\", id: \"team_123\" });\n```\n\n### Checking Permissions\n\n```typescript\n// Boolean check\nconst canEdit = await authz.can(ctx, userId, \"documents:update\");\n\n// Throws if denied\nawait authz.require(ctx, userId, \"documents:update\");\n\n// With scope\nconst canEditTeamDocs = await authz.can(ctx, userId, \"documents:update\", {\n  type: \"team\",\n  id: \"team_123\",\n});\n```\n\n### Checking Roles\n\n```typescript\nconst isAdmin = await authz.hasRole(ctx, userId, \"admin\");\n\n// Scoped\nconst isTeamAdmin = await authz.hasRole(ctx, userId, \"admin\", {\n  type: \"team\",\n  id: \"team_123\",\n});\n```\n\n### Wildcard and pattern-matching permissions\n\nPermission checks and overrides support **wildcard patterns** so you can grant or deny whole families of permissions in one go.\n\n**Pattern format:** `resource:action`. Either `resource` or `action` (or both) may be `*`:\n\n\n| Pattern       | Meaning                       | Example matches                      |\n| ------------- | ----------------------------- | ------------------------------------ |\n| `*`           | All permissions               | `documents:read`, `settings:manage`  |\n| `documents:`* | All actions on `documents`    | `documents:read`, `documents:update` |\n| `*:read`      | Read on any resource          | `documents:read`, `settings:read`    |\n| `*:*`         | All permissions (same as `*`) | any `resource:action`                |\n\n\n**Checking:** When you call `can(ctx, userId, \"documents:read\")` or `require(ctx, userId, \"documents:read\")`, the backend treats any stored role or override that *matches* that permission as granting it. So if the user has a role with `documents:`* or an override `*:read`, they are allowed for `documents:read`.\n\n**Allocation:** You can pass a pattern into `grantPermission` and `denyPermission`:\n\n```typescript\n// Grant all document actions\nawait authz.grantPermission(ctx, userId, \"documents:*\", undefined, \"Full document access\");\n\n// Deny read on any resource\nawait authz.denyPermission(ctx, userId, \"*:read\", undefined, \"Read access revoked\");\n\n// Remove the direct override and fall back to role/policy-derived access\nawait authz.removeOverride(ctx, userId, \"*:read\");\n```\n\n**Role definitions:** When the component evaluates permissions, it matches the requested permission against each role’s permission list using the same pattern rules. So if a role’s permissions include `\"documents:*\"` (in the flattened role–permission map), then `can(ctx, userId, \"documents:read\")` is allowed. With `defineRoles` you typically list concrete actions per resource (e.g. `documents: [\"read\", \"update\"]`); to use patterns in roles you would supply a role-permission map that includes pattern strings for that role.\n\n**Client-side helper:** To test whether a pattern matches a permission without calling the backend, use the exported helper:\n\n```typescript\nimport { matchesPermissionPattern } from \"@djpanda/convex-authz\";\n\nmatchesPermissionPattern(\"documents:read\", \"documents:*\"); // true\nmatchesPermissionPattern(\"documents:read\", \"*:read\");      // true\nmatchesPermissionPattern(\"settings:read\", \"documents:*\"); // false\n```\n\nThe same wildcard behavior applies to all permission checks.\n\n### Getting User Roles\n\n```typescript\nconst roles = await authz.getUserRoles(ctx, userId);\n// Returns: [{ role: \"admin\", scopeKey: \"global\" }, { role: \"editor\", scopeKey: \"team:123\", scope: { type: \"team\", id: \"123\" } }]\n```\n\n### Custom roles (tenant-defined, opt-in)\n\nFor B2B SaaS apps where tenant admins need to define their own role bundles\n(e.g. \"Senior Editor\", \"Approver\", \"Outside Counsel\"), enable the `customRoles`\noption on the `Authz` constructor. Tenants compose roles from a SaaS-provider-\ndefined whitelist of permissions — they cannot invent new permission strings\nat runtime, which keeps the type-safety win above intact and prevents\npermission-escalation footguns.\n\n#### Configure the whitelist\n\n```typescript\nconst authz = new Authz(components.authz, {\n  permissions,\n  roles,\n  tenantId: \"tenant-acme\",\n  customRoles: {\n    enabled: true,\n    grantablePermissions: [\n      \"documents:read\",\n      \"documents:update\",\n      \"documents:delete\",\n      \"settings:view\",\n    ] as const,                  // typed against PermissionArg<P> — typos rejected at compile time\n    maxRolesPerTenant: 50,        // optional, default 100\n  },\n});\n```\n\n#### Lifecycle\n\n```typescript\n// Tenant admin creates a custom role\nconst roleId = await authz.createCustomRole(ctx, {\n  name: \"Senior Editor\",\n  permissions: [\"documents:read\", \"documents:update\"],\n  description: \"Can edit but not delete\",\n  createdBy: currentUserId,\n});\n\n// Assign / revoke (same shape as system roles, but with the branded id)\nawait authz.assignCustomRole(ctx, userId, roleId);\nawait authz.revokeCustomRole(ctx, userId, roleId);\n\n// Permission checks are unchanged — `can()` doesn't care where a permission came from\nconst canEdit = await authz.can(ctx, userId, \"documents:update\");\n\n// Update propagates to every assigned user via recomputeUser\nconst result = await authz.updateCustomRole(ctx, {\n  customRoleId: roleId,\n  permissions: [\"documents:read\", \"documents:update\", \"documents:delete\"],\n});\n// → { permissionsChanged: true, usersRecomputed: 17 }\n\n// List / lookup\nconst page = await authz.listCustomRoles(ctx, { numItems: 50, cursor: null });\nconst role = await authz.getCustomRole(ctx, roleId);\nconst byName = await authz.getCustomRoleByName(ctx, \"Senior Editor\");\n\n// Delete (refuses if any user holds the role unless `force: true`)\nawait authz.deleteCustomRole(ctx, { customRoleId: roleId, force: true });\n```\n\n#### Design decisions worth knowing\n\n- **Composition only, never new permissions.** Tenant admins compose existing\n  permissions; the whitelist enforced at create/update time is the security\n  boundary.\n- **Branded `CustomRoleId`.** Cannot be confused with system role names\n  (`keyof R`) or with raw strings at compile time.\n- **Snapshot semantics from system roles.** A custom role's `permissions[]`\n  is a snapshot at create time. If the SaaS provider later updates a system\n  role definition, custom roles built around the old set do not auto-update —\n  preventing surprise breakage on redeploy.\n- **Cascade on definition update.** When a tenant admin edits a custom role's\n  permissions, every user holding it is re-materialized via `recomputeUser`.\n  Direct grants and direct denies on each user are preserved.\n- **Permission-check hot path is unchanged.** `can()` reads `effectivePermissions`\n  identically — custom-role-derived rows are indistinguishable from system-role-\n  derived rows. Zero performance impact for permission checks regardless of\n  whether the feature is enabled.\n\n---\n\n## Bulk operations and offboarding\n\nFor large-scale or enterprise workflows, the API supports bulk permission checks (up to 100 permissions) and role updates (up to 20 roles) in a single call.\n\n### Bulk permission check (canAny)\n\nCheck whether the user has **any** of the given permissions in one round-trip:\n\n```typescript\nconst allowed = await authz.canAny(ctx, userId, [\n  \"documents:read\",\n  \"documents:update\",\n  \"documents:delete\",\n], scope);\n// true if the user has at least one of these permissions\n```\n\n### Bulk role assign and revoke\n\nAssign or revoke multiple roles for one user in a **single transaction**:\n\n```typescript\n// Assign multiple roles at once (max 20 per call)\nconst { assigned, assignmentIds } = await authz.assignRoles(ctx, userId, [\n  { role: \"admin\" },\n  { role: \"editor\", scope: { type: \"team\", id: \"team_1\" } },\n  { role: \"viewer\", scope: { type: \"org\", id: \"org_1\" }, expiresAt: Date.now() + 86400000 },\n], actorId);\n\n// Revoke multiple roles at once (max 20 per call)\nconst { revoked } = await authz.revokeRoles(ctx, userId, [\n  { role: \"editor\", scope: { type: \"team\", id: \"team_1\" } },\n  { role: \"viewer\" },\n], actorId);\n```\n\n### Revoke all roles\n\nRevoke every role for a user (optionally only in a given scope):\n\n```typescript\nconst count = await authz.revokeAllRoles(ctx, userId);\nconst countScoped = await authz.revokeAllRoles(ctx, userId, { type: \"team\", id: \"team_1\" }, actorId);\n```\n\n### Full user offboarding\n\nRemove all roles, permission overrides, attributes, and optionally ReBAC relationships for a user in one call (optionally scoped). Also clears indexed `effectiveRoles`, `effectivePermissions`, and `effectiveRelationships` when present:\n\n```typescript\nconst result = await authz.offboardUser(ctx, userId, {\n  scope: { type: \"org\", id: \"org_1\" },  // optional: only remove data in this scope\n  actorId: \"system\",\n  removeAttributes: true,   // default true\n  removeOverrides: true,    // default true\n  removeRelationships: true, // default true when no scope (full offboard)\n});\n// result: { rolesRevoked, overridesRemoved, attributesRemoved, relationshipsRemoved, effectiveRolesRemoved, effectivePermissionsRemoved, effectiveRelationshipsRemoved }\n```\n\nWhen **scope is omitted**, the call performs a full deprovision: all roles, overrides, attributes, and all ReBAC relationships where the user is the subject are removed. When scope is provided, only data in that scope is removed and relationships are left unchanged.\n\n### User deprovisioning (full wipe)\n\nFor security incident response, enterprise offboarding, or single-button deactivation, use **deprovisionUser** to atomically wipe all roles, attributes, relationships, and permission overrides for a user (no scope, no options):\n\n```typescript\nconst result = await authz.deprovisionUser(ctx, userId, {\n  actorId: \"security-team\",\n  enableAudit: true,\n});\n// result: { rolesRevoked, overridesRemoved, attributesRemoved, relationshipsRemoved, effectiveRolesRemoved, effectivePermissionsRemoved, effectiveRelationshipsRemoved }\n```\n\nBulk arrays are limited per call: permissions in `canAny` up to **100 items**, roles in `assignRoles` / `revokeRoles` up to **20 items**. The client and component validate and throw a clear error if exceeded.\n\n---\n\n## ABAC (Attribute-Based Access Control)\n\n### Setting User Attributes\n\n```typescript\nawait authz.setAttribute(ctx, userId, \"department\", \"engineering\");\nawait authz.setAttribute(ctx, userId, \"clearanceLevel\", 5);\nawait authz.setAttribute(ctx, userId, \"location\", { country: \"US\", state: \"CA\" });\n```\n\n### Getting Attributes\n\n```typescript\nconst attributes = await authz.getUserAttributes(ctx, userId);\n// Returns: [{ key: \"department\", value: \"engineering\" }, { key: \"clearanceLevel\", value: 5 }]\n```\n\n### Defining Policies\n\nThe `condition` function may return either a `boolean` or a `Promise<boolean>`, so you can use async logic (e.g. querying the database or calling external APIs).\n\n```typescript\nimport { definePolicies, evaluatePolicyCondition } from \"@djpanda/convex-authz\";\n\nconst policies = definePolicies({\n  \"documents:update\": {\n    // User can update if they own the document (sync)\n    condition: (ctx) => ctx.resource?.ownerId === ctx.subject.userId,\n    message: \"Only document owners can update\",\n  },\n  \"reports:view\": {\n    // Only engineering department with clearance >= 3 (sync)\n    condition: (ctx) => \n      ctx.subject.attributes.department === \"engineering\" &&\n      (ctx.subject.attributes.clearanceLevel as number) >= 3,\n    message: \"Requires engineering department with clearance level 3+\",\n  },\n  \"documents:delete\": {\n    // Async: e.g. check external service or fetch extra data\n    condition: async (ctx) => {\n      const doc = await getDocument(ctx.resource?.id);\n      return doc != null && doc.ownerId === ctx.subject.userId;\n    },\n    message: \"Only document owners can delete\",\n  },\n});\n\nconst authz = new Authz(components.authz, { permissions, roles, policies, tenantId: \"my-app\" });\n```\n\nWhen you **evaluate** a policy (e.g. after RBAC allows), always **await** the condition so both sync and async policies work. Use `evaluatePolicyCondition` to normalize to a Promise:\n\n```typescript\nconst policy = policies[\"documents:update\"];\nif (policy) {\n  const allowed = await evaluatePolicyCondition(policy.condition, policyCtx);\n  if (!allowed) throw new Error(policy.message ?? \"Permission denied\");\n}\n```\n\n### Policy Context\n\nPolicies receive a context object with:\n\n```typescript\ninterface PolicyContext {\n  subject: {\n    userId: string;\n    roles: string[];\n    attributes: Record<string, unknown>;\n  };\n  resource?: {\n    type: string;\n    id: string;\n    [key: string]: unknown; // Resource data\n  };\n  action: string; // The permission being checked\n  environment?: {\n    timestamp: number;\n    ip?: string;\n  };\n  hasRole: (role: string) => boolean;\n  hasAttribute: (key: string) => boolean;\n  getAttribute: <T = unknown>(key: string, defaultValue?: T) => T | undefined;\n}\n```\n\n**API note:** Existing sync conditions remain valid. There is no breaking change; only the return type is widened to allow `Promise<boolean>` for async policies.\n\n---\n\n## ReBAC (Relationship-Based Access Control)\n\nReBAC enables access control based on relationships between entities, perfect for hierarchical systems like CRMs, document sharing, and organizational structures.\n\n### Relationship Model\n\nRelationships are stored as tuples: `(subject, relation, object)`\n\n```\nuser:alice  ──member──►  team:sales\nteam:sales  ──owner──►   account:acme\naccount:acme ──parent──► deal:big_deal\n```\n\n### Adding Relationships\n\n```typescript\n// Use the Authz client — NOT direct component calls\n// User is member of team\nawait authz.addRelation(ctx, { type: \"user\", id: \"alice\" }, \"member\", { type: \"team\", id: \"sales\" });\n\n// Team owns account\nawait authz.addRelation(ctx, { type: \"team\", id: \"sales\" }, \"owner\", { type: \"account\", id: \"acme\" });\n```\n\n### Checking Direct Relationships\n\n```typescript\n// Use the Authz client — NOT direct component calls\nconst isMember = await authz.hasRelation(ctx, { type: \"user\", id: \"alice\" }, \"member\", { type: \"team\", id: \"sales\" });\n// Returns: true\n\n// To remove a relationship\nawait authz.removeRelation(ctx, { type: \"user\", id: \"alice\" }, \"member\", { type: \"team\", id: \"sales\" });\n```\n\n### Relationship Traversal (Inherited Access)\n\nThe real power of ReBAC is checking access through relationship chains:\n\n```typescript\n// Define how permissions flow through relationships\nconst traversalRules = {\n  // A deal viewer is anyone who can view the parent account\n  \"deal:viewer\": [\n    { through: \"account\", via: \"parent\", inherit: \"viewer\" }\n  ],\n  // An account viewer is any member of the owning team\n  \"account:viewer\": [\n    { through: \"team\", via: \"owner\", inherit: \"member\" }\n  ],\n};\n\n// Graph traversal is available via the component query directly\n// Check: Can alice view big_deal?\nconst result = await ctx.runQuery(components.authz.rebac.checkRelationWithTraversal, {\n  subjectType: \"user\",\n  subjectId: \"alice\",\n  relation: \"viewer\",\n  objectType: \"deal\",\n  objectId: \"big_deal\",\n  traversalRules,\n  maxDepth: 5,\n});\n\n// Returns:\n// {\n//   allowed: true,\n//   path: [\n//     \"account:acme -[parent]-> deal:big_deal\",\n//     \"team:sales -[owner]-> account:acme\",\n//     \"user:alice -[member]-> team:sales\"\n//   ],\n//   reason: \"Access via relationship chain\"\n// }\n```\n\nTraversal uses a **maxDepth** limit (default 5) and tracks visited `(objectType, objectId, relation)` nodes so that circular relationships do not cause infinite loops.\n\n### CRM Example\n\n```typescript\n// Setup CRM hierarchy using the Authz client\nconst setupCRM = async (ctx) => {\n  // Sales rep Alice is on sales team\n  await authz.addRelation(ctx, { type: \"user\", id: \"alice\" }, \"member\", { type: \"team\", id: \"sales\" });\n\n  // Sales team owns Acme Corp account\n  await authz.addRelation(ctx, { type: \"team\", id: \"sales\" }, \"owner\", { type: \"account\", id: \"acme_corp\" });\n\n  // Acme Corp has a big deal\n  await authz.addRelation(ctx, { type: \"account\", id: \"acme_corp\" }, \"parent\", { type: \"deal\", id: \"big_deal\" });\n\n  // Now Alice can access the deal through the relationship chain!\n};\n```\n\n---\n\n## O(1) Indexed Lookups\n\nFor high-performance production use, the indexed system pre-computes permissions for instant lookups.\n\n### Using the Indexed API\n\n```typescript\nimport { Authz } from \"@djpanda/convex-authz\";\nimport { components } from \"./_generated/api\";\n\nconst authz = new Authz(components.authz, { permissions, roles, tenantId: \"my-app\" });\n\n// O(1) permission check - single index lookup\nconst canEdit = await authz.can(ctx, userId, \"documents:update\");\n\n// O(1) role check\nconst isAdmin = await authz.hasRole(ctx, userId, \"admin\");\n\n// O(1) relationship check\nconst isMember = await authz.hasRelation(ctx, { type: \"user\", id: userId }, \"member\", { type: \"team\", id: \"sales\" });\n```\n\n### How It Works\n\n```\nTraditional (O(n)):                    Indexed (O(1)):\n┌──────┐                               ┌──────┐\n│ User │                               │ User │\n└──┬───┘                               └──┬───┘\n   │                                      │\n   ▼                                      ▼\n┌──────────┐                           ┌─────────────────────────────┐\n│ Get Roles│ ◄── Query                 │ Index Lookup:               │\n└──┬───────┘                           │ effectivePermissions        │\n   │                                   │ [userId, permission, scope] │\n   ▼                                   └─────────────────────────────┘\n┌───────────────┐                                  │\n│ Expand Perms  │ ◄── Loop                         ▼\n└──┬────────────┘                              true/false\n   │\n   ▼\n┌──────────────┐\n│ Check Each   │ ◄── Loop\n│ Permission   │\n└──┬───────────┘\n   │\n   ▼\ntrue/false\n```\n\n### Trade-offs\n\n\n| Operation        | Traditional      | Indexed               |\n| ---------------- | ---------------- | --------------------- |\n| Permission Check | O(roles × perms) | **O(1)**              |\n| Role Assignment  | O(1)             | O(permissions)        |\n| Permission Grant | O(1)             | O(1)                  |\n| Memory Usage     | Lower            | Higher (denormalized) |\n\n\n**Use Indexed for production workloads with many permission checks.**\n\n---\n\n## Audit Logging\n\nAll authorization changes are logged for compliance and debugging.\n\n### Automatic Logging\n\nThe following actions are automatically logged:\n\n- `role_assigned` - When a role is assigned\n- `role_revoked` - When a role is revoked\n- `permission_granted` - When a direct permission is granted\n- `permission_denied` - When a permission is explicitly denied\n- `attribute_set` - When a user attribute is set\n- `attribute_removed` - When a user attribute is removed\n- `permission_check` - (Optional) When permissions are checked\n\n### Querying the Audit Log\n\nWithout pagination options, `getAuditLog` returns a simple array (optional `limit`, default 100):\n\n```typescript\n// Get all logs for a user\nconst logs = await authz.getAuditLog(ctx, {\n  userId: \"user_123\",\n  limit: 50,\n});\n\n// Get logs by action type\nconst roleChanges = await authz.getAuditLog(ctx, {\n  action: \"role_assigned\",\n  limit: 100,\n});\n```\n\nFor scalable browsing, use **cursor-based pagination** by passing `numItems` (and optionally `cursor` for the next page). The return value is then `{ page, isDone, continueCursor }`:\n\n```typescript\n// First page\nconst result = await authz.getAuditLog(ctx, { numItems: 50 });\nif (!Array.isArray(result)) {\n  console.log(result.page);\n  if (!result.isDone) {\n    // Next page\n    const next = await authz.getAuditLog(ctx, {\n      numItems: 50,\n      cursor: result.continueCursor,\n    });\n  }\n}\n```\n\n### Log Entry Structure\n\n```typescript\n{\n  _id: \"...\",\n  timestamp: 1704672000000,\n  actorId: \"admin_user\",  // Who made the change\n  action: \"role_assigned\",\n  userId: \"target_user\",  // Who was affected\n  details: {\n    role: \"editor\",\n    scope: { type: \"team\", id: \"team_123\" },\n  },\n}\n```\n\n---\n\n## Permission Overrides\n\nGrant or deny specific permissions that override role-based assignments.\n\n### Granting Permissions\n\n```typescript\n// Grant a permission directly (bypasses role checks)\nawait authz.grantPermission(ctx, userId, \"documents:delete\", undefined, \"Temporary access for migration\");\n\n// With scope\nawait authz.grantPermission(ctx, userId, \"documents:delete\", { type: \"team\", id: \"team_123\" });\n\n// With expiration\nawait authz.grantPermission(ctx, userId, \"documents:delete\", undefined, \"Temporary\", Date.now() + 3600000);\n```\n\n### Denying Permissions\n\n```typescript\n// Deny a permission (even if user has it via role)\nawait authz.denyPermission(ctx, userId, \"documents:delete\", undefined, \"Access restricted\");\n```\n\n### Removing Permission Overrides\n\n`grantPermission` and `denyPermission` are **not** inverses. They upsert into\nthe same `permissionOverrides` row (keyed by user + permission + scope), so\ncalling `denyPermission` after `grantPermission` rewrites the row's `effect`\nfrom `\"allow\"` to `\"deny\"` — the override persists, just inverted. A deny is\nmeaningfully different from \"no override at all\": a deny blocks the\npermission even if the user holds it via a role, while no override lets\nrole-derived access flow through normally.\n\nTo truly undo an override and return to baseline (no row in\n`permissionOverrides`), use `removeOverride`. After removal, role-derived\nsources, deferred policy state, and role-based expiry are all properly\nrestored on the effective row.\n\n```typescript\n// Remove a direct grant or deny — returns true if a row was deleted, false if no\n// override existed for the given (user, permission, scope) tuple. Idempotent.\nconst removed = await authz.removeOverride(ctx, userId, \"documents:delete\");\n\n// Scope must match the original override exactly — global is distinct from scoped\nawait authz.removeOverride(ctx, userId, \"documents:delete\", { type: \"team\", id: \"team_123\" });\n```\n\n---\n\n## Schema Reference\n\n### Tables\n\n\n| Table                    | Purpose                           |\n| ------------------------ | --------------------------------- |\n| `roleAssignments`        | User role assignments             |\n| `userAttributes`         | User attributes for ABAC          |\n| `permissionOverrides`    | Direct permission grants/denials  |\n| `relationships`          | ReBAC relationship tuples         |\n| `effectivePermissions`   | Pre-computed permissions (O(1))   |\n| `effectiveRoles`         | Pre-computed roles (O(1))         |\n| `effectiveRelationships` | Pre-computed relationships (O(1)) |\n| `auditLog`               | Authorization audit trail         |\n\n\n### Indexes\n\nAll tables have optimized indexes for common query patterns:\n\n```typescript\n// roleAssignments\n.index(\"by_user\", [\"userId\"])\n.index(\"by_role\", [\"role\"])\n.index(\"by_user_and_role\", [\"userId\", \"role\"])\n\n// effectivePermissions (O(1) lookup)\n.index(\"by_user_permission_scope\", [\"userId\", \"permission\", \"scopeKey\"])\n\n// relationships\n.index(\"by_subject_relation_object\", [\"subjectType\", \"subjectId\", \"relation\", \"objectType\", \"objectId\"])\n```\n\n---\n\n## API Reference\n\n### Authz Client\n\n```typescript\nclass Authz<P, R, Policy> {\n  // Permission checks\n  can(ctx, userId, permission, scope?): Promise<boolean>\n  canAny(ctx, userId, permissions: string[], scope?): Promise<boolean>   // bulk: any of N permissions (max 100)\n  require(ctx, userId, permission, scope?): Promise<void>\n  \n  // Role management\n  hasRole(ctx, userId, role, scope?): Promise<boolean>\n  assignRole(ctx, userId, role, scope?, expiresAt?, actorId?): Promise<string>\n  assignRoles(ctx, userId, roles: RoleAssignItem[], actorId?): Promise<{ assigned: number; assignmentIds: string[] }>  // bulk, max 20\n  revokeRole(ctx, userId, role, scope?, actorId?): Promise<boolean>\n  revokeRoles(ctx, userId, roles: RoleScopeItem[], actorId?): Promise<{ revoked: number }>  // bulk, max 20\n  revokeAllRoles(ctx, userId, scope?, actorId?): Promise<number>\n  getUserRoles(ctx, userId, scope?): Promise<Role[]>\n  getUserPermissions(ctx, userId, scope?): Promise<PermissionResult>\n\n  // Offboarding\n  offboardUser(ctx, userId, options?: { scope?, actorId?, removeAttributes?, removeOverrides?, removeRelationships? }): Promise<OffboardResult>\n  deprovisionUser(ctx, userId, options?: { actorId?, enableAudit? }): Promise<OffboardResult>  // full wipe: roles, overrides, attributes, relationships\n  \n  // Attribute management\n  setAttribute(ctx, userId, key, value, actorId?): Promise<string>\n  removeAttribute(ctx, userId, key, actorId?): Promise<boolean>\n  getUserAttributes(ctx, userId): Promise<Attribute[]>\n  \n  // Permission overrides\n  grantPermission(ctx, userId, permission, scope?, reason?, expiresAt?, actorId?): Promise<string>\n  denyPermission(ctx, userId, permission, scope?, reason?, expiresAt?, actorId?): Promise<string>\n  removeOverride(ctx, userId, permission, scope?, actorId?): Promise<boolean>\n  \n  // Audit\n  getAuditLog(ctx, options?): Promise<AuditEntry[] | { page: AuditEntry[]; isDone: boolean; continueCursor: string }>\n}\n```\n\n### Argument validation\n\nAll public methods on `Authz` validate their arguments before calling the component. Invalid inputs throw an `Error` with a clear message so you can fail fast and fix call sites.\n\n\n| Argument                              | Rule                                                                                       | Example error                                                         |\n| ------------------------------------- | ------------------------------------------------------------------------------------------ | --------------------------------------------------------------------- |\n| `userId`                              | Non-empty string, max 512 characters                                                       | `\"userId must be a non-empty string\"`                                 |\n| `permission`                          | Must be `resource:action` (e.g. `documents:read`)                                          | `\"Invalid permission format: \\\"read\\\". Expected \\\"resource:action\\\"\"` |\n| `scope`                               | When provided, `type` and `id` must be non-empty strings                                   | `\"scope must have non-empty type when provided\"`                      |\n| `role`                                | Non-empty string; must be one of the roles passed at construction                          | `\"Unknown role: \\\"superadmin\\\"\"`                                      |\n| `expiresAt`                           | When provided, must be a finite number (timestamp)                                         | `\"expiresAt must be a finite number\"`                                 |\n| Attribute `key`                       | Non-empty string                                                                           | `\"Attribute key must be a non-empty string\"`                          |\n| `getAuditLog` `limit`                 | When provided, positive integer 1–1000                                                     | `\"limit must be a positive integer when provided\"`                    |\n| `getAuditLog` `numItems`              | When provided (pagination), positive integer 1–1000                                        | same as `limit`                                                       |\n| Relation args                         | `subjectType`, `subjectId`, `relation`, `objectType`, `objectId` must be non-empty strings | `\"subjectType must be a non-empty string\"`                            |\n| `canAny` `permissions`                | Non-empty array, each element valid `resource:action`, length ≤ 100                        | `\"permissions must not exceed 100 items\"`                             |\n| `assignRoles` / `revokeRoles` `roles` | Non-empty array, each role valid, length ≤ 20                                              | `\"roles must not exceed 20 items\"`                                    |\n\n\nOptional parameters are only validated when present (e.g. omitting `scope` is valid; passing `scope: { type: \"\", id: \"x\" }` throws).\n\n---\n\n## Inspired by Google Zanzibar\n\nThis component implements concepts from [Google Zanzibar](https://research.google/pubs/pub48190/), Google's global authorization system that powers Google Drive, YouTube, Cloud, and more.\n\n### Zanzibar Concepts Implemented\n\n\n| Zanzibar Concept       | Our Implementation           | Description                        |\n| ---------------------- | ---------------------------- | ---------------------------------- |\n| **Relation Tuples**    | `relationships` table        | `(user:alice, member, team:sales)` |\n| **Usersets**           | Traversal rules              | Groups defined by relationships    |\n| **Check API**          | `checkPermissionFast`        | O(1) \"can user X do Y on Z?\"       |\n| **Expand API**         | `checkRelationWithTraversal` | Find all paths granting access     |\n| **Read API**           | `getSubjectRelations`        | List all relationships             |\n| **Watch API**          | Convex reactivity            | Real-time permission updates       |\n| **Computed Relations** | `effectivePermissions`       | Pre-computed for O(1) lookup       |\n\n\n### How Zanzibar Works\n\n```\n┌─────────────────────────────────────────────────────────────────────────────┐\n│                        Google Zanzibar Model                                 │\n├─────────────────────────────────────────────────────────────────────────────┤\n│                                                                              │\n│  Relation Tuples (stored):                                                  │\n│  ┌────────────────────────────────────────────────────────────────────┐     │\n│  │  (user:alice, member, team:sales)                                  │     │\n│  │  (team:sales, owner, account:acme)                                 │     │\n│  │  (account:acme, parent, deal:big_deal)                             │     │\n│  └────────────────────────────────────────────────────────────────────┘     │\n│                                                                              │\n│  Authorization Model (defines inheritance):                                  │\n│  ┌────────────────────────────────────────────────────────────────────┐     │\n│  │  type deal                                                         │     │\n│  │    relations                                                       │     │\n│  │      define parent: [account]                                      │     │\n│  │      define viewer: viewer from parent  ← Computed relation        │     │\n│  └────────────────────────────────────────────────────────────────────┘     │\n│                                                                              │\n│  Check: \"Can alice view deal:big_deal?\"                                     │\n│  ┌────────────────────────────────────────────────────────────────────┐     │\n│  │  1. deal:big_deal.viewer = viewer from parent                      │     │\n│  │  2. parent = account:acme                                          │     │\n│  │  3. account:acme.viewer = member from owner                        │     │\n│  │  4. owner = team:sales                                             │     │\n│  │  5. team:sales.member includes user:alice ✓                        │     │\n│  │  → ALLOWED                                                         │     │\n│  └────────────────────────────────────────────────────────────────────┘     │\n│                                                                              │\n└─────────────────────────────────────────────────────────────────────────────┘\n```\n\n### Key Zanzibar Benefits We Provide\n\n1. **Consistency at Scale**\n  - Pre-computed permissions ensure fast, consistent checks\n  - No permission drift between reads\n2. **Flexible Permission Model**\n  - Combine RBAC, ABAC, and ReBAC as needed\n  - Support complex hierarchies (org → team → project → resource)\n3. **Auditability**\n  - Full audit log of all permission changes\n  - Path tracing shows WHY access was granted\n4. **Real-time Updates**\n  - Convex reactivity means UI updates instantly when permissions change\n  - No polling required (better than Zanzibar!)\n\n---\n\n## Comparison with Other Solutions\n\n\n| Feature       | @djpanda/convex-authz | OpenFGA    | Oso     | Cerbos  |\n| ------------- | --------------------- | ---------- | ------- | ------- |\n| RBAC          | ✅                     | ✅          | ✅       | ✅       |\n| ABAC          | ✅                     | ⚠️ Limited | ✅       | ✅       |\n| ReBAC         | ✅                     | ✅ Native   | ✅       | ⚠️      |\n| O(1) Lookups  | ✅                     | ✅          | ✅       | ✅       |\n| Convex Native | ✅                     | ❌          | ❌       | ❌       |\n| Type Safety   | ✅ TypeScript          | DSL        | Polar   | YAML    |\n| Real-time     | ✅ Convex queries      | Polling    | Polling | Polling |\n| Self-hosted   | ✅                     | ✅          | ✅       | ✅       |\n\n\n---\n\n## Testing\n\n### Running Package Tests\n\n```bash\ncd packages/authz\nnpm test\n```\n\n### Using with convex-test\n\n```typescript\nimport { convexTest } from \"convex-test\";\nimport { describe, expect, it } from \"vitest\";\nimport schema from \"./component/schema.js\";\nimport { api } from \"./component/_generated/api.js\";\n\ndescribe(\"authorization\", () => {\n  it(\"should assign and check roles\", async () => {\n    const t = convexTest(schema, modules);\n\n    await t.mutation(api.mutations.assignRole, {\n      userId: \"user_123\",\n      role: \"admin\",\n    });\n\n    const hasRole = await t.query(api.queries.hasRole, {\n      userId: \"user_123\",\n      role: \"admin\",\n    });\n\n    expect(hasRole).toBe(true);\n  });\n});\n```\n\n---\n\n## Multi-Tenant Data Isolation\n\nEvery table in the authz component includes a required `tenantId` field as the leading column in every database index. This provides database-level data isolation between tenants — queries for one tenant can never return data from another.\n\n### tenantId vs scope\n\n\n|                 | `tenantId`                    | `scope`                               |\n| --------------- | ----------------------------- | ------------------------------------- |\n| **Purpose**     | Data isolation boundary       | Resource-level grouping               |\n| **Enforcement** | Database-level (index prefix) | Application-level (query filter)      |\n| **Required**    | Always                        | Optional                              |\n| **Example**     | `\"acme-corp\"`                 | `{ type: \"project\", id: \"proj-123\" }` |\n\n\n- **tenantId** answers: \"whose data is this?\" — the organization/customer boundary\n- **scope** answers: \"within this tenant, what resource does this apply to?\" — e.g. admin of a specific project\n\n### Configuration\n\n```typescript\n// Single-tenant apps — pass any constant string\nconst authz = new Authz(components.authz, {\n  permissions, roles,\n  tenantId: \"my-app\",\n});\n\n// Multi-tenant apps — pass the current organization/tenant ID\nconst authz = new Authz(components.authz, {\n  permissions, roles,\n  tenantId: currentOrgId,\n});\n```\n\n### Routing operations to a specific tenant\n\n`withTenant(tenantId)` returns a new `Authz` instance bound to a different tenant. Two patterns are supported:\n\n1. **Per-request construction** — simplest when each handler always knows its tenant:\n\n   ```typescript\n   function makeAuthz(tenantId: string) {\n     return new Authz(components.authz, { permissions, roles, tenantId });\n   }\n   // Inside a handler:\n   const authz = makeAuthz(args.tenantId);\n   await authz.assignRole(ctx, userId, \"editor\", scope);\n   ```\n\n2. **Module-scope instance + `withTenant(tenantId)` per call** — best for shared abstractions and integration packages (e.g. `@djpanda/convex-tenants`) that hold one `Authz` reference and route per request:\n\n   ```typescript\n   // Module scope:\n   export const authz = new Authz(components.authz, {\n     permissions, roles,\n     tenantId: \"deployment-default\", // any constant; `withTenant` overrides it per call\n   });\n\n   // In each handler:\n   await authz.withTenant(orgId).assignRole(ctx, userId, \"editor\", scope);\n   ```\n\n   The constructor's `tenantId` still governs operations that don't go through `withTenant()` — typically pre-org operations (e.g. gating `createOrganization`) or platform-level admin checks where no organization context exists yet.\n\n`withTenant` also covers the rarer cross-tenant admin case where a single handler legitimately reads or writes another tenant's data.\n\n### Compliance\n\nThe `tenantId`-first index design satisfies SOC2 and HIPAA data isolation requirements:\n\n- All queries are partitioned by tenant at the database index level\n- Cross-tenant data access is structurally impossible through the standard API\n- `tenantId` is required in the constructor — it cannot be accidentally omitted\n\n---\n\n## Best Practices\n\n### 1. Use Scoped Roles for Multi-tenancy\n\n```typescript\n// Don't: Global admin\nawait authz.assignRole(ctx, userId, \"admin\");\n\n// Do: Scoped admin\nawait authz.assignRole(ctx, userId, \"admin\", { type: \"org\", id: orgId });\n```\n\n### 2. Use the Authz Client for Production\n\n```typescript\n// Authz uses O(1) indexed lookups by default — no separate class needed\nconst authz = new Authz(components.authz, { permissions, roles, tenantId: \"my-app\" });\n```\n\n### 3. Use ReBAC for Complex Hierarchies\n\n```typescript\n// CRM, document sharing, org charts → ReBAC\n// Simple role assignments → RBAC\n```\n\n### 4. Set Expiration for Temporary Access\n\n```typescript\nawait authz.assignRole(ctx, userId, \"contractor\", undefined, \n  Date.now() + 30 * 24 * 60 * 60 * 1000 // 30 days\n);\n```\n\n### 5. Always Use Audit Logging\n\nThe audit log is invaluable for:\n\n- Compliance (SOC2, GDPR)\n- Debugging access issues\n- Security incident investigation\n\n### 6. Cleanup of Expired Data (Scheduled via Component)\n\nExpired role assignments and permission overrides (and their indexed rows) are purged by a **scheduled cleanup job** that you enable once—no need to add `convex/crons.ts` yourself. The component embeds [@convex-dev/crons](https://www.convex.dev/components/crons); run this **once** after installing the component to register the daily job:\n\n```bash\nnpx convex run authz/cronSetup:ensureCleanupCronRegistered\n```\n\nOr from an init script that runs on deploy (e.g. `convex/init.ts` invoked via `convex dev --run init`):\n\n```typescript\nawait ctx.runMutation(components.authz.cronSetup.ensureCleanupCronRegistered, {});\n```\n\nThe job runs every 24 hours and cleans `roleAssignments`, `permissionOverrides`, `effectiveRoles`, and `effectivePermissions`. Optional: you can instead define the cleanup in your app's `convex/crons.ts` or run `components.authz.mutations.runScheduledCleanup` manually.\n\n### 6.1. Audit log retention\n\nTo avoid unbounded growth of the audit log (compliance and cost), the same cron registration also schedules a **daily audit retention job**. Configure it with Convex environment variables (Dashboard or CLI):\n\n\n| Variable                      | Description                                                                                                         |\n| ----------------------------- | ------------------------------------------------------------------------------------------------------------------- |\n| `AUDIT_RETENTION_DAYS`        | Delete entries older than this many days (e.g. `90`). Omit or `0` = do not prune by age.                            |\n| `AUDIT_RETENTION_MAX_ENTRIES` | Cap total entries by deleting oldest until count ≤ this value (e.g. `100000`). Omit or `0` = do not prune by count. |\n\n\nSet at least one to enable retention. The job runs every 24 hours (same `ensureCleanupCronRegistered` flow). You can also run `components.authz.mutations.runAuditRetentionCleanup` manually with optional args `{ maxAgeDays?, maxEntries? }` to override env for that run.\n\n### 7. Use Authz as a Global Singleton\n\nAuthz is a **global component** — install it once and share a single client instance across your entire app. Do not create multiple `Authz` instances per app.\n\n```\nconvex/\n  convex.config.ts   ← app.use(authz) — registered once\n  authz.ts           ← definePermissions, defineRoles, export authz client\n  documents.ts       ← import { authz } from \"./authz\"\n  billing.ts         ← import { authz } from \"./authz\"\n  settings.ts        ← import { authz } from \"./authz\"\n```\n\n```typescript\n// convex/authz.ts — single source of truth\nimport { Authz, definePermissions, defineRoles } from \"@djpanda/convex-authz\";\nimport { components } from \"./_generated/api\";\n\nconst permissions = definePermissions({\n  documents: { create: true, read: true, update: true, delete: true },\n  billing: { view: true, manage: true },\n  settings: { view: true, manage: true },\n});\n\nconst roles = defineRoles(permissions, {\n  admin: {\n    documents: [\"create\", \"read\", \"update\", \"delete\"],\n    billing: [\"view\", \"manage\"],\n    settings: [\"view\", \"manage\"],\n  },\n  viewer: {\n    documents: [\"read\"],\n    settings: [\"view\"],\n  },\n});\n\n// Export the single authz client — import this everywhere\nexport const authz = new Authz(components.authz, { permissions, roles, tenantId: \"my-app\" });\n```\n\n```typescript\n// convex/documents.ts — uses the shared client\nimport { mutation } from \"./_generated/server\";\nimport { authz } from \"./authz\";\n\nexport const deleteDocument = mutation({\n  args: { docId: v.id(\"documents\") },\n  handler: async (ctx, args) => {\n    await authz.require(ctx, userId, \"documents:delete\");\n    // ...\n  },\n});\n```\n\n### 7. Organize Permissions by Domain\n\nIn larger apps, split permission and role definitions by domain and merge them into a single authz client using `definePermissions` and `defineRoles`:\n\n```typescript\n// convex/permissions/documents.ts\nexport const documentPermissions = {\n  documents: { create: true, read: true, update: true, delete: true },\n};\nexport const documentRoles = {\n  editor: { documents: [\"create\", \"read\", \"update\"] as const },\n  viewer: { documents: [\"read\"] as const },\n};\n\n// convex/permissions/billing.ts\nexport const billingPermissions = {\n  billing: { view: true, manage: true },\n};\nexport const billingRoles = {\n  billing_admin: { billing: [\"view\", \"manage\"] as const },\n};\n```\n\n```typescript\n// convex/authz.ts — merge all domains\nimport { Authz, definePermissions, defineRoles } from \"@djpanda/convex-authz\";\nimport { components } from \"./_generated/api\";\nimport { documentPermissions, documentRoles } from \"./permissions/documents\";\nimport { billingPermissions, billingRoles } from \"./permissions/billing\";\n\nconst permissions = definePermissions(documentPermissions, billingPermissions);\nconst roles = defineRoles(permissions, documentRoles, billingRoles);\n\nexport const authz = new Authz(components.authz, { permissions, roles, tenantId: \"my-app\" });\n```\n\nThis keeps each domain self-contained while producing a single, type-safe authz client.\n\n### 8. Integrating with Other Convex Components\n\nWhen other Convex components (e.g., `@djpanda/convex-tenants`) need authorization, they share the **same global authz instance**. The pattern:\n\n1. Register both components independently in `convex.config.ts`\n2. The other component exports its required permissions and roles\n3. Merge them with your app's own definitions\n4. Pass the authz client to the other component's API factory\n\n```mermaid\ngraph LR\n  subgraph app [\"Your App (convex.config.ts)\"]\n    AuthzComp[\"authz component\"]\n    TenantsComp[\"tenants component\"]\n  end\n\n  subgraph authzSetup [\"convex/authz.ts\"]\n    AppPerms[\"App permissions\"]\n    TenantPerms[\"Tenant permissions\"]\n    Merge[\"definePermissions + defineRoles\"]\n    Client[\"authz client (singleton)\"]\n    AppPerms --> Merge\n    TenantPerms --> Merge\n    Merge --> Client\n  end\n\n  Client -->|\"import { authz }\"| Docs[\"convex/documents.ts\"]\n  Client -->|\"import { authz }\"| Billing[\"convex/billing.ts\"]\n  Client -->|\"passed to makeTenantsAPI\"| TenantsAPI[\"convex/tenants.ts\"]\n```\n\n\n\n```typescript\n// convex/convex.config.ts — register both components\nimport { defineApp } from \"convex/server\";\nimport authz from \"@djpanda/convex-authz/convex.config\";\nimport tenants from \"@djpanda/convex-tenants/convex.config\";\n\nconst app = defineApp();\napp.use(authz);\napp.use(tenants);\nexport default app;\n```\n\n```typescript\n// convex/authz.ts — merge app + component permissions\nimport { Authz, definePermissions, defineRoles } from \"@djpanda/convex-authz\";\nimport { TENANTS_PERMISSIONS, TENANTS_ROLES } from \"@djpanda/convex-tenants\";\nimport { components } from \"./_generated/api\";\n\n// Your app's own permissions\nconst appPermissions = {\n  documents: { create: true, read: true, update: true, delete: true },\n};\nconst appRoles = {\n  editor: { documents: [\"create\", \"read\", \"update\"] as const },\n};\n\n// Merge with tenant component's permissions\nconst permissions = definePermissions(appPermissions, TENANTS_PERMISSIONS);\nconst roles = defineRoles(permissions, appRoles, TENANTS_ROLES);\n\nexport const authz = new Authz(components.authz, { permissions, roles, tenantId: \"my-app\" });\n```\n\n```typescript\n// convex/tenants.ts — pass the shared authz client\nimport { makeTenantsAPI } from \"@djpanda/convex-tenants\";\nimport { components } from \"./_generated/api\";\nimport { authz } from \"./authz\";\n\nexport const {\n  createOrg,\n  inviteMember,\n  removeMember,\n  // ...\n} = makeTenantsAPI(components.tenants, {\n  authz,\n  creatorRole: \"owner\",\n  auth: async (ctx) => {\n    // return the current user ID\n  },\n});\n```\n\nThis way every part of your app — your own functions and third-party components — shares a single, consistent authorization layer.\n\n---\n\n<!-- SKILL-EXCLUDE-START -->\n\n## Using with AI Coding Agents\n\n`@djpanda/convex-authz` is available as an [agent skill](https://vercel.com/docs/agent-resources/skills) that gives AI coding agents deep knowledge of this library's API, patterns, and best practices.\n\n### Install the skill\n\n```bash\nnpx skills add dbjpanda/convex-authz\n```\n\nThis works with **18+ AI agents** including Claude Code, Cursor, GitHub Copilot, Cline, Windsurf, and others. Once installed, your agent will automatically know how to:\n\n- Set up the component and define permissions/roles\n- Use RBAC, ABAC, and ReBAC patterns correctly\n- Write O(1) permission checks\n- Configure multi-tenant isolation\n- Follow Convex conventions\n\n### Browse more skills\n\n```bash\nnpx skills find convex\n```\n\nOr visit [skills.sh](https://skills.sh) to discover community skills.\n\n<!-- SKILL-EXCLUDE-END -->\n\n---\n\n## Development\n\n```bash\n# Install dependencies\nnpm install\n\n# Run development mode\nnpm run dev\n\n# Run tests\nnpm test\n\n# Build for production\nnpm run build\n\n# Type check\nnpm run typecheck\n```\n\n---\n\n## File Structure\n\n```\npackages/authz/\n├── package.json          # Package configuration\n├── README.md             # This documentation\n├── src/\n│   ├── client/\n│   │   ├── index.ts      # Main exports (Authz, helpers)\n│   │   └── index.test.ts # Client tests\n│   ├── component/\n│   │   ├── convex.config.ts  # Component registration\n│   │   ├── schema.ts         # Database tables and indexes\n│   │   ├── helpers.ts        # Shared utilities\n│   │   ├── queries.ts        # Query functions\n│   │   ├── mutations.ts      # Mutation functions\n│   │   ├── rebac.ts          # ReBAC relationship functions\n│   │   ├── indexed.ts        # O(1) indexed functions\n│   │   ├── authz.test.ts     # RBAC/ABAC tests\n│   │   ├── rebac.test.ts     # ReBAC tests\n│   │   ├── indexed.test.ts   # O(1) indexed tests\n│   │   └── _generated/       # Auto-generated types\n│   └── test.ts           # Test helpers\n└── example/              # Example app\n```\n\n---\n\n## License\n\nMIT\n\n---\n\n## Contributing\n\nContributions are welcome! Please read our [CONTRIBUTING.md](CONTRIBUTING.md) before submitting a PR.\n","readmeFilename":"README.md"}