@robelest/convex-auth 0.0.4-preview.32 → 0.0.4-preview.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/dist/component/_generated/component.d.ts +1611 -2039
  2. package/dist/component/account.js +3 -0
  3. package/dist/component/convex.config.d.ts +2 -2
  4. package/dist/component/factor/device.js +3 -0
  5. package/dist/component/factor/passkey.js +3 -0
  6. package/dist/component/factor/totp.js +3 -0
  7. package/dist/component/group/invite.js +3 -0
  8. package/dist/component/group/member.js +3 -0
  9. package/dist/component/group.js +3 -0
  10. package/dist/component/model.d.ts +342 -30
  11. package/dist/component/model.js +22 -4
  12. package/dist/component/modules.js +24 -21
  13. package/dist/component/public/factors/devices.js +37 -106
  14. package/dist/component/public/factors/passkeys.js +29 -149
  15. package/dist/component/public/factors/totp.js +32 -159
  16. package/dist/component/public/groups/core.js +19 -82
  17. package/dist/component/public/groups/invites.js +15 -104
  18. package/dist/component/public/groups/members.js +26 -149
  19. package/dist/component/public/identity/accounts.js +12 -94
  20. package/dist/component/public/identity/codes.js +13 -73
  21. package/dist/component/public/identity/sessions.js +5 -107
  22. package/dist/component/public/identity/tokens.js +13 -103
  23. package/dist/component/public/identity/users.js +188 -185
  24. package/dist/component/public/identity/verifiers.js +17 -80
  25. package/dist/component/public/security/keys.js +13 -120
  26. package/dist/component/public/security/limits.js +0 -43
  27. package/dist/component/public/sso/audit.js +0 -28
  28. package/dist/component/public/sso/core.js +31 -104
  29. package/dist/component/public/sso/domains.js +0 -71
  30. package/dist/component/public/sso/scim.js +63 -239
  31. package/dist/component/public/sso/secrets.js +0 -30
  32. package/dist/component/public/sso/webhooks.js +23 -128
  33. package/dist/component/rateLimit.js +3 -0
  34. package/dist/component/schema.d.ts +378 -342
  35. package/dist/component/schema.js +11 -1
  36. package/dist/component/session.js +3 -0
  37. package/dist/component/sso/audit.js +3 -0
  38. package/dist/component/sso/connection/domain/verification.js +3 -0
  39. package/dist/component/sso/connection/domain.js +3 -0
  40. package/dist/component/sso/connection/scim/config.js +3 -0
  41. package/dist/component/sso/connection/scim/identity.js +3 -0
  42. package/dist/component/sso/connection/secret.js +3 -0
  43. package/dist/component/sso/connection.js +3 -0
  44. package/dist/component/sso/webhook/delivery.js +3 -0
  45. package/dist/component/sso/webhook/endpoint.js +3 -0
  46. package/dist/component/token/pkce.js +3 -0
  47. package/dist/component/token/refresh.js +3 -0
  48. package/dist/component/token/verification.js +3 -0
  49. package/dist/component/user/email.js +3 -0
  50. package/dist/component/user/key.js +3 -0
  51. package/dist/component/user.js +62 -0
  52. package/dist/core/index.d.ts +131 -28
  53. package/dist/core/index.js +2 -0
  54. package/dist/model.js +391 -0
  55. package/dist/providers/credentials.d.ts +1 -1
  56. package/dist/providers/github.js +6 -0
  57. package/dist/providers/password.js +1 -2
  58. package/dist/server/auth.d.ts +73 -7
  59. package/dist/server/auth.js +4 -1
  60. package/dist/server/context.js +30 -3
  61. package/dist/server/contract.js +42 -42
  62. package/dist/server/core.js +224 -86
  63. package/dist/server/db.js +45 -37
  64. package/dist/server/facade.d.ts +39 -0
  65. package/dist/server/facade.js +16 -0
  66. package/dist/server/index.d.ts +5 -3
  67. package/dist/server/index.js +3 -1
  68. package/dist/server/mounts.d.ts +101 -101
  69. package/dist/server/mutations/credentials/signin.js +3 -7
  70. package/dist/server/mutations/oauth.js +9 -6
  71. package/dist/server/runtime.d.ts +147 -46
  72. package/dist/server/runtime.js +10 -8
  73. package/dist/server/services/group.js +9 -9
  74. package/dist/server/sso/domain.d.ts +1 -1
  75. package/dist/server/sso/domain.js +40 -40
  76. package/dist/server/sso/http.js +18 -18
  77. package/dist/server/sso/oidc.js +1 -1
  78. package/dist/server/sso/policies.js +3 -3
  79. package/dist/server/sso/policy.js +12 -4
  80. package/dist/server/sso/provision.js +9 -9
  81. package/dist/server/sso/validators.js +2 -2
  82. package/dist/server/sso/webhook.js +8 -8
  83. package/dist/server/types.d.ts +185 -124
  84. package/dist/server/types.js +29 -24
  85. package/dist/server/users.js +49 -2
  86. package/dist/server/validators.d.ts +745 -0
  87. package/dist/server/validators.js +60 -0
  88. package/package.json +1 -1
  89. package/dist/component/public.js +0 -22
