Skip to content

Introduction

What nice-code is — typed actions and live realms over one secure connection, plus the libraries they build on.

nice-code is a set of TypeScript libraries with one shared goal: your types stay correct everywhere your program goes — across the network, into a worker, down to storage, and inside a catch block. You write a type once and it follows your data the whole way.

PackageWhat it does
@nice-code/wireThe secure connection everything rides — one self-healing, authenticated link that actions and realms multiplex over.
@nice-code/actionCall a function on another machine as if it were local — fully typed.
@nice-code/realmLive, authoritative shared state — many clients watch and optimistically write one server-owned tree.
@nice-code/errorErrors you can type, send over the network, and match on by name.
@nice-code/stateA small state store (built on Immer) that only re-renders the parts that changed.
@nice-code/common-errorsReady-made validation errors + Hono middleware.
@nice-code/utilTyped storage helpers and crypto helpers.

Two planes, one connection. @nice-code/action (call functions) and @nice-code/realm (sync state) are the two things you build with — equal siblings, not a main package and an add-on. Both ride a single secure connection from @nice-code/wire, so an app that uses both multiplexes them over one socket. Use one plane or both: an actions app opens the connection with connectChannel, and a realm-only app opens it with createWireClient and needs no actions at all. The rest fit around them — actions describe their errors with error domains, secure connections use the crypto helper from util, and validation failures come back as common-errors.

The example below shows the core idea. An action is a single shared definition — imported by both the client and the server — that describes one function’s input, its output, and the errors it can throw:

// Both the client and the server import this exact definition.
export const act_user = act_app.createChildDomain({
domain: "act_user",
actions: {
getUser: actionSchema()
.input({ schema: v.object({ userId: v.string() }) })
.output({ schema: v.object({ id: v.string(), name: v.string() }) })
.throws(err_user, ["not_found"]), // ← an error domain from @nice-code/error
},
});
// Call it from anywhere. The output is typed, and so are the errors you can catch.
const user = await act_user.action.getUser
.request({ userId: "u_1" })
.runToOutput();
  • Not a validator. Bring your own — Valibot, Zod, or any Standard Schema library — to check data at the edges.
  • Not an HTTP framework. Actions can travel over WebSocket, HTTP, in-memory, or WebRTC, and they plug into Hono, Workers, Node, Bun, and Durable Objects.
  • Not magic. Each library is small, easy to read, and has very few dependencies.

The whole documentation site is also published as a single plain-text file at /llms.txt — every page concatenated, frontmatter stripped, each section labelled with its route. Point a local AI coding assistant at it (or paste it into a chat) to give the model the library’s entire API surface as context in one shot, so its suggestions match how nice-code actually works. It’s regenerated on every docs build, so it never drifts from the pages you’re reading.

If you’re only using one package, there’s a smaller, focused file per module — point your agent at just the one you need:

PackageFile
@nice-code/wire/llms-wire.txt
@nice-code/action/llms-action.txt
@nice-code/realm/llms-realm.txt
@nice-code/error/llms-error.txt
@nice-code/state/llms-state.txt
@nice-code/util/llms-util.txt
@nice-code/common-errors/llms-common-errors.txt
@nice-code/devtools (+ vite/relay companions)/llms-devtools.txt