@vercel/connect 0.2.4 → 0.2.5

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.
@@ -35,6 +35,23 @@ import type { ConnectOptions, ConnectTokenParams, ConnectTokenSubject } from '..
35
35
  * so consumers can branch their error translation per callback.
36
36
  */
37
37
  export type ConnectAuthorizationPhase = 'getToken' | 'startAuthorization' | 'completeAuthorization';
38
+ /**
39
+ * Eve's per-connection authorization context — the `connection` argument
40
+ * Eve's runtime hands to every `getToken` / `startAuthorization` /
41
+ * `completeAuthorization` callback alongside the resolved principal.
42
+ * Currently carries the connection's declared server `url`; Eve documents
43
+ * the shape as strictly additive, so destructuring only the fields you
44
+ * need stays forward-compatible.
45
+ *
46
+ * eve 0.6.0-beta.1 declares this type as
47
+ * `ConnectionAuthorizationContext` but does not re-export it from
48
+ * `eve/connections` (or any other public subpath), so it is derived
49
+ * structurally here from the exported authorization definition — this is
50
+ * exactly the type Eve passes at runtime. Once eve exports the type
51
+ * directly, this alias can switch to a plain re-export without a
52
+ * breaking change.
53
+ */
54
+ export type EveConnectionAuthorizationContext = Parameters<NonInteractiveAuthorizationDefinition['getToken']>[0]['connection'];
38
55
  /** Options accepted by {@link connect}. */
39
56
  export interface EveAuthorizationOptions {
40
57
  /**
@@ -69,11 +86,27 @@ export interface EveAuthorizationOptions {
69
86
  * `authorizationDetails`. Passed through verbatim.
70
87
  */
71
88
  readonly tokenParams?: Omit<ConnectTokenParams, 'subject'>;
89
+ /**
90
+ * Builds the Vercel Connect token subject from the framework-resolved
91
+ * principal plus the connection's authorization context (Eve's
92
+ * per-connection metadata — currently the declared server `url`).
93
+ *
94
+ * Needed by jwt-bearer-style connectors whose subject/assertion
95
+ * depends on more than the principal: custom claims, the connection
96
+ * URL, or an audience derived from it. Takes precedence over the
97
+ * deprecated {@link principalToSubject}. When neither is set, the
98
+ * default mapping applies — app principals map to `{ type: "app" }`
99
+ * and user principals map to `{ type: "user", id, issuer }`.
100
+ */
101
+ readonly createSubject?: (principal: ConnectionPrincipal, ctx: EveConnectionAuthorizationContext) => ConnectTokenSubject | Promise<ConnectTokenSubject>;
72
102
  /**
73
103
  * Override how Eve's framework-resolved principal is mapped to a
74
104
  * Vercel Connect token subject. When omitted, app principals map to
75
105
  * `{ type: "app" }` and user principals map to
76
106
  * `{ type: "user", id, issuer }`.
107
+ *
108
+ * @deprecated Use {@link createSubject}, which also receives the
109
+ * connection authorization context.
77
110
  */
78
111
  readonly principalToSubject?: (principal: ConnectionPrincipal) => ConnectTokenSubject | Promise<ConnectTokenSubject>;
79
112
  /**
@@ -173,9 +206,17 @@ export type EveConnectAuthorizationDefinition<TAuthorization extends Interactive
173
206
  * failed or duplicate revoke is swallowed so it never masks the error
174
207
  * that triggered eviction, and the local cache entry is dropped either
175
208
  * way.
209
+ *
210
+ * Pass `connection` (Eve's per-connection authorization context) when
211
+ * the connection uses {@link EveAuthorizationOptions.createSubject}:
212
+ * the cache entry is keyed by the resolved subject, and a
213
+ * context-dependent subject can only be reproduced with the context in
214
+ * hand. Without it, eviction falls back to the legacy
215
+ * principal-only mapping and may miss the entry.
176
216
  */
177
217
  readonly evict: (opts: {
178
218
  readonly principal: ConnectionPrincipal;
219
+ readonly connection?: EveConnectionAuthorizationContext;
179
220
  readonly revoke?: boolean;
180
221
  }) => Promise<void>;
181
222
  };
@@ -51,8 +51,8 @@ export function connect(input) {
51
51
  * local cache drop if the revoke request fails).
52
52
  */
