From 55f5cc699de93a8ded5b94074b1f7dfe44738e3a Mon Sep 17 00:00:00 2001 From: Michael Wolf Date: Sun, 11 Jan 2026 14:56:10 -0600 Subject: [PATCH] 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 --- express/app.ts | 6 +++++- express/context.ts | 27 +++++++++++++++++++++++++++ express/services/index.ts | 7 ++++--- 3 files changed, 36 insertions(+), 4 deletions(-) create mode 100644 express/context.ts diff --git a/express/app.ts b/express/app.ts index 2e4cc0b..862f59f 100644 --- a/express/app.ts +++ b/express/app.ts @@ -6,6 +6,7 @@ import { match } from "path-to-regexp"; import { Session } from "./auth"; import { cli } from "./cli"; import { contentTypes } from "./content-types"; +import { runWithContext } from "./context"; import { httpCodes } from "./http-codes"; import { routes } from "./routes"; import { services } from "./services"; @@ -75,7 +76,10 @@ routes.forEach((route: Route, _idx: number, _allRoutes: Route[]) => { }; try { - const retval = await route.handler(req); + const retval = await runWithContext( + { user: auth.user }, + () => route.handler(req), + ); return retval; } catch (error) { // Handle authentication errors diff --git a/express/context.ts b/express/context.ts new file mode 100644 index 0000000..9f52711 --- /dev/null +++ b/express/context.ts @@ -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(); + +// Run a function within a request context +function runWithContext(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 }; diff --git a/express/services/index.ts b/express/services/index.ts index b7daf88..3a9ff69 100644 --- a/express/services/index.ts +++ b/express/services/index.ts @@ -1,9 +1,10 @@ // services.ts import { AuthService } from "../auth"; +import { getCurrentUser } from "../context"; import { db, migrate, migrationStatus, PostgresAuthStore } from "../database"; import { getLogs, log } from "../logging"; -import { anonymousUser, type User } from "../user"; +import type { MaybeUser } from "../user"; const database = { db, @@ -29,8 +30,8 @@ const misc = { }; const session = { - getUser: (): User => { - return anonymousUser; + getUser: (): MaybeUser => { + return getCurrentUser(); }, };