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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@buffela/parser

v5.0.0-alpha.3

Published

Parser for the buffela data format schema

Readme

Buffela binary format

Buffela (pronounced bah-FEH-lah) is an extensible, schema-based binary format.

First, you write the schema in good old YAML:

Uuid: String(36)
Timestamp: Double

Gender:
  - FEMALE
  - MALE

Phone:
  countryCode: UInt(10)
  number: String

User:
  userId: Uuid

  Anonymous: {}

  Registered:
    verified: Boolean

    Viewer:
      birthDate: String # We'll implement our own custom "Date" type in the Custom Primitives section
      gender: Gender
      phone: Phone?

    Organizer:
      roles: String[UByte]
      userId: String
      email: String

AuthTokenPayload:
  issuedAt: Timestamp
  user: User

AuthTokenSignature:
  hmac256: Bytes(32)

Then you can import it in your favorite programming language - as long as it is Javascript, Typescript or Kotlin ;) - and write type safe serialization and deserialization code.

Buffela supports all the types you would expect (strings, booleans, numbers), along with enums, subtypes (similar to kotlin's sealed types) and arrays (in both constant and variable sized variants). You can also easily extend buffela with your own types!

What's new in Version 5

  • Added mechanism to create reusable custom primitives and import them in multiple schemas
  • Added watch mode to the compilers
  • Added compile time validation of referenced schema types
  • Added automatic package detection to the kotlin compiler
  • Fixed kotlin behavior when a nested type was shadowing a root type or a primitive

This version is binary compatible with packets generated using older versions.

Breaking API Changes

Older changes

Why not protobuf

Buffela does away with forward and backward compatible schemata, in favor of schema readability and size efficiency. For the example we'll be building in this guide, the output will end up being more than 30% smaller than the equivalent protobuf! To achieve this size efficiency, buffela gives you more control over the format. You can manually specify the byte length of numbers (byte, short, integer, long), or even the bit length if you're so inclined. The same goes for arrays, you can make them fixed size or specify the byte/bit length of the item count. Additionally, you don't have to worry about field numbers and type safety is built in.

See the equivalent protobuf schema for comparison and decide for yourself which one you would prefer writing.

Compatible languages

We currently support Javascript/Typescript and Kotlin Multiplatform.

There are two ways to implement a (de)serializer:

  1. Reflection

    Go through the schema and decide how to encode/decode each field at runtime. This can be slow, especially for typed languages but it means that the schema needs no preprocessing.

  2. Code generation

    Decide and write down the steps to encode/decode each field in a preprocessing step. Basically, compile the schema into code. Encoding or decoding at runtime is as fast as it can be.

Ideally, we would support both approaches for all supported languages, but ain't nobody got time for that. So here is what we currently support:

| Language | Reflection based serialization | Reflection based deserialization | Serializer code generation | Deserializer code generation | | --------------------- | ------------------------------ | -------------------------------- | -------------------------- | ---------------------------- | | Javascript/Typescript | ✅ | ✅ | ❌ | ❌ | | Kotlin Multiplatform | ❌ | ❌ | ✅ | ✅ |

Installation

Javascript/Typescript

Install the schema parser

npm i @buffela/parser

You want to serialize?

npm i @buffela/serializer

You want to deserialize?

npm i @buffela/deserializer

You're building a front-end?

Install the buffer browser polyfill.

JSDoc

Install typescript as a dev dependency

npm i -D typescript

Set up a simple tsconfig.json inside your project folder (don't worry it's for your editor, I won't have you compile anything)

{
  "compilerOptions": {
    "moduleResolution": "nodenext",
    "skipLibCheck": true
  }
}

Kotlin

You'll need to install some dependencies required by the generated code in your project:

gr.elaevents.buffela:serialization
gr.elaevents.buffela:deserialization

Serialization library on maven central

Deserialization library on maven central

You can skip installing either package if you're interested in only serializing or only deserializing.

Usage

Javascript/Typescript

Install the tooling as a dev dependency

npm i -D @buffela/tools

Run the converter

buffela-js convert SCHEMA_FILE

This will generate a .json and a .ts file alongside your schema file, as long as your schema file is not in a parent directory. In that case it will generate them in the current working directory. To understand this behavior or to explore all tooling options, refer to the tooling guide.

Kotlin

You can run the kotlin compiler through npm:

Don't know what npm is? Bless your innocent soul xD. Install nvm (https://github.com/nvm-sh/nvm) and then run nvm install --lts. Now you should have node and npm with it.

npx @buffela/tools-kotlin compile SCHEMA_FILE

The first time around it will ask you to download the package, press Enter to proceed.

This will create a .kt file alongside your schema file, as long as your schema file is not in a parent directory. In that case it will generate it in the current working directory. To understand this behavior or to explore all compiler options, refer to the tooling guide.

Javascript example

const { parseSchema } = require('@buffela/parser')
const { registerSerializer } = require('@buffela/serializer')
const { registerDeserializer } = require('@buffela/deserializer')

/**
 * Do this only if you have generated types
 * @type {import('./YOUR_TYPES').default}
 */
const schema = parseSchema(require('./YOUR_JSON'))
registerSerializer(schema, {})
registerDeserializer(schema, {})

const bytes = schema.AuthTokenPayload.serialize({
    issuedAt: Date.now(),
    user: {
        _type: schema.User.Registered.Viewer,
        userId: '588809b0-d8ce-4a6b-a2aa-9b10fd9d7a11',
        verified: true,
        birthDate: '2003-07-22',
        gender: schema.Gender.MALE,
        phone: {
            countryCode: 30,
            number: '1234567890',
        }
    }
})

const payload = schema.AuthTokenPayload.deserialize(bytes)
if (schema.User.Registered.instanceOf(payload.user)) {
  console.log('User is registered')
}

Typescript example

import { parseSchema } from '@buffela/parser'
import { registerSerializer } from '@buffela/serializer'
import { registerDeserializer } from '@buffela/deserializer'

import type Schema from './YOUR_TYPES'
import schemaObject from './YOUR_JSON'

const schema = parseSchema(schemaObject) as Schema
registerSerializer(schema, {})
registerDeserializer(schema, {})

const bytes = schema.AuthTokenPayload.serialize({
    issuedAt: Date.now(),
    user: {
        _type: schema.User.Registered.Viewer,
        userId: '588809b0-d8ce-4a6b-a2aa-9b10fd9d7a11',
        verified: true,
        birthDate: '2003-07-22',
        gender: schema.Gender.MALE,
        phone: {
            countryCode: 30,
            number: '1234567890',
        }
    }
})

const payload = schema.AuthTokenPayload.deserialize(bytes)
if (schema.User.Registered.instanceOf(payload.user)) {
  console.log('User is registered')
}

Kotlin example

package YOUR_PACKAGE

import gr.elaevents.buffela.serialization.serialize
import gr.elaevents.buffela.deserialization.deserialize

fun main() {
    val bytes = AuthTokenPayload(
        issuedAt = System.currentTimeMillis().toDouble(),
        user = User.Registered.Viewer(
            userId = "588809b0-d8ce-4a6b-a2aa-9b10fd9d7a11",
            verified = true,
            gender = Gender.MALE,
            birthDate = "2003-22-07",
            phone = Phone(
                countryCode = 30u,
                number = "1234567890",
            ),
        )
    ).serialize()

    val payload = AuthTokenPayload.deserialize(bytes)
    if (payload.user is User.Registered) {
        println("User is registered")
    }
}

Schema validation

We provide a JSON schema for in-editor validation of your buffela schemata.

Note that while your editor highlights all syntax errors, the error messages it provides are not very descriptive. If you need more detailed errors, you can run the compiler for your language of choice.

Javascript/Typescript

Since we expect you'll be writing schemata in a development environment, the schema is bundled with the developer tools that you probably already need.

After installing the tools, you can find the schema at node_modules/@buffela/tools-common/schemata/buffela-schema.json.

Kotlin

You can export the JSON schema through the command line tool:

npx @buffela/tools-kotlin schema

This will create a buffela-schema.json file in your current directory.

Please make sure to run the command again after every minor or major buffela update in order to get the latest features.

Installation

Install on JetBrains products (Please select the latest schema version)

For VS Code, install the RedHat YAML extension and associate the JSON schema

Schema syntax

Root types

The top level types inside your buffela schema are called root types. Their name must (1) start with a capital letter and (2) only contain letters and numbers.

Going back to our example:

Gender:
  # [...]

User:
  # [...]

AuthTokenPayload:
  # [...]
  
AuthTokenSignature:
  # [...]

Gender, User, AuthTokenPayload and AuthTokenSignature are all root types.

Root types can be either object types or enumeration types.

Enumeration types

Enumeration types represent single-choice types.

They are yaml arrays that can only contain unique values.

Their values must (1) be in all uppercase and (2) only contain letters, numbers and underscores.

Here is an enumeration type from our example:

Gender:
  - FEMALE
  - MALE

Object types

Object types represent a collection of fields.

They are yaml objects.

The keys are the field names, and they must (1) start with a lowercase letter and (2) only contain letters and numbers.

The values are the field types.

Here is an object from our example:

AuthTokenPayload:
  issuedAt: Timestamp
  user: User

Field types

References

You can reference other enumeration and object types. For example:

gender: Gender
user: User

Fixed-size primitives

These are the supported fixed-size primitive types along with their mapping to the supported languages and their size in bits.

| Buffela Type | Javascript Type | Kotlin Type | Bit Length | Description | | ------------ | --------------- | ----------- | ---------- | ------------------------------------------------------------ | | Boolean | boolean | Boolean | 1 | True or False | | Byte | number | Byte | 8 | Integers from -128 to 127 | | UByte | number | UByte | 8 | Integers from 0 to 255 | | Short | number | Short | 16 | Integers from -32,768 to 32,767 | | UShort | number | UShort | 16 | Integers from 0 to 65,535 | | Int | number | Int | 32 | Integers from -2,147,483,648 to 2,147,483,647 | | UInt | number | UInt | 32 | Integers from 0 to 4,294,967,295 | | Long | BigInt | Long | 64 | Integers from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 | | ULong | BigInt | ULong | 64 | Integers from 0 to 18,446,744,073,709,551,615 | | Float | number | Float | 32 | Decimals from 3.4E-38 to 3.4E+38 | | Double | number | Double | 64 | Decimals from 1.7E -308 to 1.7E+308 |

Here is an example:

verified: Boolean

Constant-sized primitives

These are the supported constant-sized primitive types along with their mapping to the supported languages and their size in bits.

| Buffela Type | Javascript Type | Kotlin Type | Bit Length | Description | | ------------ | --------------- | ----------- | ---------- | ------------------------------------- | | Int(N) | number | Int | N | Integers from -2^(N-1) to 2^(N-1) - 1 | | UInt(N) | number | UInt | N | Integers from 0 to 2^N - 1 |

N must always be <= 31

Here is an example:

countryCode: UInt(10)

Optimization note

Prefer using the fixed-size types instead of using N values of 8 or 16:

  • [U]Int(8) -> [U]Byte
  • [U]Int(16) -> [U]Short

Warning: This is NOT a binary compatible change

Strings

String is a sentinel type meaning that you can optionally specify a constant length if you know it beforehand in order to save one byte.

Both of these examples are valid:

email: String
userId: String(36)

Typed arrays

These are the supported typed array primitive types along with their mapping to the supported languages.

| Buffela Type | Javascript Type | Kotlin Type | | ---------------- | --------------- | ------------ | | UByteArray | Uint8Array | UByteArray | | ByteArray | Int8Array | ByteArray | | UShortArray | Uint16Array | UShortArray | | ShortArray | Int16Array | ShortArray | | UIntArray | Uint32Array | UIntArray | | IntArray | Int32Array | IntArray | | LongArray | BigInt64Array | LongArray | | ULongArray | BigUint64Array | ULongArray | | FloatArray | Float32Array | FloatArray | | DoubleArray | Float64Array | DoubleArray | | BooleanArray | boolean[] | BooleanArray |

Typed array types require a parameter that specifies the length.

You must either pass a constant length (e.g. 10) or, if your array needs to have a variable length, you must pass the numeric type that will be used to store the length. Said type will constrain the maximum length of your array. The following types are accepted: UByte, UShort, Int and UInt.

All of these examples are valid:

vector: FloatArray(10) # This array must have a length of exactly 10 floats
vector: FloatArray(UByte) # This array can have a length of up to 255 floats
vector: FloatArray(UInt(10)) # This array can have a length of up to 1023 floats

Bytes

The Bytes type maps to Uint8Array in JS and ByteArray in Kotlin. Like a typed array, it also requires a parameter that specifies the length, and the same rules apply.

Arrays

You can add a [LENGTH] suffix to any type to create an array of that type. You can also chain them to create N-dimensional arrays. The length can be either a constant or a length type just like the parameter of typed arrays.

All of these examples are valid:

roles: String[10] # This array must have a length of exactly 10 strings
roles: String[UByte] # This array can have a length of up to 255 strings
roles: String[UInt(10)] # This array can have a length of up to 1023 strings

Don't get confused by typed arrays, you can also make them higher-dimensional:

spaceTemperature: FloatArray(10)[10][10] # This represents a 10x10x10 cube of floats

Optimization note

Prefer using the primitive arrays where available e.g. Float[SIZE] -> FloatArray(SIZE)

Optional types

You can make any type optional (nullable) by adding a ? suffix. For example:

phone: Phone?

Custom primitives

In our example, we store dates (specifically the birth date of registered viewers) as strings. This is not very efficient and takes 10 bytes (=80 bits) to store. With a custom type we could wind that down to only 22 bits. Specifically, we could combine the month and year into a single 17-bit number using the following formula: year * 12 + (month - 1) and store the day in a 5-bit number, allowing us to represent any date between 0000-00-00 and 10922-08-31. As this type requires math operations, we cannot use a simple object type but we can extend buffela with our own custom primitive.

A custom type consists of a serializer and a deserializer. We will have to make use of the SerializerBuffer and DeserializerBuffer APIs.

| SerializerBuffer Methods | DeserializerBuffer Methods | | ------------------------------- | -------------------------- | | writeByte(byte) | readByte() | | writeUByte(uByte) | readUByte() | | writeShort(short) | readShort() | | writeUShort(uShort) | readUShort() | | writeInt(int, bitLength?) | readInt(bitLength?) | | writeUInt(uInt, bitLength?) | readUInt(bitLength?) | | writeLong(long) | readLong() | | writeULong(uLong) | readULong() | | writeFloat(float) | readFloat() | | writeDouble(double) | readDouble() | | writeBoolean(boolean) | readBoolean() | | writeString(string) | readString(length?) | | writeBytes(bytes) | readBytes(length) |

There are also some read-only properties:

  • SerializerBuffer.length: How many bytes have been written to the buffer thus far
  • DeserializerBuffer.length: How many bytes were initially loaded into the buffer
  • DeserializerBuffer.position: How many bytes have been read from the buffer thus far

JSDoc/Typescript

You need to create a file called 'SCHEMA.primitives.ts' in the directory where the generated 'SCHEMA.ts' file will live:

Method 1: Define and export the interface
export interface Date {
    year: number;
    month: number;
    day: number;
}
Method 2: Re-export the interface from another file or library
export type { Date } from "SOME_OTHER_FILE_OR_LIBRARY"

Or, using default exports:

export type { default as Date } from "SOME_OTHER_DIRECTORY_OR_LIBRARY/Date.ts"

Javascript/Typescript

We now need to create and register a serializer for our type. We do this in our registerSerializer call:

registerSerializer(schema, {
    Date: {
        serialize(buffer, value) {
            const yearMonth = value.year * 12 + (value.month - 1)
            const day = value.day - 1

            buffer.writeUInt(yearMonth, 17)
            buffer.writeUInt(day, 5)
        }
    }
})

Same story for the deserializer in our registerDeserializer call:

registerDeserializer(schema, {
    Date: {
        deserialize(buffer) {
            const yearMonth = buffer.readUInt(17)
            const day = buffer.readUInt(5)

            return {
                year: Math.floor(yearMonth / 12),
                month: yearMonth % 12 + 1,
                day: day + 1
            }
        }
    }
})

You could also define both the serializer and deserializer in a common object (if you, for example, want to export the primitive for use on other schemas as well):

const DatePrimitive = {
    serialize(buffer, value) {
        // [...]
    },
    deserialize(buffer) {
        // [...]
    }
}

registerSerializer(schema, {
    Date: DatePrimitive
})

registerDeserializer(schema, {
    Date: DatePrimitive
})

Kotlin

Method 1: Package-private primitives

Create a kotlin file in the same package as your compiled schema:

package YOUR_SCHEMA_PACKAGE

import gr.elaevents.buffela.serialization.SerializerBuffer
import gr.elaevents.buffela.deserialization.DeserializerBuffer

data class Date(
  val year: Int, 
  val month: Int, 
  val day: Int
)

fun SerializerBuffer.writeDate(date: Date) {
    val yearMonth = date.year * 12 + (date.month - 1)
    val day = date.day - 1

    writeUInt(yearMonth.toUInt(), 17)
    writeUInt(day.toUInt(), 5)
}

fun DeserializerBuffer.readDate(): Date {
    val yearMonth = readUInt(17).toInt()
    val day = readUInt(5).toInt()

    return Date(
        year = yearMonth / 12,
        month = yearMonth % 12 + 1,
        day = day + 1
    )
}
Method 2: Importing primitives
package SOME_PACKAGE

import gr.elaevents.buffela.serialization.SerializerBuffer
import gr.elaevents.buffela.deserialization.DeserializerBuffer

data class Date(
  // See method 1 example
)

fun SerializerBuffer.writeDate(date: Date) { 
  // See method 1 example
}

fun DeserializerBuffer.readDate(): Date { 
  // See method 1 example
}

Create a file called 'SCHEMA.primitives.yaml' in the directory where the generated 'SCHEMA.kt' file will live.

For each primitive define a key, and provide the package where the class, serializer, and deserializer live as a value.

Date: SOME_PACKAGE
Method 3: Using external types as primitives

Imagine that the Date class was provided by a third party library. You can still use it as a primitive!

package SOME_PACKAGE

import gr.elaevents.buffela.serialization.SerializerBuffer
import gr.elaevents.buffela.deserialization.DeserializerBuffer

object LIBRARY_PRIMITIVES {
    typealias Date = LIBRARY.Date
  
    fun SerializerBuffer.writeDate(date: Date) { 
      // See method 1 example
    }
  
    fun DeserializerBuffer.readDate(): Date { 
      // See method 1 example
    }
}

Create a file called 'SCHEMA.primitives.yaml' in the directory where the generated 'SCHEMA.kt' file will live.

For each primitive define a key, and provide the object where the type alias, serializer and deserializer live as a value.

Date: SOME_PACKAGE.LIBRARY_PRIMITIVES

Schema

Now we can finally use our type, so we declare the new primitive at the top of the schema:

Date: Primitive() # Note the '()', it is required

And simple change:

birthDate: String

to

birthDate: Date

Aliases

You may find yourself using a type with a specific parameter or array length over and over again in your schema. That is why aliases exist. Simply define a type alias in your schema to any primitive or complex type:

Uuid: String(36)

Your could also use aliases for purely semantic reasons:

Timestamp: Double

Have in mind though that alias resolution is not recursive, or put more simply, you cannot use aliases inside an alias.

Sub (object) types

Sometimes you may need an object that can take multiple forms. In our example we create an authentication token for all users, registered or not, containing a unique user id. But our registered users will have additional fields, like a flag indicating whether their credentials are verified. Additionally, we have two types of users: viewers and organizers. Organizers register by email and viewers register by phone. To represent this hierarchy we can use subtypes.

User:
  # [...]

  Anonymous: {}

  Registered:
    # [...]

    Viewer: 
      # [...]

    Organizer:
      # [...]

In our example, Anonymous and Registered are subtypes of User. A subtype inherits all fields from its parent type, and can have other subtypes of its own. In the compiled representation the structure is flattened, meaning that all fields live in the same level.

Anonymous is kind of special in that it doesn't define any fields nor does it have any subtypes. In this case the value can be {} (an empty object).

Subtype specification

How you specify a subtype differs from language to language:

In JavaScript, the compiled type will have an additional special field named _type. You need to provide a leaf schema type:

const schema = parseSchema(...)

const user = {
    _type: schema.User.Registered.Viewer,
    // [...]
}

To check if an object is a subtype of a type you can use the isInstance() function:

if (schema.User.Registered.isInstance(user)) {
    // [...]
}

In Kotlin, subtypes are just nested classes. So to create a registered viewer you would do:

val user = User.Registered.Viewer(/* [...] */)

To check if an object is a subtype of a type you can use the is operator:

if (user is User.Registered) {
    // [...]
}

Field overriding

You may need to override the type of a field only for specific subtypes. To do this, simply define the field again inside the subtype with the modified type. For our example, let's say that organizers can choose any user id they want, while all other users get automatically generated UUIDs. You would write this as:

User:
  userId: Uuid # Resolves to String(36)
  # [...]

  Registered:
    # [...]

    Organizer:
      userId: String

With this mechanism you can safely override the length parameter or an array's i-th dimension length (e.g. String -> String(10), Int[10] -> Int[UByte]). The only rule is that all type overrides should map to the same native type in your language of choice.

In advanced scenarios, you could also override with a more specific custom primitive (e.g Date -> DateTime).

In extreme scenarios, and if you're exclusively using Javascript, you could even override with a different numeric type (e.g. Int -> Double) as these both map to the same 'number' type. This, conversely, would be invalid in Kotlin.

Root type concatenation

In some cases you may want to serialize multiple root types into a single buffer. In this case you must work directly with the SerializerBuffer and DeserializerBuffer classes.

Simple example

If you're using buffela for client-server communication you may want to add a version header to all packets.

Javascript/Typescript

const buffer = new SerializerBuffer()

schema.Header.serialize({ version: 2 }, buffer)
schema.Body.serialize({ /* [...] */ }, buffer)

const bytes = buffer.toBytes()
const buffer = new DeserializerBuffer(bytes)

const header = schema.Header.deserialize(buffer)
if (header.version !== 2)
    throw new Error("Packet version not supported")

const body = schema.Body.deserialize(buffer)

Kotlin

val buffer = SerializerBuffer()

val header = Header(version = 2)
header.serialize(buffer)

val body = Body(/* [...] */)
body.serialize(buffer)

val bytes = buffer.toBytes()
val buffer = DeserializerBuffer(bytes)

val header = Header.deserialize(buffer)
if (header.version != 2)
    throw IllegalStateException("Packet version not supported")

val body = Body.deserialize(buffer)

Bit buffer

Buffela uses bit packing internally to minimize the output size, but that means that the boundary between consecutive types inside the buffer may not be byte-aligned. To ensure that the next serialize()/deserialize() call begins at a byte boundary, you can call the alignToByte() method on the SerializerBuffer/DeserializerBuffer. This is useful when trying to isolate the bytes belonging to a specific type in order to hash or sign them. Note that if you call alignToByte() between two serialize() calls, then you must also do it between the deserialize() calls and vice versa.

Advanced example

In our example, we want to serialize our payload, then read it back as bytes in order to calculate the HMAC-256 signature and then write the calculated signature into the same buffer.

Javascript/Typescript

const buffer = new SerializerBuffer()

// Write the payload into the buffer
schema.AuthTokenPayload.serialize({ /* [...] */ }, buffer)

// Read the serialized payload and sign it
const payloadBytes = buffer.toBytes()
const hmac256 = sign(payloadBytes)

// Align to next byte
buffer.alignToByte()

// Write the signature into the buffer
schema.AuthTokenSignature.serialize({ hmac256 }, buffer)

// Get the combined payload + signature as bytes
const bytes = buffer.toBytes()
const buffer = new Deserializer(bytes)

// Deserialize the payload
const payload = schema.AuthTokenPayload.deserialize(buffer)

// The deserializer has only read the payload, all bytes up to the current position must be the payload
const payloadBytes = bytes.subarray(0, buffer.position)

// Align to next byte
buffer.alignToByte()

// Continue to deserialize the signature
const signature = schema.AuthTokenSignature.deserialize(buffer)

// Verify the signature
verify(payloadBytes, signature.hmac256)

View the complete example

Kotlin

val buffer = SerializerBuffer()

// Write the payload into the buffer
val payload = AuthTokenPayload(/* [...] */)
payload.serialize(buffer)

// Read the serialized payload and sign it
val payloadBytes = payload.toBytes()
val hmac256 = sign(payloadBytes)

// Align to next byte
buffer.alignToByte()

// Write the signature into the buffer
val signature = AuthTokenSignature(hmac256 = hmac256)
signature.serialize(buffer)

// Get the combined payload + signature as bytes
val bytes = buffer.toBytes()
val buffer = DeserializerBuffer(bytes)

// Deserialize the payload
val payload = AuthTokenPayload.deserialize(buffer)

// The deserializer has only read the payload, all bytes up to the current position must be the payload
val payloadBytes = bytes.sliceArray(0 until buffer.position)

// Align to next byte
buffer.alignToByte()

// Continue to deserialize the signature
val signature = AuthTokenSignature.deserialize(buffer)

// Verify the signature
verify(payloadBytes, signature.hmac256)

View the complete example