Code Monkey home page Code Monkey logo

nanobuf's Introduction

Encode and decode structured data to/from a binary buffer

import { BufWriter } from 'nanobuf'

const buf = new BufWriter()

buf.u32(12345) // u̲nsigned ̲3̲2-bit integer
buf.str('Hello world!') // ̲st̲r̲ing
buf.f64(420.69) // ̲6̲4-bit f̲loat (double)

const packet = buf.toUint8Array() // Uint8Array(25) [ 0, 0, 48, 57, 12, ... ]

someSocket.send(packet)
// Receiving peer
import { BufReader } from 'nanobuf'

someSocket.on('message', data => {
	const buf = new BufReader(data)
	console.assert(buf.u32() == 12345
	            && buf.str() == 'Hello world!'
	            && buf.f64() == 420.69)
	// Check that we have read everything (optional)
	console.assert(buf.remaining == 0)
})

Perks:

  • Dependency-less
  • Performant. Know a faster implementation? Open an issue on our github!
  • Lightweight (15KB .js)
  • Import index.js as-is in the browser
  • Data is encoded in network format (big endian, tightly packed, octets, etc...)

Schemas

import { BufWriter, Struct, f32, Arr, bool, Enum } from 'nanobuf'

const Point = Struct({x: f32, y: f32})
const Polyline = Struct({
	points: Arr(Point),
	closes: bool,
	shape: Enum(['straight', 'quadratic-bezier', 'cubic-bezier', 'arc'])
})

const buf = new BufWriter()
buf.encode(Polyline, {
	points: [{x: 1, y: 2}, {x: 5, y: 2}, {x: 3, y: 5}],
	closes: true,
	shape: 'straight'
})
buf.toUint8Array() // Uint8Array(27) [ ... ]

const buf2 = new BufReader(buf)
// Decode
console.log(buf2.decode(Polyline)) // {points: [...], closes: true, shape: 'straight'}
/* Or decode in-place
  const shape = {points: [], closes: false, shape: ''}
  buf2.decode(Polyline, shape)
  console.log(shape)
*/
class Point{
	x = 0; y = 0
	static encode(buf, point){
		buf.f32(point.x)
		buf.f32(point.y)
	}
	static decode(buf, point = new Point()){
		point.x = buf.f32()
		point.y = buf.f32()
		return point
	}
	// ...
	static Triangle = Arr(Point, 3)
}

// ...
buf.encode(Point.Triangle, [
	new Point(1, 2),
	new Point(3, 4),
	new Point(5, 6)
])
// Data is always packed tightly
console.assert(buf.byteLength == f32.size * 2 * 3)
// You can also check the size of aggregate types (such as Struct() and Arr() with fixed length)
console.assert(Point.Triangle.size == 24)

Interface

type StructType = Struct | Arr | Enum | Optional | Padding | bool | u8 | i8 | u16 | i16 | u32 | i32 | u64 | i64 | bu64 | bi64 | f32 | f64 | v16 | v32 | v64 | bv64 | u8arr | str

