import type { ReactNode } from "react"; /** * Route guards. * * Two donors wrote these four, gave two of them the same names, and explained them in nearly the * same words. What differed was one line. That line is invariant 4, so it is the reason this file * exists rather than being copied a ninth time. * * These are factories because the destinations are the app's: `paths.signIn` is not something a * package can know, and one donor hardcoded `/login` in four places for exactly that reason. * * A guard is a COURTESY, never the boundary. The API refuses what it refuses whatever any of * these decide. What they buy is that somebody who types a URL lands on one screen that * explains itself instead of four panels each failing separately. */ /** What the guard needs off the session store. */ export interface SessionState { isAuthenticated: boolean; isLoading: boolean; } /** * The answer to a "who is this?" query, in the three states it actually has. * * This shape is invariant 4. A guard reading `!me?.isStaff` collapses "pending", "failed" and "no" * into one branch, so an operator arriving while `/auth/me` is 500ing is told the page does not * exist. Wrong cause, no retry, and no request id to quote to support. One donor hit that and fixed * it; the other still has the collapsed version. * * `"pending"` and not `"loading"` for one measured reason: it is react-query's own word for this * state since v5, and with the words matching a `UseQueryResult` satisfies this type * STRUCTURALLY: `createRequireProfile(useMe, …)` takes the hook directly, no adapter. With * `"loading"` it does not typecheck and every adopter hand-writes the same six-line mapping; eight * of the twelve repos this serves are on react-query, and the first migration wrote that mapping * before anyone noticed. Nothing here imports react-query at runtime. The type stays structural, so * an app on SWR or a plain `useState` still answers it in three lines. */ export type MeQuery = { status: "pending"; } | { status: "error"; error: unknown; } | { status: "success"; data: TMe; }; export interface GuardOptions { /** Drawn while the session or the profile is still resolving. */ loading: ReactNode; } /** Query parameter carrying the route a sign-in interrupted. */ export declare const AUTH_RETURN_PARAM = "next"; /** * Require sign-in and remember where they were headed, so the redirect afterwards lands on the page * they actually wanted rather than the home screen. */ export declare function createRequireAuth(useSession: () => SessionState, signInPath: string, { loading }: GuardOptions): ({ children }: { children: ReactNode; }) => import("react").JSX.Element; /** The mirror: somebody already signed in has no business on the sign-in screen. */ export declare function createRequireAnonymous(useSession: () => SessionState, homePath: string, { loading }: GuardOptions): ({ children }: { children: ReactNode; }) => import("react").JSX.Element; /** * A gate on a profile field, such as whether the person is suspended, staff or on a given plan. * * `allow` gets the profile and answers yes or no. `onDenied` draws the refusal; `onError` draws * the failure, and they are SEPARATE arguments on purpose. That separation is the whole point * of this file: "you may not see this" and "we could not find out" are different sentences, and * showing the first when the second is true is how an outage becomes a support ticket about * permissions. */ export declare function createRequireProfile(useMe: () => MeQuery, { loading }: GuardOptions): (allow: (me: TMe) => boolean, onDenied: ReactNode | (() => ReactNode), onError: (error: unknown) => ReactNode) => ({ children }: { children: ReactNode; }) => import("react").JSX.Element; //# sourceMappingURL=guards.d.ts.map