62 lines · 2.6 KB · TypeScript
RawDownload
import * as Atom from "effect/unstable/reactivity/Atom"; import { AdminApiClient } from "./admin-client";import { ReactivityKey } from "./reactivity-keys"; // ---------------------------------------------------------------------------// Admin atoms — the tenant-wide users view behind `/admin/users*`.//// Read-only by construction: the admin plane serves GETs only (the owner policy// rejects writes at tenant reach), so there are no mutations here and every// atom carries the same reactivity key.//// Paging and the search term are part of the atom identity, so each page of// each search is its own cache entry and stepping back to a visited page is// instant. `Atom.family` (not a bare arrow) because the page component// re-derives the key object on every render — a fresh atom per render would// refetch in a loop.// --------------------------------------------------------------------------- /** How many users one page of the list shows. Well inside the contract's *  1..500 bound; the joined endpoint reads per-user connections, so a modest *  page keeps that join cheap. */export const ADMIN_USERS_PAGE_SIZE = 25; export interface AdminUsersPage {  readonly limit: number;  readonly offset: number;  /** The `?search=` term (name or email substring), already debounced by the   *  page. `""` is no filter and is sent as no param at all, so the unfiltered   *  list keeps one cache identity regardless of how the term was cleared. */  readonly search: string;} /** * One page of users joined with their connections — what the list renders. * * Asks for one row MORE than the page size so the pager can tell "there is a * next page" from "this is the last one" without a count endpoint (the contract * returns no total). The page component drops the extra row. */export const adminUsersWithConnectionsAtom = Atom.family((page: AdminUsersPage) =>  AdminApiClient.query("adminUsers", "listUsersWithConnections", {    query: {      limit: page.limit + 1,      offset: page.offset,      ...(page.search === "" ? {} : { search: page.search }),    },    timeToLive: "30 seconds",    reactivityKeys: [ReactivityKey.adminUsers],  }),); /** One user's connections, for the detail pane. A separate read rather than a *  slice of the list so opening a user is correct even when the list page it *  came from has since gone stale. */export const adminUserConnectionsAtom = Atom.family((externalId: string) =>  AdminApiClient.query("adminUsers", "listUserConnections", {    params: { externalId },    timeToLive: "30 seconds",    reactivityKeys: [ReactivityKey.adminUsers],  }),);