Ultimate

21.x → 22.0.0, entry by entry

Twenty-three entries, the 22.0.0 section of CHANGELOG.md. Entries 4, 7, 9, 10, 11, 12, 13, 14, 18, 20 and 21 are compile errors, and so are parts of 3 (planDeploy's fourth argument), 6 (a declaration with no policy) and 19 (selectTransport's second argument, 'redis'). Entry 15 throws when a sync pod boots, entry 19 when any realtime role boots with a transport and NATS_URL that disagree, and entry 6 when a channel module loads. Entries 1, 2 and 5 compile unchanged and refuse a string they used to accept; entry 17 changes a meta.code, and entry 23 throws where a query silently answered []. Entry 3 also installs a new helm release beside the old one unless you name it. Entry 16 is a dependency you can drop, entry 22 is one x db gen if drift asks for it, and entry 8 needs no edit. 22.0.0 is plan 101's deep sweep: every framework package, both tracked apps and the deploy path.

TypeScript 7 from here on — move it before this hop. 22.0.0 added packages/cli/src/cdp-shot-keys.ts, and @ultimat3/cli ships its .ts source: under TypeScript 6 with exactOptionalPropertyTypes it fails TS2769 at 26:43, in your typecheck, on every release from 22.0.0 through 31. bun add -d typescript@^7.0.2 in every workspace that declares it — what x new has scaffolded since 20.0.0 (TYPESCRIPT_VERSION). Not a CHANGELOG.md entry, so not counted above.

