@robelest/convex-auth 0.0.4-preview.28 → 0.0.4-preview.29

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 (50) hide show
  1. package/dist/bin.js +4 -1
  2. package/dist/component/_generated/component.d.ts +344 -0
  3. package/dist/component/convex.config.d.ts +2 -2
  4. package/dist/component/model.d.ts +25 -25
  5. package/dist/component/model.js +1 -0
  6. package/dist/component/public/factors/totp.js +12 -0
  7. package/dist/component/public/groups/core.js +89 -1
  8. package/dist/component/public/groups/members.js +39 -1
  9. package/dist/component/public/identity/accounts.js +22 -3
  10. package/dist/component/public/identity/users.js +79 -5
  11. package/dist/component/public/sso/audit.js +5 -2
  12. package/dist/component/public/sso/core.js +3 -3
  13. package/dist/component/public/sso/domains.js +7 -3
  14. package/dist/component/public/sso/scim.js +36 -1
  15. package/dist/component/public.js +5 -5
  16. package/dist/component/schema.d.ts +293 -289
  17. package/dist/component/schema.js +2 -1
  18. package/dist/core/index.d.ts +63 -27
  19. package/dist/core/index.js +1 -0
  20. package/dist/providers/credentials.d.ts +12 -0
  21. package/dist/providers/password.js +28 -7
  22. package/dist/server/auth-context.d.ts +17 -0
  23. package/dist/server/auth-context.js +1 -1
  24. package/dist/server/auth.d.ts +18 -6
  25. package/dist/server/auth.js +1 -0
  26. package/dist/server/context.js +11 -5
  27. package/dist/server/contract.js +44 -12
  28. package/dist/server/core.js +146 -149
  29. package/dist/server/ctxCache.js +94 -0
  30. package/dist/server/limits.js +39 -9
  31. package/dist/server/mounts.d.ts +85 -85
  32. package/dist/server/mutations/credentialsSignIn.js +114 -0
  33. package/dist/server/mutations/index.js +2 -1
  34. package/dist/server/mutations/refresh.js +25 -12
  35. package/dist/server/mutations/retrieve.js +2 -1
  36. package/dist/server/mutations/signin.js +27 -9
  37. package/dist/server/mutations/store.js +5 -0
  38. package/dist/server/mutations/verify.js +32 -8
  39. package/dist/server/runtime.d.ts +81 -47
  40. package/dist/server/services/group.js +23 -16
  41. package/dist/server/sessions.d.ts +21 -0
  42. package/dist/server/sessions.js +37 -27
  43. package/dist/server/signin.js +32 -9
  44. package/dist/server/sso/domain.js +1 -2
  45. package/dist/server/sso/oidc.js +1 -1
  46. package/dist/server/tokens.js +19 -3
  47. package/dist/server/users.js +3 -2
  48. package/dist/server/utils/span.js +18 -0
  49. package/package.json +1 -1
  50. package/README.md +0 -161
@@ -19,6 +19,7 @@ var schema_default = defineSchema({
19
19
  phone: v.optional(v.string()),
20
20
  phoneVerificationTime: v.optional(v.number()),
21
21
  isAnonymous: v.optional(v.boolean()),
22
+ hasTotp: v.optional(v.boolean()),
22
23
  extend: v.optional(v.any())
23
24
  }).index("email", ["email"]).index("email_verified", ["email", "emailVerificationTime"]).index("phone", ["phone"]).index("phone_verified", ["phone", "phoneVerificationTime"]),
