Skip to content

refactor(js): extract a generic store from TokenCache#8860

Merged
jacekradko merged 8 commits into
mainfrom
jacek/sdk-117-decouple-tokencache
Jun 26, 2026
Merged

refactor(js): extract a generic store from TokenCache#8860
jacekradko merged 8 commits into
mainfrom
jacek/sdk-117-decouple-tokencache

Conversation

@jacekradko

@jacekradko jacekradko commented Jun 14, 2026

Copy link
Copy Markdown
Member

First slice of the TokenCache decoupling (SDK-117), no behavior change. It backfills characterization tests that pin down current cache behavior, then lifts the raw key/value storage out of MemoryTokenCache into a generic createTokenStore<V> and rewires the cache onto it, so storage is testable on its own without timers or channel mocks.

The part worth a careful read is the cache.* -> store.* rewire in tokenCache.ts. The deleteKey identity check and the overwrite guard both rely on store.get(key) !== value preserving reference identity, which the Map-backed store does. The full clerk-js suite stays green with Session and Client as the real consumers.

The legacy browser bundle budget ticks up from 114KB to 115KB because main already sits at 113.99KB and the extracted store module costs about 50 bytes. Empty changeset, since nothing here is user-facing.

Summary by CodeRabbit

  • Refactor

    • Introduced a dedicated in-memory token storage abstraction and updated the token cache to use it, improving cache lifecycle handling (clearing, sizing, and eviction behavior).
  • Tests

    • Added regression tests covering session token cache channel lifecycle (close/recreate), resilience when broadcast messaging is unavailable, and correct behavior when broadcast messaging fails.
    • Added unit tests for the new token store contract (set/get/delete/clear/iteration/size).
  • Chores

    • Added a release changeset entry for the update.

Lock in current cross-tab, lifecycle, degradation, and audience-keying
behavior as the regression bar for the TokenCache decoupling (SDK-117).
The broadcast postMessage-failure case is a known pre-existing bug
(SDK-119), captured as an it.fails tripwire.
Lift the raw key/value storage out of MemoryTokenCache into a small
generic createTokenStore<V> (pure storage: no timers, no BroadcastChannel,
no JWT knowledge) and rewire the cache onto it. No behavior change; the
identity-based deleteKey guard and overwrite check are preserved. First
structural step of SDK-117.
@changeset-bot

changeset-bot Bot commented Jun 14, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: b8c8d55

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

vercel Bot commented Jun 14, 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, Comment Jun 26, 2026 1:11pm
swingset Ready Ready Preview, Comment Jun 26, 2026 1:11pm

Request Review

@coderabbitai

coderabbitai Bot commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a generic TokenStore abstraction, migrates MemoryTokenCache to it, and expands token cache regression coverage for BroadcastChannel behavior and audience key handling.

Changes

TokenCache Store Decoupling

Layer / File(s) Summary
TokenStore interface and factory
packages/clerk-js/src/core/tokenStore.ts, packages/clerk-js/src/core/__tests__/tokenStore.test.ts
Exports the TokenStore<V> interface and createTokenStore<V>() backed by a Map, with tests covering get/set/delete/clear, iteration, size, overwrites, and generic object identity.
MemoryTokenCache migration to TokenStore
packages/clerk-js/src/core/tokenCache.ts, .changeset/decouple-tokencache-store.md, packages/clerk-js/bundlewatch.config.json
Replaces the internal Map with createTokenStore<TokenCacheValue>() and updates cache lifecycle, eviction, overwrite handling, size reporting, the changeset, and the bundle size threshold.
TokenCache characterization tests
packages/clerk-js/src/core/__tests__/tokenCache.test.ts
Adds SDK-117 regression coverage for BroadcastChannel close/reopen behavior, missing-channel fallback, and audience key coalescing/isolation.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • wobsoriano

Poem

🐇 Hop hop, the cache got a tidy new store,
Tokens keep hopping through one little door.
Broadcasts stay tested, audiences aligned,
I twitch my nose happily—cleaner by design.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main refactor: extracting a generic store from TokenCache.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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

@pkg-pr-new

pkg-pr-new Bot commented Jun 14, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

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

@clerk/electron

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

@clerk/electron-passkeys

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

@clerk/eslint-plugin

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

@clerk/expo

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

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/hono

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

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

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

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

commit: b8c8d55

@github-actions

github-actions Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-06-26T13:11:11.251Z

Summary

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

No API Changes Detected

All packages have stable APIs with no detected changes.


Report generated by Break Check

Last ran on b8c8d55.

@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.

🧹 Nitpick comments (1)
packages/clerk-js/src/core/tokenStore.ts (1)