// Always use buf.<thing>(123) instead of buf.encode(<thing>, 123) when available, since the argument types are static it will always be more performant
class BufWriter{
	constructor(arr?: ArrayBuffer, head?: number) // Construct a new BufWriter, optionally passing the underlying ArrayBuffer and head position. Once the head surpasses the ArrayBuffer's length, it is discarded (and possibly detached) and a new ArrayBuffer is allocated and used
	bool(n: boolean) // true or false, 1 byte
	u8(n: number) // Write a value in [0, 255], 1 byte
	i8(n: number) // Write a value in [-128, 127], 1 byte
	u16(n: number) // Write a value in [0, 65535], 2 bytes
	i16(n: number) // Write a value in [-32768, 32767], 2 bytes
	u32(n: number) // Write a value in [0, 4294967295], 4 bytes
	i32(n: number) // Write a value in [-2147483648, 2147483647], 4 bytes
	u64(n: number) // Write a value in [0, 18446744073709552000], 8 bytes
	i64(n: number) // Write a value in [-9223372036854776000, 9223372036854776000], 8 bytes
	bu64(n: bigint) // Write a value in [0n,18446744073709551615n], 8 bytes
	bi64(n: bigint) // Write a value in [-9223372036854775808n, 9223372036854775807n], 8 bytes
	f32(n: number) // Write a value in [-3.40282e+38, 3.40282e+38], Precision ~7 digits, 4 bytes
	f64(n: number) // Write a value in [-1.797693134862e+308, 1.797693134862e+308], Precision ~15 digits, 8 bytes
	v16(n: number) // Write a value in [0, 32767], 1 or 2 bytes (depending on largeness of n)
	v32(n: number) // Write a value in [0, 2147483647], 1, 2 or 4 bytes
	v64(n: number) // Write a value in [0, 9223372036854775807], 1, 2, 4 or 8 bytes
	bv64(n: bigint) // Write a value in [0n, 9223372036854775807n], 1, 2, 4 or 8 bytes
	u8arr(arr: Uint8Array, len?: number) // Write the length of arr using v32, followed by arr's raw bytes. If len is specified, the length is implicit and not written (however it must match arr.length)
	str(s: string) // Write s's length using v32, followed by s encoded as UTF-8, maximum s.length*3 bytes
	enum(enumType: Enum, value: string) // Write value's corresponding integer value according to enumType using v32
	encode(type: StructType, value: any) // Encode an arbitrary type
	skip(bytes: number) // Skip a number of bytes, leaving them unwritten (0)

	toUint8Array(): Uint8Array // View into the currently written data. May become detached as writer grows, consider using a copying method
	toReader(): BufReader // Reader for the currently written data. May become detached as writer grows, consider using a copying method
	copyToArrayBuffer(): ArrayBuffer // Get a copy of the written data as an ArrayBuffer
	copy(): BufWriter // Get a copy of the written data as a second BufWriter
	copyToReader(): BufReader // Same as new BufReader(this.copyToArrayBuffer())
	written: number // How many bytes have been written to this buffer so far
	buffer: ArrayBuffer // The underlying array buffer that is being modified. May be larger than this.written (this is intentional to avoid excessive reallocations). May become detached as writer grows
	byteOffset: number // Always 0
	byteLength: number // Same as .written
}

class BufReader extends DataView{
	bool(): boolean // true or false, 1 byte
	u8(): number // Read a value in [0, 255], 1 byte
	i8(): number // Read a value in [-128, 127], 1 byte
	u16(): number // Read a value in [0, 65535], 2 bytes
	i16(): number // Read a value in [-32768, 32767], 2 bytes
	u32(): number // Read a value in [0, 4294967295], 4 bytes
	i32(): number // Read a value in [-2147483648, 2147483647], 4 bytes
	u64(): number // Read a value in [0, 18446744073709552000], 8 bytes
	i64(): number // Read a value in [-9223372036854776000, 9223372036854776000], 8 bytes
	bu64(): bigint // Read a value in [0n,18446744073709551615n], 8 bytes
	bi64(): bigint // Read a value in [-9223372036854775808n, 9223372036854775807n], 8 bytes
	f32(): number // Read a value in [-3.40282e+38, 3.40282e+38], Precision ~7 digits, 4 bytes
	f64(): number // Read a value in [-1.797693134862e+308, 1.797693134862e+308], Precision ~15 digits, 8 bytes
	v16(): number // Read a value in [0, 32767], 1 or 2 bytes (depending on largeness of n)
	v32(): number // Read a value in [0, 2147483647], 1, 2 or 4 bytes
	v64(): number // Read a value in [0, 9223372036854775807], 1, 2, 4 or 8 bytes
	bv64(): bigint // Read a value in [0n, 9223372036854775807n], 1, 2, 4 or 8 bytes
	u8arr(len?: number): Uint8Array // Read a Uint8Array of length len, or (this.v32()) if unspecified
	str(): string // Read a string of length (this.v32()), decoded from UTF-8, maximum result.length*3 bytes
	enum(enumType: Enum): string // Read a v32 and return its corresponding string value according to enumType
	decode(type: StructType): any // Decode an arbitrary type
	skip(bytes: number) // Skip a number of bytes, not bothering to read them
	view(bytes: number) // Skip a number of bytes and return a Uint8Array pointing to those skipped bytes. Similar to u8arr(len)

