-
Notifications
You must be signed in to change notification settings - Fork 0
Getting Started
bunx create-ultimate myapp && cd myapp && bin/setup && x dev
Four commands, and bin/setup is one of them. It is the scaffold's own script — bun install,
x db gen "initial", x db migrate, x db seed — idempotent, so re-running it after a pull is
safe. bunx create-ultimate myapp && cd myapp && x dev is what this page said until 2026-08-23 and
it does not work: x new writes files and installs nothing, so the app has no node_modules, no x
of its own, and x dev stops on X_BUILD_FAILED — "Could not resolve @ultimat3/ui. Maybe you
need to bun install?"
No Docker daemon, no .env scavenger hunt, no service to provision — .env.development ships
committed non-secret defaults and an empty DATABASE_URL means embedded PGlite.
bin/setup, then x dev, measured on a fresh scaffold As of 2026-08-23 (6.7s for setup on a warm
Bun cache):
$ bin/setup
packages/db/migrations/20260823155026_initial.sql
✓ migration 20260823155026_initial generated
✓ migrations applied
✓ 1 seed(s): 2 inserted, 0 updated, 0 already stored
setup complete — next: x dev
$ x dev
roles web, sync, worker, scheduler
panels routes, timeline, live, jobs, db, mail, cache, policy, manifest, services, boundaries
manifest /path/to/myapp/x.manifest.json
introspect http://localhost:3000/_x
✓ dev ready on http://localhost:3000 — /_x mounted (11 panels), db=embedded events=embedded storage=embedded mail=embedded cdn=none
replicator is the fifth role and is opt-in — it takes a replication slot, which is not
something every x dev should do by starting: x dev --role web,sync,worker,scheduler,replicator.
Measured on a fresh scaffold, As of 2026-08-23.
| Thing | Where | Detail |
|---|---|---|
| Embedded Postgres | .x/pgdata |
PGlite in-process, no local install; bin/setup migrates and seeds it |
| In-process events | same process |
events=embedded; the same seam NATS fills in prod |
| S3 | ./.x/storage |
real S3 API surface via Bun.s3
|
/_x inbox |
captured, never sent | |
| Redis (the shared cache tier) | not started | the scaffold's cache.tiers is ['request-memo', 'lru']; adding 'redis' is what builds the shared rung |
/_x dev panel |
http://localhost:3000/_x |
11 panels: routes, timeline, live, jobs, db, mail, cache, policy, manifest, services, boundaries |
| MCP server | x mcp serve |
13 dev tools over stdio or http. Not started by x dev — /mcp on the dev server is 404 |
| Landing page | apps/web/site/page.tsx |
render: 'static', hydrate: 'never', budget: { js: '0kb' }, real meta + JSON-LD |
| Dashboard | apps/web/app/dashboard/page.tsx |
render: 'ssr', hydrate: 'visible', budget: { js: '60kb' }, behind policy: { permission: 'dashboard:read' }. Not stream — stream needs a hole marker the renderer does not yet have, and the template says so in its own comment |
| Admin app | apps/admin/ |
one ssr page; your actions reach an agent through mcp: { expose: true } and x mcp serve, not through this app |
| Green gate | x verify |
19 steps, in this order: typecheck, lint, boundaries, filesize, package-shape, errors, unit, contract, live, job, e2e, eval, drift, contract-diff, budgets, seo, i18n, manifest, roadmap. On a fresh scaffold: red on lint and budgets, then 18 of 19 after running the fix: lines it printed |
x dev runs every role in one process with isolation simulated, not skipped: separate ALS contexts, a real Postgres queue, real logical replication, a real SIGTERM drain on x dev restart. Nothing in the framework branches on if (dev) — only the drivers differ.
x g action publish-post
export const publishPost = action({
input: t.object({ postId: t.uuid, notify: t.boolean.default(true) }),
output: PostView,
policy: can('post:publish', ({ input, actor }) => ownsPost(actor, input.postId)),
cache: { invalidates: [tag.post, tag.feed] },
mcp: { expose: true, description: 'Publish a draft post' },
async handle({ input, ctx }) {
const post = await ctx.posts.publish(input.postId);
if (input.notify) await notifySubscribers.enqueue({ postId: post.id, orgId: post.orgId });
return post;
},
});That one declaration emits six artifacts — HTTP route, OpenAPI operation, typed client function, job handle, MCP tool, test scaffold. See Actions.
Register it, with every other primitive, in one call. Importing api/index.ts IS the boot.
// apps/web/api/index.ts
import { defineApi } from '@ultimat3/action';
import * as postActions from '../app/posts/actions';
import * as postJobs from '../app/posts/jobs';
import * as postQueries from '../app/posts/live';
import * as postMutators from '../app/posts/mutator';
import * as scheduledTasks from './tasks';
export const api = defineApi({
actions: [postActions],
mutators: [postMutators],
queries: [postQueries],
jobs: [postJobs],
tasks: [scheduledTasks],
});
export type Api = typeof api;app/ may import api/ types only. The typed client is derived from those types; there is no codegen step to remember and no fetch.
// apps/web/shared/client.ts
import { rpc } from '@ultimat3/action';
import type { Api } from '../api';
export const api = rpc<Api['actions']>({ baseUrl: '/' });// apps/web/app/posts/ui/publish-button.tsx
import { api } from '../../../shared/client';
import { t as translate } from '@ultimat3/i18n';
import type { PostView } from '@myapp/domain';
export function PublishButton(props: { post: PostView }) {
const publish = async (): Promise<void> => {
await api.publishPost({ postId: props.post.id, notify: true });
};
return (
<button type="button" onClick={publish}>
{translate('post.publish')}
</button>
);
}Rename postId in the action and this file fails typecheck. One rename, N errors, all real work.
x dev does not serve MCP — measured As of 2026-08-23, GET /mcp on the dev server answers
404 and nothing listens on 9229. The MCP server is its own command, and it prints the bearer token
your client must send:
x mcp serve --transport stdio # a client that spawns the server
x mcp serve --transport http --port 9229 # POST /mcp, bearer token printed at boot
stdio and http are the two transports (x help mcp); there is no ws transport, and a client
configured for one gets no connection. Register the printed URL and token with whatever MCP client
you use.
The agent now sees publishPost as a tool with JSON Schema from input, the description from mcp.description, and the action's own policy as its authorization — unwrapped, identical to the HTTP path. A denial is the same code in all three encodings:
{ "code": "X_FORBIDDEN", "cause": "actor user_2 lacks post:publish on post_9",
"fix": "grant post:publish to the actor's role, or call as the post owner",
"docs": "https://github.com/developerz-ai/ultimate/wiki/Error-Codes" }Introspection an agent should use instead of grepping:
| Want | Command | MCP tool |
|---|---|---|
| every action + schemas | x actions list --json |
actions.list |
| one action in detail | x actions describe publishPost --json |
actions.list |
| is this protected | x policy explain publishPost --json |
none — policies.list returns the catalog, not the per-declaration matrix |
| the whole app as data | x manifest --json |
manifest.get |
what an X_* code means |
x errors explain X_FORBIDDEN |
errors.explain |
One command. Green means shippable.
$ x verify
✓ typecheck ✓ lint ✓ boundaries ✓ filesize ✓ package-shape ✓ errors
✓ unit ✓ contract ✓ live ✓ job ✓ e2e ✓ eval
✗ drift
X_DB_DRIFT: schema differs from migrations
cause: table "posts" has column "publish_at" not present in any migration
fix: x db gen "add publish_at"
x verify --json emits the same content machine-readably. CI runs exactly x verify — a check that lives only in CI is a check you cannot run.
| Page | Read it for |
|---|---|
| Installation | prerequisites, x new flags, typed env, MCP client setup |
| Project layout | the generated tree, surfaces, the site/ → app/ boundary |
| The eight primitives | the whole conceptual surface, in eight lines |
| Actions | every field, all six projections, introspection |
| Entities and migrations | schema, drift, branch DBs |
| Policies and authz | one authz system, tenancy, denials |
| Queries and live queries | reads, live: true
|
| Jobs and workflows | durable steps, idempotency |
| Routes and render modes |
static / isr / ssr / stream
|
| Testing | six test types, DB-clone parallelism |
| CLI reference | every command, every --json
|
| Error codes | code → cause → fix |
| Troubleshooting | first-run failures |
As of 2026-08-23. Stable API — semver from here (Upgrading). The repository holds 29 @ultimat3/* packages plus the unscoped create-ultimate and versions all 30 in lockstep. Every major so far has been a correctness sweep with no codemod, so each BREAKING — entry names its own manual edit; Upgrading walks all of them, oldest first, and each section states its own count. Only the footer stamps a version number, and this page states none. What bunx create-ultimate myapp installs is npm's latest, one version for all 30 workspaces, each published by the release workflow over OIDC with a provenance attestation. Resolve it, do not trust this line — npm view @ultimat3/core version, and npm view @ultimat3/core dist.attestations for the attestation. No publication holes: @ultimat3/scraping was the last never-published package, bootstrapped by hand at 2.0.0, so bun add @ultimat3/scraping resolves. Milestones 0–10 are ✅; milestone 11 is 🚧, open on its two-platform deploy proof. Realtime tiers 1–2 ship; tier 3 (local-first) has not shipped. The 50k-socket forced-restart benchmark is measured and committed — first patch on the reconnected socket at p50 54.0s / p90 105.5s, on one node; delivery is a second run, 10,000 clients, 1,666,882 patches, 0 observed sequence gaps (Realtime). Status markers come from docs/idea/14-roadmap.md. See FAQ.
Ultimate — v11.3.0 As of 2026-08. Stable API, semver from here. MIT licensed. What npm serves is npm view @ultimat3/core version, never this line.
This footer is the only page that stamps a version. It renders under every wiki page, so one release bumps one line; a stamp on a second page is 46 hand-copies of one fact, and every one of them goes stale on the next tag.
Repository · Issues · Changelog · llms.txt
Edits to these pages are synced from wiki/ in the repository — change the file there, not the wiki, or the next sync overwrites it.
Start
Tutorials
- 1 · First app
- 2 · First feature
- 3 · Auth and admin
- 4 · Jobs and realtime
- 5 · Deploy free
- 6 · Growing up
Primitives
- The eight primitives
- Building your own base
- Actions
- Entities and migrations
- Policies and authz
- Queries and live queries
- Jobs and workflows
- Scheduled tasks
- Routes and render modes
Capabilities
- Realtime
- Caching and invalidation
- Batching and preloading
- N+1 detection
- PWA and offline
- MCP and AI
- Agents
- Admin dashboard
- Scraping
Cross-cutting
- I18n
- Theming
- UI components
- Timezones and dates
- Money
- Resource management
- Migrations and backfills
- Testing
Reference