4.2 KiB
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, andTokenManagerinstances supplied by the caller. The package does not own a partition store. - AbortSignal threaded through every action. A pre-aborted signal raises
a
DriveReactSharingErrorbefore any engine mutation. - Surfaces failures via both
result.error(for inline rendering) andthrow(for promise-chain orchestration). Underlying engine errors are preserved oncause. - 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_RECIPIENTsynchronously. - Permission update is a revoke-then-create round-trip because the engine
has no atomic mutate API on
GrantManager.
Usage: Basic Example
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 (
<button
type="button"
onClick={() => sharing.addRecipient("bob", "read")}
>
Add bob
</button>
);
}
Usage: Advanced Example
Composing with AbortController so a parent dialog can cancel inflight
mutations on close:
import { useEffect } from "react";
function Composer({
partitionId,
sharing,
}: {
partitionId: string;
sharing: ReturnType<typeof useSharing>;
}) {
useEffect(() => {
const ctrl = new AbortController();
return () => ctrl.abort();
}, [partitionId]);
async function bulkPromote(ids: readonly string[]): Promise<void> {
const ctrl = new AbortController();
for (const id of ids) {
await sharing.updatePermission(id, "admin", ctrl.signal);
}
}
return null;
}
API Reference
useSharing(options: UseSharingOptions): UseSharingResultoptions.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 fromgrants.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.