# @zorsh/zorsh

> A TypeScript-first implementation of the Borsh binary serialization format, with a modern Zod-like API and full type inference.

Latest version **0.5.1** (published 2026-09-14) · ISC license · 0 weekly downloads

## Install

```sh
npm install @zorsh/zorsh
pnpm add @zorsh/zorsh
yarn add @zorsh/zorsh
bun add @zorsh/zorsh
```

## Health

**Score 70/100 (B)** — status: active.

Positive: has types; no vulnerabilities; has provenance; recently updated; high maintenance score; high quality score.

Warnings: low downloads; no esm support; pre 1.0.

## Facts

| | |
|---|---|
| Version | 0.5.1 |
| Published | 2026-09-14 |
| First published | 2025-02-14 |
| Weekly downloads | 0 |
| License | ISC |
| TypeScript types | bundled |
| Module format | CommonJS |
| Dependencies | 0 |
| Unpacked size | 83.3 KB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| Provenance | attested (GitHub Actions) |
| Maintainers | ricky-is |

## Links

- npm: https://www.npmjs.com/package/@zorsh/zorsh
- Repository: https://github.com/r-near/zorsh
- Homepage: https://github.com/r-near/zorsh#readme
- Issues: https://github.com/r-near/zorsh/issues
- npm.io page: https://npm.io/package/@zorsh/zorsh

## Recent versions

- 0.5.1 (latest) — 2026-09-14
- 0.5.0 — 2026-03-26
- 0.4.0 — 2025-11-19
- 0.3.3 — 2025-03-24
- 0.3.2 — 2025-03-14
- 0.3.1 — 2025-03-01
- 0.3.0 — 2025-02-28
- 0.2.3 — 2025-02-26
- 0.2.2 — 2025-02-18
- 0.2.1 — 2025-02-14
- 0.2.0 — 2025-02-14

## README

# Zorsh

