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▾
UI Reference▾

akanjs/base

akanjs/base holds the value types models and signals are built from, plus a few runtime helpers. It imports nothing else from Akan, so server files, client files and common/ can all use it.
import { dayjs, enumOf, ID, Int } from "akanjs/base";
A document id: a 24-character hex string.
Whole numbers and decimals. JavaScript Number is not a field type.
An open value Akan does not check. You name its shape with a type argument.
Raw bytes in a signal argument or return.
A file in the body of the upload mutation.
dayjsDayjs
The date library and its type. Every Date field holds a Dayjs.
Turns a fixed list of values into an enum class.
The running app's name, environment and server addresses.
getApiPrefixgetWsPrefix
The paths that signals and the websocket are served under.
A list keyed by id. Every model list in a store is one.
Where Each Type Goes
Type
Model field
Signal argument
Signal return
Import from akanjs/base
ID
✓
✓
✓
Document ids.
Int · Float
✓
✓
✓
Counts and decimals.
Any
✓
✓
✓
Payloads whose shape stays open.
Binary
✓
✓
Keep stored bytes in a File model.
Upload
✓
Only in the body of a fileUpload: true mutation.
JavaScript globals, no import
String
✓
✓
✓
Plain text.
Boolean
✓
✓
✓
Text such as "true", "false", "1" and "0" is read as a boolean.
Date
✓
✓
✓
The value is a Dayjs, not a JavaScript Date.
✓Can be usedNot used here
  • Arrays wrap the type. field([Float]) is a list of decimals, and .body("files", [Upload]) takes several files.
  • The three globals need no import. Akan extends String, Boolean and Date so they work as types as they are.
Values Without a Default
A required field with no default starts at the value below. An .optional() field starts at null.
TypeTypeScript valueWithout a default
IDstring""
Int · Floatnumber0
AnyT — field<T>(Any)null
Stringstring""
Booleanbooleanfalse
DateDayjsdayjs(new Date(-1))

ID

ID is a document id: a 24-character hex string, not a UUID. Use it for an id a model keeps without a relation, and for id arguments of a signal.
The file meta scalar keeps the id of a file it does not load:
libs/shared/lib/__scalar/fileMeta/fileMeta.constant.ts
  • A relation uses the model class. field(File) stores a relation to a file; field(ID) stores only the id string.
  • The format is checked. Anything but 24 hex characters is refused. The empty string "" passes as the placeholder for an id not set yet.
  • Name the owner with ref. field(ID, { ref: "org", cascade: "removeWith" }) removes this document when that org is removed.

Int

Int is a whole number: counters, quantities, page numbers, metric samples. A value that is not a safe integer is refused.
The access stat scalar counts four things, each starting at zero:
libs/util/lib/__scalar/accessStat/accessStat.constant.ts
  • Number is not a type. field(Number) and .body("x", Number) fail to typecheck. Pick Int or Float.
  • Text is converted. A query-string or form value such as "3" arrives as the number 3.

Float

Float is any finite number: coordinates, rates, balances, resource metrics. Use it when a fraction is valid data; NaN and Infinity are refused.
The coordinate scalar keeps a longitude/latitude pair and an altitude:
libs/util/lib/__scalar/coordinate/coordinate.constant.ts
  • Whole numbers stay Int. A count, a quantity or an index is Int, even when it could be stored as a float.
  • Text is converted. "1.5" from a query string arrives as 1.5.

Any

Any is a value whose shape Akan does not check. Use it for integration payloads and loose metadata; when the shape is stable, declare real fields instead.
An event payload that keeps its body open but typed:
apps/myapp/lib/__scalar/eventPayload/eventPayload.constant.ts
  • Name the shape. The type argument in field<Record<string, unknown>> keeps the value typed in TypeScript, though nothing checks it at runtime.
  • Give an object default as a function. A literal {} is one object shared by every instance; () => ({}) gives each its own.
  • Agents do not get it. A signal that returns Any, or takes a required Any argument, is not published to MCP.

Binary

Binary carries raw bytes in a signal argument or return. It is a Uint8Array on both sides, and a Node Buffer is one, so you can pass it straight in.
Requests and Responses
Sent as a base64 string in JSON. Either side accepts base64 or bytes.
pubsub(Binary)
Sent as a websocket binary frame, with no JSON and no base64. Only when the whole return is Binary.
A stream endpoint with one lossy room and one room that must see every frame:
apps/myapp/lib/_stream/stream.signal.ts
  • A slow subscriber gets the newest frame. By default a pubsub(Binary) room keeps only the latest frame, which suits telemetry and video.
  • backpressure: "queue" keeps every frame. Use it for a sequence such as deltas; the send buffer then grows with the slowest subscriber.
  • Agents do not get it. A signal that returns Binary is not published to MCP.

Upload

Upload is a file in the body of the upload mutation, and nowhere else. An app that mounts libs/shared already has that mutation:
libs/shared/lib/file/file.signal.ts
  • Only with fileUpload: true. Upload is valid only in the body of a mutation flagged this way, and that mutation is never published to MCP.
  • One per app. The generated fetch.add<Model>Files(fileList) and store action upload<Field>On<Model>(fileList) both post to the mutation marked fileUpload: true. With two, only the first is used.
  • The body is fixed. The client always sends files, metas, type and parentId, and fileList may be a File[] or the FileList from input.files.
  • Models reference File. Declare image: field(File).optional() or images: field([File]), never field(Upload).

