Skip to content

chore(backend,nextjs): Improve machine auth type inference and hover types - #9346

Draft
wobsoriano wants to merge 9 commits into
mainfrom
rob/machine-auth-types-improvement
Draft

chore(backend,nextjs): Improve machine auth type inference and hover types#9346
wobsoriano wants to merge 9 commits into
mainfrom
rob/machine-auth-types-improvement

Conversation

@wobsoriano

@wobsoriano wobsoriano commented Aug 5, 2026

Copy link
Copy Markdown
Member

Description

Reworks the internal acceptsToken type inference in @clerk/backend for readability and better editor hovers, with no changes to runtime behavior or type assignability, and moves @clerk/nextjs onto the new helper.

  • Adds a single distributive InferAuthObject<T, SessionType, MachineType> helper that replaces the manual session-only / mixed / machine-only branch analysis. Union distribution performs the case analysis, and Extract filters the machine union per token type instead of stamping it with a tokenType intersection. Exported from @clerk/backend/internal.
  • InferAuthObjectFromToken and InferAuthObjectFromTokenArray remain exported from @clerk/backend/internal with unchanged signatures as one-line delegates, since published framework SDK dists import them by name.
  • Names the distributive branches of AuthenticatedMachineObject and UnauthenticatedMachineObject, so machine auth results hover as AuthenticatedMachineObjectFor<"api_key"> | UnauthenticatedMachineObjectFor<"api_key"> instead of expanded structural intersections.
  • Switches the AuthProtect overloads in @clerk/nextjs to InferAuthObject and adds protect.test-d.ts pinning every overload's return type, including tokenType narrowing.
  • Pins the existing contract in request.test-d.ts and getAuth.test-d.ts: exact resolved state unions per accepted token type, mutual assignability of every getAuth()/auth() return type, Parameters/ReturnType extraction, widened and readonly array behavior, and the any-input collapse that auth.protect() in @clerk/nextjs relies on. Also fixes the vitest typecheck.include patterns in both packages so .test-d.ts files are actually collected.

Checklist

  • pnpm test runs as expected.
  • pnpm build runs as expected.
  • (If applicable) JSDoc comments have been added or updated for any package exports
  • (If applicable) Documentation has been updated

Type of change

  • 🐛 Bug fix
  • 🌟 New feature
  • 🔨 Breaking change
  • 📖 Refactoring / dependency upgrade / documentation
  • other:

@vercel

vercel Bot commented Aug 5, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
clerk-js-sandbox Ready Ready Preview Aug 6, 2026 5:43am
swingset Ready Ready Preview Aug 6, 2026 5:43am

Request Review

@changeset-bot

changeset-bot Bot commented Aug 5, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 41b6633

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 10 packages
Name Type
@clerk/backend Patch
@clerk/astro Patch
@clerk/express Patch
@clerk/fastify Patch
@clerk/hono Patch
@clerk/nextjs Patch
@clerk/nuxt Patch
@clerk/react-router Patch
@clerk/tanstack-react-start Patch
@clerk/testing Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new

pkg-pr-new Bot commented Aug 5, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

npm i https://pkg.pr.new/@clerk/astro@9346

@clerk/backend

npm i https://pkg.pr.new/@clerk/backend@9346

@clerk/chrome-extension

npm i https://pkg.pr.new/@clerk/chrome-extension@9346

@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@9346

@clerk/electron

npm i https://pkg.pr.new/@clerk/electron@9346

@clerk/electron-passkeys

npm i https://pkg.pr.new/@clerk/electron-passkeys@9346

@clerk/eslint-plugin

npm i https://pkg.pr.new/@clerk/eslint-plugin@9346

@clerk/expo

npm i https://pkg.pr.new/@clerk/expo@9346

@clerk/expo-google-signin

npm i https://pkg.pr.new/@clerk/expo-google-signin@9346

@clerk/expo-passkeys

npm i https://pkg.pr.new/@clerk/expo-passkeys@9346

@clerk/express

npm i https://pkg.pr.new/@clerk/express@9346

@clerk/fastify

npm i https://pkg.pr.new/@clerk/fastify@9346

@clerk/hono

npm i https://pkg.pr.new/@clerk/hono@9346

@clerk/localizations

npm i https://pkg.pr.new/@clerk/localizations@9346

@clerk/nextjs

npm i https://pkg.pr.new/@clerk/nextjs@9346

@clerk/nuxt

npm i https://pkg.pr.new/@clerk/nuxt@9346

@clerk/react

npm i https://pkg.pr.new/@clerk/react@9346

@clerk/react-router

npm i https://pkg.pr.new/@clerk/react-router@9346

@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@9346

@clerk/tanstack-react-start

npm i https://pkg.pr.new/@clerk/tanstack-react-start@9346

@clerk/testing

