YouBothAgent▾
You — Business rules and flows you own. Read these yourself.
Both — Know the idea; your agent follows the details.
Agent — Conventions and references your agent follows. Look up as needed.
CLI Reference▾
AkanJS Reference▾
akanjs/fetch
akanjs/fetch holds the types that carry fetched data from a route to its components, and the client that sends those calls. Zone files import its types with import type.Words Used On This Page
TermDescription
handle
What
fetch.init*, fetch.view* and fetch.edit* return. Await it, or read one field off it.payload
Plain data a Zone receives, such as
userInitInOrg. It can cross from server to client.hydrated instance
A model class instance such as
cnst.User or a DataList. It stays in server components.slice
A named list query of one model. The
inOrg slice of user gives fetch.initUserInOrg.Zone
A module's client component. It fills the store from a payload and renders it.
Exports
exportDescription
InitHandleViewHandleEditHandle
What
fetch.init*, fetch.view* and fetch.edit* return: awaitable, or split per field.Type of a Zone's
init prop: a list payload or its promise.ClientViewClientEdit
Types of a Zone's
view and edit props for one record.ServerInitServerViewServerEdit
The same three payloads, already resolved instead of a promise.
Names the slice a component works on:
refName, sliceName and argLength.Options for loading a list: page, limit, sort, insight, default values and invalidate.
QuerySetting
{ queryKey, args }: the filter a root-slice list component runs.The account data a sign-in token carries.
The runtime client behind every app's
fetch.getRequestheaderscookies
Read the request a page is being rendered for. Server only.
Everything else
HttpClient, WsClient, AgentTurn, request helpers like getRequestTheme, and client types.InitHandle / ViewHandle / EditHandle
A route's
fetch.init*, fetch.view* and fetch.edit* calls return a handle. Await it and you get the same object these helpers always gave; read a field off it and you get that field's own promise.So each section renders as soon as its own data lands, and the page never waits for the slowest query.
Three Handles
| Handle | Returned by |
|---|---|
| ↳ Fields | |
InitHandle | fetch.init<Model><Suffix>(...args, option?) |
| <model>Init<Suffix> · <model>List<Suffix> · <model>Insight<Suffix> | |
ViewHandle | fetch.view<Model>(id, option?) |
| <model> · <model>View | |
EditHandle | fetch.edit<Model>(id, option?) |
| <model> · <model>Edit | |
Splitting A Page
Destructure the handle instead of awaiting it, and hand each field to the section that renders it:
apps/myapp/page/org/[orgId]/_index.tsx
- Every request leaves at call time. Splitting the result never makes the queries run one after another.
- The list lands first.
<model>List<Suffix>resolves when the rows arrive.<model>Init<Suffix>also waits for the count, because it carrieslastPageOf<Model>. - Await what the first HTML needs.
awaitstill returns the whole object and keeps that section in the shell, which SEO snapshots and prerendering read. - Just the payload?
fetch.get<Model>Init<Suffix>,fetch.get<Model>Viewandfetch.get<Model>Editreturn it as a plain promise.
Where Each Field Goes
Field
Zone
init · view · edit
Server
Unit · View · Load.Stream
Plain payload
<model>Init<Suffix>
✓
The list payload. Pass it to the Zone's
init prop.<model>View · <model>Edit
✓
One record's payload. Pass it to the Zone's
view or edit prop.Hydrated instances
<model>List<Suffix>
✓
A
DataList of Light models, such as a list to count.<model>Insight<Suffix>
✓
The aggregate as an Insight model instance.
<model>
✓
The full model instance of one record.
✓hand it herenot here


