@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.
@@ -0,0 +1,356 @@
1
+ import {
2
+ assertIsFixedSize,
3
+ Codec,
4
+ combineCodec,
5
+ containsBytes,
6
+ Decoder,
7
+ Encoder,
8
+ fixDecoderSize,
9
+ FixedSizeCodec,
10
+ FixedSizeDecoder,
11
+ FixedSizeEncoder,
12
+ fixEncoderSize,
13
+ ReadonlyUint8Array,
14
+ transformDecoder,
15
+ transformEncoder,
16
+ VariableSizeCodec,
17
+ VariableSizeDecoder,
18
+ VariableSizeEncoder,
19
+ } from '@solana/codecs-core';
20
+ import {
21
+ FixedSizeNumberCodec,
22
+ FixedSizeNumberDecoder,
23
+ FixedSizeNumberEncoder,
24
+ getU8Decoder,
25
+ getU8Encoder,
26
+ NumberCodec,
27
+ NumberDecoder,
28
+ NumberEncoder,
29
+ } from '@solana/codecs-numbers';
30
+
31
+ import { getBooleanDecoder, getBooleanEncoder } from './boolean';
32
+ import { getConstantDecoder, getConstantEncoder } from './constant';
33
+ import { getTupleDecoder, getTupleEncoder } from './tuple';
34
+ import { getUnionDecoder, getUnionEncoder } from './union';
35
+ import { getUnitDecoder, getUnitEncoder } from './unit';
36
+
37
+ /**
38
+ * Defines the configuration options for nullable codecs.
39
+ *
40
+ * This configuration controls how nullable values are encoded and decoded.
41
+ *
42
+ * By default, nullable values are prefixed with a `u8` (0 = `null`, 1 = present).
43
+ * The `noneValue` and `prefix` options allow customizing this behavior.
44
+ *
45
+ * @typeParam TPrefix - A number codec, encoder, or decoder used as the presence prefix.
46
+ *
47
+ * @see {@link getNullableEncoder}
48
+ * @see {@link getNullableDecoder}
49
+ * @see {@link getNullableCodec}
50
+ */
51
+ export type NullableCodecConfig<TPrefix extends NumberCodec | NumberDecoder | NumberEncoder> = {
52
+ /**
53
+ * Specifies how `null` values are represented in the encoded data.
54
+ *
55
+ * - By default, `null` values are omitted from encoding.
56
+ * - `'zeroes'`: The bytes allocated for the value are filled with zeroes. This requires a fixed-size codec.
57
+ * - Custom byte array: `null` values are replaced with a predefined byte sequence. This results in a variable-size codec.
58
+ *
59
+ * @defaultValue No explicit `noneValue` is used; `null` values are omitted.
60
+ */
61
+ noneValue?: ReadonlyUint8Array | 'zeroes';
62
+
63
+ /**
64
+ * The presence prefix used to distinguish between `null` and present values.
65
+ *
66
+ * - By default, a `u8` prefix is used (`0 = null`, `1 = present`).
67
+ * - Custom number codec: Allows defining a different number size for the prefix.
68
+ * - `null`: No prefix is used; `noneValue` (if provided) determines `null`.
69
+ * If no `noneValue` is set, `null` is identified by the absence of bytes.
70
+ *
71
+ * @defaultValue `u8` prefix.
72
+ */
73
+ prefix?: TPrefix | null;
74
+ };
75
+
76
+ /**
77
+ * Returns an encoder for optional values, allowing `null` values to be encoded.
78
+ *
79
+ * This encoder serializes an optional value using a configurable approach:
80
+ * - By default, a `u8` prefix is used (0 = `null`, 1 = present). This can be customized or disabled.
81
+ * - If `noneValue: 'zeroes'` is set, `null` values are encoded as zeroes.
82
+ * - If `noneValue` is a byte array, `null` values are replaced with the provided constant.
83
+ *
84
+ * For more details, see {@link getNullableCodec}.
85
+ *
86
+ * @typeParam TFrom - The type of the main value being encoded.
87
+ *
88
+ * @param item - The encoder for the value that may be present.
89
+ * @param config - Configuration options for encoding optional values.
90
+ * @returns A `FixedSizeEncoder` or `VariableSizeEncoder` for encoding nullable values.
91
+ *
92
+ * @example
93
+ * Encoding an optional number.
94
+ * ```ts
95
+ * const encoder = getNullableEncoder(getU32Encoder());
96
+ *
97
+ * encoder.encode(null); // 0x00
98
+ * encoder.encode(42); // 0x012a000000
99
+ * ```
100
+ *
101
+ * @see {@link getNullableCodec}
102
+ */
103
+ export function getNullableEncoder<TFrom, TSize extends number>(
104
+ item: FixedSizeEncoder<TFrom, TSize>,
105
+ config: NullableCodecConfig<NumberEncoder> & { noneValue: 'zeroes'; prefix: null },
106
+ ): FixedSizeEncoder<TFrom | null, TSize>;
107
+ export function getNullableEncoder<TFrom>(
108
+ item: FixedSizeEncoder<TFrom>,
109
+ config: NullableCodecConfig<FixedSizeNumberEncoder> & { noneValue: 'zeroes' },
110
+ ): FixedSizeEncoder<TFrom | null>;
111
+ export function getNullableEncoder<TFrom>(
112
+ item: FixedSizeEncoder<TFrom>,
113
+ config: NullableCodecConfig<NumberEncoder> & { noneValue: 'zeroes' },
114
+ ): VariableSizeEncoder<TFrom | null>;
115
+ export function getNullableEncoder<TFrom>(
116
+ item: Encoder<TFrom>,
117
+ config?: NullableCodecConfig<NumberEncoder> & { noneValue?: ReadonlyUint8Array },
118
+ ): VariableSizeEncoder<TFrom | null>;
119
+ export function getNullableEncoder<TFrom>(
120
+ item: Encoder<TFrom>,
121
+ config: NullableCodecConfig<NumberEncoder> = {},
122
+ ): Encoder<TFrom | null> {
123
+ const prefix = (() => {
124
+ if (config.prefix === null) {
125
+ return transformEncoder(getUnitEncoder(), (_boolean: boolean) => undefined);
126
+ }
127
+ return getBooleanEncoder({ size: config.prefix ?? getU8Encoder() });
128
+ })();
129
+ const noneValue = (() => {
130
+ if (config.noneValue === 'zeroes') {
131
+ assertIsFixedSize(item);
132
+ return fixEncoderSize(getUnitEncoder(), item.fixedSize);
133
+ }
134
+ if (!config.noneValue) {
135
+ return getUnitEncoder();
136
+ }
137
+ return getConstantEncoder(config.noneValue);
138
+ })();
139
+
140
+ return getUnionEncoder(
141
+ [
142
+ transformEncoder(getTupleEncoder([prefix, noneValue]), (_value: null): [boolean, void] => [
143
+ false,
144
+ undefined,
145
+ ]),
146
+ transformEncoder(getTupleEncoder([prefix, item]), (value: TFrom): [boolean, TFrom] => [true, value]),
147
+ ],
148
+ variant => Number(variant !== null),
149
+ );
150
+ }
151
+
152
+ /**
153
+ * Returns a decoder for optional values, allowing `null` values to be recognized.
154
+ *
155
+ * This decoder deserializes an optional value using a configurable approach:
156
+ * - By default, a `u8` prefix is used (0 = `null`, 1 = present). This can be customized or disabled.
157
+ * - If `noneValue: 'zeroes'` is set, `null` values are identified by zeroes.
158
+ * - If `noneValue` is a byte array, `null` values match the provided constant.
159
+ *
160
+ * For more details, see {@link getNullableCodec}.
161
+ *
162
+ * @typeParam TTo - The type of the main value being decoded.
163
+ *
164
+ * @param item - The decoder for the value that may be present.
165
+ * @param config - Configuration options for decoding optional values.
166
+ * @returns A `FixedSizeDecoder` or `VariableSizeDecoder` for decoding nullable values.
167
+ *
168
+ * @example
169
+ * Decoding an optional number.
170
+ * ```ts
171
+ * const decoder = getNullableDecoder(getU32Decoder());
172
+ *
173
+ * decoder.decode(new Uint8Array([0x00])); // null
174
+ * decoder.decode(new Uint8Array([0x01, 0x2a, 0x00, 0x00, 0x00])); // 42
175
+ * ```
176
+ *
177
+ * @see {@link getNullableCodec}
178
+ */
179
+ export function getNullableDecoder<TTo, TSize extends number>(
180
+ item: FixedSizeDecoder<TTo, TSize>,
181
+ config: NullableCodecConfig<NumberDecoder> & { noneValue: 'zeroes'; prefix: null },
182
+ ): FixedSizeDecoder<TTo | null, TSize>;
183
+ export function getNullableDecoder<TTo>(
184
+ item: FixedSizeDecoder<TTo>,
185
+ config: NullableCodecConfig<FixedSizeNumberDecoder> & { noneValue: 'zeroes' },
186
+ ): FixedSizeDecoder<TTo | null>;
187
+ export function getNullableDecoder<TTo>(
188
+ item: FixedSizeDecoder<TTo>,
189
+ config: NullableCodecConfig<NumberDecoder> & { noneValue: 'zeroes' },
190
+ ): VariableSizeDecoder<TTo | null>;
191
+ export function getNullableDecoder<TTo>(
192
+ item: Decoder<TTo>,
193
+ config?: NullableCodecConfig<NumberDecoder> & { noneValue?: ReadonlyUint8Array },
194
+ ): VariableSizeDecoder<TTo | null>;
195
+ export function getNullableDecoder<TTo>(
196
+ item: Decoder<TTo>,
197
+ config: NullableCodecConfig<NumberDecoder> = {},
198
+ ): Decoder<TTo | null> {
199
+ const prefix = (() => {
200
+ if (config.prefix === null) {
201
+ return transformDecoder(getUnitDecoder(), () => false);
202
+ }
203
+ return getBooleanDecoder({ size: config.prefix ?? getU8Decoder() });
204
+ })();
205
+ const noneValue = (() => {
206
+ if (config.noneValue === 'zeroes') {
207
+ assertIsFixedSize(item);
208
+ return fixDecoderSize(getUnitDecoder(), item.fixedSize);
209
+ }
210
+ if (!config.noneValue) {
211
+ return getUnitDecoder();
212
+ }
213
+ return getConstantDecoder(config.noneValue);
214
+ })();
215
+
216
+ return getUnionDecoder(
217
+ [
218
+ transformDecoder(getTupleDecoder([prefix, noneValue]), () => null),
219
+ transformDecoder(getTupleDecoder([prefix, item]), ([, value]): TTo => value),
220
+ ],
221
+ (bytes, offset) => {
222
+ if (config.prefix === null && !config.noneValue) {
223
+ return Number(offset < bytes.length);
224
+ }
225
+ if (config.prefix === null && config.noneValue != null) {
226
+ const zeroValue =
227
+ config.noneValue === 'zeroes' ? new Uint8Array(noneValue.fixedSize).fill(0) : config.noneValue;
228
+ return containsBytes(bytes, zeroValue, offset) ? 0 : 1;
229
+ }
230
+ return Number(prefix.read(bytes, offset)[0]);
231
+ },
232
+ );
233
+ }
234
+
235
+ /**
236
+ * Returns a codec for encoding and decoding optional values, allowing `null` values to be handled.
237
+ *
238
+ * This codec serializes and deserializes optional values using a configurable approach:
239
+ * - By default, a `u8` prefix is used (0 = `null`, 1 = present).
240
+ * This can be customized using a custom number codec or even disabled by setting
241
+ * the `prefix` to `null`.
242
+ * - If `noneValue: 'zeroes'` is set, `null` values are encoded/decoded as zeroes.
243
+ * - If `noneValue` is a byte array, `null` values are represented by the provided constant.
244
+ *
245
+ * For more details on the configuration options, see {@link NullableCodecConfig}.
246
+ *
247
+ * @typeParam TFrom - The type of the main value being encoded.
248
+ * @typeParam TTo - The type of the main value being decoded.
249
+ *
250
+ * @param item - The codec for the value that may be present.
251
+ * @param config - Configuration options for encoding and decoding optional values.
252
+ * @returns A `FixedSizeCodec` or `VariableSizeCodec` for encoding and decoding nullable values.
253
+ *
254
+ * @example
255
+ * Encoding and decoding an optional number using a `u8` prefix (default).
256
+ * ```ts
257
+ * const codec = getNullableCodec(getU32Codec());
258
+ *
259
+ * codec.encode(null); // 0x00
260
+ * codec.encode(42); // 0x012a000000
261
+ *
262
+ * codec.decode(new Uint8Array([0x00])); // null
263
+ * codec.decode(new Uint8Array([0x01, 0x2a, 0x00, 0x00, 0x00])); // 42
264
+ * ```
265
+ *
266
+ * @example
267
+ * Encoding and decoding an optional number using a fixed-size codec, by filling `null` values with zeroes.
268
+ * ```ts
269
+ * const codec = getNullableCodec(getU32Codec(), { noneValue: 'zeroes' });
270
+ *
271
+ * codec.encode(null); // 0x0000000000
272
+ * codec.encode(42); // 0x012a000000
273
+ *
274
+ * codec.decode(new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x00])); // null
275
+ * codec.decode(new Uint8Array([0x01, 0x2a, 0x00, 0x00, 0x00])); // 42
276
+ * ```
277
+ *
278
+ * @example
279
+ * Encoding and decoding `null` values with zeroes and no prefix.
280
+ * ```ts
281
+ * const codec = getNullableCodec(getU32Codec(), {
282
+ * noneValue: 'zeroes',
283
+ * prefix: null,
284
+ * });
285
+ *
286
+ * codec.encode(null); // 0x00000000
287
+ * codec.encode(42); // 0x2a000000
288
+ *
289
+ * codec.decode(new Uint8Array([0x00, 0x00, 0x00, 0x00])); // null
290
+ * codec.decode(new Uint8Array([0x2a, 0x00, 0x00, 0x00])); // 42
291
+ * ```
292
+ *
293
+ * @example
294
+ * Encoding and decoding `null` values with a custom byte sequence and no prefix.
295
+ * ```ts
296
+ * const codec = getNullableCodec(getU16Codec(), {
297
+ * noneValue: new Uint8Array([0xff, 0xff]),
298
+ * prefix: null,
299
+ * });
300
+ *
301
+ * codec.encode(null); // 0xffff
302
+ * codec.encode(42); // 0x2a00
303
+ *
304
+ * codec.decode(new Uint8Array([0xff, 0xff])); // null
305
+ * codec.decode(new Uint8Array([0x2a, 0x00])); // 42
306
+ * ```
307
+ *
308
+ * @example
309
+ * Identifying `null` values by the absence of bytes.
310
+ * ```ts
311
+ * const codec = getNullableCodec(getU16Codec(), { prefix: null });
312
+ *
313
+ * codec.encode(null); // Empty bytes
314
+ * codec.encode(42); // 0x2a00
315
+ *
316
+ * codec.decode(new Uint8Array([])); // null
317
+ * codec.decode(new Uint8Array([0x2a, 0x00])); // 42
318
+ * ```
319
+ *
320
+ * @remarks
321
+ * Separate {@link getNullableEncoder} and {@link getNullableDecoder} functions are available.
322
+ *
323
+ * ```ts
324
+ * const bytes = getNullableEncoder(getU32Encoder()).encode(42);
325
+ * const value = getNullableDecoder(getU32Decoder()).decode(bytes);
326
+ * ```
327
+ *
328
+ * @see {@link getNullableEncoder}
329
+ * @see {@link getNullableDecoder}
330
+ */
331
+ export function getNullableCodec<TFrom, TTo extends TFrom, TSize extends number>(
332
+ item: FixedSizeCodec<TFrom, TTo, TSize>,
333
+ config: NullableCodecConfig<NumberCodec> & { noneValue: 'zeroes'; prefix: null },
334
+ ): FixedSizeCodec<TFrom | null, TTo | null, TSize>;
335
+ export function getNullableCodec<TFrom, TTo extends TFrom = TFrom>(
336
+ item: FixedSizeCodec<TFrom, TTo>,
337
+ config: NullableCodecConfig<FixedSizeNumberCodec> & { noneValue: 'zeroes' },
338
+ ): FixedSizeCodec<TFrom | null, TTo | null>;
339
+ export function getNullableCodec<TFrom, TTo extends TFrom = TFrom>(
340
+ item: FixedSizeCodec<TFrom, TTo>,
341
+ config: NullableCodecConfig<NumberCodec> & { noneValue: 'zeroes' },
342
+ ): VariableSizeCodec<TFrom | null, TTo | null>;
343
+ export function getNullableCodec<TFrom, TTo extends TFrom = TFrom>(
344
+ item: Codec<TFrom, TTo>,
345
+ config?: NullableCodecConfig<NumberCodec> & { noneValue?: ReadonlyUint8Array },
346
+ ): VariableSizeCodec<TFrom | null, TTo | null>;
347
+ export function getNullableCodec<TFrom, TTo extends TFrom = TFrom>(
348
+ item: Codec<TFrom, TTo>,
349
+ config: NullableCodecConfig<NumberCodec> = {},
350
+ ): Codec<TFrom | null, TTo | null> {
351
+ type ConfigCast = NullableCodecConfig<NumberCodec> & { noneValue?: ReadonlyUint8Array };
352
+ return combineCodec(
353
+ getNullableEncoder<TFrom>(item, config as ConfigCast),
354
+ getNullableDecoder<TTo>(item, config as ConfigCast),
355
+ );
356
+ }
@@ -0,0 +1,312 @@
1
+ import {
2
+ Codec,
3
+ combineCodec,
4
+ Decoder,
5
+ Encoder,
6
+ FixedSizeCodec,
7
+ FixedSizeDecoder,
8
+ FixedSizeEncoder,
9
+ ReadonlyUint8Array,
10
+ VariableSizeCodec,
11
+ VariableSizeDecoder,
12
+ VariableSizeEncoder,
13
+ } from '@solana/codecs-core';
14
+ import {
15
+ SOLANA_ERROR__CODECS__INVALID_PATTERN_MATCH_BYTES,
16
+ SOLANA_ERROR__CODECS__INVALID_PATTERN_MATCH_VALUE,
17
+ SolanaError,
18
+ } from '@solana/errors';
19
+
20
+ import { getUnionDecoder, getUnionEncoder } from './union';
21
+
22
+ type PatternMatchEncoderEntry<TNarrowed, TFrom = TNarrowed> = TNarrowed extends TFrom
23
+ ? // Boolean predicate with original encoder
24
+ | readonly [(value: TFrom) => boolean, Encoder<TFrom>]
25
+ // Type predicate with narrowed encoder
26
+ | readonly [(value: TFrom) => value is TNarrowed, Encoder<TNarrowed>]
27
+ : never;
28
+
29
+ type FixedSizePatternMatchEncoderEntry<
30
+ TNarrowed,
31
+ TFrom = TNarrowed,
32
+ TSize extends number = number,
33
+ > = TNarrowed extends TFrom
34
+ ? // Boolean predicate with original encoder
35
+ | readonly [(value: TFrom) => boolean, FixedSizeEncoder<TFrom, TSize>]
36
+ // Type predicate with narrowed encoder
37
+ | readonly [(value: TFrom) => value is TNarrowed, FixedSizeEncoder<TNarrowed, TSize>]
38
+ : never;
39
+
40
+ type VariableSizePatternMatchEncoderEntry<TNarrowed, TFrom = TNarrowed> = TNarrowed extends TFrom
41
+ ? // Boolean predicate with original encoder
42
+ | readonly [(value: TFrom) => boolean, VariableSizeEncoder<TFrom>]
43
+ // Type predicate with narrowed encoder
44
+ | readonly [(value: TFrom) => value is TNarrowed, VariableSizeEncoder<TNarrowed>]
45
+ : never;
46
+
47
+ /**
48
+ * Returns an encoder that selects which variant encoder to use based on pattern matching.
49
+ *
50
+ * This encoder evaluates the value against a series of predicate functions in order,
51
+ * and uses the first matching encoder to encode the value.
52
+ *
53
+ * @typeParam TFrom - The type of the value to encode.
54
+ *
55
+ * @param patterns - An array of `[predicate, encoder]` pairs. Predicates are tested in order
56
+ * and the first matching encoder is used to encode the value. Note that predicates can be either
57
+ * type predicates that narrow the type of the value, or boolean predicates. If using type predicates,
58
+ * the encoder can be for the narrowed type.
59
+ * @returns An encoder that selects the appropriate variant based on the matched pattern.
60
+ *
61
+ * @throws Throws a {@link SOLANA_ERROR__CODECS__INVALID_PATTERN_MATCH_VALUE} error
62
+ * if the value does not match any of the specified patterns.
63
+ *
64
+ * @example
65
+ * Encoding values using pattern matching.
66
+ * ```ts
67
+ * const encoder = getPatternMatchEncoder([
68
+ * [(n: number) => n < 256, getU8Encoder()],
69
+ * [(n: number) => n < 2 ** 16, getU16Encoder()],
70
+ * [(n: number) => n < 2 ** 32, getU32Encoder()]
71
+ * ]);
72
+ *
73
+ * encoder.encode(42);
74
+ * // 0x2a
75
+ * // └── Small number encoded as u8
76
+ *
77
+ * encoder.encode(1000);
78
+ * // 0xe803
79
+ * // └── Medium number encoded as u16
80
+ *
81
+ *
82
+ * encoder.encode(100_000);
83
+ * // 0xa0860100
84
+ * // └── Large number encoded as u32
85
+ *
86
+ * ender.encode(2 ** 32 + 1);
87
+ * // Throws an error because the value does not match any pattern
88
+ * ```
89
+ *
90
+ * @see {@link getPatternMatchCodec}
91
+ */
92
+ export function getPatternMatchEncoder<TFrom, TSize extends number>(
93
+ patterns: FixedSizePatternMatchEncoderEntry<TFrom, TFrom, TSize>[],
94
+ ): FixedSizeEncoder<TFrom, TSize>;
95
+ export function getPatternMatchEncoder<TFrom>(
96
+ patterns: FixedSizePatternMatchEncoderEntry<TFrom>[],
97
+ ): FixedSizeEncoder<TFrom>;
98
+ export function getPatternMatchEncoder<TFrom>(
99
+ patterns: VariableSizePatternMatchEncoderEntry<TFrom>[],
100
+ ): VariableSizeEncoder<TFrom>;
101
+ export function getPatternMatchEncoder<TFrom>(patterns: PatternMatchEncoderEntry<TFrom>[]): Encoder<TFrom>;
102
+ export function getPatternMatchEncoder<TFrom>(patterns: PatternMatchEncoderEntry<TFrom>[]): Encoder<TFrom> {
103
+ return getUnionEncoder(
104
+ patterns.map(([, encoder]) => encoder),
105
+ (value: TFrom) => {
106
+ const index = patterns.findIndex(([predicate]) => predicate(value));
107
+ if (index === -1) {
108
+ throw new SolanaError(SOLANA_ERROR__CODECS__INVALID_PATTERN_MATCH_VALUE);
109
+ }
110
+ return index;
111
+ },
112
+ );
113
+ }
114
+
115
+ /**
116
+ * Returns a decoder that selects which variant decoder to use based on pattern matching.
117
+ *
118
+ * This decoder evaluates the byte array against a series of predicate functions in order,
119
+ * and uses the first matching decoder to decode the value.
120
+ *
121
+ * @typeParam TTo - The type of the value to decode.
122
+ *
123
+ * @param patterns - An array of `[predicate, decoder]` pairs. Predicates are tested in order
124
+ * and the first matching decoder is used to decode the byte array.
125
+ * @returns A decoder that selects the appropriate variant based on the matched byte pattern.
126
+ *
127
+ * @throws Throws a {@link SOLANA_ERROR__CODECS__INVALID_PATTERN_MATCH_BYTES} error
128
+ * if the byte array does not match any of the specified patterns.
129
+ *
130
+ * @example
131
+ * Decoding values using pattern matching on bytes.
132
+ * ```ts
133
+ * const decoder = getPatternMatchDecoder([
134
+ * [(bytes) => bytes.length === 1, getU8Decoder()],
135
+ * [(bytes) => bytes.length === 2, getU16Decoder()],
136
+ * [(bytes) => bytes.length <= 4, getU32Decoder()]
137
+ * ]);
138
+ *
139
+ * decoder.decode(new Uint8Array([0x2a])); // 42 (decoded as u8)
140
+ * decoder.decode(new Uint8Array([0xe8, 0x03])) // 1000 (decoded as u16)
141
+ * decoder.decode(new Uint8Array([0xa0, 0x86, 0x01, 0x00])) // 100_000 (decoded as u32)
142
+ * decoder.decode(new Uint8Array([0xa0, 0x86, 0x01, 0x00, 0x00]))
143
+ * // Throws an error because the bytes do not match any pattern
144
+ * ```
145
+ *
146
+ * @see {@link getPatternMatchCodec}
147
+ * @see {@link getPatternMatchEncoder}
148
+ */
149
+ export function getPatternMatchDecoder<TTo, TSize extends number>(
150
+ patterns: [(value: ReadonlyUint8Array) => boolean, FixedSizeDecoder<TTo, TSize>][],
151
+ ): FixedSizeDecoder<TTo, TSize>;
152
+ export function getPatternMatchDecoder<TTo>(
153
+ patterns: [(value: ReadonlyUint8Array) => boolean, FixedSizeDecoder<TTo>][],
154
+ ): FixedSizeDecoder<TTo>;
155
+ export function getPatternMatchDecoder<TTo>(
156
+ patterns: [(value: ReadonlyUint8Array) => boolean, VariableSizeDecoder<TTo>][],
157
+ ): VariableSizeDecoder<TTo>;
158
+ export function getPatternMatchDecoder<TTo>(
159
+ patterns: [(value: ReadonlyUint8Array) => boolean, Decoder<TTo>][],
160
+ ): Decoder<TTo>;
161
+ export function getPatternMatchDecoder<TTo>(
162
+ patterns: [(value: ReadonlyUint8Array) => boolean, Decoder<TTo>][],
163
+ ): Decoder<TTo> {
164
+ return getUnionDecoder(
165
+ patterns.map(([, decoder]) => decoder),
166
+ (value: ReadonlyUint8Array) => {
167
+ const index = patterns.findIndex(([predicate]) => predicate(value));
168
+ if (index === -1) {
169
+ throw new SolanaError(SOLANA_ERROR__CODECS__INVALID_PATTERN_MATCH_BYTES, {
170
+ bytes: value,
171
+ });
172
+ }
173
+ return index;
174
+ },
175
+ );
176
+ }
177
+
178
+ type PatternMatchCodecEntry<TNarrowedFrom, TFrom = TNarrowedFrom, TTo = TNarrowedFrom> = TNarrowedFrom extends TFrom
179
+ ? TTo extends TNarrowedFrom
180
+ ?
181
+ | readonly [
182
+ (value: TFrom) => value is TNarrowedFrom,
183
+ (bytes: ReadonlyUint8Array) => boolean,
184
+ Codec<TNarrowedFrom, TTo>,
185
+ ]
186
+ | readonly [(value: TFrom) => boolean, (bytes: ReadonlyUint8Array) => boolean, Codec<TFrom, TTo>]
187
+ : never
188
+ : never;
189
+
190
+ type FixedSizePatternMatchCodecEntry<
191
+ TNarrowedFrom,
192
+ TFrom = TNarrowedFrom,
193
+ TTo = TNarrowedFrom,
194
+ TSize extends number = number,
195
+ > = TNarrowedFrom extends TFrom
196
+ ? TTo extends TNarrowedFrom
197
+ ?
198
+ | readonly [
199
+ (value: TFrom) => boolean,
200
+ (bytes: ReadonlyUint8Array) => boolean,
201
+ FixedSizeCodec<TFrom, TTo, TSize>,
202
+ ]
203
+ | readonly [
204
+ (value: TFrom) => value is TNarrowedFrom,
205
+ (bytes: ReadonlyUint8Array) => boolean,
206
+ FixedSizeCodec<TNarrowedFrom, TTo, TSize>,
207
+ ]
208
+ : never
209
+ : never;
210
+
211
+ type VariableSizePatternMatchCodecEntry<
212
+ TNarrowedFrom,
213
+ TFrom = TNarrowedFrom,
214
+ TTo = TNarrowedFrom,
215
+ > = TNarrowedFrom extends TFrom
216
+ ? TTo extends TNarrowedFrom
217
+ ?
218
+ | readonly [
219
+ (value: TFrom) => boolean,
220
+ (bytes: ReadonlyUint8Array) => boolean,
221
+ VariableSizeCodec<TFrom, TTo>,
222
+ ]
223
+ | readonly [
224
+ (value: TFrom) => value is TNarrowedFrom,
225
+ (bytes: ReadonlyUint8Array) => boolean,
226
+ VariableSizeCodec<TNarrowedFrom, TTo>,
227
+ ]
228
+ : never
229
+ : never;
230
+
231
+ /**
232
+ * Returns a codec that selects which variant codec to use based on pattern matching.
233
+ *
234
+ * This codec evaluates values and byte arrays against a series of predicate functions in order,
235
+ * using the first matching codec for encoding or decoding.
236
+ *
237
+ * @typeParam TFrom - The type of the value to encode.
238
+ * @typeParam TTo - The type of the value to decode.
239
+ *
240
+ * @param patterns - An array of `[valuePredicate, bytesPredicate, codec]` triples. Predicates
241
+ * are tested in order and the first match determines the codec used. During encoding,
242
+ * `valuePredicate` receives the value to encode. During decoding, `bytesPredicate` receives
243
+ * the byte array.
244
+ * @returns A codec that selects the appropriate variant based on the matched pattern.
245
+ *
246
+ * @throws Throws a {@link SOLANA_ERROR__CODECS__INVALID_PATTERN_MATCH_VALUE} error
247
+ * if a value being encoded does not match any of the specified patterns.
248
+ * @throws Throws a {@link SOLANA_ERROR__CODECS__INVALID_PATTERN_MATCH_BYTES} error
249
+ * if a byte array being decoded does not match any of the specified patterns.
250
+ *
251
+ * @example
252
+ * Encoding and decoding using pattern matching.
253
+ * ```ts
254
+ * const codec = getPatternMatchCodec([
255
+ * [
256
+ * (n: number) => n < 256,
257
+ * (bytes) => bytes.length === 1,
258
+ * getU8Codec(),
259
+ * ],
260
+ * [
261
+ * (n: number) => n < 2 ** 16,
262
+ * (bytes) => bytes.length === 2,
263
+ * getU16Codec(),
264
+ * ],
265
+ * [
266
+ * (n: number) => n < 2 ** 32,
267
+ * (bytes) => bytes.length <= 4,
268
+ * getU32Codec(),
269
+ * ]
270
+ * ]);
271
+ *
272
+ * const bytes1 = codec.encode(42); // 0x2a, encoded as u8
273
+ * const value1 = codec.decode(bytes1); // 42, decoded as u8
274
+ *
275
+ * const bytes2 = codec.encode(1000); // 0xe803, encoded as u16
276
+ * const value2 = codec.decode(bytes2); // 1000, decoded as u16
277
+ *
278
+ * const bytes3 = codec.encode(100_000); //0xa0860100, encoded as u32
279
+ * const value3 = codec.decode(bytes3); // 100_000, decoded as u32
280
+ *
281
+ * codec.encode(2 ** 32 + 1);
282
+ * // throws, no encode pattern matches
283
+ * codec.decode(new Uint8Array([0xa0, 0x86, 0x01, 0x00, 0x00]))
284
+ * // throws, no decode pattern matches
285
+ * ```
286
+ *
287
+ * @see {@link getPatternMatchEncoder}
288
+ * @see {@link getPatternMatchDecoder}
289
+ * @see {@link getUnionCodec}
290
+ */
291
+ export function getPatternMatchCodec<TFrom, TTo extends TFrom = TFrom, TSize extends number = number>(
292
+ patterns: FixedSizePatternMatchCodecEntry<TFrom, TFrom, TTo, TSize>[],
293
+ ): FixedSizeCodec<TFrom, TTo, TSize>;
294
+ export function getPatternMatchCodec<TFrom, TTo extends TFrom = TFrom>(
295
+ patterns: FixedSizePatternMatchCodecEntry<TFrom, TFrom, TTo>[],
296
+ ): FixedSizeCodec<TFrom, TTo>;
297
+ export function getPatternMatchCodec<TFrom, TTo extends TFrom = TFrom>(
298
+ patterns: VariableSizePatternMatchCodecEntry<TFrom, TFrom, TTo>[],
299
+ ): VariableSizeCodec<TFrom, TTo>;
300
+ export function getPatternMatchCodec<TFrom, TTo extends TFrom = TFrom>(
301
+ patterns: PatternMatchCodecEntry<TFrom, TFrom, TTo>[],
302
+ ): Codec<TFrom, TTo>;
303
+ export function getPatternMatchCodec<TFrom, TTo extends TFrom = TFrom>(
304
+ patterns: PatternMatchCodecEntry<TFrom, TFrom, TTo>[],
305
+ ): Codec<TFrom, TTo> {
306
+ return combineCodec(
307
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
308
+ getPatternMatchEncoder(patterns.map(([valuePredicate, , codec]) => [valuePredicate, codec]) as any),
309
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
310
+ getPatternMatchDecoder(patterns.map(([, bytesPredicate, codec]) => [bytesPredicate, codec]) as any),
311
+ );
312
+ }