	toUint8Array(): Uint8Array // Get a Uint8Array pointing to remaining unread data. This is a reference and not a copy. Use Uint8Array.slice() to turn it into a copy
	copyReadToArrayBuffer(): ArrayBuffer // Copies all the bytes that have already been read since this object's creation into a new ArrayBuffer
	copyRemainingToArrayBuffer(): ArrayBuffer // Copies all the bytes yet to be read into a new ArrayBuffer
	copyToWriter(): BufReader // Same as new BufWriter(this.copyReadToArrayBuffer(), this.i)
	copy(): BufReader // Same as new BufReader(this.copyRemainingToArrayBuffer())
	read: number // How many bytes have been read from this buffer so far
	remaining: number // How many more bytes can be read before reaching the end of the buffer
	overran: boolean // Whether we have reached and passed the end of the buffer. All read functions will return "null" values (i.e, 0, "", Uint8Array(0)[], false, ...)
}

// The following types are constructors and implement the static encode and decode methods, so they can be used with schemas. These methods are ommitted for brevity. Do not use new with these constructors
// E.g u8(257) == 1
// E.g bv64("999") == 999n
// E.g new f64(0) throws TypeError
// E.g i64.encode(someBuf, 123456)
// E.g myNum = f32.decode(someBuf)

type bool

type u8
type i8
type u16
type i16
type u32
type i32
type u64
type i64
type bu64
type bi64

type f32
type f64

// Values 0-127 encoded as:
// 0xxxxxxx
// Values 128-32767 encoded as:  (it's possible to also encode 0-127 like this, forming an overlong encoding, which is valid but undesired)
// 1xxxxxxx xxxxxxxx
type v16
// Values 0-63 encoded as:
// 00xxxxxx
// Values 128-16383 encoded as:
// 01xxxxxx xxxxxxxx
// Values 16384-2147483647 encoded as:
// 1xxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx
type v32
// Values 0-63 encoded as:
// 00xxxxxx
// Values 128-8191 encoded as:
// 010xxxxx xxxxxxxx
// Values 8192-536870911 encoded as:
// 011xxxxx xxxxxxxx xxxxxxxx xxxxxxxx
// Values 536870912-9223372036854775807 encoded as:
// 1xxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx
type v64
type bv64

type u8arr
type str

// Returns a Schema type whose encode/decode is equivalent to calling .u8arr() with the specified length. The length is therefore implicit and not written to or read from buffers
function u8arr.len(length: number): SchemaType

// Returns a Schema type that encodes/decodes an object of the specified structure. Optionally places the static encode and decode functions on a specified class (in that case the class is returned)
function Struct(obj: {[key: string]: SchemaType}, class?: any): SchemaType

// Returns a Schema type that encodes/decodes an array of the specified element type. If len is specified, the length becomes implicit and is not written to or read from buffers
function Arr(type: SchemaType, len?: number): SchemaType

// Returns a Schema type that encodes/decodes a string as a v32, based on a set of specified possible values
// E.g Enum(["apple", "orange", "banana"])
// E.g Enum({apple: 0, orange: 1, banana: 2, pear: 5})
// Invalid values are encoded to an invalid enum and decoded back to the empty string ""
function Enum(values: string[] | {[key: string]: number}): SchemaType

// Returns a Schema type that can encode/decode the type or null. This is done by encoding an extra boolean value immediately before, indicating if the value is null (false) or not (true)
function Optional(type: SchemaType): SchemaType

// Returns a Schema type that advances a number of bytes without interpreting them, essentially skipping the bytes. The encoded/decoded value is always undefined
function Padding(bytes: number): SchemaType

nanobuf's People

Contributors

partyblob avatar

Watchers

BlobTheKat avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.