24
25
  Session: defineTable({
@@ -136,7 +137,7 @@ var schema_default = defineSchema({
136
137
  status: vGroupConnectionStatus,
137
138
  config: v.optional(v.any()),
138
139
  extend: v.optional(v.any())
139
- }).index("group_id", ["groupId"]).index("slug", ["slug"]).index("status", ["status"]),
140
+ }).index("group_id", ["groupId"]).index("slug", ["slug"]).index("status", ["status"]).index("group_id_status", ["groupId", "status"]).index("group_id_slug", ["groupId", "slug"]),
140
141
  GroupConnectionDomain: defineTable({
141
142
  connectionId: v.id("GroupConnection"),
142
143
  groupId: v.id("Group"),
@@ -1,5 +1,5 @@
1
1
  import { ComponentCtx, ComponentReadCtx } from "../server/componentContext.js";
2
- import { AuthAuthorizationConfig, ConvexAuthConfig, KeyDoc, KeyScope, ScopeChecker, UserOrderBy, UserWhere } from "../server/types.js";
2
+ import { AuthAuthorizationConfig, ConvexAuthConfig, Doc, KeyDoc, KeyScope, ScopeChecker, UserOrderBy, UserWhere } from "../server/types.js";
3
3
  import { AuthProfile } from "../server/payloads.js";
4
4
  import { AuthConfig, AuthContext, AuthContextConfig, AuthContextFactory as AuthContextFactory$1, AuthContextResolver as AuthContextResolver$1, OptionalAuthContext, UserDoc } from "../server/auth-context.js";
5
5
  import "../component/index.js";
@@ -37,7 +37,10 @@ declare function createAuthContext(component: ConvexAuthConfig["component"], con
37
37
  authorization?: AuthAuthorizationConfig;
38
38
  }): {
39
39
  user: {
40
- get: (ctx: ComponentReadCtx, userId: string) => Promise<any>;
40
+ get: {
41
+ (ctx: ComponentReadCtx, userId: string): Promise<Doc<"User"> | null>;
42
+ (ctx: ComponentReadCtx, userIds: readonly string[]): Promise<Array<Doc<"User"> | null>>;
43
+ };
41
44
  list: (ctx: ComponentReadCtx, opts?: {
42
45
  where?: UserWhere;
43
46
  limit?: number;
@@ -47,7 +50,7 @@ declare function createAuthContext(component: ConvexAuthConfig["component"], con
47
50
  }) => Promise<any>;
48
51
  viewer: (ctx: ComponentReadCtx & {
49
52
  auth: convex_server0.Auth;
50
- }) => Promise<any>;
53
+ }) => Promise<Doc<"User"> | null>;
51
54
  update: (ctx: ComponentCtx, userId: string, data: Record<string, unknown>) => Promise<{
52
55
  userId: string;
53
56
  }>;
@@ -74,6 +77,9 @@ declare function createAuthContext(component: ConvexAuthConfig["component"], con
74
77
  current: (ctx: {
75
78
  auth: convex_server0.Auth;
76
79
  }) => Promise<convex_values0.GenericId<"Session"> | null>;
80
+ userId: (ctx: {
81
+ auth: convex_server0.Auth;
82
+ }) => Promise<convex_values0.GenericId<"User"> | null>;
77
83
  invalidate: <DataModel extends fluent_convex0.GenericDataModel>(ctx: convex_server0.GenericActionCtx<DataModel>, args: {
78
84
  userId: convex_values0.GenericId<"User">;
79
85
  except?: convex_values0.GenericId<"Session">[];
@@ -160,7 +166,10 @@ declare function createAuthContext(component: ConvexAuthConfig["component"], con
160
166
  }) => Promise<{
161
167
  groupId: string;
162
168
  }>;
163
- get: (ctx: ComponentReadCtx, groupId: string) => Promise<any>;
169
+ get: {
170
+ (ctx: ComponentReadCtx, groupId: string): Promise<Doc<"Group"> | null>;
171
+ (ctx: ComponentReadCtx, groupIds: readonly string[]): Promise<Array<Doc<"Group"> | null>>;
172
+ };
164
173
  list: (ctx: ComponentReadCtx, opts?: {
165
174
  where?: {
166
175
  slug?: string;
@@ -193,7 +202,7 @@ declare function createAuthContext(component: ConvexAuthConfig["component"], con
193
202
  maxDepth?: number;
194
203
  includeSelf?: boolean;
195
204
  }) => Promise<{
196
- ancestors: Record<string, unknown>[];
205
+ ancestors: Array<Exclude<Doc<"Group"> | null, null>>;
197
206
  cycleDetected: boolean;
198
207
  maxDepthReached: boolean;
199
208
  }>;
@@ -227,29 +236,44 @@ declare function createAuthContext(component: ConvexAuthConfig["component"], con
227
236
  update: (ctx: ComponentCtx, memberId: string, data: Record<string, unknown>) => Promise<{
228
237
  memberId: string;
229
238
  }>;
230
- inspect: (ctx: ComponentReadCtx, opts: {
231
- userId: string;
232
- groupId: string;
233
- ancestry?: boolean;
234
- maxDepth?: number;
235
- }) => Promise<{
236
- membership: null;
237
- roleIds: string[];
238
- grants: string[];
239
- } | {
240
- membership: {
241
- _id: string;
242
- _creationTime: number;
239
+ inspect: {
240
+ (ctx: ComponentReadCtx, opts: {
241
+ userId: string;
243
242
  groupId: string;
243
+ ancestry?: boolean;
244
+ maxDepth?: number;
245
+ }): Promise<{
246
+ membership: {
247
+ _id: string;
248
+ _creationTime: number;
249
+ groupId: string;
250
+ userId: string;
251
+ role?: string;
252
+ roleIds?: string[];
253
+ status?: string;
254
+ extend?: Record<string, unknown>;
255
+ } | null;
256
+ roleIds: string[];
257
+ grants: string[];
258
+ }>;
259
+ (ctx: ComponentReadCtx, opts: {
244
260
  userId: string;
245
- role?: string;
246
- roleIds?: string[];
247
- status?: string;
248
- extend?: Record<string, unknown>;
249
- };
250
- roleIds: string[];
251
- grants: string[];
252
- }>;
261
+ groupIds: readonly string[];
262
+ }): Promise<Array<{
263
+ membership: {
264
+ _id: string;
265
+ _creationTime: number;
266
+ groupId: string;
267
+ userId: string;
268
+ role?: string;
269
+ roleIds?: string[];
270
+ status?: string;
271
+ extend?: Record<string, unknown>;
272
+ } | null;
273
+ roleIds: string[];
274
+ grants: string[];
275
+ }>>;
276
+ };
253
277
  require: (ctx: ComponentReadCtx, opts: {
254
278
  userId: string;
255
279
  groupId: string;
@@ -267,7 +291,7 @@ declare function createAuthContext(component: ConvexAuthConfig["component"], con
267
291
  roleIds?: string[];
268
292
  status?: string;
269
293
  extend?: Record<string, unknown>;
270
- };
294
+ } | null;
271
295
  roleIds: string[];
272
296
  grants: string[];
273
297
  }>;
@@ -372,6 +396,18 @@ declare function createAuthContext(component: ConvexAuthConfig["component"], con
372
396
  secret: string;
373
397
  }>;
374
398
  };
399
+ /**
400
+ * Zero-round-trip helper: parse the current user id straight from the
401
+ * JWT subject claim. No component read, no cache lookup — just the
402
+ * identity token. Returns `null` when unauthenticated.
403
+ *
404
+ * Prefer this over `auth.context(ctx)` when you only need the user's
405
+ * id (e.g. early guards, routing decisions, audit logging). For the
406
+ * full user document, follow up with `auth.user.get(ctx, userId)`.
407
+ */
408
+ getUserId: (ctx: {
409
+ auth: convex_server0.Auth;
410
+ }) => Promise<convex_values0.GenericId<"User"> | null>;
375
411
  context: AuthContextResolver;
376
412
  ctx: AuthContextFactory;
377
413
  };
@@ -70,6 +70,7 @@ function createAuthContext(component, config) {
70
70
  member: domains.member,
71
71
  invite: domains.invite,
72
72
  key: domains.key,
73
+ getUserId: domains.session.userId,
73
74
  context: ((ctx, config$1) => {
74
75
  assertAuthResolverContext(ctx);
75
76
  return createPublicAuthContext(authLike, ctx, config$1);
@@ -1,3 +1,4 @@
1
+ import { SessionIssuance } from "../server/sessions.js";
1
2
  import { AuthProviderConfig, ConvexCredentialsConfig, GenericActionCtxWithAuthConfig } from "../server/types.js";
2
3
  import { GenericId, Value } from "convex/values";
3
4
  import { GenericDataModel } from "convex/server";
@@ -14,6 +15,17 @@ interface CredentialsConfig<DataModel extends GenericDataModel = GenericDataMode
14
15
  authorize: (credentials: Partial<Record<string, Value | undefined>>, ctx: GenericActionCtxWithAuthConfig<DataModel>) => Promise<{
15
16
  userId: GenericId<"User">;
16
17
  sessionId?: GenericId<"Session">;
18
+ /**
19
+ * TOTP step-up hint. `false` skips the `totpGetVerifiedByUserId` query;
20
+ * `true`/`undefined` falls back to it.
21
+ */
22
+ hasTotp?: boolean;
23
+ /**
24
+ * Pre-issued session from a combined verify+issue mutation. When set,
25
+ * the framework skips the second `callSignIn` mutation and finalizes
26
+ * the issuance directly on the action side.
27
+ */
28
+ issuance?: SessionIssuance;
17
29
  } | null>;
18
30
  /** Optional hashing helpers for password-style credential verification. */
19
31
  crypto?: {
@@ -1,4 +1,6 @@
1
+ import { callCredentialsSignIn } from "../server/mutations/credentialsSignIn.js";
1
2
  import { credentials } from "./credentials.js";
3
+ import { ConvexError } from "convex/values";
2
4
  import { scryptAsync } from "@noble/hashes/scrypt.js";
3
5
  import { bytesToHex } from "@noble/hashes/utils.js";
4
6
 
@@ -92,7 +94,11 @@ function password(config = {}) {
92
94
  accountId: account._id,
93
95
  params
94
96
  });
95
- return { userId: user._id };
97
+ const hasTotp = user.hasTotp;
98
+ return {
99
+ userId: user._id,
100
+ hasTotp
101
+ };
96
102
  };
97
103
  if (flowDispatch.tag === "signUp") {
98
104
  const secret = requirePasswordParam(params.password, "signUp");
@@ -108,16 +114,31 @@ function password(config = {}) {
108
114
  });
109
115
  return await finalizeCredentialsResult(created.account, created.user);
110
116
  } else if (flowDispatch.tag === "signIn") {
111
- const secret = requirePasswordParam(params.password, "signIn");
112
- const retrieved = await ctx.auth.account.get(ctx, {
117
+ const result = await callCredentialsSignIn(ctx, {
113
118
  provider,
114
119
  account: {
115
120
  id: email,
116
- secret
117
- }
121
+ secret: requirePasswordParam(params.password, "signIn")
122
+ },
123
+ generateTokens: true,
124
+ requireVerifiedEmail: verifyProvider !== void 0,
125
+ enforceTotp: true
118
126
  });
119
- if (retrieved === null) throw new Error("Invalid credentials");
120
- return await finalizeCredentialsResult(retrieved.account, retrieved.user);
127
+ if (result.kind === "invalidAccount" || result.kind === "invalidSecret") throw new Error("Invalid credentials");
128
+ if (result.kind === "tooManyAttempts") throw new ConvexError({
129
+ code: "RATE_LIMITED",
130
+ message: "Too many failed sign-in attempts. Please try again later."
131
+ });
132
+ if (result.kind === "emailVerificationRequired") return await ctx.auth.provider.signIn(ctx, verifyProvider, {
133
+ accountId: result.account._id,
134
+ params
135
+ });
136
+ const hasTotp = result.kind === "signedIn" ? result.user.hasTotp : true;
137
+ return {
138
+ userId: result.user._id,
139
+ hasTotp,
140
+ issuance: result.issuance
141
+ };
121
142
  } else if (flowDispatch.tag === "reset") {
122
143
  if (!resetProvider) throw new Error(`Password reset is not enabled for ${provider}`);
123
144
  const { account } = await ctx.auth.account.get(ctx, {
@@ -139,6 +139,23 @@ type AuthContextConfig<TResolve extends Record<string, unknown> = Record<string,
139
139
  * of throwing `NOT_SIGNED_IN`.
140
140
  */
141
141
  optional?: boolean;
142
+ /**
143
+ * Resolve the active group + membership when building `ctx.auth`.
144
+ *
145
+ * The full resolver fires three sequential component reads on every
146
+ * call: `user.get`, `user.getActiveGroup`, `member.inspect`. For
147
+ * handlers that only need `userId` / `user` (e.g. `account:listPasskeys`,
148
+ * profile reads, self-service settings), the second and third reads are
149
+ * pure overhead.
150
+ *
151
+ * Set `group: false` to skip them. `ctx.auth.groupId`, `ctx.auth.role`,
152
+ * and `ctx.auth.grants` come back as `null` / `[]` without any
153
+ * component round-trip. Saves ~10–30ms per auth-gated call on the
154
+ * lightweight path.
155
+ *
156
+ * @defaultValue true
157
+ */
158
+ group?: boolean;
142
159
  /**
143
160
  * Attach additional derived fields to the auth context after the base auth
144
161
  * context is resolved.
@@ -3,7 +3,7 @@ import { ConvexError } from "convex/values";
3
3
 
4
4
  //#region src/server/auth-context.ts
5
5
  async function resolveConfiguredAuthContext(auth, ctx, config) {
6
- const fallback = () => getAuthContext(auth, ctx);
6
+ const fallback = () => getAuthContext(auth, ctx, { group: config?.group !== false });
7
7
  const authOverride = config?.authResolve ? await config.authResolve(ctx, fallback) : void 0;
8
8
  return authOverride === void 0 ? await fallback() : authOverride;
9
9
  }
@@ -34,12 +34,7 @@ type MemberApiWithAuthorization<TAuthorization extends AuthAuthorizationConfig |
34
34
  }) => Promise<{
35
35
  memberId: string;
36
36
  }>;
37
- inspect: (ctx: Parameters<ReturnType<typeof Auth>["auth"]["member"]["inspect"]>[0], opts: {
38
- userId: string;
39
- groupId: string;
40
- ancestry?: boolean;
41
- maxDepth?: number;
42
- }) => ReturnType<ReturnType<typeof Auth>["auth"]["member"]["inspect"]>;
37
+ inspect: ReturnType<typeof Auth>["auth"]["member"]["inspect"];
43
38
  require: (ctx: Parameters<ReturnType<typeof Auth>["auth"]["member"]["require"]>[0], opts: {
44
39
  userId: string;
45
40
  groupId: string;
@@ -77,6 +72,23 @@ type AuthApiBase<TAuthorization extends AuthAuthorizationConfig | undefined = un
77
72
  invite: ReturnType<typeof Auth>["auth"]["invite"];
78
73
  key: ReturnType<typeof Auth>["auth"]["key"];
79
74
  http: ReturnType<typeof Auth>["auth"]["http"];
75
+ /**
76
+ * Zero-round-trip helper: parse the current user id from the JWT subject
77
+ * claim. No component read, no cache lookup. Returns `null` when
78
+ * unauthenticated.
79
+ *
80
+ * Prefer this over `auth.context(ctx)` when a handler only needs the
81
+ * user's id (audit logging, early guards, routing). For the full user
82
+ * document, follow up with `auth.user.get(ctx, userId)` — cached per
83
+ * request.
84
+ *
85
+ * @example
86
+ * ```ts
87
+ * const userId = await auth.getUserId(ctx);
88
+ * if (!userId) throw new Error("Not signed in");
89
+ * ```
90
+ */
91
+ getUserId: ReturnType<typeof Auth>["auth"]["session"]["userId"];
80
92
  /**
81
93
  * Resolve the current request's auth context. Framework-agnostic — use
82
94
  * this in fluent-convex middleware, custom wrappers, or anywhere you
@@ -145,6 +145,7 @@ function createAuth(component, config) {
145
145
  invite: authResult.auth.invite,
146
146
  key: authResult.auth.key,
147
147
  http: authResult.auth.http,
148
+ getUserId: authResult.auth.session.userId,
148
149
  context: ((ctx, config$1) => {
149
150
  assertAuthResolverContext(ctx);
150
151
  return createPublicAuthContext(authResult.auth, ctx, config$1);
@@ -8,9 +8,15 @@ async function getSessionUserId(ctx) {
8
8
  return userIdFromIdentitySubject(identity.subject);
9
9
  }
10
10
  /** @internal */
11
- async function getAuthContextForUser(auth, ctx, userId) {
12
- const user = await auth.user.get(ctx, userId);
13
- const groupId = await auth.user.getActiveGroup(ctx, { userId });
11
+ async function getAuthContextForUser(auth, ctx, userId, opts) {
12
+ if (opts?.group === false) return {
13
+ userId,
14
+ user: await auth.user.get(ctx, userId),
15
+ groupId: null,
16
+ role: null,
17
+ grants: []
18
+ };
19
+ const [user, groupId] = await Promise.all([auth.user.get(ctx, userId), auth.user.getActiveGroup(ctx, { userId })]);
14
20
  let role = null;
15
21
  let grants = [];
16
22
  if (groupId) {
@@ -32,10 +38,10 @@ async function getAuthContextForUser(auth, ctx, userId) {
32
38
  };
33
39
  }
34
40
  /** @internal */
35
- async function getAuthContext(auth, ctx) {
41
+ async function getAuthContext(auth, ctx, opts) {
36
42
  const userId = await getSessionUserId(ctx);
37
43
  if (userId === null) return null;
38
- return await getAuthContextForUser(auth, ctx, userId);
44
+ return await getAuthContextForUser(auth, ctx, userId, opts);
39
45
  }
40
46
  /** @internal */
41
47
  function createUnauthenticatedAuthContext() {
@@ -1,25 +1,57 @@
1
+ import { cached, invalidateCtxCache } from "./ctxCache.js";
2
+
1
3
  //#region src/server/contract.ts
2
4
  const query = (ctx, ref, args) => ctx.runQuery(ref, args);
3
5
  const mutate = (ctx, ref, args) => ctx.runMutation(ref, args);
4
- const getGroupConnection = (ctx, componentPublic, connectionId) => query(ctx, componentPublic.groupConnectionGet, { connectionId });
5
- const getGroupConnectionByDomain = (ctx, componentPublic, domain) => query(ctx, componentPublic.groupConnectionGetByDomain, { domain });
6
+ const getGroupConnection = (ctx, componentPublic, connectionId) => cached(ctx, `group-connection:${connectionId}`, () => query(ctx, componentPublic.groupConnectionGet, { connectionId }));
7
+ const getGroupConnectionByDomain = (ctx, componentPublic, domain) => cached(ctx, `group-connection-domain:${domain}`, () => query(ctx, componentPublic.groupConnectionGetByDomain, { domain }));
6
8
  const listGroupConnections = (ctx, componentPublic, args) => query(ctx, componentPublic.groupConnectionList, args);
7
9
  const createGroupConnection = (ctx, componentPublic, args) => mutate(ctx, componentPublic.groupConnectionCreate, args);
8
- const updateGroupConnection = (ctx, componentPublic, args) => mutate(ctx, componentPublic.groupConnectionUpdate, args);
9
- const deleteGroupConnection = (ctx, componentPublic, connectionId) => mutate(ctx, componentPublic.groupConnectionDelete, { connectionId });
10
- const getGroup = (ctx, componentPublic, groupId) => query(ctx, componentPublic.groupGet, { groupId });
11
- const listConnectionDomains = (ctx, componentPublic, connectionId) => query(ctx, componentPublic.groupConnectionDomainList, { connectionId });
12
- const addConnectionDomain = (ctx, componentPublic, args) => mutate(ctx, componentPublic.groupConnectionDomainAdd, args);
13
- const deleteConnectionDomain = (ctx, componentPublic, domainId) => mutate(ctx, componentPublic.groupConnectionDomainDelete, { domainId });
14
- const getScimConfigByConnection = (ctx, componentPublic, connectionId) => query(ctx, componentPublic.groupConnectionScimConfigGetByGroupConnection, { connectionId });
10
+ const updateGroupConnection = async (ctx, componentPublic, args) => {
11
+ const result = await mutate(ctx, componentPublic.groupConnectionUpdate, args);
12
+ invalidateCtxCache(ctx, `group-connection:${args.connectionId}`);
13
+ invalidateCtxCache(ctx, "group-connection-domain");
14
+ return result;
15
+ };
16
+ const deleteGroupConnection = async (ctx, componentPublic, connectionId) => {
17
+ const result = await mutate(ctx, componentPublic.groupConnectionDelete, { connectionId });
18
+ invalidateCtxCache(ctx, `group-connection:${connectionId}`);
19
+ invalidateCtxCache(ctx, "group-connection-domain");
20
+ invalidateCtxCache(ctx, `connection-domains:${connectionId}`);
21
+ invalidateCtxCache(ctx, "group-connection-secret");
22
+ return result;
23
+ };
24
+ const getGroup = (ctx, componentPublic, groupId) => cached(ctx, `group-record:${groupId}`, () => query(ctx, componentPublic.groupGet, { groupId }));
25
+ const listConnectionDomains = (ctx, componentPublic, connectionId) => cached(ctx, `connection-domains:${connectionId}`, () => query(ctx, componentPublic.groupConnectionDomainList, { connectionId }));
26
+ const addConnectionDomain = async (ctx, componentPublic, args) => {
27
+ const result = await mutate(ctx, componentPublic.groupConnectionDomainAdd, args);
28
+ invalidateCtxCache(ctx, `connection-domains:${args.connectionId}`);
29
+ invalidateCtxCache(ctx, "group-connection-domain");
30
+ return result;
31
+ };
32
+ const deleteConnectionDomain = async (ctx, componentPublic, domainId) => {
33
+ const result = await mutate(ctx, componentPublic.groupConnectionDomainDelete, { domainId });
34
+ invalidateCtxCache(ctx, "connection-domains");
35
+ invalidateCtxCache(ctx, "group-connection-domain");
36
+ return result;
37
+ };
38
+ const getScimConfigByConnection = (ctx, componentPublic, connectionId) => cached(ctx, `scim-config-by-connection:${connectionId}`, () => query(ctx, componentPublic.groupConnectionScimConfigGetByGroupConnection, { connectionId }));
15
39
  const getScimConfigByTokenHash = (ctx, componentPublic, tokenHash) => query(ctx, componentPublic.groupConnectionScimConfigGetByTokenHash, { tokenHash });
16
- const upsertScimConfig = (ctx, componentPublic, args) => mutate(ctx, componentPublic.groupConnectionScimConfigUpsert, args);
40
+ const upsertScimConfig = async (ctx, componentPublic, args) => {
41
+ const result = await mutate(ctx, componentPublic.groupConnectionScimConfigUpsert, args);
42
+ invalidateCtxCache(ctx, `scim-config-by-connection:${args.connectionId}`);
43
+ return result;
44
+ };
17
45
  const getConnectionDomainVerification = (ctx, componentPublic, domainId) => query(ctx, componentPublic.groupConnectionDomainVerificationGet, { domainId });
18
46
  const upsertConnectionDomainVerification = (ctx, componentPublic, args) => mutate(ctx, componentPublic.groupConnectionDomainVerificationUpsert, args);
19
47
  const deleteConnectionDomainVerification = (ctx, componentPublic, domainId) => mutate(ctx, componentPublic.groupConnectionDomainVerificationDelete, { domainId });
20
48
  const verifyConnectionDomain = (ctx, componentPublic, args) => mutate(ctx, componentPublic.groupConnectionDomainVerify, args);
21
- const getGroupConnectionSecret = (ctx, componentPublic, args) => query(ctx, componentPublic.groupConnectionSecretGet, args);
22
- const upsertGroupConnectionSecret = (ctx, componentPublic, args) => mutate(ctx, componentPublic.groupConnectionSecretUpsert, args);
49
+ const getGroupConnectionSecret = (ctx, componentPublic, args) => cached(ctx, `group-connection-secret:${args.connectionId}:${args.kind}`, () => query(ctx, componentPublic.groupConnectionSecretGet, args));
50
+ const upsertGroupConnectionSecret = async (ctx, componentPublic, args) => {
51
+ const result = await mutate(ctx, componentPublic.groupConnectionSecretUpsert, args);
52
+ invalidateCtxCache(ctx, `group-connection-secret:${args.connectionId}:${args.kind}`);
53
+ return result;
54
+ };
23
55
  const listWebhookEndpoints = (ctx, componentPublic, connectionId) => query(ctx, componentPublic.groupWebhookEndpointList, { connectionId });
24
56
  const listWebhookDeliveries = (ctx, componentPublic, args) => query(ctx, componentPublic["groupWebhookDeliveryList"], args);
25
57
  const listScimIdentitiesByConnection = (ctx, componentPublic, connectionId) => query(ctx, componentPublic.groupConnectionScimIdentityListByGroupConnection, { connectionId });