snipe-auth-rbac 0.6.3 → 0.6.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/admin/transport.ts","../../src/admin/hooks.tsx","../../src/admin/PermissionsMatrix.tsx","../../src/admin/RolesList.tsx","../../src/admin/InviteMemberForm.tsx"],"sourcesContent":["/**\n * Default Supabase implementation of the admin transport. Hits the\n * package's tables in the `rbac` schema directly via `.schema('rbac').\n * from(...)` and the auth admin endpoint for invites.\n *\n * Adopters must add `rbac` to their PostgREST exposed-schemas list\n * (Supabase Studio → Settings → API → Exposed schemas) for these\n * calls to reach the tables.\n *\n * Projects that route admin writes through their own backend\n * (e.g. for audit logging or extra validation) skip this and\n * implement `AdminTransport` themselves.\n */\n\nimport type { Action, ResourceDescriptor } from \"../types.js\";\n\nimport type {\n AdminCompany,\n AdminMember,\n AdminResourceDependency,\n AdminRole,\n AdminRolePermission,\n AdminRolePermissionOverride,\n AdminTransport,\n} from \"./types.js\";\n\ninterface RbacSchemaClient {\n from(table: string): {\n select: (cols: string) => {\n eq: (col: string, value: unknown) => any;\n is: (col: string, value: unknown) => any;\n order: (col: string, opts?: { ascending: boolean }) => any;\n };\n insert: (row: Record<string, unknown>) => {\n select: (cols: string) => { single: () => any };\n };\n update: (patch: Record<string, unknown>) => {\n eq: (col: string, value: unknown) => {\n select: (cols: string) => { single: () => any };\n };\n };\n upsert: (\n row: Record<string, unknown> | Array<Record<string, unknown>>,\n opts?: { onConflict: string },\n ) => Promise<{ error: { message: string } | null }>;\n delete: () => { eq: (col: string, value: unknown) => any };\n };\n rpc(\n fn: string,\n args: Record<string, unknown>,\n ): Promise<{ data: unknown; error: { message: string } | null }>;\n}\n\ninterface SupabaseAdmin {\n schema(name: string): RbacSchemaClient;\n auth: {\n admin: {\n inviteUserByEmail: (\n email: string,\n opts?: { data?: Record<string, unknown>; redirectTo?: string },\n ) => Promise<{ data: unknown; error: { message: string } | null }>;\n };\n };\n}\n\nexport interface SupabaseAdminClientOptions {\n supabase: SupabaseAdmin;\n /** Where the invitee should land after setting their password. */\n inviteRedirectUrl?: string;\n}\n\nconst ACTION_COLUMN: Record<Action, string> = {\n read: \"can_read\",\n write: \"can_write\",\n update: \"can_update\",\n delete: \"can_delete\",\n};\n\nconst GRANTED_VIA_COLUMN: Record<Action, string> = {\n read: \"read_granted_via\",\n write: \"write_granted_via\",\n update: \"update_granted_via\",\n delete: \"delete_granted_via\",\n};\n\n/**\n * Pull `dependsOn` edges out of a registry array and flatten them\n * into one row per (parent, child, action). Shared helper used by\n * `syncResources` and by adopters who want to sync dependencies\n * manually.\n */\nexport function extractResourceDependencies(\n resources: ReadonlyArray<ResourceDescriptor>,\n): AdminResourceDependency[] {\n const out: AdminResourceDependency[] = [];\n for (const r of resources) {\n for (const edge of r.dependsOn ?? []) {\n const child = typeof edge === \"string\" ? edge : edge.resource;\n const actions =\n typeof edge === \"string\" ? ([\"read\"] as const) : (edge.actions ?? [\"read\"]);\n for (const action of actions) {\n out.push({\n parent_resource: r.resource,\n child_resource: child,\n action,\n });\n }\n }\n }\n return out;\n}\n\nexport function createSupabaseAdminClient(\n opts: SupabaseAdminClientOptions,\n): AdminTransport {\n const sb = opts.supabase;\n const rbac = sb.schema(\"rbac\");\n\n const syncResourceDependencies = async (\n edges: ReadonlyArray<AdminResourceDependency>,\n ): Promise<number> => {\n // Atomic replace-all via the package's RPC. Sidesteps PostgREST's\n // refusal of wildcard deletes and gives a single round-trip.\n const payload = edges.map((e) => ({\n parent_resource: e.parent_resource,\n child_resource: e.child_resource,\n action: e.action,\n }));\n const { error } = await rbac.rpc(\"replace_resource_dependencies\", {\n p_edges: payload,\n });\n if (error) {\n throw new Error(`syncResourceDependencies: ${error.message}`);\n }\n return edges.length;\n };\n\n return {\n async syncResources(resources) {\n if (resources.length === 0) {\n return 0;\n }\n const payload = resources.map((r: ResourceDescriptor) => ({\n resource: r.resource,\n scope: r.scope,\n label: r.label,\n description: r.description ?? null,\n group_label: r.group ?? null,\n }));\n const { error } = await rbac\n .from(\"resources\")\n .upsert(payload, { onConflict: \"resource\" });\n if (error) {\n throw new Error(`syncResources: ${error.message}`);\n }\n // 0.4.0+: also sync dependency edges declared via `dependsOn`.\n // Pre-0.4.0 SQL won't have `rbac.resource_dependencies` yet —\n // syncResourceDependencies tolerates that case internally and\n // surfaces other errors normally.\n const edges = extractResourceDependencies(resources);\n try {\n await syncResourceDependencies(edges);\n } catch (err) {\n if (\n err instanceof Error &&\n /resource_dependencies/i.test(err.message) &&\n /(does not exist|relation .* does not exist)/i.test(err.message)\n ) {\n // Pre-0.4.0 SQL — silently skip.\n } else {\n throw err;\n }\n }\n return resources.length;\n },\n\n async listRoles({ scope, companyId, templatesOnly }) {\n let q = rbac.from(\"roles\").select(\"*\").eq(\"scope\", scope);\n if (templatesOnly) {\n q = q.is(\"company_id\", null);\n } else if (companyId !== undefined) {\n q = companyId === null ? q.is(\"company_id\", null) : q.eq(\"company_id\", companyId);\n }\n const { data, error } = await q.order(\"name\", { ascending: true });\n if (error) {\n throw new Error(`listRoles: ${error.message}`);\n }\n return (data ?? []) as AdminRole[];\n },\n\n async listRolePermissions(roleId) {\n const { data, error } = await rbac\n .from(\"role_permissions\")\n .select(\"*\")\n .eq(\"role_id\", roleId);\n if (error) {\n throw new Error(`listRolePermissions: ${error.message}`);\n }\n return (data ?? []) as AdminRolePermission[];\n },\n\n async createRole(input) {\n const row = {\n scope: input.scope,\n company_id: input.companyId ?? null,\n name: input.name,\n description: input.description ?? null,\n frontend_config: input.frontend_config ?? {},\n };\n const { data, error } = await rbac\n .from(\"roles\")\n .insert(row)\n .select(\"*\")\n .single();\n if (error) {\n throw new Error(`createRole: ${error.message}`);\n }\n return data as AdminRole;\n },\n\n async updateRole(id, patch) {\n const { data, error } = await rbac\n .from(\"roles\")\n .update(patch)\n .eq(\"id\", id)\n .select(\"*\")\n .single();\n if (error) {\n throw new Error(`updateRole: ${error.message}`);\n }\n return data as AdminRole;\n },\n\n async deleteRole(id) {\n const { error } = await rbac.from(\"roles\").delete().eq(\"id\", id);\n if (error) {\n throw new Error(`deleteRole: ${error.message}`);\n }\n },\n\n async setRolePermissionCell({ role_id, resource, action, value, grantedVia }) {\n const actionCol = ACTION_COLUMN[action];\n const originCol = GRANTED_VIA_COLUMN[action];\n // grantedVia semantics:\n // undefined → don't touch the origin column (legacy callers)\n // null → explicit \"this is a direct grant\" — set origin\n // to NULL even if a previous parent owned it\n // string → record the parent name\n const row: Record<string, unknown> = {\n role_id,\n resource,\n [actionCol]: value,\n };\n if (grantedVia !== undefined) {\n // When clearing the action (value=false) we always clear the\n // origin too. Otherwise we record whatever the caller passed.\n row[originCol] = value ? grantedVia : null;\n }\n const { error } = await rbac\n .from(\"role_permissions\")\n .upsert(row, { onConflict: \"role_id,resource\" });\n if (error) {\n // Tolerate pre-0.4.0 SQL that doesn't have the origin column\n // — retry without the origin field so existing adopters can\n // still toggle cells. The matrix UI's cascade simply won't\n // produce the implied badge until they migrate.\n if (\n grantedVia !== undefined &&\n /column .*granted_via.* does not exist/i.test(error.message)\n ) {\n const fallbackRow: Record<string, unknown> = {\n role_id,\n resource,\n [actionCol]: value,\n };\n const { error: retryErr } = await rbac\n .from(\"role_permissions\")\n .upsert(fallbackRow, { onConflict: \"role_id,resource\" });\n if (retryErr) {\n throw new Error(`setRolePermissionCell: ${retryErr.message}`);\n }\n return;\n }\n throw new Error(`setRolePermissionCell: ${error.message}`);\n }\n },\n\n async batchSetRolePermissionCells(writes) {\n if (writes.length === 0) {\n return;\n }\n // Group writes by (role_id, resource) so the upsert payload has\n // at most one row per matrix cell — otherwise the upsert would\n // need conflict resolution on its own input.\n const byKey = new Map<string, Record<string, unknown>>();\n for (const w of writes) {\n const key = `${w.role_id}::${w.resource}`;\n const existing = byKey.get(key) ?? {\n role_id: w.role_id,\n resource: w.resource,\n };\n existing[ACTION_COLUMN[w.action]] = w.value;\n if (w.grantedVia !== undefined) {\n existing[GRANTED_VIA_COLUMN[w.action]] = w.value ? w.grantedVia : null;\n }\n byKey.set(key, existing);\n }\n const payload = Array.from(byKey.values());\n const { error } = await rbac\n .from(\"role_permissions\")\n .upsert(payload, { onConflict: \"role_id,resource\" });\n if (error) {\n throw new Error(`batchSetRolePermissionCells: ${error.message}`);\n }\n },\n\n syncResourceDependencies,\n\n async listResourceDependencies() {\n // `.order(...)` returns an awaitable filter chain in the\n // Supabase JS runtime — using it here keeps the mock interface\n // in this file happy without widening it.\n const { data, error } = await rbac\n .from(\"resource_dependencies\")\n .select(\"parent_resource, child_resource, action\")\n .order(\"parent_resource\", { ascending: true });\n if (error) {\n // Pre-0.4.0 SQL: table doesn't exist. Treat as \"no\n // dependencies declared\" so legacy callers don't blow up.\n if (\n /resource_dependencies/i.test(error.message) &&\n /does not exist/i.test(error.message)\n ) {\n return [];\n }\n throw new Error(`listResourceDependencies: ${error.message}`);\n }\n return (data ?? []) as AdminResourceDependency[];\n },\n\n async listRolePermissionOverrides(roleId: string) {\n const { data, error } = await rbac\n .from(\"role_permission_overrides\")\n .select(\"role_id, resource, action\")\n .eq(\"role_id\", roleId);\n if (error) {\n // Pre-0.6.0 SQL — table absent. Empty override set is the\n // right fallback (no rows = no suppressions).\n if (\n /role_permission_overrides/i.test(error.message) &&\n /does not exist/i.test(error.message)\n ) {\n return [];\n }\n throw new Error(`listRolePermissionOverrides: ${error.message}`);\n }\n return (data ?? []) as AdminRolePermissionOverride[];\n },\n\n async setRolePermissionOverride({ role_id, resource, action, suppress }) {\n if (suppress) {\n const { error } = await rbac\n .from(\"role_permission_overrides\")\n .upsert(\n { role_id, resource, action },\n { onConflict: \"role_id,resource,action\" },\n );\n if (error) {\n throw new Error(`setRolePermissionOverride(insert): ${error.message}`);\n }\n return;\n }\n const { error } = await rbac\n .from(\"role_permission_overrides\")\n .delete()\n .eq(\"role_id\", role_id)\n .eq(\"resource\", resource)\n .eq(\"action\", action);\n if (error) {\n throw new Error(`setRolePermissionOverride(delete): ${error.message}`);\n }\n },\n\n async applyTemplateDefaults({ role_id, only_missing = true }) {\n const { data, error } = await rbac.rpc(\"apply_template_defaults\", {\n p_role_id: role_id,\n p_only_missing: only_missing,\n });\n if (error) {\n throw new Error(`applyTemplateDefaults: ${error.message}`);\n }\n if (typeof data === \"number\") return data;\n return Number(data ?? 0);\n },\n\n async listCompanies() {\n const { data, error } = await rbac\n .from(\"companies\")\n .select(\"*\")\n .order(\"name\", { ascending: true });\n if (error) {\n throw new Error(`listCompanies: ${error.message}`);\n }\n return (data ?? []) as AdminCompany[];\n },\n\n async createCompany(input) {\n const { data, error } = await rbac\n .from(\"companies\")\n .insert({\n name: input.name,\n slug: input.slug ?? null,\n type: input.type ?? null,\n })\n .select(\"*\")\n .single();\n if (error) {\n throw new Error(`createCompany: ${error.message}`);\n }\n return data as AdminCompany;\n },\n\n async listCompanyMembers(companyId) {\n // The package doesn't ship a view that joins users + invitations\n // out of the box because the host's auth.users schema may differ.\n // Adopters that need a richer join replace this with their own\n // transport. Fallback: list raw assignments.\n const { data, error } = await rbac\n .from(\"user_company_roles\")\n .select(\"user_id, role_id, assigned_at\")\n .eq(\"company_id\", companyId);\n if (error) {\n throw new Error(`listCompanyMembers: ${error.message}`);\n }\n const grouped = new Map<string, AdminMember>();\n for (const row of (data ?? []) as Array<{\n user_id: string;\n role_id: string;\n assigned_at: string;\n }>) {\n const existing = grouped.get(row.user_id);\n if (existing) {\n existing.role_ids.push(row.role_id);\n } else {\n grouped.set(row.user_id, {\n user_id: row.user_id,\n email: null,\n full_name: null,\n role_ids: [row.role_id],\n invited_at: row.assigned_at,\n invitation_status: \"accepted\",\n });\n }\n }\n return Array.from(grouped.values());\n },\n\n async inviteCompanyMember({ companyId, email, roleIds }) {\n const { error } = await sb.auth.admin.inviteUserByEmail(email, {\n data: {\n rbac_company_id: companyId,\n rbac_role_ids: roleIds,\n },\n redirectTo: opts.inviteRedirectUrl,\n });\n if (error) {\n throw new Error(`inviteCompanyMember: ${error.message}`);\n }\n return { invited: true };\n },\n };\n}\n","/**\n * React hooks for the admin surface. UI-kit-agnostic — adopters\n * render whatever JSX they like with the data + mutations these\n * expose. A copy-paste reference page styled with Tailwind primitives\n * lives in `examples/react-admin/`.\n *\n * Pattern: each hook returns `{ data, isLoading, error, refresh }`\n * and where applicable `{ mutate }`. We deliberately avoid pulling in\n * react-query as a dependency so the package stays peer-light;\n * adopters that already use react-query can wrap these primitives\n * with an extra hook of their own (5 lines).\n */\n\nimport { createContext, useCallback, useContext, useEffect, useMemo, useState } from \"react\";\n\nimport type { Action, FrontendConfig, ResourceScope } from \"../types.js\";\n\nimport type {\n AdminCompany,\n AdminMember,\n AdminResourceDependency,\n AdminRole,\n AdminRolePermission,\n AdminRolePermissionOverride,\n AdminTransport,\n} from \"./types.js\";\n\n// ─────────────────────────────────────────────────────────────────\n// Context — adopter mounts <AdminTransportProvider> once\n// ─────────────────────────────────────────────────────────────────\n\nconst AdminTransportContext = createContext<AdminTransport | null>(null);\n\nexport interface AdminTransportProviderProps {\n transport: AdminTransport;\n children: React.ReactNode;\n}\n\nexport function AdminTransportProvider(props: AdminTransportProviderProps) {\n return (\n <AdminTransportContext.Provider value={props.transport}>\n {props.children}\n </AdminTransportContext.Provider>\n );\n}\n\nfunction useAdminTransport(): AdminTransport {\n const t = useContext(AdminTransportContext);\n if (!t) {\n throw new Error(\n \"auth-rbac admin hooks require <AdminTransportProvider> — wrap your admin pages with one.\",\n );\n }\n return t;\n}\n\n// ─────────────────────────────────────────────────────────────────\n// Tiny generic async-state helper. Avoids reinventing react-query\n// while keeping the boilerplate per-hook to a single line.\n// ─────────────────────────────────────────────────────────────────\n\ninterface AsyncState<T> {\n data: T | null;\n isLoading: boolean;\n error: Error | null;\n}\n\nfunction useAsync<T>(loader: () => Promise<T>, deps: ReadonlyArray<unknown>) {\n const [state, setState] = useState<AsyncState<T>>({\n data: null,\n isLoading: true,\n error: null,\n });\n\n const refresh = useCallback(async () => {\n setState((s) => ({ ...s, isLoading: true, error: null }));\n try {\n const data = await loader();\n setState({ data, isLoading: false, error: null });\n } catch (e) {\n setState({\n data: null,\n isLoading: false,\n error: e instanceof Error ? e : new Error(String(e)),\n });\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, deps);\n\n useEffect(() => {\n void refresh();\n }, [refresh]);\n\n return { ...state, refresh };\n}\n\n// ─────────────────────────────────────────────────────────────────\n// Reads\n// ─────────────────────────────────────────────────────────────────\n\nexport function useAdminRoles(args: {\n scope: ResourceScope;\n companyId?: string | null;\n templatesOnly?: boolean;\n}) {\n const transport = useAdminTransport();\n return useAsync(\n () => transport.listRoles(args),\n [transport, args.scope, args.companyId, args.templatesOnly],\n );\n}\n\nexport function useAdminRolePermissions(roleId: string | null) {\n const transport = useAdminTransport();\n return useAsync(\n async () =>\n roleId == null ? [] : transport.listRolePermissions(roleId),\n [transport, roleId],\n );\n}\n\nexport function useAdminCompanies() {\n const transport = useAdminTransport();\n return useAsync(() => transport.listCompanies(), [transport]);\n}\n\nexport function useAdminCompanyMembers(companyId: string | null) {\n const transport = useAdminTransport();\n return useAsync(\n async () =>\n companyId == null ? [] : transport.listCompanyMembers(companyId),\n [transport, companyId],\n );\n}\n\n// ─────────────────────────────────────────────────────────────────\n// Mutations — return `{ mutate, isPending, error }`. Adopters wrap\n// these in their own toast / error-boundary as needed.\n// ─────────────────────────────────────────────────────────────────\n\ninterface MutationState {\n isPending: boolean;\n error: Error | null;\n}\n\nfunction useMutation<TArgs extends unknown[], TResult>(\n fn: (...args: TArgs) => Promise<TResult>,\n) {\n const [state, setState] = useState<MutationState>({\n isPending: false,\n error: null,\n });\n\n const mutate = useCallback(\n async (...args: TArgs): Promise<TResult> => {\n setState({ isPending: true, error: null });\n try {\n const result = await fn(...args);\n setState({ isPending: false, error: null });\n return result;\n } catch (e) {\n const err = e instanceof Error ? e : new Error(String(e));\n setState({ isPending: false, error: err });\n throw err;\n }\n },\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [fn],\n );\n\n return { mutate, ...state };\n}\n\nexport function useCreateRole() {\n const transport = useAdminTransport();\n return useMutation(transport.createRole);\n}\n\nexport function useUpdateRole() {\n const transport = useAdminTransport();\n return useMutation(transport.updateRole);\n}\n\nexport function useDeleteRole() {\n const transport = useAdminTransport();\n return useMutation(transport.deleteRole);\n}\n\nexport function useSetRolePermissionCell() {\n const transport = useAdminTransport();\n return useMutation(transport.setRolePermissionCell);\n}\n\nexport function useApplyTemplateDefaults() {\n const transport = useAdminTransport();\n return useMutation(transport.applyTemplateDefaults);\n}\n\n/**\n * 0.4.0+. Materialised dependency edges. Loaded once per admin\n * session — the underlying table mutates only on app boot (via\n * `syncResources` → `syncResourceDependencies`).\n */\nexport function useAdminResourceDependencies() {\n const transport = useAdminTransport();\n return useAsync(\n () => transport.listResourceDependencies(),\n [transport],\n );\n}\n\n/**\n * 0.6.0+. Per-role override map. Returns a Set of\n * `\"<resource>:<action>\"` keys for the given role plus a\n * `setOverride(resource, action, suppress)` mutator. Optimistic —\n * the local set flips immediately, then a re-fetch reconciles.\n *\n * Use this in tandem with `useRolePermissionGrid` to render a\n * matrix UI that distinguishes:\n * * direct grants (the row is on rbac.role_permissions)\n * * implied grants (resource_dependencies expansion)\n * * overrides (this hook's set — admin clicked an implied cell\n * off to carve it out for this specific role)\n */\nexport function useRolePermissionOverrides(roleId: string | null) {\n const transport = useAdminTransport();\n const [overrides, setOverrides] = useState<Set<string>>(() => new Set<string>());\n const [error, setError] = useState<Error | null>(null);\n\n const fetchOverrides = useCallback(async () => {\n if (!roleId) {\n setOverrides(new Set<string>());\n return;\n }\n try {\n const rows = await transport.listRolePermissionOverrides(roleId);\n setOverrides(new Set(rows.map((r) => `${r.resource}:${r.action}`)));\n setError(null);\n } catch (e) {\n setError(e instanceof Error ? e : new Error(String(e)));\n }\n }, [transport, roleId]);\n\n useEffect(() => {\n void fetchOverrides();\n }, [fetchOverrides]);\n\n const setOverride = useCallback(\n async (resource: string, action: Action, suppress: boolean) => {\n if (!roleId) {\n return;\n }\n const key = `${resource}:${action}`;\n // Optimistic flip.\n setOverrides((prev) => {\n const next = new Set(prev);\n if (suppress) {\n next.add(key);\n } else {\n next.delete(key);\n }\n return next;\n });\n try {\n await transport.setRolePermissionOverride({\n role_id: roleId,\n resource,\n action,\n suppress,\n });\n } catch (e) {\n setError(e instanceof Error ? e : new Error(String(e)));\n }\n // Reconcile against the server in case the optimistic flip\n // was wrong (e.g. concurrent edit from another admin tab).\n void fetchOverrides();\n },\n [transport, roleId, fetchOverrides],\n );\n\n return { overrides, setOverride, error, refresh: fetchOverrides };\n}\n\nexport function useCreateCompany() {\n const transport = useAdminTransport();\n return useMutation(transport.createCompany);\n}\n\nexport function useInviteCompanyMember() {\n const transport = useAdminTransport();\n return useMutation(transport.inviteCompanyMember);\n}\n\n// ─────────────────────────────────────────────────────────────────\n// Convenience: hold a role's full state (role + permission grid)\n// in one hook, with a `setCell` mutator that optimistically updates\n// the local cache and writes through to the transport.\n// ─────────────────────────────────────────────────────────────────\n\nexport interface RolePermissionGrid {\n // resource → action → boolean\n [resource: string]: { [A in Action]: boolean };\n}\n\n/**\n * Per-cell origin marker:\n * * `'direct'` — explicit admin grant on rbac.role_permissions\n * * `'override'` — admin set an override on rbac.role_permission_overrides\n * (0.6.0+; cell is OFF even if a direct or\n * implied grant would otherwise apply)\n * * `<string>` — name of the parent resource that implies this\n * cell via rbac.resource_dependencies\n * * `null` — neither granted nor implied\n *\n * In 0.4.x this was driven by the `<action>_granted_via` columns on\n * rbac.role_permissions. In 0.5.0+ implied rows are no longer\n * materialised — origin is computed client-side from the dependency\n * graph + the role's direct grants + (0.6.0+) any overrides.\n */\nexport interface RolePermissionOriginGrid {\n [resource: string]: { [A in Action]: string | null };\n}\n\nexport function useRolePermissionGrid(roleId: string | null) {\n const { data, isLoading, error, refresh } = useAdminRolePermissions(roleId);\n const dependencies = useAdminResourceDependencies();\n const overridesHook = useRolePermissionOverrides(roleId);\n const setCell = useSetRolePermissionCell();\n const transport = useAdminTransport();\n\n // 0.5.0: grid reflects the role's DIRECT grants only (the data\n // returned from rbac.role_permissions). Implied state for matrix\n // rendering is computed by the consumer (see PermissionsMatrix —\n // it expands the grid against the dependency graph for both the\n // isCellEnabled and cellOrigin render props).\n const grid = useMemo<RolePermissionGrid>(() => {\n const out: RolePermissionGrid = {};\n for (const row of data ?? []) {\n out[row.resource] = {\n read: row.can_read,\n write: row.can_write,\n update: row.can_update,\n delete: row.can_delete,\n };\n }\n return out;\n }, [data]);\n\n // Reverse-indexed dependency map: child → list of (parent, action)\n // edges that confer implied access. Used by PermissionsMatrix to\n // resolve cellOrigin in O(1) per cell.\n const parentsByChild = useMemo(() => {\n const map = new Map<\n string,\n ReadonlyArray<{ parent: string; action: Action }>\n >();\n for (const edge of dependencies.data ?? []) {\n const list = map.get(edge.child_resource) ?? [];\n map.set(edge.child_resource, [\n ...list,\n { parent: edge.parent_resource, action: edge.action },\n ]);\n }\n return map;\n }, [dependencies.data]);\n\n const originGrid = useMemo<RolePermissionOriginGrid>(() => {\n const out: RolePermissionOriginGrid = {};\n const resources = new Set<string>(Object.keys(grid));\n for (const child of parentsByChild.keys()) {\n resources.add(child);\n }\n const overrides = overridesHook.overrides;\n for (const resource of resources) {\n const directs = grid[resource];\n const cellOrigins: { [A in Action]: string | null } = {\n read: null,\n write: null,\n update: null,\n delete: null,\n };\n for (const action of ACTIONS) {\n // 0.6.2+ priority order:\n // 1. Direct grant ALWAYS wins. Override on a directly-granted\n // cell stays a no-op until / unless the direct grant is\n // revoked — matches the resolver SQL which only checks\n // overrides on the implied path.\n // 2. Implied-via-parent, suppressed by override → 'override'.\n // 3. Implied-via-parent, not suppressed → parent name.\n // 4. Nothing → null.\n if (directs?.[action]) {\n cellOrigins[action] = \"direct\";\n continue;\n }\n const parents = parentsByChild.get(resource) ?? [];\n const impliedFrom = parents.find(\n (p) => p.action === action && grid[p.parent]?.[action] === true,\n );\n if (impliedFrom) {\n if (overrides.has(`${resource}:${action}`)) {\n cellOrigins[action] = \"override\";\n } else {\n cellOrigins[action] = impliedFrom.parent;\n }\n continue;\n }\n cellOrigins[action] = null;\n }\n out[resource] = cellOrigins;\n }\n return out;\n }, [grid, parentsByChild, overridesHook.overrides]);\n\n const updateCell = useCallback(\n async (resource: string, action: Action, value: boolean) => {\n if (!roleId) {\n return;\n }\n // 0.5.0: every write is a DIRECT grant (or direct revoke). No\n // cascade — implied access is computed by the resolver at\n // query time, never materialised. Implied cells are\n // non-editable in the UI (PermissionsMatrix disables them),\n // so this code path only fires for direct cells.\n await setCell.mutate({\n role_id: roleId,\n resource,\n action,\n value,\n grantedVia: null,\n });\n void refresh();\n },\n [roleId, setCell, refresh],\n );\n\n // transport is intentionally part of the closure to keep\n // batchSetRolePermissionCells available to adopters extending the\n // hook (template applies, batch role copies). It's not invoked\n // here in 0.5.0 because per-cell writes are sufficient.\n void transport;\n\n return {\n grid,\n originGrid,\n parentsByChild,\n /** 0.6.0+. Set of `\"<resource>:<action>\"` for this role's overrides. */\n overrides: overridesHook.overrides,\n /**\n * 0.6.0+. Suppress (`suppress=true`) or restore (`suppress=false`)\n * an implied permission for this role. The grid + originGrid\n * re-render with `'override'` state on the cell as soon as the\n * optimistic flip lands.\n */\n setOverride: overridesHook.setOverride,\n isLoading: isLoading || dependencies.isLoading,\n error: error ?? dependencies.error ?? overridesHook.error,\n refresh,\n updateCell,\n isUpdating: setCell.isPending,\n updateError: setCell.error,\n };\n}\n\nconst ACTIONS: ReadonlyArray<Action> = [\"read\", \"write\", \"update\", \"delete\"];\n","/**\n * Headless permissions matrix.\n *\n * Owns:\n * - reading the role's current permission grid\n * - debounced write-through on every cell toggle\n * - grouping resources by `group` for a sectioned UI\n *\n * Owns NOTHING about styling — the consumer renders all JSX via the\n * single `children` render-prop. A copy-paste reference styled with\n * Tailwind + Radix lives in `examples/react-admin/`.\n *\n * @example minimum viable adoption\n *\n * <PermissionsMatrix\n * roleId={role.id}\n * resources={resources.filter(r => r.scope === role.scope)}\n * >\n * {({ groups, isCellEnabled, setCell, isLoading }) =>\n * groups.map((g) => (\n * <section key={g.group}>\n * <h3>{g.group}</h3>\n * {g.resources.map((r) => (\n * <div key={r.resource}>\n * <span>{r.label}</span>\n * {([\"read\", \"write\", \"update\", \"delete\"] as const).map((a) => (\n * <input\n * key={a}\n * type=\"checkbox\"\n * checked={isCellEnabled(r.resource, a)}\n * disabled={isLoading}\n * onChange={(e) => setCell(r.resource, a, e.target.checked)}\n * />\n * ))}\n * </div>\n * ))}\n * </section>\n * ))\n * }\n * </PermissionsMatrix>\n */\n\nimport { useMemo } from \"react\";\n\nimport type {\n Action,\n ResourceDescriptor,\n} from \"../types.js\";\nimport { groupResources } from \"../client.js\";\n\nimport { useRolePermissionGrid } from \"./hooks.js\";\n\nexport interface MatrixGroup {\n group: string;\n resources: ResourceDescriptor[];\n}\n\nexport interface MatrixRenderArgs {\n /** Resources grouped by their `group` label, original insertion order. */\n groups: MatrixGroup[];\n /**\n * Effective state of a cell after applying direct grants, the\n * resource-dependency expansion, and any per-role overrides.\n * What the resolver would answer for a user holding this role.\n */\n isCellEnabled: (resource: string, action: Action) => boolean;\n /**\n * Origin of a single cell:\n * * `'direct'` — explicit admin grant on rbac.role_permissions\n * * `'override'` — admin suppressed it via rbac.role_permission_overrides\n * (0.6.0+; cell is off even if a parent would imply)\n * * `<string>` — the name of the parent resource whose\n * `dependsOn` edge implies this cell\n *\n * Available since 0.4.0; the `'override'` value is 0.6.0+.\n */\n cellOrigin: (resource: string, action: Action) => \"direct\" | \"override\" | string;\n /**\n * 0.6.3+. True only when this role has an explicit row in\n * `rbac.role_permissions` for `(resource, action)`. Implied access\n * via a parent's `dependsOn` cascade returns `false` here — the\n * implied state is exposed via `cellOrigin` (parent name) and\n * belongs in the dependents-expander UI, not in the main row.\n *\n * Use this for the main cell switch state so direct and implied\n * grants are never conflated in the matrix:\n *\n * const checked = isCellDirect(resource, action);\n * const onToggle = (v: boolean) => setCell(resource, action, v);\n *\n * Then surface the implied cascade and its per-role override toggle\n * in the parent's expander section, where the admin can suppress a\n * single implied edge without touching the role's direct grants.\n */\n isCellDirect: (resource: string, action: Action) => boolean;\n /**\n * Toggle a DIRECT grant on rbac.role_permissions. Use for cells\n * that the matrix UI shows as \"direct\" (no implied parent). For\n * cells that are implied, use `setOverride` instead — that's what\n * lets the admin opt a single role out of a cascade without\n * touching the parent grant or the registry.\n */\n setCell: (resource: string, action: Action, value: boolean) => Promise<void>;\n /**\n * 0.6.0+. Suppress (`suppress=true`) or restore (`suppress=false`)\n * an implied permission for this role via\n * rbac.role_permission_overrides. Writes are optimistic; the\n * `cellOrigin` reflects the new state immediately.\n */\n setOverride: (\n resource: string,\n action: Action,\n suppress: boolean,\n ) => Promise<void>;\n isLoading: boolean;\n isUpdating: boolean;\n error: Error | null;\n /** All four actions, exposed for the consumer to render headers. */\n actions: ReadonlyArray<Action>;\n}\n\nexport interface PermissionsMatrixProps {\n roleId: string | null;\n resources: ReadonlyArray<ResourceDescriptor>;\n children: (args: MatrixRenderArgs) => React.ReactNode;\n}\n\nconst ACTIONS = [\"read\", \"write\", \"update\", \"delete\"] as const;\n\nexport function PermissionsMatrix(props: PermissionsMatrixProps) {\n const {\n grid,\n originGrid,\n isLoading,\n error,\n updateCell,\n isUpdating,\n setOverride: gridSetOverride,\n } = useRolePermissionGrid(props.roleId);\n\n const groups = useMemo<MatrixGroup[]>(\n () => groupResources(props.resources),\n [props.resources],\n );\n\n // A cell is \"enabled\" when its origin is direct or implied via a\n // parent — anything except null/`override`/`undefined`. The\n // 0.6.0 override marker counts as \"not enabled\" because the\n // resolver subtracts it.\n const isCellEnabled = (resource: string, action: Action): boolean => {\n const origin = originGrid[resource]?.[action];\n if (origin == null || origin === \"override\") {\n return false;\n }\n return true;\n };\n\n const cellOrigin = (\n resource: string,\n action: Action,\n ): \"direct\" | \"override\" | string => {\n const origin = originGrid[resource]?.[action];\n // No grant of any kind → still \"direct\" for the UI (an empty\n // direct cell). Implied grants return the parent resource name.\n return origin == null ? \"direct\" : origin;\n };\n\n // 0.6.3+. Strict direct-grant check — true only when there's an\n // actual row on rbac.role_permissions for this (role, resource,\n // action). Differs from isCellEnabled, which also returns true\n // for implied-via-parent cells. The main row in the matrix should\n // bind to this; implied state belongs in the dependents-expander.\n const isCellDirect = (resource: string, action: Action): boolean => {\n return grid[resource]?.[action] === true;\n };\n\n const setCell = async (resource: string, action: Action, value: boolean) => {\n await updateCell(resource, action, value);\n };\n\n const setOverride = async (\n resource: string,\n action: Action,\n suppress: boolean,\n ) => {\n await gridSetOverride(resource, action, suppress);\n };\n\n return (\n <>\n {props.children({\n groups,\n isCellEnabled,\n cellOrigin,\n isCellDirect,\n setCell,\n setOverride,\n isLoading,\n isUpdating,\n error,\n actions: ACTIONS,\n })}\n </>\n );\n}\n","/**\n * Headless roles-list controller. Tracks selection + create/delete\n * mutations; consumer renders the list, the new-role dialog, and\n * the destructive-action confirmation.\n */\n\nimport { useCallback, useState } from \"react\";\n\nimport type { ResourceScope } from \"../types.js\";\n\nimport {\n useAdminRoles,\n useCreateRole,\n useDeleteRole,\n} from \"./hooks.js\";\nimport type { AdminRole } from \"./types.js\";\n\nexport interface RolesListRenderArgs {\n roles: AdminRole[];\n isLoading: boolean;\n error: Error | null;\n\n selectedRoleId: string | null;\n selectRole: (id: string | null) => void;\n\n createRole: (input: {\n name: string;\n description?: string;\n }) => Promise<AdminRole>;\n isCreating: boolean;\n createError: Error | null;\n\n deleteRole: (id: string) => Promise<void>;\n isDeleting: boolean;\n deleteError: Error | null;\n\n refresh: () => Promise<void>;\n}\n\nexport interface RolesListProps {\n scope: ResourceScope;\n /** Required for company-scope. Pass `null` for templates. */\n companyId?: string | null;\n /** Pre-select the first role on load. Default: true. */\n autoSelectFirst?: boolean;\n children: (args: RolesListRenderArgs) => React.ReactNode;\n}\n\nexport function RolesList(props: RolesListProps) {\n const { scope, companyId, autoSelectFirst = true } = props;\n\n const list = useAdminRoles({ scope, companyId });\n const create = useCreateRole();\n const remove = useDeleteRole();\n\n const [selectedRoleId, setSelectedRoleId] = useState<string | null>(null);\n\n // Auto-select first role on load.\n if (\n autoSelectFirst &&\n selectedRoleId == null &&\n list.data != null &&\n list.data.length > 0\n ) {\n setSelectedRoleId(list.data[0]!.id);\n }\n\n const createRole = useCallback(\n async (input: { name: string; description?: string }) => {\n const role = await create.mutate({\n scope,\n companyId: companyId ?? null,\n name: input.name,\n description: input.description,\n });\n await list.refresh();\n setSelectedRoleId(role.id);\n return role;\n },\n [create, scope, companyId, list],\n );\n\n const deleteRole = useCallback(\n async (id: string) => {\n await remove.mutate(id);\n if (selectedRoleId === id) {\n setSelectedRoleId(null);\n }\n await list.refresh();\n },\n [remove, list, selectedRoleId],\n );\n\n return (\n <>\n {props.children({\n roles: list.data ?? [],\n isLoading: list.isLoading,\n error: list.error,\n selectedRoleId,\n selectRole: setSelectedRoleId,\n createRole,\n isCreating: create.isPending,\n createError: create.error,\n deleteRole,\n isDeleting: remove.isPending,\n deleteError: remove.error,\n refresh: list.refresh,\n })}\n </>\n );\n}\n","/**\n * Headless invite-member form state. Tracks email + selected role\n * ids, runs basic local validation, and exposes a submit handler\n * that calls the configured transport (Supabase Auth invite by\n * default).\n */\n\nimport { useCallback, useState } from \"react\";\n\nimport { useAdminRoles, useInviteCompanyMember } from \"./hooks.js\";\nimport type { AdminRole } from \"./types.js\";\n\nexport interface InviteMemberFormRenderArgs {\n // form state\n email: string;\n setEmail: (v: string) => void;\n selectedRoleIds: Set<string>;\n toggleRole: (roleId: string) => void;\n resetForm: () => void;\n\n // catalog\n roles: AdminRole[];\n rolesLoading: boolean;\n rolesError: Error | null;\n\n // submission\n submit: () => Promise<void>;\n isSubmitting: boolean;\n submitError: Error | null;\n submittedSuccessfully: boolean;\n\n // validation\n isValid: boolean;\n errors: { email?: string; roles?: string };\n}\n\nexport interface InviteMemberFormProps {\n companyId: string;\n /** Called after a successful invite — typically clears a dialog. */\n onSuccess?: () => void;\n children: (args: InviteMemberFormRenderArgs) => React.ReactNode;\n}\n\nexport function InviteMemberForm(props: InviteMemberFormProps) {\n const rolesQuery = useAdminRoles({\n scope: \"company\",\n companyId: props.companyId,\n });\n const invite = useInviteCompanyMember();\n\n const [email, setEmail] = useState(\"\");\n const [selectedRoleIds, setSelectedRoleIds] = useState<Set<string>>(\n new Set(),\n );\n const [submittedSuccessfully, setSubmittedSuccessfully] = useState(false);\n\n const toggleRole = useCallback((roleId: string) => {\n setSelectedRoleIds((prev) => {\n const next = new Set(prev);\n if (next.has(roleId)) {\n next.delete(roleId);\n } else {\n next.add(roleId);\n }\n return next;\n });\n }, []);\n\n const resetForm = useCallback(() => {\n setEmail(\"\");\n setSelectedRoleIds(new Set());\n setSubmittedSuccessfully(false);\n }, []);\n\n const errors: InviteMemberFormRenderArgs[\"errors\"] = {};\n if (email.trim() && !/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email.trim())) {\n errors.email = \"Bitte gib eine gültige E-Mail-Adresse ein.\";\n }\n if (selectedRoleIds.size === 0) {\n errors.roles = \"Bitte mindestens eine Rolle auswählen.\";\n }\n const isValid =\n email.trim().length > 0 &&\n Object.keys(errors).length === 0;\n\n const submit = useCallback(async () => {\n if (!isValid) {\n return;\n }\n await invite.mutate({\n companyId: props.companyId,\n email: email.trim(),\n roleIds: Array.from(selectedRoleIds),\n });\n setSubmittedSuccessfully(true);\n props.onSuccess?.();\n }, [invite, props, email, selectedRoleIds, isValid]);\n\n return (\n <>\n {props.children({\n email,\n setEmail,\n selectedRoleIds,\n toggleRole,\n resetForm,\n roles: rolesQuery.data ?? [],\n rolesLoading: rolesQuery.isLoading,\n rolesError: rolesQuery.error,\n submit,\n isSubmitting: invite.isPending,\n submitError: invite.error,\n submittedSuccessfully,\n isValid,\n errors,\n })}\n </>\n );\n}\n"],"mappings":"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","names":["error","useMemo","jsx","ACTIONS","useMemo","useCallback","useState","Fragment","jsx","useState","useCallback","useCallback","useState","Fragment","jsx","useState","useCallback"]}
1
+ {"version":3,"sources":["../../src/admin/transport.ts","../../src/admin/hooks.tsx","../../src/admin/PermissionsMatrix.tsx","../../src/admin/RolesList.tsx","../../src/admin/InviteMemberForm.tsx"],"sourcesContent":["/**\n * Default Supabase implementation of the admin transport. Hits the\n * package's tables in the `rbac` schema directly via `.schema('rbac').\n * from(...)` and the auth admin endpoint for invites.\n *\n * Adopters must add `rbac` to their PostgREST exposed-schemas list\n * (Supabase Studio → Settings → API → Exposed schemas) for these\n * calls to reach the tables.\n *\n * Projects that route admin writes through their own backend\n * (e.g. for audit logging or extra validation) skip this and\n * implement `AdminTransport` themselves.\n */\n\nimport type { Action, ResourceDescriptor } from \"../types.js\";\n\nimport type {\n AdminCompany,\n AdminMember,\n AdminResourceDependency,\n AdminRole,\n AdminRolePermission,\n AdminRolePermissionOverride,\n AdminTransport,\n} from \"./types.js\";\n\ninterface RbacSchemaClient {\n from(table: string): {\n select: (cols: string) => {\n eq: (col: string, value: unknown) => any;\n is: (col: string, value: unknown) => any;\n order: (col: string, opts?: { ascending: boolean }) => any;\n };\n insert: (row: Record<string, unknown>) => {\n select: (cols: string) => { single: () => any };\n };\n update: (patch: Record<string, unknown>) => {\n eq: (col: string, value: unknown) => {\n select: (cols: string) => { single: () => any };\n };\n };\n upsert: (\n row: Record<string, unknown> | Array<Record<string, unknown>>,\n opts?: { onConflict: string },\n ) => Promise<{ error: { message: string } | null }>;\n delete: () => { eq: (col: string, value: unknown) => any };\n };\n rpc(\n fn: string,\n args: Record<string, unknown>,\n ): Promise<{ data: unknown; error: { message: string } | null }>;\n}\n\ninterface SupabaseAdmin {\n schema(name: string): RbacSchemaClient;\n auth: {\n admin: {\n inviteUserByEmail: (\n email: string,\n opts?: { data?: Record<string, unknown>; redirectTo?: string },\n ) => Promise<{ data: unknown; error: { message: string } | null }>;\n };\n };\n}\n\nexport interface SupabaseAdminClientOptions {\n supabase: SupabaseAdmin;\n /** Where the invitee should land after setting their password. */\n inviteRedirectUrl?: string;\n}\n\nconst ACTION_COLUMN: Record<Action, string> = {\n read: \"can_read\",\n write: \"can_write\",\n update: \"can_update\",\n delete: \"can_delete\",\n};\n\nconst GRANTED_VIA_COLUMN: Record<Action, string> = {\n read: \"read_granted_via\",\n write: \"write_granted_via\",\n update: \"update_granted_via\",\n delete: \"delete_granted_via\",\n};\n\n/**\n * Pull `dependsOn` edges out of a registry array and flatten them\n * into one row per (parent, child, action). Shared helper used by\n * `syncResources` and by adopters who want to sync dependencies\n * manually.\n */\nexport function extractResourceDependencies(\n resources: ReadonlyArray<ResourceDescriptor>,\n): AdminResourceDependency[] {\n const out: AdminResourceDependency[] = [];\n for (const r of resources) {\n for (const edge of r.dependsOn ?? []) {\n const child = typeof edge === \"string\" ? edge : edge.resource;\n const actions =\n typeof edge === \"string\" ? ([\"read\"] as const) : (edge.actions ?? [\"read\"]);\n for (const action of actions) {\n out.push({\n parent_resource: r.resource,\n child_resource: child,\n action,\n });\n }\n }\n }\n return out;\n}\n\nexport function createSupabaseAdminClient(\n opts: SupabaseAdminClientOptions,\n): AdminTransport {\n const sb = opts.supabase;\n const rbac = sb.schema(\"rbac\");\n\n const syncResourceDependencies = async (\n edges: ReadonlyArray<AdminResourceDependency>,\n ): Promise<number> => {\n // Atomic replace-all via the package's RPC. Sidesteps PostgREST's\n // refusal of wildcard deletes and gives a single round-trip.\n const payload = edges.map((e) => ({\n parent_resource: e.parent_resource,\n child_resource: e.child_resource,\n action: e.action,\n }));\n const { error } = await rbac.rpc(\"replace_resource_dependencies\", {\n p_edges: payload,\n });\n if (error) {\n throw new Error(`syncResourceDependencies: ${error.message}`);\n }\n return edges.length;\n };\n\n return {\n async syncResources(resources) {\n if (resources.length === 0) {\n return 0;\n }\n const payload = resources.map((r: ResourceDescriptor) => ({\n resource: r.resource,\n scope: r.scope,\n label: r.label,\n description: r.description ?? null,\n group_label: r.group ?? null,\n }));\n const { error } = await rbac\n .from(\"resources\")\n .upsert(payload, { onConflict: \"resource\" });\n if (error) {\n throw new Error(`syncResources: ${error.message}`);\n }\n // 0.4.0+: also sync dependency edges declared via `dependsOn`.\n // Pre-0.4.0 SQL won't have `rbac.resource_dependencies` yet —\n // syncResourceDependencies tolerates that case internally and\n // surfaces other errors normally.\n const edges = extractResourceDependencies(resources);\n try {\n await syncResourceDependencies(edges);\n } catch (err) {\n if (\n err instanceof Error &&\n /resource_dependencies/i.test(err.message) &&\n /(does not exist|relation .* does not exist)/i.test(err.message)\n ) {\n // Pre-0.4.0 SQL — silently skip.\n } else {\n throw err;\n }\n }\n return resources.length;\n },\n\n async listRoles({ scope, companyId, templatesOnly }) {\n let q = rbac.from(\"roles\").select(\"*\").eq(\"scope\", scope);\n if (templatesOnly) {\n q = q.is(\"company_id\", null);\n } else if (companyId !== undefined) {\n q = companyId === null ? q.is(\"company_id\", null) : q.eq(\"company_id\", companyId);\n }\n const { data, error } = await q.order(\"name\", { ascending: true });\n if (error) {\n throw new Error(`listRoles: ${error.message}`);\n }\n return (data ?? []) as AdminRole[];\n },\n\n async listRolePermissions(roleId) {\n const { data, error } = await rbac\n .from(\"role_permissions\")\n .select(\"*\")\n .eq(\"role_id\", roleId);\n if (error) {\n throw new Error(`listRolePermissions: ${error.message}`);\n }\n return (data ?? []) as AdminRolePermission[];\n },\n\n async createRole(input) {\n const row = {\n scope: input.scope,\n company_id: input.companyId ?? null,\n name: input.name,\n description: input.description ?? null,\n frontend_config: input.frontend_config ?? {},\n };\n const { data, error } = await rbac\n .from(\"roles\")\n .insert(row)\n .select(\"*\")\n .single();\n if (error) {\n throw new Error(`createRole: ${error.message}`);\n }\n return data as AdminRole;\n },\n\n async updateRole(id, patch) {\n const { data, error } = await rbac\n .from(\"roles\")\n .update(patch)\n .eq(\"id\", id)\n .select(\"*\")\n .single();\n if (error) {\n throw new Error(`updateRole: ${error.message}`);\n }\n return data as AdminRole;\n },\n\n async deleteRole(id) {\n const { error } = await rbac.from(\"roles\").delete().eq(\"id\", id);\n if (error) {\n throw new Error(`deleteRole: ${error.message}`);\n }\n },\n\n async setRolePermissionCell({ role_id, resource, action, value, grantedVia }) {\n const actionCol = ACTION_COLUMN[action];\n const originCol = GRANTED_VIA_COLUMN[action];\n // grantedVia semantics:\n // undefined → don't touch the origin column (legacy callers)\n // null → explicit \"this is a direct grant\" — set origin\n // to NULL even if a previous parent owned it\n // string → record the parent name\n const row: Record<string, unknown> = {\n role_id,\n resource,\n [actionCol]: value,\n };\n if (grantedVia !== undefined) {\n // When clearing the action (value=false) we always clear the\n // origin too. Otherwise we record whatever the caller passed.\n row[originCol] = value ? grantedVia : null;\n }\n const { error } = await rbac\n .from(\"role_permissions\")\n .upsert(row, { onConflict: \"role_id,resource\" });\n if (error) {\n // Tolerate pre-0.4.0 SQL that doesn't have the origin column\n // — retry without the origin field so existing adopters can\n // still toggle cells. The matrix UI's cascade simply won't\n // produce the implied badge until they migrate.\n if (\n grantedVia !== undefined &&\n /column .*granted_via.* does not exist/i.test(error.message)\n ) {\n const fallbackRow: Record<string, unknown> = {\n role_id,\n resource,\n [actionCol]: value,\n };\n const { error: retryErr } = await rbac\n .from(\"role_permissions\")\n .upsert(fallbackRow, { onConflict: \"role_id,resource\" });\n if (retryErr) {\n throw new Error(`setRolePermissionCell: ${retryErr.message}`);\n }\n return;\n }\n throw new Error(`setRolePermissionCell: ${error.message}`);\n }\n },\n\n async batchSetRolePermissionCells(writes) {\n if (writes.length === 0) {\n return;\n }\n // Group writes by (role_id, resource) so the upsert payload has\n // at most one row per matrix cell — otherwise the upsert would\n // need conflict resolution on its own input.\n const byKey = new Map<string, Record<string, unknown>>();\n for (const w of writes) {\n const key = `${w.role_id}::${w.resource}`;\n const existing = byKey.get(key) ?? {\n role_id: w.role_id,\n resource: w.resource,\n };\n existing[ACTION_COLUMN[w.action]] = w.value;\n if (w.grantedVia !== undefined) {\n existing[GRANTED_VIA_COLUMN[w.action]] = w.value ? w.grantedVia : null;\n }\n byKey.set(key, existing);\n }\n const payload = Array.from(byKey.values());\n const { error } = await rbac\n .from(\"role_permissions\")\n .upsert(payload, { onConflict: \"role_id,resource\" });\n if (error) {\n throw new Error(`batchSetRolePermissionCells: ${error.message}`);\n }\n },\n\n syncResourceDependencies,\n\n async listResourceDependencies() {\n // `.order(...)` returns an awaitable filter chain in the\n // Supabase JS runtime — using it here keeps the mock interface\n // in this file happy without widening it.\n const { data, error } = await rbac\n .from(\"resource_dependencies\")\n .select(\"parent_resource, child_resource, action\")\n .order(\"parent_resource\", { ascending: true });\n if (error) {\n // Pre-0.4.0 SQL: table doesn't exist. Treat as \"no\n // dependencies declared\" so legacy callers don't blow up.\n if (\n /resource_dependencies/i.test(error.message) &&\n /does not exist/i.test(error.message)\n ) {\n return [];\n }\n throw new Error(`listResourceDependencies: ${error.message}`);\n }\n return (data ?? []) as AdminResourceDependency[];\n },\n\n async listRolePermissionOverrides(roleId: string) {\n const { data, error } = await rbac\n .from(\"role_permission_overrides\")\n .select(\"role_id, resource, action\")\n .eq(\"role_id\", roleId);\n if (error) {\n // Pre-0.6.0 SQL — table absent. Empty override set is the\n // right fallback (no rows = no suppressions).\n if (\n /role_permission_overrides/i.test(error.message) &&\n /does not exist/i.test(error.message)\n ) {\n return [];\n }\n throw new Error(`listRolePermissionOverrides: ${error.message}`);\n }\n return (data ?? []) as AdminRolePermissionOverride[];\n },\n\n async setRolePermissionOverride({ role_id, resource, action, suppress }) {\n if (suppress) {\n const { error } = await rbac\n .from(\"role_permission_overrides\")\n .upsert(\n { role_id, resource, action },\n { onConflict: \"role_id,resource,action\" },\n );\n if (error) {\n throw new Error(`setRolePermissionOverride(insert): ${error.message}`);\n }\n return;\n }\n const { error } = await rbac\n .from(\"role_permission_overrides\")\n .delete()\n .eq(\"role_id\", role_id)\n .eq(\"resource\", resource)\n .eq(\"action\", action);\n if (error) {\n throw new Error(`setRolePermissionOverride(delete): ${error.message}`);\n }\n },\n\n async applyTemplateDefaults({ role_id, only_missing = true }) {\n const { data, error } = await rbac.rpc(\"apply_template_defaults\", {\n p_role_id: role_id,\n p_only_missing: only_missing,\n });\n if (error) {\n throw new Error(`applyTemplateDefaults: ${error.message}`);\n }\n if (typeof data === \"number\") return data;\n return Number(data ?? 0);\n },\n\n async listCompanies() {\n const { data, error } = await rbac\n .from(\"companies\")\n .select(\"*\")\n .order(\"name\", { ascending: true });\n if (error) {\n throw new Error(`listCompanies: ${error.message}`);\n }\n return (data ?? []) as AdminCompany[];\n },\n\n async createCompany(input) {\n const { data, error } = await rbac\n .from(\"companies\")\n .insert({\n name: input.name,\n slug: input.slug ?? null,\n type: input.type ?? null,\n })\n .select(\"*\")\n .single();\n if (error) {\n throw new Error(`createCompany: ${error.message}`);\n }\n return data as AdminCompany;\n },\n\n async listCompanyMembers(companyId) {\n // The package doesn't ship a view that joins users + invitations\n // out of the box because the host's auth.users schema may differ.\n // Adopters that need a richer join replace this with their own\n // transport. Fallback: list raw assignments.\n const { data, error } = await rbac\n .from(\"user_company_roles\")\n .select(\"user_id, role_id, assigned_at\")\n .eq(\"company_id\", companyId);\n if (error) {\n throw new Error(`listCompanyMembers: ${error.message}`);\n }\n const grouped = new Map<string, AdminMember>();\n for (const row of (data ?? []) as Array<{\n user_id: string;\n role_id: string;\n assigned_at: string;\n }>) {\n const existing = grouped.get(row.user_id);\n if (existing) {\n existing.role_ids.push(row.role_id);\n } else {\n grouped.set(row.user_id, {\n user_id: row.user_id,\n email: null,\n full_name: null,\n role_ids: [row.role_id],\n invited_at: row.assigned_at,\n invitation_status: \"accepted\",\n });\n }\n }\n return Array.from(grouped.values());\n },\n\n async inviteCompanyMember({ companyId, email, roleIds }) {\n const { error } = await sb.auth.admin.inviteUserByEmail(email, {\n data: {\n rbac_company_id: companyId,\n rbac_role_ids: roleIds,\n },\n redirectTo: opts.inviteRedirectUrl,\n });\n if (error) {\n throw new Error(`inviteCompanyMember: ${error.message}`);\n }\n return { invited: true };\n },\n };\n}\n","/**\n * React hooks for the admin surface. UI-kit-agnostic — adopters\n * render whatever JSX they like with the data + mutations these\n * expose. A copy-paste reference page styled with Tailwind primitives\n * lives in `examples/react-admin/`.\n *\n * Pattern: each hook returns `{ data, isLoading, error, refresh }`\n * and where applicable `{ mutate }`. We deliberately avoid pulling in\n * react-query as a dependency so the package stays peer-light;\n * adopters that already use react-query can wrap these primitives\n * with an extra hook of their own (5 lines).\n */\n\nimport { createContext, useCallback, useContext, useEffect, useMemo, useState } from \"react\";\n\nimport type { Action, FrontendConfig, ResourceScope } from \"../types.js\";\n\nimport type {\n AdminCompany,\n AdminMember,\n AdminResourceDependency,\n AdminRole,\n AdminRolePermission,\n AdminRolePermissionOverride,\n AdminTransport,\n} from \"./types.js\";\n\n// ─────────────────────────────────────────────────────────────────\n// Context — adopter mounts <AdminTransportProvider> once\n// ─────────────────────────────────────────────────────────────────\n\nconst AdminTransportContext = createContext<AdminTransport | null>(null);\n\nexport interface AdminTransportProviderProps {\n transport: AdminTransport;\n children: React.ReactNode;\n}\n\nexport function AdminTransportProvider(props: AdminTransportProviderProps) {\n return (\n <AdminTransportContext.Provider value={props.transport}>\n {props.children}\n </AdminTransportContext.Provider>\n );\n}\n\nfunction useAdminTransport(): AdminTransport {\n const t = useContext(AdminTransportContext);\n if (!t) {\n throw new Error(\n \"auth-rbac admin hooks require <AdminTransportProvider> — wrap your admin pages with one.\",\n );\n }\n return t;\n}\n\n// ─────────────────────────────────────────────────────────────────\n// Tiny generic async-state helper. Avoids reinventing react-query\n// while keeping the boilerplate per-hook to a single line.\n// ─────────────────────────────────────────────────────────────────\n\ninterface AsyncState<T> {\n data: T | null;\n isLoading: boolean;\n error: Error | null;\n}\n\nfunction useAsync<T>(loader: () => Promise<T>, deps: ReadonlyArray<unknown>) {\n const [state, setState] = useState<AsyncState<T>>({\n data: null,\n isLoading: true,\n error: null,\n });\n\n const refresh = useCallback(async () => {\n setState((s) => ({ ...s, isLoading: true, error: null }));\n try {\n const data = await loader();\n setState({ data, isLoading: false, error: null });\n } catch (e) {\n setState({\n data: null,\n isLoading: false,\n error: e instanceof Error ? e : new Error(String(e)),\n });\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, deps);\n\n useEffect(() => {\n void refresh();\n }, [refresh]);\n\n return { ...state, refresh };\n}\n\n// ─────────────────────────────────────────────────────────────────\n// Reads\n// ─────────────────────────────────────────────────────────────────\n\nexport function useAdminRoles(args: {\n scope: ResourceScope;\n companyId?: string | null;\n templatesOnly?: boolean;\n}) {\n const transport = useAdminTransport();\n return useAsync(\n () => transport.listRoles(args),\n [transport, args.scope, args.companyId, args.templatesOnly],\n );\n}\n\nexport function useAdminRolePermissions(roleId: string | null) {\n const transport = useAdminTransport();\n return useAsync(\n async () =>\n roleId == null ? [] : transport.listRolePermissions(roleId),\n [transport, roleId],\n );\n}\n\nexport function useAdminCompanies() {\n const transport = useAdminTransport();\n return useAsync(() => transport.listCompanies(), [transport]);\n}\n\nexport function useAdminCompanyMembers(companyId: string | null) {\n const transport = useAdminTransport();\n return useAsync(\n async () =>\n companyId == null ? [] : transport.listCompanyMembers(companyId),\n [transport, companyId],\n );\n}\n\n// ─────────────────────────────────────────────────────────────────\n// Mutations — return `{ mutate, isPending, error }`. Adopters wrap\n// these in their own toast / error-boundary as needed.\n// ─────────────────────────────────────────────────────────────────\n\ninterface MutationState {\n isPending: boolean;\n error: Error | null;\n}\n\nfunction useMutation<TArgs extends unknown[], TResult>(\n fn: (...args: TArgs) => Promise<TResult>,\n) {\n const [state, setState] = useState<MutationState>({\n isPending: false,\n error: null,\n });\n\n const mutate = useCallback(\n async (...args: TArgs): Promise<TResult> => {\n setState({ isPending: true, error: null });\n try {\n const result = await fn(...args);\n setState({ isPending: false, error: null });\n return result;\n } catch (e) {\n const err = e instanceof Error ? e : new Error(String(e));\n setState({ isPending: false, error: err });\n throw err;\n }\n },\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [fn],\n );\n\n return { mutate, ...state };\n}\n\nexport function useCreateRole() {\n const transport = useAdminTransport();\n return useMutation(transport.createRole);\n}\n\nexport function useUpdateRole() {\n const transport = useAdminTransport();\n return useMutation(transport.updateRole);\n}\n\nexport function useDeleteRole() {\n const transport = useAdminTransport();\n return useMutation(transport.deleteRole);\n}\n\nexport function useSetRolePermissionCell() {\n const transport = useAdminTransport();\n return useMutation(transport.setRolePermissionCell);\n}\n\nexport function useApplyTemplateDefaults() {\n const transport = useAdminTransport();\n return useMutation(transport.applyTemplateDefaults);\n}\n\n/**\n * 0.4.0+. Materialised dependency edges. Loaded once per admin\n * session — the underlying table mutates only on app boot (via\n * `syncResources` → `syncResourceDependencies`).\n */\nexport function useAdminResourceDependencies() {\n const transport = useAdminTransport();\n return useAsync(\n () => transport.listResourceDependencies(),\n [transport],\n );\n}\n\n/**\n * 0.6.0+. Per-role override map. Returns a Set of\n * `\"<resource>:<action>\"` keys for the given role plus a\n * `setOverride(resource, action, suppress)` mutator. Optimistic —\n * the local set flips immediately, then a re-fetch reconciles.\n *\n * Use this in tandem with `useRolePermissionGrid` to render a\n * matrix UI that distinguishes:\n * * direct grants (the row is on rbac.role_permissions)\n * * implied grants (resource_dependencies expansion)\n * * overrides (this hook's set — admin clicked an implied cell\n * off to carve it out for this specific role)\n */\nexport function useRolePermissionOverrides(roleId: string | null) {\n const transport = useAdminTransport();\n const [overrides, setOverrides] = useState<Set<string>>(() => new Set<string>());\n const [error, setError] = useState<Error | null>(null);\n\n const fetchOverrides = useCallback(async () => {\n if (!roleId) {\n setOverrides(new Set<string>());\n return;\n }\n try {\n const rows = await transport.listRolePermissionOverrides(roleId);\n setOverrides(new Set(rows.map((r) => `${r.resource}:${r.action}`)));\n setError(null);\n } catch (e) {\n setError(e instanceof Error ? e : new Error(String(e)));\n }\n }, [transport, roleId]);\n\n useEffect(() => {\n void fetchOverrides();\n }, [fetchOverrides]);\n\n const setOverride = useCallback(\n async (resource: string, action: Action, suppress: boolean) => {\n if (!roleId) {\n return;\n }\n const key = `${resource}:${action}`;\n // Optimistic flip.\n setOverrides((prev) => {\n const next = new Set(prev);\n if (suppress) {\n next.add(key);\n } else {\n next.delete(key);\n }\n return next;\n });\n try {\n await transport.setRolePermissionOverride({\n role_id: roleId,\n resource,\n action,\n suppress,\n });\n setError(null);\n } catch (e) {\n const err = e instanceof Error ? e : new Error(String(e));\n setError(err);\n // Reconcile FIRST so the optimistic Set reverts before the\n // caller's catch runs (otherwise their re-render still shows\n // the optimistic value), THEN rethrow so callers staging\n // writes can surface the real failure (RLS denial, network\n // error, …) in their own toast / save log. Silently\n // swallowing the error here looked like \"save succeeded but\n // toggle flipped back to previous\", which was indistinguishable\n // from a no-op for the admin.\n await fetchOverrides();\n throw err;\n }\n // Reconcile against the server in case the optimistic flip\n // was wrong (e.g. concurrent edit from another admin tab).\n void fetchOverrides();\n },\n [transport, roleId, fetchOverrides],\n );\n\n return { overrides, setOverride, error, refresh: fetchOverrides };\n}\n\nexport function useCreateCompany() {\n const transport = useAdminTransport();\n return useMutation(transport.createCompany);\n}\n\nexport function useInviteCompanyMember() {\n const transport = useAdminTransport();\n return useMutation(transport.inviteCompanyMember);\n}\n\n// ─────────────────────────────────────────────────────────────────\n// Convenience: hold a role's full state (role + permission grid)\n// in one hook, with a `setCell` mutator that optimistically updates\n// the local cache and writes through to the transport.\n// ─────────────────────────────────────────────────────────────────\n\nexport interface RolePermissionGrid {\n // resource → action → boolean\n [resource: string]: { [A in Action]: boolean };\n}\n\n/**\n * Per-cell origin marker:\n * * `'direct'` — explicit admin grant on rbac.role_permissions\n * * `'override'` — admin set an override on rbac.role_permission_overrides\n * (0.6.0+; cell is OFF even if a direct or\n * implied grant would otherwise apply)\n * * `<string>` — name of the parent resource that implies this\n * cell via rbac.resource_dependencies\n * * `null` — neither granted nor implied\n *\n * In 0.4.x this was driven by the `<action>_granted_via` columns on\n * rbac.role_permissions. In 0.5.0+ implied rows are no longer\n * materialised — origin is computed client-side from the dependency\n * graph + the role's direct grants + (0.6.0+) any overrides.\n */\nexport interface RolePermissionOriginGrid {\n [resource: string]: { [A in Action]: string | null };\n}\n\nexport function useRolePermissionGrid(roleId: string | null) {\n const { data, isLoading, error, refresh } = useAdminRolePermissions(roleId);\n const dependencies = useAdminResourceDependencies();\n const overridesHook = useRolePermissionOverrides(roleId);\n const setCell = useSetRolePermissionCell();\n const transport = useAdminTransport();\n\n // 0.5.0: grid reflects the role's DIRECT grants only (the data\n // returned from rbac.role_permissions). Implied state for matrix\n // rendering is computed by the consumer (see PermissionsMatrix —\n // it expands the grid against the dependency graph for both the\n // isCellEnabled and cellOrigin render props).\n const grid = useMemo<RolePermissionGrid>(() => {\n const out: RolePermissionGrid = {};\n for (const row of data ?? []) {\n out[row.resource] = {\n read: row.can_read,\n write: row.can_write,\n update: row.can_update,\n delete: row.can_delete,\n };\n }\n return out;\n }, [data]);\n\n // Reverse-indexed dependency map: child → list of (parent, action)\n // edges that confer implied access. Used by PermissionsMatrix to\n // resolve cellOrigin in O(1) per cell.\n const parentsByChild = useMemo(() => {\n const map = new Map<\n string,\n ReadonlyArray<{ parent: string; action: Action }>\n >();\n for (const edge of dependencies.data ?? []) {\n const list = map.get(edge.child_resource) ?? [];\n map.set(edge.child_resource, [\n ...list,\n { parent: edge.parent_resource, action: edge.action },\n ]);\n }\n return map;\n }, [dependencies.data]);\n\n const originGrid = useMemo<RolePermissionOriginGrid>(() => {\n const out: RolePermissionOriginGrid = {};\n const resources = new Set<string>(Object.keys(grid));\n for (const child of parentsByChild.keys()) {\n resources.add(child);\n }\n const overrides = overridesHook.overrides;\n for (const resource of resources) {\n const directs = grid[resource];\n const cellOrigins: { [A in Action]: string | null } = {\n read: null,\n write: null,\n update: null,\n delete: null,\n };\n for (const action of ACTIONS) {\n // 0.6.2+ priority order:\n // 1. Direct grant ALWAYS wins. Override on a directly-granted\n // cell stays a no-op until / unless the direct grant is\n // revoked — matches the resolver SQL which only checks\n // overrides on the implied path.\n // 2. Implied-via-parent, suppressed by override → 'override'.\n // 3. Implied-via-parent, not suppressed → parent name.\n // 4. Nothing → null.\n if (directs?.[action]) {\n cellOrigins[action] = \"direct\";\n continue;\n }\n const parents = parentsByChild.get(resource) ?? [];\n const impliedFrom = parents.find(\n (p) => p.action === action && grid[p.parent]?.[action] === true,\n );\n if (impliedFrom) {\n if (overrides.has(`${resource}:${action}`)) {\n cellOrigins[action] = \"override\";\n } else {\n cellOrigins[action] = impliedFrom.parent;\n }\n continue;\n }\n cellOrigins[action] = null;\n }\n out[resource] = cellOrigins;\n }\n return out;\n }, [grid, parentsByChild, overridesHook.overrides]);\n\n const updateCell = useCallback(\n async (resource: string, action: Action, value: boolean) => {\n if (!roleId) {\n return;\n }\n // 0.5.0: every write is a DIRECT grant (or direct revoke). No\n // cascade — implied access is computed by the resolver at\n // query time, never materialised. Implied cells are\n // non-editable in the UI (PermissionsMatrix disables them),\n // so this code path only fires for direct cells.\n await setCell.mutate({\n role_id: roleId,\n resource,\n action,\n value,\n grantedVia: null,\n });\n void refresh();\n },\n [roleId, setCell, refresh],\n );\n\n // transport is intentionally part of the closure to keep\n // batchSetRolePermissionCells available to adopters extending the\n // hook (template applies, batch role copies). It's not invoked\n // here in 0.5.0 because per-cell writes are sufficient.\n void transport;\n\n return {\n grid,\n originGrid,\n parentsByChild,\n /** 0.6.0+. Set of `\"<resource>:<action>\"` for this role's overrides. */\n overrides: overridesHook.overrides,\n /**\n * 0.6.0+. Suppress (`suppress=true`) or restore (`suppress=false`)\n * an implied permission for this role. The grid + originGrid\n * re-render with `'override'` state on the cell as soon as the\n * optimistic flip lands.\n */\n setOverride: overridesHook.setOverride,\n isLoading: isLoading || dependencies.isLoading,\n error: error ?? dependencies.error ?? overridesHook.error,\n refresh,\n updateCell,\n isUpdating: setCell.isPending,\n updateError: setCell.error,\n };\n}\n\nconst ACTIONS: ReadonlyArray<Action> = [\"read\", \"write\", \"update\", \"delete\"];\n","/**\n * Headless permissions matrix.\n *\n * Owns:\n * - reading the role's current permission grid\n * - debounced write-through on every cell toggle\n * - grouping resources by `group` for a sectioned UI\n *\n * Owns NOTHING about styling — the consumer renders all JSX via the\n * single `children` render-prop. A copy-paste reference styled with\n * Tailwind + Radix lives in `examples/react-admin/`.\n *\n * @example minimum viable adoption\n *\n * <PermissionsMatrix\n * roleId={role.id}\n * resources={resources.filter(r => r.scope === role.scope)}\n * >\n * {({ groups, isCellEnabled, setCell, isLoading }) =>\n * groups.map((g) => (\n * <section key={g.group}>\n * <h3>{g.group}</h3>\n * {g.resources.map((r) => (\n * <div key={r.resource}>\n * <span>{r.label}</span>\n * {([\"read\", \"write\", \"update\", \"delete\"] as const).map((a) => (\n * <input\n * key={a}\n * type=\"checkbox\"\n * checked={isCellEnabled(r.resource, a)}\n * disabled={isLoading}\n * onChange={(e) => setCell(r.resource, a, e.target.checked)}\n * />\n * ))}\n * </div>\n * ))}\n * </section>\n * ))\n * }\n * </PermissionsMatrix>\n */\n\nimport { useMemo } from \"react\";\n\nimport type {\n Action,\n ResourceDescriptor,\n} from \"../types.js\";\nimport { groupResources } from \"../client.js\";\n\nimport { useRolePermissionGrid } from \"./hooks.js\";\n\nexport interface MatrixGroup {\n group: string;\n resources: ResourceDescriptor[];\n}\n\nexport interface MatrixRenderArgs {\n /** Resources grouped by their `group` label, original insertion order. */\n groups: MatrixGroup[];\n /**\n * Effective state of a cell after applying direct grants, the\n * resource-dependency expansion, and any per-role overrides.\n * What the resolver would answer for a user holding this role.\n */\n isCellEnabled: (resource: string, action: Action) => boolean;\n /**\n * Origin of a single cell:\n * * `'direct'` — explicit admin grant on rbac.role_permissions\n * * `'override'` — admin suppressed it via rbac.role_permission_overrides\n * (0.6.0+; cell is off even if a parent would imply)\n * * `<string>` — the name of the parent resource whose\n * `dependsOn` edge implies this cell\n *\n * Available since 0.4.0; the `'override'` value is 0.6.0+.\n */\n cellOrigin: (resource: string, action: Action) => \"direct\" | \"override\" | string;\n /**\n * 0.6.3+. True only when this role has an explicit row in\n * `rbac.role_permissions` for `(resource, action)`. Implied access\n * via a parent's `dependsOn` cascade returns `false` here — the\n * implied state is exposed via `cellOrigin` (parent name) and\n * belongs in the dependents-expander UI, not in the main row.\n *\n * Use this for the main cell switch state so direct and implied\n * grants are never conflated in the matrix:\n *\n * const checked = isCellDirect(resource, action);\n * const onToggle = (v: boolean) => setCell(resource, action, v);\n *\n * Then surface the implied cascade and its per-role override toggle\n * in the parent's expander section, where the admin can suppress a\n * single implied edge without touching the role's direct grants.\n */\n isCellDirect: (resource: string, action: Action) => boolean;\n /**\n * Toggle a DIRECT grant on rbac.role_permissions. Use for cells\n * that the matrix UI shows as \"direct\" (no implied parent). For\n * cells that are implied, use `setOverride` instead — that's what\n * lets the admin opt a single role out of a cascade without\n * touching the parent grant or the registry.\n */\n setCell: (resource: string, action: Action, value: boolean) => Promise<void>;\n /**\n * 0.6.0+. Suppress (`suppress=true`) or restore (`suppress=false`)\n * an implied permission for this role via\n * rbac.role_permission_overrides. Writes are optimistic; the\n * `cellOrigin` reflects the new state immediately.\n */\n setOverride: (\n resource: string,\n action: Action,\n suppress: boolean,\n ) => Promise<void>;\n isLoading: boolean;\n isUpdating: boolean;\n error: Error | null;\n /** All four actions, exposed for the consumer to render headers. */\n actions: ReadonlyArray<Action>;\n}\n\nexport interface PermissionsMatrixProps {\n roleId: string | null;\n resources: ReadonlyArray<ResourceDescriptor>;\n children: (args: MatrixRenderArgs) => React.ReactNode;\n}\n\nconst ACTIONS = [\"read\", \"write\", \"update\", \"delete\"] as const;\n\nexport function PermissionsMatrix(props: PermissionsMatrixProps) {\n const {\n grid,\n originGrid,\n isLoading,\n error,\n updateCell,\n isUpdating,\n setOverride: gridSetOverride,\n } = useRolePermissionGrid(props.roleId);\n\n const groups = useMemo<MatrixGroup[]>(\n () => groupResources(props.resources),\n [props.resources],\n );\n\n // A cell is \"enabled\" when its origin is direct or implied via a\n // parent — anything except null/`override`/`undefined`. The\n // 0.6.0 override marker counts as \"not enabled\" because the\n // resolver subtracts it.\n const isCellEnabled = (resource: string, action: Action): boolean => {\n const origin = originGrid[resource]?.[action];\n if (origin == null || origin === \"override\") {\n return false;\n }\n return true;\n };\n\n const cellOrigin = (\n resource: string,\n action: Action,\n ): \"direct\" | \"override\" | string => {\n const origin = originGrid[resource]?.[action];\n // No grant of any kind → still \"direct\" for the UI (an empty\n // direct cell). Implied grants return the parent resource name.\n return origin == null ? \"direct\" : origin;\n };\n\n // 0.6.3+. Strict direct-grant check — true only when there's an\n // actual row on rbac.role_permissions for this (role, resource,\n // action). Differs from isCellEnabled, which also returns true\n // for implied-via-parent cells. The main row in the matrix should\n // bind to this; implied state belongs in the dependents-expander.\n const isCellDirect = (resource: string, action: Action): boolean => {\n return grid[resource]?.[action] === true;\n };\n\n const setCell = async (resource: string, action: Action, value: boolean) => {\n await updateCell(resource, action, value);\n };\n\n const setOverride = async (\n resource: string,\n action: Action,\n suppress: boolean,\n ) => {\n await gridSetOverride(resource, action, suppress);\n };\n\n return (\n <>\n {props.children({\n groups,\n isCellEnabled,\n cellOrigin,\n isCellDirect,\n setCell,\n setOverride,\n isLoading,\n isUpdating,\n error,\n actions: ACTIONS,\n })}\n </>\n );\n}\n","/**\n * Headless roles-list controller. Tracks selection + create/delete\n * mutations; consumer renders the list, the new-role dialog, and\n * the destructive-action confirmation.\n */\n\nimport { useCallback, useState } from \"react\";\n\nimport type { ResourceScope } from \"../types.js\";\n\nimport {\n useAdminRoles,\n useCreateRole,\n useDeleteRole,\n} from \"./hooks.js\";\nimport type { AdminRole } from \"./types.js\";\n\nexport interface RolesListRenderArgs {\n roles: AdminRole[];\n isLoading: boolean;\n error: Error | null;\n\n selectedRoleId: string | null;\n selectRole: (id: string | null) => void;\n\n createRole: (input: {\n name: string;\n description?: string;\n }) => Promise<AdminRole>;\n isCreating: boolean;\n createError: Error | null;\n\n deleteRole: (id: string) => Promise<void>;\n isDeleting: boolean;\n deleteError: Error | null;\n\n refresh: () => Promise<void>;\n}\n\nexport interface RolesListProps {\n scope: ResourceScope;\n /** Required for company-scope. Pass `null` for templates. */\n companyId?: string | null;\n /** Pre-select the first role on load. Default: true. */\n autoSelectFirst?: boolean;\n children: (args: RolesListRenderArgs) => React.ReactNode;\n}\n\nexport function RolesList(props: RolesListProps) {\n const { scope, companyId, autoSelectFirst = true } = props;\n\n const list = useAdminRoles({ scope, companyId });\n const create = useCreateRole();\n const remove = useDeleteRole();\n\n const [selectedRoleId, setSelectedRoleId] = useState<string | null>(null);\n\n // Auto-select first role on load.\n if (\n autoSelectFirst &&\n selectedRoleId == null &&\n list.data != null &&\n list.data.length > 0\n ) {\n setSelectedRoleId(list.data[0]!.id);\n }\n\n const createRole = useCallback(\n async (input: { name: string; description?: string }) => {\n const role = await create.mutate({\n scope,\n companyId: companyId ?? null,\n name: input.name,\n description: input.description,\n });\n await list.refresh();\n setSelectedRoleId(role.id);\n return role;\n },\n [create, scope, companyId, list],\n );\n\n const deleteRole = useCallback(\n async (id: string) => {\n await remove.mutate(id);\n if (selectedRoleId === id) {\n setSelectedRoleId(null);\n }\n await list.refresh();\n },\n [remove, list, selectedRoleId],\n );\n\n return (\n <>\n {props.children({\n roles: list.data ?? [],\n isLoading: list.isLoading,\n error: list.error,\n selectedRoleId,\n selectRole: setSelectedRoleId,\n createRole,\n isCreating: create.isPending,\n createError: create.error,\n deleteRole,\n isDeleting: remove.isPending,\n deleteError: remove.error,\n refresh: list.refresh,\n })}\n </>\n );\n}\n","/**\n * Headless invite-member form state. Tracks email + selected role\n * ids, runs basic local validation, and exposes a submit handler\n * that calls the configured transport (Supabase Auth invite by\n * default).\n */\n\nimport { useCallback, useState } from \"react\";\n\nimport { useAdminRoles, useInviteCompanyMember } from \"./hooks.js\";\nimport type { AdminRole } from \"./types.js\";\n\nexport interface InviteMemberFormRenderArgs {\n // form state\n email: string;\n setEmail: (v: string) => void;\n selectedRoleIds: Set<string>;\n toggleRole: (roleId: string) => void;\n resetForm: () => void;\n\n // catalog\n roles: AdminRole[];\n rolesLoading: boolean;\n rolesError: Error | null;\n\n // submission\n submit: () => Promise<void>;\n isSubmitting: boolean;\n submitError: Error | null;\n submittedSuccessfully: boolean;\n\n // validation\n isValid: boolean;\n errors: { email?: string; roles?: string };\n}\n\nexport interface InviteMemberFormProps {\n companyId: string;\n /** Called after a successful invite — typically clears a dialog. */\n onSuccess?: () => void;\n children: (args: InviteMemberFormRenderArgs) => React.ReactNode;\n}\n\nexport function InviteMemberForm(props: InviteMemberFormProps) {\n const rolesQuery = useAdminRoles({\n scope: \"company\",\n companyId: props.companyId,\n });\n const invite = useInviteCompanyMember();\n\n const [email, setEmail] = useState(\"\");\n const [selectedRoleIds, setSelectedRoleIds] = useState<Set<string>>(\n new Set(),\n );\n const [submittedSuccessfully, setSubmittedSuccessfully] = useState(false);\n\n const toggleRole = useCallback((roleId: string) => {\n setSelectedRoleIds((prev) => {\n const next = new Set(prev);\n if (next.has(roleId)) {\n next.delete(roleId);\n } else {\n next.add(roleId);\n }\n return next;\n });\n }, []);\n\n const resetForm = useCallback(() => {\n setEmail(\"\");\n setSelectedRoleIds(new Set());\n setSubmittedSuccessfully(false);\n }, []);\n\n const errors: InviteMemberFormRenderArgs[\"errors\"] = {};\n if (email.trim() && !/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email.trim())) {\n errors.email = \"Bitte gib eine gültige E-Mail-Adresse ein.\";\n }\n if (selectedRoleIds.size === 0) {\n errors.roles = \"Bitte mindestens eine Rolle auswählen.\";\n }\n const isValid =\n email.trim().length > 0 &&\n Object.keys(errors).length === 0;\n\n const submit = useCallback(async () => {\n if (!isValid) {\n return;\n }\n await invite.mutate({\n companyId: props.companyId,\n email: email.trim(),\n roleIds: Array.from(selectedRoleIds),\n });\n setSubmittedSuccessfully(true);\n props.onSuccess?.();\n }, [invite, props, email, selectedRoleIds, isValid]);\n\n return (\n <>\n {props.children({\n email,\n setEmail,\n selectedRoleIds,\n toggleRole,\n resetForm,\n roles: rolesQuery.data ?? [],\n rolesLoading: rolesQuery.isLoading,\n rolesError: rolesQuery.error,\n submit,\n isSubmitting: invite.isPending,\n submitError: invite.error,\n submittedSuccessfully,\n isValid,\n errors,\n })}\n </>\n );\n}\n"],"mappings":"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","names":["error","useMemo","jsx","ACTIONS","useMemo","useCallback","useState","Fragment","jsx","useState","useCallback","useCallback","useState","Fragment","jsx","useState","useCallback"]}
@@ -169,27 +169,41 @@ function AuthRbacProvider(props) {
169
169
  },
