Files
skills/docs/networking/drive/drive-react-sharing/src/hooks/use-sharing/use-sharing.md
2026-05-29 08:20:56 +01:00

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, 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

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): 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.