# Vue

Source: https://permdock.com/docs/adapters/vue

permdock/vue maps the snapshot-backed provider, hook and guard from the React adapter onto a Vue plugin, composables and a component.

## Purpose [#purpose]

Same model as [React](/docs/adapters/react): the server serialises `permdock.snapshot()`, the client evaluates portable grants locally with the core evaluator, and closure grants go to the batched decision endpoint. `permdock/vue` exposes that through Vue's own primitives: an app plugin for installation, composables returning refs, and a guard component. No policy or server module is imported.

## API [#api]

```ts
// main.ts
import { createApp } from "vue";
import { permdockPlugin } from "permdock/vue";

createApp(App)
  .use(permdockPlugin, { snapshot, endpoint: "/api/permdock" })
  .mount("#app");
```

```vue
<script setup lang="ts">
import { usePermDock, usePermission } from "permdock/vue";
import { permissions } from "@/permissions";

const props = defineProps<{ post: Post }>();
const permdock = usePermDock(); // can / decide / status / invalidate
const { allowed, status } = usePermission(
  permissions.post.update,
  () => props.post,
); // refs; refetch when post.id changes
</script>

<template>
  <Protected :permission="permissions.post.update" :data="post">
    <EditButton />
    <template #pending><Skeleton /></template>
    <template #fallback><Locked /></template>
  </Protected>
</template>
```

| Export | Role |
| --- | --- |
| `permdockPlugin` | `app.use(permdockPlugin, options)` with `snapshot`, `endpoint` (`false` denies endpoint-only checks with `server-only`), `snapshotUrl`, `approvals`, `tenant`, `fetch`, `headers`, `maxAge` and `verifier`. `tenant`, `headers` and `verifier` may be a ref or getter: `headers` is read on every request, and a changed `tenant` calls `refresh({ tenant })`. Provides the store through `provide`/`inject`. `snapshot` may be a ref or getter (the store re-hydrates when it changes) or a promise (the store is `pending` until it settles; await the same promise in an async `setup` under `<Suspense>`). |
| `usePermDock` | Returns the snapshot-backed instance with reactive `status`. |
| `usePermission` | Accepts the reference and the resource as a `MaybeRefOrGetter`. Pass a getter or ref so the answer tracks `id` changes; a plain object is read once, because Vue cannot observe replacement of a plain argument. Returns `allowed`, `status`, `decision` as refs. |
| `Protected` | Component with `permission`, `data` and optional `tenant` props and `default`, `pending`, `fallback` slots. The default slot receives the granted `Decision`. |
| `usePermissions`, `useFilter` | Composables mirroring the React hooks: several references against one getter, and `filter` over a getter of rows; results are computed refs. |
| `useTenant`, `useMemberships`, `useRoles`, `useAssignableRoles`, `useAssignablePermissions` | Composables returning refs (`tenant`, `tenants`, `memberships`, `roles`, `assignable`, the assignable `Permission[]`) plus `switchTo`; the same semantics as React ([UI](/docs/concepts/ui), [tenancy](/docs/concepts/tenancy)). |
| `useApproval`, `useSubject` | Composables for the `approval-required` flow and the snapshot's subject summary (`simulated` included). |

For Nuxt, `createPermDockUnplugin.vite()` runs collect ([unplugin](/docs/cli/unplugin)). The snapshot arrives from a Nitro route built with the [server kernel](/docs/adapters/server-kernel); the client uses this Vue adapter. There is no `permdock/nuxt` entry and no Nuxt-specific composable. Gating is done with composables and `Protected`; there is no `v-protected` directive.

### PermissionBoundary [#permissionboundary]

`PermissionBoundary` catches `PermDockDeniedError` and `PermDockApprovalRequiredError` thrown below it (for example by `permdock.assert` in a render function) through `onErrorCaptured`, and renders the `denied` slot, or the `approval` slot for an approval request. Both slots receive `{ outcome, permission, token?, retry }`; `usePermissionBoundary()` returns the same state inside them. Any other error propagates to the next handler.

```ts
h(PermissionBoundary, null, {
  default: () => h(PublishPanel),
  denied: ({ permission }) => h("p", `You cannot ${permission}.`),
});
```

## Request lifecycle [#request-lifecycle]

1. The server creates a request-scoped `PermDock`, calls `snapshot()` and embeds the JSON in the page or a session endpoint.
2. `permdockPlugin` validates the snapshot and creates one client store per app instance (SSR-safe: no module-level singleton).
3. `usePermission` computes the cache key from `reference.key` plus the resource `id`. Portable grants answer synchronously; non-portable grants enqueue a batched AuthZEN `evaluations` request to `endpoint`.
4. Answers are reactive; `invalidate(permissions.post)` drops cached answers under the namespace and pending components re-request.

During SSR (Vite SSR or Nuxt) the store is created per request and hydrated on the client from the same snapshot, so the first client render matches the server (the first-render mismatch permix had in its Vue adapter came from subscribing inside an effect instead of during setup).

## What it validates [#what-it-validates]

The [shared adapter contract](/docs/adapters) applies: an invalid snapshot puts the store in `server-only` mode, the arity of the permission check (collection versus instance actions) is a type error, and resource data is never validated on the client because the decision endpoint validates posted data at the boundary and the API re-checks every mutation. Denials follow the React adapter: `allowed` is `false`, `decision` explains why, and `Protected` renders the `fallback` slot with the `Decision`.

## Example app [#example-app]

`apps/examples/vue`: Vue 3 with a snapshot from `snapshotFor(policy, memberUser)`, `permdockPlugin`, `Protected` on a portable ownership check, a `usePermission` delete button and a `PermissionBoundary` around a panel that calls `assert`. Vite serves `http://127.0.0.1:3481/`. The page shows `edit`, `ask to delete` and `no post.publish`.

## Related standards [#related-standards]

* [AuthZEN](/docs/standards/authzen), [Problem Details](/docs/standards/problem-details), [Standard Schema](/docs/standards/standard-schema).
* Concepts: [snapshots](/docs/concepts/snapshots), [decisions](/docs/concepts/decisions).