170
170
  [persist]
171
171
  );
172
- const refresh = (0, import_react.useCallback)(async () => {
173
- setLoading(true);
172
+ const fetcherRef = (0, import_react.useRef)(fetcher);
173
+ const activeCompanyIdRef = (0, import_react.useRef)(activeCompanyId);
174
+ const persistRef = (0, import_react.useRef)(persist);
175
+ fetcherRef.current = fetcher;
176
+ activeCompanyIdRef.current = activeCompanyId;
177
+ persistRef.current = persist;
178
+ const refresh = (0, import_react.useCallback)(async (opts) => {
179
+ const silent = opts?.silent === true;
180
+ if (!silent) {
181
+ setLoading(true);
182
+ }
174
183
  setError(null);
175
184
  try {
176
- const next = await fetcher.fetchProfile();
185
+ const next = await fetcherRef.current.fetchProfile();
177
186
  setProfile(next);
178
- const stillMember = activeCompanyId != null && next.memberships.some((m) => m.company_id === activeCompanyId);
187
+ const currentCompanyId = activeCompanyIdRef.current;
188
+ const stillMember = currentCompanyId != null && next.memberships.some((m) => m.company_id === currentCompanyId);
179
189
  if (!stillMember) {
180
190
  const fallback = next.memberships[0]?.company_id ?? null;
181
191
  setActiveCompanyState(fallback);
182
- persist(fallback);
192
+ persistRef.current(fallback);
183
193
  }
184
194
  } catch (e) {
185
195
  setError(e instanceof Error ? e : new Error(String(e)));
186
196
  } finally {
187
- setLoading(false);
197
+ if (!silent) {
198
+ setLoading(false);
199
+ }
188
200
  }
189
- }, [fetcher]);
201
+ }, []);
202
+ const hasFetchedOnceRef = (0, import_react.useRef)(false);
190
203
  (0, import_react.useEffect)(() => {
191
- void refresh();
192
- }, [refresh]);
204
+ void refresh({ silent: hasFetchedOnceRef.current });
205
+ hasFetchedOnceRef.current = true;
206
+ }, [fetcher, refresh]);
193
207
  const resolver = (0, import_react.useMemo)(() => {
194
208
  if (profile == null) {
195
209
  return {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/react/index.ts","../../src/react/AuthRbacProvider.tsx","../../src/client.ts","../../src/react/useCan.ts","../../src/react/useCanAccessSection.ts","../../src/react/Can.tsx","../../src/react/RequirePermission.tsx","../../src/react/useActiveCompany.ts","../../src/react/useFrontendConfig.ts","../../src/define.ts","../../src/fetchers.ts"],"sourcesContent":["/**\n * React + Next.js entry. Import this in browser code:\n *\n * import { AuthRbacProvider, useCan } from \"snipe-auth-rbac/react\";\n *\n * The non-React entry (`snipe-auth-rbac`) re-exports types and the\n * pure resolver, suitable for Node, edge workers, and tests.\n */\n\nexport {\n AuthRbacProvider,\n useAuthRbac,\n type AuthRbacProviderProps,\n} from \"./AuthRbacProvider.js\";\n\nexport { useCan } from \"./useCan.js\";\nexport { useCanAccessSection } from \"./useCanAccessSection.js\";\nexport { Can, type CanProps } from \"./Can.js\";\nexport {\n RequirePermission,\n type RequirePermissionProps,\n} from \"./RequirePermission.js\";\nexport { useActiveCompany, type ActiveCompany } from \"./useActiveCompany.js\";\nexport { useFrontendConfig } from \"./useFrontendConfig.js\";\n\n// Re-exports for convenience so consumers don't need two imports.\nexport type {\n Action,\n AuthRbacFetcher,\n CompanyMembership,\n DependencyEdge,\n DirectGrantMap,\n FrontendConfig,\n PermissionGrid,\n PermissionMap,\n ResourceDescriptor,\n ResourceRegistry,\n ResourceScope,\n RoleSummary,\n UserProfile,\n} from \"../types.js\";\n\nexport { RbacRegistryError } from \"../define.js\";\n\nexport {\n createSupabaseFetcher,\n createHttpFetcher,\n detectRbacSchema,\n} from \"../fetchers.js\";\n\nimport { defineAuthRbac as _defineAuthRbac } from \"../define.js\";\nimport { Can } from \"./Can.js\";\nimport { RequirePermission } from \"./RequirePermission.js\";\nimport { useCan } from \"./useCan.js\";\nimport { useCanAccessSection } from \"./useCanAccessSection.js\";\n\nimport type { ResourceDescriptor } from \"../types.js\";\nimport type { TypedGuards } from \"../define.js\";\n\n/**\n * Typed factory — pass a const-asserted resource registry and get\n * back guards whose `resource` arg is constrained to the registered\n * names. Recommended at the top of every host project.\n *\n * See ../define.ts for the full doc + example.\n */\nexport function defineAuthRbac<\n const Reg extends ReadonlyArray<ResourceDescriptor>,\n>(resources: Reg): TypedGuards<Reg[number][\"resource\"]> {\n return _defineAuthRbac(resources, {\n useCan,\n useCanAccessSection,\n Can,\n RequirePermission,\n });\n}\n\nexport type { TypedGuards } from \"../define.js\";\n","import {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useState,\n type ReactNode,\n} from \"react\";\n\nimport {\n buildPermissionResolver,\n type AuthRbacClient,\n} from \"../client.js\";\nimport type {\n AuthRbacFetcher,\n PermissionMap,\n ResourceRegistry,\n UserProfile,\n} from \"../types.js\";\n\ninterface AuthRbacContextValue {\n /**\n * `null` means we haven't hydrated yet. Components should treat\n * the not-loaded state as \"no permissions\" (fail-closed).\n */\n profile: UserProfile | null;\n loading: boolean;\n error: Error | null;\n resources: ResourceRegistry;\n activeCompanyId: string | null;\n setActiveCompany: (id: string | null) => void;\n refresh: () => Promise<void>;\n resolver: AuthRbacClient;\n}\n\nconst AuthRbacContext = createContext<AuthRbacContextValue | null>(null);\n\nexport interface AuthRbacProviderProps {\n fetcher: AuthRbacFetcher;\n resources: ResourceRegistry;\n /**\n * Initial active company. Common patterns:\n * - read from URL query/path\n * - read from localStorage\n * - omit and let the user pick from the switcher\n */\n initialCompanyId?: string | null;\n /**\n * Persistence hook. Called every time the active company changes.\n * Default: writes to `localStorage` under\n * `auth-rbac:active-company`. Pass `false` to disable.\n */\n persistActiveCompany?:\n | ((id: string | null) => void)\n | false;\n children: ReactNode;\n}\n\nconst STORAGE_KEY = \"auth-rbac:active-company\";\n\nconst defaultPersist = (id: string | null) => {\n if (typeof window === \"undefined\") {\n return;\n }\n try {\n if (id == null) {\n window.localStorage.removeItem(STORAGE_KEY);\n } else {\n window.localStorage.setItem(STORAGE_KEY, id);\n }\n } catch {\n // localStorage may be unavailable (private browsing, SSR) —\n // fall back to in-memory only.\n }\n};\n\nconst readPersisted = (): string | null => {\n if (typeof window === \"undefined\") {\n return null;\n }\n try {\n return window.localStorage.getItem(STORAGE_KEY);\n } catch {\n return null;\n }\n};\n\nexport function AuthRbacProvider(props: AuthRbacProviderProps) {\n const { fetcher, resources, initialCompanyId, persistActiveCompany } = props;\n\n const [profile, setProfile] = useState<UserProfile | null>(null);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n\n const [activeCompanyId, setActiveCompanyState] = useState<string | null>(\n initialCompanyId ?? readPersisted(),\n );\n\n const persist = useMemo(() => {\n if (persistActiveCompany === false) {\n return () => {};\n }\n return persistActiveCompany ?? defaultPersist;\n }, [persistActiveCompany]);\n\n const setActiveCompany = useCallback(\n (id: string | null) => {\n setActiveCompanyState(id);\n persist(id);\n },\n [persist],\n );\n\n const refresh = useCallback(async () => {\n setLoading(true);\n setError(null);\n try {\n const next = await fetcher.fetchProfile();\n setProfile(next);\n // If the persisted company isn't a membership, fall back to\n // the first one (or null for users with no memberships).\n const stillMember =\n activeCompanyId != null &&\n next.memberships.some((m) => m.company_id === activeCompanyId);\n if (!stillMember) {\n const fallback =\n next.memberships[0]?.company_id ?? null;\n setActiveCompanyState(fallback);\n persist(fallback);\n }\n } catch (e) {\n setError(e instanceof Error ? e : new Error(String(e)));\n } finally {\n setLoading(false);\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [fetcher]);\n\n useEffect(() => {\n void refresh();\n }, [refresh]);\n\n const resolver = useMemo<AuthRbacClient>(() => {\n if (profile == null) {\n // Empty resolver until the profile lands. Always returns\n // false → guards fall through to the unauthenticated branch.\n return {\n can: () => false,\n canAccessSection: () => false,\n activePermissions: () => ({}) as PermissionMap,\n systemPermissions: () => ({}) as PermissionMap,\n };\n }\n return buildPermissionResolver(resources, profile, activeCompanyId);\n }, [profile, resources, activeCompanyId]);\n\n const value = useMemo<AuthRbacContextValue>(\n () => ({\n profile,\n loading,\n error,\n resources,\n activeCompanyId,\n setActiveCompany,\n refresh,\n resolver,\n }),\n [\n profile,\n loading,\n error,\n resources,\n activeCompanyId,\n setActiveCompany,\n refresh,\n resolver,\n ],\n );\n\n return (\n <AuthRbacContext.Provider value={value}>\n {props.children}\n </AuthRbacContext.Provider>\n );\n}\n\nexport function useAuthRbac(): AuthRbacContextValue {\n const ctx = useContext(AuthRbacContext);\n if (!ctx) {\n throw new Error(\n \"useAuthRbac must be used within an <AuthRbacProvider> — wrap your app at the root.\",\n );\n }\n return ctx;\n}\n","/**\n * Transport-agnostic client: turns an adopter-supplied\n * `AuthRbacFetcher` into a permission resolver. The React provider\n * wraps this; non-React consumers (Node scripts, edge functions)\n * can use it directly.\n */\n\nimport type {\n Action,\n AuthRbacFetcher,\n PermissionMap,\n ResourceDescriptor,\n ResourceRegistry,\n ResourceScope,\n UserProfile,\n} from \"./types.js\";\n\nexport interface AuthRbacClientOptions {\n fetcher: AuthRbacFetcher;\n /**\n * The host project's full resource list. Required so the resolver\n * can look up a resource's scope without a DB round-trip per call.\n * Re-using the same array the host syncs into the\n * `rbac.resources` table at boot keeps everything in lockstep.\n */\n resources: ResourceRegistry;\n}\n\nexport interface CanOptions {\n /**\n * Override the active company. Omit to use the company the\n * caller has currently activated (the React provider tracks\n * this; for direct client use you must pass it).\n */\n companyId?: string | null;\n}\n\n/**\n * Pure resolver. Given a hydrated profile it answers boolean\n * questions instantly — no I/O. The `resourceMap` is built once at\n * construction so per-call work is two map lookups.\n */\nexport function buildPermissionResolver(\n resources: ResourceRegistry,\n profile: UserProfile,\n defaultCompanyId: string | null,\n) {\n const scopeByResource = new Map<string, ResourceScope>(\n resources.map((r) => [r.resource, r.scope]),\n );\n\n const can = (\n resource: string,\n action: Action,\n options?: CanOptions,\n ): boolean => {\n if (profile.is_super_admin) {\n return true;\n }\n const scope = scopeByResource.get(resource);\n if (!scope) {\n // Unknown resource — fail closed.\n return false;\n }\n if (scope === \"system\") {\n return readGrid(profile.system_permissions, resource, action);\n }\n const companyId = options?.companyId ?? defaultCompanyId;\n if (!companyId) {\n return false;\n }\n const membership = profile.memberships.find(\n (m) => m.company_id === companyId,\n );\n if (!membership) {\n return false;\n }\n return readGrid(membership.permissions, resource, action);\n };\n\n /**\n * Direct-grant lookup: returns true only if the user has the\n * action granted on the resource as a direct admin grant —\n * `<action>_granted_via IS NULL` in `rbac.role_permissions`.\n * Implied rows (granted as a side-effect of a parent resource's\n * `dependsOn` cascade) return false here.\n *\n * Use for top-level navigation / list-page gating: a Verwalter\n * with only `leases:read` direct gets the Leases sidebar item but\n * not Tenants / Units / Properties, even though `can(...)` returns\n * true for those (because the implied rows let the lease detail\n * page render its joined data).\n *\n * Available since 0.4.0. For older SQL that doesn't return\n * `direct_*` maps, every cell answers false — equivalent to\n * \"no direct grants known\". Adopters running pre-0.4.0 SQL should\n * keep using `can(...)`.\n */\n const canAccessSection = (\n resource: string,\n action: Action = \"read\",\n options?: CanOptions,\n ): boolean => {\n if (profile.is_super_admin) {\n return true;\n }\n const scope = scopeByResource.get(resource);\n if (!scope) {\n return false;\n }\n if (scope === \"system\") {\n return readDirect(profile, action, resource);\n }\n const companyId = options?.companyId ?? defaultCompanyId;\n if (!companyId) {\n return false;\n }\n const membership = profile.memberships.find(\n (m) => m.company_id === companyId,\n );\n if (!membership) {\n return false;\n }\n return readDirectMembership(membership, action, resource);\n };\n\n return {\n can,\n canAccessSection,\n /** Permission map for the active (or specified) company. */\n activePermissions: (companyId?: string | null): PermissionMap => {\n const id = companyId ?? defaultCompanyId;\n if (!id) {\n return {};\n }\n return (\n profile.memberships.find((m) => m.company_id === id)?.permissions ?? {}\n );\n },\n systemPermissions: (): PermissionMap => profile.system_permissions,\n };\n}\n\nfunction readDirect(\n profile: UserProfile,\n action: Action,\n resource: string,\n): boolean {\n const map =\n action === \"read\"\n ? profile.system_direct_reads\n : action === \"write\"\n ? profile.system_direct_writes\n : action === \"update\"\n ? profile.system_direct_updates\n : profile.system_direct_deletes;\n return map?.[resource] === true;\n}\n\nfunction readDirectMembership(\n membership: { direct_reads?: Readonly<Record<string, boolean>>;\n direct_writes?: Readonly<Record<string, boolean>>;\n direct_updates?: Readonly<Record<string, boolean>>;\n direct_deletes?: Readonly<Record<string, boolean>>; },\n action: Action,\n resource: string,\n): boolean {\n const map =\n action === \"read\"\n ? membership.direct_reads\n : action === \"write\"\n ? membership.direct_writes\n : action === \"update\"\n ? membership.direct_updates\n : membership.direct_deletes;\n return map?.[resource] === true;\n}\n\nfunction readGrid(\n map: PermissionMap,\n resource: string,\n action: Action,\n): boolean {\n const grid = map[resource];\n if (!grid) {\n return false;\n }\n return grid[action];\n}\n\n/**\n * Helper: groups a resource registry by `group` for the matrix UI.\n * Returns groups in insertion order with their resources.\n */\nexport function groupResources(\n registry: ResourceRegistry,\n): Array<{ group: string; resources: ResourceDescriptor[] }> {\n const order: string[] = [];\n const buckets = new Map<string, ResourceDescriptor[]>();\n for (const r of registry) {\n const key = r.group ?? \"Sonstige\";\n if (!buckets.has(key)) {\n buckets.set(key, []);\n order.push(key);\n }\n buckets.get(key)!.push(r);\n }\n return order.map((g) => ({ group: g, resources: buckets.get(g)! }));\n}\n\nexport type AuthRbacClient = ReturnType<typeof buildPermissionResolver>;\nexport type { AuthRbacClientOptions as ClientOptions };\n","import type { Action } from \"../types.js\";\nimport type { CanOptions } from \"../client.js\";\n\nimport { useAuthRbac } from \"./AuthRbacProvider.js\";\n\n/**\n * Boolean permission check.\n *\n * @example\n * const canEdit = useCan(\"properties\", \"update\");\n * <Button disabled={!canEdit}>Speichern</Button>\n *\n * @example explicitly target a non-active company\n * const canRead = useCan(\"payments\", \"read\", { companyId: targetId });\n */\nexport function useCan(\n resource: string,\n action: Action,\n options?: CanOptions,\n): boolean {\n const { resolver } = useAuthRbac();\n return resolver.can(resource, action, options);\n}\n","import type { Action } from \"../types.js\";\nimport type { CanOptions } from \"../client.js\";\n\nimport { useAuthRbac } from \"./AuthRbacProvider.js\";\n\n/**\n * Direct-grant check, for sidebar items and list-page route guards.\n *\n * Returns `true` only when the action is granted on the resource as\n * a **direct** admin grant (no `<action>_granted_via`). Rows that\n * exist only because a parent resource's `dependsOn` cascade\n * materialised them return `false` here — even though\n * `useCan(resource, action)` would return `true` for the same role.\n *\n * Use case: a Verwalter with only `leases:read` direct should see\n * Mietverträge in the sidebar but **not** Mieter / Einheiten /\n * Liegenschaften — those reads are implied so the lease detail page\n * can render, not because the role should navigate to them as\n * top-level sections.\n *\n * @example sidebar item filtering\n * const showLeasesInSidebar = useCanAccessSection(\"leases\");\n * const showUnitsInSidebar = useCanAccessSection(\"units\");\n *\n * @example list-route gating\n * if (!useCanAccessSection(\"units\")) return <Forbidden />;\n *\n * Available since 0.4.0. With older SQL that doesn't return\n * `direct_*` maps, this always answers `false` — adopters still on\n * pre-0.4.0 SQL should keep using `useCan`.\n */\nexport function useCanAccessSection(\n resource: string,\n action: Action = \"read\",\n options?: CanOptions,\n): boolean {\n const { resolver } = useAuthRbac();\n return resolver.canAccessSection(resource, action, options);\n}\n","import type { ReactNode } from \"react\";\n\nimport type { Action } from \"../types.js\";\nimport type { CanOptions } from \"../client.js\";\n\nimport { useCan } from \"./useCan.js\";\n\nexport interface CanProps extends CanOptions {\n resource: string;\n action: Action;\n /** Rendered when the user has the permission. */\n children: ReactNode;\n /**\n * Rendered when the user does NOT have the permission. Defaults\n * to `null` (silent hide). Pass a `<NoPermissionView />` or a\n * tooltip-wrapper to surface the denial explicitly.\n */\n fallback?: ReactNode;\n}\n\n/**\n * Subtree gate. Bails before children render so any data fetching\n * inside `children` is skipped for users without permission.\n */\nexport function Can(props: CanProps) {\n const { resource, action, companyId, children, fallback = null } = props;\n const allowed = useCan(resource, action, { companyId });\n return <>{allowed ? children : fallback}</>;\n}\n","import type { ReactNode } from \"react\";\n\nimport type { Action } from \"../types.js\";\nimport type { CanOptions } from \"../client.js\";\n\nimport { useAuthRbac } from \"./AuthRbacProvider.js\";\nimport { useCan } from \"./useCan.js\";\n\nexport interface RequirePermissionProps extends CanOptions {\n resource: string;\n action: Action;\n /**\n * What to render while the profile is still loading. Defaults to\n * `null` (no flash) — pass a spinner if your routes typically\n * mount before the profile lands.\n */\n loadingFallback?: ReactNode;\n /**\n * What to render when access is denied. Defaults to a minimal\n * \"Sie haben keinen Zugriff\" message; pass your own component to\n * theme it.\n */\n deniedFallback?: ReactNode;\n /**\n * For `react-router-dom v6` route-element usage, pass an `<Outlet />`\n * here — the gate resolves to either the outlet or the denied\n * fallback. For component-tree usage, pass any children.\n */\n children?: ReactNode;\n}\n\n/**\n * Route- or component-level guard. Three render branches:\n *\n * - profile not yet loaded → `loadingFallback`\n * - permission denied → `deniedFallback`\n * - permission granted → `children`\n *\n * Drop-in replacement for the legacy `<RequireRolesRoute>` pattern.\n *\n * @example\n * // App.tsx route table\n * <Route element={\n * <RequirePermission resource=\"payments\" action=\"read\">\n * <Outlet />\n * </RequirePermission>\n * }>\n * <Route path=\"/payments\" element={<PaymentsPage />} />\n * </Route>\n */\nexport function RequirePermission(props: RequirePermissionProps) {\n const {\n resource,\n action,\n companyId,\n loadingFallback = null,\n deniedFallback = (\n <div role=\"alert\" style={{ padding: 24 }}>\n <strong>Sie haben keinen Zugriff.</strong>\n </div>\n ),\n children = null,\n } = props;\n\n const { profile, loading } = useAuthRbac();\n const allowed = useCan(resource, action, { companyId });\n\n if (loading || profile == null) {\n return <>{loadingFallback}</>;\n }\n if (!allowed) {\n return <>{deniedFallback}</>;\n }\n return <>{children}</>;\n}\n","import { useMemo } from \"react\";\n\nimport type { CompanyMembership } from \"../types.js\";\n\nimport { useAuthRbac } from \"./AuthRbacProvider.js\";\n\nexport interface ActiveCompany {\n id: string | null;\n membership: CompanyMembership | null;\n memberships: CompanyMembership[];\n setActive: (id: string | null) => void;\n}\n\n/**\n * Read + switch the active company.\n *\n * @example\n * const { id, memberships, setActive } = useActiveCompany();\n *\n * return (\n * <select value={id ?? \"\"} onChange={(e) => setActive(e.target.value || null)}>\n * {memberships.map((m) => (\n * <option key={m.company_id} value={m.company_id}>{m.company_name}</option>\n * ))}\n * </select>\n * );\n */\nexport function useActiveCompany(): ActiveCompany {\n const { profile, activeCompanyId, setActiveCompany } = useAuthRbac();\n\n return useMemo(() => {\n const memberships = profile?.memberships ?? [];\n const membership =\n memberships.find((m) => m.company_id === activeCompanyId) ?? null;\n return {\n id: activeCompanyId,\n membership,\n memberships,\n setActive: setActiveCompany,\n };\n }, [profile, activeCompanyId, setActiveCompany]);\n}\n","import { useMemo } from \"react\";\n\nimport type { FrontendConfig } from \"../types.js\";\n\nimport { useAuthRbac } from \"./AuthRbacProvider.js\";\n\n/**\n * Reads the merged `frontend_config` for the user. Sources in\n * priority order: active company's membership > system roles. Use\n * this to drive sidebar items, dashboard defaults, and any other\n * \"what should this role see\" UX without hardcoded role checks.\n *\n * The shape is intentionally `Record<string, unknown>` — your host\n * project owns the schema. Document your keys (e.g. `sidebar`,\n * `default_dashboard`) once and stick to them.\n */\nexport function useFrontendConfig(): FrontendConfig {\n const { profile, activeCompanyId } = useAuthRbac();\n return useMemo(() => {\n if (!profile) {\n return {};\n }\n const membershipConfig =\n profile.memberships.find((m) => m.company_id === activeCompanyId)\n ?.frontend_config ?? {};\n return { ...profile.system_frontend_config, ...membershipConfig };\n }, [profile, activeCompanyId]);\n}\n","/**\n * Typed factory — turns a const-asserted resource registry into a\n * set of hooks/components whose `resource` arg is constrained to\n * the registered names. Typos become TypeScript errors instead of\n * silent runtime `false`.\n *\n * @example\n * // src/auth/resources.ts\n * import { defineAuthRbac } from \"snipe-auth-rbac/react\";\n *\n * export const RESOURCES = [\n * { resource: \"properties\", scope: \"company\", label: \"Liegenschaften\", group: \"Stammdaten\" },\n * { resource: \"payments\", scope: \"company\", label: \"Zahlungen\", group: \"Finanzen\" },\n * { resource: \"system_audit\", scope: \"system\", label: \"Audit-Log\", group: \"Plattform\" },\n * ] as const;\n *\n * export const { useCan, Can, RequirePermission } = defineAuthRbac(RESOURCES);\n *\n * // ----- elsewhere -----\n * useCan(\"properties\", \"update\"); // ✓\n * useCan(\"paymetns\", \"update\"); // ✗ TS error: not assignable to type \"properties\" | \"payments\" | \"system_audit\"\n */\n\nimport type { ComponentType, ReactNode } from \"react\";\n\nimport type {\n Action,\n DependencyEdge,\n ResourceDescriptor,\n ResourceRegistry,\n} from \"./types.js\";\n\n/**\n * Thrown by `defineAuthRbac` when the registry contains a cycle in\n * its `dependsOn` graph. The cycle path is included in the message,\n * named in registry order.\n */\nexport class RbacRegistryError extends Error {\n constructor(message: string) {\n super(`auth-rbac: ${message}`);\n this.name = \"RbacRegistryError\";\n }\n}\n\n/**\n * Walks the `dependsOn` graph with three-colour DFS. Throws on\n * **dangling** edges (an edge points at a resource that isn't in\n * the registry — almost always a typo). Cycles are *not* fatal:\n * the matrix UI's cascade is cycle-safe by construction (it only\n * flips cells on and skips already-on cells), and real-world\n * dependency graphs in property-management / multi-entity SaaS are\n * naturally cyclic — properties and units mutually need each\n * other's read to render either detail view. We emit a single\n * console warning per registry build so unintended cycles are\n * still visible during development, without breaking adopters whose\n * graphs are cyclic by design.\n *\n * Pre-0.4.1: cycles threw RbacRegistryError. The throw was wrong —\n * the cascade has always been safe under cycles. The change in\n * 0.4.1 keeps the diagnostic but downgrades it to a warning.\n */\nfunction detectDependencyCycles(registry: ResourceRegistry): void {\n const WHITE = 0;\n const GREY = 1;\n const BLACK = 2;\n const colour = new Map<string, number>();\n const byName = new Map<string, ResourceDescriptor>();\n for (const r of registry) {\n byName.set(r.resource, r);\n colour.set(r.resource, WHITE);\n }\n const stack: string[] = [];\n const cyclesFound = new Set<string>();\n\n const edgeTarget = (edge: string | DependencyEdge): string =>\n typeof edge === \"string\" ? edge : edge.resource;\n\n function visit(name: string): void {\n const state = colour.get(name);\n if (state === BLACK) {\n return;\n }\n if (state === GREY) {\n // Cycle. Record the path for the warning but keep walking so\n // we don't mask further dangling-edge errors deeper in the\n // graph.\n const cycleStart = stack.indexOf(name);\n const cycle = [...stack.slice(cycleStart), name].join(\" → \");\n cyclesFound.add(cycle);\n return;\n }\n if (state === undefined) {\n // Dangling edge — referenced resource not in registry. Treat\n // as a registration error so adopters fix the typo at boot.\n throw new RbacRegistryError(\n `dependsOn target '${name}' is not a registered resource ` +\n `(referenced by '${stack[stack.length - 1] ?? \"<root>\"}')`,\n );\n }\n colour.set(name, GREY);\n stack.push(name);\n const descriptor = byName.get(name);\n const edges = descriptor?.dependsOn ?? [];\n for (const edge of edges) {\n visit(edgeTarget(edge));\n }\n stack.pop();\n colour.set(name, BLACK);\n }\n\n for (const r of registry) {\n visit(r.resource);\n }\n\n if (cyclesFound.size > 0) {\n // eslint-disable-next-line no-console\n console.warn(\n `[auth-rbac] dependency cycle(s) detected in resource registry. ` +\n `Cycles are runtime-safe — the matrix UI's cascade only flips ` +\n `cells on and skips already-on cells — but they may indicate ` +\n `unintended edges. Found: ${Array.from(cyclesFound).join(\"; \")}`,\n );\n }\n}\n\n/**\n * Drop-in replacement signatures for the three guards, with a\n * narrowed `resource` arg.\n */\nexport interface TypedGuards<R extends string> {\n useCan: (\n resource: R,\n action: Action,\n options?: { companyId?: string | null },\n ) => boolean;\n /**\n * `useCan` for sidebar / list-page access: returns true only when\n * the user holds the action on the resource as a **direct** grant\n * (no `<action>_granted_via`). Implied rows answer false here.\n *\n * `action` defaults to `'read'` because the canonical use is\n * top-level-nav gating. Available since 0.4.0.\n */\n useCanAccessSection: (\n resource: R,\n action?: Action,\n options?: { companyId?: string | null },\n ) => boolean;\n Can: ComponentType<{\n resource: R;\n action: Action;\n companyId?: string | null;\n children: ReactNode;\n fallback?: ReactNode;\n }>;\n RequirePermission: ComponentType<{\n resource: R;\n action: Action;\n companyId?: string | null;\n loadingFallback?: ReactNode;\n deniedFallback?: ReactNode;\n children?: ReactNode;\n }>;\n /** The const-asserted registry, re-exported so call-sites can iterate. */\n resources: ResourceRegistry;\n /** All registered resource names as a union — handy for typing\n * application-side data structures. */\n resourceNames: ReadonlyArray<R>;\n}\n\n/**\n * Factory. Run once at module-init time in your host project; the\n * returned hooks/components are referentially stable.\n */\nexport function defineAuthRbac<\n const Reg extends ReadonlyArray<ResourceDescriptor>,\n>(\n resources: Reg,\n // The runtime guards live in the React entry; we accept them\n // here as plain refs so this module stays React-free at the type\n // level. The `react` entry calls this factory passing its own\n // exports so adopters never see the wiring.\n runtime: {\n useCan: TypedGuards<string>[\"useCan\"];\n useCanAccessSection: TypedGuards<string>[\"useCanAccessSection\"];\n Can: TypedGuards<string>[\"Can\"];\n RequirePermission: TypedGuards<string>[\"RequirePermission\"];\n },\n): TypedGuards<Reg[number][\"resource\"]> {\n // Cycle detection runs once at module-init time so misconfigured\n // registries fail at app boot, not on the first matrix toggle.\n detectDependencyCycles(resources);\n\n type R = Reg[number][\"resource\"];\n return {\n useCan: runtime.useCan as TypedGuards<R>[\"useCan\"],\n useCanAccessSection: runtime.useCanAccessSection as TypedGuards<R>[\"useCanAccessSection\"],\n Can: runtime.Can as TypedGuards<R>[\"Can\"],\n RequirePermission: runtime.RequirePermission as TypedGuards<R>[\"RequirePermission\"],\n resources,\n resourceNames: resources.map((r) => r.resource) as ReadonlyArray<R>,\n };\n}\n","/**\n * Built-in fetchers — adopters can use these or pass their own\n * implementation of `AuthRbacFetcher`.\n */\n\nimport type { AuthRbacFetcher, UserProfile } from \"./types.js\";\n\n/**\n * Calls the package's SQL function `rbac.user_profile(uuid)` via\n * a Supabase JS client. Easiest path when the host project already\n * uses Supabase.\n *\n * The function lives in the dedicated `rbac` Postgres schema, so the\n * adopter must add `rbac` to their PostgREST exposed-schemas list\n * (Supabase Studio → Settings → API → Exposed schemas) for the\n * `.schema('rbac')` call below to reach it.\n *\n * @example\n * createSupabaseFetcher({ supabase, userId: session.user.id })\n */\nexport function createSupabaseFetcher(opts: {\n supabase: {\n schema: (name: string) => {\n rpc: (\n fn: string,\n args: Record<string, unknown>,\n ) => Promise<{ data: unknown; error: { message: string } | null }>;\n };\n };\n userId: string;\n}): AuthRbacFetcher {\n return {\n async fetchProfile(): Promise<UserProfile> {\n const { data, error } = await opts.supabase.schema(\"rbac\").rpc(\n \"user_profile\",\n { p_user_id: opts.userId },\n );\n if (error) {\n throw new Error(\n `auth-rbac: failed to load user profile via Supabase RPC: ${error.message}`,\n );\n }\n return normalizeProfile(data);\n },\n };\n}\n\n/**\n * Cheap probe — returns true if the package's `rbac` schema looks\n * reachable. Useful at app start to fail loudly if the migration\n * hasn't been applied OR if `rbac` isn't in the project's PostgREST\n * exposed-schemas list.\n *\n * @example\n * if (!(await detectRbacSchema(supabase))) {\n * console.error(\"rbac schema not reachable — apply 0001_initial.sql\");\n * }\n */\nexport async function detectRbacSchema(supabase: {\n schema: (name: string) => {\n rpc: (\n fn: string,\n args: Record<string, unknown>,\n ) => Promise<{ data: unknown; error: { message: string } | null }>;\n };\n}): Promise<boolean> {\n try {\n const { error } = await supabase.schema(\"rbac\").rpc(\"user_can\", {\n p_user_id: \"00000000-0000-0000-0000-000000000000\",\n p_resource: \"__rbac_self_check__\",\n p_action: \"read\",\n p_company_id: null,\n });\n return error === null;\n } catch {\n return false;\n }\n}\n\n/**\n * Calls a regular HTTP endpoint that returns a `UserProfile` JSON\n * payload. Use this when the host project has its own backend that\n * wraps the package's Python helpers (or any equivalent).\n *\n * @example\n * createHttpFetcher({ url: \"/api/users/me/profile\" })\n */\nexport function createHttpFetcher(opts: {\n url: string;\n /** Forwarded as-is to `fetch`. Use this to attach auth headers. */\n init?: RequestInit;\n /** Override the global `fetch` if you're in a non-browser env. */\n fetch?: typeof fetch;\n}): AuthRbacFetcher {\n const fetchImpl = opts.fetch ?? globalThis.fetch;\n return {\n async fetchProfile(): Promise<UserProfile> {\n const res = await fetchImpl(opts.url, opts.init);\n if (!res.ok) {\n throw new Error(\n `auth-rbac: profile endpoint ${opts.url} returned ${res.status}`,\n );\n }\n const json = (await res.json()) as unknown;\n return normalizeProfile(json);\n },\n };\n}\n\n/**\n * Defensive normalisation: the Supabase RPC returns whatever the SQL\n * function emitted. We coerce missing fields into the empty defaults\n * so consumers can iterate without null checks. Throws if the shape\n * is unrecognisable.\n */\nfunction normalizeProfile(raw: unknown): UserProfile {\n if (!raw || typeof raw !== \"object\") {\n throw new Error(\"auth-rbac: profile payload is not an object\");\n }\n const p = raw as Partial<UserProfile> & Record<string, unknown>;\n if (typeof p.user_id !== \"string\") {\n throw new Error(\"auth-rbac: profile payload missing user_id\");\n }\n return {\n user_id: p.user_id,\n is_super_admin: !!p.is_super_admin,\n system_roles: Array.isArray(p.system_roles) ? p.system_roles : [],\n system_permissions:\n p.system_permissions && typeof p.system_permissions === \"object\"\n ? (p.system_permissions as UserProfile[\"system_permissions\"])\n : {},\n system_frontend_config:\n p.system_frontend_config && typeof p.system_frontend_config === \"object\"\n ? (p.system_frontend_config as UserProfile[\"system_frontend_config\"])\n : {},\n memberships: Array.isArray(p.memberships)\n ? (p.memberships as UserProfile[\"memberships\"])\n : [],\n };\n}\n"],"mappings":"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","names":["defineAuthRbac","import_jsx_runtime","import_jsx_runtime","import_react","import_react","defineAuthRbac"]}
1
+ {"version":3,"sources":["../../src/react/index.ts","../../src/react/AuthRbacProvider.tsx","../../src/client.ts","../../src/react/useCan.ts","../../src/react/useCanAccessSection.ts","../../src/react/Can.tsx","../../src/react/RequirePermission.tsx","../../src/react/useActiveCompany.ts","../../src/react/useFrontendConfig.ts","../../src/define.ts","../../src/fetchers.ts"],"sourcesContent":["/**\n * React + Next.js entry. Import this in browser code:\n *\n * import { AuthRbacProvider, useCan } from \"snipe-auth-rbac/react\";\n *\n * The non-React entry (`snipe-auth-rbac`) re-exports types and the\n * pure resolver, suitable for Node, edge workers, and tests.\n */\n\nexport {\n AuthRbacProvider,\n useAuthRbac,\n type AuthRbacProviderProps,\n} from \"./AuthRbacProvider.js\";\n\nexport { useCan } from \"./useCan.js\";\nexport { useCanAccessSection } from \"./useCanAccessSection.js\";\nexport { Can, type CanProps } from \"./Can.js\";\nexport {\n RequirePermission,\n type RequirePermissionProps,\n} from \"./RequirePermission.js\";\nexport { useActiveCompany, type ActiveCompany } from \"./useActiveCompany.js\";\nexport { useFrontendConfig } from \"./useFrontendConfig.js\";\n\n// Re-exports for convenience so consumers don't need two imports.\nexport type {\n Action,\n AuthRbacFetcher,\n CompanyMembership,\n DependencyEdge,\n DirectGrantMap,\n FrontendConfig,\n PermissionGrid,\n PermissionMap,\n ResourceDescriptor,\n ResourceRegistry,\n ResourceScope,\n RoleSummary,\n UserProfile,\n} from \"../types.js\";\n\nexport { RbacRegistryError } from \"../define.js\";\n\nexport {\n createSupabaseFetcher,\n createHttpFetcher,\n detectRbacSchema,\n} from \"../fetchers.js\";\n\nimport { defineAuthRbac as _defineAuthRbac } from \"../define.js\";\nimport { Can } from \"./Can.js\";\nimport { RequirePermission } from \"./RequirePermission.js\";\nimport { useCan } from \"./useCan.js\";\nimport { useCanAccessSection } from \"./useCanAccessSection.js\";\n\nimport type { ResourceDescriptor } from \"../types.js\";\nimport type { TypedGuards } from \"../define.js\";\n\n/**\n * Typed factory — pass a const-asserted resource registry and get\n * back guards whose `resource` arg is constrained to the registered\n * names. Recommended at the top of every host project.\n *\n * See ../define.ts for the full doc + example.\n */\nexport function defineAuthRbac<\n const Reg extends ReadonlyArray<ResourceDescriptor>,\n>(resources: Reg): TypedGuards<Reg[number][\"resource\"]> {\n return _defineAuthRbac(resources, {\n useCan,\n useCanAccessSection,\n Can,\n RequirePermission,\n });\n}\n\nexport type { TypedGuards } from \"../define.js\";\n","import {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n type ReactNode,\n} from \"react\";\n\nimport {\n buildPermissionResolver,\n type AuthRbacClient,\n} from \"../client.js\";\nimport type {\n AuthRbacFetcher,\n PermissionMap,\n ResourceRegistry,\n UserProfile,\n} from \"../types.js\";\n\ninterface AuthRbacContextValue {\n /**\n * `null` means we haven't hydrated yet. Components should treat\n * the not-loaded state as \"no permissions\" (fail-closed).\n */\n profile: UserProfile | null;\n loading: boolean;\n error: Error | null;\n resources: ResourceRegistry;\n activeCompanyId: string | null;\n setActiveCompany: (id: string | null) => void;\n /**\n * Re-fetches the profile.\n *\n * By default the call toggles `loading: true` while the request is in\n * flight — matches `useState` ergonomics for adopters wiring a manual\n * \"Refresh roles\" button. Pass `{ silent: true }` to keep `loading`\n * false; the existing profile stays visible until the new one lands.\n * Adopters who re-create their `fetcher` reference on auth-token\n * rotation should not need this flag at all — the provider already\n * runs internal fetcher-change refreshes silently (see CHANGELOG\n * 0.6.5). It's exposed for cases like background poll loops or\n * post-mutation reconciliation where the consumer wants to avoid\n * flickering a loading state through their own UI.\n */\n refresh: (opts?: { silent?: boolean }) => Promise<void>;\n resolver: AuthRbacClient;\n}\n\nconst AuthRbacContext = createContext<AuthRbacContextValue | null>(null);\n\nexport interface AuthRbacProviderProps {\n fetcher: AuthRbacFetcher;\n resources: ResourceRegistry;\n /**\n * Initial active company. Common patterns:\n * - read from URL query/path\n * - read from localStorage\n * - omit and let the user pick from the switcher\n */\n initialCompanyId?: string | null;\n /**\n * Persistence hook. Called every time the active company changes.\n * Default: writes to `localStorage` under\n * `auth-rbac:active-company`. Pass `false` to disable.\n */\n persistActiveCompany?:\n | ((id: string | null) => void)\n | false;\n children: ReactNode;\n}\n\nconst STORAGE_KEY = \"auth-rbac:active-company\";\n\nconst defaultPersist = (id: string | null) => {\n if (typeof window === \"undefined\") {\n return;\n }\n try {\n if (id == null) {\n window.localStorage.removeItem(STORAGE_KEY);\n } else {\n window.localStorage.setItem(STORAGE_KEY, id);\n }\n } catch {\n // localStorage may be unavailable (private browsing, SSR) —\n // fall back to in-memory only.\n }\n};\n\nconst readPersisted = (): string | null => {\n if (typeof window === \"undefined\") {\n return null;\n }\n try {\n return window.localStorage.getItem(STORAGE_KEY);\n } catch {\n return null;\n }\n};\n\nexport function AuthRbacProvider(props: AuthRbacProviderProps) {\n const { fetcher, resources, initialCompanyId, persistActiveCompany } = props;\n\n const [profile, setProfile] = useState<UserProfile | null>(null);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n\n const [activeCompanyId, setActiveCompanyState] = useState<string | null>(\n initialCompanyId ?? readPersisted(),\n );\n\n const persist = useMemo(() => {\n if (persistActiveCompany === false) {\n return () => {};\n }\n return persistActiveCompany ?? defaultPersist;\n }, [persistActiveCompany]);\n\n const setActiveCompany = useCallback(\n (id: string | null) => {\n setActiveCompanyState(id);\n persist(id);\n },\n [persist],\n );\n\n // Hold the inputs `refresh` reads in refs so the callback identity\n // stays stable across renders. Previously `refresh` was rebuilt\n // whenever `fetcher` changed, which in turn churned the context\n // value memo and triggered downstream re-renders / re-mounts in\n // adopter route gates and dialogs. With refs, the callback has\n // empty deps and a permanent identity for the provider's lifetime.\n const fetcherRef = useRef(fetcher);\n const activeCompanyIdRef = useRef(activeCompanyId);\n const persistRef = useRef(persist);\n fetcherRef.current = fetcher;\n activeCompanyIdRef.current = activeCompanyId;\n persistRef.current = persist;\n\n const refresh = useCallback(async (opts?: { silent?: boolean }) => {\n const silent = opts?.silent === true;\n if (!silent) {\n setLoading(true);\n }\n setError(null);\n try {\n const next = await fetcherRef.current.fetchProfile();\n setProfile(next);\n // If the persisted company isn't a membership, fall back to\n // the first one (or null for users with no memberships).\n const currentCompanyId = activeCompanyIdRef.current;\n const stillMember =\n currentCompanyId != null &&\n next.memberships.some((m) => m.company_id === currentCompanyId);\n if (!stillMember) {\n const fallback =\n next.memberships[0]?.company_id ?? null;\n setActiveCompanyState(fallback);\n persistRef.current(fallback);\n }\n } catch (e) {\n setError(e instanceof Error ? e : new Error(String(e)));\n } finally {\n if (!silent) {\n setLoading(false);\n }\n }\n }, []);\n\n // Track the first run so we can flip `loading: true` exactly once on\n // mount. Subsequent fires (e.g. the adopter swapped in a new fetcher\n // reference after auth-token rotation) happen silently — the old\n // profile keeps serving permissions until the new one lands, which\n // prevents route gates from flickering through their loading branch\n // and tearing down portaled dialogs / in-progress forms.\n const hasFetchedOnceRef = useRef(false);\n useEffect(() => {\n void refresh({ silent: hasFetchedOnceRef.current });\n hasFetchedOnceRef.current = true;\n }, [fetcher, refresh]);\n\n const resolver = useMemo<AuthRbacClient>(() => {\n if (profile == null) {\n // Empty resolver until the profile lands. Always returns\n // false → guards fall through to the unauthenticated branch.\n return {\n can: () => false,\n canAccessSection: () => false,\n activePermissions: () => ({}) as PermissionMap,\n systemPermissions: () => ({}) as PermissionMap,\n };\n }\n return buildPermissionResolver(resources, profile, activeCompanyId);\n }, [profile, resources, activeCompanyId]);\n\n const value = useMemo<AuthRbacContextValue>(\n () => ({\n profile,\n loading,\n error,\n resources,\n activeCompanyId,\n setActiveCompany,\n refresh,\n resolver,\n }),\n [\n profile,\n loading,\n error,\n resources,\n activeCompanyId,\n setActiveCompany,\n refresh,\n resolver,\n ],\n );\n\n return (\n <AuthRbacContext.Provider value={value}>\n {props.children}\n </AuthRbacContext.Provider>\n );\n}\n\nexport function useAuthRbac(): AuthRbacContextValue {\n const ctx = useContext(AuthRbacContext);\n if (!ctx) {\n throw new Error(\n \"useAuthRbac must be used within an <AuthRbacProvider> — wrap your app at the root.\",\n );\n }\n return ctx;\n}\n","/**\n * Transport-agnostic client: turns an adopter-supplied\n * `AuthRbacFetcher` into a permission resolver. The React provider\n * wraps this; non-React consumers (Node scripts, edge functions)\n * can use it directly.\n */\n\nimport type {\n Action,\n AuthRbacFetcher,\n PermissionMap,\n ResourceDescriptor,\n ResourceRegistry,\n ResourceScope,\n UserProfile,\n} from \"./types.js\";\n\nexport interface AuthRbacClientOptions {\n fetcher: AuthRbacFetcher;\n /**\n * The host project's full resource list. Required so the resolver\n * can look up a resource's scope without a DB round-trip per call.\n * Re-using the same array the host syncs into the\n * `rbac.resources` table at boot keeps everything in lockstep.\n */\n resources: ResourceRegistry;\n}\n\nexport interface CanOptions {\n /**\n * Override the active company. Omit to use the company the\n * caller has currently activated (the React provider tracks\n * this; for direct client use you must pass it).\n */\n companyId?: string | null;\n}\n\n/**\n * Pure resolver. Given a hydrated profile it answers boolean\n * questions instantly — no I/O. The `resourceMap` is built once at\n * construction so per-call work is two map lookups.\n */\nexport function buildPermissionResolver(\n resources: ResourceRegistry,\n profile: UserProfile,\n defaultCompanyId: string | null,\n) {\n const scopeByResource = new Map<string, ResourceScope>(\n resources.map((r) => [r.resource, r.scope]),\n );\n\n const can = (\n resource: string,\n action: Action,\n options?: CanOptions,\n ): boolean => {\n if (profile.is_super_admin) {\n return true;\n }\n const scope = scopeByResource.get(resource);\n if (!scope) {\n // Unknown resource — fail closed.\n return false;\n }\n if (scope === \"system\") {\n return readGrid(profile.system_permissions, resource, action);\n }\n const companyId = options?.companyId ?? defaultCompanyId;\n if (!companyId) {\n return false;\n }\n const membership = profile.memberships.find(\n (m) => m.company_id === companyId,\n );\n if (!membership) {\n return false;\n }\n return readGrid(membership.permissions, resource, action);\n };\n\n /**\n * Direct-grant lookup: returns true only if the user has the\n * action granted on the resource as a direct admin grant —\n * `<action>_granted_via IS NULL` in `rbac.role_permissions`.\n * Implied rows (granted as a side-effect of a parent resource's\n * `dependsOn` cascade) return false here.\n *\n * Use for top-level navigation / list-page gating: a Verwalter\n * with only `leases:read` direct gets the Leases sidebar item but\n * not Tenants / Units / Properties, even though `can(...)` returns\n * true for those (because the implied rows let the lease detail\n * page render its joined data).\n *\n * Available since 0.4.0. For older SQL that doesn't return\n * `direct_*` maps, every cell answers false — equivalent to\n * \"no direct grants known\". Adopters running pre-0.4.0 SQL should\n * keep using `can(...)`.\n */\n const canAccessSection = (\n resource: string,\n action: Action = \"read\",\n options?: CanOptions,\n ): boolean => {\n if (profile.is_super_admin) {\n return true;\n }\n const scope = scopeByResource.get(resource);\n if (!scope) {\n return false;\n }\n if (scope === \"system\") {\n return readDirect(profile, action, resource);\n }\n const companyId = options?.companyId ?? defaultCompanyId;\n if (!companyId) {\n return false;\n }\n const membership = profile.memberships.find(\n (m) => m.company_id === companyId,\n );\n if (!membership) {\n return false;\n }\n return readDirectMembership(membership, action, resource);\n };\n\n return {\n can,\n canAccessSection,\n /** Permission map for the active (or specified) company. */\n activePermissions: (companyId?: string | null): PermissionMap => {\n const id = companyId ?? defaultCompanyId;\n if (!id) {\n return {};\n }\n return (\n profile.memberships.find((m) => m.company_id === id)?.permissions ?? {}\n );\n },\n systemPermissions: (): PermissionMap => profile.system_permissions,\n };\n}\n\nfunction readDirect(\n profile: UserProfile,\n action: Action,\n resource: string,\n): boolean {\n const map =\n action === \"read\"\n ? profile.system_direct_reads\n : action === \"write\"\n ? profile.system_direct_writes\n : action === \"update\"\n ? profile.system_direct_updates\n : profile.system_direct_deletes;\n return map?.[resource] === true;\n}\n\nfunction readDirectMembership(\n membership: { direct_reads?: Readonly<Record<string, boolean>>;\n direct_writes?: Readonly<Record<string, boolean>>;\n direct_updates?: Readonly<Record<string, boolean>>;\n direct_deletes?: Readonly<Record<string, boolean>>; },\n action: Action,\n resource: string,\n): boolean {\n const map =\n action === \"read\"\n ? membership.direct_reads\n : action === \"write\"\n ? membership.direct_writes\n : action === \"update\"\n ? membership.direct_updates\n : membership.direct_deletes;\n return map?.[resource] === true;\n}\n\nfunction readGrid(\n map: PermissionMap,\n resource: string,\n action: Action,\n): boolean {\n const grid = map[resource];\n if (!grid) {\n return false;\n }\n return grid[action];\n}\n\n/**\n * Helper: groups a resource registry by `group` for the matrix UI.\n * Returns groups in insertion order with their resources.\n */\nexport function groupResources(\n registry: ResourceRegistry,\n): Array<{ group: string; resources: ResourceDescriptor[] }> {\n const order: string[] = [];\n const buckets = new Map<string, ResourceDescriptor[]>();\n for (const r of registry) {\n const key = r.group ?? \"Sonstige\";\n if (!buckets.has(key)) {\n buckets.set(key, []);\n order.push(key);\n }\n buckets.get(key)!.push(r);\n }\n return order.map((g) => ({ group: g, resources: buckets.get(g)! }));\n}\n\nexport type AuthRbacClient = ReturnType<typeof buildPermissionResolver>;\nexport type { AuthRbacClientOptions as ClientOptions };\n","import type { Action } from \"../types.js\";\nimport type { CanOptions } from \"../client.js\";\n\nimport { useAuthRbac } from \"./AuthRbacProvider.js\";\n\n/**\n * Boolean permission check.\n *\n * @example\n * const canEdit = useCan(\"properties\", \"update\");\n * <Button disabled={!canEdit}>Speichern</Button>\n *\n * @example explicitly target a non-active company\n * const canRead = useCan(\"payments\", \"read\", { companyId: targetId });\n */\nexport function useCan(\n resource: string,\n action: Action,\n options?: CanOptions,\n): boolean {\n const { resolver } = useAuthRbac();\n return resolver.can(resource, action, options);\n}\n","import type { Action } from \"../types.js\";\nimport type { CanOptions } from \"../client.js\";\n\nimport { useAuthRbac } from \"./AuthRbacProvider.js\";\n\n/**\n * Direct-grant check, for sidebar items and list-page route guards.\n *\n * Returns `true` only when the action is granted on the resource as\n * a **direct** admin grant (no `<action>_granted_via`). Rows that\n * exist only because a parent resource's `dependsOn` cascade\n * materialised them return `false` here — even though\n * `useCan(resource, action)` would return `true` for the same role.\n *\n * Use case: a Verwalter with only `leases:read` direct should see\n * Mietverträge in the sidebar but **not** Mieter / Einheiten /\n * Liegenschaften — those reads are implied so the lease detail page\n * can render, not because the role should navigate to them as\n * top-level sections.\n *\n * @example sidebar item filtering\n * const showLeasesInSidebar = useCanAccessSection(\"leases\");\n * const showUnitsInSidebar = useCanAccessSection(\"units\");\n *\n * @example list-route gating\n * if (!useCanAccessSection(\"units\")) return <Forbidden />;\n *\n * Available since 0.4.0. With older SQL that doesn't return\n * `direct_*` maps, this always answers `false` — adopters still on\n * pre-0.4.0 SQL should keep using `useCan`.\n */\nexport function useCanAccessSection(\n resource: string,\n action: Action = \"read\",\n options?: CanOptions,\n): boolean {\n const { resolver } = useAuthRbac();\n return resolver.canAccessSection(resource, action, options);\n}\n","import type { ReactNode } from \"react\";\n\nimport type { Action } from \"../types.js\";\nimport type { CanOptions } from \"../client.js\";\n\nimport { useCan } from \"./useCan.js\";\n\nexport interface CanProps extends CanOptions {\n resource: string;\n action: Action;\n /** Rendered when the user has the permission. */\n children: ReactNode;\n /**\n * Rendered when the user does NOT have the permission. Defaults\n * to `null` (silent hide). Pass a `<NoPermissionView />` or a\n * tooltip-wrapper to surface the denial explicitly.\n */\n fallback?: ReactNode;\n}\n\n/**\n * Subtree gate. Bails before children render so any data fetching\n * inside `children` is skipped for users without permission.\n */\nexport function Can(props: CanProps) {\n const { resource, action, companyId, children, fallback = null } = props;\n const allowed = useCan(resource, action, { companyId });\n return <>{allowed ? children : fallback}</>;\n}\n","import type { ReactNode } from \"react\";\n\nimport type { Action } from \"../types.js\";\nimport type { CanOptions } from \"../client.js\";\n\nimport { useAuthRbac } from \"./AuthRbacProvider.js\";\nimport { useCan } from \"./useCan.js\";\n\nexport interface RequirePermissionProps extends CanOptions {\n resource: string;\n action: Action;\n /**\n * What to render while the profile is still loading. Defaults to\n * `null` (no flash) — pass a spinner if your routes typically\n * mount before the profile lands.\n */\n loadingFallback?: ReactNode;\n /**\n * What to render when access is denied. Defaults to a minimal\n * \"Sie haben keinen Zugriff\" message; pass your own component to\n * theme it.\n */\n deniedFallback?: ReactNode;\n /**\n * For `react-router-dom v6` route-element usage, pass an `<Outlet />`\n * here — the gate resolves to either the outlet or the denied\n * fallback. For component-tree usage, pass any children.\n */\n children?: ReactNode;\n}\n\n/**\n * Route- or component-level guard. Three render branches:\n *\n * - profile not yet loaded → `loadingFallback`\n * - permission denied → `deniedFallback`\n * - permission granted → `children`\n *\n * Drop-in replacement for the legacy `<RequireRolesRoute>` pattern.\n *\n * @example\n * // App.tsx route table\n * <Route element={\n * <RequirePermission resource=\"payments\" action=\"read\">\n * <Outlet />\n * </RequirePermission>\n * }>\n * <Route path=\"/payments\" element={<PaymentsPage />} />\n * </Route>\n */\nexport function RequirePermission(props: RequirePermissionProps) {\n const {\n resource,\n action,\n companyId,\n loadingFallback = null,\n deniedFallback = (\n <div role=\"alert\" style={{ padding: 24 }}>\n <strong>Sie haben keinen Zugriff.</strong>\n </div>\n ),\n children = null,\n } = props;\n\n const { profile, loading } = useAuthRbac();\n const allowed = useCan(resource, action, { companyId });\n\n if (loading || profile == null) {\n return <>{loadingFallback}</>;\n }\n if (!allowed) {\n return <>{deniedFallback}</>;\n }\n return <>{children}</>;\n}\n","import { useMemo } from \"react\";\n\nimport type { CompanyMembership } from \"../types.js\";\n\nimport { useAuthRbac } from \"./AuthRbacProvider.js\";\n\nexport interface ActiveCompany {\n id: string | null;\n membership: CompanyMembership | null;\n memberships: CompanyMembership[];\n setActive: (id: string | null) => void;\n}\n\n/**\n * Read + switch the active company.\n *\n * @example\n * const { id, memberships, setActive } = useActiveCompany();\n *\n * return (\n * <select value={id ?? \"\"} onChange={(e) => setActive(e.target.value || null)}>\n * {memberships.map((m) => (\n * <option key={m.company_id} value={m.company_id}>{m.company_name}</option>\n * ))}\n * </select>\n * );\n */\nexport function useActiveCompany(): ActiveCompany {\n const { profile, activeCompanyId, setActiveCompany } = useAuthRbac();\n\n return useMemo(() => {\n const memberships = profile?.memberships ?? [];\n const membership =\n memberships.find((m) => m.company_id === activeCompanyId) ?? null;\n return {\n id: activeCompanyId,\n membership,\n memberships,\n setActive: setActiveCompany,\n };\n }, [profile, activeCompanyId, setActiveCompany]);\n}\n","import { useMemo } from \"react\";\n\nimport type { FrontendConfig } from \"../types.js\";\n\nimport { useAuthRbac } from \"./AuthRbacProvider.js\";\n\n/**\n * Reads the merged `frontend_config` for the user. Sources in\n * priority order: active company's membership > system roles. Use\n * this to drive sidebar items, dashboard defaults, and any other\n * \"what should this role see\" UX without hardcoded role checks.\n *\n * The shape is intentionally `Record<string, unknown>` — your host\n * project owns the schema. Document your keys (e.g. `sidebar`,\n * `default_dashboard`) once and stick to them.\n */\nexport function useFrontendConfig(): FrontendConfig {\n const { profile, activeCompanyId } = useAuthRbac();\n return useMemo(() => {\n if (!profile) {\n return {};\n }\n const membershipConfig =\n profile.memberships.find((m) => m.company_id === activeCompanyId)\n ?.frontend_config ?? {};\n return { ...profile.system_frontend_config, ...membershipConfig };\n }, [profile, activeCompanyId]);\n}\n","/**\n * Typed factory — turns a const-asserted resource registry into a\n * set of hooks/components whose `resource` arg is constrained to\n * the registered names. Typos become TypeScript errors instead of\n * silent runtime `false`.\n *\n * @example\n * // src/auth/resources.ts\n * import { defineAuthRbac } from \"snipe-auth-rbac/react\";\n *\n * export const RESOURCES = [\n * { resource: \"properties\", scope: \"company\", label: \"Liegenschaften\", group: \"Stammdaten\" },\n * { resource: \"payments\", scope: \"company\", label: \"Zahlungen\", group: \"Finanzen\" },\n * { resource: \"system_audit\", scope: \"system\", label: \"Audit-Log\", group: \"Plattform\" },\n * ] as const;\n *\n * export const { useCan, Can, RequirePermission } = defineAuthRbac(RESOURCES);\n *\n * // ----- elsewhere -----\n * useCan(\"properties\", \"update\"); // ✓\n * useCan(\"paymetns\", \"update\"); // ✗ TS error: not assignable to type \"properties\" | \"payments\" | \"system_audit\"\n */\n\nimport type { ComponentType, ReactNode } from \"react\";\n\nimport type {\n Action,\n DependencyEdge,\n ResourceDescriptor,\n ResourceRegistry,\n} from \"./types.js\";\n\n/**\n * Thrown by `defineAuthRbac` when the registry contains a cycle in\n * its `dependsOn` graph. The cycle path is included in the message,\n * named in registry order.\n */\nexport class RbacRegistryError extends Error {\n constructor(message: string) {\n super(`auth-rbac: ${message}`);\n this.name = \"RbacRegistryError\";\n }\n}\n\n/**\n * Walks the `dependsOn` graph with three-colour DFS. Throws on\n * **dangling** edges (an edge points at a resource that isn't in\n * the registry — almost always a typo). Cycles are *not* fatal:\n * the matrix UI's cascade is cycle-safe by construction (it only\n * flips cells on and skips already-on cells), and real-world\n * dependency graphs in property-management / multi-entity SaaS are\n * naturally cyclic — properties and units mutually need each\n * other's read to render either detail view. We emit a single\n * console warning per registry build so unintended cycles are\n * still visible during development, without breaking adopters whose\n * graphs are cyclic by design.\n *\n * Pre-0.4.1: cycles threw RbacRegistryError. The throw was wrong —\n * the cascade has always been safe under cycles. The change in\n * 0.4.1 keeps the diagnostic but downgrades it to a warning.\n */\nfunction detectDependencyCycles(registry: ResourceRegistry): void {\n const WHITE = 0;\n const GREY = 1;\n const BLACK = 2;\n const colour = new Map<string, number>();\n const byName = new Map<string, ResourceDescriptor>();\n for (const r of registry) {\n byName.set(r.resource, r);\n colour.set(r.resource, WHITE);\n }\n const stack: string[] = [];\n const cyclesFound = new Set<string>();\n\n const edgeTarget = (edge: string | DependencyEdge): string =>\n typeof edge === \"string\" ? edge : edge.resource;\n\n function visit(name: string): void {\n const state = colour.get(name);\n if (state === BLACK) {\n return;\n }\n if (state === GREY) {\n // Cycle. Record the path for the warning but keep walking so\n // we don't mask further dangling-edge errors deeper in the\n // graph.\n const cycleStart = stack.indexOf(name);\n const cycle = [...stack.slice(cycleStart), name].join(\" → \");\n cyclesFound.add(cycle);\n return;\n }\n if (state === undefined) {\n // Dangling edge — referenced resource not in registry. Treat\n // as a registration error so adopters fix the typo at boot.\n throw new RbacRegistryError(\n `dependsOn target '${name}' is not a registered resource ` +\n `(referenced by '${stack[stack.length - 1] ?? \"<root>\"}')`,\n );\n }\n colour.set(name, GREY);\n stack.push(name);\n const descriptor = byName.get(name);\n const edges = descriptor?.dependsOn ?? [];\n for (const edge of edges) {\n visit(edgeTarget(edge));\n }\n stack.pop();\n colour.set(name, BLACK);\n }\n\n for (const r of registry) {\n visit(r.resource);\n }\n\n if (cyclesFound.size > 0) {\n // eslint-disable-next-line no-console\n console.warn(\n `[auth-rbac] dependency cycle(s) detected in resource registry. ` +\n `Cycles are runtime-safe — the matrix UI's cascade only flips ` +\n `cells on and skips already-on cells — but they may indicate ` +\n `unintended edges. Found: ${Array.from(cyclesFound).join(\"; \")}`,\n );\n }\n}\n\n/**\n * Drop-in replacement signatures for the three guards, with a\n * narrowed `resource` arg.\n */\nexport interface TypedGuards<R extends string> {\n useCan: (\n resource: R,\n action: Action,\n options?: { companyId?: string | null },\n ) => boolean;\n /**\n * `useCan` for sidebar / list-page access: returns true only when\n * the user holds the action on the resource as a **direct** grant\n * (no `<action>_granted_via`). Implied rows answer false here.\n *\n * `action` defaults to `'read'` because the canonical use is\n * top-level-nav gating. Available since 0.4.0.\n */\n useCanAccessSection: (\n resource: R,\n action?: Action,\n options?: { companyId?: string | null },\n ) => boolean;\n Can: ComponentType<{\n resource: R;\n action: Action;\n companyId?: string | null;\n children: ReactNode;\n fallback?: ReactNode;\n }>;\n RequirePermission: ComponentType<{\n resource: R;\n action: Action;\n companyId?: string | null;\n loadingFallback?: ReactNode;\n deniedFallback?: ReactNode;\n children?: ReactNode;\n }>;\n /** The const-asserted registry, re-exported so call-sites can iterate. */\n resources: ResourceRegistry;\n /** All registered resource names as a union — handy for typing\n * application-side data structures. */\n resourceNames: ReadonlyArray<R>;\n}\n\n/**\n * Factory. Run once at module-init time in your host project; the\n * returned hooks/components are referentially stable.\n */\nexport function defineAuthRbac<\n const Reg extends ReadonlyArray<ResourceDescriptor>,\n>(\n resources: Reg,\n // The runtime guards live in the React entry; we accept them\n // here as plain refs so this module stays React-free at the type\n // level. The `react` entry calls this factory passing its own\n // exports so adopters never see the wiring.\n runtime: {\n useCan: TypedGuards<string>[\"useCan\"];\n useCanAccessSection: TypedGuards<string>[\"useCanAccessSection\"];\n Can: TypedGuards<string>[\"Can\"];\n RequirePermission: TypedGuards<string>[\"RequirePermission\"];\n },\n): TypedGuards<Reg[number][\"resource\"]> {\n // Cycle detection runs once at module-init time so misconfigured\n // registries fail at app boot, not on the first matrix toggle.\n detectDependencyCycles(resources);\n\n type R = Reg[number][\"resource\"];\n return {\n useCan: runtime.useCan as TypedGuards<R>[\"useCan\"],\n useCanAccessSection: runtime.useCanAccessSection as TypedGuards<R>[\"useCanAccessSection\"],\n Can: runtime.Can as TypedGuards<R>[\"Can\"],\n RequirePermission: runtime.RequirePermission as TypedGuards<R>[\"RequirePermission\"],\n resources,\n resourceNames: resources.map((r) => r.resource) as ReadonlyArray<R>,\n };\n}\n","/**\n * Built-in fetchers — adopters can use these or pass their own\n * implementation of `AuthRbacFetcher`.\n */\n\nimport type { AuthRbacFetcher, UserProfile } from \"./types.js\";\n\n/**\n * Calls the package's SQL function `rbac.user_profile(uuid)` via\n * a Supabase JS client. Easiest path when the host project already\n * uses Supabase.\n *\n * The function lives in the dedicated `rbac` Postgres schema, so the\n * adopter must add `rbac` to their PostgREST exposed-schemas list\n * (Supabase Studio → Settings → API → Exposed schemas) for the\n * `.schema('rbac')` call below to reach it.\n *\n * @example\n * createSupabaseFetcher({ supabase, userId: session.user.id })\n */\nexport function createSupabaseFetcher(opts: {\n supabase: {\n schema: (name: string) => {\n rpc: (\n fn: string,\n args: Record<string, unknown>,\n ) => Promise<{ data: unknown; error: { message: string } | null }>;\n };\n };\n userId: string;\n}): AuthRbacFetcher {\n return {\n async fetchProfile(): Promise<UserProfile> {\n const { data, error } = await opts.supabase.schema(\"rbac\").rpc(\n \"user_profile\",\n { p_user_id: opts.userId },\n );\n if (error) {\n throw new Error(\n `auth-rbac: failed to load user profile via Supabase RPC: ${error.message}`,\n );\n }\n return normalizeProfile(data);\n },\n };\n}\n\n/**\n * Cheap probe — returns true if the package's `rbac` schema looks\n * reachable. Useful at app start to fail loudly if the migration\n * hasn't been applied OR if `rbac` isn't in the project's PostgREST\n * exposed-schemas list.\n *\n * @example\n * if (!(await detectRbacSchema(supabase))) {\n * console.error(\"rbac schema not reachable — apply 0001_initial.sql\");\n * }\n */\nexport async function detectRbacSchema(supabase: {\n schema: (name: string) => {\n rpc: (\n fn: string,\n args: Record<string, unknown>,\n ) => Promise<{ data: unknown; error: { message: string } | null }>;\n };\n}): Promise<boolean> {\n try {\n const { error } = await supabase.schema(\"rbac\").rpc(\"user_can\", {\n p_user_id: \"00000000-0000-0000-0000-000000000000\",\n p_resource: \"__rbac_self_check__\",\n p_action: \"read\",\n p_company_id: null,\n });\n return error === null;\n } catch {\n return false;\n }\n}\n\n/**\n * Calls a regular HTTP endpoint that returns a `UserProfile` JSON\n * payload. Use this when the host project has its own backend that\n * wraps the package's Python helpers (or any equivalent).\n *\n * @example\n * createHttpFetcher({ url: \"/api/users/me/profile\" })\n */\nexport function createHttpFetcher(opts: {\n url: string;\n /** Forwarded as-is to `fetch`. Use this to attach auth headers. */\n init?: RequestInit;\n /** Override the global `fetch` if you're in a non-browser env. */\n fetch?: typeof fetch;\n}): AuthRbacFetcher {\n const fetchImpl = opts.fetch ?? globalThis.fetch;\n return {\n async fetchProfile(): Promise<UserProfile> {\n const res = await fetchImpl(opts.url, opts.init);\n if (!res.ok) {\n throw new Error(\n `auth-rbac: profile endpoint ${opts.url} returned ${res.status}`,\n );\n }\n const json = (await res.json()) as unknown;\n return normalizeProfile(json);\n },\n };\n}\n\n/**\n * Defensive normalisation: the Supabase RPC returns whatever the SQL\n * function emitted. We coerce missing fields into the empty defaults\n * so consumers can iterate without null checks. Throws if the shape\n * is unrecognisable.\n */\nfunction normalizeProfile(raw: unknown): UserProfile {\n if (!raw || typeof raw !== \"object\") {\n throw new Error(\"auth-rbac: profile payload is not an object\");\n }\n const p = raw as Partial<UserProfile> & Record<string, unknown>;\n if (typeof p.user_id !== \"string\") {\n throw new Error(\"auth-rbac: profile payload missing user_id\");\n }\n return {\n user_id: p.user_id,\n is_super_admin: !!p.is_super_admin,\n system_roles: Array.isArray(p.system_roles) ? p.system_roles : [],\n system_permissions:\n p.system_permissions && typeof p.system_permissions === \"object\"\n ? (p.system_permissions as UserProfile[\"system_permissions\"])\n : {},\n system_frontend_config:\n p.system_frontend_config && typeof p.system_frontend_config === \"object\"\n ? (p.system_frontend_config as UserProfile[\"system_frontend_config\"])\n : {},\n memberships: Array.isArray(p.memberships)\n ? (p.memberships as UserProfile[\"memberships\"])\n : [],\n };\n}\n"],"mappings":"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","names":["defineAuthRbac","import_jsx_runtime","import_jsx_runtime","import_react","import_react","defineAuthRbac"]}