Memberships

ReBAC membership tuples for Convex: idempotent edges, paginated member/resource queries, and relation guards — auth-agnostic, sandboxed tables.

Installation

npm install @vllnt/convex-memberships

About Memberships

Memberships models auth-agnostic relationship edges — org memberships, team roles, document sharing, group nesting — as flat `(memberRef, resourceRef, relation)` tuples with an opaque `memberKind`; transitive expansion is host-responsibility (not auto-resolved). Edges are idempotent on re-add, removable with `{ removed }` audit feedback, and queryable paginated by member or resource with optional relation filtering. Tuple data lives in sandboxed component tables, every write is host-gated, and the component runs identically on Convex Cloud and self-hosted convex-backend.

Benefits

Use cases

how to implement multi-tenant role-based access control in Convex

The `@vllnt/convex-memberships` component stores `(memberRef, resourceRef, relation)` tuples so you can call `memberships.add(ctx, userId, orgId, 'admin')` to assign a role and `memberships.isMember(ctx, userId, orgId, 'admin')` to gate access. Your host mutation handles auth; the component only stores and queries the edge. This keeps authorization logic centralized without coupling it to a specific auth provider.

how to list all members of an organization in Convex with pagination

`@vllnt/convex-memberships` exposes `memberships.listMembers(ctx, resourceRef, paginationOpts, relation?)` which returns a paginated `Page<MemberEntry>` compatible with Convex's built-in pagination. You can optionally narrow results to a single relation such as `'admin'` or `'invited'`. The symmetric `memberships.members(ctx, memberRef, paginationOpts)` returns all resources a member belongs to.

how to move a user from invited to member role in Convex without race conditions

`@vllnt/convex-memberships` provides `memberships.setRelation(ctx, memberRef, resourceRef, fromRelation, toRelation)` which atomically transitions an edge from one relation to another. The method returns `{ ok, reason? }` where `reason` is `NOT_FOUND` if the source edge does not exist or `EXISTS` if the destination already exists, preventing silent collisions.

ReBAC relationship graph Convex component

`@vllnt/convex-memberships` implements flat ReBAC tuples as a sandboxed Convex component. Each tuple is `(memberRef, resourceRef, relation)` with an optional `memberKind` and `status`, all stored as opaque host strings the component never interprets. Transitive graph traversal is not yet supported and is planned for a future version.

Frequently asked questions

Does `@vllnt/convex-memberships` work with any auth provider?

`@vllnt/convex-memberships` is fully auth-agnostic. The host application resolves identity and decides who may create or read an edge; the component only receives and stores opaque string refs. This means it works with Clerk, Auth0, custom JWT auth, or any other system you use with Convex.

What happens if I call `add` twice with the same member, resource, and relation?

`add` in `@vllnt/convex-memberships` is idempotent on the full `(memberRef, resourceRef, relation)` tuple. If a matching edge already exists, the call returns the existing document id and patches `memberKind` and `status` if they differ. No duplicate edges are created.

Is calling `isMember` without a relation safe for authorization gates?

Calling `memberships.isMember(ctx, memberRef, resourceRef)` without a `relation` argument returns `true` for any stored relation, including `'invited'`, `'suspended'`, or `'banned'`. For authorization gates, always pass an explicit `relation` or enumerate the relations your policy accepts to avoid granting access to non-active membership states.

Does `@vllnt/convex-memberships` support transitive group membership?

No. `@vllnt/convex-memberships` currently stores flat edges only. A member of a group that is itself a member of an org will not be automatically resolved as a member of that org. Transitive expansion is planned for a future version.

How do I use the React hooks provided by `@vllnt/convex-memberships`?

Import hooks from `@vllnt/convex-memberships/react` and pass your host app's re-exported query reference as the first argument. For example, `useIsMember(api.memberships.isMember, { memberRef: userId, resourceRef: orgId, relation: 'admin' })` returns `boolean | undefined`, where `undefined` means the query is still loading. React is an optional peer dependency; backend-only consumers do not pull it in.

Links