# `useSharing` hook _(no submodules)_ ## Purpose `useSharing` is the React-tier binding for `@suaveplan/drive-sharing`. It holds a local list of `ShareRecipient` rows that mirror the active grants on a partition, and exposes four async actions (`addRecipient`, `removeRecipient`, `updatePermission`, `refresh`) that delegate to the real engine — no in-memory stub, no mocks. After every successful action the hook re-syncs from `GrantManager.list({ partitionId, active: true })` so optimistic UI never drifts from the engine's truth. ## Features - Wires directly to live `PartitionManager`, `GrantManager`, and `TokenManager` instances supplied by the caller. The package does not own a partition store. - AbortSignal threaded through every action. A pre-aborted signal raises a `DriveReactSharingError` before any engine mutation. - Surfaces failures via both `result.error` (for inline rendering) and `throw` (for promise-chain orchestration). Underlying engine errors are preserved on `cause`. - Emits three OTel counters: `drive.react.sharing.recipients_added_total`, `drive.react.sharing.recipients_removed_total`, `drive.react.sharing.permissions_updated_total{permission}`. - Empty-string recipient ids raise `INVALID_RECIPIENT` synchronously. - Permission update is a revoke-then-create round-trip because the engine has no atomic mutate API on `GrantManager`. ## Usage: Basic Example ```tsx import { GrantManager, InMemoryPartitionStore, KeyRing, PartitionManager, TokenManager, userId, } from "@suaveplan/drive-sharing"; import { useSharing } from "@suaveplan/drive-react-sharing"; const partitions = new PartitionManager({ store: new InMemoryPartitionStore(), keyRing: new KeyRing(), }); const grants = new GrantManager(); const tokens = new TokenManager(); function PanelWidget({ partitionId }: { partitionId: string }) { const sharing = useSharing({ partitionId, grantorId: userId("alice"), partitions, grants, tokens, }); return ( ); } ``` ## Usage: Advanced Example Composing with `AbortController` so a parent dialog can cancel inflight mutations on close: ```tsx import { useEffect } from "react"; function Composer({ partitionId, sharing, }: { partitionId: string; sharing: ReturnType; }) { useEffect(() => { const ctrl = new AbortController(); return () => ctrl.abort(); }, [partitionId]); async function bulkPromote(ids: readonly string[]): Promise { const ctrl = new AbortController(); for (const id of ids) { await sharing.updatePermission(id, "admin", ctrl.signal); } } return null; } ``` ## API Reference - `useSharing(options: UseSharingOptions): UseSharingResult` - `options.partitionId: string` — partition the grants belong to. - `options.grantorId: UserId` — typically the current user. - `options.partitions: PartitionManager` — engine partition manager. - `options.grants: GrantManager` — engine grant manager. - `options.tokens: TokenManager` — engine token manager. - `options.initialRecipients?: readonly ShareRecipient[]` — optional SSR / Storybook seed; when omitted the hook seeds from `grants.list({ partitionId, active: true })`. - Returns `UseSharingResult` — `{ recipients, loading, error, addRecipient(id, perm, signal?), removeRecipient(grantId, signal?), updatePermission(grantId, perm, signal?), refresh(signal?) }`. ## Implementation Notes The recipient list is refreshed by re-reading from the engine after every mutation rather than spliced in place. This keeps the hook free of merge-conflict logic when multiple components share the same `GrantManager`. `primaryPermission` on each row is computed at projection time from a fixed tier ranking (`read < write < delete < share < admin`); consumers should still consult the full `permissions` array when making entitlement decisions, but UI badges can render the primary tier directly.