npm i https://pkg.pr.new/@clerk/testing@9346

@clerk/ui

npm i https://pkg.pr.new/@clerk/ui@9346

@clerk/upgrade

npm i https://pkg.pr.new/@clerk/upgrade@9346

@clerk/vue

npm i https://pkg.pr.new/@clerk/vue@9346

commit: 41b6633

@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-08-05T23:49:27.276Z

Summary

Metric Count
Packages analyzed 19
Packages with changes 1
🔴 Breaking changes 0
🟡 Non-breaking changes 6
🟢 Additions 2

🤖 This report was reviewed by claude-sonnet-4-6.


@clerk/backend

Current version: 3.15.1
Recommended bump: MINOR → 3.16.0

Subpath ./internal

🟡 Non-breaking Changes (6)

Modified: GetAuthFn.undefined
  <T extends TokenType[]>(req: RequestType, options: AuthOptions & {
          acceptsToken: T;
-     }): MaybePromise<InferAuthObjectFromTokenArray<T, SessionAuthObject, MachineAuthObject<Exclude<T[number], SessionTokenType>>> | InvalidTokenAuthObject, ReturnsPromise>;
+     }): MaybePromise<InferAuthObject<T[number], SessionAuthObject, MachineAuthObject<Exclude<T[number], SessionTokenType>>> | InvalidTokenAuthObject, ReturnsPromise>;

Static analyzer: Breaking change in call signature GetAuthFn.undefined: Return type changed: import("@clerk/backend").~MaybePromise<import("@clerk/backend").InferAuthObjectFromTokenArray<T,import("@clerk/backend").~SessionAuthObject,import("@clerk/backend").~MachineAuthObject<!Exclude:type<T[number],import("@clerk/backend").SessionTokenType>>>|import("@clerk/backend").~InvalidTokenAuthObject,ReturnsPromise>import("@clerk/backend").~MaybePromise<import("@clerk/backend").~InferAuthObject<T[number],import("@clerk/backend").~SessionAuthObject,import("@clerk/backend").~MachineAuthObject<!Exclude:type<T[number],import("@clerk/backend").SessionTokenType>>>|import("@clerk/backend").~InvalidTokenAuthObject,ReturnsPromise>

🤖 AI review (reclassified as non-breaking) (85%): The return type changed from InferAuthObjectFromTokenArray<T, ...> to InferAuthObject<T[number], ...>, and InferAuthObjectFromTokenArray now delegates to InferAuthObject<T[number], ...> (as shown in change 541ce9b12ccb), so the resolved shape is structurally identical — this is a rename/refactor, not a semantic change.

Modified: GetAuthFn.undefined
  <T extends TokenType>(req: RequestType, options: AuthOptions & {
          acceptsToken: T;
-     }): MaybePromise<InferAuthObjectFromToken<T, SessionAuthObject, MachineAuthObject<Exclude<T, SessionTokenType>>>, ReturnsPromise>;
+     }): MaybePromise<InferAuthObject<T, SessionAuthObject, MachineAuthObject<Exclude<T, SessionTokenType>>>, ReturnsPromise>;

Static analyzer: Breaking change in call signature GetAuthFn.undefined: Return type changed: import("@clerk/backend").~MaybePromise<import("@clerk/backend").InferAuthObjectFromToken<T,import("@clerk/backend").~SessionAuthObject,import("@clerk/backend").~MachineAuthObject<!Exclude:type<T,import("@clerk/backend").SessionTokenType>>>,ReturnsPromise>import("@clerk/backend").~MaybePromise<import("@clerk/backend").~InferAuthObject<T,import("@clerk/backend").~SessionAuthObject,import("@clerk/backend").~MachineAuthObject<!Exclude:type<T,import("@clerk/backend").SessionTokenType>>>,ReturnsPromise>

🤖 AI review (reclassified as non-breaking) (85%): The return type changed from InferAuthObjectFromToken<T, ...> to InferAuthObject<T, ...>, and InferAuthObjectFromToken now delegates to InferAuthObject<T, ...> (as shown in change 46644466039d), so the resolved shape is structurally identical — this is a rename/refactor, not a semantic change.

Modified: GetAuthFnNoRequest.undefined
  <T extends TokenType[]>(options: AuthOptions & {
          acceptsToken: T;
-     }): MaybePromise<InferAuthObjectFromTokenArray<T, SessionAuthType, MachineAuthObject<Exclude<T[number], SessionTokenType>>> | InvalidTokenAuthObject, ReturnsPromise>;
+     }): MaybePromise<InferAuthObject<T[number], SessionAuthType, MachineAuthObject<Exclude<T[number], SessionTokenType>>> | InvalidTokenAuthObject, ReturnsPromise>;