22-24: ⚡ Quick win

Add @returns tag to JSDoc for customer-facing documentation.

Since this is a public/reference-facing API, the JSDoc should include a @returns tag. Clerk Docs generates reference documentation from JSDoc comments in this repository, so completeness is important for customer-facing clarity.

📝 Suggested JSDoc enhancement
 /**
  * Creates an empty in-memory {`@link` TokenStore} backed by a Map.
+ *
+ * `@returns` A new TokenStore instance with no entries
  */

As per coding guidelines: "Document functions with JSDoc comments including @param, @returns, @throws, and @example tags" and "Treat JSDoc on public/reference-facing APIs as customer-facing documentation."

🤖 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/clerk-js/src/core/tokenStore.ts` around lines 22 - 24, The JSDoc
comment for the function that creates an empty in-memory TokenStore is missing
the `@returns` tag. Add a `@returns` tag to the JSDoc block that documents what type
of object is returned by this function, describing that it returns a TokenStore
instance. This will ensure the auto-generated customer-facing documentation is
complete and provides clear information about the function's return value.

Source: Coding guidelines

🤖 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.

Nitpick comments:
In `@packages/clerk-js/src/core/tokenStore.ts`:
- Around line 22-24: The JSDoc comment for the function that creates an empty
in-memory TokenStore is missing the `@returns` tag. Add a `@returns` tag to the
JSDoc block that documents what type of object is returned by this function,
describing that it returns a TokenStore instance. This will ensure the
auto-generated customer-facing documentation is complete and provides clear
information about the function's return value.

ℹ️ Review info
⚙️ Run configuration

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

Review profile: CHILL

Plan: Pro

Run ID: 9c96bed2-1827-46a8-8152-0445c5ffc9ad

📥 Commits

Reviewing files that changed from the base of the PR and between f4488f5 and 917e259.

📒 Files selected for processing (5)
  • .changeset/decouple-tokencache-store.md
  • packages/clerk-js/src/core/__tests__/tokenCache.test.ts
  • packages/clerk-js/src/core/__tests__/tokenStore.test.ts
  • packages/clerk-js/src/core/tokenCache.ts
  • packages/clerk-js/src/core/tokenStore.ts

@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: 2

🤖 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/clerk-js/src/core/tokenCache.ts`:
- Around line 437-480: The SDK-119 regression tripwire is now stale because the
broadcast failure is intentionally swallowed in tokenCache’s token update path,
so update the related test to pass normally or remove it if it no longer adds
coverage, and delete the “known bug”/`it.fails` expectation tied to this
behavior. Make sure the test still exercises the token broadcast flow around the
tokenCache update logic and reflects the new best-effort behavior, and update
the changeset/release note to describe the fix instead of referencing a known
failure.
- Line 443: The broadcast token id lookup in tokenCache should not rely on an
unsafe any cast for claims.o; keep claims.org_id as-is, but narrow claims.o to a
valid object with a string id before passing it into TokenId.build. Update the
organizationId assignment in tokenCache so it only uses claims.o.id when the
shape is verified, avoiding non-string values from reaching the broadcast match
logic.
🪄 Autofix (Beta)

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), Repository UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: e4a4eed5-733c-41cb-a954-a7eab78d61d7

📥 Commits

Reviewing files that changed from the base of the PR and between 917e259 and 5c6d888.

📒 Files selected for processing (2)
  • packages/clerk-js/src/core/__tests__/tokenCache.test.ts
  • packages/clerk-js/src/core/tokenCache.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/clerk-js/src/core/tests/tokenCache.test.ts

@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.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 2

