(
target: {
readonly owner: Owner;
readonly name: ConnectionName;
readonly integration: IntegrationSlug;
readonly template: AuthTemplateSlug;
readonly identityLabel?: string | null;
},
client: LoadedOAuthClient,
token: OAuth2TokenResponse,
/** The scope set requested at /authorize + /token (the integration's
* declared or discovered scopes) — the recorded-scope fallback when the AS
* omits `scope`. */
requestedScopes: readonly string[],
/** The owner of `client` — persisted so refresh loads it by explicit owner. */
clientOwner: Owner,
/** Regional token endpoint override to persist when the code was redeemed
* off the client's configured host; null to use the client's token URL. */
oauthTokenUrl: string | null,
)
| 1412 | // connection row with OAuth lifecycle fields + produce its tools. |
| 1413 | // ----------------------------------------------------------------------- |
| 1414 | const mintFromToken = ( |
| 1415 | target: { |
| 1416 | readonly owner: Owner; |
| 1417 | readonly name: ConnectionName; |
| 1418 | readonly integration: IntegrationSlug; |
| 1419 | readonly template: AuthTemplateSlug; |
| 1420 | readonly identityLabel?: string | null; |
| 1421 | }, |
| 1422 | client: LoadedOAuthClient, |
| 1423 | token: OAuth2TokenResponse, |
| 1424 | /** The scope set requested at /authorize + /token (the integration's |
| 1425 | * declared or discovered scopes) — the recorded-scope fallback when the AS |
| 1426 | * omits `scope`. */ |
| 1427 | requestedScopes: readonly string[], |
| 1428 | /** The owner of `client` — persisted so refresh loads it by explicit owner. */ |
| 1429 | clientOwner: Owner, |
| 1430 | /** Regional token endpoint override to persist when the code was redeemed |
| 1431 | * off the client's configured host; null to use the client's token URL. */ |
| 1432 | oauthTokenUrl: string | null, |
| 1433 | ): Effect.Effect<Connection, StorageFailure> => |
| 1434 | Effect.gen(function* () { |
| 1435 | const provider = deps.defaultWritableProvider(); |
| 1436 | if (!provider || !provider.set) { |
| 1437 | return yield* new StorageError({ |
| 1438 | message: |
| 1439 | "No default writable credential provider is registered to store the OAuth access token.", |
| 1440 | cause: undefined, |
| 1441 | }); |
| 1442 | } |
| 1443 | const itemId = accessItemId(target.owner, target.integration, target.name); |
| 1444 | yield* provider.set(ProviderItemId.make(itemId), token.access_token); |
| 1445 | |
| 1446 | let refreshItemId: string | null = null; |
| 1447 | if (token.refresh_token) { |
| 1448 | refreshItemId = refreshItemIdFor(itemId); |
| 1449 | yield* provider.set(ProviderItemId.make(refreshItemId), token.refresh_token); |
| 1450 | } |
| 1451 | |
| 1452 | const oauthScope = recordedOAuthScope(token, requestedScopes); |
| 1453 | const missingScopes = |
| 1454 | client.grant === "authorization_code" |
| 1455 | ? missingGrantedOAuthScopes(requestedScopes, oauthScope) |
| 1456 | : []; |
| 1457 | // The freshness facts of this connection AT BIRTH, on the enclosing |
| 1458 | // span (executor.oauth.complete, or the reconnect path's request |
| 1459 | // envelope). Every "why did this connection later go stale" question |
| 1460 | // starts here: a partial grant fails later as oauth_scope_insufficient |
| 1461 | // in an unrelated trace; no refresh token means the first expiry is |
| 1462 | // terminal; no advertised expiry means only the reactive 401 path can |
| 1463 | // ever refresh it. Counts and booleans only — scope VALUES can encode |
| 1464 | // customer resource names on some providers. |
| 1465 | yield* Effect.annotateCurrentSpan({ |
| 1466 | "executor.oauth.scope_requested_count": requestedScopes.length, |
| 1467 | "executor.oauth.scope_missing_count": missingScopes.length, |
| 1468 | "executor.oauth.has_refresh_token": token.refresh_token !== undefined, |
| 1469 | "executor.oauth.has_advertised_expiry": typeof token.expires_in === "number", |
| 1470 | }); |
| 1471 | return yield* deps.mintOAuthConnection({ |
no test coverage detected