npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@eduvia-app/nuxt-can

v1.1.1

Published

Nuxt directives (`v-can`, `v-cannot`) to layer permissions without touching business v-ifs.

Downloads

53

Readme

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 @eduvia-app/nuxt-can
# or
npx nuxi module add @eduvia-app/nuxt-can

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

// nuxt.config.ts
import NuxtCan from '@eduvia-app/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="__can__('employee', 'edit') && (isReady)">Edit profile</button>
<p v-if="!(__can__('employee', 'edit'))">Access denied</p>

Directive Patterns

Guard entire v-if / v-else-if / v-else chains

Once the first branch of a conditional chain carries v-can, the transformer automatically mirrors that guard (with the same permission path) on every subsequent v-else-if and v-else. You can still repeat the directive manually for clarity, but it’s no longer required.

<div v-if="status === 'draft'" v-can="can.foo.bar">
  Draft state
</div>
<div v-else-if="status === 'pending'">
  Pending state
</div>
<div v-else>
  Fallback state
</div>
<div v-cannot="can.foo.bar">
  Missing permission
</div>

Transforms into:

<div v-if="__can__('foo', 'bar') && (status === 'draft')">
  Draft state
</div>
<div v-else-if="__can__('foo', 'bar') && (status === 'pending')">
  Pending state
</div>
<div v-else-if="__can__('foo', 'bar')">
  Fallback state
</div>
<div v-if="!__can__('foo', 'bar')">
  Missing permission
</div>

Pass arguments to v-cannot

v-cannot can mirror the permission expression used by its matching v-can by adding the same argument (v-cannot="can.foo.bar"). When no argument is specified, the directive must immediately follow the preceding v-can block so the transformer can re-use that context.

<button v-can="can.contract.submit">Submit contract</button>
<p v-cannot="can.contract.submit">Contact your admin to unlock submissions.</p>

<template>
  <button v-if="isReady" v-can="can.contract.edit">Edit</button>
  <p v-cannot>Only editors can update this contract.</p>
</template>

<!-- Need to wrap the fallback? pass the expression explicitly -->
<div class="notice">
  <p v-cannot="can.contract.edit">Only editors can update this contract.</p>
</div>

Both v-cannot branches above compile to v-if="!__can__('contract', 'submit')" and v-if="!__can__('contract', 'edit')" respectively.

Keep v-cannot next to its v-can

When v-cannot omits an expression, it must immediately follow the guarded block:

<div v-if="isReady" v-can="can.foo.bar">Ready!</div>
<p v-cannot>Not allowed</p> <!-- ✅ adjacent, guard is inferred -->

<div>
  <p v-cannot>Not allowed</p> <!-- ❌ wrapped, missing explicit expression -->
</div>

<div>
  <p v-cannot="can.foo.bar">Not allowed</p> <!-- ✅ wrapper + explicit permission -->
</div>

Usage Rules & Errors

The transformer validates every template and throws descriptive errors when:

  • v-can expressions differ within the same v-if / v-else-if / v-else block (the guard is mirrored automatically, but mixed expressions are disallowed).
  • v-cannot without an argument is separated from its originating v-can.
  • v-cannot mixes in modifiers or a v-if condition (keep it standalone).
  • 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:).