Static analyzer: Breaking change in call signature GetAuthFnNoRequest.undefined: Return type changed: import("@clerk/backend").~MaybePromise<import("@clerk/backend").InferAuthObjectFromTokenArray<T,SessionAuthType,import("@clerk/backend").~MachineAuthObject<!Exclude:type<T[number],import("@clerk/backend").SessionTokenType>>>|import("@clerk/backend").~InvalidTokenAuthObject,ReturnsPromise>import("@clerk/backend").~MaybePromise<import("@clerk/backend").~InferAuthObject<T[number],SessionAuthType,import("@clerk/backend").~MachineAuthObject<!Exclude:type<T[number],import("@clerk/backend").SessionTokenType>>>|import("@clerk/backend").~InvalidTokenAuthObject,ReturnsPromise>

🤖 AI review (reclassified as non-breaking) (85%): Same pattern as dcafb4933a7d: InferAuthObjectFromTokenArray<T, ...> now resolves to InferAuthObject<T[number], ...>, making the before and after return types structurally identical.

Modified: GetAuthFnNoRequest.undefined
  <T extends TokenType>(options: AuthOptions & {
          acceptsToken: T;
-     }): MaybePromise<InferAuthObjectFromToken<T, SessionAuthType, MachineAuthObject<Exclude<T, SessionTokenType>>>, ReturnsPromise>;
+     }): MaybePromise<InferAuthObject<T, SessionAuthType, MachineAuthObject<Exclude<T, SessionTokenType>>>, ReturnsPromise>;

Static analyzer: Breaking change in call signature GetAuthFnNoRequest.undefined: Return type changed: import("@clerk/backend").~MaybePromise<import("@clerk/backend").InferAuthObjectFromToken<T,SessionAuthType,import("@clerk/backend").~MachineAuthObject<!Exclude:type<T,import("@clerk/backend").SessionTokenType>>>,ReturnsPromise>import("@clerk/backend").~MaybePromise<import("@clerk/backend").~InferAuthObject<T,SessionAuthType,import("@clerk/backend").~MachineAuthObject<!Exclude:type<T,import("@clerk/backend").SessionTokenType>>>,ReturnsPromise>

🤖 AI review (reclassified as non-breaking) (85%): Same pattern as e50bed4dca79: InferAuthObjectFromToken<T, ...> now resolves to InferAuthObject<T, ...>, making the before and after return types structurally identical.

Modified: InferAuthObjectFromToken
- export type InferAuthObjectFromToken<T extends TokenType, SessionType extends AuthObject, MachineType extends AuthObject> = T extends SessionTokenType ? SessionType : MachineType & {
-     tokenType: Exclude<T, SessionTokenType>;
- };
+ export type InferAuthObjectFromToken<T extends TokenType, SessionType extends AuthObject, MachineType extends AuthObject> = InferAuthObject<T, SessionType, MachineType>;

