@vercel/connect 0.2.2 → 0.2.3

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.
@@ -151,6 +151,33 @@ export interface VercelConnectMetadata {
151
151
  */
152
152
  export type EveConnectAuthorizationDefinition<TAuthorization extends InteractiveAuthorizationDefinition | NonInteractiveAuthorizationDefinition> = TAuthorization & {
153
153
  readonly vercelConnect: VercelConnectMetadata;
154
+ /**
155
+ * Drops the in-process Vercel Connect token cache entry for
156
+ * `principal` so the next `getToken` re-fetches instead of re-serving a
157
+ * rejected bearer. Eve's runtime calls this from its shared eviction
158
+ * path when a resolved token is rejected (a downstream `401` mapped to
159
+ * `requireAuth()`, or an MCP server rejecting the bearer), cascading
160
+ * invalidation from Eve's per-step cache down into this adapter's cache.
161
+ *
162
+ * By default this is a local-cache-only operation: it preserves the
163
+ * underlying Connect grant (and its refresh token), so the next
164
+ * `getToken` can refresh a merely-expired access token without forcing
165
+ * a new consent flow. That is the right default for the automatic
166
+ * `401` cascade, where the rejected bearer is usually just stale.
167
+ *
168
+ * Pass `revoke: true` only when the grant itself is known to be dead
169
+ * and you want it torn down at Vercel Connect (refresh token included)
170
+ * so the next `getToken` surfaces `user_authorization_required` and
171
+ * re-runs consent — e.g. a user-initiated "disconnect this
172
+ * integration" action. Revocation is destructive and best-effort: a
173
+ * failed or duplicate revoke is swallowed so it never masks the error
174
+ * that triggered eviction, and the local cache entry is dropped either
175
+ * way.
176
+ */
177
+ readonly evict: (opts: {
178
+ readonly principal: ConnectionPrincipal;
179
+ readonly revoke?: boolean;
180
+ }) => Promise<void>;
154
181
  };
155
182
  /**
156
183
  * Builds an Eve {@link AuthorizationDefinition} backed by Vercel
@@ -30,14 +30,48 @@
30
30
  */
31
31
  import { ConnectionAuthorizationFailedError, ConnectionAuthorizationRequiredError, } from 'eve/connections';
32
32
  import { startAuthorization } from '../authorization.js';
33
- import { ConnectorInstallationRequiredError, getTokenResponse, NoValidTokenError, UserAuthorizationRequiredError, } from '../token.js';
33
+ import { ConnectorInstallationRequiredError, deleteTokenCacheEntry, getTokenResponse, NoValidTokenError, revokeToken, UserAuthorizationRequiredError, } from '../token.js';
34
34
  export function connect(input) {
35
35
  const options = normalizeAuthorizationOptions(input);
36
36
  const vercelConnect = { connector: options.connector };
37
+ const evict = makeEvict(options);
37
38
  if (options.principalType === 'app') {
38
- return { ...buildNonInteractiveDefinition(options), vercelConnect };
39
+ return { ...buildNonInteractiveDefinition(options), vercelConnect, evict };
39
40
  }
40
- return { ...buildInteractiveDefinition(options), vercelConnect };
41
+ return { ...buildInteractiveDefinition(options), vercelConnect, evict };
42
+ }
43
+ /**
44
+ * Builds the {@link EveConnectAuthorizationDefinition.evict} callback for
45
+ * a connector. Resolves the same token params {@link getToken} uses for
46
+ * `principal`, then drops exactly that cache entry — leaving every other
47
+ * principal's cached token intact.
48
+ *
49
+ * When called with `revoke: true` it instead tears the grant down at
50
+ * Vercel Connect via {@link revokeToken} (best-effort, falling back to a
51
+ * local cache drop if the revoke request fails).
52
+ */
53
+ function makeEvict(options) {
54
+ return async ({ principal, revoke }) => {
55
+ const params = await buildTokenParams(options, principal);
56
+ if (revoke) {
57
+ try {
58
+ // Destructive: tears down the grant at Vercel Connect (refresh
59
+ // token included) and clears the in-process cache. Best-effort —
60
+ // a failed or duplicate revoke must not mask the auth error that
61
+ // triggered eviction.
62
+ await revokeToken(options.connector, {
63
+ subject: params.subject,
64
+ installationId: params.installationId,
65
+ }, options.connectOptions);
66
+ return;
67
+ }
68
+ catch {
69
+ // Fall through to the local cache drop so the rejected bearer is
70
+ // gone even when the server-side revoke failed.
71
+ }
72
+ }
73
+ deleteTokenCacheEntry(options.connector, params);
74
+ };
41
75
  }
42
76
  function normalizeAuthorizationOptions(input) {
43
77
  if (typeof input === 'string') {
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- export { getToken, getTokenResponse, revokeToken, ConnectError, NoValidTokenError, UserAuthorizationRequiredError, ConnectorInstallationRequiredError, type ConnectErrorOptions, type ConnectOptions, type ConnectTokenParams, type ConnectTokenResponse, type ConnectTokenSubject, type ConnectVendorErrorPayload, } from './token.js';
1
+ export { deleteTokenCacheEntry, getToken, getTokenResponse, revokeToken, ConnectError, NoValidTokenError, UserAuthorizationRequiredError, ConnectorInstallationRequiredError, type ConnectErrorOptions, type ConnectOptions, type ConnectTokenParams, type ConnectTokenResponse, type ConnectTokenSubject, type ConnectVendorErrorPayload, } from './token.js';
2
2
  export { startAuthorization, type ConnectAuthorizationOptions, type ConnectAuthorizationResponse, } from './authorization.js';
3
3
  export type { ConnectAuthorizationDetail } from './authorization-details.js';
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- export { getToken, getTokenResponse, revokeToken, ConnectError, NoValidTokenError, UserAuthorizationRequiredError, ConnectorInstallationRequiredError, } from './token.js';
1
+ export { deleteTokenCacheEntry, getToken, getTokenResponse, revokeToken, ConnectError, NoValidTokenError, UserAuthorizationRequiredError, ConnectorInstallationRequiredError, } from './token.js';
2
2
  export { startAuthorization, } from './authorization.js';
package/dist/token.d.ts CHANGED
@@ -99,4 +99,22 @@ export declare function revokeToken(connector: string, params: {
99
99
  subject: ConnectTokenSubject;
100
100
  installationId?: string;
101
101
  }, options?: ConnectOptions): Promise<void>;
102
+ /**
103
+ * Remove a single cached token entry for `(connector, params)` from the
104
+ * in-process cache.
105
+ *
106
+ * Targeted counterpart to {@link revokeToken}'s `cache.clear()`: it drops
107
+ * exactly the entry {@link getTokenResponse} would serve for these
108
+ * arguments, leaving every other connector/principal untouched. Use it
109
+ * when a credential is known to be bad (the resource server rejected the
110
+ * bearer with a `401`) so the next {@link getTokenResponse} re-fetches
111
+ * instead of re-serving the rejected token — without paying for a Connect
112
+ * round trip on every call the way {@link ConnectOptions.forceRefresh}
113
+ * does.
114
+ *
115
+ * The cache key is derived from `connector` plus every field of `params`,
116
+ * so pass the same `params` used for the original {@link getTokenResponse}
117
+ * call. No-op when no matching entry exists.
118
+ */
119
+ export declare function deleteTokenCacheEntry(connector: string, params: ConnectTokenParams): void;
102
120
  export declare function createConnectErrorFromResponse(response: Response, fallbackMessage: string): Promise<ConnectError>;
package/dist/token.js CHANGED
@@ -37,7 +37,7 @@ export async function getToken(connector, params, options) {
37
37
  }
38
38
  export async function getTokenResponse(connector, params, options) {
39
39
  const bufferMs = params.validityBufferMs ?? DEFAULT_VALIDITY_BUFFER_MS;
40
- const cacheKey = JSON.stringify({ connector, ...params });
40
+ const cacheKey = tokenCacheKey(connector, params);
41
41
  if (options?.forceRefresh) {
42
42
  cache.delete(cacheKey);
43
43
  }
@@ -90,9 +90,37 @@ export async function revokeToken(connector, params, options) {
90
90
  }
91
91
  cache.clear();
92
92
  }
93
+ /**
94
+ * Remove a single cached token entry for `(connector, params)` from the
95
+ * in-process cache.
96
+ *
97
+ * Targeted counterpart to {@link revokeToken}'s `cache.clear()`: it drops
98
+ * exactly the entry {@link getTokenResponse} would serve for these
99
+ * arguments, leaving every other connector/principal untouched. Use it
100
+ * when a credential is known to be bad (the resource server rejected the
101
+ * bearer with a `401`) so the next {@link getTokenResponse} re-fetches
102
+ * instead of re-serving the rejected token — without paying for a Connect
103
+ * round trip on every call the way {@link ConnectOptions.forceRefresh}
104
+ * does.
105
+ *
106
+ * The cache key is derived from `connector` plus every field of `params`,
107
+ * so pass the same `params` used for the original {@link getTokenResponse}
108
+ * call. No-op when no matching entry exists.
109
+ */
110
+ export function deleteTokenCacheEntry(connector, params) {
111
+ cache.delete(tokenCacheKey(connector, params));
112
+ }
93
113
  const DEFAULT_VALIDITY_BUFFER_MS = 30_000;
94
114
  const MAX_CACHE_SIZE = 100;
95
115
  const cache = new Map();
116
+ /**
117
+ * Cache key for a `(connector, params)` pair. Stable across calls with
118
+ * equal arguments so {@link getTokenResponse} and
119
+ * {@link deleteTokenCacheEntry} address the same entry.
120
+ */
121
+ function tokenCacheKey(connector, params) {
122
+ return JSON.stringify({ connector, ...params });
123
+ }
96
124
  function evictLru() {
97
125
  let oldestKey;
98
126
  let oldestTime = Infinity;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vercel/connect",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "repository": {