21 | Is Permix ready?
22 | {{ isReady ? 'Yes' : 'No' }}
23 |
24 | My user is
25 | {{ user?.id ?? '...' }}
26 |
27 |
28 |
29 | Post {{ post.id }}
30 |
31 | Can I edit the post where authorId is
32 | {{ post.authorId }}?
33 | {{ check('post', 'edit', post) ? 'Yes' : 'No' }}
34 |
35 | I can edit a post inside the Check component
36 |
37 | I don't have permission to edit a post inside the Check component
38 |
39 |
40 |
41 |
36 | Can I edit the post where authorId is
37 | {post.authorId}
38 | ?
39 |
40 | {check('post', 'edit', post) ? 'Yes' : 'No'}
41 |
42 |
48 | I can edit a post inside the Check component
49 |
50 |
51 |
37 | Can I edit the post where authorId is
38 | {post.authorId}
39 | ?
40 |
41 | {check('post', 'edit', post) ? 'Yes' : 'No'}
42 |
43 |
49 | I can edit a post inside the Check component
50 |
51 |
52 |
35 |
36 |
41 |
42 |
43 | )
44 | }
45 |
46 | export async function generateStaticParams() {
47 | return source.generateParams()
48 | }
49 |
50 | export async function generateMetadata(
51 | props: PageProps<'/docs/[[...slug]]'>,
52 | ): Promise {
53 | const params = await props.params
54 | const page = source.getPage(params.slug)
55 |
56 | if (!page)
57 | notFound()
58 |
59 | return {
60 | title: page.data.title,
61 | description: page.data.description,
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/permix/src/elysia/create-permix.ts:
--------------------------------------------------------------------------------
1 | import type { Context } from 'elysia'
2 | import type { Permix, PermixDefinition, PermixRules } from '../core/create-permix'
3 | import type { CheckContext, CheckFunctionParams } from '../core/params'
4 | import type { MaybePromise } from '../core/utils'
5 | import { createPermix as createPermixCore } from '../core/create-permix'
6 | import { createCheckContext } from '../core/params'
7 | import { createTemplate } from '../core/template'
8 | import { pick } from '../utils'
9 |
10 | export interface ElysiaContext {
11 | context: Context
12 | }
13 |
14 | export interface PermixOptions {
15 | /**
16 | * Custom error handler
17 | */
18 | onForbidden?: (params: CheckContext & ElysiaContext) => MaybePromise
19 | }
20 |
21 | /**
22 | * Create a middleware function that checks permissions for Elysia routes.
23 | *
24 | * @link https://permix.letstri.dev/docs/integrations/elysia
25 | */
26 | export function createPermix(
27 | {
28 | onForbidden = ({ context }) => {
29 | context.set.status = 403
30 | return { error: 'Forbidden' }
31 | },
32 | }: PermixOptions = {},
33 | ) {
34 | const derive = (rules: PermixRules) => ({
35 | permix: pick(createPermixCore(rules), ['check', 'dehydrate']),
36 | })
37 |
38 | const checkHandler = (...params: CheckFunctionParams) => {
39 | return async (context: Context & { permix: Pick, 'check' | 'dehydrate'> }) => {
40 | if (!context.permix) {
41 | throw new Error('[Permix]: Instance not found. Please use the `setupMiddleware` function.')
42 | }
43 |
44 | const hasPermission = context.permix.check(...params)
45 |
46 | if (!hasPermission) {
47 | return onForbidden({
48 | context,
49 | ...createCheckContext(...params),
50 | })
51 | }
52 | }
53 | }
54 |
55 | function template(...params: Parameters>) {
56 | return createTemplate(...params)
57 | }
58 |
59 | return {
60 | derive,
61 | checkHandler,
62 | template,
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/examples/express-trpc-react/server/main.ts:
--------------------------------------------------------------------------------
1 | import type { PermissionsDefinition } from '@/shared/permix'
2 | import { initTRPC, TRPCError } from '@trpc/server'
3 | import * as trpcExpress from '@trpc/server/adapters/express'
4 | import cors from 'cors'
5 | import express from 'express'
6 | import { createPermix } from 'permix/trpc'
7 | import { z } from 'zod'
8 | import { getRules } from '@/shared/permix'
9 |
10 | const app = express()
11 |
12 | app.use(cors())
13 |
14 | const t = initTRPC.context<{ extraInfo: string }>().create()
15 |
16 | export const permix = createPermix({
17 | forbiddenError: () => new TRPCError({
18 | code: 'FORBIDDEN',
19 | message: 'You do not have permission to access this resource',
20 | }),
21 | })
22 |
23 | export const router = t.router
24 | export const publicProcedure = t.procedure.use(({ next }) => {
25 | // Imagine this is a middleware that gets the user from the request
26 | const user = {
27 | role: 'admin' as const,
28 | }
29 |
30 | return next({
31 | ctx: {
32 | permix: permix.setup(getRules(user.role)),
33 | },
34 | })
35 | })
36 |
37 | export const appRouter = router({
38 | userList: publicProcedure
39 | .use(permix.checkMiddleware('user', 'read'))
40 | // Imagine this is a database query
41 | .query(() => [
42 | {
43 | id: '1',
44 | name: 'John Doe',
45 | email: 'john.doe@example.com',
46 | },
47 | {
48 | id: '2',
49 | name: 'Jane Doe 2',
50 | email: 'jane.doe2@example.com',
51 | },
52 | ]),
53 | userWrite: publicProcedure
54 | .use(permix.checkMiddleware('user', 'create'))
55 | .input(z.object({
56 | name: z.string(),
57 | email: z.email(),
58 | }))
59 | .mutation(() => {
60 | // Imagine this is a database mutation
61 | return { id: '1', name: 'John Doe', email: 'john.doe@example.com' }
62 | }),
63 | })
64 |
65 | export type AppRouter = typeof appRouter
66 |
67 | app.use(
68 | '/trpc',
69 | trpcExpress.createExpressMiddleware({
70 | router: appRouter,
71 | createContext: () => ({
72 | extraInfo: 'some extra info',
73 | }),
74 | }),
75 | )
76 | app.listen(3000, () => {
77 | // eslint-disable-next-line no-console
78 | console.log('Server is running on port 3000')
79 | })
80 |
--------------------------------------------------------------------------------
/docs/content/docs/guide/ready.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Ready State
3 | description: Learn how to use the `isReady()` method to check if permissions are ready to use.
4 | ---
5 |
6 | ## Overview
7 |
8 | Sometimes you need to know when permissions are ready to use. For example, you might want to wait for permissions to be ready before rendering a component. That's where the `isReady()` and `isReadyAsync()` methods come in.
9 |
10 |
11 | Note that `isReady()` and `isReadyAsync()` will always return `false` on server-side. It only becomes `true` after the first successful call to `setup()` on the client.
12 |
13 |
14 | ## Usage
15 |
16 | ### Basic
17 |
18 | Permix provides an `isReady()` method to check if permissions have been properly initialized on the client side:
19 |
20 | ```ts twoslash
21 | import { createPermix } from 'permix'
22 |
23 | const permix = createPermix()
24 |
25 | console.log(permix.isReady()) // false
26 |
27 | // After setup completes
28 | permix.setup({
29 | post: {
30 | create: true,
31 | read: true
32 | }
33 | })
34 |
35 | console.log(permix.isReady()) // true
36 | ```
37 |
38 | ### Async
39 |
40 | If you need to wait for permissions to be ready in an async context, you can use the `isReadyAsync()` method. This returns a promise that resolves when permissions are ready:
41 |
42 | ```ts
43 | import { createPermix } from 'permix'
44 |
45 | const permix = createPermix()
46 |
47 | async function init() {
48 | await permix.isReadyAsync()
49 | // Permissions are now ready to use
50 | const canCreate = permix.check('post', 'create')
51 | }
52 | ```
53 |
54 | ### SSR
55 |
56 | This is particularly useful in SSR applications when using function-based permissions, since the dehydration process converts all function permissions to `false` until they are properly rehydrated on the client.
57 |
58 |
59 | Read more about [hydration](/guide/hydration) to learn how to transfer permissions from the server to the client.
60 |
61 |
62 | ```ts
63 | import { dehydrate, hydrate, createPermix } from 'permix'
64 | import { permix } from './permix'
65 |
66 | permix.setup({
67 | post: {
68 | create: true,
69 | read: post => post.isPublic
70 | }
71 | })
72 |
73 | // Dehydrate permissions on the server
74 | const state = dehydrate(permix)
75 | // { post: { create: true, read: false } }
76 |
77 | // Rehydrate permissions on the client
78 | hydrate(permix, state)
79 |
80 | const canRead = permix.check('post', 'read', { isPublic: true }) // false
81 |
82 | permix.hook('ready', (state) => {
83 | const canRead = permix.check('post', 'read', { isPublic: true })
84 | console.log(canRead) // true
85 | })
86 | ```
87 |
--------------------------------------------------------------------------------
/permix/src/orpc/create-permix.ts:
--------------------------------------------------------------------------------
1 | import type { Permix, PermixDefinition, PermixRules } from '../core/create-permix'
2 | import type { CheckContext, CheckFunctionParams } from '../core/params'
3 | import { ORPCError, os } from '@orpc/server'
4 | import { createPermix as createPermixCore } from '../core/create-permix'
5 | import { createCheckContext } from '../core/params'
6 | import { createTemplate } from '../core/template'
7 | import { pick } from '../utils'
8 |
9 | export interface PermixOptions {
10 | /**
11 | * Custom error to throw when permission is denied
12 | */
13 | forbiddenError?: (params: CheckContext & { context: C }) => ORPCError
14 | }
15 |
16 | /**
17 | * Create a middleware function that checks permissions for ORPC routes.
18 | *
19 | * @link https://permix.letstri.dev/docs/integrations/orpc
20 | */
21 | export function createPermix(
22 | {
23 | forbiddenError = () => new ORPCError('FORBIDDEN', {
24 | message: 'You do not have permission to perform this action',
25 | }),
26 | }: PermixOptions = {},
27 | ) {
28 | const plugin = os.$context<{ permix: Pick, 'check' | 'dehydrate'> }>()
29 |
30 | function setup(rules: PermixRules) {
31 | return pick(createPermixCore(rules), ['check', 'dehydrate'])
32 | }
33 |
34 | function checkMiddleware(...params: CheckFunctionParams) {
35 | return plugin.middleware(async ({ context, next }) => {
36 | if (!context.permix) {
37 | throw new Error('[Permix] Instance not found. Please use the `setupMiddleware` function.')
38 | }
39 |
40 | const hasPermission = context.permix.check(...params)
41 |
42 | if (!hasPermission) {
43 | const error = typeof forbiddenError === 'function'
44 | ? forbiddenError({
45 | ...createCheckContext(...params),
46 | context,
47 | })
48 | : forbiddenError
49 |
50 | if (!(error instanceof ORPCError)) {
51 | console.error('[Permix]: forbiddenError is not ORPCError')
52 |
53 | throw new ORPCError('FORBIDDEN', {
54 | message: 'You do not have permission to perform this action',
55 | })
56 | }
57 |
58 | throw error
59 | }
60 |
61 | return next()
62 | })
63 | }
64 |
65 | function template(...params: Parameters>) {
66 | return createTemplate(...params)
67 | }
68 |
69 | return {
70 | setup,
71 | checkMiddleware,
72 | template,
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/permix/src/trpc/create-permix.ts:
--------------------------------------------------------------------------------
1 | import type { Permix, PermixDefinition, PermixRules } from '../core/create-permix'
2 | import type { CheckContext, CheckFunctionParams } from '../core/params'
3 | import { initTRPC, TRPCError } from '@trpc/server'
4 | import { createPermix as createPermixCore } from '../core/create-permix'
5 | import { createCheckContext } from '../core/params'
6 | import { createTemplate } from '../core/template'
7 | import { pick } from '../utils'
8 |
9 | export interface PermixOptions {
10 | /**
11 | * Custom error to throw when permission is denied
12 | */
13 | forbiddenError?: (params: CheckContext & { ctx: C }) => TRPCError
14 | }
15 |
16 | /**
17 | * Create a middleware function that checks permissions for TRPC routes.
18 | *
19 | * @link https://permix.letstri.dev/docs/integrations/trpc
20 | */
21 | export function createPermix(
22 | {
23 | forbiddenError = () => new TRPCError({
24 | code: 'FORBIDDEN',
25 | message: 'You do not have permission to perform this action',
26 | }),
27 | }: PermixOptions = {},
28 | ) {
29 | const plugin = initTRPC.context<{ permix: Pick, 'check' | 'dehydrate'> }>().create()
30 |
31 | function setup(rules: PermixRules) {
32 | return pick(createPermixCore(rules), ['check', 'dehydrate'])
33 | }
34 |
35 | function checkMiddleware(...params: CheckFunctionParams) {
36 | return plugin.middleware(async ({ ctx, next }) => {
37 | if (!ctx.permix) {
38 | throw new TRPCError({
39 | code: 'INTERNAL_SERVER_ERROR',
40 | message: '[Permix] Instance not found. Please use the `setup` function.',
41 | })
42 | }
43 |
44 | const hasPermission = ctx.permix.check(...params)
45 |
46 | if (!hasPermission) {
47 | const error = typeof forbiddenError === 'function'
48 | ? forbiddenError({
49 | ...createCheckContext(...params),
50 | ctx,
51 | })
52 | : forbiddenError
53 |
54 | if (!(error instanceof TRPCError)) {
55 | console.error('[Permix]: forbiddenError is not TRPCError')
56 |
57 | throw new TRPCError({
58 | code: 'FORBIDDEN',
59 | message: 'You do not have permission to perform this action',
60 | })
61 | }
62 |
63 | throw error
64 | }
65 |
66 | return next()
67 | })
68 | }
69 |
70 | function template(...params: Parameters>) {
71 | return createTemplate(...params)
72 | }
73 |
74 | return {
75 | setup,
76 | checkMiddleware,
77 | template,
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/permix/src/solid/components.tsx:
--------------------------------------------------------------------------------
1 | import type { JSX } from 'solid-js'
2 | import type { Permix, PermixDefinition } from '../core'
3 | import type { PermixStateJSON } from '../core/create-permix'
4 | import type { CheckFunctionObject } from '../core/params'
5 | import type { PermixContext } from './hooks'
6 | import { createEffect, createMemo, onCleanup } from 'solid-js'
7 | import { createStore } from 'solid-js/store'
8 | import { getRules, validatePermix } from '../core/create-permix'
9 | import { Context, usePermix, usePermixContext } from './hooks'
10 |
11 | /**
12 | * Provider that provides the Permix context to your Solid components.
13 | *
14 | * @link https://permix.letstri.dev/docs/integrations/solid
15 | */
16 | export function PermixProvider(props: {
17 | children: JSX.Element
18 | permix: Permix
19 | }): JSX.Element {
20 | validatePermix(props.permix)
21 |
22 | const [context, setContext] = createStore>({
23 | permix: props.permix,
24 | isReady: props.permix.isReady(),
25 | rules: getRules(props.permix),
26 | })
27 |
28 | createEffect(() => {
29 | const setup = props.permix.hook('setup', () => setContext('rules', getRules(props.permix)))
30 | const ready = props.permix.hook('ready', () => setContext('isReady', props.permix.isReady()))
31 |
32 | onCleanup(() => {
33 | setup()
34 | ready()
35 | })
36 | })
37 |
38 | return (
39 |
40 | {props.children}
41 |
42 | )
43 | }
44 |
45 | export function PermixHydrate(props: { children: JSX.Element, state: PermixStateJSON }) {
46 | const context = usePermixContext()
47 |
48 | validatePermix(context.permix)
49 |
50 | context.permix.hydrate(props.state)
51 |
52 | return props.children
53 | }
54 |
55 | export type CheckProps = CheckFunctionObject & {
56 | children: JSX.Element
57 | otherwise?: JSX.Element
58 | reverse?: boolean
59 | }
60 |
61 | export interface PermixComponents {
62 | Check: (props: CheckProps) => JSX.Element
63 | }
64 |
65 | export function createComponents(permix: Permix): PermixComponents {
66 | function Check(props: CheckProps): JSX.Element {
67 | const context = usePermix(permix)
68 | const hasPermission = createMemo(() => context.check(props.entity, props.action, props.data))
69 |
70 | return (
71 | <>
72 | {props.reverse
73 | ? hasPermission() ? (props.otherwise || null) : props.children
74 | : hasPermission() ? props.children : (props.otherwise || null)}
75 | >
76 | )
77 | }
78 |
79 | return {
80 | Check,
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/permix/src/express/create-permix.ts:
--------------------------------------------------------------------------------
1 | import type { Handler, Request, Response } from 'express'
2 | import type { Permix, PermixDefinition, PermixRules } from '../core/create-permix'
3 | import type { CheckContext, CheckFunctionParams } from '../core/params'
4 | import type { MaybePromise } from '../core/utils'
5 | import { createPermix as createPermixCore } from '../core/create-permix'
6 | import { createCheckContext } from '../core/params'
7 | import { createTemplate } from '../core/template'
8 | import { pick } from '../utils'
9 |
10 | const permixSymbol = Symbol('permix')
11 |
12 | export interface MiddlewareContext {
13 | req: Request
14 | res: Response
15 | }
16 |
17 | export interface PermixOptions {
18 | /**
19 | * Custom error handler
20 | */
21 | onForbidden?: (params: CheckContext & MiddlewareContext) => MaybePromise
22 | }
23 |
24 | /**
25 | * Create a middleware function that checks permissions for Express routes.
26 | *
27 | * @link https://permix.letstri.dev/docs/integrations/express
28 | */
29 | export function createPermix(
30 | {
31 | onForbidden = ({ res }) => {
32 | res.status(403).json({ error: 'Forbidden' })
33 | },
34 | }: PermixOptions = {},
35 | ) {
36 | function getPermix(req: Request, res: Response) {
37 | try {
38 | const permix = (req as any)[permixSymbol] as Permix | undefined
39 |
40 | if (!permix) {
41 | throw new Error('Not found')
42 | }
43 |
44 | return pick(permix, ['check', 'dehydrate'])
45 | }
46 | catch {
47 | res.status(500).json({ error: '[Permix]: Instance not found. Please use the `setupMiddleware` function.' })
48 | return null!
49 | }
50 | }
51 |
52 | function setupMiddleware(callback: (context: MiddlewareContext) => MaybePromise>): Handler {
53 | return async (req, res, next) => {
54 | (req as any)[permixSymbol] = createPermixCore(await callback({ req, res }))
55 |
56 | return next()
57 | }
58 | }
59 |
60 | function checkMiddleware(...params: CheckFunctionParams): Handler {
61 | return async (req, res, next) => {
62 | const permix = getPermix(req, res)
63 |
64 | if (!permix)
65 | return
66 |
67 | const hasPermission = permix.check(...params)
68 |
69 | if (!hasPermission) {
70 | await onForbidden({
71 | req,
72 | res,
73 | ...createCheckContext(...params),
74 | })
75 | return
76 | }
77 |
78 | return next()
79 | }
80 | }
81 |
82 | function template(...params: Parameters>) {
83 | return createTemplate(...params)
84 | }
85 |
86 | return {
87 | setupMiddleware,
88 | checkMiddleware,
89 | template,
90 | get: getPermix,
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/permix/src/core/template.test.ts:
--------------------------------------------------------------------------------
1 | import type { PermixDefinition } from './create-permix'
2 | import { describe, expect, it } from 'vitest'
3 | import { createPermix } from './create-permix'
4 |
5 | interface Post {
6 | id: string
7 | title: string
8 | authorId: string
9 | }
10 |
11 | interface Comment {
12 | id: string
13 | content: string
14 | postId: string
15 | }
16 |
17 | type Definition = PermixDefinition<{
18 | post: {
19 | dataType: Post
20 | action: 'create' | 'read'
21 | }
22 | comment: {
23 | dataType: Comment
24 | action: 'create' | 'read' | 'update'
25 | }
26 | }>
27 |
28 | describe('createTemplate', () => {
29 | it('should define permissions with template', () => {
30 | const permix = createPermix()
31 |
32 | const permissions = permix.template({
33 | post: {
34 | create: true,
35 | read: true,
36 | },
37 | comment: {
38 | create: true,
39 | read: true,
40 | update: true,
41 | },
42 | })
43 |
44 | expect(permissions()).toEqual({
45 | post: {
46 | create: true,
47 | read: true,
48 | },
49 | comment: {
50 | create: true,
51 | read: true,
52 | update: true,
53 | },
54 | })
55 |
56 | permix.setup(permissions())
57 |
58 | expect(permix.check('post', 'create')).toBe(true)
59 | })
60 |
61 | it('should throw an error if permissions are not valid', () => {
62 | const permix = createPermix()
63 |
64 | expect(() => permix.template({
65 | // @ts-expect-error create isn't valid
66 | post: { create: 1 },
67 | })).toThrow()
68 | expect(() => permix.template({
69 | // @ts-expect-error create isn't valid
70 | post: { create: 'string' },
71 | })).toThrow()
72 | expect(() => permix.template({
73 | // @ts-expect-error create isn't valid
74 | post: { create: [] },
75 | })).toThrow()
76 | expect(() => permix.template({
77 | // @ts-expect-error create isn't valid
78 | post: { create: {} },
79 | })).toThrow()
80 | expect(() => permix.template({
81 | // @ts-expect-error create isn't valid
82 | post: { create: null },
83 | })).toThrow()
84 | })
85 |
86 | it('should work with template function with param', () => {
87 | const permix = createPermix()
88 |
89 | const rules1 = permix.template(({ user }: { user: { role: string } }) => ({
90 | post: {
91 | create: user.role !== 'admin',
92 | read: true,
93 | update: true,
94 | },
95 | comment: {
96 | create: user.role !== 'admin',
97 | read: true,
98 | update: true,
99 | },
100 | }))
101 |
102 | permix.setup(rules1({ user: { role: 'admin' } }))
103 |
104 | expect(permix.check('post', 'create')).toBe(false)
105 | })
106 | })
107 |
--------------------------------------------------------------------------------
/permix/src/react/components.tsx:
--------------------------------------------------------------------------------
1 | import type { Permix, PermixDefinition } from '../core'
2 | import type { PermixStateJSON } from '../core/create-permix'
3 | import type { CheckFunctionObject } from '../core/params'
4 | import type { PermixContext } from './hooks'
5 | import * as React from 'react'
6 | import { getRules, validatePermix } from '../core/create-permix'
7 | import { Context, usePermix, usePermixContext } from './hooks'
8 |
9 | /**
10 | * Provider that provides the Permix context to your React components.
11 | *
12 | * @link https://permix.letstri.dev/docs/integrations/react
13 | */
14 | export function PermixProvider({
15 | children,
16 | permix,
17 | }: { children: React.ReactNode, permix: Permix }) {
18 | validatePermix(permix)
19 |
20 | const [context, setContext] = React.useState>(() => ({
21 | permix,
22 | isReady: permix.isReady(),
23 | rules: getRules(permix),
24 | }))
25 |
26 | React.useEffect(() => {
27 | const setup = permix.hook('setup', () => setContext(c => ({ ...c, rules: getRules(permix) })))
28 | const ready = permix.hook('ready', () => setContext(c => ({ ...c, isReady: permix.isReady() })))
29 |
30 | return () => {
31 | setup()
32 | ready()
33 | }
34 | }, [permix])
35 |
36 | return (
37 | // eslint-disable-next-line react/no-context-provider
38 |
39 | {children}
40 |
41 | )
42 | }
43 |
44 | export function PermixHydrate({ children, state }: { children: React.ReactNode, state: PermixStateJSON }) {
45 | const { permix } = usePermixContext()
46 |
47 | validatePermix(permix)
48 |
49 | React.useMemo(() => permix.hydrate(state), [permix, state])
50 |
51 | return children
52 | }
53 |
54 | export type CheckProps = CheckFunctionObject & {
55 | children: React.ReactNode
56 | otherwise?: React.ReactNode
57 | reverse?: boolean
58 | }
59 |
60 | export interface PermixComponents {
61 | Check: (props: CheckProps) => React.ReactNode
62 | }
63 |
64 | // eslint-disable-next-line react-refresh/only-export-components
65 | export function createComponents(permix: Permix): PermixComponents {
66 | function Check({
67 | children,
68 | entity,
69 | action,
70 | data,
71 | otherwise = null,
72 | reverse = false,
73 | }: CheckProps) {
74 | const { check } = usePermix(permix)
75 |
76 | const hasPermission = check(entity, action, data)
77 | return reverse
78 | ? hasPermission ? otherwise : children
79 | : hasPermission ? children : otherwise
80 | }
81 |
82 | Check.displayName = 'Check'
83 |
84 | return {
85 | Check,
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/permix/src/hono/create-permix.ts:
--------------------------------------------------------------------------------
1 | import type { Context, MiddlewareHandler } from 'hono'
2 | import type { Permix, PermixDefinition, PermixRules } from '../core/create-permix'
3 | import type { CheckContext, CheckFunctionParams } from '../core/params'
4 | import type { MaybePromise } from '../core/utils'
5 | import { createMiddleware } from 'hono/factory'
6 | import { HTTPException } from 'hono/http-exception'
7 | import { createPermix as createPermixCore } from '../core/create-permix'
8 | import { createCheckContext } from '../core/params'
9 | import { createTemplate } from '../core/template'
10 | import { pick } from '../utils'
11 |
12 | const permixSymbol = Symbol('permix') as unknown as string
13 |
14 | export interface MiddlewareContext {
15 | c: Context
16 | }
17 |
18 | export interface PermixOptions {
19 | /**
20 | * Custom error handler
21 | */
22 | onForbidden?: (params: CheckContext & { c: Context }) => MaybePromise
23 | }
24 |
25 | /**
26 | * Create a middleware function that checks permissions for Hono routes.
27 | *
28 | * @link https://permix.letstri.dev/docs/integrations/hono
29 | */
30 | export function createPermix(
31 | {
32 | onForbidden = ({ c }) => c.json({ error: 'Forbidden' }, 403),
33 | }: PermixOptions = {},
34 | ) {
35 | function getPermix(c: Context) {
36 | try {
37 | const permix = c.get(permixSymbol) as Permix | undefined
38 |
39 | if (!permix) {
40 | throw new Error('Not found')
41 | }
42 |
43 | return pick(permix, ['check', 'dehydrate'])
44 | }
45 | catch {
46 | throw new HTTPException(500, {
47 | message: '[Permix] Instance not found. Please use the `setupMiddleware` function.',
48 | })
49 | }
50 | }
51 |
52 | function setupMiddleware(callback: (context: { c: Context }) => PermixRules | Promise>): MiddlewareHandler {
53 | return createMiddleware(async (c, next) => {
54 | c.set(permixSymbol, createPermixCore(await callback({ c })))
55 |
56 | await next()
57 | })
58 | }
59 |
60 | function checkMiddleware(...params: CheckFunctionParams): MiddlewareHandler {
61 | return createMiddleware(async (c, next) => {
62 | try {
63 | const permix = getPermix(c)
64 |
65 | const hasPermission = permix.check(...params)
66 |
67 | if (!hasPermission) {
68 | return await onForbidden({ c, ...createCheckContext(...params) })
69 | }
70 |
71 | await next()
72 | }
73 | catch {
74 | return await onForbidden({ c, ...createCheckContext(...params) })
75 | }
76 | })
77 | }
78 |
79 | function template(...params: Parameters>) {
80 | return createTemplate(...params)
81 | }
82 |
83 | return {
84 | setupMiddleware,
85 | checkMiddleware,
86 | template,
87 | get: getPermix,
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/permix/src/node/create-permix.ts:
--------------------------------------------------------------------------------
1 | import type { IncomingMessage, ServerResponse } from 'node:http'
2 | import type { Permix, PermixDefinition, PermixRules } from '../core/create-permix'
3 | import type { CheckContext, CheckFunctionParams } from '../core/params'
4 | import { createPermix as createPermixCore } from '../core/create-permix'
5 | import { createCheckContext } from '../core/params'
6 | import { createTemplate } from '../core/template'
7 | import { pick } from '../utils'
8 |
9 | const permixSymbol = Symbol('permix')
10 |
11 | export interface MiddlewareContext {
12 | req: IncomingMessage
13 | res: ServerResponse
14 | }
15 |
16 | export interface PermixOptions {
17 | /**
18 | * Custom error handler
19 | */
20 | onForbidden?: (params: CheckContext & MiddlewareContext) => void
21 | }
22 |
23 | /**
24 | * Create a middleware function that checks permissions for Node.js HTTP servers.
25 | * Compatible with raw Node.js HTTP servers.
26 | *
27 | * @link https://permix.letstri.dev/docs/integrations/node
28 | */
29 | export function createPermix(
30 | {
31 | onForbidden = ({ res }) => {
32 | res.statusCode = 403
33 | res.setHeader('Content-Type', 'application/json')
34 | res.end(JSON.stringify({ error: 'Forbidden' }))
35 | },
36 | }: PermixOptions = {},
37 | ) {
38 | function getPermix(req: IncomingMessage, res: ServerResponse) {
39 | try {
40 | const permix = (req as any)[permixSymbol] as Permix | undefined
41 |
42 | if (!permix) {
43 | throw new Error('Not found')
44 | }
45 |
46 | return pick(permix, ['check', 'dehydrate'])
47 | }
48 | catch {
49 | res.statusCode = 500
50 | res.setHeader('Content-Type', 'application/json')
51 | res.end(JSON.stringify({ error: '[Permix]: Instance not found. Please use the `setupMiddleware` function.' }))
52 | return null!
53 | }
54 | }
55 |
56 | function setupMiddleware(callback: (context: MiddlewareContext) => PermixRules | Promise>) {
57 | return async (context: MiddlewareContext) => {
58 | (context.req as any)[permixSymbol] = createPermixCore(await callback(context))
59 | }
60 | }
61 |
62 | function checkMiddleware(...params: CheckFunctionParams) {
63 | return async (context: MiddlewareContext) => {
64 | const permix = getPermix(context.req, context.res)
65 |
66 | if (!permix)
67 | return
68 |
69 | const hasPermission = permix.check(...params)
70 |
71 | if (!hasPermission) {
72 | return onForbidden({
73 | ...createCheckContext(...params),
74 | req: context.req,
75 | res: context.res,
76 | })
77 | }
78 | }
79 | }
80 |
81 | function template(...params: Parameters>) {
82 | return createTemplate(...params)
83 | }
84 |
85 | return {
86 | setupMiddleware,
87 | checkMiddleware,
88 | template,
89 | get: getPermix,
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/permix/src/fastify/create-permix.ts:
--------------------------------------------------------------------------------
1 | import type { FastifyPluginAsync, FastifyReply, FastifyRequest, RouteHandler } from 'fastify'
2 | import type { Permix, PermixDefinition, PermixRules } from '../core/create-permix'
3 | import type { CheckContext, CheckFunctionParams } from '../core/params'
4 | import type { MaybePromise } from '../core/utils'
5 | import fp from 'fastify-plugin'
6 | import { createPermix as createPermixCore } from '../core/create-permix'
7 | import { createCheckContext } from '../core/params'
8 | import { createTemplate } from '../core/template'
9 | import { pick } from '../utils'
10 |
11 | const permixSymbol = Symbol('permix')
12 |
13 | export interface MiddlewareContext {
14 | request: FastifyRequest
15 | reply: FastifyReply
16 | }
17 |
18 | export interface PermixOptions {
19 | /**
20 | * Custom error handler
21 | */
22 | onForbidden?: (params: CheckContext & MiddlewareContext) => MaybePromise
23 | }
24 |
25 | /**
26 | * Create a middleware function that checks permissions for Fastify routes.
27 | *
28 | * @link https://permix.letstri.dev/docs/integrations/fastify
29 | */
30 | export function createPermix(
31 | {
32 | onForbidden = ({ reply }) => {
33 | reply.status(403).send({ error: 'Forbidden' })
34 | },
35 | }: PermixOptions = {},
36 | ) {
37 | function getPermix(request: FastifyRequest, reply: FastifyReply) {
38 | try {
39 | const permix = request.getDecorator(permixSymbol) as Permix | undefined
40 |
41 | if (!permix) {
42 | throw new Error('Not found')
43 | }
44 |
45 | return pick(permix, ['check', 'dehydrate'])
46 | }
47 | catch {
48 | reply.status(500).send({ error: '[Permix]: Instance not found. Please register the `plugin` function.' })
49 | return null!
50 | }
51 | }
52 |
53 | function plugin(callback: (context: MiddlewareContext) => MaybePromise>): FastifyPluginAsync {
54 | return fp(async (fastify) => {
55 | fastify.decorateRequest(permixSymbol, null)
56 |
57 | fastify.addHook('onRequest', async (request, reply) => {
58 | const permix = createPermixCore(await callback({ request, reply }))
59 | request.setDecorator(permixSymbol, permix)
60 | })
61 | }, {
62 | fastify: '5.x',
63 | name: 'permix',
64 | })
65 | }
66 |
67 | function checkHandler(...params: CheckFunctionParams): RouteHandler {
68 | return async (request, reply) => {
69 | const permix = getPermix(request, reply)
70 |
71 | const hasPermission = permix.check(...params)
72 |
73 | if (!hasPermission) {
74 | await onForbidden({
75 | request,
76 | reply,
77 | ...createCheckContext(...params),
78 | })
79 | }
80 | }
81 | }
82 |
83 | function template(...params: Parameters>) {
84 | return createTemplate(...params)
85 | }
86 |
87 | return {
88 | plugin,
89 | checkHandler,
90 | template,
91 | get: getPermix,
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/docs/content/docs/guide/check.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Check
3 | description: Learn how to check permissions in your application
4 | ---
5 |
6 | ## Overview
7 |
8 | Permix provides two methods for checking permissions: `check` and `checkAsync`. Both methods return a boolean indicating whether the action is allowed.
9 |
10 | ## `check`
11 |
12 | The `check` method allows you to verify if certain actions are permitted. It returns a boolean indicating whether the action is allowed:
13 |
14 | ```ts
15 | permix.check('post', 'create') // returns true/false
16 | ```
17 |
18 | ## Array
19 |
20 | You can check multiple actions at once by passing an array of actions. All actions must be permitted for the check to return true:
21 |
22 | ```ts
23 | // Check if both create and read are allowed
24 | permix.check('post', ['create', 'read']) // returns true if both are allowed
25 | ```
26 |
27 | ## All
28 |
29 | Use the special 'all' keyword to verify if all possible actions for an entity are permitted:
30 |
31 | ```ts
32 | // Check if all actions are allowed for posts
33 | permix.check('post', 'all') // returns true only if all actions are permitted
34 | ```
35 |
36 | ## Any
37 |
38 | Use the special 'any' keyword to verify if any of the actions for an entity are permitted:
39 |
40 | ```ts
41 | // Check if any action is allowed for posts
42 | permix.check('post', 'any') // returns true if any action is permitted
43 | ```
44 |
45 | ## `checkAsync`
46 |
47 | When you need to ensure permissions are ready before checking, use `checkAsync`. This is useful when permissions might be set up asynchronously:
48 |
49 | ```ts
50 | setTimeout(() => {
51 | permix.setup({
52 | post: { create: true }
53 | })
54 | }, 1000)
55 |
56 | await permix.checkAsync('post', 'create') // waits for setup
57 | ```
58 |
59 |
60 | In most cases you should use `check` instead of `checkAsync`. `checkAsync` is useful when you need to ensure permissions are ready before checking, for example in route middleware.
61 |
62 |
63 | ## Data-Based
64 |
65 | You can define permissions that depend on the data being accessed:
66 |
67 | ```ts
68 | permix.setup({
69 | post: {
70 | // Only allow updates if user is the author
71 | update: post => post.authorId === currentUserId,
72 | // Static permission
73 | read: true
74 | }
75 | })
76 |
77 | // Check with data
78 | const post = { id: '1', authorId: 'user1' }
79 |
80 | permix.check('post', 'update', post) // true if currentUserId === 'user1'
81 | ```
82 |
83 |
84 | You still can check permissions without providing the data, but it will return `false` in this case.
85 |
86 |
87 | ## Type Safety
88 |
89 | Permix provides full type safety for your permissions:
90 |
91 | ```ts twoslash
92 | import { createPermix } from 'permix'
93 |
94 | const permix = createPermix<{
95 | post: {
96 | action: 'create' | 'update'
97 | }
98 | }>()
99 |
100 | // @errors: 2345
101 | // This will cause a TypeScript error but will return false
102 | permix.check('post', 'invalid-action')
103 |
104 | permix.check('invalid-entity', 'create')
105 | ```
106 |
--------------------------------------------------------------------------------
/permix/src/core/hooks.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, it, vi } from 'vitest'
2 | import { createHooks } from './hooks'
3 |
4 | describe('createHooks', () => {
5 | it('should register and call hooks', () => {
6 | const { hook, callHook } = createHooks()
7 | const mockFn = vi.fn()
8 |
9 | hook('test', mockFn)
10 | callHook('test', 'arg1', 'arg2')
11 |
12 | expect(mockFn).toHaveBeenCalledWith('arg1', 'arg2')
13 | })
14 |
15 | it('should allow removing hooks', () => {
16 | const { hook, callHook } = createHooks()
17 | const mockFn = vi.fn()
18 |
19 | const remove = hook('test', mockFn)
20 | remove()
21 | callHook('test', 'arg')
22 |
23 | expect(mockFn).not.toHaveBeenCalled()
24 | })
25 |
26 | it('should handle hookOnce correctly', () => {
27 | const { hookOnce, callHook } = createHooks()
28 | const mockFn = vi.fn()
29 |
30 | hookOnce('test', mockFn)
31 | callHook('test', 'arg1')
32 | callHook('test', 'arg2')
33 |
34 | expect(mockFn).toHaveBeenCalledTimes(1)
35 | expect(mockFn).toHaveBeenCalledWith('arg1')
36 | })
37 |
38 | it('should remove specific hooks with removeHook', () => {
39 | const { hook, removeHook, callHook } = createHooks()
40 | const mockFn1 = vi.fn()
41 | const mockFn2 = vi.fn()
42 |
43 | hook('test', mockFn1)
44 | hook('test', mockFn2)
45 | removeHook('test', mockFn1)
46 | callHook('test', 'arg')
47 |
48 | expect(mockFn1).not.toHaveBeenCalled()
49 | expect(mockFn2).toHaveBeenCalledWith('arg')
50 | })
51 |
52 | it('should clear specific hooks with clearHook', () => {
53 | const { hook, clearHook, callHook } = createHooks()
54 | const mockFn = vi.fn()
55 |
56 | hook('test', mockFn)
57 | clearHook('test')
58 | callHook('test', 'arg')
59 |
60 | expect(mockFn).not.toHaveBeenCalled()
61 | })
62 |
63 | it('should clear all hooks with clearAllHooks', () => {
64 | const { hook, clearAllHooks, callHook } = createHooks()
65 | const mockFn1 = vi.fn()
66 | const mockFn2 = vi.fn()
67 |
68 | hook('test1', mockFn1)
69 | hook('test2', mockFn2)
70 | clearAllHooks()
71 | callHook('test1', 'arg')
72 | callHook('test2', 'arg')
73 |
74 | expect(mockFn1).not.toHaveBeenCalled()
75 | expect(mockFn2).not.toHaveBeenCalled()
76 | })
77 |
78 | it('should handle multiple hooks for the same event', () => {
79 | const { hook, callHook } = createHooks()
80 | const mockFn1 = vi.fn()
81 | const mockFn2 = vi.fn()
82 |
83 | hook('test', mockFn1)
84 | hook('test', mockFn2)
85 | callHook('test', 'arg')
86 |
87 | expect(mockFn1).toHaveBeenCalledWith('arg')
88 | expect(mockFn2).toHaveBeenCalledWith('arg')
89 | })
90 |
91 | it('should safely handle calling non-existent hooks', () => {
92 | const { callHook } = createHooks()
93 | expect(() => callHook('nonexistent', 'arg')).not.toThrow()
94 | })
95 |
96 | it('should call hooks with generic', () => {
97 | const { hook, callHook } = createHooks<{
98 | test: (arg: string) => void
99 | }>()
100 |
101 | const mockFn = vi.fn()
102 |
103 | hook('test', mockFn)
104 | callHook('test', 'arg')
105 |
106 | expect(mockFn).toHaveBeenCalledWith('arg')
107 | })
108 | })
109 |
--------------------------------------------------------------------------------
/docs/content/docs/guide/hydration.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Hydration (SSR)
3 | description: Learn how to hydrate and dehydrate permissions in your application
4 | ---
5 |
6 | ## Overview
7 |
8 | Hydration is the process of converting server-side state into client-side state. In Permix, hydration allows you to serialize permissions on the server and restore them on the client.
9 |
10 |
11 | Note that function-based permissions will be converted to `false` during dehydration since functions cannot be serialized to JSON. You should call `setup` method on the client side after hydration to fully restore function-based permissions.
12 |
13 |
14 | ## Usage
15 |
16 | Permix provides two instance methods for handling hydration:
17 |
18 | - `dehydrate()` - Converts the current permissions state into a JSON-serializable format
19 | - `hydrate(state)` - Restores permissions from a previously dehydrated state
20 |
21 | ```ts twoslash
22 | import { createPermix } from 'permix'
23 |
24 | const permix = createPermix<{
25 | post: {
26 | dataType: { isPublic: boolean }
27 | action: 'create' | 'read'
28 | }
29 | }>()
30 |
31 | // Set up initial permissions
32 | permix.setup({
33 | post: {
34 | create: true,
35 | read: post => !!post?.isPublic
36 | }
37 | })
38 |
39 | // Dehydrate permissions to JSON
40 | const state = permix.dehydrate()
41 | // Result: { post: { create: true, read: false } }
42 |
43 | // Later, hydrate permissions from the state
44 | permix.hydrate(state)
45 | ```
46 |
47 | ## Server-Side Rendering
48 |
49 | Hydration is particularly useful in server-side rendering scenarios where you want to transfer permissions from the server to the client:
50 |
51 | ```ts twoslash
52 | // Express server
53 | import express from 'express'
54 | import { createPermix } from 'permix'
55 |
56 | const app = express()
57 | const permix = createPermix<{
58 | post: {
59 | action: 'create' | 'read'
60 | }
61 | }>()
62 |
63 | app.get('/', (req, res) => {
64 | // Setup permissions on the server
65 | permix.setup({
66 | post: {
67 | create: true,
68 | read: true
69 | }
70 | })
71 |
72 | // Dehydrate permissions for client
73 | const dehydratedState = permix.dehydrate()
74 |
75 | // Send HTML with embedded permissions data
76 | res.send(`
77 |
78 |
79 |
80 |
83 |
84 |
85 |
86 |
104 |
105 |
106 | `)
107 | })
108 | ```
109 |
--------------------------------------------------------------------------------
/docs/content/docs/quick-start.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Quick Start
3 | description: A quick start guide to start using Permix and validating your permissions
4 | icon: RiPlayLargeLine
5 | ---
6 |
7 | ## Try Permix
8 |
9 | Want to explore Permix before installing? Try our interactive sandbox environment where you can experiment with type-safe permissions management right in your browser.
10 |
11 | [Try Permix Sandbox](https://stackblitz.com/edit/permix-sandbox?file=src%2Fmain.ts&terminal=dev)
12 |
13 | ## Installation
14 |
15 |
16 |
17 |
18 |
19 | ### Install Permix
20 |
21 | Typically you'll need to install Permix using your package manager:
22 |
23 | ```package-install
24 | permix
25 | ```
26 |
27 |
28 |
29 |
30 |
31 | ### Create an instance
32 |
33 | To create a base instance, you need to provide a schema as a generic type to `createPermix` function that defines your permissions:
34 |
35 | ```ts title="/lib/permix.ts"
36 | import { createPermix } from 'permix'
37 |
38 | export const permix = createPermix<{
39 | post: {
40 | action: 'create' | 'read' | 'update' | 'delete'
41 | }
42 | }>()
43 |
44 | // ...
45 | ```
46 |
47 | Learn more about features and configuration of instances in the [instance guide](/docs/guide/instance).
48 |
49 |
50 |
51 |
52 |
53 | ### Setup your permissions
54 |
55 | You can setup your permissions by calling `setup` method on your instance in any place you want:
56 |
57 | ```ts title="/lib/permix.ts"
58 | // ...
59 |
60 | // Call setupPermissions in your application
61 | export function setupPermissions() {
62 | permix.setup({
63 | post: {
64 | create: true,
65 | read: true,
66 | update: true,
67 | delete: false,
68 | },
69 | })
70 | }
71 | ```
72 |
73 |
74 |
75 |
76 |
77 | ### Check permissions
78 |
79 | After setup, you can use `check` method to check available permissions:
80 |
81 | ```ts
82 | permix.check('post', 'create') // true
83 | ```
84 |
85 |
86 |
87 |
88 |
89 | ### Finish
90 |
91 | That's it! 🎉
92 |
93 | You've now got a basic setup of Permix. The next step is to learn more about 3 core concepts of Permix:
94 |
95 | - [Instance](/docs/guide/instance)
96 | - [Setup](/docs/guide/setup)
97 | - [Check](/docs/guide/check)
98 | - Of course, do not hesitate to read other guide pages.
99 |
100 |
101 |
102 |
103 |
104 | ## Integrations
105 |
106 | Continuing from the quick start, you can now explore how Permix integrates with other libraries and frameworks.
107 |
108 |
109 |
110 |
111 | Integration with React via provider and hook.
112 |
113 |
114 |
115 | Integration with Vue via plugin and composable.
116 |
117 |
118 |
119 | Integration with Node.js via middleware.
120 |
121 |
122 |
123 | Integration with Hono via middleware.
124 |
125 |
126 |
127 | Integration with Express via middleware.
128 |
129 |
130 |
131 | Integration with tRPC via middleware.
132 |
133 |
134 |
135 |
--------------------------------------------------------------------------------
/docs/app/(home)/page.tsx:
--------------------------------------------------------------------------------
1 | import { CodeBlock, Pre } from 'fumadocs-ui/components/codeblock'
2 | import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
3 | import InitCode from './code/init.mdx'
4 | import SetupCode from './code/setup.mdx'
5 | import UsageCode from './code/usage.mdx'
6 |
7 | export default function Page() {
8 | return (
9 |
10 |
11 |
12 |
13 | Permix
14 |
15 |
16 | A lightweight, framework-agnostic, type-safe permissions management library for client-side and server-side JavaScript applications.
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | {[
47 | {
48 | emoji: '🔒',
49 | title: 'Type-safe',
50 | description:
51 | 'Permix is built with TypeScript in mind, providing full type safety and autocompletion for your permissions system.',
52 | },
53 | {
54 | emoji: '🌐',
55 | title: 'Framework Agnostic',
56 | description:
57 | 'Use Permix with any JavaScript framework or runtime - it works everywhere from React to Node.js.',
58 | },
59 | {
60 | emoji: '🛠️',
61 | title: 'Simple DX',
62 | description:
63 | 'Permix provides an intuitive API that makes managing permissions straightforward and easy to understand.',
64 | },
65 | ].map((item) => {
66 | return (
67 |
71 |
72 | {item.emoji}
73 |
74 |
{item.title}
75 |
80 |
81 | )
82 | })}
83 |
84 |
85 | )
86 | }
87 |
--------------------------------------------------------------------------------
/docs/content/docs/integrations/react.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: React
3 | description: Learn how to use Permix with React applications
4 | ---
5 |
6 | ## Overview
7 |
8 | Permix provides official React integration through the `PermixProvider` component and `usePermix` hook. This allows you to manage permissions reactively in your React app.
9 |
10 |
11 | Before getting started with React integration, make sure you've completed the initial setup steps in the [Quick Start](/docs/quick-start) guide.
12 |
13 |
14 |
15 |
16 |
17 |
18 | ## Setup
19 |
20 | First, wrap your application with the `PermixProvider`:
21 |
22 | ```tsx title="App.tsx"
23 | import { PermixProvider } from 'permix/react'
24 | import { permix } from './lib/permix'
25 |
26 | function App() {
27 | return (
28 |
29 |
30 |
31 | )
32 | }
33 | ```
34 |
35 |
36 | Remember to always pass the same Permix instance to both the `PermixProvider` and `usePermix` hook to maintain type safety.
37 |
38 |
39 |
40 |
41 |
42 |
43 | ## Hook
44 |
45 | For checking permissions in your components, you can use the `usePermix` hook. And to avoid importing the hook and Permix instance in every component, you can create a custom hook:
46 |
47 | ```tsx title="hooks/use-permissions.ts"
48 | import { usePermix } from 'permix/react'
49 | import { permix } from '../lib/permix'
50 |
51 | export function usePermissions() {
52 | return usePermix(permix)
53 | }
54 | ```
55 |
56 |
57 |
58 |
59 |
60 | ## Components
61 |
62 | If you prefer using components, you can import the `createComponents` function from `permix/react` and create checking components:
63 |
64 | ```ts title="lib/permix.ts"
65 | import { createComponents } from 'permix/react'
66 |
67 | // ...
68 |
69 | export const { Check } = createComponents(permix)
70 | ```
71 |
72 | And then you can use the `Check` component in your components:
73 |
74 | ```tsx title="page.tsx"
75 | export default function Page() {
76 | return (
77 | Will show this if a user doesn't have permission
} // Will show this if a user doesn't have permission
82 | reverse // Will flip the logic of the permission check
83 | >
84 | Will show this if a user has permission
85 |
86 | )
87 | }
88 | ```
89 |
90 |
91 |
92 |
93 |
94 | ## Usage
95 |
96 | Use the `usePermix` hook and checking components in your components:
97 |
98 | ```tsx title="page.tsx"
99 | import { usePermix } from 'permix/react'
100 | import { permix } from './lib/permix'
101 | import { Check } from './lib/permix-components'
102 |
103 | export default function Page() {
104 | const post = usePost()
105 | const { check, isReady } = usePermix(permix)
106 |
107 | if (!isReady) {
108 | return
119 | )}
120 |
121 | Can I create a post inside the Check component?
122 |
123 |
124 | )
125 | }
126 | ```
127 |
128 |
129 |
130 |
131 |
132 | ## Example
133 |
134 | You can find the example of the React integration [here](https://github.com/letstri/permix/tree/main/examples/react).
135 |
136 |
137 |
138 |
139 |
--------------------------------------------------------------------------------
/docs/content/docs/integrations/vue.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Vue
3 | description: Learn how to use Permix with Vue applications
4 | ---
5 |
6 | ## Overview
7 |
8 | Permix provides official Vue integration through the `permixPlugin` and `usePermix` composable. This allows you to manage permissions reactively in your Vue app.
9 |
10 |
11 | Before getting started with Vue integration, make sure you've completed the initial setup steps in the [Quick Start](/docs/quick-start) guide.
12 |
13 |
14 |
15 |
16 |
17 |
18 | ## Setup
19 |
20 | First, install the Vue plugin in your application:
21 |
22 | ```ts title="main.ts"
23 | import { createApp } from 'vue'
24 | import { permixPlugin } from 'permix/vue'
25 | import { permix } from './lib/permix'
26 | import App from './App.vue'
27 |
28 | const app = createApp(App)
29 |
30 | app.use(permixPlugin, { permix })
31 | app.mount('#app')
32 | ```
33 |
34 |
35 | Remember to always use the same Permix instance here and in the `usePermix` composable to maintain type safety.
36 |
37 |
38 |
39 |
40 |
41 |
42 | ## Composable
43 |
44 | For checking permissions in your components, you can use the `usePermix` composable. And to avoid importing the composable and Permix instance in every component, you can create a custom composable:
45 |
46 | ```ts title="composables/use-permissions.ts"
47 | import { usePermix } from 'permix/vue'
48 | import { permix } from './lib/permix'
49 |
50 | export function usePermissions() {
51 | return usePermix(permix)
52 | }
53 | ```
54 |
55 |
56 |
57 |
58 |
59 | ## Components
60 |
61 | If you prefer using components, you can import the `createComponents` function from `permix/vue` and create checking components:
62 |
63 | ```ts title="lib/permix.ts"
64 | import { createComponents } from 'permix/vue'
65 |
66 | // ...
67 |
68 | export const { Check } = createComponents(permix)
69 | ```
70 |
71 | And then you can use the `Check` component in your templates:
72 |
73 | ```vue title="page.vue"
74 |
75 |
79 |
80 | Will show this if a user have permission
81 |
82 | Will show this if a user doesn't have permission
83 |
84 |
85 |
86 | ```
87 |
88 |
89 |
90 |
91 |
92 | ## Usage
93 |
94 | Use the `usePermix` composable in your components to check permissions:
95 |
96 | ```vue title="page.vue"
97 |
107 |
108 |
109 |
110 |
Loading permissions...
111 |
112 |
113 |
You don't have permission to edit this post
114 |
115 |
116 | Can I edit this post?
117 |
118 | You don't have permission to edit this post
119 |
120 |
121 |
122 |
123 | ```
124 |
125 |
126 |
127 |
128 |
129 | ## Example
130 |
131 | You can find the example of the Vue integration [here](https://github.com/letstri/permix/tree/main/examples/vue).
132 |
133 |
134 |
135 |
136 |
--------------------------------------------------------------------------------
/docs/content/docs/integrations/solid.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Solid
3 | description: Learn how to use Permix with Solid applications
4 | ---
5 |
6 | ## Overview
7 |
8 | Permix provides official Solid integration through the `PermixProvider` component and `usePermix` hook. This allows you to manage permissions reactively in your Solid app.
9 |
10 |
11 | Before getting started with Solid integration, make sure you've completed the initial setup steps in the [Quick Start](/docs/quick-start) guide.
12 |
13 |
14 |
15 |
16 |
17 |
18 | ## Setup
19 |
20 | First, wrap your application with the `PermixProvider`:
21 |
22 | ```tsx title="App.tsx"
23 | import { PermixProvider } from 'permix/solid'
24 | import { permix } from './lib/permix'
25 |
26 | function App() {
27 | return (
28 |
29 |
30 |
31 | )
32 | }
33 | ```
34 |
35 |
36 | Remember to always pass the same Permix instance to both the `PermixProvider` and `usePermix` hook to maintain type safety.
37 |
38 |
39 |
40 |
41 |
42 |
43 | ## Hook
44 |
45 | For checking permissions in your components, you can use the `usePermix` hook. And to avoid importing the hook and Permix instance in every component, you can create a custom utility:
46 |
47 | ```tsx title="hooks/use-permissions.ts"
48 | import { usePermix } from 'permix/solid'
49 | import { permix } from '../lib/permix'
50 |
51 | export function usePermissions() {
52 | return usePermix(permix)
53 | }
54 | ```
55 |
56 |
57 |
58 |
59 |
60 | ## Components
61 |
62 | If you prefer using components, you can import the `createComponents` function from `permix/solid` and create checking components:
63 |
64 | ```ts title="lib/permix.ts"
65 | import { createComponents } from 'permix/solid'
66 |
67 | // ...
68 |
69 | export const { Check } = createComponents(permix)
70 | ```
71 |
72 | And then you can use the `Check` component in your components:
73 |
74 | ```tsx title="page.tsx"
75 | export default function Page() {
76 | return (
77 | Will show this if a user doesn't have permission} // Will show this if a user doesn't have permission
82 | reverse // Will flip the logic of the permission check
83 | >
84 | Will show this if a user has permission
85 |
86 | )
87 | }
88 | ```
89 |
90 |
91 |
92 |
93 |
94 | ## Usage
95 |
96 | Use the `usePermix` hook and checking components in your components:
97 |
98 | ```tsx title="page.tsx"
99 | import { usePermix } from 'permix/solid'
100 | import { permix } from './lib/permix'
101 | import { Check } from './lib/permix-components'
102 |
103 | export default function Page() {
104 | const post = usePost()
105 | const { check, isReady } = usePermix(permix)
106 |
107 | const canEdit = () => check('post', 'edit', post)
108 |
109 | return (
110 | <>
111 | {!isReady()
112 | ?
Loading permissions...
113 | : (
114 |
115 | {canEdit() ? (
116 |
117 | ) : (
118 |
You don't have permission to edit this post
119 | )}
120 |
121 | Can I create a post inside the Check component?
122 |
123 |