(
input: MintOAuthConnectionInput,
)
| 2914 | // by the OAuth service) + produce the connection's tools. Mirrors |
| 2915 | // `connectionsCreate`'s upsert + tool-production, stamping the OAuth columns. |
| 2916 | const mintOAuthConnection = ( |
| 2917 | input: MintOAuthConnectionInput, |
| 2918 | ): Effect.Effect<Connection, StorageFailure> => |
| 2919 | Effect.gen(function* () { |
| 2920 | const name = connectionIdentifier(String(input.name)); |
| 2921 | yield* requireUserSubject(input.owner); |
| 2922 | const integrationRow = yield* findIntegrationRow(input.integration); |
| 2923 | if (!integrationRow) { |
| 2924 | return yield* new StorageError({ |
| 2925 | message: `Integration not found: ${input.integration}`, |
| 2926 | cause: undefined, |
| 2927 | }); |
| 2928 | } |
| 2929 | const keys = yield* Effect.try({ |
| 2930 | try: () => ownedKeys(input.owner), |
| 2931 | catch: (cause) => storageFailureFromUnknown("invalid owner", cause), |
| 2932 | }); |
| 2933 | const now = new Date(); |
| 2934 | const ref: ConnectionRef = { |
| 2935 | owner: input.owner, |
| 2936 | integration: input.integration, |
| 2937 | name, |
| 2938 | }; |
| 2939 | // Label precedence: an explicit (user-chosen) label always wins; a |
| 2940 | // derived label (OIDC claims) only FILLS an empty slot. Like |
| 2941 | // `description` below, a reconnect or token refresh must not erase a |
| 2942 | // label the user curated. Resolved once, used by every write below. |
| 2943 | let identityLabel: string | null = null; |
| 2944 | yield* transaction( |
| 2945 | Effect.gen(function* () { |
| 2946 | const existing = yield* findConnectionRow(ref); |
| 2947 | const existingLabel = existing?.identity_label?.trim() ? existing.identity_label : null; |
| 2948 | identityLabel = |
| 2949 | input.identityLabel ?? existingLabel ?? input.derivedIdentityLabel ?? null; |
| 2950 | const set: Record<string, unknown> = { |
| 2951 | template: String(input.template), |
| 2952 | provider: input.provider, |
| 2953 | item_ids: { [PRIMARY_INPUT_VARIABLE]: input.itemId }, |
| 2954 | identity_label: identityLabel, |
| 2955 | oauth_client: String(input.oauthClient), |
| 2956 | oauth_client_owner: input.oauthClientOwner, |
| 2957 | refresh_item_id: input.refreshItemId, |
| 2958 | expires_at: input.expiresAt, |
| 2959 | oauth_scope: input.oauthScope, |
| 2960 | oauth_token_url: input.oauthTokenUrl ?? null, |
| 2961 | provider_state: |
| 2962 | input.missingOAuthScopes && input.missingOAuthScopes.length > 0 |
| 2963 | ? { missingOAuthScopes: input.missingOAuthScopes } |
| 2964 | : null, |
| 2965 | // A re-mint replaces the grant, so any persisted verdict describes |
| 2966 | // a credential that no longer exists. Clear it rather than let a |
| 2967 | // pre-reconnect "expired" outlive the reconnect; the next health |
| 2968 | // check writes the verdict for the new grant. |
| 2969 | last_health: null, |
| 2970 | updated_at: now, |
| 2971 | }; |
| 2972 | if (existing) { |
| 2973 | yield* core.updateMany("connection", { |
no test coverage detected