{"_id":"@alphinex/permissions","_rev":"3-8dfd61deb5c9633eb0756d6f6db801cf","name":"@alphinex/permissions","dist-tags":{"latest":"1.1.0"},"versions":{"1.0.0":{"name":"@alphinex/permissions","version":"1.0.0","license":"UNLICENSED","_id":"@alphinex/permissions@1.0.0","maintainers":[{"name":"anasaliqureshi","email":"anas@alphinex.com"}],"dist":{"shasum":"a47cdb9f097b55a08c2eed9acfd8120c53012347","tarball":"https://registry.npmjs.org/@alphinex/permissions/-/permissions-1.0.0.tgz","fileCount":5,"integrity":"sha512-uK4pPHjwBNlL5LEz6aworhboG0Z6SZwtP2NKLFGPMEU9ZXreq4VZ9hyoRSKpbtt0Yg0QV5sfE8XoI7xgOBnoAA==","signatures":[{"sig":"MEQCIFqTSKsiHbqjMBLfUECt+Soq/kCWB3uf9b4DWyCHbKmjAiBELWT3VmBzynCrSlt9qZnM0QmkFHtiNXhweD6zyIA5IA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":8994},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./package.json":"./package.json"},"private":false,"scripts":{"dev":"vite build --watch","lint":"eslint .","test":"vitest run","build":"vite build","clean":"rimraf dist .turbo","typecheck":"tsc --noEmit"},"_npmUser":{"name":"anasaliqureshi","email":"anas@alphinex.com"},"description":"Ability/permission checking: <Can>, usePermission, policy resolution.","directories":{},"sideEffects":false,"_nodeVersion":"22.14.0","dependencies":{"@alphinex/auth":"1.0.0","@alphinex/core":"1.0.0"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^6.0.5","jsdom":"^25.0.1","react":"^19.0.0","eslint":"^9.17.0","vitest":"^2.1.8","react-dom":"^19.0.0","typescript":"^5.7.2","@types/react":"^19.0.2","@types/react-dom":"^19.0.2","@alphinex/testing":"1.0.0","@vitejs/plugin-react":"^4.3.4","@alphinex/build-config":"0.0.0","@testing-library/react":"^16.1.0","@alphinex/eslint-config":"0.0.0","@testing-library/jest-dom":"^6.6.3","@alphinex/typescript-config":"0.0.0","@testing-library/user-event":"^14.5.2"},"peerDependencies":{"react":"^19.0.0","react-dom":"^19.0.0"},"peerDependenciesMeta":{"react":{"optional":false},"react-dom":{"optional":false}},"_npmOperationalInternal":{"tmp":"tmp/permissions_1.0.0_1785852479994_0.2344351743268196","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"@alphinex/permissions","version":"1.0.1","license":"UNLICENSED","_id":"@alphinex/permissions@1.0.1","maintainers":[{"name":"anasaliqureshi","email":"anas@alphinex.com"}],"dist":{"shasum":"0992035f3b92ce8b28519297b65d3d73c28f3d5c","tarball":"https://registry.npmjs.org/@alphinex/permissions/-/permissions-1.0.1.tgz","fileCount":5,"integrity":"sha512-JDCMQ9+CgF5KywX/6GqFyxZBgpoK3D44AabR+hBsJ6UdoDiClfMIhpG+I871zHYenl2+yz7JOC0qeiWDlLENSg==","signatures":[{"sig":"MEYCIQDEn+iASvuVhzVfD37Z6uZvC6bZgPccxYfgPIZSp8eFlAIhAMWeud85biIgdt6/3njl7PJDCNC1Z3V7+fYpCWbeQZY4","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":13171},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./package.json":"./package.json"},"private":false,"scripts":{"dev":"vite build --watch","lint":"eslint .","test":"vitest run","build":"vite build","clean":"rimraf dist .turbo","typecheck":"tsc --noEmit"},"_npmUser":{"name":"anasaliqureshi","email":"anas@alphinex.com"},"description":"Ability/permission checking: <Can>, usePermission, policy resolution.","directories":{},"sideEffects":false,"_nodeVersion":"22.14.0","dependencies":{"@alphinex/auth":"1.0.1","@alphinex/core":"1.0.0"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^6.0.5","jsdom":"^25.0.1","react":"^19.0.0","eslint":"^9.17.0","vitest":"^2.1.8","react-dom":"^19.0.0","typescript":"^5.7.2","@types/react":"^19.0.2","@types/react-dom":"^19.0.2","@alphinex/testing":"1.0.2","@vitejs/plugin-react":"^4.3.4","@alphinex/build-config":"0.0.0","@testing-library/react":"^16.1.0","@alphinex/eslint-config":"0.0.0","@testing-library/jest-dom":"^6.6.3","@alphinex/typescript-config":"0.0.0","@testing-library/user-event":"^14.5.2"},"peerDependencies":{"react":"^19.0.0","react-dom":"^19.0.0"},"peerDependenciesMeta":{"react":{"optional":false},"react-dom":{"optional":false}},"_npmOperationalInternal":{"tmp":"tmp/permissions_1.0.1_1786525492045_0.7282353074348198","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"@alphinex/permissions","version":"1.1.0","private":false,"description":"Ability/permission checking: <Can>, usePermission, policy resolution.","license":"UNLICENSED","type":"module","sideEffects":false,"main":"./dist/index.js","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./package.json":"./package.json"},"dependencies":{"@alphinex/core":"1.0.0","@alphinex/auth":"1.0.1"},"peerDependencies":{"react":"^19.0.0","react-dom":"^19.0.0"},"peerDependenciesMeta":{"react":{"optional":false},"react-dom":{"optional":false}},"devDependencies":{"typescript":"^5.7.2","vite":"^6.0.5","vitest":"^2.1.8","react":"^19.0.0","react-dom":"^19.0.0","@types/react":"^19.0.2","@types/react-dom":"^19.0.2","@testing-library/react":"^16.1.0","@testing-library/jest-dom":"^6.6.3","@testing-library/user-event":"^14.5.2","@vitejs/plugin-react":"^4.3.4","jsdom":"^25.0.1","eslint":"^9.17.0","@alphinex/eslint-config":"1.0.0","@alphinex/testing":"1.0.2","@alphinex/build-config":"0.0.0","@alphinex/typescript-config":"0.0.0"},"scripts":{"build":"vite build","dev":"vite build --watch","lint":"eslint .","typecheck":"tsc --noEmit","test":"vitest run","clean":"rimraf dist .turbo"},"_nodeVersion":"22.14.0","_id":"@alphinex/permissions@1.1.0","dist":{"integrity":"sha512-jC1xHUUn8mmk20PuppAl10l8Tdps8iH7fHIdZyDm813k5orbKOSMmHUBio8Fj6Lx8oEQowlppe7qbzhE/DlGaw==","shasum":"c87faa3a5364fb04ef85f83299eaf943641eb681","tarball":"https://registry.npmjs.org/@alphinex/permissions/-/permissions-1.1.0.tgz","fileCount":5,"unpackedSize":17838,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIE9P1tcJOjIzX//TA0oSkMnz70FJDJsJDuf2Ep/5mCglAiEAzlRF+o/ee81frnklncJ5sbJg4Jlvt0hTgZwYOMM+hWg="}]},"_npmUser":{"name":"anasaliqureshi","email":"anas@alphinex.com"},"directories":{},"maintainers":[{"name":"anasaliqureshi","email":"anas@alphinex.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/permissions_1.1.0_1786551565038_0.1628013317874748"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-04T14:07:59.830Z","modified":"2026-08-12T16:19:25.428Z","1.0.0":"2026-08-04T14:08:00.130Z","1.0.1":"2026-08-12T09:04:52.187Z","1.1.0":"2026-08-12T16:19:25.187Z"},"license":"UNLICENSED","description":"Ability/permission checking: <Can>, usePermission, policy resolution.","maintainers":[{"name":"anasaliqureshi","email":"anas@alphinex.com"}],"readme":"# @alphinex/permissions\n\nAbility/permission checking for the UI layer: a `PermissionsProvider`/`usePermission` context,\na `<Can>` component for declarative conditional rendering, and a pluggable adapter model for\nturning whatever shape your backend returns (roles, ability rules, a flat string list, ...)\ninto a uniform `PermissionResolver`. This package never decides _how_ permissions are computed\n— that's the app's `PermissionsAdapter`, per documentation/ARCHITECTURE.md §3.3.\n\n## `PermissionsProvider` + `usePermission`\n\n`PermissionsProvider` takes a `resolver: PermissionResolver` — a\n`(action: string, subject: string, field?: string) => boolean` function — and makes it\navailable to the tree below. `usePermission(action, subject, field?)` reads it:\n\n```tsx\nimport {\n  PermissionsProvider,\n  usePermission,\n  createListPermissionsAdapter,\n} from \"@alphinex/permissions\";\n\nconst resolver = createListPermissionsAdapter([\"view:Invoice\", \"edit:Invoice\"]);\n\nfunction App() {\n  return (\n    <PermissionsProvider resolver={resolver}>\n      <InvoiceToolbar />\n    </PermissionsProvider>\n  );\n}\n\nfunction InvoiceToolbar() {\n  const canEdit = usePermission(\"edit\", \"Invoice\");\n  return <button disabled={!canEdit}>Edit invoice</button>;\n}\n```\n\n`usePermission()` throws if called outside a `<PermissionsProvider>`.\n\n## `Can`\n\nA CASL-flavored declarative gate for JSX, built on `usePermission()` under the hood. `I` is the\naction, `a` is the subject, `field` is an optional field-level check, `fallback` renders when\nthe check fails (defaults to nothing), and `children` may also be a render-prop function that\nreceives the boolean directly:\n\n```tsx\nimport { Can } from \"@alphinex/permissions\";\n\nfunction DeleteInvoiceButton() {\n  return (\n    <Can I=\"delete\" a=\"Invoice\" fallback={<p className=\"text-fg-muted text-xs\">Admins only</p>}>\n      <button className=\"text-danger\">Delete invoice</button>\n    </Can>\n  );\n}\n\nfunction AmountField() {\n  return (\n    <Can I=\"edit\" a=\"Invoice\" field=\"amount\">\n      {(allowed) => <input readOnly={!allowed} />}\n    </Can>\n  );\n}\n```\n\n## `createListPermissionsAdapter`\n\nA ready-made `PermissionsAdapter<string[]>` for the common case of a flat `\"action:subject\"`\nrule list — the shape most simple Laravel policy-to-JSON serializers produce (e.g.\n`[\"view:Invoice\", \"edit:Invoice\", \"*:Report\"]`). `\"*\"` acts as a wildcard for either the action\nor the subject (or both). Field-level checks always pass, since this source shape carries no\nfield granularity — write a custom `PermissionsAdapter` if you need that:\n\n```ts\nimport { createListPermissionsAdapter } from \"@alphinex/permissions\";\n\nconst resolver = createListPermissionsAdapter([\"*:Invoice\", \"view:Report\"]);\nresolver(\"edit\", \"Invoice\"); // true — matches the \"*:Invoice\" wildcard rule\nresolver(\"edit\", \"Report\"); // false\n```\n\n## `PermissionResolver` / `PermissionsAdapter<TSource>`\n\nThe two types that make this package pluggable. A `PermissionsAdapter<TSource>` is\n`(source: TSource) => PermissionResolver` — write your own to map any backend shape (a roles\narray, CASL rules, a permissions object keyed by subject, ...) into a `PermissionResolver`, and\nhand the result to `PermissionsProvider`.\n\n## Composing with `@alphinex/auth`\n\nThe common pattern is to derive the resolver from the currently authenticated user, so\npermissions update automatically on login/logout/role change. Compute the rule list from\n`useAuth()`'s `user`, memoize it, and feed it through an adapter:\n\n```tsx\nimport { useAuth } from \"@alphinex/auth\";\nimport { PermissionsProvider, createListPermissionsAdapter } from \"@alphinex/permissions\";\nimport { useMemo, type ReactNode } from \"react\";\n\nconst ROLE_RULES: Record<string, string[]> = {\n  admin: [\"*:Invoice\"],\n  member: [\"view:Invoice\", \"edit:Invoice\"],\n};\n\nfunction PermissionsFromAuth({ children }: { children: ReactNode }) {\n  const { user } = useAuth();\n  const resolver = useMemo(\n    () => createListPermissionsAdapter(user ? (ROLE_RULES[user.role] ?? []) : []),\n    [user]\n  );\n  return <PermissionsProvider resolver={resolver}>{children}</PermissionsProvider>;\n}\n```\n\nMount `PermissionsFromAuth` inside `<AuthProvider>` (from `@alphinex/auth`) so `useAuth()`\nresolves, and outside anything that calls `usePermission()`/`<Can>`.\n\nSee [documentation/ARCHITECTURE.md](../../documentation/ARCHITECTURE.md) for the full package contract, dependency rules, and roadmap placement.\n","readmeFilename":""}