Permix

Effect

Learn how to use Permix with Effect

Overview

Permix provides a first-class Effect integration that exposes your permissions as an Effect service via a Context tag and Layer constructors. Checks return Effect<boolean>, so you decide how to handle denial — in any Effect program, HTTP handler, or RPC service.

Unlike the server integrations (which lock the surface down to check / dehydrate / template), the Effect integration exposes the full Permix instance — including setup, hydrate, isReady, hooks, and getRules — so it works equally well in client-side or long-running Effect programs.

The integration depends only on effect (no @effect/platform required), so it works everywhere Effect runs.

Before getting started with the Effect integration, make sure you've completed the initial setup steps in the Quick Start guide.

Installation

npm install permix effect

Setup

Create your Permix Effect instance using createPermix:

import { Effect } from 'effect'
import { createPermix } from 'permix/effect'

const permix = createPermix<{
  post: ['create', 'read', 'update']
  user: ['delete']
}>()

Providing Rules

Static rules

Use permix.layer(rules) to provide a Layer with fixed rules:

const PermixLive = permix.layer({
  post: { create: true, read: true, update: false },
  user: { delete: false },
})

Dynamic rules from another service

Use permix.layerSetup(effect) when rules depend on other services (e.g. the current user). Requirements flow through automatically:

import { Context } from 'effect'

interface User { id: string; role: 'admin' | 'member' }
class CurrentUser extends Context.Tag('CurrentUser')<CurrentUser, User>() {}

const PermixLive = permix.layerSetup(Effect.gen(function* () {
  const user = yield* CurrentUser
  return {
    post: {
      create: user.role === 'admin',
      read: true,
      update: user.role === 'admin',
    },
    user: { delete: user.role === 'admin' },
  }
}))

Checking Permissions

permix.check(...) returns Effect<boolean>. Use it inside Effect.gen:

const program = Effect.gen(function* () {
  if (yield* permix.check('post.create')) {
    return 'created'
  }
  return yield* Effect.fail(new Error('Forbidden'))
})

Effect.runPromise(program.pipe(Effect.provide(PermixLive)))

Dehydrating Permissions

Serialize the current rules into a JSON-safe object:

const state = Effect.gen(function* () {
  return yield* permix.dehydrate()
})

// { post: { create: true, read: true, update: false }, user: { delete: false } }

Using Templates

Create reusable permission rule sets with template:

const adminTemplate = permix.template({
  post: { create: true, read: true, update: true },
  user: { delete: true },
})

const PermixLive = permix.layer(adminTemplate())

Runtime Updates

Because the Effect integration exposes the full Permix instance, you can update permissions at runtime — useful on the client or in long-running programs where rules change after the initial setup (e.g. a role change).

Start from an empty layer (call permix.layer() with no rules) and configure it later with setup:

const program = Effect.gen(function* () {
  // Not ready yet
  yield* permix.isReady() // false

  // Configure once permissions are available
  yield* permix.setup({
    post: { create: true, read: true, update: false },
    user: { delete: false },
  })

  yield* permix.isReady() // true
  return yield* permix.check('post.create')
})

Effect.runPromise(program.pipe(Effect.provide(permix.layer())))

Hydration

Restore previously serialized permissions (e.g. from SSR or storage) with hydrate:

const program = Effect.gen(function* () {
  yield* permix.hydrate(serializedState)
  return yield* permix.check('post.read')
})

hydrate does not mark the instance ready and cannot restore function-based rules. Follow with setup using the full rule set when you need dynamic checks or isReady. See the Hydration guide.

Reacting to Changes

Register hooks that fire when permissions are set up. hook yields a function that removes the listener:

const program = Effect.gen(function* () {
  const remove = yield* permix.hook('setup', () => {
    console.log('Permissions updated')
  })

  // ...later
  remove()
})

Inspecting Rules

Read the current rules object (or null if not set up yet) with getRules:

const rules = Effect.gen(function* () {
  return yield* permix.getRules()
})

Full Example

import { Context, Effect, Layer } from 'effect'
import { createPermix } from 'permix/effect'

// 1. Define permissions
const permix = createPermix<{
  post: ['create', 'read', 'update']
  user: ['delete']
}>()

// 2. Define a service for the current user
interface User { id: string; role: 'admin' | 'member' }
class CurrentUser extends Context.Tag('CurrentUser')<CurrentUser, User>() {}

// 3. Build a Layer that derives rules from CurrentUser
const PermixLive = permix.layerSetup(Effect.gen(function* () {
  const user = yield* CurrentUser
  return {
    post: {
      create: user.role === 'admin',
      read: true,
      update: user.role === 'admin',
    },
    user: { delete: user.role === 'admin' },
  }
}))

// 4. Use in a program
const program = Effect.gen(function* () {
  const canCreate = yield* permix.check('post.create')
  const canRead = yield* permix.check('post.read')
  return { canCreate, canRead }
})

// 5. Provide the layers and run
const CurrentUserLive = Layer.succeed(CurrentUser, { id: '1', role: 'admin' })

Effect.runPromise(
  program.pipe(
    Effect.provide(PermixLive),
    Effect.provide(CurrentUserLive),
  ),
).then(console.log)
// { canCreate: true, canRead: true }

Multiple Instances

You can create multiple Permix instances with different id values. They coexist on the same Effect context without conflict:

const admin = createPermix<Definition>({ id: 'admin' })
const guest = createPermix<Definition>({ id: 'guest' })

const program = Effect.gen(function* () {
  const adminCreate = yield* admin.check('post.create')
  const guestCreate = yield* guest.check('post.create')
  return { adminCreate, guestCreate }
})

program.pipe(
  Effect.provide(admin.layer({ ... })),
  Effect.provide(guest.layer({ ... })),
)

On this page