Add request-scoped context for session.getUser()

Use AsyncLocalStorage to provide request context so services can access
the current user without needing Call passed through every function.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-11 14:56:10 -06:00
parent afcb447b2b
commit 55f5cc699d
3 changed files with 36 additions and 4 deletions

View File

@@ -6,6 +6,7 @@ import { match } from "path-to-regexp";
import { Session } from "./auth"; import { Session } from "./auth";
import { cli } from "./cli"; import { cli } from "./cli";
import { contentTypes } from "./content-types"; import { contentTypes } from "./content-types";
import { runWithContext } from "./context";
import { httpCodes } from "./http-codes"; import { httpCodes } from "./http-codes";
import { routes } from "./routes"; import { routes } from "./routes";
import { services } from "./services"; import { services } from "./services";
@@ -75,7 +76,10 @@ routes.forEach((route: Route, _idx: number, _allRoutes: Route[]) => {
}; };
try { try {
const retval = await route.handler(req); const retval = await runWithContext(
{ user: auth.user },
() => route.handler(req),
);
return retval; return retval;
} catch (error) { } catch (error) {
// Handle authentication errors // Handle authentication errors

27
express/context.ts Normal file
View File

@@ -0,0 +1,27 @@
// context.ts
//
// Request-scoped context using AsyncLocalStorage.
// Allows services to access request data (like the current user) without
// needing to pass Call through every function.
import { AsyncLocalStorage } from "node:async_hooks";
import { AnonymousUser, type MaybeUser } from "./user";
type RequestContext = {
user: MaybeUser;
};
const asyncLocalStorage = new AsyncLocalStorage<RequestContext>();
// Run a function within a request context
function runWithContext<T>(context: RequestContext, fn: () => T): T {
return asyncLocalStorage.run(context, fn);
}
// Get the current user from context, or AnonymousUser if not in a request
function getCurrentUser(): MaybeUser {
const context = asyncLocalStorage.getStore();
return context?.user ?? AnonymousUser;
}
export { getCurrentUser, runWithContext, type RequestContext };

View File

@@ -1,9 +1,10 @@
// services.ts // services.ts
import { AuthService } from "../auth"; import { AuthService } from "../auth";
import { getCurrentUser } from "../context";
import { db, migrate, migrationStatus, PostgresAuthStore } from "../database"; import { db, migrate, migrationStatus, PostgresAuthStore } from "../database";
import { getLogs, log } from "../logging"; import { getLogs, log } from "../logging";
import { anonymousUser, type User } from "../user"; import type { MaybeUser } from "../user";
const database = { const database = {
db, db,
@@ -29,8 +30,8 @@ const misc = {
}; };
const session = { const session = {
getUser: (): User => { getUser: (): MaybeUser => {
return anonymousUser; return getCurrentUser();
}, },
}; };