Static analyzer: Breaking change in type alias InferAuthObjectFromToken: Type changed: T extends import("@clerk/backend").SessionTokenType ? SessionType:MachineType&{tokenType:!Exclude:type<T,import("@clerk…import("@clerk/backend").~InferAuthObject<T,SessionType,MachineType>

🤖 AI review (reclassified as non-breaking) (85%): InferAuthObjectFromToken now delegates entirely to InferAuthObject<T, SessionType, MachineType>, which by definition resolves to the same conditional shape (session branch or machine branch with tokenType discriminant); consumers reading this type see the same resolved structure as before.

Modified: InferAuthObjectFromTokenArray
- export type InferAuthObjectFromTokenArray<T extends readonly TokenType[], SessionType extends AuthObject, MachineType extends AuthObject> = SessionTokenType extends T[number] ? T[number] extends SessionTokenType ? SessionType : SessionType | (MachineType & {
-     tokenType: Exclude<T[number], SessionTokenType>;
- }) : MachineType & {
-     tokenType: Exclude<T[number], SessionTokenType>;
- };
+ export type InferAuthObjectFromTokenArray<T extends readonly TokenType[], SessionType extends AuthObject, MachineType extends AuthObject> = InferAuthObject<T[number], SessionType, MachineType>;

Static analyzer: Breaking change in type alias InferAuthObjectFromTokenArray: Type changed: import("@clerk/backend").SessionTokenType extends T[number]? T[number]extends import("@clerk/backend").SessionTokenType…import("@clerk/backend").~InferAuthObject<T[number],SessionType,MachineType>

🤖 AI review (reclassified as non-breaking) (80%): InferAuthObjectFromTokenArray now delegates to InferAuthObject<T[number], SessionType, MachineType>; the internal ~InferAuthObject type is designed to replicate the same conditional logic (session vs machine union), so the resolved shape for all valid T inputs is structurally equivalent to the prior inline conditional.

🟢 Additions (2)

Added: AuthenticatedMachineObject
+ export type AuthenticatedMachineObject<T extends MachineTokenType = MachineTokenType> = T extends any ? AuthenticatedMachineObjectFor<T> : never;

Added type alias AuthenticatedMachineObject

Added: UnauthenticatedMachineObject
+ export type UnauthenticatedMachineObject<T extends MachineTokenType = MachineTokenType> = T extends any ? UnauthenticatedMachineObjectFor<T> : never;

Added type alias UnauthenticatedMachineObject


Report generated by Break Check

Last ran on 2f7fc74.

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The change adds reusable machine authentication object helpers and a distributed InferAuthObject type. Authentication overloads now use this inference for token-specific return types. New declaration tests cover token combinations, resolved states, narrowing, overload extraction, and any inputs. Vitest typechecking now includes .test-d.ts files. A patch changeset documents the type improvements.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Suggested reviewers: brkalow, manovotny

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main changes to machine authentication type inference and editor hover types in the backend and Next.js packages.
Description check ✅ Passed The description directly explains the type inference refactor, improved hover output, added tests, and Vitest configuration updates.

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/backend/src/tokens/__tests__/getAuth.test-d.ts`:
- Around line 206-223: Strengthen the type assertions in the direct helper
contract tests for ApiKey, Mixed, and MachineOnly so they verify exact
assignability in both directions. Replace the one-way toExtend checks with
toEqualTypeOf where appropriate, or add the inverse assertions, ensuring each
helper remains token-specific rather than accepting broader machine auth
members.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: ba6721bd-5f76-4549-939f-2740086621f2

📥 Commits

Reviewing files that changed from the base of the PR and between f38cf02 and 2f7fc74.

📒 Files selected for processing (6)
  • .changeset/spicy-hover-types.md
  • packages/backend/src/tokens/__tests__/getAuth.test-d.ts
  • packages/backend/src/tokens/__tests__/request.test-d.ts
  • packages/backend/src/tokens/authObjects.ts
  • packages/backend/src/tokens/types.ts
  • packages/backend/vitest.config.mts
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • clerk/clerk_go (manual)
  • clerk/dashboard (manual)
  • clerk/accounts (manual)
  • clerk/backoffice (manual)
  • clerk/clerk (manual)
  • clerk/clerk-docs (manual)
  • clerk/cloudflare-workers (manual)
  • clerk/clerk-ios (auto-detected)
  • clerk/clerk-android (auto-detected)
  • clerk/cli (auto-detected)

Comment on lines +206 to +223
type ApiKey = InferAuthObjectFromToken<'api_key', SignedInAuthObject, AuthenticatedMachineObject>;
expectTypeOf<AuthenticatedMachineObject<'api_key'>>().toExtend<ApiKey>();
});

test('array helper accepts every clean member per token type', () => {
type Mixed = InferAuthObjectFromTokenArray<
('session_token' | 'm2m_token')[],
SignedInAuthObject,
AuthenticatedMachineObject
>;
expectTypeOf<SignedInAuthObject | AuthenticatedMachineObject<'m2m_token'>>().toExtend<Mixed>();

type MachineOnly = InferAuthObjectFromTokenArray<
('m2m_token' | 'oauth_token')[],
SignedInAuthObject,
AuthenticatedMachineObject
>;
expectTypeOf<AuthenticatedMachineObject<'m2m_token' | 'oauth_token'>>().toExtend<MachineOnly>();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert the inverse assignability for direct helper contracts.

These assertions do not fail if either helper returns all machine auth members. Add the inverse assertion, or use toEqualTypeOf, for ApiKey, Mixed, and MachineOnly. This keeps the direct @clerk/backend/internal helper contract token-specific.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/backend/src/tokens/__tests__/getAuth.test-d.ts` around lines 206 -
223, Strengthen the type assertions in the direct helper contract tests for
ApiKey, Mixed, and MachineOnly so they verify exact assignability in both
directions. Replace the one-way toExtend checks with toEqualTypeOf where
appropriate, or add the inverse assertions, ensuring each helper remains
token-specific rather than accepting broader machine auth members.

@vercel
vercel Bot temporarily deployed to Preview – clerk-js-sandbox August 6, 2026 02:07 Inactive
@wobsoriano wobsoriano changed the title chore(backend): Improve machine auth type inference and hover types chore(backend,nextjs): Improve machine auth type inference and hover types Aug 6, 2026
@github-actions github-actions Bot added the nextjs label Aug 6, 2026
…-over

- Build each constructor branch against its concrete object type so property
  drift fails compile; the remaining casts only bridge generic correlation
- Fix getAuthObjectForAcceptedToken passing the debug function instead of its
  data, which silently emptied debug output on token-type downgrade
- Make AuthenticatedState/UnauthenticatedState distributive so narrowing on
  tokenType also narrows toAuth()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant