npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

clavis-js

v0.2.0

Published

Secure encrypted communication over asynchronous streams - TypeScript implementation

Readme

clavis-js

Secure encrypted communication over asynchronous streams - TypeScript implementation of the Clavis protocol.

Features

  • 🔐 X25519 Key Exchange - Secure key exchange using elliptic curve cryptography
  • 🛡️ XChaCha20-Poly1305 Encryption - Authenticated encryption for all packets
  • 🔑 Pre-shared Key (PSK) Support - Optional authentication via PSK
  • 📦 Packet-based Protocol - Simple packet serialization/deserialization
  • 🌐 Stream-based - Works with Node.js streams (TCP, TLS, etc.)
  • TypeScript First - Full TypeScript support with type safety
  • 🔄 Bincode Compatible - Matches Rust's bincode serialization format

Installation

npm install clavis-js
# or
bun add clavis-js

Requirements

  • Node.js >= 18.0.0 or Bun >= 1.0.0
  • TypeScript >= 5.0 (for TypeScript projects)

Quick Start

import { EncryptedStream } from "clavis-js";
import { createConnection } from "net";

// Client
const socket = await new Promise((resolve, reject) => {
  const conn = createConnection({ host: "127.0.0.1", port: 7272 }, () => resolve(conn));
  conn.on("error", reject);
});

const encryptedStream = await EncryptedStream.new(socket, {
  maxPacketSize: 65536,
  psk: new TextEncoder().encode("your-pre-shared-key-at-least-16-bytes"), // Optional
});

const [reader, writer] = encryptedStream.split();

// Send encrypted packet
await writer.writePacket(yourPacket);

// Read encrypted packet
const packet = await reader.readPacket();

Protocol Definition

Define your packet types using the protocol DSL:

import { protocol } from "clavis-js";

interface PingPongData {
  message: string;
}

const Packet = protocol({
  Ping: [{ message: String }],
  Pong: [{ message: String }],
  Shutdown: [],
});

// Create a packet
const ping = Packet.Ping({ message: "hello" });

// Serialize (automatically uses VarintEncoding for variant indices)
const serialized = ping.serialize();

Bincode Serialization

The library provides bincode-compatible serialization that matches Rust's bincode format with serde. This is especially important when communicating with Rust services.

Key Features

  • VarintEncoding: Enum variant indices use VarintEncoding (values < 251 are single byte)
  • DateTime Support: Serializes Date objects as chrono::DateTime<Utc> format (struct { secs: i64, nsecs: u32 })
  • Option Types: Supports Rust's Option<T> serialization (u8 discriminant + value)
  • String Pairs: Helper for Vec<(String, String)> (e.g., environment variables)

Example: Matching Rust Protocol

import { 
  serializeMessageHeader, 
  serializeResourceSpec, 
  writeVarintU32,
  writeString,
  writeU16 
} from "clavis-js";

// Serialize a message matching Rust's protocol! macro
function serializeOrchestratorHello(buffer: number[], hello: {
  header: { correlation_id: string; timestamp?: Date };
  orchestrator_id: string;
  protocol_version: string;
  client_name: string;
}): void {
  // Write variant index (Varint-encoded)
  writeVarintU32(buffer, 19); // OrchestratorHello variant index
  
  // Serialize header
  serializeMessageHeader(buffer, hello.header);
  
  // Serialize fields in order
  writeString(buffer, hello.orchestrator_id);
  writeString(buffer, hello.protocol_version);
  writeString(buffer, hello.client_name);
}

Available Helpers

  • writeVarintU32() - Varint-encoded u32 (for enum variant indices)
  • writeString() - String serialization (u64 length + UTF-8 bytes)
  • writeOptionString() - Option serialization
  • writeStringPairVec() - Vec<(String, String)> serialization
  • writeDateTime() - DateTime serialization (struct { secs: i64, nsecs: u32 })
  • serializeMessageHeader() - MessageHeader struct serialization
  • serializeResourceSpec() - ResourceSpec struct serialization

API

EncryptedStream

Main class for encrypted communication.

EncryptedStream.new(stream, options?)

Creates a new encrypted stream by performing handshake.

  • stream: Node.js Readable & Writable stream (e.g., TCP socket)
  • options: Optional configuration
    • maxPacketSize?: number - Maximum packet size (default: 65536)
    • psk?: Uint8Array - Pre-shared key for authentication (minimum 16 bytes)

split(): [EncryptedReader, EncryptedWriter]

Splits the stream into separate reader and writer for bidirectional communication.

EncryptedReader

Read-only encrypted stream.

  • readPacket<P>(): Promise<P> - Read and decrypt a packet

EncryptedWriter

Write-only encrypted stream.

  • writePacket(packet: PacketTrait): Promise<void> - Encrypt and write a packet

Bincode Format Details

Enum Serialization

Enums are serialized with Varint-encoded variant indices:

  • Values < 251: Single byte (u8)
  • Values >= 251: 0xFB marker + 4 bytes (little-endian u32)

This matches bincode 1.3's default VarintEncoding configuration.

DateTime Serialization

Date objects are serialized as chrono::DateTime<Utc>:

  • secs: i64 (seconds since Unix epoch)
  • nsecs: u32 (nanoseconds within that second, 0-999,999,999)

Option Types

Rust's Option<T> is serialized as:

  • None: u8 0
  • Some(value): u8 1 + serialized value

Security

  • Uses X25519 for key exchange (ECDH over Curve25519)
  • Uses XChaCha20-Poly1305 for authenticated encryption
  • Supports pre-shared keys (PSK) for authentication
  • Constant-time MAC comparison to prevent timing attacks

Note: Without a PSK, connections are vulnerable to man-in-the-middle attacks. Always use a PSK in production.

Compatibility with Rust

This library is designed to work seamlessly with the Rust clavis library. When using clavis::protocol! in Rust, ensure your TypeScript serialization matches:

  1. Use writeVarintU32() for enum variant indices (not fixed u32)
  2. Use writeDateTime() for DateTime<Utc> fields
  3. Serialize struct fields in the same order as Rust
  4. Use writeOptionString() for Option<String> fields

License

MIT

Links