@cloudflare/workers-oauth-provider 0.0.0-a204c64 → 0.0.0-b7784c7

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.
package/README.md CHANGED
@@ -38,7 +38,7 @@ export default new OAuthProvider({
38
38
  // You can provide either an object with a fetch method (ExportedHandler)
39
39
  // or a class extending WorkerEntrypoint.
40
40
  apiHandler: ApiHandler, // Using a WorkerEntrypoint class
41
-
41
+
42
42
  // For multi-handler setups, you can use apiHandlers instead of apiRoute+apiHandler.
43
43
  // This allows you to use different handlers for different API routes.
44
44
  // Note: You must use either apiRoute+apiHandler (single-handler) OR apiHandlers (multi-handler), not both.
@@ -87,7 +87,13 @@ export default new OAuthProvider({
87
87
  // Note: Creating public clients via the OAuthHelpers.createClient() method
88
88
  // is always allowed regardless of this setting.
89
89
  // Defaults to false.
90
- disallowPublicClientRegistration: false
90
+ disallowPublicClientRegistration: false,
91
+
92
+ // Optional: Time-to-live for refresh tokens in seconds.
93
+ // If not specified, refresh tokens do not expire.
94
+ // Set to 0 to disable refresh tokens (only access tokens will be issued).
95
+ // For example: 3600 = 1 hour, 86400 = 1 day, 2592000 = 30 days
96
+ refreshTokenTTL: 2592000 // 30 days
91
97
  });
92
98
 
93
99
  // The default handler object - the OAuthProvider will pass through HTTP requests to this object's fetch method
@@ -261,6 +267,7 @@ The callback can:
261
267
  - Return only `accessTokenProps` to update just the current access token
262
268
  - Return only `newProps` to update both the grant and access token (the access token inherits these props)
263
269
  - Return `accessTokenTTL` to override the default TTL for this specific access token
270
+ - Return `refreshTokenTTL` to override the default TTL for this specific refresh token
264
271
  - Return nothing to keep the original props unchanged
265
272
 
266
273
  The `accessTokenTTL` override is particularly useful when the application is also an OAuth client to another service and wants to match its access token TTL to the upstream access token TTL. This helps prevent situations where the downstream token is still valid but the upstream token has expired.
@@ -33,6 +33,13 @@ interface TokenExchangeCallbackResult {
33
33
  * Value should be in seconds.
34
34
  */
35
35
  accessTokenTTL?: number;
36
+ /**
37
+ * Override the default refresh token TTL (time-to-live) for this specific grant.
38
+ * Value should be in seconds.
39
+ * Note: This is only honored during authorization code exchange. If returned during
40
+ * refresh token exchange, it will be ignored.
41
+ */
42
+ refreshTokenTTL?: number;
36
43
  }
37
44
  /**
38
45
  * Options for token exchange callback functions
@@ -61,6 +68,33 @@ interface TokenExchangeCallbackOptions {
61
68
  */
62
69
  props: any;
63
70
  }
71
+ /**
72
+ * Input parameters for the resolveExternalToken callback function
73
+ */
74
+ interface ResolveExternalTokenInput {
75
+ /**
76
+ * The token string that was provided in the Authorization header
77
+ */
78
+ token: string;
79
+ /**
80
+ * The original HTTP request
81
+ */
82
+ request: Request;
83
+ /**
84
+ * Cloudflare Worker environment variables
85
+ */
86
+ env: any;
87
+ }
88
+ /**
89
+ * Result returned from the resolveExternalToken callback function
90
+ */
91
+ interface ResolveExternalTokenResult {
92
+ /**
93
+ * Application-specific properties that will be passed to the API handlers
94
+ * These properties are set in the execution context (ctx.props) when the external token is validated
95
+ */
96
+ props: any;
97
+ }
64
98
  interface OAuthProviderOptions {
65
99
  /**
66
100
  * URL(s) for API routes. Requests with URLs starting with any of these prefixes
@@ -116,6 +150,12 @@ interface OAuthProviderOptions {
116
150
  * Defaults to 1 hour (3600 seconds) if not specified.
117
151
  */
118
152
  accessTokenTTL?: number;
153
+ /**
154
+ * Time-to-live for refresh tokens in seconds.
155
+ * If not specified, refresh tokens do not expire.
156
+ * For example: 3600 = 1 hour, 2592000 = 30 days
157
+ */
158
+ refreshTokenTTL?: number;
119
159
  /**
120
160
  * List of scopes supported by this OAuth provider.
121
161
  * If not provided, the 'scopes_supported' field will be omitted from the OAuth metadata.
@@ -144,6 +184,16 @@ interface OAuthProviderOptions {
144
184
  * If the callback returns nothing or undefined for a props field, the original props will be used.
145
185
  */
146
186
  tokenExchangeCallback?: (options: TokenExchangeCallbackOptions) => Promise<TokenExchangeCallbackResult | void> | TokenExchangeCallbackResult | void;
187
+ /**
188
+ * Optional callback function that is called when a provided token was not found in the internal KV.
189
+ * This allows authentication through external OAuth servers.
190
+ * For example, if a request includes an authenticated token from a different OAuth authentication server,
191
+ * the callback can be used to authenticate it and set the context props through it.
192
+ *
193
+ * The callback can optionally return props values that will passed-through to the apiHandlers.
194
+ * The callback can return `null` to signal resolution failure.
195
+ */
196
+ resolveExternalToken?: (input: ResolveExternalTokenInput) => Promise<ResolveExternalTokenResult | null>;
147
197
  /**
148
198
  * Optional callback function that is called whenever the OAuthProvider returns an error response
149
199
  * This allows the client to emit notifications or perform other actions when an error occurs.
@@ -381,6 +431,10 @@ interface Grant {
381
431
  * Unix timestamp when the grant was created
382
432
  */
383
433
  createdAt: number;
434
+ /**
435
+ * Unix timestamp when the grant expires (if TTL is configured)
436
+ */
437
+ expiresAt?: number;
384
438
  /**
385
439
  * The hash of the current refresh token associated with this grant
386
440
  */
@@ -523,6 +577,10 @@ interface GrantSummary {
523
577
  * Unix timestamp when the grant was created
524
578
  */
525
579
  createdAt: number;
580
+ /**
581
+ * Unix timestamp when the grant expires (if TTL is configured)
582
+ */
583
+ expiresAt?: number;
526
584
  }
527
585
  /**
528
586
  * OAuth 2.0 Provider implementation for Cloudflare Workers
@@ -547,4 +605,4 @@ declare class OAuthProvider {
547
605
  fetch(request: Request, env: any, ctx: ExecutionContext): Promise<Response>;
548
606
  }
549
607
 
550
- export { type AuthRequest, type ClientInfo, type CompleteAuthorizationOptions, type Grant, type GrantSummary, type ListOptions, type ListResult, type OAuthHelpers, OAuthProvider, type OAuthProviderOptions, type Token, type TokenExchangeCallbackOptions, type TokenExchangeCallbackResult, OAuthProvider as default };
608
+ export { type AuthRequest, type ClientInfo, type CompleteAuthorizationOptions, type Grant, type GrantSummary, type ListOptions, type ListResult, type OAuthHelpers, OAuthProvider, type OAuthProviderOptions, type ResolveExternalTokenInput, type ResolveExternalTokenResult, type Token, type TokenExchangeCallbackOptions, type TokenExchangeCallbackResult, OAuthProvider as default };
@@ -478,12 +478,10 @@ var OAuthProviderImpl = class {
478
478
  }
479
479
  }
480
480
  const accessTokenSecret = generateRandomString(TOKEN_LENGTH);
481
- const refreshTokenSecret = generateRandomString(TOKEN_LENGTH);
482
481
  const accessToken = `${userId}:${grantId}:${accessTokenSecret}`;
483
- const refreshToken = `${userId}:${grantId}:${refreshTokenSecret}`;
484
482
  const accessTokenId = await generateTokenId(accessToken);
485
- const refreshTokenId = await generateTokenId(refreshToken);
486
483
  let accessTokenTTL = this.options.accessTokenTTL;
484
+ let refreshTokenTTL = this.options.refreshTokenTTL;
487
485
  const encryptionKey = await unwrapKeyWithToken(code, grantData.authCodeWrappedKey);
488
486
  let grantEncryptionKey = encryptionKey;
489
487
  let accessTokenEncryptionKey = encryptionKey;
@@ -513,6 +511,9 @@ var OAuthProviderImpl = class {
513
511
  if (callbackResult.accessTokenTTL !== void 0) {
514
512
  accessTokenTTL = callbackResult.accessTokenTTL;
515
513
  }
514
+ if ("refreshTokenTTL" in callbackResult) {
515
+ refreshTokenTTL = callbackResult.refreshTokenTTL;
516
+ }
516
517
  }
517
518
  const grantResult = await encryptProps(grantProps);
518
519
  grantData.encryptedProps = grantResult.encryptedData;
@@ -528,17 +529,26 @@ var OAuthProviderImpl = class {
528
529
  }
529
530
  const now = Math.floor(Date.now() / 1e3);
530
531
  const accessTokenExpiresAt = now + accessTokenTTL;
532
+ const useRefreshToken = refreshTokenTTL !== 0;
531
533
  const accessTokenWrappedKey = await wrapKeyWithToken(accessToken, accessTokenEncryptionKey);
532
- const refreshTokenWrappedKey = await wrapKeyWithToken(refreshToken, grantEncryptionKey);
533
534
  delete grantData.authCodeId;
534
535
  delete grantData.codeChallenge;
535
536
  delete grantData.codeChallengeMethod;
536
537
  delete grantData.authCodeWrappedKey;
537
- grantData.refreshTokenId = refreshTokenId;
538
- grantData.refreshTokenWrappedKey = refreshTokenWrappedKey;
539
- grantData.previousRefreshTokenId = void 0;
540
- grantData.previousRefreshTokenWrappedKey = void 0;
541
- await env.OAUTH_KV.put(grantKey, JSON.stringify(grantData));
538
+ let refreshToken;
539
+ if (useRefreshToken) {
540
+ const refreshTokenSecret = generateRandomString(TOKEN_LENGTH);
541
+ refreshToken = `${userId}:${grantId}:${refreshTokenSecret}`;
542
+ const refreshTokenId = await generateTokenId(refreshToken);
543
+ const refreshTokenWrappedKey = await wrapKeyWithToken(refreshToken, grantEncryptionKey);
544
+ const expiresAt = refreshTokenTTL !== void 0 ? now + refreshTokenTTL : void 0;
545
+ grantData.refreshTokenId = refreshTokenId;
546
+ grantData.refreshTokenWrappedKey = refreshTokenWrappedKey;
547
+ grantData.previousRefreshTokenId = void 0;
548
+ grantData.previousRefreshTokenWrappedKey = void 0;
549
+ grantData.expiresAt = expiresAt;
550
+ }
551
+ await this.saveGrantWithTTL(env, grantKey, grantData, now);
542
552
  const accessTokenData = {
543
553
  id: accessTokenId,
544
554
  grantId,
@@ -555,18 +565,18 @@ var OAuthProviderImpl = class {
555
565
  await env.OAUTH_KV.put(`token:${userId}:${grantId}:${accessTokenId}`, JSON.stringify(accessTokenData), {
556
566
  expirationTtl: accessTokenTTL
557
567
  });
558
- return new Response(
559
- JSON.stringify({
560
- access_token: accessToken,
561
- token_type: "bearer",
562
- expires_in: accessTokenTTL,
563
- refresh_token: refreshToken,
564
- scope: grantData.scope.join(" ")
565
- }),
566
- {
567
- headers: { "Content-Type": "application/json" }
568
- }
569
- );
568
+ const tokenResponse = {
569
+ access_token: accessToken,
570
+ token_type: "bearer",
571
+ expires_in: accessTokenTTL,
572
+ scope: grantData.scope.join(" ")
573
+ };
574
+ if (refreshToken) {
575
+ tokenResponse.refresh_token = refreshToken;
576
+ }
577
+ return new Response(JSON.stringify(tokenResponse), {
578
+ headers: { "Content-Type": "application/json" }
579
+ });
570
580
  }
571
581
  /**
572
582
  * Handles the refresh token grant type
@@ -600,12 +610,15 @@ var OAuthProviderImpl = class {
600
610
  if (grantData.clientId !== clientInfo.clientId) {
601
611
  return this.createErrorResponse("invalid_grant", "Client ID mismatch");
602
612
  }
613
+ if (grantData.expiresAt !== void 0) {
614
+ const now2 = Math.floor(Date.now() / 1e3);
615
+ if (now2 >= grantData.expiresAt) {
616
+ return this.createErrorResponse("invalid_grant", "Refresh token has expired");
617
+ }
618
+ }
603
619
  const accessTokenSecret = generateRandomString(TOKEN_LENGTH);
604
620
  const newAccessToken = `${userId}:${grantId}:${accessTokenSecret}`;
605
621
  const accessTokenId = await generateTokenId(newAccessToken);
606
- const refreshTokenSecret = generateRandomString(TOKEN_LENGTH);
607
- const newRefreshToken = `${userId}:${grantId}:${refreshTokenSecret}`;
608
- const newRefreshTokenId = await generateTokenId(newRefreshToken);
609
622
  let accessTokenTTL = this.options.accessTokenTTL;
610
623
  let wrappedKeyToUse;
611
624
  if (isCurrentToken) {
@@ -617,6 +630,7 @@ var OAuthProviderImpl = class {
617
630
  let grantEncryptionKey = encryptionKey;
618
631
  let accessTokenEncryptionKey = encryptionKey;
619
632
  let encryptedAccessTokenProps = grantData.encryptedProps;
633
+ let grantPropsChanged = false;
620
634
  if (this.options.tokenExchangeCallback) {
621
635
  const decryptedProps = await decryptProps(encryptionKey, grantData.encryptedProps);
622
636
  let grantProps = decryptedProps;
@@ -629,7 +643,6 @@ var OAuthProviderImpl = class {
629
643
  props: decryptedProps
630
644
  };
631
645
  const callbackResult = await Promise.resolve(this.options.tokenExchangeCallback(callbackOptions));
632
- let grantPropsChanged = false;
633
646
  if (callbackResult) {
634
647
  if (callbackResult.newProps) {
635
648
  grantProps = callbackResult.newProps;
@@ -644,6 +657,12 @@ var OAuthProviderImpl = class {
644
657
  if (callbackResult.accessTokenTTL !== void 0) {
645
658
  accessTokenTTL = callbackResult.accessTokenTTL;
646
659
  }
660
+ if ("refreshTokenTTL" in callbackResult) {
661
+ return this.createErrorResponse(
662
+ "invalid_request",
663
+ "refreshTokenTTL cannot be changed during refresh token exchange"
664
+ );
665
+ }
647
666
  }
648
667
  if (grantPropsChanged) {
649
668
  const grantResult = await encryptProps(grantProps);
@@ -665,14 +684,23 @@ var OAuthProviderImpl = class {
665
684
  }
666
685
  }
667
686
  const now = Math.floor(Date.now() / 1e3);
687
+ if (grantData.expiresAt !== void 0) {
688
+ const remainingRefreshTokenLifetime = grantData.expiresAt - now;
689
+ if (remainingRefreshTokenLifetime > 0) {
690
+ accessTokenTTL = Math.min(accessTokenTTL, remainingRefreshTokenLifetime);
691
+ }
692
+ }
668
693
  const accessTokenExpiresAt = now + accessTokenTTL;
669
694
  const accessTokenWrappedKey = await wrapKeyWithToken(newAccessToken, accessTokenEncryptionKey);
695
+ const refreshTokenSecret = generateRandomString(TOKEN_LENGTH);
696
+ const newRefreshToken = `${userId}:${grantId}:${refreshTokenSecret}`;
697
+ const newRefreshTokenId = await generateTokenId(newRefreshToken);
670
698
  const newRefreshTokenWrappedKey = await wrapKeyWithToken(newRefreshToken, grantEncryptionKey);
671
699
  grantData.previousRefreshTokenId = providedTokenHash;
672
700
  grantData.previousRefreshTokenWrappedKey = wrappedKeyToUse;
673
701
  grantData.refreshTokenId = newRefreshTokenId;
674
702
  grantData.refreshTokenWrappedKey = newRefreshTokenWrappedKey;
675
- await env.OAUTH_KV.put(grantKey, JSON.stringify(grantData));
703
+ await this.saveGrantWithTTL(env, grantKey, grantData, now);
676
704
  const accessTokenData = {
677
705
  id: accessTokenId,
678
706
  grantId,
@@ -689,18 +717,16 @@ var OAuthProviderImpl = class {
689
717
  await env.OAUTH_KV.put(`token:${userId}:${grantId}:${accessTokenId}`, JSON.stringify(accessTokenData), {
690
718
  expirationTtl: accessTokenTTL
691
719
  });
692
- return new Response(
693
- JSON.stringify({
694
- access_token: newAccessToken,
695
- token_type: "bearer",
696
- expires_in: accessTokenTTL,
697
- refresh_token: newRefreshToken,
698
- scope: grantData.scope.join(" ")
699
- }),
700
- {
701
- headers: { "Content-Type": "application/json" }
702
- }
703
- );
720
+ const tokenResponse = {
721
+ access_token: newAccessToken,
722
+ token_type: "bearer",
723
+ expires_in: accessTokenTTL,
724
+ refresh_token: newRefreshToken,
725
+ scope: grantData.scope.join(" ")
726
+ };
727
+ return new Response(JSON.stringify(tokenResponse), {
728
+ headers: { "Content-Type": "application/json" }
729
+ });
704
730
  }
705
731
  /**
706
732
  * Handles OAuth 2.0 token revocation requests (RFC 7009)
@@ -736,7 +762,7 @@ var OAuthProviderImpl = class {
736
762
  } else if (isRefreshToken) {
737
763
  await this.createOAuthHelpers(env).revokeGrant(grantId, userId);
738
764
  }
739
- return new Response("", { status: 500 });
765
+ return new Response("", { status: 200 });
740
766
  }
741
767
  /**
742
768
  * Revokes a specific access token without affecting the refresh token
@@ -914,30 +940,40 @@ var OAuthProviderImpl = class {
914
940
  });
915
941
  }
916
942
  const accessToken = authHeader.substring(7);
917
- const tokenParts = accessToken.split(":");
918
- if (tokenParts.length !== 3) {
919
- return this.createErrorResponse("invalid_token", "Invalid token format", 401, {
920
- "WWW-Authenticate": 'Bearer realm="OAuth", error="invalid_token"'
921
- });
922
- }
923
- const [userId, grantId, _] = tokenParts;
924
- const accessTokenId = await generateTokenId(accessToken);
925
- const tokenKey = `token:${userId}:${grantId}:${accessTokenId}`;
926
- const tokenData = await env.OAUTH_KV.get(tokenKey, { type: "json" });
927
- if (!tokenData) {
943
+ const parts = accessToken.split(":");
944
+ const isPossiblyInternalFormat = parts.length === 3;
945
+ let tokenData = null;
946
+ let userId = "";
947
+ let grantId = "";
948
+ if (isPossiblyInternalFormat) {
949
+ [userId, grantId] = parts;
950
+ const id = await generateTokenId(accessToken);
951
+ tokenData = await env.OAUTH_KV.get(`token:${userId}:${grantId}:${id}`, { type: "json" });
952
+ }
953
+ if (!tokenData && !this.options.resolveExternalToken) {
928
954
  return this.createErrorResponse("invalid_token", "Invalid access token", 401, {
929
955
  "WWW-Authenticate": 'Bearer realm="OAuth", error="invalid_token"'
930
956
  });
931
957
  }
932
- const now = Math.floor(Date.now() / 1e3);
933
- if (tokenData.expiresAt < now) {
934
- return this.createErrorResponse("invalid_token", "Access token expired", 401, {
935
- "WWW-Authenticate": 'Bearer realm="OAuth", error="invalid_token"'
936
- });
958
+ if (tokenData) {
959
+ const now = Math.floor(Date.now() / 1e3);
960
+ if (tokenData.expiresAt < now) {
961
+ return this.createErrorResponse("invalid_token", "Access token expired", 401, {
962
+ "WWW-Authenticate": 'Bearer realm="OAuth", error="invalid_token"'
963
+ });
964
+ }
965
+ const encryptionKey = await unwrapKeyWithToken(accessToken, tokenData.wrappedEncryptionKey);
966
+ const decryptedProps = await decryptProps(encryptionKey, tokenData.grant.encryptedProps);
967
+ ctx.props = decryptedProps;
968
+ } else if (this.options.resolveExternalToken) {
969
+ const ext = await this.options.resolveExternalToken({ token: accessToken, request, env });
970
+ if (!ext) {
971
+ return this.createErrorResponse("invalid_token", "Invalid access token", 401, {
972
+ "WWW-Authenticate": 'Bearer realm="OAuth", error="invalid_token"'
973
+ });
974
+ }
975
+ ctx.props = ext.props;
937
976
  }
938
- const encryptionKey = await unwrapKeyWithToken(accessToken, tokenData.wrappedEncryptionKey);
939
- const decryptedProps = await decryptProps(encryptionKey, tokenData.grant.encryptedProps);
940
- ctx.props = decryptedProps;
941
977
  if (!env.OAUTH_PROVIDER) {
942
978
  env.OAUTH_PROVIDER = this.createOAuthHelpers(env);
943
979
  }
@@ -962,6 +998,17 @@ var OAuthProviderImpl = class {
962
998
  createOAuthHelpers(env) {
963
999
  return new OAuthHelpersImpl(env, this);
964
1000
  }
1001
+ /**
1002
+ * Saves a grant to KV with appropriate TTL based on expiration
1003
+ * @param env - The environment bindings
1004
+ * @param grantKey - The KV key for the grant
1005
+ * @param grantData - The grant data to save
1006
+ * @param now - Current timestamp in seconds
1007
+ */
1008
+ async saveGrantWithTTL(env, grantKey, grantData, now) {
1009
+ const kvOptions = grantData.expiresAt !== void 0 ? { expiration: grantData.expiresAt } : {};
1010
+ await env.OAUTH_KV.put(grantKey, JSON.stringify(grantData), kvOptions);
1011
+ }
965
1012
  /**
966
1013
  * Fetches client information from KV storage
967
1014
  * This method is not private because `OAuthHelpers` needs to call it. Note that since
@@ -1005,7 +1052,7 @@ async function hashSecret(secret) {
1005
1052
  return generateTokenId(secret);
1006
1053
  }
1007
1054
  function generateRandomString(length) {
1008
- const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
1055
+ const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
1009
1056
  let result = "";
1010
1057
  const values = new Uint8Array(length);
1011
1058
  crypto.getRandomValues(values);
@@ -1174,6 +1221,9 @@ var OAuthHelpersImpl = class {
1174
1221
  const state = url.searchParams.get("state") || "";
1175
1222
  const codeChallenge = url.searchParams.get("code_challenge") || void 0;
1176
1223
  const codeChallengeMethod = url.searchParams.get("code_challenge_method") || "plain";
1224
+ if (redirectUri.startsWith("javascript:") || redirectUri.startsWith("data:") || redirectUri.startsWith("vbscript:") || redirectUri.startsWith("file:") || redirectUri.startsWith("mailto:") || redirectUri.startsWith("blob:")) {
1225
+ throw new Error("Invalid redirect URI");
1226
+ }
1177
1227
  if (responseType === "token" && !this.provider.options.allowImplicitFlow) {
1178
1228
  throw new Error("The implicit grant flow is not enabled for this provider");
1179
1229
  }
@@ -1441,7 +1491,8 @@ var OAuthHelpersImpl = class {
1441
1491
  userId: grantData.userId,
1442
1492
  scope: grantData.scope,
1443
1493
  metadata: grantData.metadata,
1444
- createdAt: grantData.createdAt
1494
+ createdAt: grantData.createdAt,
1495
+ expiresAt: grantData.expiresAt
1445
1496
  };
1446
1497
  grantSummaries.push(summary);
1447
1498
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloudflare/workers-oauth-provider",
3
- "version": "0.0.0-a204c64",
3
+ "version": "0.0.0-b7784c7",
4
4
  "description": "OAuth provider for Cloudflare Workers",
5
5
  "main": "dist/oauth-provider.js",
6
6
  "types": "dist/oauth-provider.d.ts",
@@ -26,11 +26,12 @@
26
26
  },
27
27
  "devDependencies": {
28
28
  "@changesets/changelog-github": "^0.5.1",
29
- "@changesets/cli": "^2.29.5",
29
+ "@changesets/cli": "^2.29.7",
30
30
  "@cloudflare/workers-types": "^4.20250807.0",
31
+ "pkg-pr-new": "^0.0.59",
31
32
  "prettier": "^3.6.2",
32
33
  "tsup": "^8.5.0",
33
- "tsx": "^4.20.3",
34
+ "tsx": "^4.20.5",
34
35
  "typescript": "^5.9.2",
35
36
  "vitest": "^3.2.4"
36
37
  },