Never pass a hydrated instance to a Zone. React Flight refuses class instances as client-component props, so
<model>List<Suffix>, <model>Insight<Suffix> and <model> stay in server components.ClientInit
ClientInit is the type of a Zone's init prop. It takes the resolved list payload or the <model>Init<Suffix> promise from the init handle; a pending promise renders behind the Zone's own Suspense boundary.A list Zone declares it like this:
apps/myapp/lib/user/User.Zone.tsx
Type Parameters
Two are usually enough: the ref name and the Light model. The other three default to
any.RefNamestring
The model's ref name, such as
"user". The payload's keys are named after it.Light
The Light model each row is, such as
cnst.LightUser.Insightdefault any
The Insight model of the aggregate.
QueryArgsdefault any
The slice's argument tuple.
Filterdefault any
The model's filter class. It types the sort key.
What The Payload Holds
Every key but the first three is named after the model. For
user, the rows are userObjList:keyDescription
refNamesliceNameargLength
Which slice the list came from: the same three fields as
SliceMeta.<model>ObjList
The rows, as plain objects.
<model>ObjInsight
The aggregate as a plain object.
null when loaded with insight: false.pageOf<Model>limitOf<Model>lastPageOf<Model>
The current page, the page size, and the last page worked out from the count.
hasMoreOf<Model>
Whether another batch exists, read off the batch size rather than the count.
queryArgsOf<Model>sortOf<Model>
The arguments and the sort key the list was loaded with.
<model>InitAt
When the list was loaded.
ClientView / ClientEdit
ClientView and ClientEdit are the Zone prop types for one record. Each takes the resolved payload or the promise the view or edit handle hands out.| Type | Handle field |
|---|---|
| ↳ Consumed by | |
ClientView | fetch.view<Model>(id) → <model>View |
The view prop of Load.View. | |
ClientEdit | fetch.edit<Model>(id) → <model>Edit |
The edit prop of Load.Edit. | |
Both payloads have the same three keys:
keyDescription
refName
The model's ref name.
<model>Obj
The record, as a plain object.
<model>ViewAt
When the record was loaded. The edit payload uses this same key.
A Zone that shows a ticket and edits it:
apps/myapp/lib/ticket/Ticket.Zone.tsx
- A new record needs no request. The
editprop ofLoad.EditandModel.EditModalalso takes a partial model, so a new-record page passes default values instead of a payload. - The full model is a separate field.
<model>on the same handle is a hydrated instance for server components; the Zone takes only the payload.
SliceMeta
SliceMeta names the slice a component works on. Model.* and Data.* components take it as their slice prop, to know which store and which list to update after a save.FieldDescription
refName
The model's ref name, such as
"ticket".sliceName
Ref name plus slice suffix, such as
ticketInProject. The root slice's is the ref name itself.argLength
How many query arguments the slice takes.
Read one off
fetch.slice, and let a component take it as an optional prop:apps/myapp/lib/ticket/Ticket.Util.tsx
fetch.sliceholds one per slice, keyed bysliceNameand typed from the app's signals.- Every list payload carries the same three fields, so
Load.Unitsfinds its slice frominitalone.
FetchInitForm
FetchInitForm is the option object for loading a list: which page, how many rows, what order, and whether to count. It is the last argument of fetch.init<Model><Suffix>() and st.do.init<Model><Suffix>().pagenumberdefault 1
The page to load, counted from 1.
limitnumberdefault 20
Rows per page.
sortExtractSort<Filter>default "latest"
One of the filter's sort keys.
latest, oldest and relevance always exist.insightbooleandefault true
false skips the aggregate query, so <model>ObjInsight is null and there is no total.defaultPartial<DefaultOf<Input>>st.do.init*
Values the slice's form starts from, and returns to after each save.
invalidatebooleandefault falsest.do.init*
false reuses a list already loaded with the same arguments, page, limit and sort.Its type arguments,
Input and Filter, type default and sort. Fields tagged st.do.init* are read by the store only. The defaults above apply to fetch.init*; st.do.init* keeps the list's current page, limit and sort when you leave them out.A member list that shows no total loads without the count:
apps/myapp/page/org/[orgId]/member.tsx
- Pass
insight: falsewhen the screen shows no total and no pagination. The rows in hand are then the whole count there is. - The same object takes per-call options.
fetch.init*also acceptstoken,timeoutand the otherFetchPolicyfields in it. - List components take it as their init prop.
Data.CardList,Data.TableListandData.ListContainerpass it on tost.do.init*.
Account
Account is the account data a sign-in token carries. It always has appName and environment, and its type argument adds the app's own claims.Read the current account with
getAccount() from akanjs/client, in a page render or in the browser:apps/myapp/webkit/cookie.ts
- A token for another app reads as signed out.
getAccount()returns only{ appName, environment }when the token was issued for another app or environment. getDefaultAccount()is that signed-out value, built from the current env'sappNameandenvironment.- The server decodes the same shape.
AccountMiddlewareinlibs/sharedputs it on each call, and guards read it withcontext.get("account").
FetchClient
FetchClient turns the app's signal metadata into typed HTTP and WebSocket functions. The fetch an app imports is a proxy around one instance, so its instance methods are callable on fetch itself.MemberDescription
new FetchClient(origin)
Builds a client for an API origin such as
getEnv().serverHttpUri, prefix included.setJwt(jwt)
Sends this token with every later call, over HTTP and WebSocket.
null clears it.clone({ origin, jwt, connect })
A copy with the same endpoints, for another origin or user.
connect defaults to true.setTimeout(ms)
Budget for calls whose endpoint and caller name none: 30 seconds by default,
false for no limit.connect()disconnect()
Open or close the WebSocket that
pubsub and message endpoints use.fetch.instance
The
FetchClient inside an app's fetch proxy.FetchClient.fromFetchClient.build
Build an app's
fetch in the generated lib/sig.ts and lib/useClient.ts.Signal tests use
clone to call the server as a signed-in user:apps/myapp/lib/user/user.signal.spec.ts
- One clone per user. Each clone carries its own token, so two users can call the same server side by side.
connect: falseskips the WebSocket for a copy that only makes HTTP calls. The API explorer clones this way.- Never write the API prefix by hand.
getEnv().serverHttpUrifromakanjs/basealready ends with it.
getRequest / headers / cookies
These read the request a page is being rendered for.
akanjs/fetch pulls in no client code, so a server component can import them freely.getRequest()Request | undefined
The request being rendered.
headers()Map<string, string>
The request headers, keys in lower case. A new Map on every call.
cookies()Map<string, { name, value }>
The parsed
Cookie header. A j: value is decoded as JSON.getRequestStore()AkanRequestStore | undefined
The whole per-request store: the request, its theme and its query cache.
A page can read them while it renders:
apps/myapp/page/_index.tsx
- They see a request only while a page renders. Anywhere else the maps are empty and
getRequest()isundefined. - Endpoints read the caller another way:
.with(Self)in the signal, orcontext.get("account")in a guard. - Code that runs on both sides uses
akanjs/client. ItsgetCookie(key)reads the request on the server anddocument.cookiein the browser.