import { type StoreApi, type UseBoundStore } from "zustand"; /** * Session FLAGS only. * * Who the person is, including their plan, staff bit and suspension status, comes from `GET * /auth/me` through react-query, never from here. Mirroring server state in a client store is how * two sources of truth start disagreeing, and the one that is wrong is always the one on screen. * Three repos wrote this same store; two of them wrote that same warning in a comment. * * `TUser` stays generic because it is the one part that differs: one product carries an * anonymous-browsing flag, another does not. Keep it to what a GUARD needs. All three donors had an * id and an email. */ export interface AuthState { user: TUser | null; isAuthenticated: boolean; /** * True until the first session lookup resolves. The guards must not bounce somebody to * /login while we are still finding out whether they are signed in. */ isLoading: boolean; /** * Set (or clear) the signed-in user. This ENDS the loading state, because the bootstrap was * waiting to learn who they are. Two of the three donors left `isLoading` alone here and relied * on a separate `setLoading(false)`; forgetting that call leaves every guard spinning, so the * safe default is to do it in one write. */ setUser: (user: TUser | null) => void; setLoading: (isLoading: boolean) => void; /** Sign-out: clears the user and ends the loading state in one write. */ clear: () => void; } /** * Create the store. * * The whole reason this is a factory and not a store: `isLoading` must start FALSE where there * is no window, and only a factory can decide that at the call site rather than at import. * * A session bootstrap can only be in flight in a browser. The BUILD renders this app to files with * no window at all, so `true` there is a wait that never ends. One donor shipped a route guard * holding its loading screen forever, and prerendered a public page as 1,174 bytes of * `role="status"`: a spinner as the indexable body of a page whose entire purpose was to be found. * With no browser there is no session and never will be, which is exactly the state a first-time * visitor arrives in, so that is what the page should render. * * Only one of the three donors knew this. It is invariant 7. */ export declare function createAuthStore(): UseBoundStore>>; /** * zustand's `set`, narrowed to the two writes this slice makes. * * Not `Partial & TExtra>`, which is what it means and what a caller passes: * inside a generic function TypeScript cannot check `{ isLoading }` against a partial of an * unresolved type parameter, because `TExtra` might yet declare an `isLoading` of its own. The * union says the same thing in terms it can check at both ends. Any real store's setter * satisfies it, where `TExtra` is a concrete type. */ type AuthSet = (partial: (TExtra & Partial>) | Pick, "isLoading">) => void; /** * The session flags as a plain object, for a product whose store needs more than them. * * `createAuthStore` owns its `create()` call, which means it owns the whole store: a product cannot * wrap it in `persist`, cannot add an action, and cannot add a field. Both adopters that met it had * a superset and neither could use it. One holds a remember-me choice, a profile row and the * sign-in methods themselves, under `persist`. The other adds `isAnonymous`, which is the reason * this takes a `derive` function rather than just letting the caller spread extra keys in: * `isAnonymous` is read off the user, so it has to be rewritten by `setUser` and `clear`, which are * exactly the two writes the product does not own. * * The caller keeps `create`, so middleware, extra actions and the store's own name stay theirs: * * ```ts * const useAuthStore = create & { isAnonymous: boolean }>((set) => ({ * ...authSlice(set, (user) => ({ isAnonymous: !!user?.isAnonymous })), * })); * ``` */ export declare function authSlice(set: AuthSet, derive: (user: TUser | null) => TExtra): AuthState & TExtra; export {}; //# sourceMappingURL=auth-store.d.ts.map