@solana/codecs-data-structures 6.3.1 → 6.3.2-canary-20260313112147

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/tuple.ts ADDED
@@ -0,0 +1,228 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import {
3
+ Codec,
4
+ combineCodec,
5
+ createDecoder,
6
+ createEncoder,
7
+ Decoder,
8
+ Encoder,
9
+ FixedSizeCodec,
10
+ FixedSizeDecoder,
11
+ FixedSizeEncoder,
12
+ getEncodedSize,
13
+ ReadonlyUint8Array,
14
+ VariableSizeCodec,
15
+ VariableSizeDecoder,
16
+ VariableSizeEncoder,
17
+ } from '@solana/codecs-core';
18
+
19
+ import { assertValidNumberOfItemsForCodec } from './assertions';
20
+ import { DrainOuterGeneric, getFixedSize, getMaxSize, sumCodecSizes } from './utils';
21
+
22
+ /**
23
+ * Infers the TypeScript type for a tuple that can be encoded using a tuple codec.
24
+ *
25
+ * This type maps each provided item encoder to its corresponding value type.
26
+ *
27
+ * @typeParam TItems - An array of encoders, each corresponding to a tuple element.
28
+ */
29
+ type GetEncoderTypeFromItems<TItems extends readonly Encoder<any>[]> = DrainOuterGeneric<{
30
+ [I in keyof TItems]: TItems[I] extends Encoder<infer TFrom> ? TFrom : never;
31
+ }>;
32
+
33
+ /**
34
+ * Infers the TypeScript type for a tuple that can be decoded using a tuple codec.
35
+ *
36
+ * This type maps each provided item decoder to its corresponding value type.
37
+ *
38
+ * @typeParam TItems - An array of decoders, each corresponding to a tuple element.
39
+ */
40
+ type GetDecoderTypeFromItems<TItems extends readonly Decoder<any>[]> = DrainOuterGeneric<{
41
+ [I in keyof TItems]: TItems[I] extends Decoder<infer TTo> ? TTo : never;
42
+ }>;
43
+
44
+ /**
45
+ * Defines the configuration options for tuple codecs.
46
+ */
47
+ export type TupleCodecConfig = {
48
+ /**
49
+ * An optional description for the codec, that will be used in error messages.
50
+ */
51
+ description?: string;
52
+ };
53
+
54
+ /**
55
+ * Returns an encoder for tuples.
56
+ *
57
+ * This encoder serializes a fixed-size array (tuple) by encoding its items
58
+ * sequentially using the provided item encoders.
59
+ *
60
+ * For more details, see {@link getTupleCodec}.
61
+ *
62
+ * @typeParam TItems - An array of encoders, each corresponding to a tuple element.
63
+ *
64
+ * @param items - The encoders for each item in the tuple.
65
+ * @param config - Optional configuration for the description.
66
+ * @returns A `FixedSizeEncoder` or `VariableSizeEncoder` for encoding tuples.
67
+ *
68
+ * @example
69
+ * Encoding a tuple with 2 items.
70
+ * ```ts
71
+ * const encoder = getTupleEncoder([fixCodecSize(getUtf8Encoder(), 5), getU8Encoder()]);
72
+ *
73
+ * const bytes = encoder.encode(['Alice', 42]);
74
+ * // 0x416c6963652a
75
+ * // | └── Second item (42)
76
+ * // └── First item ("Alice")
77
+ * ```
78
+ *
79
+ * @see {@link getTupleCodec}
80
+ */
81
+ export function getTupleEncoder<const TItems extends readonly FixedSizeEncoder<any>[]>(
82
+ items: TItems,
83
+ config?: TupleCodecConfig,
84
+ ): FixedSizeEncoder<GetEncoderTypeFromItems<TItems>>;
85
+ export function getTupleEncoder<const TItems extends readonly Encoder<any>[]>(
86
+ items: TItems,
87
+ config?: TupleCodecConfig,
88
+ ): VariableSizeEncoder<GetEncoderTypeFromItems<TItems>>;
89
+ export function getTupleEncoder<const TItems extends readonly Encoder<any>[]>(
90
+ items: TItems,
91
+ config?: TupleCodecConfig,
92
+ ): Encoder<GetEncoderTypeFromItems<TItems>> {
93
+ type TFrom = GetEncoderTypeFromItems<TItems>;
94
+ const fixedSize = sumCodecSizes(items.map(getFixedSize));
95
+ const maxSize = sumCodecSizes(items.map(getMaxSize)) ?? undefined;
96
+
97
+ return createEncoder({
98
+ ...(fixedSize === null
99
+ ? {
100
+ getSizeFromValue: (value: TFrom) =>
101
+ items.map((item, index) => getEncodedSize(value[index], item)).reduce((all, one) => all + one, 0),
102
+ maxSize,
103
+ }
104
+ : { fixedSize }),
105
+ write: (value: TFrom, bytes, offset) => {
106
+ assertValidNumberOfItemsForCodec(config?.description ?? 'tuple', items.length, value.length);
107
+ items.forEach((item, index) => {
108
+ offset = item.write(value[index], bytes, offset);
109
+ });
110
+ return offset;
111
+ },
112
+ });
113
+ }
114
+
115
+ /**
116
+ * Returns a decoder for tuples.
117
+ *
118
+ * This decoder deserializes a fixed-size array (tuple) by decoding its items
119
+ * sequentially using the provided item decoders.
120
+ *
121
+ * For more details, see {@link getTupleCodec}.
122
+ *
123
+ * @typeParam TItems - An array of decoders, each corresponding to a tuple element.
124
+ *
125
+ * @param items - The decoders for each item in the tuple.
126
+ * @returns A `FixedSizeDecoder` or `VariableSizeDecoder` for decoding tuples.
127
+ *
128
+ * @example
129
+ * Decoding a tuple with 2 items.
130
+ * ```ts
131
+ * const decoder = getTupleDecoder([fixCodecSize(getUtf8Decoder(), 5), getU8Decoder()]);
132
+ *
133
+ * const tuple = decoder.decode(new Uint8Array([
134
+ * 0x41,0x6c,0x69,0x63,0x65,0x2a
135
+ * ]));
136
+ * // ['Alice', 42]
137
+ * ```
138
+ *
139
+ * @see {@link getTupleCodec}
140
+ */
141
+ export function getTupleDecoder<const TItems extends readonly FixedSizeDecoder<any>[]>(
142
+ items: TItems,
143
+ ): FixedSizeDecoder<GetDecoderTypeFromItems<TItems>>;
144
+ export function getTupleDecoder<const TItems extends readonly Decoder<any>[]>(
145
+ items: TItems,
146
+ ): VariableSizeDecoder<GetDecoderTypeFromItems<TItems>>;
147
+ export function getTupleDecoder<const TItems extends readonly Decoder<any>[]>(
148
+ items: TItems,
149
+ ): Decoder<GetDecoderTypeFromItems<TItems>> {
150
+ type TTo = GetDecoderTypeFromItems<TItems>;
151
+ const fixedSize = sumCodecSizes(items.map(getFixedSize));
152
+ const maxSize = sumCodecSizes(items.map(getMaxSize)) ?? undefined;
153
+
154
+ return createDecoder({
155
+ ...(fixedSize === null ? { maxSize } : { fixedSize }),
156
+ read: (bytes: ReadonlyUint8Array | Uint8Array, offset) => {
157
+ const values = [] as Array<any> & TTo;
158
+ items.forEach(item => {
159
+ const [newValue, newOffset] = item.read(bytes, offset);
160
+ values.push(newValue);
161
+ offset = newOffset;
162
+ });
163
+ return [values, offset];
164
+ },
165
+ });
166
+ }
167
+
168
+ /**
169
+ * Returns a codec for encoding and decoding tuples.
170
+ *
171
+ * This codec serializes tuples by encoding and decoding each item sequentially.
172
+ *
173
+ * Unlike the {@link getArrayCodec} codec, each item in the tuple has its own codec
174
+ * and, therefore, can be of a different type.
175
+ *
176
+ * @typeParam TItems - An array of codecs, each corresponding to a tuple element.
177
+ *
178
+ * @param items - The codecs for each item in the tuple.
179
+ * @returns A `FixedSizeCodec` or `VariableSizeCodec` for encoding and decoding tuples.
180
+ *
181
+ * @example
182
+ * Encoding and decoding a tuple with 2 items.
183
+ * ```ts
184
+ * const codec = getTupleCodec([fixCodecSize(getUtf8Codec(), 5), getU8Codec()]);
185
+ *
186
+ * const bytes = codec.encode(['Alice', 42]);
187
+ * // 0x416c6963652a
188
+ * // | └── Second item (42)
189
+ * // └── First item ("Alice")
190
+ *
191
+ * const tuple = codec.decode(bytes);
192
+ * // ['Alice', 42]
193
+ * ```
194
+ *
195
+ * @remarks
196
+ * Separate {@link getTupleEncoder} and {@link getTupleDecoder} functions are available.
197
+ *
198
+ * ```ts
199
+ * const bytes = getTupleEncoder([fixCodecSize(getUtf8Encoder(), 5), getU8Encoder()])
200
+ * .encode(['Alice', 42]);
201
+ *
202
+ * const tuple = getTupleDecoder([fixCodecSize(getUtf8Decoder(), 5), getU8Decoder()])
203
+ * .decode(bytes);
204
+ * ```
205
+ *
206
+ * @see {@link getTupleEncoder}
207
+ * @see {@link getTupleDecoder}
208
+ */
209
+ export function getTupleCodec<const TItems extends readonly FixedSizeCodec<any>[]>(
210
+ items: TItems,
211
+ config?: TupleCodecConfig,
212
+ ): FixedSizeCodec<GetEncoderTypeFromItems<TItems>, GetDecoderTypeFromItems<TItems> & GetEncoderTypeFromItems<TItems>>;
213
+ export function getTupleCodec<const TItems extends readonly Codec<any>[]>(
214
+ items: TItems,
215
+ config?: TupleCodecConfig,
216
+ ): VariableSizeCodec<
217
+ GetEncoderTypeFromItems<TItems>,
218
+ GetDecoderTypeFromItems<TItems> & GetEncoderTypeFromItems<TItems>
219
+ >;
220
+ export function getTupleCodec<const TItems extends readonly Codec<any>[]>(
221
+ items: TItems,
222
+ config?: TupleCodecConfig,
223
+ ): Codec<GetEncoderTypeFromItems<TItems>, GetDecoderTypeFromItems<TItems> & GetEncoderTypeFromItems<TItems>> {
224
+ return combineCodec(
225
+ getTupleEncoder(items, config),
226
+ getTupleDecoder(items) as Decoder<GetDecoderTypeFromItems<TItems> & GetEncoderTypeFromItems<TItems>>,
227
+ );
228
+ }
package/src/union.ts ADDED
@@ -0,0 +1,257 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import {
3
+ Codec,
4
+ combineCodec,
5
+ createDecoder,
6
+ createEncoder,
7
+ Decoder,
8
+ Encoder,
9
+ FixedSizeCodec,
10
+ FixedSizeDecoder,
11
+ FixedSizeEncoder,
12
+ getEncodedSize,
13
+ isFixedSize,
14
+ Offset,
15
+ ReadonlyUint8Array,
16
+ } from '@solana/codecs-core';
17
+ import { SOLANA_ERROR__CODECS__UNION_VARIANT_OUT_OF_RANGE, SolanaError } from '@solana/errors';
18
+
19
+ import { DrainOuterGeneric, getMaxSize, maxCodecSizes } from './utils';
20
+
21
+ /**
22
+ * Infers the TypeScript type for values that can be encoded using a union codec.
23
+ *
24
+ * This type maps the provided variant encoders to their corresponding value types.
25
+ *
26
+ * @typeParam TVariants - An array of encoders, each corresponding to a union variant.
27
+ */
28
+ type GetEncoderTypeFromVariants<TVariants extends readonly Encoder<any>[]> = DrainOuterGeneric<{
29
+ [I in keyof TVariants]: TVariants[I] extends Encoder<infer TFrom> ? TFrom : never;
30
+ }>[number];
31
+
32
+ /**
33
+ * Infers the TypeScript type for values that can be decoded using a union codec.
34
+ *
35
+ * This type maps the provided variant decoders to their corresponding value types.
36
+ *
37
+ * @typeParam TVariants - An array of decoders, each corresponding to a union variant.
38
+ */
39
+ type GetDecoderTypeFromVariants<TVariants extends readonly Decoder<any>[]> = DrainOuterGeneric<{
40
+ [I in keyof TVariants]: TVariants[I] extends Decoder<infer TFrom> ? TFrom : never;
41
+ }>[number];
42
+
43
+ type UnionEncoder<TVariants extends readonly Encoder<unknown>[]> = TVariants extends readonly FixedSizeEncoder<any>[]
44
+ ? FixedSizeEncoder<GetEncoderTypeFromVariants<TVariants>>
45
+ : Encoder<GetEncoderTypeFromVariants<TVariants>>;
46
+
47
+ type UnionDecoder<TVariants extends readonly Decoder<unknown>[]> = TVariants extends readonly FixedSizeDecoder<any>[]
48
+ ? FixedSizeDecoder<GetDecoderTypeFromVariants<TVariants>>
49
+ : Decoder<GetDecoderTypeFromVariants<TVariants>>;
50
+
51
+ type UnionCodec<TVariants extends readonly Codec<unknown>[]> = TVariants extends readonly FixedSizeCodec<any>[]
52
+ ? FixedSizeCodec<
53
+ GetEncoderTypeFromVariants<TVariants>,
54
+ GetDecoderTypeFromVariants<TVariants> & GetEncoderTypeFromVariants<TVariants>
55
+ >
56
+ : Codec<
57
+ GetEncoderTypeFromVariants<TVariants>,
58
+ GetDecoderTypeFromVariants<TVariants> & GetEncoderTypeFromVariants<TVariants>
59
+ >;
60
+
61
+ /**
62
+ * Returns an encoder for union types.
63
+ *
64
+ * This encoder serializes values by selecting the correct variant encoder
65
+ * based on the `getIndexFromValue` function.
66
+ *
67
+ * Unlike other codecs, this encoder does not store the variant index.
68
+ * It is the user's responsibility to manage discriminators separately.
69
+ *
70
+ * For more details, see {@link getUnionCodec}.
71
+ *
72
+ * @typeParam TVariants - An array of encoders, each corresponding to a union variant.
73
+ *
74
+ * @param variants - The encoders for each variant of the union.
75
+ * @param getIndexFromValue - A function that determines the variant index from the provided value.
76
+ * @returns An `Encoder` for encoding union values.
77
+ *
78
+ * @example
79
+ * Encoding a union of numbers and booleans.
80
+ * ```ts
81
+ * const encoder = getUnionEncoder(
82
+ * [getU16Encoder(), getBooleanEncoder()],
83
+ * value => (typeof value === 'number' ? 0 : 1)
84
+ * );
85
+ *
86
+ * encoder.encode(42);
87
+ * // 0x2a00
88
+ * // └── Encoded number (42) as `u16`
89
+ *
90
+ * encoder.encode(true);
91
+ * // 0x01
92
+ * // └── Encoded boolean (`true`) as `u8`
93
+ * ```
94
+ *
95
+ * @see {@link getUnionCodec}
96
+ */
97
+ export function getUnionEncoder<const TVariants extends readonly Encoder<any>[]>(
98
+ variants: TVariants,
99
+ getIndexFromValue: (value: GetEncoderTypeFromVariants<TVariants>) => number,
100
+ ): UnionEncoder<TVariants> {
101
+ type TFrom = GetEncoderTypeFromVariants<TVariants>;
102
+ const fixedSize = getUnionFixedSize(variants);
103
+ const write: Encoder<TFrom>['write'] = (variant, bytes, offset) => {
104
+ const index = getIndexFromValue(variant);
105
+ assertValidVariantIndex(variants, index);
106
+ return variants[index].write(variant, bytes, offset);
107
+ };
108
+
109
+ if (fixedSize !== null) {
110
+ return createEncoder({ fixedSize, write }) as UnionEncoder<TVariants>;
111
+ }
112
+
113
+ const maxSize = getUnionMaxSize(variants);
114
+ return createEncoder({
115
+ ...(maxSize !== null ? { maxSize } : {}),
116
+ getSizeFromValue: variant => {
117
+ const index = getIndexFromValue(variant);
118
+ assertValidVariantIndex(variants, index);
119
+ return getEncodedSize(variant, variants[index]);
120
+ },
121
+ write,
122
+ }) as UnionEncoder<TVariants>;
123
+ }
124
+
125
+ /**
126
+ * Returns a decoder for union types.
127
+ *
128
+ * This decoder deserializes values by selecting the correct variant decoder
129
+ * based on the `getIndexFromBytes` function.
130
+ *
131
+ * Unlike other codecs, this decoder does not assume a stored discriminator.
132
+ * It is the user's responsibility to manage discriminators separately.
133
+ *
134
+ * For more details, see {@link getUnionCodec}.
135
+ *
136
+ * @typeParam TVariants - An array of decoders, each corresponding to a union variant.
137
+ *
138
+ * @param variants - The decoders for each variant of the union.
139
+ * @param getIndexFromBytes - A function that determines the variant index from the byte array.
140
+ * @returns A `Decoder` for decoding union values.
141
+ *
142
+ * @example
143
+ * Decoding a union of numbers and booleans.
144
+ * ```ts
145
+ * const decoder = getUnionDecoder(
146
+ * [getU16Decoder(), getBooleanDecoder()],
147
+ * (bytes, offset) => (bytes.length - offset > 1 ? 0 : 1)
148
+ * );
149
+ *
150
+ * decoder.decode(new Uint8Array([0x2a, 0x00])); // 42
151
+ * decoder.decode(new Uint8Array([0x01])); // true
152
+ * // Type is inferred as `number | boolean`
153
+ * ```
154
+ *
155
+ * @see {@link getUnionCodec}
156
+ */
157
+ export function getUnionDecoder<const TVariants extends readonly Decoder<any>[]>(
158
+ variants: TVariants,
159
+ getIndexFromBytes: (bytes: ReadonlyUint8Array, offset: Offset) => number,
160
+ ): UnionDecoder<TVariants> {
161
+ type TTo = GetDecoderTypeFromVariants<TVariants>;
162
+ const fixedSize = getUnionFixedSize(variants);
163
+ const read: Decoder<TTo>['read'] = (bytes, offset) => {
164
+ const index = getIndexFromBytes(bytes, offset);
165
+ assertValidVariantIndex(variants, index);
166
+ return variants[index].read(bytes, offset);
167
+ };
168
+
169
+ if (fixedSize !== null) {
170
+ return createDecoder({ fixedSize, read }) as UnionDecoder<TVariants>;
171
+ }
172
+
173
+ const maxSize = getUnionMaxSize(variants);
174
+ return createDecoder({ ...(maxSize !== null ? { maxSize } : {}), read }) as UnionDecoder<TVariants>;
175
+ }
176
+
177
+ /**
178
+ * Returns a codec for encoding and decoding union types.
179
+ *
180
+ * This codec serializes and deserializes union values by selecting the correct variant
181
+ * based on the provided index functions.
182
+ *
183
+ * Unlike the {@link getDiscriminatedUnionCodec}, this codec does not assume a stored
184
+ * discriminator and must be used with an explicit mechanism for managing discriminators.
185
+ *
186
+ * @typeParam TVariants - An array of codecs, each corresponding to a union variant.
187
+ *
188
+ * @param variants - The codecs for each variant of the union.
189
+ * @param getIndexFromValue - A function that determines the variant index from the provided value.
190
+ * @param getIndexFromBytes - A function that determines the variant index from the byte array.
191
+ * @returns A `Codec` for encoding and decoding union values.
192
+ *
193
+ * @example
194
+ * Encoding and decoding a union of numbers and booleans.
195
+ * ```ts
196
+ * const codec = getUnionCodec(
197
+ * [getU16Codec(), getBooleanCodec()],
198
+ * value => (typeof value === 'number' ? 0 : 1),
199
+ * (bytes, offset) => (bytes.length - offset > 1 ? 0 : 1)
200
+ * );
201
+ *
202
+ * const bytes1 = codec.encode(42); // 0x2a00
203
+ * const value1: number | boolean = codec.decode(bytes1); // 42
204
+ *
205
+ * const bytes2 = codec.encode(true); // 0x01
206
+ * const value2: number | boolean = codec.decode(bytes2); // true
207
+ * ```
208
+ *
209
+ * @remarks
210
+ * If you need a codec that includes a stored discriminator,
211
+ * consider using {@link getDiscriminatedUnionCodec}.
212
+ *
213
+ * Separate {@link getUnionEncoder} and {@link getUnionDecoder} functions are also available.
214
+ *
215
+ * ```ts
216
+ * const bytes = getUnionEncoder(variantEncoders, getIndexFromValue).encode(42);
217
+ * const value = getUnionDecoder(variantDecoders, getIndexFromBytes).decode(bytes);
218
+ * ```
219
+ *
220
+ * @see {@link getUnionEncoder}
221
+ * @see {@link getUnionDecoder}
222
+ * @see {@link getDiscriminatedUnionCodec}
223
+ */
224
+ export function getUnionCodec<const TVariants extends readonly Codec<any>[]>(
225
+ variants: TVariants,
226
+ getIndexFromValue: (value: GetEncoderTypeFromVariants<TVariants>) => number,
227
+ getIndexFromBytes: (bytes: ReadonlyUint8Array, offset: Offset) => number,
228
+ ): UnionCodec<TVariants> {
229
+ return combineCodec(
230
+ getUnionEncoder(variants, getIndexFromValue),
231
+ getUnionDecoder(variants as readonly Decoder<any>[], getIndexFromBytes) as Decoder<
232
+ GetDecoderTypeFromVariants<TVariants> & GetEncoderTypeFromVariants<TVariants>
233
+ >,
234
+ ) as UnionCodec<TVariants>;
235
+ }
236
+
237
+ function assertValidVariantIndex(variants: readonly unknown[], index: number) {
238
+ if (typeof variants[index] === 'undefined') {
239
+ throw new SolanaError(SOLANA_ERROR__CODECS__UNION_VARIANT_OUT_OF_RANGE, {
240
+ maxRange: variants.length - 1,
241
+ minRange: 0,
242
+ variant: index,
243
+ });
244
+ }
245
+ }
246
+
247
+ function getUnionFixedSize<const TVariants extends readonly (Decoder<any> | Encoder<any>)[]>(variants: TVariants) {
248
+ if (variants.length === 0) return 0;
249
+ if (!isFixedSize(variants[0])) return null;
250
+ const variantSize = variants[0].fixedSize;
251
+ const sameSizedVariants = variants.every(variant => isFixedSize(variant) && variant.fixedSize === variantSize);
252
+ return sameSizedVariants ? variantSize : null;
253
+ }
254
+
255
+ function getUnionMaxSize<const TVariants extends readonly (Decoder<any> | Encoder<any>)[]>(variants: TVariants) {
256
+ return maxCodecSizes(variants.map(variant => getMaxSize(variant)));
257
+ }
package/src/unit.ts ADDED
@@ -0,0 +1,111 @@
1
+ import {
2
+ combineCodec,
3
+ createDecoder,
4
+ createEncoder,
5
+ FixedSizeCodec,
6
+ FixedSizeDecoder,
7
+ FixedSizeEncoder,
8
+ ReadonlyUint8Array,
9
+ } from '@solana/codecs-core';
10
+
11
+ /**
12
+ * Returns an encoder for `void` values.
13
+ *
14
+ * This encoder writes nothing to the byte array and has a fixed size of 0 bytes.
15
+ * It is useful when working with structures that require a no-op encoder,
16
+ * such as empty variants in {@link getDiscriminatedUnionEncoder}.
17
+ *
18
+ * For more details, see {@link getUnitCodec}.
19
+ *
20
+ * @returns A `FixedSizeEncoder<void, 0>`, representing an empty encoder.
21
+ *
22
+ * @example
23
+ * Encoding a `void` value.
24
+ * ```ts
25
+ * getUnitEncoder().encode(undefined); // Produces an empty byte array.
26
+ * ```
27
+ *
28
+ * @see {@link getUnitCodec}
29
+ */
30
+ export function getUnitEncoder(): FixedSizeEncoder<void, 0> {
31
+ return createEncoder({
32
+ fixedSize: 0,
33
+ write: (_value, _bytes, offset) => offset,
34
+ });
35
+ }
36
+
37
+ /**
38
+ * Returns a decoder for `void` values.
39
+ *
40
+ * This decoder always returns `undefined` and has a fixed size of 0 bytes.
41
+ * It is useful when working with structures that require a no-op decoder,
42
+ * such as empty variants in {@link getDiscriminatedUnionDecoder}.
43
+ *
44
+ * For more details, see {@link getUnitCodec}.
45
+ *
46
+ * @returns A `FixedSizeDecoder<void, 0>`, representing an empty decoder.
47
+ *
48
+ * @example
49
+ * Decoding a `void` value.
50
+ * ```ts
51
+ * getUnitDecoder().decode(anyBytes); // Returns `undefined`.
52
+ * ```
53
+ *
54
+ * @see {@link getUnitCodec}
55
+ */
56
+ export function getUnitDecoder(): FixedSizeDecoder<void, 0> {
57
+ return createDecoder({
58
+ fixedSize: 0,
59
+ read: (_bytes: ReadonlyUint8Array | Uint8Array, offset) => [undefined, offset],
60
+ });
61
+ }
62
+
63
+ /**
64
+ * Returns a codec for `void` values.
65
+ *
66
+ * This codec does nothing when encoding or decoding and has a fixed size of 0 bytes.
67
+ * Namely, it always returns `undefined` when decoding and produces an empty byte array when encoding.
68
+ *
69
+ * This can be useful when working with structures that require a no-op codec,
70
+ * such as empty variants in {@link getDiscriminatedUnionCodec}.
71
+ *
72
+ * @returns A `FixedSizeCodec<void, void, 0>`, representing an empty codec.
73
+ *
74
+ * @example
75
+ * Encoding and decoding a `void` value.
76
+ * ```ts
77
+ * const codec = getUnitCodec();
78
+ *
79
+ * codec.encode(undefined); // Produces an empty byte array.
80
+ * codec.decode(new Uint8Array([])); // Returns `undefined`.
81
+ * ```
82
+ *
83
+ * @example
84
+ * Using unit codecs as empty variants in a discriminated union.
85
+ * ```ts
86
+ * type Message =
87
+ * | { __kind: 'Enter' }
88
+ * | { __kind: 'Leave' }
89
+ * | { __kind: 'Move'; x: number; y: number };
90
+ *
91
+ * const messageCodec = getDiscriminatedUnionCodec([
92
+ * ['Enter', getUnitCodec()], // <- No-op codec for empty data
93
+ * ['Leave', getUnitCodec()], // <- No-op codec for empty data
94
+ * ['Move', getStructCodec([...])]
95
+ * ]);
96
+ * ```
97
+ *
98
+ * @remarks
99
+ * Separate {@link getUnitEncoder} and {@link getUnitDecoder} functions are available.
100
+ *
101
+ * ```ts
102
+ * const bytes = getUnitEncoder().encode();
103
+ * const value = getUnitDecoder().decode(bytes);
104
+ * ```
105
+ *
106
+ * @see {@link getUnitEncoder}
107
+ * @see {@link getUnitDecoder}
108
+ */
109
+ export function getUnitCodec(): FixedSizeCodec<void, void, 0> {
110
+ return combineCodec(getUnitEncoder(), getUnitDecoder());
111
+ }
package/src/utils.ts ADDED
@@ -0,0 +1,32 @@
1
+ import { isFixedSize } from '@solana/codecs-core';
2
+
3
+ /**
4
+ * Functionally, this type helper is equivalent to the identity type — i.e. `type Identity<T> = T`.
5
+ * However, wrapping generic object mappings in this type significantly reduces the number
6
+ * of instantiation expressions processed, which increases TypeScript performance and
7
+ * prevents "Type instantiation is excessively deep and possibly infinite" errors.
8
+ *
9
+ * This works because TypeScript doesn't create a new level of nesting when encountering conditional generic types.
10
+ * @see https://github.com/microsoft/TypeScript/issues/34933
11
+ * @see https://github.com/kysely-org/kysely/pull/483
12
+ */
13
+ export type DrainOuterGeneric<T> = [T] extends [unknown] ? T : never;
14
+
15
+ export function maxCodecSizes(sizes: (number | null)[]): number | null {
16
+ return sizes.reduce(
17
+ (all, size) => (all === null || size === null ? null : Math.max(all, size)),
18
+ 0 as number | null,
19
+ );
20
+ }
21
+
22
+ export function sumCodecSizes(sizes: (number | null)[]): number | null {
23
+ return sizes.reduce((all, size) => (all === null || size === null ? null : all + size), 0 as number | null);
24
+ }
25
+
26
+ export function getFixedSize(codec: { fixedSize: number } | { maxSize?: number }): number | null {
27
+ return isFixedSize(codec) ? codec.fixedSize : null;
28
+ }
29
+
30
+ export function getMaxSize(codec: { fixedSize: number } | { maxSize?: number }): number | null {
31
+ return isFixedSize(codec) ? codec.fixedSize : (codec.maxSize ?? null);
32
+ }