Session Cache
Session Cache
src/server/session-cache.ts dedupes auth.api.getSession() calls within a single request. requireUser(), requireAdmin(), and bootstrap.ts each need the session; without this, a typical page load would verify the session (real HMAC/crypto + DB cost) once per caller.
The cache key is the Headers object's identity, not its contents
const sessionCache = new WeakMap<object, Promise<Session>>();
export const getCachedSession = (
headers: Headers,
getSession: GetSessionFn = opts => auth.api.getSession(opts)
): Promise<Session> => {
let session = sessionCache.get(headers);
if (!session) {
session = getSession({ headers });
sessionCache.set(headers, session);
}
return session;
};
WeakMap keys on object identity. Two Headers instances with identical cookies are different keys and will not share a cache entry.
Always call with getRequestHeaders(), which returns the same Headers instance for the lifetime of the current request:
// RIGHT , same Headers instance every call within this request, cache hits
import { getRequestHeaders } from '@tanstack/react-start/server';
const session = await getCachedSession(getRequestHeaders());
// WRONG , fresh Headers instance, cache always misses, defeats the whole point
const session = await getCachedSession(new Headers({ cookie: someCookieString }));
A cache miss isn't a correctness bug (it just calls getSession again), but it silently reintroduces the per-call cost this file exists to remove. If you're adding a new call site and it doesn't have an obvious getRequestHeaders() available, that's a sign the call is happening somewhere unexpected (outside request context) , check that first rather than constructing a Headers object to satisfy the type.
In-flight promise, not just the resolved value
The map stores the Promise<Session>, not the awaited result, so concurrent calls that start before the first resolves also dedupe. A plain WeakMap<object, Session> only catches sequential calls within a request.
Tests: inject getSession, don't mock the module
The second parameter is a DI seam for tests, not bun:test's mock()/spyOn():
// require-gates.test.ts pattern
const session = await getCachedSession(new Headers(), async () => fakeSession);
mock.module() on auth.api.getSession leaks process-wide across test files with no reliable way to undo it (see the testing skill's note on Bun's shared-process test model). The param-injection pattern predates this cache and still works unchanged , keep using it for new tests that need a fake session.