yatta/frontend
Query cache, realtime state, and environment detection.
11 exported symbols and 42 members, read from src/types/frontend.ts.
Construct
cacheKey
functionA cache key from a route name and its arguments, so calls agree on identity.
cacheKey(name: string, args?: unknown): stringconnectRealtime
functionWraps a so its state is observable.
Takes the connection as an argument rather than making one, so the frontend and the transport are the same object. Two connections would mean two topics and two sets of events, and neither would be the one the server is pushing to.
connectRealtime(client: RealtimeClient, options?: { queueWhileOffline?: boolean; }): RealtimeHandlecreateFrontend
functionBuilds the client-side surface.
createFrontend<const R extends Record<string, RouteDef>>(source: R | App<any>, options?: FrontendOptions<R>): Frontend<R>import { createFrontend } from "yatta/frontend";import { routes } from "./api-contract"; export const frontend = createFrontend(routes, { baseUrl: "/api", realtime: createRealtimeClient({ url: "/realtime" }), cookieName: "session",});QueryCache
classA small query cache: deduplicate in flight, share one value per key.
Not a general-purpose cache and not trying to be. It exists so two components asking for the same thing share one request and one value, and so a refetch does not blank the screen.
class QueryCache11 members
entriespropertyentries:inFlightpropertyinFlight:subscribesubscribe(onChange: (changedKey?: string) => void, key?: string): () => voidSubscribes to changes.
- onChange
- Told on every change, or — when a key is given — only when that key changes. Keyed is what a bound call uses: an unkeyed subscription wakes every mounted component when any one of them writes, so fifty calls on a page each re-render on every keystroke anywhere.
- key
- Limit to this key. Omit to hear about everything.
watcherspropertywatchers:notifynotify(changedKey?: string): voidgetget<T>(key: string): CacheEntry<T>resolveresolve<T>(key: string, fetcher: () => Promise<T>, options?: { force?: boolean; }): Promise<T | undefined>Reads a key, starting a load if there is nothing fresh.
A second caller for a key already loading gets the same promise, so ten components mounting at once cause one request.
setset<T>(key: string, updater: T | ((previous: T | undefined) => T)): voidWrites a value directly, without a request.
invalidateinvalidate(key?: string, all?: boolean): voidMarks keys stale so the next read refetches.
- key
- Exact key, or a prefix ending in `:` — `"getUser:"` invalidates every call to that route whatever its arguments. A mutation on a user has to invalidate `getUser` for every id, and listing each one means writing down every id that was ever fetched.
- all
- Invalidate everything. Separate from `key`, because `undefined` means "this key" and cannot also mean "all of them".
clearclear(key?: string, all?: boolean): voidClears one key, or everything.
A cache is only per-request state, so there is no TTL sweep here: on the server the whole instance is thrown away with the render, and in the browser a reload starts empty.
evictevict(): voidDrops the oldest entries past the cap, so the cache cannot grow forever.
Types
CacheEntry
interfaceOne entry in the query cache.
interface CacheEntry<T = unknown>5 members
datapropertydata: T | undefinedThe value, or `undefined` while the first load is still in flight.
errorpropertyerror: CallError | Error | undefinedfetchingpropertyfetching: booleanWhether a request is in flight right now.
updatedAtpropertyupdatedAt: number | undefinedWhen the data was last written, as epoch milliseconds.
stalepropertystale: booleanWhether the value is from a previous request rather than this one.
Distinct from `fetching` on its own: a refetch that keeps the old data on screen means the UI can keep showing it instead of flashing empty.
CacheOptions
interfaceinterface CacheOptions2 members
staleTimepropertystaleTime?: numberHow long a value stays fresh, in milliseconds. Defaults to 30 seconds.
maxEntriespropertymaxEntries?: numberUpper bound on stored entries. Defaults to 200.
Frontend
interfaceThe whole client-side surface, in one object.
Built from the same route table the server serves, so `client` is typed from the schemas the server validates with.
interface Frontend<R extends Record<string, RouteDef>>7 members
apipropertyapi: ReturnType<typeof createClient<R>>Typed API methods, one per route.
cachepropertycache: QueryCacheQuery cache shared by every consumer of this frontend.
realtimepropertyrealtime: RealtimeHandle | undefinedRealtime, when a connection was supplied.
interactivepropertyinteractive: booleanWhether this instance can open connections.
`false` during a server render. Hooks use it to decide between "load it" and "wait for the browser", which is the difference between a working static render and a hydration mismatch.
getCookiepropertygetCookie?: (name: string) => string | undefinedReads a session cookie value. `undefined` on the server.
setHeaderspropertysetHeaders?: (headers: Headers) => voidSends on every request, for auth headers and the like.
disposedispose(): voidReleases timers and connections.
FrontendOptions
interfaceinterface FrontendOptions<R extends Record<string, RouteDef>>5 members
realtimepropertyrealtime?: RealtimeClientA realtime connection to observe. One to serve both, not two.
queueWhileOfflinepropertyqueueWhileOffline?: booleanQueue sends made while disconnected. Defaults to `true`.
cachepropertycache?: CacheOptionscookieNamepropertycookieName?: stringThe session cookie name, read on the server and sent by the browser.
Read rather than accepted as a value, so a server render and the browser agree on who the user is without the app threading a token through.
interactivepropertyinteractive?: booleanOverrides the environment check. Set false in tests.
QueryResult
interfaceRead-only view of one cache entry, as a hook receives it.
interface QueryResult<T>6 members
datapropertydata: T | undefinederrorpropertyerror: CallError | Error | undefinedfetchingpropertyfetching: booleanstalepropertystale: booleanThe value is from a previous request; a new one is in flight.
refetchpropertyrefetch: () => Promise<T | undefined>setpropertyset: (updater: T | ((previous: T | undefined) => T)) => voidWrite a value without a request. For optimistic updates.
RealtimeHandle
interfaceinterface RealtimeHandle6 members
statuspropertystatus: ConnectionStatusCurrent status. Read it at any time; it is kept up to date.
onStatusonStatus(onChange: (status: ConnectionStatus) => void): () => voidTold on every status change. Returns an unsubscribe function.
onon<T = unknown>(event: string, handler: (data: T) => void): () => voidListens for one event.
Returns an unsubscribe function, so it pairs with a component's teardown.
joinjoin(topic: string): () => voidJoins a topic. Returns the function that leaves it.
sendsend(event: string, data: unknown): Promise<void>Sends an event. Queued while disconnected rather than thrown.
queuedpropertyqueued: numberForgets queued sends. Called when the connection goes away.
ConnectionStatus
typeRealtime connection state, as observable values.
The transport holds this privately; a UI needs to read it, and re-deriving it in each component is how two components end up disagreeing about whether the app is online.
type ConnectionStatus = "connecting" | "open" | "closed"AuthConfig or JobPayload — declaring your schema once is enough for the rest to follow. See Typed keys.