@@ -39,15 +39,6 @@ function normalizeTags(tags) {
39
39
  * @param args.extend - An optional arbitrary payload for application-specific metadata.
40
40
  * @returns The `Id<"Group">` of the newly created group document.
41
41
  *
42
- * @example
43
- * ```ts
44
- * const groupId = await ctx.runMutation(components.auth.groups.groupCreate, {
45
- * name: "Acme Corp",
46
- * slug: "acme-corp",
47
- * type: "organization",
48
- * tags: [{ key: "plan", value: "group-sso" }],
49
- * });
50
- * ```
51
42
  */
52
43
  const groupCreate = mutation({
53
44
  args: {
@@ -81,52 +72,26 @@ const groupCreate = mutation({
81
72
  }
82
73
  });
83
74
  /**
84
- * Retrieve a group by its document ID.
85
- *
86
- * Performs a direct lookup in the `Group` table and returns the full group
87
- * document, or `null` if no group exists with the given ID.
88
- *
89
- * @param args.groupId - The `Id<"Group">` of the group to retrieve.
90
- * @returns The group document (including `name`, `slug`, `type`, `tags`, hierarchy fields, etc.) or `null` if not found.
91
- *
92
- * @example
93
- * ```ts
94
- * const group = await ctx.runQuery(components.auth.groups.groupGet, {
95
- * groupId: existingGroupId,
96
- * });
97
- * if (group !== null) {
98
- * console.log(group.name, group.slug);
99
- * }
100
- * ```
75
+ * Read a group by identity one function, all-optional args, unioned
76
+ * return: `{ id }` → `Doc<"Group"> | null`, or `{ ids }` → ordered
77
+ * `(Doc<"Group"> | null)[]` (deduped).
101
78
  */
102
79
  const groupGet = query({
103
- args: { groupId: v.id("Group") },
104
- returns: v.union(vGroupDoc, v.null()),
105
- handler: async (ctx, { groupId }) => {
106
- return await ctx.db.get("Group", groupId);
107
- }
108
- });
109
- /**
110
- * Batched equivalent of {@link groupGet}. Fetches many group documents by ID
111
- * in a single component round-trip. Returns each group (or `null`) in the
112
- * same slot as the input ID; duplicates are tolerated but de-duplicated
113
- * internally so each `ctx.db.get` runs exactly once per distinct ID.
114
- *
115
- * Used by app-side aggregate handlers (e.g. `groups:listMyGroups`) that
116
- * previously fanned out one `groupGet` per item.
117
- *
118
- * @param args.groupIds - Array of group document IDs.
119
- * @returns Array of group documents (or `null` entries) in input order.
120
- */
121
- const groupGetMany = query({
122
- args: { groupIds: v.array(v.id("Group")) },
123
- returns: v.array(v.union(vGroupDoc, v.null())),
124
- handler: async (ctx, { groupIds }) => {
125
- if (groupIds.length === 0) return [];
126
- const unique = Array.from(new Set(groupIds));
127
- const docs = await Promise.all(unique.map((id) => ctx.db.get("Group", id)));
128
- const byId = new Map(unique.map((id, i) => [id, docs[i] ?? null]));
129
- return groupIds.map((id) => byId.get(id) ?? null);
80
+ args: {
81
+ id: v.optional(v.id("Group")),
82
+ ids: v.optional(v.array(v.id("Group")))
83
+ },
84
+ returns: v.union(vGroupDoc, v.null(), v.array(v.union(vGroupDoc, v.null()))),
85
+ handler: async (ctx, args) => {
86
+ if (args.ids !== void 0) {
87
+ if (args.ids.length === 0) return [];
88
+ const unique = Array.from(new Set(args.ids));
89
+ const docs = await Promise.all(unique.map((id) => ctx.db.get("Group", id)));
90
+ const byId = new Map(unique.map((id, i) => [id, docs[i] ?? null]));
91
+ return args.ids.map((id) => byId.get(id) ?? null);
92
+ }
93
+ if (args.id === void 0) return null;
94
+ return await ctx.db.get("Group", args.id);
130
95
  }
131
96
  });
132
97
  /**
@@ -216,17 +181,6 @@ const groupAncestors = query({
216
181
  * @param args.order - Sort direction: `"asc"` or `"desc"` (defaults to `"desc"`).
217
182
  * @returns An object `{ items, nextCursor }` where `items` is an array of group documents and `nextCursor` is `null` when there are no more pages.
218
183
  *
219
- * @example
220
- * ```ts
221
- * const { items, nextCursor } = await ctx.runQuery(
222
- * components.auth.groups.groupList,
223
- * {
224
- * where: { type: "team", isRoot: false },
225
- * limit: 20,
226
- * order: "asc",
227
- * },
228
- * );
229
- * ```
230
184
  */
231
185
  const groupList = query({
232
186
  args: {
@@ -314,16 +268,6 @@ const groupList = query({
314
268
  * @param args.data - A partial object of fields to patch. Supported keys include `name`, `slug`, `type`, `parentGroupId`, `tags`, and `extend`.
315
269
  * @returns `null` on success.
316
270
  *
317
- * @example
318
- * ```ts
319
- * await ctx.runMutation(components.auth.groups.groupUpdate, {
320
- * groupId: existingGroupId,
321
- * data: {
322
- * name: "Acme Corp (renamed)",
323
- * tags: [{ key: "plan", value: "pro" }],
324
- * },
325
- * });
326
- * ```
327
271
  */
328
272
  const groupUpdate = mutation({
329
273
  args: {
@@ -376,13 +320,6 @@ const groupUpdate = mutation({
376
320
  * @param args.groupId - The `Id<"Group">` of the group to delete. All children are deleted recursively.
377
321
  * @returns `null` on success.
378
322
  *
379
- * @example
380
- * ```ts
381
- * // Delete an organization and everything nested under it
382
- * await ctx.runMutation(components.auth.groups.groupDelete, {
383
- * groupId: organizationGroupId,
384
- * });
385
- * ```
386
323
  */
387
324
  const groupDelete = mutation({
388
325
  args: { groupId: v.id("Group") },
@@ -405,5 +342,5 @@ const groupDelete = mutation({
405
342
  });
406
343
 
407
344
  //#endregion
408
- export { groupAncestors, groupCreate, groupDelete, groupGet, groupGetMany, groupList, groupUpdate };
345
+ export { groupAncestors, groupCreate, groupDelete, groupGet, groupList, groupUpdate };
409
346
  //# sourceMappingURL=core.js.map
@@ -1,4 +1,4 @@
1
- import { vGroupInviteDoc, vInviteAcceptByTokenResult, vInviteStatus, vPaginated } from "../../model.js";
1
+ import { vGroupInviteDoc, vInviteRedeemResult, vInviteStatus, vPaginated } from "../../model.js";
2
2
  import { mutation, query } from "../../functions.js";
3
3
  import { ConvexError, v } from "convex/values";
4
4
 
@@ -28,21 +28,6 @@ import { ConvexError, v } from "convex/values";
28
28
  * @param args.extend - Optional arbitrary payload for application-specific metadata.
29
29
  * @returns The `Id<"GroupInvite">` of the newly created invite document.
30
30
  *
31
- * @example
32
- * ```ts
33
- * const inviteId = await ctx.runMutation(
34
- * components.auth.groups.inviteCreate,
35
- * {
36
- * groupId: teamGroupId,
37
- * invitedByUserId: currentUserId,
38
- * email: "alice@example.com",
39
- * tokenHash: hashedToken,
40
- * roleIds: ["editor"],
41
- * status: "pending",
42
- * expiresTime: Date.now() + 7 * 24 * 60 * 60 * 1000, // 7 days
43
- * },
44
- * );
45
- * ```
46
31
  */
47
32
  const inviteCreate = mutation({
48
33
  args: {
@@ -92,57 +77,20 @@ const inviteCreate = mutation({
92
77
  }
93
78
  });
94
79
  /**
95
- * Retrieve an invite by its document ID.
80
+ * Read an invite by identity one function, all-optional args, unioned
81
+ * return: `{ id }` (point lookup) or `{ tokenHash }` (token index).
96
82
  *
97
- * Performs a direct lookup in the `GroupInvite` table and returns the full
98
- * invite document, or `null` if no invite exists with the given ID.
99
- *
100
- * @param args.inviteId - The `Id<"GroupInvite">` of the invite to retrieve.
101
- * @returns The invite document (including `email`, `status`, `groupId`, `tokenHash`, etc.) or `null` if not found.
102
- *
103
- * @example
104
- * ```ts
105
- * const invite = await ctx.runQuery(components.auth.groups.inviteGet, {
106
- * inviteId: existingInviteId,
107
- * });
108
- * if (invite !== null) {
109
- * console.log(invite.email, invite.status);
110
- * }
111
- * ```
112
83
  */
113
84
  const inviteGet = query({
114
- args: { inviteId: v.id("GroupInvite") },
115
- returns: v.union(vGroupInviteDoc, v.null()),
116
- handler: async (ctx, { inviteId }) => {
117
- return await ctx.db.get("GroupInvite", inviteId);
118
- }
119
- });
120
- /**
121
- * Retrieve an invite by its hashed token.
122
- *
123
- * Looks up the `GroupInvite` table using the `token_hash` index. This is
124
- * the primary mechanism for resolving an invite from a URL-embedded token.
125
- * Returns `null` if no invite matches the given hash.
126
- *
127
- * @param args.tokenHash - The hashed token string to look up (must match the value stored at creation time).
128
- * @returns The invite document or `null` if no invite exists with the given token hash.
129
- *
130
- * @example
131
- * ```ts
132
- * const invite = await ctx.runQuery(
133
- * components.auth.groups.inviteGetByTokenHash,
134
- * { tokenHash: "sha256_abc123..." },
135
- * );
136
- * if (invite !== null && invite.status === "pending") {
137
- * // proceed with acceptance flow
138
- * }
139
- * ```
140
- */
141
- const inviteGetByTokenHash = query({
142
- args: { tokenHash: v.string() },
85
+ args: {
86
+ id: v.optional(v.id("GroupInvite")),
87
+ tokenHash: v.optional(v.string())
88
+ },
143
89
  returns: v.union(vGroupInviteDoc, v.null()),
144
- handler: async (ctx, { tokenHash }) => {
145
- return await ctx.db.query("GroupInvite").withIndex("token_hash", (q) => q.eq("tokenHash", tokenHash)).first();
90
+ handler: async (ctx, args) => {
91
+ if (args.tokenHash !== void 0) return await ctx.db.query("GroupInvite").withIndex("token_hash", (q) => q.eq("tokenHash", args.tokenHash)).first();
92
+ if (args.id === void 0) return null;
93
+ return await ctx.db.get("GroupInvite", args.id);
146
94
  }
147
95
  });
148
96
  /**
@@ -169,17 +117,6 @@ const inviteGetByTokenHash = query({
169
117
  * @param args.order - Sort direction: `"asc"` or `"desc"` (defaults to `"desc"`).
170
118
  * @returns An object `{ items, nextCursor }` where `items` is an array of invite documents and `nextCursor` is `null` when there are no more pages.
171
119
  *
172
- * @example
173
- * ```ts
174
- * const { items, nextCursor } = await ctx.runQuery(
175
- * components.auth.groups.inviteList,
176
- * {
177
- * where: { groupId: teamGroupId, status: "pending" },
178
- * limit: 25,
179
- * order: "desc",
180
- * },
181
- * );
182
- * ```
183
120
  */
184
121
  const inviteList = query({
185
122
  args: {
@@ -242,7 +179,7 @@ const inviteList = query({
242
179
  * automatically transitioned to `"expired"` and the acceptance is rejected.
243
180
  *
244
181
  * The caller is responsible for creating the corresponding member record
245
- * (see {@link inviteAcceptByToken} for an all-in-one alternative that also
182
+ * (see {@link inviteRedeem} for an all-in-one alternative that also
246
183
  * handles membership).
247
184
  *
248
185
  * @param args.inviteId - The `Id<"GroupInvite">` of the invite to accept.
@@ -252,13 +189,6 @@ const inviteList = query({
252
189
  * @throws `ConvexError` with code `INVITE_NOT_PENDING` if the invite has already been accepted, revoked, or otherwise finalized.
253
190
  * @throws `ConvexError` with code `INVITE_EXPIRED` if the invite's `expiresTime` has passed.
254
191
  *
255
- * @example
256
- * ```ts
257
- * await ctx.runMutation(components.auth.groups.inviteAccept, {
258
- * inviteId: pendingInviteId,
259
- * acceptedByUserId: currentUserId,
260
- * });
261
- * ```
262
192
  */
263
193
  const inviteAccept = mutation({
264
194
  args: {
@@ -321,26 +251,13 @@ const inviteAccept = mutation({
321
251
  * @throws `ConvexError` with code `INVITE_NOT_PENDING` if the invite has been revoked or is in another non-pending state.
322
252
  * @throws `ConvexError` with code `INVITE_EMAIL_MISMATCH` if the accepting user's email does not match the invite's email.
323
253
  *
324
- * @example
325
- * ```ts
326
- * const result = await ctx.runMutation(
327
- * components.auth.groups.inviteAcceptByToken,
328
- * {
329
- * tokenHash: "sha256_abc123...",
330
- * acceptedByUserId: currentUserId,
331
- * },
332
- * );
333
- * if (result.membershipStatus === "joined") {
334
- * console.log("Joined group", result.groupId, "as member", result.memberId);
335
- * }
336
- * ```
337
254
  */
338
- const inviteAcceptByToken = mutation({
255
+ const inviteRedeem = mutation({
339
256
  args: {
340
257
  tokenHash: v.string(),
341
258
  acceptedByUserId: v.id("User")
342
259
  },
343
- returns: vInviteAcceptByTokenResult,
260
+ returns: vInviteRedeemResult,
344
261
  handler: async (ctx, { tokenHash, acceptedByUserId }) => {
345
262
  const invite = await ctx.db.query("GroupInvite").withIndex("token_hash", (q) => q.eq("tokenHash", tokenHash)).first();
346
263
  if (invite === null) throw new ConvexError({
@@ -424,12 +341,6 @@ const inviteAcceptByToken = mutation({
424
341
  * @throws `ConvexError` with code `INVITE_NOT_FOUND` if the invite does not exist.
425
342
  * @throws `ConvexError` with code `INVITE_NOT_PENDING` if the invite has already been accepted, revoked, or expired.
426
343
  *
427
- * @example
428
- * ```ts
429
- * await ctx.runMutation(components.auth.groups.inviteRevoke, {
430
- * inviteId: pendingInviteId,
431
- * });
432
- * ```
433
344
  */
434
345
  const inviteRevoke = mutation({
435
346
  args: { inviteId: v.id("GroupInvite") },
@@ -453,5 +364,5 @@ const inviteRevoke = mutation({
453
364
  });
454
365
 
455
366
  //#endregion
456
- export { inviteAccept, inviteAcceptByToken, inviteCreate, inviteGet, inviteGetByTokenHash, inviteList, inviteRevoke };
367
+ export { inviteAccept, inviteCreate, inviteGet, inviteList, inviteRedeem, inviteRevoke };
457
368
  //# sourceMappingURL=invites.js.map
@@ -22,18 +22,6 @@ import { ConvexError, v } from "convex/values";
22
22
  * @returns The `Id<"GroupMember">` of the newly created member document.
23
23
  * @throws `ConvexError` with code `DUPLICATE_MEMBERSHIP` if the user is already a member of this group.
24
24
  *
25
- * @example
26
- * ```ts
27
- * const memberId = await ctx.runMutation(
28
- * components.auth.groups.memberAdd,
29
- * {
30
- * groupId: teamGroupId,
31
- * userId: newUserId,
32
- * roleIds: ["viewer"],
33
- * status: "active",
34
- * },
35
- * );
36
- * ```
37
25
  */
38
26
  const memberAdd = mutation({
39
27
  args: {
@@ -57,29 +45,33 @@ const memberAdd = mutation({
57
45
  }
58
46
  });
59
47
  /**
60
- * Retrieve a member record by its document ID.
61
- *
62
- * Performs a direct lookup in the `GroupMember` table and returns the full
63
- * member document, or `null` if no member exists with the given ID.
64
- *
65
- * @param args.memberId - The `Id<"GroupMember">` of the member record to retrieve.
66
- * @returns The member document (including `groupId`, `userId`, `roleIds`, `status`, etc.) or `null` if not found.
67
- *
68
- * @example
69
- * ```ts
70
- * const member = await ctx.runQuery(components.auth.groups.memberGet, {
71
- * memberId: existingMemberId,
72
- * });
73
- * if (member !== null) {
74
- * console.log(member.userId, member.roleIds);
75
- * }
76
- * ```
48
+ * Read a membership by identity. Accepts exactly one selector:
49
+ * - `{ id }` — point lookup → `Doc | null`.
50
+ * - `{ groupId, userId }` unique per group+user `Doc | null`.
51
+ * - `{ userId, groupIds }` batch: resolve this user's membership in
52
+ * each group, returning `(Doc | null)[]` aligned to `groupIds` order
53
+ * (duplicates de-duplicated internally).
77
54
  */
78
55
  const memberGet = query({
79
- args: { memberId: v.id("GroupMember") },
80
- returns: v.union(vGroupMemberDoc, v.null()),
81
- handler: async (ctx, { memberId }) => {
82
- return await ctx.db.get("GroupMember", memberId);
56
+ args: {
57
+ id: v.optional(v.id("GroupMember")),
58
+ groupId: v.optional(v.id("Group")),
59
+ userId: v.optional(v.id("User")),
60
+ groupIds: v.optional(v.array(v.id("Group")))
61
+ },
62
+ returns: v.union(vGroupMemberDoc, v.null(), v.array(v.union(vGroupMemberDoc, v.null()))),
63
+ handler: async (ctx, args) => {
64
+ if (args.userId !== void 0 && args.groupIds !== void 0) {
65
+ const groupIds = args.groupIds;
66
+ if (groupIds.length === 0) return [];
67
+ const unique = Array.from(new Set(groupIds));
68
+ const docs = await Promise.all(unique.map((groupId) => ctx.db.query("GroupMember").withIndex("group_id_user_id", (q) => q.eq("groupId", groupId).eq("userId", args.userId)).unique()));
69
+ const byGroupId = new Map(unique.map((id, i) => [id, docs[i] ?? null]));
70
+ return groupIds.map((id) => byGroupId.get(id) ?? null);
71
+ }
72
+ if (args.groupId !== void 0 && args.userId !== void 0) return await ctx.db.query("GroupMember").withIndex("group_id_user_id", (q) => q.eq("groupId", args.groupId).eq("userId", args.userId)).unique();
73
+ if (args.id === void 0) return null;
74
+ return await ctx.db.get("GroupMember", args.id);
83
75
  }
84
76
  });
85
77
  /**
@@ -102,17 +94,6 @@ const memberGet = query({
102
94
  * @param args.order - Sort direction: `"asc"` or `"desc"` (defaults to `"desc"`).
103
95
  * @returns An object `{ items, nextCursor }` where `items` is an array of member documents and `nextCursor` is `null` when there are no more pages.
104
96
  *
105
- * @example
106
- * ```ts
107
- * const { items, nextCursor } = await ctx.runQuery(
108
- * components.auth.groups.memberList,
109
- * {
110
- * where: { groupId: teamGroupId, status: "active" },
111
- * limit: 30,
112
- * order: "asc",
113
- * },
114
- * );
115
- * ```
116
97
  */
117
98
  const memberList = query({
118
99
  args: {
@@ -163,77 +144,6 @@ const memberList = query({
163
144
  }
164
145
  });
165
146
  /**
166
- * Look up a specific user's membership in a specific group.
167
- *
168
- * Uses the `group_id_user_id` compound index for an efficient exact-match
169
- * lookup. Returns `null` if the user is not a member of the group. Unlike
170
- * {@link memberResolve}, this does **not** walk the group hierarchy — it
171
- * checks only the specified group.
172
- *
173
- * @param args.groupId - The `Id<"Group">` of the group to check.
174
- * @param args.userId - The `Id<"User">` of the user whose membership to look up.
175
- * @returns The member document or `null` if the user is not a direct member of the group.
176
- *
177
- * @example
178
- * ```ts
179
- * const member = await ctx.runQuery(
180
- * components.auth.groups.memberGetByGroupAndUser,
181
- * { groupId: teamGroupId, userId: currentUserId },
182
- * );
183
- * if (member !== null) {
184
- * console.log("User has roles:", member.roleIds);
185
- * }
186
- * ```
187
- */
188
- const memberGetByGroupAndUser = query({
189
- args: {
190
- groupId: v.id("Group"),
191
- userId: v.id("User")
192
- },
193
- returns: v.union(vGroupMemberDoc, v.null()),
194
- handler: async (ctx, { groupId, userId }) => {
195
- return await ctx.db.query("GroupMember").withIndex("group_id_user_id", (q) => q.eq("groupId", groupId).eq("userId", userId)).unique();
196
- }
197
- });
198
- /**
199
- * Batched equivalent of {@link memberGetByGroupAndUser}. Resolves many
200
- * `(groupId, userId)` pairs for the same user in a single component
201
- * round-trip. Used by app-side handlers (e.g. `groups:getDashboard`) that
202
- * need to inspect the current user's membership across every root group
203
- * they can see.
204
- *
205
- * Each `groupId` is resolved using the `group_id_user_id` index; missing
206
- * memberships come back as `null` in the same slot order as the input.
207
- *
208
- * @param args.userId - The user whose memberships to look up.
209
- * @param args.groupIds - One or more groups to resolve. Order is preserved;
210
- * duplicates tolerated (but de-duplicated internally so the DB only sees
211
- * each pair once).
212
- * @returns Array of member documents or `null` entries, in `groupIds` order.
213
- *
214
- * @example
215
- * ```ts
216
- * const members = await ctx.runQuery(
217
- * components.auth.groups.memberGetByGroupAndUserMany,
218
- * { userId: viewerId, groupIds: rootGroupIds },
219
- * );
220
- * ```
221
- */
222
- const memberGetByGroupAndUserMany = query({
223
- args: {
224
- userId: v.id("User"),
225
- groupIds: v.array(v.id("Group"))
226
- },
227
- returns: v.array(v.union(vGroupMemberDoc, v.null())),
228
- handler: async (ctx, { userId, groupIds }) => {
229
- if (groupIds.length === 0) return [];
230
- const unique = Array.from(new Set(groupIds));
231
- const docs = await Promise.all(unique.map((groupId) => ctx.db.query("GroupMember").withIndex("group_id_user_id", (q) => q.eq("groupId", groupId).eq("userId", userId)).unique()));
232
- const byGroupId = new Map(unique.map((id, i) => [id, docs[i] ?? null]));
233
- return groupIds.map((id) => byGroupId.get(id) ?? null);
234
- }
235
- });
236
- /**
237
147
  * Resolve a user's membership by walking the group hierarchy from the
238
148
  * requested group up to the root. Returns the first matching membership
239
149
  * found, enabling inherited (ancestor-level) access checks.
@@ -261,23 +171,6 @@ const memberGetByGroupAndUserMany = query({
261
171
  * - `isInherited` — `true` when `depth > 0`.
262
172
  * - `traversedGroupIds` — (only when `ancestry` is `true`) array of group IDs visited.
263
173
  *
264
- * @example
265
- * ```ts
266
- * const result = await ctx.runQuery(
267
- * components.auth.groups.memberResolve,
268
- * {
269
- * userId: currentUserId,
270
- * groupId: subTeamGroupId,
271
- * maxDepth: 5,
272
- * ancestry: true,
273
- * },
274
- * );
275
- * if (result.membership !== null) {
276
- * console.log(
277
- * result.isDirect ? "Direct member" : `Inherited from depth ${result.depth}`,
278
- * );
279
- * }
280
- * ```
281
174
  */
282
175
  const memberResolve = query({
283
176
  args: {
@@ -339,12 +232,6 @@ const memberResolve = query({
339
232
  * @param args.memberId - The `Id<"GroupMember">` of the member record to delete.
340
233
  * @returns `null` on success.
341
234
  *
342
- * @example
343
- * ```ts
344
- * await ctx.runMutation(components.auth.groups.memberRemove, {
345
- * memberId: memberToRemoveId,
346
- * });
347
- * ```
348
235
  */
349
236
  const memberRemove = mutation({
350
237
  args: { memberId: v.id("GroupMember") },
@@ -365,16 +252,6 @@ const memberRemove = mutation({
365
252
  * @param args.data - A partial object of fields to patch. Supported keys include `roleIds`, `status`, and `extend`.
366
253
  * @returns `null` on success.
367
254
  *
368
- * @example
369
- * ```ts
370
- * await ctx.runMutation(components.auth.groups.memberUpdate, {
371
- * memberId: existingMemberId,
372
- * data: {
373
- * roleIds: ["admin", "editor"],
374
- * status: "active",
375
- * },
376
- * });
377
- * ```
378
255
  */
379
256
  const memberUpdate = mutation({
380
257
  args: {
@@ -389,5 +266,5 @@ const memberUpdate = mutation({
389
266
  });
390
267
 
391
268
  //#endregion
392
- export { memberAdd, memberGet, memberGetByGroupAndUser, memberGetByGroupAndUserMany, memberList, memberRemove, memberResolve, memberUpdate };
269
+ export { memberAdd, memberGet, memberList, memberRemove, memberResolve, memberUpdate };
393
270
  //# sourceMappingURL=members.js.map