🤖 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/clerk-js/src/core/tokenCache.ts`:
- Around line 437-480: The SDK-119 regression tripwire is now stale because the
broadcast failure is intentionally swallowed in tokenCache’s token update path,
so update the related test to pass normally or remove it if it no longer adds
coverage, and delete the “known bug”/`it.fails` expectation tied to this
behavior. Make sure the test still exercises the token broadcast flow around the
tokenCache update logic and reflects the new best-effort behavior, and update
the changeset/release note to describe the fix instead of referencing a known
failure.
- Line 443: The broadcast token id lookup in tokenCache should not rely on an
unsafe any cast for claims.o; keep claims.org_id as-is, but narrow claims.o to a
valid object with a string id before passing it into TokenId.build. Update the
organizationId assignment in tokenCache so it only uses claims.o.id when the
shape is verified, avoiding non-string values from reaching the broadcast match
logic.
🪄 Autofix (Beta)

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), Repository UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: e4a4eed5-733c-41cb-a954-a7eab78d61d7

📥 Commits

Reviewing files that changed from the base of the PR and between 917e259 and 5c6d888.

📒 Files selected for processing (2)
  • packages/clerk-js/src/core/__tests__/tokenCache.test.ts
  • packages/clerk-js/src/core/tokenCache.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/clerk-js/src/core/tests/tokenCache.test.ts
🛑 Comments failed to post (2)
packages/clerk-js/src/core/tokenCache.ts (2)

437-480: 📐 Maintainability & Code Quality | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether SDK-119 is still documented as an expected failure while tokenCache.ts fixes it.
rg -n "SDK-119|it\.fails|postMessage|Failed to broadcast" \
  packages/clerk-js/src/core/__tests__/tokenCache.test.ts \
  packages/clerk-js/src/core/tokenCache.ts \
  .changeset || true

Repository: clerk/javascript

Length of output: 3995


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the relevant tokenCache tests and any changeset references to SDK-119.
sed -n '480,560p' packages/clerk-js/src/core/__tests__/tokenCache.test.ts
printf '\n---\n'
sed -n '1660,1705p' packages/clerk-js/src/core/__tests__/tokenCache.test.ts
printf '\n---\n'
rg -n "SDK-119|tokenCache|postMessage" .changeset packages/clerk-js/src/core/__tests__/tokenCache.test.ts packages/clerk-js/src/core/tokenCache.ts || true

Repository: clerk/javascript

Length of output: 9301


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check whether SDK-119 is tracked in a changeset or release note.
fd -a .changeset -t f | xargs -r rg -n "SDK-119|tokenCache|postMessage" || true

Repository: clerk/javascript

Length of output: 154


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the relevant tokenCache tests and any changeset references to SDK-119.
sed -n '480,560p' packages/clerk-js/src/core/__tests__/tokenCache.test.ts
printf '\n---\n'
sed -n '1660,1705p' packages/clerk-js/src/core/__tests__/tokenCache.test.ts
printf '\n---\n'
rg -n "SDK-119|tokenCache|postMessage" .changeset packages/clerk-js/src/core/__tests__/tokenCache.test.ts packages/clerk-js/src/core/tokenCache.ts || true

Repository: clerk/javascript

Length of output: 9301


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check whether SDK-119 is tracked in a changeset or release note.
fd -a .changeset -t f | xargs -r rg -n "SDK-119|tokenCache|postMessage" || true

Repository: clerk/javascript

Length of output: 154


Remove the stale SDK-119 tripwire. This code now swallows broadcast failures, so the it.fails regression test and its “known bug” comment should be flipped to a passing test or removed if it’s redundant, and the changeset/release note should describe the fix.

🧰 Tools
🪛 ast-grep (0.44.0)

[warning] 447-447: Do not use Math.random() to generate security-sensitive values such as tokens, secrets, passwords, API keys, salts, nonces, OTPs, or session IDs. Math.random() is not cryptographically secure and is predictable. Use crypto.randomBytes()/crypto.randomUUID() (Node) or crypto.getRandomValues() (Web Crypto) instead.
Context: Math.random()
Note: [CWE-330] Use of Insufficiently Random Values.

(insecure-random-security-token-typescript)

🤖 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/clerk-js/src/core/tokenCache.ts` around lines 437 - 480, The SDK-119
regression tripwire is now stale because the broadcast failure is intentionally
swallowed in tokenCache’s token update path, so update the related test to pass
normally or remove it if it no longer adds coverage, and delete the “known
bug”/`it.fails` expectation tied to this behavior. Make sure the test still
exercises the token broadcast flow around the tokenCache update logic and
reflects the new best-effort behavior, and update the changeset/release note to
describe the fix instead of referencing a known failure.

443-443: 🎯 Functional Correctness | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

sed -n '380,470p' packages/clerk-js/src/core/tokenCache.ts

Repository: clerk/javascript

Length of output: 3948


🏁 Script executed:

sed -n '380,470p' packages/clerk-js/src/core/tokenCache.ts

Repository: clerk/javascript

Length of output: 3948


🏁 Script executed:

rg -n "claims\.o|org_id|TokenId\.build|extractTemplate|interface .*claims|type .*claims" packages/clerk-js/src/core/tokenCache.ts packages/clerk-js/src -g '!**/*.map'

Repository: clerk/javascript

Length of output: 9739


🏁 Script executed:

sed -n '1,220p' packages/clerk-js/src/core/tokenCache.ts && printf '\n---\n' && rg -n "claims\.o|org_id|TokenId\.build|extractTemplate|interface .*claims|type .*claims" packages/clerk-js/src -g '!**/*.map'

