2025-11-14 06:31:50 +01:00
2025-11-14 00:17:13 +01:00
2025-11-14 00:17:13 +01:00
2025-11-14 06:31:50 +01:00
2025-11-14 06:31:50 +01:00
2025-11-14 00:17:13 +01:00
2025-11-14 00:17:13 +01:00
2025-11-14 00:17:13 +01:00
2025-11-14 00:17:13 +01:00
2025-11-14 06:21:19 +01:00
2025-11-14 00:17:13 +01:00
2025-11-14 06:20:22 +01:00
2025-11-14 06:19:50 +01:00
2025-11-14 00:17:13 +01:00

nuxt-can

npm version npm downloads License Nuxt

nuxt-can ships two Vue directives (v-can, v-cannot) so you can encode permissions directly in Nuxt templates. Each directive is transformed at build time into a composable __can__ call provided by your app, keeping the runtime lean, tree-shake friendly, and fully typed.

Highlights

  • Cleanly adds permissions to existing templates without rewriting your business v-ifs
  • Compile-time transform of v-can / v-cannot into v-if guards
  • Smart merge with existing v-if conditions (no extra wrappers)
  • Auto-generated can proxy with types derived from your permissions map
  • Pluggable import of the host __can__ function (stores, APIs, etc.)
  • Helpful DX errors for unsupported directive shapes

Quick Start

Install the module in your Nuxt app:

npm install nuxt-can
# or
npx nuxi module add nuxt-can

Enable it inside nuxt.config.ts and describe the permissions tree:

// nuxt.config.ts
import NuxtCan from 'nuxt-can'

export default defineNuxtConfig({
  modules: [NuxtCan],
  nuxtCan: {
    permissions: {
      employee: ['view', 'edit'],
      contract: ['create'],
    },
    canFunctionImport: '~/permissions/can', // path to your __can__ implementation
  },
})

Provide the __can__ implementation referenced above:

// permissions/can.ts
const permissionsStore = usePermissionsStore()

export function __can__(path: string[]) {
  return permissionsStore.check(path.join('.'))
}

Now you can write directives that stay type-safe:

<template>
  <button v-can="can.employee.view">View profile</button>
  <button v-if="isReady" v-can="can.employee.edit">
    Edit profile
  </button>
  <p v-cannot>Access denied</p>
</template>

…and the compiler rewrites them into plain conditionals:

<button v-if="__can__(['employee', 'view'])">View profile</button>
<button v-if="(isReady) && __can__(['employee', 'edit'])">Edit profile</button>
<p v-if="!(__can__(['employee', 'edit']))">Access denied</p>

Usage Rules & Errors

The transformer validates every template and throws descriptive errors when:

  • v-cannot does not immediately follow its matching v-can.
  • v-can appears on an element already using v-else / v-else-if.
  • v-cannot uses an argument, modifiers, or a v-if condition.
  • Multiple v-cannot blocks exist for the same v-can.
  • The expression is not a static dotted path like can.resource.action.

Generated Types

The permissions map feeds a generated types/nuxt-can.d.ts declaration that augments:

  • ComponentCustomProperties with can, $can, and __can__.
  • NuxtApp with $can and $__can__.
  • Runtime typings for the #build/nuxt-can/can-import.mjs bridge.

No extra setup is required for editors or strict TypeScript projects.

Why v-can?

Retrofitting authorization into an existing codebase often means revisiting every v-if to sprinkle permission checks alongside business logic. That makes templates harder to read, increases the risk of regressions, and couples security rules with UI state management. v-can and v-cannot isolate the permission layer: you keep your original conditions untouched while the transformer injects the __can__ guards for you. As a result, business logic stays readable, authorization lives in one place, and code reviews can focus on either concern without stepping on each other.

Playground

Run npm run dev to explore the playground app located in /playground. It demonstrates:

  • Stacked v-can / v-cannot pairs.
  • Interaction with existing v-ifs and v-fors.
  • Template blocks that share the same permission guard.
  • A live permission summary powered by the injected __can__ function.

Feel free to wire your own ~/playground/permissions/__can__.ts to mimic a real backend.

Local Development

# Install dependencies
npm install

# Prepare type stubs and the playground
npm run dev:prepare

# Playground dev server
npm run dev

# Build the playground
npm run dev:build

# Lint & tests
npm run lint
npm run test
npm run test:watch

# Type checks (module + playground)
npm run test:types

# Release pipeline
npm run release

Contributing

  1. Fork & clone the repo.
  2. Run npm run dev:prepare once to scaffold stubs.
  3. Use the playground (npm run dev) to reproduce issues.
  4. Add tests under test/ and fixtures under test/fixtures/*.
  5. Open a PR following Conventional Commits (e.g. feat:, fix:).

Description
No description provided
Readme MIT 157 KiB
Languages
TypeScript 95.8%
Vue 2.9%
JavaScript 1.3%