# `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.