Ultimate

Client data

Every browser request goes through one function, and every entity row it returns lands in one record per entity:id. You never write fetch, never write a store, and never mirror a row into a signal by hand. Declare the entity, return its row, call the typed client.

Shipped in 21.0.0, both halves: the HTTP seam and the store that receives its records, with the hooks that read it. The store is installed the first time a realtime hook runs on the page; records answered before that wait on the page handle until it is. The design is docs/architecture/21-client-data-layer.md.

One idiom per task

TaskWriteStatus
declare what a record isentity('post', { columns: { id: uuid().primaryKey(), … } })shipped
an action that returns a recordoutput: Post.$schema, or a schema that contains itshipped in 21.0.0
a query whose rows are recordsrows: Post.$schema on query()shipped in 21.0.0
call an action from an islandrpc<Api['actions']>({ baseUrl: '' })shipped; now goes through the one transport
call a query from an islandqueryClient<Api['queries']>({ baseUrl: '' })shipped; now goes through the one transport
ignore an answer for a principal that signed outif (isSuperseded(error)) return;shipped in 21.0.0
any other failureisUltimateError(error), then switch on error.codeshipped
install realtime in an islandnothing: x build does it for every island whose own graph imports @ultimat3/realtime. An island that reaches realtime only through a package calls installRealtime({ signal: createSignal }) in mountshipped in 21.0.0
show one record in a componentuseRecord('post', id): record type, then record key, from @ultimat3/realtimeshipped in 21.0.0
show a listuseQuery({ name: 'postList', entity: 'post' }, input), or { name, live: true } for a live queryshipped in 21.0.0
read once, no socketuseQuery({ name: 'postList', entity: 'post' }, input) with no live — one HTTP GET through the query client, rows into the same store. Nothing opens a socket (packages/realtime/src/use-query.ts): a list that needs no push needs no timer either — never pollshipped in 21.0.0
re-read a list on a timeruseQuery(ref, input, { poll: pollEvery(ms) }) on a non-live ref — refetch through core's visibleInterval, in the bundle only of an island that imports pollEvery: no read while the tab is hidden, one make-up read when it shows, none after a lost session, stopped by release(). The safety read beside an at-most-once channel. A live ref refuses poll by type. Never a hand-rolled setIntervalshipped in 30.0.0
poll what has no changefeedvisibleInterval(run, ms, { maxMs? }) from @ultimat3/core/page, the disposer from onCleanup: no call while the tab is hidden, one make-up call when it shows; with maxMs each failed run (a throw or rejection, re-raised for the page's error hook) doubles the wait up to it, jittered, and a success resets it to ms; an async run is never overlapped. The first X_UNAUTHENTICATED / X_SESSION_EXPIRED on a page rendered for a principal is a lost session: every visibleInterval stops, sessionIsLost() is true and onSessionLost(fn) fires once (late subscribers at once) until rescope(). An anonymous page's 401 is never a loss. The sign-in UI is the app's. guards/raw-interval.ts refuses a bare setInterval( in an islandshipped in 30.0.0
follow a channeldeclare it on one ref in two halves: channelRef('org-feed', { params, catchUp }) for the browser, channel(ORG_FEED, { records, policy }) on the server. Then useChannel(ORG_FEED, { orgId }, { onEvent, onPresence }) in the island: records reach the store, events the handlers. usePresence(ORG_FEED, params) for a roster. An island whose only realtime is this is never hidden while it boots (Realtime)shipped in 21.0.0
redraw markup that holds other islandsdisposeIslands(node) from @ultimat3/render/client on each element being dropped, before or after it leaves the page: every island under it (and the node itself, when it is one) has what its mount returned called, and one that never booted mounts nothing. Never read el.__x yourselfshipped in 27.6.0
sign outan action (the reference app's endSession, POST /api/sessions/end), posted by a native form, whose response appends signOutHeaders() from @ultimat3/auth: Clear-Site-Data: "cache", "storage" empties the page store, local storage and the service worker's cache. The redirect is a full navigation, so the next document carries the new scopeshipped in 21.0.0
keep a record type on disk across reloadsentity('post', { columns, persist: true }). Default false: a record is private data, and disk is a decisionshipped in 21.0.0
write with an optimistic updateuseMutation({ name: 'renamePost', local, conflict }), posted over HTTPshipped in 21.0.0

Never write fetch(, new WebSocket( or new XMLHttpRequest( in an island, or in anything an island imports. x verify's boundaries step refuses it with X_BROWSER_TRANSPORT_BYPASS and names the line — in every app, As of 2026-10. Built in, not a file in guards/: there is no opt-out and no allowlist.

The raw call wasWrite instead
an action (POST /api/…)await browserClient.<action>(input) — rpc<Api['actions']> in shared/browser-client.ts
a read (GET /_x/query/…)useQuery(<QUERY_REF>, input); outside a component, await browserQueries.<query>(input)
a socket or an EventSourceuseQuery(<QUERY_REF>, input) for rows, useChannel(<CHANNEL_REF>, params, { onEvent }) for events
a file uploadawait uploadFile({ file, grant, onProgress }) from @ultimat3/storage — the presigned PUT is its, not yours
anything elseawait clientTransport({ method: 'GET', url }) from @ultimat3/core/page
a page or fragment that answers HTML, CSV or plain textawait clientTransport({ method: 'GET', url, responseType: 'text' }) — the body as a string, never read off onResponse
FactRule
browser-reachableevery *.island.tsx, every module that calls clientTransport / pageClient itself, and what each imports — followed name by name through barrels, into the app's own packages/*
not reporteda route.ts, a job, a task, a test: server code no island imports
the package boundarythe walk stops at node_modules. @ultimat3/*'s three seams are trusted, never re-read
a server barrel@ultimat3/entity or @ultimat3/query imported for a VALUE in that closure is X_BROWSER_SERVER_BARREL; the fix names @ultimat3/entity/record / @ultimat3/query/client
cannot seea browser module that is no island, names no seam and is imported by neither; a request inside an installed package; globalThis['fetch']; a bare fetch( in a file that binds its own fetch

1. The entity is the record

// app/posts/entity.ts
import { entity, text, uuid } from '@ultimat3/entity';

export const Post = entity('post', {
  columns: { id: uuid().primaryKey(), orgId: uuid(), title: text({ max: 120 }) },
});

The record type is the entity name (post). The record key is the primary key, in declared order. Nothing else is declared.

2. An action that returns the row

// app/posts/actions/rename-post.ts
import { action, t } from '@ultimat3/action';
import { Post } from '../entity';
import { postWrite } from '../policies';
import { repo } from '../repo';

export const renamePost = action({
  input: t.object({ postId: t.uuid, title: t.string }),
  output: Post.$schema, // or t.object({ post: Post.$schema.nullable() }): found at any depth
  policy: postWrite,
  handle: ({ input }) => repo.rename(input.postId, input.title), // returns the WHOLE row
});

Because the output schema references Post.$schema, the response is { data, records } with the header x-ultimate-records: 1. Nothing to declare. .pick(), .omit() or .extend() on the row schema drops the brand, so a partial row never overwrites a full record.

3. A query whose rows are records

// app/posts/queries.ts
import { from, query, t } from '@ultimat3/query';
import { Post } from './entity';
import { postRead } from './policies';
import { repo } from './repo';

type PostRow = typeof Post.$row;

export const postList = query({
  input: t.object({ orgId: t.uuid }),
  policy: postRead,
  rows: Post.$schema, // without this line the rows never reach the store
  sql: ({ orgId }) =>
    from<PostRow>('posts', () => repo.list()).where({ orgId }).orderBy('id'),
});

sql: names its table as a string, so rows: is the one place the schema comes from. Leave it off and the query still works, but its answer is bare rows the store never sees.

4. Call both from an island

// shared/browser-client.ts — type-only import of the api, so no server code reaches the island
import { rpc } from '@ultimat3/action';
import { queryClient } from '@ultimat3/query/client'; // the browser entry: 11,012 B vs 25,237 B via the barrel
import type { Api } from '../api';

export const browserClient = rpc<Api['actions']>({ baseUrl: '' });
export const browserQueries = queryClient<Api['queries']>({ baseUrl: '' });
FactRule
who writes the filex new — apps/web/shared/browser-client.ts, both lines, with browser-client.test.ts beside it
app sizeboth lines typecheck at any size — pinned at 300 actions in 100 modules and 100 reads in 50 (client-scale-pins.ts in each package). As of 2026-10; before, a 48th module in one defineApi list was TS2589
pathStylestated nowhere here. The server stamps defineApi({ http: { pathStyle } }) into the document; the browser derives every action URL under it
// app/posts/rename.island.tsx
import { isSuperseded, isUltimateError } from '@ultimat3/core';
import { browserClient } from '../../shared/browser-client';

export async function rename(postId: string, title: string): Promise<void> {
  try {
    await browserClient.renamePost({ postId, title }); // returns `data`, the envelope is stripped
  } catch (error) {
    if (isSuperseded(error)) return; // the page changed principal: render nothing
    if (isUltimateError(error) && error.code === 'X_CLIENT_TRANSPORT_FAILED') {
      // no usable answer: the network, a proxy, or a non-JSON body
    }
    throw error;
  }
}

baseUrl: '' is the page's own origin. A browser chunk has no process.env.

How a record travels

#WhereWhat happensStatus
1serverthe action or query route runs rowsOf(schema, answer), collecting every branded row as record type → record key → rowin tree
2wire{ data, records, removed? } behind x-ultimate-records: 1. An answer with no entity rows is byte-identical to beforein tree
3browserclientTransport decodes the envelope, checks the principal has not changed, and calls pageClient().store.adopt(type, rows)in tree
4browserthe one RecordStore per tab, shared by every island through globalThis[Symbol.for('ultimate.client')], updates the record oncein tree (packages/realtime/src/page-store.ts). Installed by the first realtime hook on the page. Until one runs, step 3 finds no store and the page holds the records (packages/core/src/pending-records.ts), handing them over on install
5componentevery useRecord / useQuery showing that record re-renders, with no refetchin tree

Errors you will hit

CodeWhenFix
X_CLIENT_TRANSPORT_FAILEDno usable answer: the network refused, a proxy answered a non-2xx with no framework code, or a 2xx body was not JSONx doctor --json checks the network and the gateway in front of the app, then retry. A write with no idempotencyKey may already have landed
X_CLIENT_SCOPE_CHANGEDthe page changed principal while a read was in flightif (isSuperseded(error)) return;, then read again under the new principal
X_CLIENT_RECORD_ENVELOPE_INVALIDa response carried x-ultimate-records: 1 and a body of the wrong shapebuild the body with encodeRecordEnvelope() from @ultimat3/core in the handler that set the header, or stop setting the header
X_RECORD_KEY_MISSINGa returned row lacks a primary-key columnx entities show <entity> --json lists the key. Return the whole row
X_MUTATOR_CLOCK_MISSINGa mutator declares conflict: 'last-write-wins' and its entity has no number updatedAt, or its output has no entity rowadd updatedAt (a number, epoch ms, written by the server) to the entity, or declare conflict: 'server-wins'
X_REALTIME_UNINSTALLEDa realtime hook ran in an island that never called installRealtime()x build. If the island reaches realtime only through a package, call installRealtime({ signal: createSignal }) in its mount
X_RECORD_REJECTEDa row reached the store with no key, or not as an objectx entities show <entity> --json lists the key. Return whole rows
X_CONTRACT_DRIFTthe client bundle and the server are on different buildsreload the page to pick up the new client bundle

Full rows: Error codes.

Shared island chunks

islands: { sharedChunks: true } in app.config.ts builds every island in one split build, so a module two islands import is one /islands/chunk-<hash>.js a page fetches once. Off by default, by measurement (packages/core/src/config-islands.ts): tree shaking across one build keeps what any importer uses, so an island that takes one helper from a module the others use heavily pays for all of it — the reference app's plain-DOM update banner went 712 → 16,288 B with it on.

Turn it on whenLeave it off when
one surface's pages render several islands over one shared graph (the same form kit, the same chart)an island is small and plain-DOM, or the islands share little
x build --target static, then x verify --only budgets, shows the routes that render several islands getting smallerany route's budget.js grows past its budget

Layering: the last config layer that states sharedChunks wins; absent everywhere is false (mergeIslands). A value that is not a boolean is refused at boot (X_CONFIG_INVALID). As of 2026-10.

Edit this page on GitHubThis page as markdown