53
53
  function makeEvict(options) {
54
- return async ({ principal, revoke }) => {
55
- const params = await buildTokenParams(options, principal);
54
+ return async ({ principal, connection, revoke }) => {
55
+ const params = await buildTokenParams(options, principal, connection);
56
56
  if (revoke) {
57
57
  try {
58
58
  // Destructive: tears down the grant at Vercel Connect (refresh
@@ -82,16 +82,16 @@ function normalizeAuthorizationOptions(input) {
82
82
  function buildInteractiveDefinition(options) {
83
83
  return {
84
84
  principalType: 'user',
85
- async getToken({ principal }) {
85
+ async getToken({ principal, connection, }) {
86
86
  try {
87
- const response = await getTokenResponse(options.connector, await buildTokenParams(options, principal), getTokenConnectOptions(options));
87
+ const response = await getTokenResponse(options.connector, await buildTokenParams(options, principal, connection), getTokenConnectOptions(options));
88
88
  return { token: response.token, expiresAt: response.expiresAt };
89
89
  }
90
90
  catch (error) {
91
91
  throw translate(error, 'getToken', options);
92
92
  }
93
93
  },
94
- async startAuthorization({ principal, callbackUrl, webhook, }) {
94
+ async startAuthorization({ principal, connection, callbackUrl, webhook, }) {
95
95
  try {
96
96
  // Eve's `webhook` parameter is semantically a browser-redirect
97
97
  // target — the orchestrator mints it via `createWebhook({
@@ -114,7 +114,7 @@ function buildInteractiveDefinition(options) {
114
114
  // mints one URL, Vercel Connect redirects there" mental
115
115
  // model. Revisit if tab-close timeouts become a real problem
116
116
  // in production.
117
- const response = await startAuthorization(options.connector, await buildTokenParams(options, principal), {
117
+ const response = await startAuthorization(options.connector, await buildTokenParams(options, principal, connection), {
118
118
  ...options.connectOptions,
119
119
  callbackUrl: callbackUrl ?? webhook,
120
120
  deviceCode: true,
@@ -136,9 +136,9 @@ function buildInteractiveDefinition(options) {
136
136
  throw translate(error, 'startAuthorization', options);
137
137
  }
138
138
  },
139
- async completeAuthorization({ principal, }) {
139
+ async completeAuthorization({ principal, connection, }) {
140
140
  try {
141
- const response = await getTokenResponse(options.connector, await buildTokenParams(options, principal), options.connectOptions);
141
+ const response = await getTokenResponse(options.connector, await buildTokenParams(options, principal, connection), options.connectOptions);
142
142
  return { token: response.token, expiresAt: response.expiresAt };
143
143
  }
144
144
  catch (error) {
@@ -150,9 +150,9 @@ function buildInteractiveDefinition(options) {
150
150
  function buildNonInteractiveDefinition(options) {
151
151
  return {
152
152
  principalType: 'app',
153
- async getToken({ principal }) {
153
+ async getToken({ principal, connection, }) {
154
154
  try {
155
- const response = await getTokenResponse(options.connector, await buildTokenParams(options, principal), getTokenConnectOptions(options));
155
+ const response = await getTokenResponse(options.connector, await buildTokenParams(options, principal, connection), getTokenConnectOptions(options));
156
156
  return { token: response.token, expiresAt: response.expiresAt };
157
157
  }
158
158
  catch (error) {
@@ -173,13 +173,35 @@ function getTokenConnectOptions(options) {
173
173
  }
174
174
  return { ...options.connectOptions, forceRefresh: true };
175
175
  }
176
- async function buildTokenParams(options, principal) {
177
- const toSubject = options.principalToSubject ?? principalToSubject;
176
+ async function buildTokenParams(options, principal, connection) {
178
177
  return {
179
178
  ...options.tokenParams,
180
- subject: await toSubject(principal),
179
+ subject: await resolveSubject(options, principal, connection),
181
180
  };
182
181
  }
182
+ /**
183
+ * Resolves the Vercel Connect token subject for `principal`, applying
184
+ * the documented precedence: {@link EveAuthorizationOptions.createSubject}
185
+ * (when the connection context is in hand), then the deprecated
186
+ * {@link EveAuthorizationOptions.principalToSubject}, then the default
187
+ * principal mapping.
188
+ *
189
+ * Eve's runtime passes `connection` to every authorization callback, so
190
+ * on the `getToken` / `startAuthorization` / `completeAuthorization`
191
+ * paths `createSubject` always receives it. Only the adapter's own
192
+ * `evict` entry point may run without a context (its callers predate the
193
+ * context plumbing); in that case the resolution falls back past
194
+ * `createSubject` so eviction stays best-effort instead of throwing.
195
+ */
196
+ function resolveSubject(options, principal, connection) {
197
+ if (options.createSubject !== undefined && connection !== undefined) {
198
+ return options.createSubject(principal, connection);
199
+ }
200
+ if (options.principalToSubject !== undefined) {
201
+ return options.principalToSubject(principal);
202
+ }
203
+ return principalToSubject(principal);
204
+ }
183
205
  function principalToSubject(principal) {
184
206
  if (principal.type === 'app') {
185
207
  return { type: 'app' };
@@ -6,7 +6,7 @@
6
6
  * this barrel re-exports the public API so consumers import
7
7
  * everything from `@vercel/connect/eve`.
8
8
  */
9
- export { connect, type EveAuthorizationInput, type EveAuthorizationOptions, type EveConnectAuthorizationDefinition, type ConnectAuthorizationPhase, type VercelConnectMetadata, } from './connection-authorization.js';
9
+ export { connect, type EveAuthorizationInput, type EveAuthorizationOptions, type EveConnectAuthorizationDefinition, type EveConnectionAuthorizationContext, type ConnectAuthorizationPhase, type VercelConnectMetadata, } from './connection-authorization.js';
10
10
  export { CONNECT_OAUTH_ISSUER, connectOAuth, type ConnectOAuthAudienceEnvironment, type ConnectOAuthEnvironment, type ConnectOAuthOptions, } from './connect-oauth.js';
11
11
  export { connectGitHubCredentials, type ConnectGitHubCredentialsParams, } from './github-credentials.js';
12
12
  export { connectLinearCredentials, type ConnectLinearCredentialsParams, } from './linear-credentials.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vercel/connect",
3
- "version": "0.2.4",
3
+ "version": "0.2.5",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "repository": {
@@ -43,7 +43,7 @@
43
43
  "peerDependencies": {
44
44
  "@ai-sdk/mcp": "^1 || ^2",
45
45
  "@auth/core": ">=0.37.0",
46
- "ai": "^6 || ^7",
46
+ "ai": "^6 || ^7.0.0-beta.0",
47
47
  "better-auth": ">=1.5.0",
48
48
  "eve": ">=0.6.0-beta.1"
49
49
  },