UNPKG

@solana/codecs-data-structures

Version:

Codecs for various data structures

161 lines • 7.81 kB
import { Decoder, FixedSizeDecoder, VariableSizeDecoder } from '@solana/codecs-core'; import { DrainOuterGeneric } from './utils'; /** * A function that builds a {@link Decoder} for a struct field whose shape * depends on the values of previously decoded fields in the same struct. * * The function receives a frozen snapshot of all fields that have been decoded * so far, in declaration order, and must return the {@link Decoder} that should * be used to read the current field from the byte stream. * * @typeParam TPriorFields - The shape of the fields that have already been * decoded by the time this factory is invoked. * @typeParam TValue - The type of the value produced by the returned decoder. * * @see {@link createDependentStructDecoder} */ export type DependentStructDecoderFieldFactory<TPriorFields extends Record<string, unknown>, TValue> = (fields: Readonly<TPriorFields>) => Decoder<TValue>; /** * A fluent builder that accumulates field decoders for a struct whose later * fields may depend on the values of earlier ones. * * Each call to {@link DependentStructDecoderBuilder.field | `field`} returns a * new builder whose accumulated field type is widened by the newly added field. * Call {@link DependentStructDecoderBuilder.build | `build`} to obtain the * final {@link Decoder} once every field has been declared. * * The builder tracks at the type level whether the struct decoded so far has a * fixed size. Adding a {@link FixedSizeDecoder} preserves that property, while * adding a {@link VariableSizeDecoder} or a {@link DependentStructDecoderFieldFactory | field factory} * drops the builder to variable size and it stays variable thereafter. * * Instances of this type are immutable. Calling `field` does not mutate the * receiver; it returns a new builder. * * @typeParam TFields - The shape of the struct that has been accumulated so far. * @typeParam TIsFixedSize - `true` while every field added so far is a * {@link FixedSizeDecoder}, `false` once any variable size decoder or * factory has been added. * * @see {@link createDependentStructDecoder} */ export type DependentStructDecoderBuilder<TFields extends Record<string, unknown>, TIsFixedSize extends boolean> = { /** * Finalizes the builder and returns a {@link Decoder} that decodes each * declared field in turn, in the order they were added. * * Returns a {@link FixedSizeDecoder} when every field has been added with a * fixed size decoder, and a {@link VariableSizeDecoder} otherwise. */ build(): TIsFixedSize extends true ? FixedSizeDecoder<DrainOuterGeneric<TFields>> : VariableSizeDecoder<DrainOuterGeneric<TFields>>; /** * Adds a field decoded by a {@link VariableSizeDecoder}. Drops the builder * to variable size; subsequent {@link field} calls cannot raise it back to * fixed size. * * Adding a field that has already been declared on this builder is a * compile time error. */ field<TName extends string, TValue>(name: TName extends keyof TFields ? never : TName, decoder: VariableSizeDecoder<TValue>): DependentStructDecoderBuilder<DrainOuterGeneric<TFields & { [K in TName]: TValue; }>, false>; /** * Adds a field whose decoder is built from a frozen snapshot of the fields * that precede it. Drops the builder to variable size since the byte * length of the produced decoder cannot be known at type time. * * Adding a field that has already been declared on this builder is a * compile time error. */ field<TName extends string, TValue>(name: TName extends keyof TFields ? never : TName, factory: DependentStructDecoderFieldFactory<TFields, TValue>): DependentStructDecoderBuilder<DrainOuterGeneric<TFields & { [K in TName]: TValue; }>, false>; /** * Adds a field decoded by a {@link FixedSizeDecoder}. Preserves the fixed * size property of the builder. * * Adding a field that has already been declared on this builder is a * compile time error. */ field<TName extends string, TValue>(name: TName extends keyof TFields ? never : TName, decoder: FixedSizeDecoder<TValue>): DependentStructDecoderBuilder<DrainOuterGeneric<TFields & { [K in TName]: TValue; }>, TIsFixedSize>; }; /** * Creates a fluent builder for a struct decoder whose later fields may depend * on the decoded values of earlier ones. * * Unlike {@link getStructDecoder}, which accepts a fixed array of named * decoders, this builder lets each field provide a factory that receives the * values that have already been decoded. This is useful for binary formats * where a count, version, or discriminator decoded near the start of the * struct controls how a later field must be parsed. * * The builder mirrors the fixed vs variable size behaviour of * {@link getStructDecoder}. The empty builder finishes to a * {@link FixedSizeDecoder} of size zero. Adding a {@link FixedSizeDecoder} * preserves the fixed size property and the sizes accumulate. Adding a * {@link VariableSizeDecoder} or a {@link DependentStructDecoderFieldFactory | field factory} * drops the builder to variable size, which is then preserved by every * subsequent {@link DependentStructDecoderBuilder.field | `field`} call. * * The returned builder is immutable; each {@link DependentStructDecoderBuilder.field | `field`} * call returns a new builder whose accumulated field type is widened by the * newly added field. Call {@link DependentStructDecoderBuilder.build | `build`} * to produce the final decoder. * * @remarks * Prefer {@link getStructDecoder} when every field's decoder is independent of * the values that precede it. Reach for this builder only when at least one * field needs to be parameterised by another. * * The encoder direction does not need a dependent variant. An encoder already * has access to the entire value when serialising, so the existing * {@link getStructEncoder} can be paired with the decoder returned by this * builder and combined with `combineCodec` to obtain a full codec. * * @example * Decoding a struct whose array length is read from an earlier field. * ```ts * import { getArrayDecoder } from '@solana/codecs-data-structures'; * import { getU8Decoder, getU32Decoder } from '@solana/codecs-numbers'; * * const decoder = createDependentStructDecoder() * .field('count', getU8Decoder()) * .field('values', fields => getArrayDecoder(getU32Decoder(), { size: fields.count })) * .build(); * * decoder.decode(new Uint8Array([0x02, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00])); * // { count: 2, values: [1, 2] } * ``` * * @example * Mixing static and dependent fields, with a discriminator selecting the payload decoder. * ```ts * const decoder = createDependentStructDecoder() * .field('version', getU8Decoder()) * .field('header', fields => fields.version === 0 ? getU16Decoder() : getU32Decoder()) * .build(); * ``` * * @example * Combining the dependent decoder with a static encoder to obtain a full codec. * ```ts * import { combineCodec } from '@solana/codecs-core'; * * const encoder = getStructEncoder([ * ['count', getU8Encoder()], * ['values', getArrayEncoder(getU32Encoder())], * ]); * const decoder = createDependentStructDecoder() * .field('count', getU8Decoder()) * .field('values', fields => getArrayDecoder(getU32Decoder(), { size: fields.count })) * .build(); * const codec = combineCodec(encoder, decoder); * ``` * * @see {@link getStructDecoder} * @see {@link getStructEncoder} */ export declare function createDependentStructDecoder(): DependentStructDecoderBuilder<Record<never, never>, true>; //# sourceMappingURL=dependent-struct.d.ts.map