Skip to content

chore(repo): add auth({ or }) accessor discrimination POC#8749

Draft
jacekradko wants to merge 1 commit into
mainfrom
jacek/auth-or-poc
Draft

chore(repo): add auth({ or }) accessor discrimination POC#8749
jacekradko wants to merge 1 commit into
mainfrom
jacek/auth-or-poc

Conversation

@jacekradko
Copy link
Copy Markdown
Member

Draft, not for merge. A typed, runnable proof-of-concept for an auth({ or }) idea that came out of looking at how Stack Auth shapes getUser({ or }): drive the gate with an or option and let the return type narrow on the literal, so there's no User | null to deal with at the call site once you've written or: 'redirect'.

The first thing worth poking at is that Clerk already hands you that guarantee, just split across two functions: auth() is the nullable door, auth.protect() is the guaranteed one. So or only buys something where nullability actually lives, which is the accessor. Putting it on protect adds an option to the one surface that already always narrows. This POC puts or on auth() instead and checks whether the type system can carry the guarantee.

It can, and more cleanly than I expected going in. A single generic call signature with a conditional return type absorbs both the or axis and the existing token axis, so there's no overload blow-up against the six overloads protect carries today. The return type keys solely on the unauthenticated outcome (authorization failure never returns, so it never enters the type), which is what lets the object form or: { unauthenticated, unauthorized } keep the redirect-anon-but-404-the-signed-in-non-admin behavior a single scalar would flatten, while still narrowing. Bare auth() stays type-identical to today, so no existing call site shifts. And protect falls out as a one-line preset, auth({ or: 'notFound' }).

poc/auth-or-poc.ts is self-contained: minimal mirrors of the real auth-object shapes, with file references back to the originals. Every negative case is pinned by @ts-expect-error, and it runs 15 behavioral scenarios.

cd poc
tsc --noEmit -p tsconfig.json   # type proofs
tsx auth-or-poc.ts              # 15/15 runtime scenarios

Things I left open on purpose, since they're judgment calls:

  • Passing an authorization param without an or currently type-checks and silently doesn't gate at runtime (you get the signed-in object back even when the role check failed). Requiring or whenever a role or permission is present would close that.
  • auth() already names this option acceptsToken while protect calls it token; a unified accessor is a chance to settle on one name.
  • Whether or supersedes unauthenticatedUrl/unauthorizedUrl or layers over them. The POC composes them.
  • What the verbs mean once this leaves Next, since redirect/notFound are Next primitives and protect only ships there today.

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Jun 4, 2026

🦋 Changeset detected

Latest commit: 1cff95d

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

This PR includes changesets to release 0 packages

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

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

@vercel
Copy link
Copy Markdown

vercel Bot commented Jun 4, 2026

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

Project Deployment Actions Updated (UTC)
clerk-js-sandbox Ready Ready Preview, Comment Jun 4, 2026 3:40am

Request Review

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Jun 4, 2026

Open in StackBlitz

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

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

@clerk/expo

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

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/hono

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

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

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

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

commit: 1cff95d

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant