@msgpack/msgpack

repository·main·Indexed 23 days ago

https://github.com/msgpack/msgpack-javascript

A high-performance implementation of the MessagePack binary serialization format for JavaScript and TypeScript. Compatible with browsers, Node.js, and ES2015+ environments, it provides tools for encoding and decoding data, including support for streaming via decodeAsync, decodeMultiStream, and decodeArrayStream. Features include ExtensionCodec for custom types, BigInt support, and optimized Encoder/Decoder classes for performance.

Tokens
9K
Snippets
20
Records
45
Agent score
81%

What's inside @msgpack/msgpack

  1. Handle custom types with ExtensionCodec

    main

    To support custom JavaScript/TypeScript classes (like Map, Set, or custom domain objects) that are not part of the standard MessagePack specification, use the ExtensionCodec class.

    1. Create an instance of ExtensionCodec.
    2. Register an extension using .register({ type, encode, decode }).
    3. Important: Custom extension types must use a type ID in the range [0, 127]. The range [-1, -128] is reserved for MessagePack internals.
    4. Important: When performing recursive encoding or decoding, you must pass the extensionCodec instance in the options object of the encode or decode calls.
    import { encode, decode, ExtensionCodec } from "@msgpack/msgpack";
    
    const extensionCodec = new ExtensionCodec();
    
    // Example: Registering Set<T>
    const SET_EXT_TYPE = 0;
    extensionCodec.register({
      type: SET_EXT_TYPE,
      encode: (object: unknown): Uint8Array | null => {
        if (object instanceof Set) {
          return encode([...object], { extensionCodec });
        } else {
          return null;
        }
      },
      decode: (data: Uint8Array) => {
        const array = decode(data, { extensionCodec }) as Array<unknown>;
        return new Set(array);
      },
    });
    
    const encoded = encode([new Set<any>()], { extensionCodec });
    const decoded = decode(encoded, { extensionCodec });
  2. Understand MessagePack data mapping

    main

    MessagePack maps JavaScript values to specific MessagePack formats. The mapping behavior changes depending on the useBigInt64 option.

    Default Mode (useBigInt64: false)

    • null, undefined $\rightarrow$ nil (decoded as null)
    • boolean $\rightarrow$ bool family
    • number (53-bit int) $\rightarrow$ int family
    • number (64-bit float) $\rightarrow$ float family
    • string $\rightarrow$ str family (use rawStrings: true to decode as Uint8Array instead of UTF-8)
    • ArrayBufferView (including NodeJS Buffer) $\rightarrow$ bin family (decoded as Uint8Array)
    • Array $\rightarrow$ array family
    • Object $\rightarrow$ map family (treated as Record<string, unknown>)
    • Date $\rightarrow$ timestamp ext family (nanoseconds may be lost during decoding unless a custom extension codec is used)
    • bigint $\rightarrow$ Not supported (requires custom extension codec)

    BigInt Mode (useBigInt64: true)

    • number (32-bit int) $\rightarrow$ int family
    • number (other) $\rightarrow$ float family
    • bigint $\rightarrow$ int64 / uint64 (decoded as bigint. Note: behavior is undefined if value exceeds 64-bit limits)
  3. Improve performance by reusing `Encoder` and `Decoder` instances

    main

    For high-performance scenarios, use the Encoder and Decoder classes instead of the standalone encode() and decode() functions. Reusing instances avoids repeated allocation and can be significantly faster:

    • Encoder reuse is approximately 20% faster than encode().
    • Decoder reuse is approximately 2% faster than decode().

    Both classes accept the same options as their functional counterparts.

    import { deepStrictEqual } from "assert";
    import { Encoder, Decoder } from "@msgpack/msgpack";
    
    const encoder = new Encoder();
    const decoder = new Decoder();
    
    const object = { foo: "bar" };
    const encoded: Uint8Array = encoder.encode(object);
    deepStrictEqual(decoder.decode(encoded), object);
  4. Decode MessagePack from a Web Blob

    main

    To decode MessagePack data stored in a browser Blob, use the asynchronous API.

    • If blob.stream() is available, use decodeAsync(blob.stream()). This is the recommended approach for large objects as it supports streaming.
    • If blob.stream() is not available, fallback to decode(await blob.arrayBuffer()).
    async function decodeFromBlob(blob: Blob): Promise<unknown> {
      if (blob.stream) {
        // Blob#stream(): ReadableStream<Uint8Array> (recommended)
        return await decodeAsync(blob.stream());
      } else {
        // Blob#arrayBuffer(): Promise<ArrayBuffer> (if stream() is not available)
        return decode(await blob.arrayBuffer());
      }
    }
  5. Install @msgpack/msgpack via NPM or CDN

    main

    NPM

    Install the package via npm for use in NodeJS or bundlers like Webpack.

    CDN

    For direct browser usage, include the script via unpkg:

    <script crossorigin src="https://unpkg.com/@msgpack/msgpack"></script>

    This loads the MessagePack module onto the global object.

  6. Handle BigInt in MessagePack

    main

    The library does not handle BigInt by default. You have two options:

    1. Simple approach: Set useBigInt64: true in your options. This maps bigint to MessagePack's int64/uint64.
      • Limitations: It always uses 8-byte binaries even for small integers, and values must fit within the signed/unsigned 64-bit range, otherwise behavior is undefined.
    2. Custom approach: Define a custom ExtensionCodec to map bigint to a specific extension type. This is useful if you need to support arbitrary-precision integers (e.g., by encoding them as strings).
    import { encode, decode, ExtensionCodec, DecodeError } from "@msgpack/msgpack";
    
    const BIGINT_EXT_TYPE = 0;
    const extensionCodec = new ExtensionCodec();
    extensionCodec.register({
      type: BIGINT_EXT_TYPE,
      encode(input: unknown): Uint8Array | null {
        if (typeof input === "bigint") {
          if (input <= Number.MAX_SAFE_INTEGER && input >= Number.MIN_SAFE_INTEGER) {
            return encode(Number(input));
          } else {
            return encode(String(input));
          }
        } else {
          return null;
        }
      },
      decode(data: Uint8Array): bigint {
        const val = decode(data);
        if (!(typeof val === "string" || typeof val === "number")) {
          throw new DecodeError(`unexpected BigInt source: ${val} (${typeof val})`);
        }
        return BigInt(val);
      },
    });
    
    const value = BigInt(Number.MAX_SAFE_INTEGER) + BigInt(1);
    const encoded = encode(value, { extensionCodec });
    const decoded = decode(encoded, { extensionCodec });
  7. Prerequisites and Environment Support

    main

    This is a universal JavaScript library supporting major browsers and NodeJS.

    ECMA-262 (Browsers)

    • Requires ES2015+ features.
    • Requires ES2024 standard library features (Typed arrays, Async iterations, etc.).
    • Requires TextEncoder and TextDecoder (whatwg encodings).
    • IE11 Support: Not supported in current versions. Use v2.x for IE11.
    • Polyfills: ES2022 features can be polyfilled with core-js.

    NodeJS

    • Requires NodeJS v18 or higher.

    TypeScript

    • Requires type definitions for AsyncIterator, ArrayBufferLike, and whatwg streams. Recommended tsconfig.json setting: "lib": ["ES2024", "DOM"].
  8. Use @msgpack/msgpack with Deno or Bun

    main

    The library is compatible with both Deno and Bun runtimes.

    • Deno: Use the module directly. Refer to example/deno-*.ts in the repository for implementation patterns. Note that deno.land/x is not supported.
    • Bun: Supported natively.
  9. Optimize decoding of large Float arrays

    main

    For high-performance decoding of large Float32Array or Float64Array payloads, you can use an ExtensionCodec that returns a function from the encode method. This allows you to implement buffer alignment (padding) so that the decode method can create a view on the existing buffer without copying data, which is significantly faster.

    const extensionCodec = new ExtensionCodec();
    
    const EXT_TYPE_FLOAT32ARRAY = 0;
    extensionCodec.register({
      type: EXT_TYPE_FLOAT32ARRAY,
      encode: (object: unknown) => {
        if (object instanceof Float32Array) {
          return (pos: number) => {
            const bpe = Float32Array.BYTES_PER_ELEMENT;
            const padding = 1 + ((bpe - ((pos + 1) % bpe)) % bpe);
            const data = new Uint8Array(object.buffer);
            const result = new Uint8Array(padding + data.length);
            result[0] = padding;
            result.set(data, padding);
            return result;
          };
        }
        return null;
      },
      decode: (data: Uint8Array) => {
        const padding = data[0]!;
        const bpe = Float32Array.BYTES_PER_ELEMENT;
        const offset = data.byteOffset + padding;
        const length = data.byteLength - padding;
        return new Float32Array(data.buffer, offset, length / bpe);
      },
    });
  10. Understand the ExtData class for unregistered extension types

    main

    When decoding MessagePack data, if the decoder encounters an extension type that has not been registered with an ExtensionCodec, it will represent that data using an ExtData instance.

    An ExtData object contains:

    • type: The extension type number (an integer).
    • data: The raw payload, provided either as a Uint8Array or as a function (pos: number) => Uint8Array that retrieves the data at a specific position.