snipe-auth-rbac 0.4.0 → 0.5.0

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.
@@ -381,8 +381,6 @@ function useRolePermissionGrid(roleId) {
381
381
  const dependencies = useAdminResourceDependencies();
382
382
  const setCell = useSetRolePermissionCell();
383
383
  const transport = useAdminTransport();
384
- const [isCascading, setCascading] = useState(false);
385
- const [cascadeError, setCascadeError] = useState(null);
386
384
  const grid = useMemo(() => {
387
385
  const out = {};
388
386
  for (const row of data ?? []) {
@@ -395,105 +393,81 @@ function useRolePermissionGrid(roleId) {
395
393
  }
396
394
  return out;
397
395
  }, [data]);
398
- const originGrid = useMemo(() => {
399
- const out = {};
400
- for (const row of data ?? []) {
401
- out[row.resource] = {
402
- read: row.read_granted_via ?? null,
403
- write: row.write_granted_via ?? null,
404
- update: row.update_granted_via ?? null,
405
- delete: row.delete_granted_via ?? null
406
- };
407
- }
408
- return out;
409
- }, [data]);
410
- const edgesByParent = useMemo(() => {
396
+ const parentsByChild = useMemo(() => {
411
397
  const map = /* @__PURE__ */ new Map();
412
398
  for (const edge of dependencies.data ?? []) {
413
- const list = map.get(edge.parent_resource) ?? [];
414
- map.set(edge.parent_resource, [
399
+ const list = map.get(edge.child_resource) ?? [];
400
+ map.set(edge.child_resource, [
415
401
  ...list,
416
- { child: edge.child_resource, action: edge.action }
402
+ { parent: edge.parent_resource, action: edge.action }
417
403
  ]);
418
404
  }
419
405
  return map;
420
406
  }, [dependencies.data]);
407
+ const originGrid = useMemo(() => {
408
+ const out = {};
409
+ const resources = new Set(Object.keys(grid));
410
+ for (const child of parentsByChild.keys()) {
411
+ resources.add(child);
412
+ }
413
+ for (const resource of resources) {
414
+ const directs = grid[resource];
415
+ const cellOrigins = {
416
+ read: null,
417
+ write: null,
418
+ update: null,
419
+ delete: null
420
+ };
421
+ for (const action of ACTIONS) {
422
+ if (directs?.[action]) {
423
+ cellOrigins[action] = "direct";
424
+ continue;
425
+ }
426
+ const parents = parentsByChild.get(resource) ?? [];
427
+ const impliedFrom = parents.find(
428
+ (p) => p.action === action && grid[p.parent]?.[action] === true
429
+ );
430
+ cellOrigins[action] = impliedFrom ? impliedFrom.parent : null;
431
+ }
432
+ out[resource] = cellOrigins;
433
+ }
434
+ return out;
435
+ }, [grid, parentsByChild]);
421
436
  const updateCell = useCallback(
422
437
  async (resource, action, value) => {
423
438
  if (!roleId) {
424
439
  return;
425
440
  }
426
- const writes = [
427
- { role_id: roleId, resource, action, value, grantedVia: null }
428
- ];
429
- if (value) {
430
- const edges = edgesByParent.get(resource) ?? [];
431
- for (const edge of edges) {
432
- if (edge.action !== action) {
433
- continue;
434
- }
435
- const childRow = (data ?? []).find((r) => r.resource === edge.child);
436
- const childValue = childRow?.[ACTION_FIELD[action]] === true;
437
- const childOrigin = childRow?.[ORIGIN_FIELD[action]] ?? null;
438
- if (childValue && childOrigin == null) {
439
- continue;
440
- }
441
- writes.push({
442
- role_id: roleId,
443
- resource: edge.child,
444
- action,
445
- value: true,
446
- grantedVia: resource
447
- });
448
- }
449
- }
450
- setCascading(true);
451
- setCascadeError(null);
452
- try {
453
- const [first, ...rest] = writes;
454
- if (first && rest.length === 0) {
455
- await setCell.mutate(first);
456
- } else {
457
- await transport.batchSetRolePermissionCells(writes);
458
- }
459
- void refresh();
460
- } catch (e) {
461
- setCascadeError(e instanceof Error ? e : new Error(String(e)));
462
- throw e;
463
- } finally {
464
- setCascading(false);
465
- }
441
+ await setCell.mutate({
442
+ role_id: roleId,
443
+ resource,
444
+ action,
445
+ value,
446
+ grantedVia: null
447
+ });
448
+ void refresh();
466
449
  },
467
- [roleId, setCell, refresh, edgesByParent, data, transport]
450
+ [roleId, setCell, refresh]
468
451
  );
452
+ void transport;
469
453
  return {
470
454
  grid,
471
455
  originGrid,
456
+ parentsByChild,
472
457
  isLoading: isLoading || dependencies.isLoading,
473
458
  error: error ?? dependencies.error,
474
459
  refresh,
475
460
  updateCell,
476
- isUpdating: setCell.isPending || isCascading,
477
- updateError: setCell.error ?? cascadeError
461
+ isUpdating: setCell.isPending,
462
+ updateError: setCell.error
478
463
  };
479
464
  }
480
- var ACTION_FIELD = {
481
- read: "can_read",
482
- write: "can_write",
483
- update: "can_update",
484
- delete: "can_delete"
485
- };
486
- var ORIGIN_FIELD = {
487
- read: "read_granted_via",
488
- write: "write_granted_via",
489
- update: "update_granted_via",
490
- delete: "delete_granted_via"
491
- };
465
+ var ACTIONS = ["read", "write", "update", "delete"];
492
466
 
493
467
  // src/admin/PermissionsMatrix.tsx
494
468
  import { useMemo as useMemo2 } from "react";
495
469
  import { Fragment, jsx as jsx2 } from "react/jsx-runtime";
496
- var ACTIONS = ["read", "write", "update", "delete"];
470
+ var ACTIONS2 = ["read", "write", "update", "delete"];
497
471
  function PermissionsMatrix(props) {
498
472
  const { grid, originGrid, isLoading, error, updateCell, isUpdating } = useRolePermissionGrid(props.roleId);
499
473
  const groups = useMemo2(
@@ -501,7 +475,10 @@ function PermissionsMatrix(props) {
501
475
  [props.resources]
502
476
  );
503
477
  const isCellEnabled = (resource, action) => {
504
- return grid[resource]?.[action] ?? false;
478
+ if (grid[resource]?.[action]) {
479
+ return true;
480
+ }
481
+ return originGrid[resource]?.[action] != null;
505
482
  };
506
483
  const cellOrigin = (resource, action) => {
507
484
  const origin = originGrid[resource]?.[action];
@@ -518,7 +495,7 @@ function PermissionsMatrix(props) {
518
495
  isLoading,
519
496
  isUpdating,
520
497
  error,
521
- actions: ACTIONS
498
+ actions: ACTIONS2
522
499
  }) });
523
500
  }
524
501
 
@@ -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 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 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 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\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 * 0.4.0+. Per-cell origin tracking — `null` means a direct admin\n * grant, a string is the name of the parent resource whose\n * `dependsOn` edge implied the row. Used by the matrix UI to render\n * the \"Implied by …\" badge.\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 setCell = useSetRolePermissionCell();\n const transport = useAdminTransport();\n const [isCascading, setCascading] = useState(false);\n const [cascadeError, setCascadeError] = useState<Error | null>(null);\n\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 const originGrid = useMemo<RolePermissionOriginGrid>(() => {\n const out: RolePermissionOriginGrid = {};\n for (const row of data ?? []) {\n out[row.resource] = {\n read: row.read_granted_via ?? null,\n write: row.write_granted_via ?? null,\n update: row.update_granted_via ?? null,\n delete: row.delete_granted_via ?? null,\n };\n }\n return out;\n }, [data]);\n\n // Index dependency edges by parent for O(1) cascade lookup.\n const edgesByParent = useMemo(() => {\n const map = new Map<\n string,\n ReadonlyArray<{ child: string; action: Action }>\n >();\n for (const edge of dependencies.data ?? []) {\n const list = map.get(edge.parent_resource) ?? [];\n map.set(edge.parent_resource, [\n ...list,\n { child: edge.child_resource, action: edge.action },\n ]);\n }\n return map;\n }, [dependencies.data]);\n\n const updateCell = useCallback(\n async (resource: string, action: Action, value: boolean) => {\n if (!roleId) {\n return;\n }\n // Direct grant (or direct revoke) — origin column flips to NULL.\n // The cascade below handles implied rows separately.\n const writes: Array<{\n role_id: string;\n resource: string;\n action: Action;\n value: boolean;\n grantedVia: string | null;\n }> = [\n { role_id: roleId, resource, action, value, grantedVia: null },\n ];\n\n if (value) {\n // Toggle-on cascade — for each dependsOn edge whose action\n // matches, add an implied row UNLESS the child already has\n // the action directly granted (don't downgrade direct →\n // implied). Same-action only: a `read` toggle cascades read\n // to children that declare read on the edge.\n const edges = edgesByParent.get(resource) ?? [];\n for (const edge of edges) {\n if (edge.action !== action) {\n continue;\n }\n const childRow = (data ?? []).find((r) => r.resource === edge.child);\n const childValue =\n childRow?.[ACTION_FIELD[action] as keyof typeof childRow] === true;\n const childOrigin =\n (childRow?.[\n ORIGIN_FIELD[action] as keyof typeof childRow\n ] as string | null | undefined) ?? null;\n if (childValue && childOrigin == null) {\n // Already direct-granted — leave alone so we don't\n // downgrade to implied.\n continue;\n }\n writes.push({\n role_id: roleId,\n resource: edge.child,\n action,\n value: true,\n grantedVia: resource,\n });\n }\n }\n // No cascade on toggle-off — per-cell override semantics.\n // Children stay as they are; the admin can revoke each one\n // individually if needed.\n\n setCascading(true);\n setCascadeError(null);\n try {\n const [first, ...rest] = writes;\n if (first && rest.length === 0) {\n await setCell.mutate(first);\n } else {\n await transport.batchSetRolePermissionCells(writes);\n }\n void refresh();\n } catch (e) {\n setCascadeError(e instanceof Error ? e : new Error(String(e)));\n throw e;\n } finally {\n setCascading(false);\n }\n },\n [roleId, setCell, refresh, edgesByParent, data, transport],\n );\n\n return {\n grid,\n originGrid,\n isLoading: isLoading || dependencies.isLoading,\n error: error ?? dependencies.error,\n refresh,\n updateCell,\n isUpdating: setCell.isPending || isCascading,\n updateError: setCell.error ?? cascadeError,\n };\n}\n\nconst ACTION_FIELD: Record<Action, string> = {\n read: \"can_read\",\n write: \"can_write\",\n update: \"can_update\",\n delete: \"can_delete\",\n};\n\nconst ORIGIN_FIELD: 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 * 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 /** Read a single cell from the current grid. */\n isCellEnabled: (resource: string, action: Action) => boolean;\n /**\n * Origin of a single cell — `'direct'` for a direct admin grant\n * (or off), or the name of the parent resource whose `dependsOn`\n * edge implied the row. The consumer renders an \"Implied by …\"\n * badge whenever this returns a non-`'direct'` value.\n *\n * Available since 0.4.0. With pre-0.4.0 SQL (no granted_via\n * columns) this always returns `'direct'`.\n */\n cellOrigin: (resource: string, action: Action) => \"direct\" | string;\n /**\n * Write a single cell. Optimistic in the local cache + writes\n * through. On toggle-on, also writes implied rows for every\n * `dependsOn` edge whose `actions` include the toggled action —\n * those rows carry the parent's name in\n * `<action>_granted_via`. Toggle-off never cascades.\n */\n setCell: (resource: string, action: Action, value: boolean) => 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 { grid, originGrid, isLoading, error, updateCell, isUpdating } =\n useRolePermissionGrid(props.roleId);\n\n const groups = useMemo<MatrixGroup[]>(\n () => groupResources(props.resources),\n [props.resources],\n );\n\n const isCellEnabled = (resource: string, action: Action): boolean => {\n return grid[resource]?.[action] ?? false;\n };\n\n const cellOrigin = (\n resource: string,\n action: Action,\n ): \"direct\" | string => {\n const origin = originGrid[resource]?.[action];\n return origin == null ? \"direct\" : origin;\n };\n\n const setCell = async (resource: string, action: Action, value: boolean) => {\n await updateCell(resource, action, value);\n };\n\n return (\n <>\n {props.children({\n groups,\n isCellEnabled,\n cellOrigin,\n setCell,\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":["useMemo","jsx","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 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 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 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\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 * 0.4.0+ shape (0.5.0 semantics): per-cell origin marker. `'direct'`\n * for an explicit admin grant, the name of the parent resource for\n * an implied grant (from rbac.resource_dependencies), or `null` /\n * absent when neither.\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.\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 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 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 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 cellOrigins[action] = impliedFrom ? impliedFrom.parent : null;\n }\n out[resource] = cellOrigins;\n }\n return out;\n }, [grid, parentsByChild]);\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 isLoading: isLoading || dependencies.isLoading,\n error: error ?? dependencies.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 /** Read a single cell from the current grid. */\n isCellEnabled: (resource: string, action: Action) => boolean;\n /**\n * Origin of a single cell — `'direct'` for a direct admin grant\n * (or off), or the name of the parent resource whose `dependsOn`\n * edge implied the row. The consumer renders an \"Implied by …\"\n * badge whenever this returns a non-`'direct'` value.\n *\n * Available since 0.4.0. With pre-0.4.0 SQL (no granted_via\n * columns) this always returns `'direct'`.\n */\n cellOrigin: (resource: string, action: Action) => \"direct\" | string;\n /**\n * Write a single cell. Optimistic in the local cache + writes\n * through. On toggle-on, also writes implied rows for every\n * `dependsOn` edge whose `actions` include the toggled action —\n * those rows carry the parent's name in\n * `<action>_granted_via`. Toggle-off never cascades.\n */\n setCell: (resource: string, action: Action, value: boolean) => 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 { grid, originGrid, isLoading, error, updateCell, isUpdating } =\n useRolePermissionGrid(props.roleId);\n\n const groups = useMemo<MatrixGroup[]>(\n () => groupResources(props.resources),\n [props.resources],\n );\n\n // 0.5.0: a cell is \"enabled\" if it has a DIRECT grant (grid row\n // says true) OR an IMPLIED grant (originGrid says a parent\n // resource — non-null AND not 'direct'). originGrid stores\n // 'direct' for the direct case and the parent resource name for\n // the implied case, so a non-null origin means the cell is\n // enabled either way.\n const isCellEnabled = (resource: string, action: Action): boolean => {\n if (grid[resource]?.[action]) {\n return true;\n }\n return originGrid[resource]?.[action] != null;\n };\n\n const cellOrigin = (\n resource: string,\n action: Action,\n ): \"direct\" | 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 const setCell = async (resource: string, action: Action, value: boolean) => {\n await updateCell(resource, action, value);\n };\n\n return (\n <>\n {props.children({\n groups,\n isCellEnabled,\n cellOrigin,\n setCell,\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":["useMemo","jsx","ACTIONS","useMemo","useCallback","useState","Fragment","jsx","useState","useCallback","useCallback","useState","Fragment","jsx","useState","useCallback"]}
@@ -16,6 +16,7 @@ function detectDependencyCycles(registry) {
16
16
  colour.set(r.resource, WHITE);
17
17
  }
18
18
  const stack = [];
19
+ const cyclesFound = /* @__PURE__ */ new Set();
19
20
  const edgeTarget = (edge) => typeof edge === "string" ? edge : edge.resource;
20
21
  function visit(name) {
21
22
  const state = colour.get(name);
@@ -25,9 +26,8 @@ function detectDependencyCycles(registry) {
25
26
  if (state === GREY) {
26
27
  const cycleStart = stack.indexOf(name);
27
28
  const cycle = [...stack.slice(cycleStart), name].join(" \u2192 ");
28
- throw new RbacRegistryError(
29
- `dependency cycle detected in resource registry: ${cycle}`
30
- );
29
+ cyclesFound.add(cycle);
30
+ return;
31
31
  }
32
32
  if (state === void 0) {
33
33
  throw new RbacRegistryError(
@@ -47,6 +47,11 @@ function detectDependencyCycles(registry) {
47
47
  for (const r of registry) {
48
48
  visit(r.resource);
49
49
  }
50
+ if (cyclesFound.size > 0) {
51
+ console.warn(
52
+ `[auth-rbac] dependency cycle(s) detected in resource registry. Cycles are runtime-safe \u2014 the matrix UI's cascade only flips cells on and skips already-on cells \u2014 but they may indicate unintended edges. Found: ${Array.from(cyclesFound).join("; ")}`
53
+ );
54
+ }
50
55
  }
51
56
  function defineAuthRbac(resources, runtime) {
52
57
  detectDependencyCycles(resources);
@@ -130,4 +135,4 @@ export {
130
135
  detectRbacSchema,
131
136
  createHttpFetcher
132
137
  };
133
- //# sourceMappingURL=chunk-5UAIIOKT.js.map
138
+ //# sourceMappingURL=chunk-WL4QZ7HO.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/define.ts","../src/fetchers.ts"],"sourcesContent":["/**\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":[]}