UNPKG

permix

Version:

Permix is a lightweight, framework-agnostic, type-safe permissions management library for JavaScript applications on the client and server sides.

224 lines (155 loc) 5.86 kB
# Permix — frontend (React / Vue / Solid / Svelte) Pick the package subpath for your framework. Pattern is the same: one shared `permix` instance, call `setup` when the user is known, wrap the tree, check in components. Docs: https://permix.letstri.dev/docs/integrations/react ## React ### Provider ```tsx import { PermixProvider } from 'permix/react' import { permix } from './lib/permix' export function App() { return ( <PermixProvider permix={permix}> <Routes /> </PermixProvider> ) } ``` ### Setup after auth ```ts // e.g. after session loads await loadUser() permix.setup(roleRulesFor(user)) ``` ### Hook (wrap once) ```ts // hooks/use-permissions.ts import { usePermix } from 'permix/react' import { permix } from '../lib/permix' export function usePermissions() { return usePermix(permix) } ``` ```tsx function EditButton({ post }) { const { check, isReady } = usePermissions() if (!isReady) return null if (!check('post.update', post)) return null return <button>Edit</button> } ``` Pass the **same** `permix` instance to `PermixProvider` and `usePermix`. ### Declarative `Check` component ```ts import { createComponents } from 'permix/react' export const { Check } = createComponents(permix) ``` ```tsx <Check path="post.create" otherwise={<span>Denied</span>}> <CreateForm /> </Check> ``` ```tsx <Check path="post.update" data={post} reverse> Hidden when allowed; shown when denied </Check> ``` ### SSR Use `PermixHydrate` + call `setup` again on the client for function rules — see **SSR and hydration** below. ## Vue Docs: https://permix.letstri.dev/docs/integrations/vue ```vue <script setup lang="ts"> import { PermixProvider } from 'permix/vue' import { permix } from './lib/permix' </script> <template> <PermixProvider :permix="permix"> <YourApp /> </PermixProvider> </template> ``` Use `usePermix` from `permix/vue` (same `setup` / `check` / `isReady` flow as React). ## Solid Docs: https://permix.letstri.dev/docs/integrations/solid Provider + hooks from `permix/solid`; mirror the React steps above. ## Svelte Docs: https://permix.letstri.dev/docs/integrations/svelte Requires Svelte 5. Provider + hooks from `permix/svelte`; mirror the React steps above. ```svelte <script lang="ts"> import { PermixProvider } from 'permix/svelte' import { permix } from './lib/permix' let { children } = $props() </script> <PermixProvider {permix}> {@render children()} </PermixProvider> ``` `usePermix(permix)` returns `{ check, isReady }` where `isReady` is a reactive getter — access it as `permissions.isReady` (don't destructure). `createComponents(permix)` returns a typed `Check` component that uses `children` / `otherwise` snippets. ## UX guidelines - Show loading or skeleton while `!isReady` — `check` returns `false` when rules are not ready in hooks. - Hide destructive actions when denied; prefer disabling with tooltip only if you explain why. - Keep permission strings in sync with server middleware paths. ## SSR and hydration Docs: https://permix.letstri.dev/docs/guide/hydration Send a JSON snapshot of booleans to the browser so the first paint can respect permissions without re-fetching policy on the client. ### Server ```ts permix.setup(serverRules) const state = permix.dehydrate() // { post: { create: true, read: false } } — functions evaluated once without data ``` Pass `state` to the client (embed in HTML, RSC payload, loader data, etc.). ### Client ```ts permix.hydrate(state) // isReady() is still FALSE — hydrate only restores booleans ``` Function-based rules are **lost** in JSON (dehydration calls functions with no data; missing required data `false`). **Always call `setup` again on the client** with full rules (including closures): ```ts permix.hydrate(serverState) permix.setup(clientRulesForUser) // restores functions + sets ready ``` Skipping client `setup` after hydrate leaves dynamic/ReBAC checks wrong. ### React ```tsx import { DehydratedState, PermixHydrate, PermixProvider } from 'permix/react' function App({ dehydratedState }: { dehydratedState: DehydratedState<typeof schema> }) { return ( <PermixProvider permix={permix}> <PermixHydrate state={dehydratedState}> <YourApp /> </PermixHydrate> </PermixProvider> ) } ``` Run client `permix.setup(...)` where you restore the session (e.g. after `PermixHydrate` mounts or in the same auth effect). ### Next.js / TanStack Start Use framework helpers from `permix/next` or `permix/tanstack-start` when available — they wire dehydrate/hydrate into the framework data flow. Docs: - https://permix.letstri.dev/docs/integrations/next - https://permix.letstri.dev/docs/integrations/tanstack-start ### Flow diagram ```text Server: setup(rules) dehydrate() send state Client: hydrate(state) setup(fullRules) isReady() check() / usePermix ``` ### Pitfalls | Issue | Cause | |-------|--------| | UI stuck not ready | `hydrate` without follow-up `setup` | | Wrong dynamic checks | Relying on dehydrated booleans only | | Mismatch server/client | Different schemas or missing actions in client `setup` | For static-only permissions (all booleans), dehydrate + hydrate + `setup` with the same booleans is enough; still call `setup` to mark ready. ## Examples in the Permix repo - React: https://github.com/letstri/permix/tree/main/examples/react - Vue: https://github.com/letstri/permix/tree/main/examples/vue - Solid: https://github.com/letstri/permix/tree/main/examples/solid - Svelte: https://github.com/letstri/permix/tree/main/examples/svelte - Next.js (SSR): https://github.com/letstri/permix/tree/main/examples/next - Role templates: https://github.com/letstri/permix/tree/main/examples/role-based - ReBAC: https://github.com/letstri/permix/tree/main/examples/rebac