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

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,384 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import {
3
+ Codec,
4
+ combineCodec,
5
+ Decoder,
6
+ Encoder,
7
+ FixedSizeCodec,
8
+ FixedSizeDecoder,
9
+ FixedSizeEncoder,
10
+ transformDecoder,
11
+ transformEncoder,
12
+ } from '@solana/codecs-core';
13
+ import { getU8Decoder, getU8Encoder, NumberCodec, NumberDecoder, NumberEncoder } from '@solana/codecs-numbers';
14
+ import { SOLANA_ERROR__CODECS__INVALID_DISCRIMINATED_UNION_VARIANT, SolanaError } from '@solana/errors';
15
+
16
+ import { getTupleDecoder, getTupleEncoder } from './tuple';
17
+ import { getUnionDecoder, getUnionEncoder } from './union';
18
+ import { DrainOuterGeneric } from './utils';
19
+
20
+ /**
21
+ * Represents a discriminated union using a specific discriminator property.
22
+ *
23
+ * A discriminated union is a TypeScript-friendly way to represent Rust-like enums.
24
+ * Each variant in the union is distinguished by a shared discriminator property.
25
+ *
26
+ * @typeParam TDiscriminatorProperty - The name of the discriminator property.
27
+ * @typeParam TDiscriminatorValue - The type of the discriminator value.
28
+ *
29
+ * @example
30
+ * ```ts
31
+ * type Message =
32
+ * | { __kind: 'Quit' } // Empty variant
33
+ * | { __kind: 'Write'; fields: [string] } // Tuple variant
34
+ * | { __kind: 'Move'; x: number; y: number }; // Struct variant
35
+ * ```
36
+ */
37
+ export type DiscriminatedUnion<
38
+ TDiscriminatorProperty extends string = '__kind',
39
+ TDiscriminatorValue extends string = string,
40
+ > = {
41
+ [P in TDiscriminatorProperty]: TDiscriminatorValue;
42
+ };
43
+
44
+ /**
45
+ * Extracts a variant from a discriminated union based on its discriminator value.
46
+ *
47
+ * @typeParam TUnion - The discriminated union type.
48
+ * @typeParam TDiscriminatorProperty - The property used as the discriminator.
49
+ * @typeParam TDiscriminatorValue - The specific variant to extract.
50
+ *
51
+ * @example
52
+ * ```ts
53
+ * type Message =
54
+ * | { __kind: 'Quit' }
55
+ * | { __kind: 'Write'; fields: [string] }
56
+ * | { __kind: 'Move'; x: number; y: number };
57
+ *
58
+ * type ClickEvent = GetDiscriminatedUnionVariant<Message, '__kind', 'Move'>;
59
+ * // -> { __kind: 'Move'; x: number; y: number }
60
+ * ```
61
+ */
62
+ export type GetDiscriminatedUnionVariant<
63
+ TUnion extends DiscriminatedUnion<TDiscriminatorProperty>,
64
+ TDiscriminatorProperty extends string,
65
+ TDiscriminatorValue extends TUnion[TDiscriminatorProperty],
66
+ > = Extract<TUnion, DiscriminatedUnion<TDiscriminatorProperty, TDiscriminatorValue>>;
67
+
68
+ /**
69
+ * Extracts a variant from a discriminated union without its discriminator property.
70
+ *
71
+ * @typeParam TUnion - The discriminated union type.
72
+ * @typeParam TDiscriminatorProperty - The property used as the discriminator.
73
+ * @typeParam TDiscriminatorValue - The specific variant to extract.
74
+ *
75
+ * @example
76
+ * ```ts
77
+ * type Message =
78
+ * | { __kind: 'Quit' }
79
+ * | { __kind: 'Write'; fields: [string] }
80
+ * | { __kind: 'Move'; x: number; y: number };
81
+ *
82
+ * type MoveContent = GetDiscriminatedUnionVariantContent<Message, '__kind', 'Move'>;
83
+ * // -> { x: number; y: number }
84
+ * ```
85
+ */
86
+ export type GetDiscriminatedUnionVariantContent<
87
+ TUnion extends DiscriminatedUnion<TDiscriminatorProperty>,
88
+ TDiscriminatorProperty extends string,
89
+ TDiscriminatorValue extends TUnion[TDiscriminatorProperty],
90
+ > = Omit<GetDiscriminatedUnionVariant<TUnion, TDiscriminatorProperty, TDiscriminatorValue>, TDiscriminatorProperty>;
91
+
92
+ /**
93
+ * Defines the configuration for discriminated union codecs.
94
+ *
95
+ * This configuration controls how the discriminator is stored and named.
96
+ *
97
+ * @typeParam TDiscriminatorProperty - The property name of the discriminator.
98
+ * @typeParam TDiscriminatorSize - The codec used for the discriminator prefix.
99
+ */
100
+ export type DiscriminatedUnionCodecConfig<
101
+ TDiscriminatorProperty extends string = '__kind',
102
+ TDiscriminatorSize = NumberCodec | NumberDecoder | NumberEncoder,
103
+ > = {
104
+ /**
105
+ * The property name of the discriminator.
106
+ * @defaultValue `__kind`
107
+ */
108
+ discriminator?: TDiscriminatorProperty;
109
+ /**
110
+ * The codec used to encode/decode the discriminator prefix.
111
+ * @defaultValue `u8` prefix
112
+ */
113
+ size?: TDiscriminatorSize;
114
+ };
115
+
116
+ type DiscriminatorValue = bigint | boolean | number | string | null | undefined;
117
+ type Variants<T> = readonly (readonly [DiscriminatorValue, T])[];
118
+ type ArrayIndices<T extends readonly unknown[]> = Exclude<Partial<T>['length'], T['length']> & number;
119
+
120
+ type GetEncoderTypeFromVariants<
121
+ TVariants extends Variants<Encoder<any>>,
122
+ TDiscriminatorProperty extends string,
123
+ > = DrainOuterGeneric<{
124
+ [I in ArrayIndices<TVariants>]: (TVariants[I][1] extends Encoder<infer TFrom>
125
+ ? TFrom extends object
126
+ ? TFrom
127
+ : object
128
+ : never) & { [P in TDiscriminatorProperty]: TVariants[I][0] };
129
+ }>[ArrayIndices<TVariants>];
130
+
131
+ type GetDecoderTypeFromVariants<
132
+ TVariants extends Variants<Decoder<any>>,
133
+ TDiscriminatorProperty extends string,
134
+ > = DrainOuterGeneric<{
135
+ [I in ArrayIndices<TVariants>]: (TVariants[I][1] extends Decoder<infer TTo>
136
+ ? TTo extends object
137
+ ? TTo
138
+ : object
139
+ : never) & { [P in TDiscriminatorProperty]: TVariants[I][0] };
140
+ }>[ArrayIndices<TVariants>];
141
+
142
+ type UnionEncoder<TVariants extends Variants<Encoder<unknown>>, TDiscriminatorProperty extends string> =
143
+ TVariants extends Variants<FixedSizeEncoder<any>>
144
+ ? FixedSizeEncoder<GetEncoderTypeFromVariants<TVariants, TDiscriminatorProperty>>
145
+ : Encoder<GetEncoderTypeFromVariants<TVariants, TDiscriminatorProperty>>;
146
+
147
+ type UnionDecoder<TVariants extends Variants<Decoder<unknown>>, TDiscriminatorProperty extends string> =
148
+ TVariants extends Variants<FixedSizeDecoder<any>>
149
+ ? FixedSizeDecoder<GetDecoderTypeFromVariants<TVariants, TDiscriminatorProperty>>
150
+ : Decoder<GetDecoderTypeFromVariants<TVariants, TDiscriminatorProperty>>;
151
+
152
+ type UnionCodec<TVariants extends Variants<Codec<unknown, unknown>>, TDiscriminatorProperty extends string> =
153
+ TVariants extends Variants<FixedSizeCodec<any, any>>
154
+ ? FixedSizeCodec<
155
+ GetEncoderTypeFromVariants<TVariants, TDiscriminatorProperty>,
156
+ GetDecoderTypeFromVariants<TVariants, TDiscriminatorProperty> &
157
+ GetEncoderTypeFromVariants<TVariants, TDiscriminatorProperty>
158
+ >
159
+ : Codec<
160
+ GetEncoderTypeFromVariants<TVariants, TDiscriminatorProperty>,
161
+ GetDecoderTypeFromVariants<TVariants, TDiscriminatorProperty> &
162
+ GetEncoderTypeFromVariants<TVariants, TDiscriminatorProperty>
163
+ >;
164
+
165
+ /**
166
+ * Returns an encoder for discriminated unions.
167
+ *
168
+ * This encoder serializes objects that follow the discriminated union pattern
169
+ * by prefixing them with a numerical discriminator that represents their variant.
170
+ *
171
+ * Unlike {@link getUnionEncoder}, this encoder automatically extracts and processes
172
+ * the discriminator property (default: `__kind`) from each variant.
173
+ *
174
+ * For more details, see {@link getDiscriminatedUnionCodec}.
175
+ *
176
+ * @typeParam TVariants - The variants of the discriminated union.
177
+ * @typeParam TDiscriminatorProperty - The property used as the discriminator.
178
+ *
179
+ * @param variants - The variant encoders as `[discriminator, encoder]` pairs.
180
+ * @param config - Configuration options for encoding.
181
+ * @returns An `Encoder` for encoding discriminated union objects.
182
+ *
183
+ * @example
184
+ * Encoding a discriminated union.
185
+ * ```ts
186
+ * type Message =
187
+ * | { __kind: 'Quit' } // Empty variant.
188
+ * | { __kind: 'Write'; fields: [string] } // Tuple variant.
189
+ * | { __kind: 'Move'; x: number; y: number }; // Struct variant.
190
+ *
191
+ * const messageEncoder = getDiscriminatedUnionEncoder([
192
+ * ['Quit', getUnitEncoder()],
193
+ * ['Write', getStructEncoder([['fields', getTupleEncoder([addCodecSizePrefix(getUtf8Encoder(), getU32Encoder())])]])],
194
+ * ['Move', getStructEncoder([['x', getI32Encoder()], ['y', getI32Encoder()]])]
195
+ * ]);
196
+ *
197
+ * messageEncoder.encode({ __kind: 'Move', x: 5, y: 6 });
198
+ * // 0x020500000006000000
199
+ * // | | └── Field y (6)
200
+ * // | └── Field x (5)
201
+ * // └── 1-byte discriminator (Index 2 — the "Move" variant)
202
+ * ```
203
+ *
204
+ * @see {@link getDiscriminatedUnionCodec}
205
+ */
206
+ export function getDiscriminatedUnionEncoder<
207
+ const TVariants extends Variants<Encoder<any>>,
208
+ const TDiscriminatorProperty extends string = '__kind',
209
+ >(
210
+ variants: TVariants,
211
+ config: DiscriminatedUnionCodecConfig<TDiscriminatorProperty, NumberEncoder> = {},
212
+ ): UnionEncoder<TVariants, TDiscriminatorProperty> {
213
+ type TFrom = GetEncoderTypeFromVariants<TVariants, TDiscriminatorProperty>;
214
+ const discriminatorProperty = (config.discriminator ?? '__kind') as TDiscriminatorProperty;
215
+ const prefix = config.size ?? getU8Encoder();
216
+ return getUnionEncoder(
217
+ variants.map(([, variant], index) =>
218
+ transformEncoder(getTupleEncoder([prefix, variant]), (value: TFrom): [number, TFrom] => [index, value]),
219
+ ),
220
+ value => getVariantDiscriminator(variants, value[discriminatorProperty]),
221
+ ) as UnionEncoder<TVariants, TDiscriminatorProperty>;
222
+ }
223
+
224
+ /**
225
+ * Returns a decoder for discriminated unions.
226
+ *
227
+ * This decoder deserializes objects that follow the discriminated union pattern
228
+ * by **reading a numerical discriminator** and mapping it to the corresponding variant.
229
+ *
230
+ * Unlike {@link getUnionDecoder}, this decoder automatically inserts the discriminator
231
+ * property (default: `__kind`) into the decoded object.
232
+ *
233
+ * For more details, see {@link getDiscriminatedUnionCodec}.
234
+ *
235
+ * @typeParam TVariants - The variants of the discriminated union.
236
+ * @typeParam TDiscriminatorProperty - The property used as the discriminator.
237
+ *
238
+ * @param variants - The variant decoders as `[discriminator, decoder]` pairs.
239
+ * @param config - Configuration options for decoding.
240
+ * @returns A `Decoder` for decoding discriminated union objects.
241
+ *
242
+ * @example
243
+ * Decoding a discriminated union.
244
+ * ```ts
245
+ * type Message =
246
+ * | { __kind: 'Quit' } // Empty variant.
247
+ * | { __kind: 'Write'; fields: [string] } // Tuple variant.
248
+ * | { __kind: 'Move'; x: number; y: number }; // Struct variant.
249
+ *
250
+ * const messageDecoder = getDiscriminatedUnionDecoder([
251
+ * ['Quit', getUnitDecoder()],
252
+ * ['Write', getStructDecoder([['fields', getTupleDecoder([addCodecSizePrefix(getUtf8Decoder(), getU32Decoder())])]])],
253
+ * ['Move', getStructDecoder([['x', getI32Decoder()], ['y', getI32Decoder()]])]
254
+ * ]);
255
+ *
256
+ * messageDecoder.decode(new Uint8Array([0x02,0x05,0x00,0x00,0x00,0x06,0x00,0x00,0x00]));
257
+ * // { __kind: 'Move', x: 5, y: 6 }
258
+ * ```
259
+ *
260
+ * @see {@link getDiscriminatedUnionCodec}
261
+ */
262
+ export function getDiscriminatedUnionDecoder<
263
+ const TVariants extends Variants<Decoder<any>>,
264
+ const TDiscriminatorProperty extends string = '__kind',
265
+ >(
266
+ variants: TVariants,
267
+ config: DiscriminatedUnionCodecConfig<TDiscriminatorProperty, NumberDecoder> = {},
268
+ ): UnionDecoder<TVariants, TDiscriminatorProperty> {
269
+ const discriminatorProperty = config.discriminator ?? '__kind';
270
+ const prefix = config.size ?? getU8Decoder();
271
+ return getUnionDecoder(
272
+ variants.map(([discriminator, variant]) =>
273
+ transformDecoder(getTupleDecoder([prefix, variant]), ([, value]) => ({
274
+ [discriminatorProperty]: discriminator,
275
+ ...value,
276
+ })),
277
+ ),
278
+ (bytes, offset) => Number(prefix.read(bytes, offset)[0]),
279
+ ) as UnionDecoder<TVariants, TDiscriminatorProperty>;
280
+ }
281
+
282
+ /**
283
+ * Returns a codec for encoding and decoding {@link DiscriminatedUnion}.
284
+ *
285
+ * A {@link DiscriminatedUnion} is a TypeScript representation of Rust-like enums, where
286
+ * each variant is distinguished by a discriminator field (default: `__kind`).
287
+ *
288
+ * This codec inserts a numerical prefix to represent the variant index.
289
+ *
290
+ * @typeParam TVariants - The variants of the discriminated union.
291
+ * @typeParam TDiscriminatorProperty - The property used as the discriminator.
292
+ *
293
+ * @param variants - The variant codecs as `[discriminator, codec]` pairs.
294
+ * @param config - Configuration options for encoding/decoding.
295
+ * @returns A `Codec` for encoding and decoding discriminated union objects.
296
+ *
297
+ * @example
298
+ * Encoding and decoding a discriminated union.
299
+ * ```ts
300
+ * type Message =
301
+ * | { __kind: 'Quit' } // Empty variant.
302
+ * | { __kind: 'Write'; fields: [string] } // Tuple variant.
303
+ * | { __kind: 'Move'; x: number; y: number }; // Struct variant.
304
+ *
305
+ * const messageCodec = getDiscriminatedUnionCodec([
306
+ * ['Quit', getUnitCodec()],
307
+ * ['Write', getStructCodec([['fields', getTupleCodec([addCodecSizePrefix(getUtf8Codec(), getU32Codec())])]])],
308
+ * ['Move', getStructCodec([['x', getI32Codec()], ['y', getI32Codec()]])]
309
+ * ]);
310
+ *
311
+ * messageCodec.encode({ __kind: 'Move', x: 5, y: 6 });
312
+ * // 0x020500000006000000
313
+ * // | | └── Field y (6)
314
+ * // | └── Field x (5)
315
+ * // └── 1-byte discriminator (Index 2 — the "Move" variant)
316
+ *
317
+ * const value = messageCodec.decode(bytes);
318
+ * // { __kind: 'Move', x: 5, y: 6 }
319
+ * ```
320
+ *
321
+ * @example
322
+ * Using a `u32` discriminator instead of `u8`.
323
+ * ```ts
324
+ * const codec = getDiscriminatedUnionCodec([...], { size: getU32Codec() });
325
+ *
326
+ * codec.encode({ __kind: 'Quit' });
327
+ * // 0x00000000
328
+ * // └------┘ 4-byte discriminator (Index 0)
329
+ *
330
+ * codec.decode(new Uint8Array([0x00, 0x00, 0x00, 0x00]));
331
+ * // { __kind: 'Quit' }
332
+ * ```
333
+ *
334
+ * @example
335
+ * Customizing the discriminator property.
336
+ * ```ts
337
+ * const codec = getDiscriminatedUnionCodec([...], { discriminator: 'message' });
338
+ *
339
+ * codec.encode({ message: 'Quit' }); // 0x00
340
+ * codec.decode(new Uint8Array([0x00])); // { message: 'Quit' }
341
+ * ```
342
+ *
343
+ * @remarks
344
+ * Separate `getDiscriminatedUnionEncoder` and `getDiscriminatedUnionDecoder` functions are available.
345
+ *
346
+ * ```ts
347
+ * const bytes = getDiscriminatedUnionEncoder(variantEncoders).encode({ __kind: 'Quit' });
348
+ * const message = getDiscriminatedUnionDecoder(variantDecoders).decode(bytes);
349
+ * ```
350
+ *
351
+ * @see {@link getDiscriminatedUnionEncoder}
352
+ * @see {@link getDiscriminatedUnionDecoder}
353
+ */
354
+ export function getDiscriminatedUnionCodec<
355
+ const TVariants extends Variants<Codec<any, any>>,
356
+ const TDiscriminatorProperty extends string = '__kind',
357
+ >(
358
+ variants: TVariants,
359
+ config: DiscriminatedUnionCodecConfig<TDiscriminatorProperty, NumberCodec> = {},
360
+ ): UnionCodec<TVariants, TDiscriminatorProperty> {
361
+ return combineCodec(
362
+ getDiscriminatedUnionEncoder(variants, config) as Encoder<
363
+ GetEncoderTypeFromVariants<TVariants, TDiscriminatorProperty>
364
+ >,
365
+ getDiscriminatedUnionDecoder(variants, config) as Decoder<
366
+ GetDecoderTypeFromVariants<TVariants, TDiscriminatorProperty> &
367
+ GetEncoderTypeFromVariants<TVariants, TDiscriminatorProperty>
368
+ >,
369
+ ) as UnionCodec<TVariants, TDiscriminatorProperty>;
370
+ }
371
+
372
+ function getVariantDiscriminator<const TVariants extends Variants<Decoder<any> | Encoder<any>>>(
373
+ variants: TVariants,
374
+ discriminatorValue: DiscriminatorValue,
375
+ ) {
376
+ const discriminator = variants.findIndex(([key]) => discriminatorValue === key);
377
+ if (discriminator < 0) {
378
+ throw new SolanaError(SOLANA_ERROR__CODECS__INVALID_DISCRIMINATED_UNION_VARIANT, {
379
+ value: discriminatorValue,
380
+ variants: variants.map(([key]) => key),
381
+ });
382
+ }
383
+ return discriminator;
384
+ }
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Defines the "lookup object" of an enum.
3
+ *
4
+ * @example
5
+ * ```ts
6
+ * enum Direction { Left, Right };
7
+ * ```
8
+ */
9
+ export type EnumLookupObject = { [key: string]: number | string };
10
+
11
+ /**
12
+ * Returns the allowed input for an enum.
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * enum Direction { Left, Right };
17
+ * type DirectionInput = GetEnumFrom<Direction>; // "Left" | "Right" | 0 | 1
18
+ * ```
19
+ */
20
+ export type GetEnumFrom<TEnum extends EnumLookupObject> = TEnum[keyof TEnum] | keyof TEnum;
21
+
22
+ /**
23
+ * Returns all the available variants of an enum.
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * enum Direction { Left, Right };
28
+ * type DirectionOutput = GetEnumTo<Direction>; // 0 | 1
29
+ * ```
30
+ */
31
+ export type GetEnumTo<TEnum extends EnumLookupObject> = TEnum[keyof TEnum];
32
+
33
+ export function getEnumStats(constructor: EnumLookupObject) {
34
+ const numericalValues = [...new Set(Object.values(constructor).filter(v => typeof v === 'number'))].sort();
35
+ const enumRecord = Object.fromEntries(Object.entries(constructor).slice(numericalValues.length)) as Record<
36
+ string,
37
+ number | string
38
+ >;
39
+ const enumKeys = Object.keys(enumRecord);
40
+ const enumValues = Object.values(enumRecord);
41
+ const stringValues: string[] = [
42
+ ...new Set([...enumKeys, ...enumValues.filter((v): v is string => typeof v === 'string')]),
43
+ ];
44
+
45
+ return { enumKeys, enumRecord, enumValues, numericalValues, stringValues };
46
+ }
47
+
48
+ export function getEnumIndexFromVariant({
49
+ enumKeys,
50
+ enumValues,
51
+ variant,
52
+ }: {
53
+ enumKeys: string[];
54
+ enumValues: (number | string)[];
55
+ variant: number | string | symbol;
56
+ }): number {
57
+ const valueIndex = findLastIndex(enumValues, value => value === variant);
58
+ if (valueIndex >= 0) return valueIndex;
59
+ return enumKeys.findIndex(key => key === variant);
60
+ }
61
+
62
+ export function getEnumIndexFromDiscriminator({
63
+ discriminator,
64
+ enumKeys,
65
+ enumValues,
66
+ useValuesAsDiscriminators,
67
+ }: {
68
+ discriminator: number;
69
+ enumKeys: string[];
70
+ enumValues: (number | string)[];
71
+ useValuesAsDiscriminators: boolean;
72
+ }): number {
73
+ if (!useValuesAsDiscriminators) {
74
+ return discriminator >= 0 && discriminator < enumKeys.length ? discriminator : -1;
75
+ }
76
+ return findLastIndex(enumValues, value => value === discriminator);
77
+ }
78
+
79
+ function findLastIndex<T>(array: Array<T>, predicate: (value: T, index: number, obj: T[]) => boolean): number {
80
+ let l = array.length;
81
+ while (l--) {
82
+ if (predicate(array[l], l, array)) return l;
83
+ }
84
+ return -1;
85
+ }
86
+
87
+ export function formatNumericalValues(values: number[]): string {
88
+ if (values.length === 0) return '';
89
+ let range: [number, number] = [values[0], values[0]];
90
+ const ranges: string[] = [];
91
+ for (let index = 1; index < values.length; index++) {
92
+ const value = values[index];
93
+ if (range[1] + 1 === value) {
94
+ range[1] = value;
95
+ } else {
96
+ ranges.push(range[0] === range[1] ? `${range[0]}` : `${range[0]}-${range[1]}`);
97
+ range = [value, value];
98
+ }
99
+ }
100
+ ranges.push(range[0] === range[1] ? `${range[0]}` : `${range[0]}-${range[1]}`);
101
+ return ranges.join(', ');
102
+ }