Skip to content

Introduction

What nice-code is — five TypeScript libraries for reliable, type-safe applications.

nice-code is a collection of TypeScript libraries that share a single goal: keep your types intact at every boundary your program crosses — the network, the worker, the storage layer, the catch block.

PackageWhat it does
@nice-code/actionTransport-agnostic, bi-directional action system with full inference. The headline.
@nice-code/errorTyped, serializable errors with domain hierarchies and pattern matching.
@nice-code/stateImmer-backed state container with fine-grained selectors and reactions.
@nice-code/common-errorsStandard Schema validation errors + Hono middleware.
@nice-code/utilTyped storage adapters and WebCrypto utilities.

Start with @nice-code/action — it’s the centre of gravity. The rest compose around it: actions declare the errors they throw using error domains, secure channels use the crypto link from util, validation failures surface as common-errors. But each one stands on its own.

// An action declares its input, output, AND the errors it can throw —
// all in one shared definition both client and server import.
export const userDomain = appRoot.createChildDomain({
domain: "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"]), // ← @nice-code/error domain
},
});
// Call it from anywhere. Output is typed; thrown errors are typed and narrowable.
const user = await userDomain.action.getUser
.request({ userId: "u_1" })
.runToOutput();
  • Not a validator. Bring Valibot, Zod, or any Standard Schema library for boundary validation.
  • Not an HTTP framework. Actions ride carriers (WebSocket, HTTP, in-memory, WebRTC) and plug into Hono, Workers, Node, Bun, Durable Objects.
  • Not magic. Each library is small, readable, and dependency-light.

Continue to the Quick start →