dayjs / Dayjs

akanjs/base re-exports the dayjs function and its Dayjs type. Every Date field holds a Dayjs, so documents, stores, services and UI all use the same API.
A date field whose default is the moment each record is made:
libs/util/lib/__scalar/accessLog/accessLog.constant.ts
Reading the value is ordinary dayjs; import the type the same way:
apps/myapp/common/dayLabel.ts
  • Import it from akanjs/base. Pages and module files may not import a third-party package, so import dayjs from "dayjs" fails lint there.
  • "Now" is a function. default: () => dayjs() runs for each record; default: dayjs() would freeze the time the module loaded.

enumOf

enumOf(name, values) turns a fixed list of values into an enum class. Use the class as a field or argument type; its static helpers read the list.
A job status declared once and used as a field:
apps/myapp/lib/job/job.constant.ts
Static Helpers
values
The list exactly as declared.
has(value)
Whether the value is in the list.
indexOf(value)
The value's position. Throws when the value is not in the list.
find(fn)findIndex(fn)
Like the Array methods, but throw when nothing matches.
filter(fn)map(fn)forEach(fn)
Same as the Array methods.
JobStatus["value"]
The type of one value: the union of the list.
  • camelCase name, as const list. The first argument names the enum; without as const the values widen to string.
  • The list decides the type. Strings make a String enum, whole numbers an Int enum, other numbers a Float enum. An empty list throws.
  • Signal arguments are checked. An argument typed with an enum refuses any value outside the list.
  • Labels live in the dictionary. Translate each value in the module dictionary's .enum() stage.

getEnv

getEnv() tells running code about its app: the name, the environment, and where the web and API servers are. The first call reads the environment variables; later calls return the same cached object.
appNamestring
From AKAN_PUBLIC_APP_NAME. Required.
repoNamestring
From AKAN_PUBLIC_REPO_NAME. Required.
serveDomainstring
From AKAN_PUBLIC_SERVE_DOMAIN. Required.
environment"testing" | "debug" | "develop" | "main" | "local"default "debug"
From AKAN_PUBLIC_ENV.
operationMode"local" | "edge" | "cloud" | "module"default "cloud"
From AKAN_PUBLIC_OPERATION_MODE. It is "local" when environment is "local".
databaseMode"single" | "multiple" | "cluster" | undefined
From AKAN_DATABASE_MODE, else the mode the app declares. undefined in the browser.
side"server" | "client"
Whether this code is running on the server or in the browser.
renderMode"ssr" | "csr"default "csr"
From AKAN_PUBLIC_RENDER_ENV.
apiPrefixstringdefault "/api"
The value getApiPrefix() returns.
wsPrefixstringdefault "/ws"
The value getWsPrefix() returns.
clientHttpUristring
The web origin, such as http://localhost:8282. Also split into clientHost and clientPort.
serverHttpUristring
The API base with the prefix, such as http://localhost:8282/api.
serverWsUristring
The websocket origin without a path, such as ws://localhost:8282.
A helper that builds the app's public host from the env:
apps/myapp/srvkit/publicHost.ts
  • Runs on both sides. side says whether the code is on the server or in the browser.
  • The types are exported too. ClientEnv is what getEnv() returns, Environment is the union of environment names, and BackendEnv types the server options.

getApiPrefix / getWsPrefix

getApiPrefix() returns the path signals are served under, and getWsPrefix() the websocket's path under it. Build URLs with them instead of writing /api or /ws, because an app can move both.
WhereSetting
↳ Who follows it
main.tsnew AkanApp({ prefix, websocketPrefix })
The server's routes, and every page the server renders.
akan.config.tsapi: { prefix, websocketPrefix }
A prebuilt CSR shell and a native app bundle, which no server renders.
(nothing set)"/api" · "/ws"
The defaults.
The OAuth consent page posts to a signal endpoint under the prefix:
libs/shared/page/oauth/consent/_index.tsx
  • A leading slash, never a trailing one. Appending "/path" is always safe. A blank value or a bare / counts as not set.
  • The websocket sits under the API prefix. The client connects to serverHttpUri plus getWsPrefix(), which is ws://localhost:8282/api/ws by default.
  • Call it where you build the URL. A moved prefix reaches the browser too, so there is no need to pass it down as a prop.

DataList

DataList is a list of light models that also finds rows by id. Every list a slice puts in a store is one: <model>List, <model>InitList and <model>Selection.
A store action that puts an updated admin back into its list:
libs/shared/lib/admin/admin.store.ts
Methods
new DataList(rows)
Builds a list from an array. A repeated id keeps the last row.
set(row)
Replaces the row with the same id, or appends it. Changes this list and returns it.
delete(id)
Removes the row with that id. Changes this list and returns it.
save()
A new DataList with the same rows. Hand this to this.set().
get(id)pick(id)
The row with that id. get returns undefined when it is missing; pick throws.
has(id)indexOf(id)at(idx)pickAt(idx)
Look up by id or position. indexOf and pickAt throw when nothing is there.
filterslicesort
Return a new DataList.
mapforEachfindsomeeveryreduce
Same as the Array methods. for...of works too.
valueslength
The rows as a plain array, and how many there are.

Released under the MIT License

Connect your AI to these docs

MCPhttps://akanjs.com/mcp
Copyright © 2026 Akan.js All rights reserved.System managed bybassman