Minimal JSON-like binary schema-full protocol for JS/TS with BYO runtime data validations.
- Small (< 1.5 KB)
- Fast
- Runtime data validations
- Customizable memory
- 36 types
Boolean
UInt8
UInt16
UInt32
Int8
Int16
Int32
Float32
Float64
Ascii8
Ascii16
Unicode8
Unicode16
Object
Array8
Array16
Enum
Tuple
NullableBoolean
NullableUInt8
NullableUInt16
NullableUInt32
NullableInt8
NullableInt16
NullableInt32
NullableFloat32
NullableFloat64
NullableAscii8
NullableAscii16
NullableUnicode8
NullableUnicode16
NullableObject
NullableArray8
NullableArray16
NullableEnum
NullableTuple
- Estimate payload size
- Static type inference
This function sets up all underlying buffers and counters.
It has to be called before encode
is called (not needed for decode
).
init({
DEFAULT_POOL_SIZE: 4_000,
MAX_POOL_SIZE: 16_000,
DEFAULT_CHUNK_SIZE: 2_000,
});
Encodes JS data into ArrayBuffer
based on Type
.
const arrayBuffer = encode({
type: Type.Object,
value: [
{ key: "name", type: Type.Ascii },
{ key: "age", type: Type.UInt8 }
]
}, {
type: "Yamiteru",
age: 27
});
Decodes ArrayBuffer
into JS data based on Type
.
const data = decode({
type: Type.Object,
value: [
{ key: "name", type: Type.Ascii },
{ key: "age", type: Type.UInt8 }
]
}, arrayBuffer);
Estimates payload size of Type
in bytes and chunks based on provided Settings
.
const {
bytes, // 66
chunks // 9
} = estimate({
type: Type.Object,
value: [
{ key: "name", type: Type.Ascii, maxLength: 64 },
{ key: "age", type: Type.UInt8 }
]
}, {
MAX_POOL_SIZE: 256,
DEFAULT_POOL_SIZE: 16,
DEFAULT_CHUNK_SIZE: 8,
});
Once you create a Type
you can easily infer it with Infer
.
encode
uses Infer
for its input value and decode
for its output value.
const user = {
type: Type.Object,
value: [
{ key: "name", type: Type.Ascii },
{ key: "age", type: Type.UInt8 }
]
} as const;
// {
// type: string,
// age: number
// }
type User = Infer<typeof user>;
Internally we use a growable ArrayBuffer
of initial size of 512 KB and maximum size of 5 MB which we segment into 8 KB chunks.
On every encode
call we check if there is enough free chunks and if there is not then we attempt to grow the buffer.
After encode
is done it frees the chunks it used, so they can be reused in subsequent calls.
Choose number of chunks based on your expected size of the data wisely since if the sub-buffer created from chunks is not big enough it will throw an overflow error.
{
type:
| Type.Boolean
| Type.NullableBoolean;
}
{
type:
| Type.UInt8
| Type.UInt16
| Type.UInt32
| Type.NullableUInt8
| Type.NullableUInt16
| Type.NullableUInt32;
}
{
type:
| Type.Int8
| Type.Int16
| Type.Int32
| Type.NullableInt8
| Type.NullableInt16
| Type.NullableInt32;
}
{
type:
| Type.Float32
| Type.Float64
| Type.NullableFloat32
| Type.NullableFloat64;
}
{
type:
| Type.Ascii8
| Type.Ascii16
| Type.NullableAscii8
| Type.NullableAscii16;
}
{
type:
| Type.Unicode8
| Type.Unicode16
| Type.NullableUnicode8
| Type.NullableUnicode16;
}
{
type:
| Type.Object
| Type.NullableObject;
value: (AnyType & Required<Type.Keyable>)[];
}
{
type:
| Type.Array8
| Type.Array16
| Type.NullableArray8
| Type.NullableArray16;
value: AnyType;
}
{
type:
| Type.Tuple
| Type.NullableTuple;
value: AnyType[];
}
{
type:
| Type.Enum
| Type.NullableEnum;
value: unknown[];
}
Used in Object
for property names.
{
key?: string;
}
Any type can be asserted by adding assert
property to it.
In encode
it's run before encoding and in decode
it's run after decoding.
If type is nullable
and the value is null
we do not run assert
in either encode
or decode
.
{
assert?: (v: unknown) => asserts v is unknown;
}