import { createMiddleware } from "@tanstack/react-start";
import { notFound } from "@tanstack/react-router";
import { sessionViewer } from "~/server/auth";
import { HttpError, unauthorized } from "~/server/errors";
import { clientSafeError } from "~/server/client-safe-error";
import type { Viewer } from "~/lib/types";
import { paymentErrorFor } from "~/server/metering/access";
import { QuotaExceededError } from "~/server/metering/quota";
const HTTP_NOT_FOUND = 404;
/** Resolves the session viewer for every server function and maps 404s onto router not-found handling. */
export const viewerMiddleware = createMiddleware({ type: "function" }).server(async ({ next }) => {
const viewer = await sessionViewer();
try {
return await next({ context: { viewer } });
} catch (error) {
if (error instanceof HttpError && error.status === HTTP_NOT_FOUND) throw notFound();
const http = error instanceof QuotaExceededError ? error.toHttpError() : error;
if (http instanceof HttpError) throw await paymentErrorFor(http, viewer?.id ?? null);
throw http;
}
});
export function requireViewer(viewer: Viewer | null): Viewer {
if (!viewer) throw unauthorized("You must be signed in");
return viewer;
}
/**
* Registered globally in start.ts: a quota error from a server function that skips viewerMiddleware still
* reaches the client as a 402 (not a masked 500), with billing context for whoever is signed in, and
* unexpected errors are masked by `clientSafeError`.
*/
export const paymentErrorMiddleware = createMiddleware({ type: "function" }).server(async ({ next }) => {
try {
return await next();
} catch (error) {
if (!(error instanceof QuotaExceededError)) throw clientSafeError(error);
throw await paymentErrorFor(error.toHttpError(), (await sessionViewer())?.id ?? null);