svelteadapters

Svelte: explicit stores and actions, not context

rbac-fs/svelte gives you two factory functions — createPermissionStore(client) and createCanAction(client) — both taking the client as an explicit argument rather than reaching for Svelte's setContext/getContext.

The two primitives

<script lang="ts">
  import { RBACClient } from 'rbac-fs/client';
  import { createCanAction, createPermissionStore } from 'rbac-fs/svelte';

  export let ownerId: string;

  // Fetch once at app init; share the client across components via a
  // module-level export or your own Svelte context, rather than
  // recreating it per-component.
  const client = new RBACClient(await fetch('/me/permissions').then((r) => r.json()));

  const permissions = createPermissionStore(client);
  const can = createCanAction(client);
</script>

<!-- use:can toggles display:none, same behavior as rbac-fs/vue's v-can -->
<button use:can={{ a: 'invoice', I: 'approve' }}>Approve invoice</button>

<button use:can={{ a: 'expense-report', I: 'approve', context: { owner_id: ownerId } }}>
  Approve my expense report
</button>

<!-- $permissions is the store's auto-subscribed value: client.can itself -->
<p>Imperative check: {$permissions('invoice', 'approve') ? 'allowed' : 'denied'}</p>

Why not Svelte context

Svelte's context API is a natural fit for dependency injection across a component tree, and it's a reasonable choice — but it's implicit: a component using getContext() has no static, greppable link to where the value came from. rbac-fs/svelte's factory functions take the client as a plain argument instead, which means the dependency is visible at every call site and traceable with a simple text search, at the cost of you (or your own thin wrapper) being responsible for sharing one client instance across components, typically via a module-level export.

The store is literally client.can

createPermissionStore(client) wraps client.can in Svelte's store contract so $permissions(...) works with auto-subscription — it isn't reactive to anything changing over time (a RBACClient's snapshot is immutable for its lifetime), it's a store purely so it's ergonomic to call from a template with the $ prefix.

For a runnable, headless verification, see Tutorial: verifying the Svelte adapter headlessly.