#SurfaceCosts you an edit if
1t.dateyou send a date that is not ISO-8601 in shape ('March 14, 2026', '3/14/2026', '12'). It is refused at validation now (coerceQuery no longer rewrites it). new Date read it at the host's local midnight, so the stored day depended on TZ. Send '2026-03-14'. Epoch-ms numbers and Date objects are unchanged
2timestamp() columnsyou write a string with no Z and no offset ('2026-03-14T09:00') or a non-ISO one. Refused now; it used to be parsed in the host's zone. Write '2026-03-14T09:00:00Z' or a Date
3x deploy --method helm, planDeployyou deployed with helm before. The release is now named after app.config.ts's name, not the literal app, so the next deploy installs a second release beside the old one. Keep the old one with x deploy --method helm --release app, or helm uninstall app after the new one is up. The deploy now waits for the rollout (default --timeout 15m) and reports data.rollout. planDeploy(image, 'helm', root) needs a fourth argument, { release, namespace, timeout }. --release, --namespace and --timeout are refused on --method compose
4invokeAdminAction({ … expectedConfirmation })you pass expectedConfirmation. Delete it; the gate derives the token from the action's entity and subject.id, and the browser still echoes confirmationToken(entity, id) in confirmation. With both fields omitted a destructive action used to run unconfirmed; it is refused now
5@ultimat3/ui DateTime, toDateyou pass a non-ISO string ('August 14, 2026 09:00', '8/14/2026'). Pass an ISO date, a date-time with Z or an offset, or a Date
6channel(name, { … }) with no policya declaration omits it. It is a type error and throws X_CHANNEL_DECLARATION_INVALID at load. A public channel says so: policy: allow('public'). ChannelDescription.policy is string, no longer string | null — delete a null branch
7a custom QueueStore or LocalStoreyou implement one. QueueStore.save(state) → write(change: QueueChange), and LocalStore.saveQueue(scope, state) → writeQueue(scope, change): one change per mutation, by key. Whole-queue records a 21.x page wrote are converted on first read, so queued writes survive the upgrade. The shipped stores need nothing
8the page's SharedWorkernothing to edit. The worker is named per build now (workerName(scope, buildId), internal), so tabs of two deploys never share a socket host
9ChangeOpyou switch over it exhaustively. Add case 'truncate': — a rowless change: a TRUNCATE empties the affected live windows and starts a new epoch on every open channel topic
10Result, Ok, Err, ok, err, map, mapErr, isOk, isErr, tryCatch, unwrap, unwrapOr from @ultimat3/coreyou import any of them. Gone: throw an UltimateError and try/catch it
11X_USERS_TABLE, X_SESSIONS_TABLE, X_ACCOUNTS_TABLE, X_VERIFICATIONS_TABLE, X_API_KEYS_TABLE, X_USERS_MIGRATION_1_3 from @ultimat3/authyou import them, or pasted them into a migration. Delete both: every boot applies AUTH_TABLES (the 1.3 upgrade included, as add column if not exists)
12internals removed from package barrelsyou import a runtime value that no other package used — CHANGELOG.md's 22.0.0 section lists them per package (auth, ui, core, http, entity, query, mcp, ai, mail, notify, pwa, render, scraping, manifest). None is an error class, a code table or a documented API. Copy the constant into your app, or use the documented API it served
13the e2e driver: installE2eDriver, e2eFixtures, startE2eApp, e2eApp, e2eBaseUrl, e2eBrowser, openE2eBrowser, cdpConnect, cdpE2eTab, findChrome, launchChrome, the Cdp*Error / E2e*Error classes and their types, from @ultimat3/cliyou import any of them. Import them from @ultimat3/testing; the test preload is @ultimat3/testing/e2e-preload. FRAMEWORK_SCRIPTS and FRAMEWORK_INLINE_SCRIPTS stay on @ultimat3/cli. The X_E2E_* / X_CDP_* codes are unchanged
14entityRow, camel from @ultimat3/realtime/serveryou decode WAL tuples yourself. entityRow(physical) → entityRow(relation, physical, 'before' | 'after'); it decodes through the registered entity (decodeRow, .column() renames and money included) and refuses a table with no registered entity (X_REPLICATION_PROTOCOL). camel is gone
15a sync role on a real databaseit has no reachable change feed. It refuses to boot with X_REALTIME_TOPOLOGY; it used to come up healthy and deliver nothing. Set NATS_URL on web, sync and one replicator, or leave sync off. (x dev --role sync,replicator runs both in one process, for development.) The scaffolded chart (roles.sync.enabled: false) and Compose file (replicas: 0) ship sync off, with the enable recipe beside the switch; a chart or compose file you copied earlier keeps whatever it had
16x shot, x shot --island, the ui.* MCP toolsyou installed puppeteer-core for them. They drive Chrome over raw CDP on the e2e step's launcher now: bun remove puppeteer-core if nothing else imports it. X_SHOT_BROWSER_MISSING now means "no Chrome to launch" — export CHROME_PATH=<binary> where Chrome is not at /usr/bin/google-chrome or the other probed paths, or pass --cdp-url. A request to a host off the allow list is refused inside the browser and recorded as refused: "host" in the verdict
17ui.interact step failuresyou match meta.code against X_SCRAPE_*. A failed step carries X_SHOT_ELEMENT_MISSING, X_SHOT_ELEMENT_UNREADY or X_SHOT_KEY_INVALID now
18backoffDelay from @ultimat3/realtimeyou import it. Import core's: import { backoffDelay } from '@ultimat3/core', and pass attempt: n + 1 — realtime's copy counted from 0, core's counts from 1. A failed channel catch-up now retries after the base wait, not twice it
19realtime.transport, NATS_URL, selectTransportyour deploy relied on NATS_URL alone to pick the bus. realtime.transport decides now: 'nats' dials the variable realtime.urlEnv names and refuses the boot (X_CONFIG_INVALID) when it is unset — it used to fall back to in-process in silence — and 'memory' with NATS_URL set refuses too. Set transport: 'nats' where the fleet shares a bus, or unset NATS_URL. realtime.enabled: false now starts no sync node and no replicator. transport: 'redis' (never built) no longer typechecks. selectTransport(env) → selectTransport(env, { transport, urlEnv })
20startLiveReplicator, LiveReplicator, LiveReplicatorOptions from @ultimat3/testingyou import them — a live-query test or a hand-rolled dev boot. Import them from @ultimat3/realtime/server; @ultimat3/testing's subscribe fixture needs nothing
21@ultimat3/cli's barrelyou import anything from it but newCommand, dbCommand, verifyCommand and the documented API — 236 internals are gone (command objects, scan internals, report helpers, option types). maskLiterals / stripComments → import { maskLiterals, stripComments } from '@ultimat3/core'. The CLI no longer depends on @ultimat3/scraping, so a workspace that reached scraping through it declares it itself
22x db gen's schema hashx verify --only drift reports drift right after the upgrade with no entity edited: the hash now uses core's canonicalJson. Run x db gen once. Neither tracked app needed it
23a query's sql: () => from(…) over rows its loader selectedthe query filters (where) or sorts (orderBy) on a column the loader's select({ … }) leaves out. That read used to answer [] in silence; it throws X_QUERY_COLUMN_UNSELECTED (QueryColumnUnselectedError) now. Add the column to the loader's select, or drop the filter if the loader already applies it. The reference app's publicPostSlugs was one — the blog prerendered no article until publishedSlugs selected status and publishedAt

Where the sites are

grep -rnE "from<|from '@ultimat3/cli'|LiveReplicator|puppeteer|X_SCRAPE_|backoffDelay|selectTransport|transport: 'redis'|expectedConfirmation|planDeploy\(|\bResult<|\b(tryCatch|unwrapOr|mapErr)\(|X_(USERS|SESSIONS|ACCOUNTS|VERIFICATIONS|API_KEYS)_TABLE|X_USERS_MIGRATION_1_3|saveQueue|entityRow\(|\bcamel\b|case 'delete':" apps packages --include=*.ts --include=*.tsx
grep -rnE "from '@ultimat3/cli'" apps packages scripts --include=*.ts | grep -E "e2e|cdp|Chrome|E2e|Cdp"
grep -rnE "channel\(" apps packages --include=*.ts | grep -v policy

The typecheck step finds every removed name, planDeploy's arity and a missing policy. It does not find a non-ISO date string in a fixture, a seed or a client — those fail at validation, so run your contract and e2e suites — nor a helm release still named app.

Edit this page on GitHubThis page as markdown