@solvei/borsh
TypeScript icon, indicating that this package has built-in type declarations

0.0.22 • Public • Published

Borsh TS

Due to organisation name change, the project has moved here

Borsh TS is unofficial implementation of the [Borsh] binary serialization format for TypeScript projects.

Borsh stands for Binary Object Representation Serializer for Hashing. It is meant to be used in security-critical projects as it prioritizes consistency, safety, speed, and comes with a strict specification.

With this imlementation on can generate serialization/deserialization Schemas using decorators.

Installation

npm install @westake/borsh

or

yarn add @westake/borsh

Examples of schema generation using decorators

Enum, variant at instruction "slot" 1.

class Super {}

@variant(0)
class Enum0 extends Super {
    @field({ type: "u8" })
    public a: number;

    constructor(a: number) {
    super();
        this.a = a;
    }
}

@variant(1)
class Enum1 extends Super {
    @field({ type: "u8" })
    public b: number;

    constructor(b: number) {
    super();
        this.b = b;
    }
}

class TestStruct {
    @field({ type: Super })
    public enum: Super;

    constructor(value: Super) {
        this.enum = value;
    }
}
const instance = new TestStruct(new Enum1(4));
const schemas = generateSchemas([Enum0, Enum1, TestStruct]);

expect(schemas.get(Enum0)).toBeDefined();
expect(schemas.get(Enum1)).toBeDefined();
expect(schemas.get(TestStruct)).toBeDefined();
const serialized = serialize(schemas, instance);
expect(serialized).toEqual(Buffer.from([1, 4]));

const deserialied = deserialize(
    schemas,
    TestStruct,
    Buffer.from(serialized),
    BinaryReader
);
expect(deserialied.enum).toBeInstanceOf(Enum1);
expect((deserialied.enum as Enum1).b).toEqual(4);

Nested Schema generation for structs

class InnerStruct {
    @field({ type: 'typeB' })
    public b: number;

}

class TestStruct {
    @field({ type: InnerStruct })
    public a: InnerStruct;

}

const generatedSchemas = generateSchemas([TestStruct])
expect(generatedSchemas.get(TestStruct)).toEqual({
    kind: 'struct',
    fields: [
        ['a', InnerStruct],
    ]
});
expect(generatedSchemas.get(InnerStruct)).toEqual({
    kind: 'struct',
    fields: [
        ['b', 'typeB'],
    ]
});

Option

class TestStruct {
  @field({ type: 'u8', option: true })
  public a: number;

}
const schema = generateSchemas([TestStruct]).get(TestStruct)
expect(schema).toEqual({
  fields: [
      [
          "a",
          {
              kind: 'option',
              type: 'u8'
          },
      ]
  ],
  kind: "struct",
});

Custom serialization and deserialization

interface ComplexObject {
    a: number;
    b: number;
}
class TestStruct {
    @field({
        serialize: (value: ComplexObject, writer) => {
            writer.writeU16(value.a + value.b);
        },
        deserialize: (reader): ComplexObject => {
            let value = reader.readU16();
            return {
                a: value,
                b: value * 2,
            };
        },
    })
    public obj: ComplexObject;
    constructor(obj: ComplexObject) {
    this.obj = obj;
    }
}

const schemas = generateSchemas([TestStruct]);
const serialized = serialize(schemas, new TestStruct({ a: 2, b: 3 }));
const deserialied = deserialize(
    schemas,
    TestStruct,
    Buffer.from(serialized),
    BinaryReader
);
expect(deserialied.obj).toBeDefined();
expect(deserialied.obj.a).toEqual(5);
expect(deserialied.obj.b).toEqual(10);

Explicit serialization order of fields

class TestStruct {
    @field({ type: 'u8', index: 1 })
    public a: number;


    @field({ type: 'u8', index: 0 })
    public b: number;
}
const schema = generateSchemas([TestStruct]).get(TestStruct)
expect(schema).toEqual({
    fields: [
        [
            "b",
            "u8",
        ],
        [
            "a",
            "u8",
        ],
    ],
    kind: "struct",
});

Inheritance

Schema generation with class inheritance is not supported (yet)

Examples of manual schema generation

const schemas = new Map([[Test, { kind: 'struct', fields: [['x', 'u8'], ['y', 'u64'], ['z', 'string'], ['q', [3]]] }]]);

Serializing an object

const value = new Test({ x: 255, y: 20, z: '123', q: [1, 2, 3] });
const buffer = serialize(SCHEMAS, value);

Deserializing an object

const value = new Test({ x: 255, y: 20, z: '123', q: [1, 2, 3] });
const newValue = deserialize(SCHEMAS, SomeClass, buffer);

In order for 'SomeClass' be deserialized into, it has to support empty constructor, i. e.

class SomeClass
{
    constructor(data = undefined)
    {
        if(data)
        {
            ...
        }
    }
}

Type Mappings

Borsh TypeScript
u8 integer number
u16 integer number
u32 integer number
u64 integer BN
u128 integer BN
u256 integer BN
u512 integer BN
f32 float N/A
f64 float N/A
fixed-size byte array Uint8Array
UTF-8 string string
option null or type
map N/A
set N/A
structs any

Contributing

Install dependencies:

yarn install

Run tests:

yarn test

Run linter

yarn lint

License

This repository is distributed under the terms of both the MIT license and the Apache License (Version 2.0). See LICENSE-MIT and LICENSE-APACHE for details.

For official releases see: [Borsh]: https://borsh.io

Package Sidebar

Install

npm i @solvei/borsh

Weekly Downloads

2

Version

0.0.22

License

Apache-2.0

Unpacked Size

46.9 kB

Total Files

15

Last publish

Collaborators

  • marcus-quantleaf