Repository: clerk/javascript

Length of output: 15873


🏁 Script executed:

sed -n '1,220p' packages/clerk-js/src/core/tokenCache.ts && printf '\n---\n' && rg -n "claims\.o|org_id|TokenId\.build|extractTemplate|interface .*claims|type .*claims" packages/clerk-js/src -g '!**/*.map'

Repository: clerk/javascript

Length of output: 15873


🏁 Script executed:

sed -n '1,220p' packages/clerk-js/src/core/tokenCache.ts && printf '\n---\n' && rg -n "claims\.o|org_id|TokenId\.build|extractTemplate|interface .*claims|type .*claims" packages/clerk-js/src -g '!**/*.map'

Repository: clerk/javascript

Length of output: 15873


🏁 Script executed:

rg -n "jwt\?\.(claims|claims:)|interface .*claims|type .*claims|claims\.o|org_id" packages/clerk-js/src packages/shared/src packages -g '!**/*.map' | sed -n '1,200p'

Repository: clerk/javascript

Length of output: 14077


🏁 Script executed:

sed -n '1,220p' packages/clerk-js/src/utils/tokenId.ts && printf '\n---\n' && sed -n '1,220p' packages/clerk-js/src/core/tokenFreshness.ts && printf '\n---\n' && rg -n "jwt\?\.(claims|claims:)|interface .*claims|type .*claims|claims\.o|org_id" packages/clerk-js/src packages/shared/src packages -g '!**/*.map' | sed -n '1,220p'

Repository: clerk/javascript

Length of output: 18631


🏁 Script executed:

sed -n '1,220p' packages/clerk-js/src/utils/tokenId.ts && printf '\n---\n' && sed -n '1,220p' packages/clerk-js/src/core/tokenFreshness.ts && printf '\n---\n' && rg -n "jwt\?\.(claims|claims:)|interface .*claims|type .*claims|claims\.o|org_id" packages/clerk-js/src packages/shared/src packages -g '!**/*.map' | sed -n '1,220p'

Repository: clerk/javascript

Length of output: 18631


🏁 Script executed:

sed -n '1,220p' packages/clerk-js/src/utils/tokenId.ts && printf '\n---\n' && sed -n '1,220p' packages/clerk-js/src/core/tokenFreshness.ts && printf '\n---\n' && rg -n "jwt\?\.(claims|claims:)|interface .*claims|type .*claims|claims\.o|org_id" packages/clerk-js/src packages/shared/src packages -g '!**/*.map' | sed -n '1,220p'

Repository: clerk/javascript

Length of output: 18631


🏁 Script executed:

sed -n '1,260p' packages/shared/src/types/jwt.ts && printf '\n---\n' && sed -n '1,260p' packages/shared/src/types/jwtv2.ts && printf '\n---\n' && sed -n '420,500p' packages/clerk-js/src/core/tokenCache.ts

Repository: clerk/javascript

Length of output: 10192


🏁 Script executed:

sed -n '1,260p' packages/shared/src/types/jwt.ts && printf '\n---\n' && sed -n '1,260p' packages/shared/src/types/jwtv2.ts && printf '\n---\n' && sed -n '420,500p' packages/clerk-js/src/core/tokenCache.ts

Repository: clerk/javascript

Length of output: 10192


Narrow claims.o before building the broadcast token id.
claims.org_id is already safe, but (claims.o as any)?.id can still pass a non-string value into TokenId.build, which can break the broadcast match. Use an object/string guard here instead of any.

🤖 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/clerk-js/src/core/tokenCache.ts` at line 443, The broadcast token id
lookup in tokenCache should not rely on an unsafe any cast for claims.o; keep
claims.org_id as-is, but narrow claims.o to a valid object with a string id
before passing it into TokenId.build. Update the organizationId assignment in
tokenCache so it only uses claims.o.id when the shape is verified, avoiding
non-string values from reaching the broadcast match logic.

Source: Coding guidelines

…cy budget

The broadcast try/catch fix for SDK-119 landed on main, so merging it in made the duplicated `it.fails` tripwire flip red ("Expect test to fail" once the body started passing). Remove it; the regular assertion already added on main covers the same contract.

Bump the legacy browser bundle budget 114KB -> 115KB so the extracted token store module fits. main sat at 113.99KB with ~10 bytes of headroom, and any separate store module costs ~50 bytes regardless of style.
@jacekradko jacekradko merged commit 3051407 into main Jun 26, 2026
53 checks passed
@jacekradko jacekradko deleted the jacek/sdk-117-decouple-tokencache branch June 26, 2026 14:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants