@nice-code/realm
v0.89.0
Published
Readme
@nice-code/realm
Docs: nicecode.io — guides, integrations, and the full API surface. Stability: nicecode.io/production/stability — the pre-1.0 posture, lockstep versioning, and the wire-format skew promise. Working with an AI assistant? Point it at nicecode.io/llms-realm.txt (just this package) or nicecode.io/llms.txt (the whole stack) — the complete, current docs flattened into plain text.
A schema-defined, authoritative shared-state engine: many clients continuously observe (and
optimistically write) one server-owned state tree, over a binary patch protocol riding your app's
one secure connection — @nice-code/action shares that connection when you use actions; the realm
carries it alone when you don't.
Realm is the state plane; actions are the action plane. Rule of thumb: realm for state that many parties watch; actions for things that happen. One-shot commands with results, reliability tiers, offline outboxes, progress streaming — those stay actions. Live positions, presence, inventory, lobby state — that's a realm.
The documented API is the supported API. The root export (plus /platform/cloudflare,
/react, /predict, /testing) matches the docs site and follows semver;
@nice-code/realm/internals (frame codec, rule trie, slicing machinery) is explicitly unstable.
The one definition
Everything derives from a single defineRealm block — types, wire token map, schema hash, rule
trie, and the client prediction bundle:
import { defineRealm, t } from "@nice-code/realm";
export const gameRealm = defineRealm({
id: "game_realm",
avatars: {
player: { persistentId: t.string() },
spectator: { persistentId: t.string() },
},
state: {
config: t.object({ roundActive: t.boolean() }),
players: t.record(t.id("playerId"), t.object({ x: t.number(), y: t.number() })),
},
rules: (r) => [
r.path("config.*").view(r.everyone).alter(r.serverOnly),
r.path("players.$playerId.{x,y}")
.view(r.everyone)
.alter(({ avatar, params }) => avatar.persistentId === params.playerId || myErr.fromId("not_yours")),
],
intents: (i) => ({ /* named, atomic, settle-only server mutations */ }),
});This module ships to clients. Never close over secrets in a realm definition. Server-only data enters at serve time via
createRealmServerEngine(realm, { ctx }), reachable only insider.serverChecked(...)rules and intentapplybodies (declare its type withserverContext: t.serverContext<MyCtx>()).
What lives here
| Area | Surface | Guide |
| --- | --- | --- |
| Defining | defineRealm, t schema builders, avatars, rules (view/alter, most-specific-wins, default-deny), intents. | defining-realms · rules |
| Serving | createRealmServerEngine + createRealmAcceptor (host-agnostic); /platform/cloudflare's serveRealmDurableObject + hostRealm register one protocol on serveWireDurableObject (realm-only, no action import) or action's serveDurableObject (both planes, shared sockets). SQLite patch log + snapshot cadence + schema-hash migrate gate are automatic. | serving |
| Connecting | connectRealm + realmConnection(client) over a createWireClient (realm-only) or a connectChannel connector (with actions). realm.store (a @nice-code/state Store), realm.update (optimistic, returns a settle handle), realm.intents.*, realm.pending, realm.listenToPatches for imperative renderers. | connecting |
| Writes | Direct writes are LWW absolute-set; anything read-modify-write, multi-path atomic, or reading hidden state is an intent. Local read-modify-write races and loses updates silently — the one contract to read before building. | writes-intents |
| Liveness | whenLive() / hasBeenLive / subscribeStatus, the staleness probe (staleProbeAfterMs), the keep-alive redial ladder, onLinkEvent, advertised server limits + flush splitting. | resilient-client · limits |
| Netplay | /predict — prediction toolkit, interpolation buffer, input lanes; realm.stats for RTT/jitter. | realtime-games |
| Security | Inherits the connection's level, declares a minimum (authenticated default, encrypted; never none), enforced fail-closed both ends. encrypted is in-flight only — DO SQLite is cleartext at rest; rejection messages are wire-visible; keep intents idempotent. | security |
| React | useRealm, useRealmValue, useRealmStatus, useRealmPending, useRealmIntent. | connecting |
| Testing | /testing's createTestRealm runs the whole loop deterministically — no network, no Cloudflare. world.serverSlice(avatar) is the oracle every projection should equal; disconnect()/reconnect() script the reconnect matrix. | testing |
| Devtools | RealmDevtoolsCore + the window's realm panel — sync health, flush timeline, RTT. | devtools |
Replicas are an avatar rule (connectRealmReplica): a Worker holding a live read copy is just
another avatar type whose view rules say "everything" — plain object + patches callback, no
Store/React stack, no special authority.
Docs trail
Guides live on the docs site (packages/documentation, the nice-realm/ section — also flattened
into /llms-realm.txt for AI assistants). Deferred work lives in the root
FUTURE.md registry. The finished design records are archived in
docs/finished_features/nice-realm/:
PLAN-v1-initial.md (the locked
spec — the plan §… references in source comments point here),
INITIAL-REVIEW.md (the deep review
and road to release — the review … references point here),
FINALIZE.md (the F1–F8 completion
record), and
PLAN-security.md (the connection-level
security hardening — the PLAN-security … references in source comments point here). Earlier
documents live in git history, see INITIAL-REVIEW §D.4.
