# Memberships Membership and relationship tuples - Package: @bijectionhq/memberships - Category: Authentication - Status: Reference - License: MIT - Bijection source (repository access required): https://github.com/bijectionhq/memberships/tree/3709c5e1e9bd794b03be90df76cf16377a239a90 - Upstream: https://github.com/vllnt/convex-memberships/tree/236426bcd6e348a00f3e6d1adef4a872b126df51 - Documentation: https://docs.bijection.com/components/using - Directory: https://bijection.com/components/memberships ## Get the source Bijection repository access is required. Request access at https://bijection.com/contact. ```sh git clone https://github.com/bijectionhq/memberships.git cd memberships git checkout 3709c5e1e9bd794b03be90df76cf16377a239a90 ``` ## Before using this component Read the package README at the recorded revision for setup, dependencies, and API examples. Upstream documentation describes the original package and may differ from the Bijection adaptation. This is a source candidate for evaluation. Catalog inclusion does not establish npm availability or runtime qualification. Check compatibility with your SDK and deployment before adoption. External providers run their own compute. ## Upstream package documentation Recorded 2026-10-23. This describes @vllnt/convex-memberships; check the Bijection adaptation separately. @vllnt/convex-memberships stores who is related to what using memberRef, resourceRef, and relation. Your app defines the meaning of each reference and relationship, so one storage model can support several domains. Add or remove relationship tuples, transition a relation, and paginate in either direction: members of a resource or resources of a member. Re-adding the same tuple is idempotent. Opaque kind and status fields let the host describe its own entities. The host owns authentication and permission policy. Always specify the accepted relation in authorization checks: an any-relation check can also match an invited or suspended relationship. This is a flat relationship store; transitive group expansion belongs in the application. Maintained by [bntvllnt](https://bntvllnt.com) at [VLLNT](https://vllnt.com). Find the author on [GitHub](https://github.com/bntvllnt) and [X](https://x.com/bntvllnt). ### Use cases - SaaS workspaces: list members and the workspaces a person belongs to. - Learning platforms: represent learners enrolled in courses or cohorts. - Games and communities: maintain guild, team, or group membership. - Collaborative tools: record document viewers, editors, or resource-specific sharing relationships. ### How it works Add or remove relationship tuples, transition a relation, and paginate in either direction: members of a resource or resources of a member. Re-adding the same tuple is idempotent. Opaque kind and status fields let the host describe its own entities. The host owns authentication and permission policy. Always specify the accepted relation in authorization checks: an any-relation check can also match an invited or suspended relationship. This is a flat relationship store; transitive group expansion belongs in the application. ### Upstream README # @vllnt/convex-memberships Auth-agnostic membership and relationship graph (ReBAC tuples), as a Convex component. ```ts const memberships = new Memberships(components.memberships); await memberships.add(ctx, userId, orgId, "admin"); const isAdmin = await memberships.isMember(ctx, userId, orgId, "admin"); ``` A tuple is `(memberRef, resourceRef, relation)`, classified by an opaque `memberKind`. Domain-neutral: org membership, document sharing, team roles, group nesting — any "who relates to what, and how" question. Edges are **flat** — transitive expansion (a member of a group that is a member of an org) is **not** auto-resolved; it is planned for vNext. ## Features - **Flat ReBAC tuples** — `(memberRef, resourceRef, relation)` with an opaque `memberKind` (defaults `"user"`) and optional `status`. - **Idempotent edges** — `add` dedupes on the full tuple, returning the existing id and patching `memberKind` / `status`. - **Paginated listing** — members of a resource, and resources of a member, both paginated for guild/workspace scale. - **Relation filtering** — list members narrowed to a single relation. - **Bounded membership checks** — exact-relation, or any-relation via index (not a full member scan). - **Guarded relation transitions** — `setRelation` moves an edge, guarded against collisions; missing source ⇒ `NOT_FOUND`. - **Auditable removal** — `remove` returns `{ removed }`; `documents` exposes stored `createdAt` and `status`. - **Opaque refs** — every ref/relation/kind/status is an arbitrary host string the component never inspects. ## Installation ```bash pnpm add @vllnt/convex-memberships ``` Peer dependency: `convex@^1.41.0`. ## Usage ```ts // convex/convex.config.ts import { defineApp } from "convex/server"; import memberships from "@vllnt/convex-memberships/convex.config"; const app = defineApp(); app.use(memberships); export default app; ``` ```ts // convex/teams.ts — host owns auth; pass opaque refs in. import { components } from "./_generated/api"; import { mutation, query } from "./_generated/server"; import { v } from "convex/values"; import { Memberships } from "@vllnt/convex-memberships"; const memberships = new Memberships(components.memberships); export const join = mutation({ args: { userId: v.string(), orgId: v.string() }, handler: (ctx, { userId, orgId }) => memberships.add(ctx, userId, orgId), }); export const isInOrg = query({ args: { userId: v.string(), orgId: v.string() }, handler: (ctx, { userId, orgId }) => memberships.isMember(ctx, userId, orgId), }); ``` Client options: `new Memberships(component, { defaultRelation = "member", defaultMemberKind = "user" })`. ## API Reference | Method | Kind | Result | |--------|------|--------| | `add(ctx, memberRef, resourceRef, relation?, memberKind?, status?)` | mutation | `id` (tuple-idempotent; patches `memberKind`/`status` on re-add) | | `remove(ctx, memberRef, resourceRef, relation?)` | mutation | `{ removed }` (idempotent) | | `setRelation(ctx, memberRef, resourceRef, fromRelation, toRelation)` | mutation | `{ ok, reason? }` (`NOT_FOUND` \| `EXISTS`) | | `listMembers(ctx, resourceRef, paginationOpts, relation?)` | query | `Page` | | `members(ctx, memberRef, paginationOpts)` | query | `Page` | | `isMember(ctx, memberRef, resourceRef, relation?)` | query | `boolean` | | `documents(ctx, paginationOpts, filter?)` | query | `Page` | Full reference: [docs/API.md](docs/API.md). ## React An optional, tree-shakeable `./react` entry ships thin hooks over `convex/react`'s `useQuery`. `react` is an optional peer dependency — a backend-only consumer pulls zero React. Each hook takes the host app's re-exported query reference. ```tsx import { useIsMember } from "@vllnt/convex-memberships/react"; import { api } from "../convex/_generated/api"; const isAdmin = useIsMember(api.memberships.isMember, { memberRef: userId, resourceRef: orgId, relation: "admin", }); // boolean | undefined (undefined while loading) ``` | Hook | Args | Returns | |------|------|---------| | `useIsMember(isMemberRef, { memberRef, resourceRef, relation? })` | the host's `isMember` ref | `boolean \| undefined` | | `useMembers(membersRef, { memberRef })` | the host's `members` ref | first page of resource entries `\| undefined` | | `useListMembers(listMembersRef, { resourceRef, relation? })` | the host's `listMembers` ref | first page of member entries `\| undefined` | ## Security - **Auth-agnostic** — the host resolves identity, decides who may create/read an edge, and passes opaque refs; tables are sandboxed (reached only via the client). - **`isMember` footgun** — called without `relation`, it returns `true` for **any** stored relation (`"invited"`, `"suspended"`, `"banned"`, …). For authz gates, always pass `relation` or enumerate the relations your policy accepts. See [docs/API.md](docs/API.md). ## Testing ```bash pnpm test # single run pnpm test:coverage # enforced 100% on covered files ``` Tests run against the real component runtime via `convex-test` (`@edge-runtime/vm`), not mocks. ## Contributing See [CONTRIBUTING.md](CONTRIBUTING.md). ## Author Built by [bntvllnt](https://github.com/bntvllnt) · [bntvllnt.com](https://bntvllnt.com) · [X @bntvllnt](https://x.com/bntvllnt) Part of the [@vllnt](https://github.com/vllnt) Convex component fleet — [vllnt.com](https://vllnt.com) If this is useful, [sponsor the work](https://github.com/sponsors/bntvllnt). ## License MIT — see [LICENSE](LICENSE).