[![codecov](https://codecov.io/gh/r-near/zorsh/graph/badge.svg?token=CODECOV_TOKEN)](https://codecov.io/gh/r-near/zorsh)

A TypeScript-first implementation of the [Borsh](https://borsh.io) binary serialization format, with a modern Zod-like API and full type inference.

## Features

- 🎯 **Type-Safe**: Full TypeScript type inference with zero runtime overhead
- 🎨 **Modern API**: Zod-inspired schema builder with an intuitive API
- ⚡ **High Performance**: Direct binary serialization without intermediate steps
- 💪 **Flexible**: Rich type system supporting complex nested structures
- 📦 **Self-Contained**: No external dependencies beyond TypeScript

## Installation

```bash
npm install @zorsh/zorsh
# or
yarn add @zorsh/zorsh
# or
pnpm add @zorsh/zorsh
```

## Quick Start

```typescript
import { b } from "@zorsh/zorsh";

// Define your schema
const UserSchema = b.struct({
  name: b.string(),
  age: b.u8(),
  scores: b.vec(b.u16()),
  metadata: b.option(
    b.struct({
      joinDate: b.u64(),
      rank: b.string(),
    })
  ),
});

// Get the TypeScript type from your schema
type User = b.infer<typeof UserSchema>;

// Use it!
const user: User = {
  name: "Alice",
  age: 25,
  scores: [100, 95, 98],
  metadata: {
    joinDate: BigInt(Date.now()),
    rank: "Gold",
  },
};

// Serialize to bytes
const bytes = UserSchema.serialize(user);

// Deserialize back to object
const decoded = UserSchema.deserialize(bytes);
```

## Type System

### Primitive Types

#### Numbers

- Unsigned integers: `u8`, `u16`, `u32`, `u64`, `u128`
- Signed integers: `i8`, `i16`, `i32`, `i64`, `i128`
- Floating point: `f32`, `f64`

```typescript
const schema = b.u64();
const value = BigInt(123456789);
const bytes = schema.serialize(value);
```

#### Other Primitives

- `string`: UTF-8 encoded strings
- `unit`: Empty type (similar to void)

```typescript
const schema = b.string();
const value = "Hello, World!";
const bytes = schema.serialize(value);
```

### Complex Types

#### Option (Nullable Values)

```typescript
const schema = b.option(b.string());
const value: string | null = "hello";
const bytes = schema.serialize(value);
```

#### Arrays and Vectors

```typescript
// Fixed-length array
const fixed = b.array(b.u16(), 3);

// Dynamic-length vector
const dynamic = b.vec(b.u16());
```

#### Bytes

```typescript
const DynamicBytes = b.bytes();     // Vec<u8>   → Uint8Array
const Pubkey = b.bytes(32);        // [u8; 32]  → Uint8Array
```

- `b.bytes()` encodes as a Borsh `Vec<u8>`: a `u32` length prefix followed by raw bytes.
- `b.bytes(N)` encodes as a Borsh `[u8; N]`: exactly `N` bytes with no length prefix.
- For raw byte blobs, prefer `b.bytes()` / `b.bytes(N)` over `b.vec(b.u8())` / `b.array(b.u8(), N)`.

#### Sets and Maps

```typescript
// Set of unique values
const setSchema = b.hashSet(b.string());

// Key-value mapping
const mapSchema = b.hashMap(b.string(), b.u128());
```

Elements and keys are serialized in canonical (Rust `Ord`) order regardless of insertion order, matching borsh-rs. See [Binary Format](#binary-format).

#### Structs

```typescript
const PersonSchema = b.struct({
  name: b.string(),
  age: b.u8(),
  balance: b.u128(),
});
```

#### Native TypeScript Enums

```typescript
// Define a TypeScript enum
enum Status {
  Idle,
  Running,
  Paused,
  Completed,
  Failed
}

// Create a schema for the enum
const schema = b.nativeEnum(Status);

// Use it directly with your TypeScript enum
const value = Status.Running;
const bytes = schema.serialize(value);
const decoded = schema.deserialize(bytes);
```

Both numeric and string enums are supported:

```typescript
// String enum
enum Color {
  Red = "RED",
  Green = "GREEN",
  Blue = "BLUE"
}

const colorSchema = b.nativeEnum(Color);
```

#### Enums with Data

```typescript
// Simple enum
const StatusSchema = b.enum({
  Pending: b.unit(),
  Fulfilled: b.unit(),
  Rejected: b.unit(),
});

// Enum with data
const ShapeSchema = b.enum({
  Square: b.u32(),
  Rectangle: b.struct({
    length: b.u32(),
    width: b.u32(),
  }),
  Circle: b.struct({
    radius: b.u32(),
  }),
});
```

## Complex Example

Here's a more complex example showing nested data structures:

```typescript
// Define game state schemas
const PlayerSchema = b.struct({
  name: b.string(),
  health: b.u8(),
  inventory: b.hashMap(b.string(), b.u16()), // item name -> count
  achievements: b.hashSet(b.string()), // unique achievement IDs
  guild: b.option(b.string()), // optional guild name
  recentScores: b.vec(b.u16()), // dynamic array of scores
});

const GameStateSchema = b.struct({
  players: b.hashMap(b.string(), PlayerSchema), // player ID -> player data
  currentRound: b.u32(),
  nextRoundStart: b.option(b.u32()), // optional timestamp
});

// Use with full type inference
type GameState = b.infer<typeof GameStateSchema>;

const gameState: GameState = {
  players: new Map([
    [
      "player1",
      {
        name: "Alice",
        health: 100,
        inventory: new Map([
          ["sword", 1],
          ["potion", 5],
        ]),
        achievements: new Set(["first_kill", "speedrun"]),
        guild: "Warriors",
        recentScores: [100, 95, 98],
      },
    ],
  ]),
  currentRound: 5,
  nextRoundStart: 1234567890,
};

const bytes = GameStateSchema.serialize(gameState);
const decoded = GameStateSchema.deserialize(bytes);
```

## Binary Format

Zorsh follows the Borsh specification for binary layout:

- Integers are little-endian
- Dynamic containers (vec, hashmap, hashset) are prefixed with u32 length
- HashMap keys and HashSet elements are written in canonical order, sorted like Rust's `Ord` (integers numerically, strings and bytes by their UTF-8/byte content with a shorter prefix first, `None` before `Some`, structs and tuples field by field, enums by variant index), so insertion order never changes the output. Entries that compare equal are rejected.
- Enums are encoded as u8 variant index followed by variant data
- Strings are length-prefixed UTF-8
- Structs are encoded in field definition order
- Option values use u8 discriminator (0 = None, 1 = Some)

## Why Zorsh?

- **Type Safety**: Full TypeScript inference means you can't accidentally serialize the wrong data type
- **Performance**: Direct binary serialization without intermediate JSON or other formats
- **Consistency**: Borsh guarantees a consistent binary representation, making it ideal for hashing and signatures
- **Modern API**: Familiar Zod-like builder pattern makes it easy to define and compose schemas

## Type Inference

Zorsh provides complete type inference through its Zod-like interface:

```typescript
const schema = b.struct({
  id: b.string(),
  data: b.option(b.vec(b.u8())),
  metadata: b.hashMap(b.string(), b.u64()),
});

// TypeScript automatically infers:
type T = b.infer<typeof schema>;
// {
//   id: string
//   data: number[] | null
//   metadata: Map<string, bigint>
// }
```

## Testing

Zorsh includes a comprehensive test suite that verifies both its TypeScript implementation and cross-language compatibility:

- **Unit Tests**: Extensive testing of all serialization types and edge cases
- **Rust Compatibility**: Tests against Rust's Borsh implementation to ensure perfect compatibility
- **Complex Scenarios**: Verification of nested and complex data structure handling

To run the test suite:

```bash
npm test
```

For details about running the Rust compatibility tests, see the [Rust tests documentation](test/rust/README.md).

## Contributing

Contributions are welcome! Feel free to:

- Report bugs
- Suggest features
- Submit pull requests

## License

MIT

---
_Source: https://npm.io/package/@zorsh/zorsh · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
