Skip to content

Getting Started

github-actions[bot] edited this page Aug 9, 2026 · 8 revisions

Getting started

bunx create-ultimate myapp && cd myapp && x dev

Nothing to install first. No Docker daemon, no .env scavenger hunt, no service to provision.

✓ postgres    embedded, migrated, seeded         420ms
✓ nats        in-process                           2ms
✓ storage     ./.x/storage (S3 API)                1ms
✓ roles       web sync worker scheduler replicator (one process, isolated)
✓ site/       12 routes   static   0kb js
✓ app/        3 routes    stream
✓ mcp         ws://localhost:9229
  http://localhost:3000        →  landing page (site/)
  http://localhost:3000/app    →  dashboard (app/)
  http://localhost:3000/_x     →  dev dashboard
  ready in 1.1s

What you get before writing a line

Thing Where Detail
Embedded Postgres .x/pg downloaded once, migrated, seeded; no local install
In-process NATS same process identical API to JetStream in prod
S3 ./.x/storage real S3 API surface via Bun.s3
Mail /_x inbox captured, never sent
Redis (cache tier 3) in-process map same interface as Bun.redis
MCP dev server ws://localhost:9229 routes, schema, policies, tests, logs, read-only SQL
/_x dev panel http://localhost:3000/_x routes, schema, queries, live, jobs, cache, mail, errors, traces, AI, env, boundaries
Landing page apps/web/site/ static, 0kb JS, real meta + JSON-LD
Dashboard apps/web/app/ stream, auth'd
Admin app apps/admin/ already exposes MCP over your actions
Green gate x verify typecheck, lint, boundaries, six test types, drift, contracts, budgets, SEO, manifest

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.

1. Write your first action

x gen 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 });
    return post;
  },
});

That one declaration emits six artifacts — HTTP route, OpenAPI operation, typed client function, job handle, MCP tool, test scaffold. See Actions.

2. Call it from app/

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 { createClient } from '@ultimat3/action';
import type * as actions from '../api/posts/actions';

export const api = createClient<typeof 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.

3. Drive it from an agent over MCP

Point any MCP client at the dev socket printed by x dev.

claude mcp add ultimate --transport ws ws://localhost:9229

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_POLICY_DENIED", "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://ultimate.dev/errors/X_POLICY_DENIED" }

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 policies.list
the whole app as data x manifest --json manifest.get
what an X_* code means x explain X_POLICY_DENIED errors.explain

4. x verify

One command. Green means shippable.

$ x verify
  ✓ typecheck  ✓ lint  ✓ boundaries  ✓ unit  ✓ contract  ✓ live  ✓ job  ✓ e2e
  ✗ migration 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.

Where next

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 / spa
Testing six test types, DB-clone parallelism
CLI reference every command, every --json
Error codes code → cause → fix
Troubleshooting first-run failures

Status

As of 2026-07: pre-v1, not production-ready. Twelve milestones, each ending in a working demo app plus green x verify; milestones 0–5 ship before realtime. Realtime tiers 1–2 are v1, tier 3 (local-first) is v2. Milestone 6 is a 50k-socket forced-reconnect benchmark — realtime topology is not frozen until that number exists. See FAQ.

Clone this wiki locally