UNPKG

@solana/codecs-data-structures

Version:

Codecs for various data structures

229 lines (219 loc) 7.96 kB
/* eslint-disable @typescript-eslint/no-explicit-any */ import { Codec, combineCodec, createDecoder, createEncoder, Decoder, Encoder, FixedSizeCodec, FixedSizeDecoder, FixedSizeEncoder, getEncodedSize, ReadonlyUint8Array, VariableSizeCodec, VariableSizeDecoder, VariableSizeEncoder, } from '@solana/codecs-core'; import { assertValidNumberOfItemsForCodec } from './assertions'; import { DrainOuterGeneric, getFixedSize, getMaxSize, sumCodecSizes } from './utils'; /** * Infers the TypeScript type for a tuple that can be encoded using a tuple codec. * * This type maps each provided item encoder to its corresponding value type. * * @typeParam TItems - An array of encoders, each corresponding to a tuple element. */ type GetEncoderTypeFromItems<TItems extends readonly Encoder<any>[]> = DrainOuterGeneric<{ [I in keyof TItems]: TItems[I] extends Encoder<infer TFrom> ? TFrom : never; }>; /** * Infers the TypeScript type for a tuple that can be decoded using a tuple codec. * * This type maps each provided item decoder to its corresponding value type. * * @typeParam TItems - An array of decoders, each corresponding to a tuple element. */ type GetDecoderTypeFromItems<TItems extends readonly Decoder<any>[]> = DrainOuterGeneric<{ [I in keyof TItems]: TItems[I] extends Decoder<infer TTo> ? TTo : never; }>; /** * Defines the configuration options for tuple codecs. */ export type TupleCodecConfig = { /** * An optional description for the codec, that will be used in error messages. */ description?: string; }; /** * Returns an encoder for tuples. * * This encoder serializes a fixed-size array (tuple) by encoding its items * sequentially using the provided item encoders. * * For more details, see {@link getTupleCodec}. * * @typeParam TItems - An array of encoders, each corresponding to a tuple element. * * @param items - The encoders for each item in the tuple. * @param config - Optional configuration for the description. * @returns A `FixedSizeEncoder` or `VariableSizeEncoder` for encoding tuples. * * @example * Encoding a tuple with 2 items. * ```ts * const encoder = getTupleEncoder([fixCodecSize(getUtf8Encoder(), 5), getU8Encoder()]); * * const bytes = encoder.encode(['Alice', 42]); * // 0x416c6963652a * // | └── Second item (42) * // └── First item ("Alice") * ``` * * @see {@link getTupleCodec} */ export function getTupleEncoder<const TItems extends readonly FixedSizeEncoder<any>[]>( items: TItems, config?: TupleCodecConfig, ): FixedSizeEncoder<GetEncoderTypeFromItems<TItems>>; export function getTupleEncoder<const TItems extends readonly Encoder<any>[]>( items: TItems, config?: TupleCodecConfig, ): VariableSizeEncoder<GetEncoderTypeFromItems<TItems>>; export function getTupleEncoder<const TItems extends readonly Encoder<any>[]>( items: TItems, config?: TupleCodecConfig, ): Encoder<GetEncoderTypeFromItems<TItems>> { type TFrom = GetEncoderTypeFromItems<TItems>; const fixedSize = sumCodecSizes(items.map(getFixedSize)); const maxSize = sumCodecSizes(items.map(getMaxSize)) ?? undefined; return createEncoder({ ...(fixedSize === null ? { getSizeFromValue: (value: TFrom) => items.map((item, index) => getEncodedSize(value[index], item)).reduce((all, one) => all + one, 0), maxSize, } : { fixedSize }), write: (value: TFrom, bytes, offset) => { assertValidNumberOfItemsForCodec(config?.description ?? 'tuple', items.length, value.length); items.forEach((item, index) => { offset = item.write(value[index], bytes, offset); }); return offset; }, }); } /** * Returns a decoder for tuples. * * This decoder deserializes a fixed-size array (tuple) by decoding its items * sequentially using the provided item decoders. * * For more details, see {@link getTupleCodec}. * * @typeParam TItems - An array of decoders, each corresponding to a tuple element. * * @param items - The decoders for each item in the tuple. * @returns A `FixedSizeDecoder` or `VariableSizeDecoder` for decoding tuples. * * @example * Decoding a tuple with 2 items. * ```ts * const decoder = getTupleDecoder([fixCodecSize(getUtf8Decoder(), 5), getU8Decoder()]); * * const tuple = decoder.decode(new Uint8Array([ * 0x41,0x6c,0x69,0x63,0x65,0x2a * ])); * // ['Alice', 42] * ``` * * @see {@link getTupleCodec} */ export function getTupleDecoder<const TItems extends readonly FixedSizeDecoder<any>[]>( items: TItems, ): FixedSizeDecoder<GetDecoderTypeFromItems<TItems>>; export function getTupleDecoder<const TItems extends readonly Decoder<any>[]>( items: TItems, ): VariableSizeDecoder<GetDecoderTypeFromItems<TItems>>; export function getTupleDecoder<const TItems extends readonly Decoder<any>[]>( items: TItems, ): Decoder<GetDecoderTypeFromItems<TItems>> { type TTo = GetDecoderTypeFromItems<TItems>; const fixedSize = sumCodecSizes(items.map(getFixedSize)); const maxSize = sumCodecSizes(items.map(getMaxSize)) ?? undefined; return createDecoder({ ...(fixedSize === null ? { maxSize } : { fixedSize }), read: (bytes: ReadonlyUint8Array | Uint8Array, offset) => { const values = [] as Array<any> & TTo; items.forEach(item => { const [newValue, newOffset] = item.read(bytes, offset); values.push(newValue); offset = newOffset; }); return [values, offset]; }, }); } /** * Returns a codec for encoding and decoding tuples. * * This codec serializes tuples by encoding and decoding each item sequentially. * * Unlike the {@link getArrayCodec} codec, each item in the tuple has its own codec * and, therefore, can be of a different type. * * @typeParam TItems - An array of codecs, each corresponding to a tuple element. * * @param items - The codecs for each item in the tuple. * @returns A `FixedSizeCodec` or `VariableSizeCodec` for encoding and decoding tuples. * * @example * Encoding and decoding a tuple with 2 items. * ```ts * const codec = getTupleCodec([fixCodecSize(getUtf8Codec(), 5), getU8Codec()]); * * const bytes = codec.encode(['Alice', 42]); * // 0x416c6963652a * // | └── Second item (42) * // └── First item ("Alice") * * const tuple = codec.decode(bytes); * // ['Alice', 42] * ``` * * @remarks * Separate {@link getTupleEncoder} and {@link getTupleDecoder} functions are available. * * ```ts * const bytes = getTupleEncoder([fixCodecSize(getUtf8Encoder(), 5), getU8Encoder()]) * .encode(['Alice', 42]); * * const tuple = getTupleDecoder([fixCodecSize(getUtf8Decoder(), 5), getU8Decoder()]) * .decode(bytes); * ``` * * @see {@link getTupleEncoder} * @see {@link getTupleDecoder} */ export function getTupleCodec<const TItems extends readonly FixedSizeCodec<any>[]>( items: TItems, config?: TupleCodecConfig, ): FixedSizeCodec<GetEncoderTypeFromItems<TItems>, GetDecoderTypeFromItems<TItems> & GetEncoderTypeFromItems<TItems>>; export function getTupleCodec<const TItems extends readonly Codec<any>[]>( items: TItems, config?: TupleCodecConfig, ): VariableSizeCodec< GetEncoderTypeFromItems<TItems>, GetDecoderTypeFromItems<TItems> & GetEncoderTypeFromItems<TItems> >; export function getTupleCodec<const TItems extends readonly Codec<any>[]>( items: TItems, config?: TupleCodecConfig, ): Codec<GetEncoderTypeFromItems<TItems>, GetDecoderTypeFromItems<TItems> & GetEncoderTypeFromItems<TItems>> { return combineCodec( getTupleEncoder(items, config), getTupleDecoder(items) as Decoder<GetDecoderTypeFromItems<TItems> & GetEncoderTypeFromItems<TItems>>, ); }