@solana/codecs-core 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/codec.ts ADDED
@@ -0,0 +1,925 @@
1
+ import {
2
+ SOLANA_ERROR__CODECS__EXPECTED_FIXED_LENGTH,
3
+ SOLANA_ERROR__CODECS__EXPECTED_VARIABLE_LENGTH,
4
+ SolanaError,
5
+ } from '@solana/errors';
6
+
7
+ import { ReadonlyUint8Array } from './readonly-uint8array';
8
+
9
+ /**
10
+ * Defines an offset in bytes.
11
+ */
12
+ export type Offset = number;
13
+
14
+ /**
15
+ * An object that can encode a value of type {@link TFrom} into a {@link ReadonlyUint8Array}.
16
+ *
17
+ * This is a common interface for {@link FixedSizeEncoder} and {@link VariableSizeEncoder}.
18
+ *
19
+ * @interface
20
+ * @typeParam TFrom - The type of the value to encode.
21
+ *
22
+ * @see {@link FixedSizeEncoder}
23
+ * @see {@link VariableSizeEncoder}
24
+ */
25
+ type BaseEncoder<TFrom> = {
26
+ /** Encode the provided value and return the encoded bytes directly. */
27
+ readonly encode: (value: TFrom) => ReadonlyUint8Array<ArrayBuffer>;
28
+ /**
29
+ * Writes the encoded value into the provided byte array at the given offset.
30
+ * Returns the offset of the next byte after the encoded value.
31
+ */
32
+ readonly write: (value: TFrom, bytes: Uint8Array, offset: Offset) => Offset;
33
+ };
34
+
35
+ /**
36
+ * An object that can encode a value of type {@link TFrom} into a fixed-size {@link ReadonlyUint8Array}.
37
+ *
38
+ * See {@link Encoder} to learn more about creating and composing encoders.
39
+ *
40
+ * @interface
41
+ * @typeParam TFrom - The type of the value to encode.
42
+ * @typeParam TSize - The fixed size of the encoded value in bytes.
43
+ *
44
+ * @example
45
+ * ```ts
46
+ * const encoder: FixedSizeEncoder<number, 4>;
47
+ * const bytes = encoder.encode(42);
48
+ * const size = encoder.fixedSize; // 4
49
+ * ```
50
+ *
51
+ * @see {@link Encoder}
52
+ * @see {@link VariableSizeEncoder}
53
+ */
54
+ export type FixedSizeEncoder<TFrom, TSize extends number = number> = BaseEncoder<TFrom> & {
55
+ /** The fixed size of the encoded value in bytes. */
56
+ readonly fixedSize: TSize;
57
+ };
58
+
59
+ /**
60
+ * An object that can encode a value of type {@link TFrom} into a variable-size {@link ReadonlyUint8Array}.
61
+ *
62
+ * See {@link Encoder} to learn more about creating and composing encoders.
63
+ *
64
+ * @interface
65
+ * @typeParam TFrom - The type of the value to encode.
66
+ *
67
+ * @example
68
+ * ```ts
69
+ * const encoder: VariableSizeEncoder<string>;
70
+ * const bytes = encoder.encode('hello');
71
+ * const size = encoder.getSizeFromValue('hello');
72
+ * ```
73
+ *
74
+ * @see {@link Encoder}
75
+ * @see {@link FixedSizeEncoder}
76
+ */
77
+ export type VariableSizeEncoder<TFrom> = BaseEncoder<TFrom> & {
78
+ /** Returns the size of the encoded value in bytes for a given input. */
79
+ readonly getSizeFromValue: (value: TFrom) => number;
80
+ /** The maximum possible size of an encoded value in bytes, if applicable. */
81
+ readonly maxSize?: number;
82
+ };
83
+
84
+ /**
85
+ * An object that can encode a value of type {@link TFrom} into a {@link ReadonlyUint8Array}.
86
+ *
87
+ * An `Encoder` can be either:
88
+ * - A {@link FixedSizeEncoder}, where all encoded values have the same fixed size.
89
+ * - A {@link VariableSizeEncoder}, where encoded values can vary in size.
90
+ *
91
+ * @typeParam TFrom - The type of the value to encode.
92
+ *
93
+ * @example
94
+ * Encoding a value into a new byte array.
95
+ * ```ts
96
+ * const encoder: Encoder<string>;
97
+ * const bytes = encoder.encode('hello');
98
+ * ```
99
+ *
100
+ * @example
101
+ * Writing the encoded value into an existing byte array.
102
+ * ```ts
103
+ * const encoder: Encoder<string>;
104
+ * const bytes = new Uint8Array(100);
105
+ * const nextOffset = encoder.write('hello', bytes, 20);
106
+ * ```
107
+ *
108
+ * @remarks
109
+ * You may create `Encoders` manually using the {@link createEncoder} function but it is more common
110
+ * to compose multiple `Encoders` together using the various helpers of the `@solana/codecs` package.
111
+ *
112
+ * For instance, here's how you might create an `Encoder` for a `Person` object type that contains
113
+ * a `name` string and an `age` number:
114
+ *
115
+ * ```ts
116
+ * import { getStructEncoder, addEncoderSizePrefix, getUtf8Encoder, getU32Encoder } from '@solana/codecs';
117
+ *
118
+ * type Person = { name: string; age: number };
119
+ * const getPersonEncoder = (): Encoder<Person> =>
120
+ * getStructEncoder([
121
+ * ['name', addEncoderSizePrefix(getUtf8Encoder(), getU32Encoder())],
122
+ * ['age', getU32Encoder()],
123
+ * ]);
124
+ * ```
125
+ *
126
+ * Note that composed `Encoder` types are clever enough to understand whether
127
+ * they are fixed-size or variable-size. In the example above, `getU32Encoder()` is
128
+ * a fixed-size encoder, while `addEncoderSizePrefix(getUtf8Encoder(), getU32Encoder())`
129
+ * is a variable-size encoder. This makes the final `Person` encoder a variable-size encoder.
130
+ *
131
+ * @see {@link FixedSizeEncoder}
132
+ * @see {@link VariableSizeEncoder}
133
+ * @see {@link createEncoder}
134
+ */
135
+ export type Encoder<TFrom> = FixedSizeEncoder<TFrom> | VariableSizeEncoder<TFrom>;
136
+
137
+ /**
138
+ * An object that can decode a byte array into a value of type {@link TTo}.
139
+ *
140
+ * This is a common interface for {@link FixedSizeDecoder} and {@link VariableSizeDecoder}.
141
+ *
142
+ * @interface
143
+ * @typeParam TTo - The type of the decoded value.
144
+ *
145
+ * @see {@link FixedSizeDecoder}
146
+ * @see {@link VariableSizeDecoder}
147
+ */
148
+ type BaseDecoder<TTo> = {
149
+ /** Decodes the provided byte array at the given offset (or zero) and returns the value directly. */
150
+ readonly decode: (bytes: ReadonlyUint8Array | Uint8Array, offset?: Offset) => TTo;
151
+ /**
152
+ * Reads the encoded value from the provided byte array at the given offset.
153
+ * Returns the decoded value and the offset of the next byte after the encoded value.
154
+ */
155
+ readonly read: (bytes: ReadonlyUint8Array | Uint8Array, offset: Offset) => [TTo, Offset];
156
+ };
157
+
158
+ /**
159
+ * An object that can decode a fixed-size byte array into a value of type {@link TTo}.
160
+ *
161
+ * See {@link Decoder} to learn more about creating and composing decoders.
162
+ *
163
+ * @interface
164
+ * @typeParam TTo - The type of the decoded value.
165
+ * @typeParam TSize - The fixed size of the encoded value in bytes.
166
+ *
167
+ * @example
168
+ * ```ts
169
+ * const decoder: FixedSizeDecoder<number, 4>;
170
+ * const value = decoder.decode(bytes);
171
+ * const size = decoder.fixedSize; // 4
172
+ * ```
173
+ *
174
+ * @see {@link Decoder}
175
+ * @see {@link VariableSizeDecoder}
176
+ */
177
+ export type FixedSizeDecoder<TTo, TSize extends number = number> = BaseDecoder<TTo> & {
178
+ /** The fixed size of the encoded value in bytes. */
179
+ readonly fixedSize: TSize;
180
+ };
181
+
182
+ /**
183
+ * An object that can decode a variable-size byte array into a value of type {@link TTo}.
184
+ *
185
+ * See {@link Decoder} to learn more about creating and composing decoders.
186
+ *
187
+ * @interface
188
+ * @typeParam TTo - The type of the decoded value.
189
+ *
190
+ * @example
191
+ * ```ts
192
+ * const decoder: VariableSizeDecoder<number>;
193
+ * const value = decoder.decode(bytes);
194
+ * ```
195
+ *
196
+ * @see {@link Decoder}
197
+ * @see {@link VariableSizeDecoder}
198
+ */
199
+ export type VariableSizeDecoder<TTo> = BaseDecoder<TTo> & {
200
+ /** The maximum possible size of an encoded value in bytes, if applicable. */
201
+ readonly maxSize?: number;
202
+ };
203
+
204
+ /**
205
+ * An object that can decode a byte array into a value of type {@link TTo}.
206
+ *
207
+ * An `Decoder` can be either:
208
+ * - A {@link FixedSizeDecoder}, where all byte arrays have the same fixed size.
209
+ * - A {@link VariableSizeDecoder}, where byte arrays can vary in size.
210
+ *
211
+ * @typeParam TTo - The type of the decoded value.
212
+ *
213
+ * @example
214
+ * Getting the decoded value from a byte array.
215
+ * ```ts
216
+ * const decoder: Decoder<string>;
217
+ * const value = decoder.decode(bytes);
218
+ * ```
219
+ *
220
+ * @example
221
+ * Reading the decoded value from a byte array at a specific offset
222
+ * and getting the offset of the next byte to read.
223
+ * ```ts
224
+ * const decoder: Decoder<string>;
225
+ * const [value, nextOffset] = decoder.read('hello', bytes, 20);
226
+ * ```
227
+ *
228
+ * @remarks
229
+ * You may create `Decoders` manually using the {@link createDecoder} function but it is more common
230
+ * to compose multiple `Decoders` together using the various helpers of the `@solana/codecs` package.
231
+ *
232
+ * For instance, here's how you might create an `Decoder` for a `Person` object type that contains
233
+ * a `name` string and an `age` number:
234
+ *
235
+ * ```ts
236
+ * import { getStructDecoder, addDecoderSizePrefix, getUtf8Decoder, getU32Decoder } from '@solana/codecs';
237
+ *
238
+ * type Person = { name: string; age: number };
239
+ * const getPersonDecoder = (): Decoder<Person> =>
240
+ * getStructDecoder([
241
+ * ['name', addDecoderSizePrefix(getUtf8Decoder(), getU32Decoder())],
242
+ * ['age', getU32Decoder()],
243
+ * ]);
244
+ * ```
245
+ *
246
+ * Note that composed `Decoder` types are clever enough to understand whether
247
+ * they are fixed-size or variable-size. In the example above, `getU32Decoder()` is
248
+ * a fixed-size decoder, while `addDecoderSizePrefix(getUtf8Decoder(), getU32Decoder())`
249
+ * is a variable-size decoder. This makes the final `Person` decoder a variable-size decoder.
250
+ *
251
+ * @see {@link FixedSizeDecoder}
252
+ * @see {@link VariableSizeDecoder}
253
+ * @see {@link createDecoder}
254
+ */
255
+ export type Decoder<TTo> = FixedSizeDecoder<TTo> | VariableSizeDecoder<TTo>;
256
+
257
+ /**
258
+ * An object that can encode and decode a value to and from a fixed-size byte array.
259
+ *
260
+ * See {@link Codec} to learn more about creating and composing codecs.
261
+ *
262
+ * @interface
263
+ * @typeParam TFrom - The type of the value to encode.
264
+ * @typeParam TTo - The type of the decoded value.
265
+ * @typeParam TSize - The fixed size of the encoded value in bytes.
266
+ *
267
+ * @example
268
+ * ```ts
269
+ * const codec: FixedSizeCodec<number | bigint, bigint, 8>;
270
+ * const bytes = codec.encode(42);
271
+ * const value = codec.decode(bytes); // 42n
272
+ * const size = codec.fixedSize; // 8
273
+ * ```
274
+ *
275
+ * @see {@link Codec}
276
+ * @see {@link VariableSizeCodec}
277
+ */
278
+ export type FixedSizeCodec<TFrom, TTo extends TFrom = TFrom, TSize extends number = number> = FixedSizeDecoder<
279
+ TTo,
280
+ TSize
281
+ > &
282
+ FixedSizeEncoder<TFrom, TSize>;
283
+
284
+ /**
285
+ * An object that can encode and decode a value to and from a variable-size byte array.
286
+ *
287
+ * See {@link Codec} to learn more about creating and composing codecs.
288
+ *
289
+ * @interface
290
+ * @typeParam TFrom - The type of the value to encode.
291
+ * @typeParam TTo - The type of the decoded value.
292
+ *
293
+ * @example
294
+ * ```ts
295
+ * const codec: VariableSizeCodec<number | bigint, bigint>;
296
+ * const bytes = codec.encode(42);
297
+ * const value = codec.decode(bytes); // 42n
298
+ * const size = codec.getSizeFromValue(42);
299
+ * ```
300
+ *
301
+ * @see {@link Codec}
302
+ * @see {@link FixedSizeCodec}
303
+ */
304
+ export type VariableSizeCodec<TFrom, TTo extends TFrom = TFrom> = VariableSizeDecoder<TTo> & VariableSizeEncoder<TFrom>;
305
+
306
+ /**
307
+ * An object that can encode and decode a value to and from a byte array.
308
+ *
309
+ * A `Codec` can be either:
310
+ * - A {@link FixedSizeCodec}, where all encoded values have the same fixed size.
311
+ * - A {@link VariableSizeCodec}, where encoded values can vary in size.
312
+ *
313
+ * @example
314
+ * ```ts
315
+ * const codec: Codec<string>;
316
+ * const bytes = codec.encode('hello');
317
+ * const value = codec.decode(bytes); // 'hello'
318
+ * ```
319
+ *
320
+ * @remarks
321
+ * For convenience, codecs can encode looser types than they decode.
322
+ * That is, type {@link TFrom} can be a superset of type {@link TTo}.
323
+ * For instance, a `Codec<bigint | number, bigint>` can encode both
324
+ * `bigint` and `number` values, but will always decode to a `bigint`.
325
+ *
326
+ * ```ts
327
+ * const codec: Codec<bigint | number, bigint>;
328
+ * const bytes = codec.encode(42);
329
+ * const value = codec.decode(bytes); // 42n
330
+ * ```
331
+ *
332
+ * It is worth noting that codecs are the union of encoders and decoders.
333
+ * This means that a `Codec<TFrom, TTo>` can be combined from an `Encoder<TFrom>`
334
+ * and a `Decoder<TTo>` using the {@link combineCodec} function. This is particularly
335
+ * useful for library authors who want to expose all three types of objects to their users.
336
+ *
337
+ * ```ts
338
+ * const encoder: Encoder<bigint | number>;
339
+ * const decoder: Decoder<bigint>;
340
+ * const codec: Codec<bigint | number, bigint> = combineCodec(encoder, decoder);
341
+ * ```
342
+ *
343
+ * Aside from combining encoders and decoders, codecs can also be created from scratch using
344
+ * the {@link createCodec} function but it is more common to compose multiple codecs together
345
+ * using the various helpers of the `@solana/codecs` package.
346
+ *
347
+ * For instance, here's how you might create a `Codec` for a `Person` object type that contains
348
+ * a `name` string and an `age` number:
349
+ *
350
+ * ```ts
351
+ * import { getStructCodec, addCodecSizePrefix, getUtf8Codec, getU32Codec } from '@solana/codecs';
352
+ *
353
+ * type Person = { name: string; age: number };
354
+ * const getPersonCodec = (): Codec<Person> =>
355
+ * getStructCodec([
356
+ * ['name', addCodecSizePrefix(getUtf8Codec(), getU32Codec())],
357
+ * ['age', getU32Codec()],
358
+ * ]);
359
+ * ```
360
+ *
361
+ * Note that composed `Codec` types are clever enough to understand whether
362
+ * they are fixed-size or variable-size. In the example above, `getU32Codec()` is
363
+ * a fixed-size codec, while `addCodecSizePrefix(getUtf8Codec(), getU32Codec())`
364
+ * is a variable-size codec. This makes the final `Person` codec a variable-size codec.
365
+ *
366
+ * @see {@link FixedSizeCodec}
367
+ * @see {@link VariableSizeCodec}
368
+ * @see {@link combineCodec}
369
+ * @see {@link createCodec}
370
+ */
371
+ export type Codec<TFrom, TTo extends TFrom = TFrom> = FixedSizeCodec<TFrom, TTo> | VariableSizeCodec<TFrom, TTo>;
372
+
373
+ /**
374
+ * Gets the encoded size of a given value in bytes using the provided encoder.
375
+ *
376
+ * @typeParam TFrom - The type of the value to encode.
377
+ * @param value - The value to be encoded.
378
+ * @param encoder - The encoder used to determine the encoded size.
379
+ * @returns The size of the encoded value in bytes.
380
+ *
381
+ * @example
382
+ * ```ts
383
+ * const fixedSizeEncoder = { fixedSize: 4 };
384
+ * getEncodedSize(123, fixedSizeEncoder); // Returns 4.
385
+ *
386
+ * const variableSizeEncoder = { getSizeFromValue: (value: string) => value.length };
387
+ * getEncodedSize("hello", variableSizeEncoder); // Returns 5.
388
+ * ```
389
+ *
390
+ * @see {@link Encoder}
391
+ */
392
+ export function getEncodedSize<TFrom>(
393
+ value: TFrom,
394
+ encoder: { fixedSize: number } | { getSizeFromValue: (value: TFrom) => number },
395
+ ): number {
396
+ return 'fixedSize' in encoder ? encoder.fixedSize : encoder.getSizeFromValue(value);
397
+ }
398
+
399
+ /**
400
+ * Creates an `Encoder` by filling in the missing `encode` function using the provided `write` function and
401
+ * either the `fixedSize` property (for {@link FixedSizeEncoder | FixedSizeEncoders}) or
402
+ * the `getSizeFromValue` function (for {@link VariableSizeEncoder | VariableSizeEncoders}).
403
+ *
404
+ * Instead of manually implementing `encode`, this utility leverages the existing `write` function
405
+ * and the size helpers to generate a complete encoder. The provided `encode` method will allocate
406
+ * a new `Uint8Array` of the correct size and use `write` to populate it.
407
+ *
408
+ * @typeParam TFrom - The type of the value to encode.
409
+ * @typeParam TSize - The fixed size of the encoded value in bytes (for fixed-size encoders).
410
+ *
411
+ * @param encoder - An encoder object that implements `write`, but not `encode`.
412
+ * - If the encoder has a `fixedSize` property, it is treated as a {@link FixedSizeEncoder}.
413
+ * - Otherwise, it is treated as a {@link VariableSizeEncoder}.
414
+ *
415
+ * @returns A fully functional `Encoder` with both `write` and `encode` methods.
416
+ *
417
+ * @example
418
+ * Creating a custom fixed-size encoder.
419
+ * ```ts
420
+ * const encoder = createEncoder({
421
+ * fixedSize: 4,
422
+ * write: (value: number, bytes, offset) => {
423
+ * bytes.set(new Uint8Array([value]), offset);
424
+ * return offset + 4;
425
+ * },
426
+ * });
427
+ *
428
+ * const bytes = encoder.encode(42);
429
+ * // 0x2a000000
430
+ * ```
431
+ *
432
+ * @example
433
+ * Creating a custom variable-size encoder:
434
+ * ```ts
435
+ * const encoder = createEncoder({
436
+ * getSizeFromValue: (value: string) => value.length,
437
+ * write: (value: string, bytes, offset) => {
438
+ * const encodedValue = new TextEncoder().encode(value);
439
+ * bytes.set(encodedValue, offset);
440
+ * return offset + encodedValue.length;
441
+ * },
442
+ * });
443
+ *
444
+ * const bytes = encoder.encode("hello");
445
+ * // 0x68656c6c6f
446
+ * ```
447
+ *
448
+ * @remarks
449
+ * Note that, while `createEncoder` is useful for defining more complex encoders, it is more common to compose
450
+ * encoders together using the various helpers and primitives of the `@solana/codecs` package.
451
+ *
452
+ * Here are some alternative examples using codec primitives instead of `createEncoder`.
453
+ *
454
+ * ```ts
455
+ * // Fixed-size encoder for unsigned 32-bit integers.
456
+ * const encoder = getU32Encoder();
457
+ * const bytes = encoder.encode(42);
458
+ * // 0x2a000000
459
+ *
460
+ * // Variable-size encoder for 32-bytes prefixed UTF-8 strings.
461
+ * const encoder = addEncoderSizePrefix(getUtf8Encoder(), getU32Encoder());
462
+ * const bytes = encoder.encode("hello");
463
+ * // 0x0500000068656c6c6f
464
+ *
465
+ * // Variable-size encoder for custom objects.
466
+ * type Person = { name: string; age: number };
467
+ * const encoder: Encoder<Person> = getStructEncoder([
468
+ * ['name', addEncoderSizePrefix(getUtf8Encoder(), getU32Encoder())],
469
+ * ['age', getU32Encoder()],
470
+ * ]);
471
+ * const bytes = encoder.encode({ name: "Bob", age: 42 });
472
+ * // 0x03000000426f622a000000
473
+ * ```
474
+ *
475
+ * @see {@link Encoder}
476
+ * @see {@link FixedSizeEncoder}
477
+ * @see {@link VariableSizeEncoder}
478
+ * @see {@link getStructEncoder}
479
+ * @see {@link getU32Encoder}
480
+ * @see {@link getUtf8Encoder}
481
+ * @see {@link addEncoderSizePrefix}
482
+ */
483
+ export function createEncoder<TFrom, TSize extends number>(
484
+ encoder: Omit<FixedSizeEncoder<TFrom, TSize>, 'encode'>,
485
+ ): FixedSizeEncoder<TFrom, TSize>;
486
+ export function createEncoder<TFrom>(encoder: Omit<VariableSizeEncoder<TFrom>, 'encode'>): VariableSizeEncoder<TFrom>;
487
+ export function createEncoder<TFrom>(
488
+ encoder: Omit<FixedSizeEncoder<TFrom>, 'encode'> | Omit<VariableSizeEncoder<TFrom>, 'encode'>,
489
+ ): Encoder<TFrom>;
490
+ export function createEncoder<TFrom>(
491
+ encoder: Omit<FixedSizeEncoder<TFrom>, 'encode'> | Omit<VariableSizeEncoder<TFrom>, 'encode'>,
492
+ ): Encoder<TFrom> {
493
+ return Object.freeze({
494
+ ...encoder,
495
+ encode: value => {
496
+ const bytes = new Uint8Array(getEncodedSize(value, encoder));
497
+ encoder.write(value, bytes, 0);
498
+ return bytes;
499
+ },
500
+ });
501
+ }
502
+
503
+ /**
504
+ * Creates a `Decoder` by filling in the missing `decode` function using the provided `read` function.
505
+ *
506
+ * Instead of manually implementing `decode`, this utility leverages the existing `read` function
507
+ * and the size properties to generate a complete decoder. The provided `decode` method will read
508
+ * from a `Uint8Array` at the given offset and return the decoded value.
509
+ *
510
+ * If the `fixedSize` property is provided, a {@link FixedSizeDecoder} will be created, otherwise
511
+ * a {@link VariableSizeDecoder} will be created.
512
+ *
513
+ * @typeParam TTo - The type of the decoded value.
514
+ * @typeParam TSize - The fixed size of the encoded value in bytes (for fixed-size decoders).
515
+ *
516
+ * @param decoder - A decoder object that implements `read`, but not `decode`.
517
+ * - If the decoder has a `fixedSize` property, it is treated as a {@link FixedSizeDecoder}.
518
+ * - Otherwise, it is treated as a {@link VariableSizeDecoder}.
519
+ *
520
+ * @returns A fully functional `Decoder` with both `read` and `decode` methods.
521
+ *
522
+ * @example
523
+ * Creating a custom fixed-size decoder.
524
+ * ```ts
525
+ * const decoder = createDecoder({
526
+ * fixedSize: 4,
527
+ * read: (bytes, offset) => {
528
+ * const value = bytes[offset];
529
+ * return [value, offset + 4];
530
+ * },
531
+ * });
532
+ *
533
+ * const value = decoder.decode(new Uint8Array([42, 0, 0, 0]));
534
+ * // 42
535
+ * ```
536
+ *
537
+ * @example
538
+ * Creating a custom variable-size decoder:
539
+ * ```ts
540
+ * const decoder = createDecoder({
541
+ * read: (bytes, offset) => {
542
+ * const decodedValue = new TextDecoder().decode(bytes.subarray(offset));
543
+ * return [decodedValue, bytes.length];
544
+ * },
545
+ * });
546
+ *
547
+ * const value = decoder.decode(new Uint8Array([104, 101, 108, 108, 111]));
548
+ * // "hello"
549
+ * ```
550
+ *
551
+ * @remarks
552
+ * Note that, while `createDecoder` is useful for defining more complex decoders, it is more common to compose
553
+ * decoders together using the various helpers and primitives of the `@solana/codecs` package.
554
+ *
555
+ * Here are some alternative examples using codec primitives instead of `createDecoder`.
556
+ *
557
+ * ```ts
558
+ * // Fixed-size decoder for unsigned 32-bit integers.
559
+ * const decoder = getU32Decoder();
560
+ * const value = decoder.decode(new Uint8Array([42, 0, 0, 0]));
561
+ * // 42
562
+ *
563
+ * // Variable-size decoder for 32-bytes prefixed UTF-8 strings.
564
+ * const decoder = addDecoderSizePrefix(getUtf8Decoder(), getU32Decoder());
565
+ * const value = decoder.decode(new Uint8Array([5, 0, 0, 0, 104, 101, 108, 108, 111]));
566
+ * // "hello"
567
+ *
568
+ * // Variable-size decoder for custom objects.
569
+ * type Person = { name: string; age: number };
570
+ * const decoder: Decoder<Person> = getStructDecoder([
571
+ * ['name', addDecoderSizePrefix(getUtf8Decoder(), getU32Decoder())],
572
+ * ['age', getU32Decoder()],
573
+ * ]);
574
+ * const value = decoder.decode(new Uint8Array([3, 0, 0, 0, 66, 111, 98, 42, 0, 0, 0]));
575
+ * // { name: "Bob", age: 42 }
576
+ * ```
577
+ *
578
+ * @see {@link Decoder}
579
+ * @see {@link FixedSizeDecoder}
580
+ * @see {@link VariableSizeDecoder}
581
+ * @see {@link getStructDecoder}
582
+ * @see {@link getU32Decoder}
583
+ * @see {@link getUtf8Decoder}
584
+ * @see {@link addDecoderSizePrefix}
585
+ */
586
+ export function createDecoder<TTo, TSize extends number>(
587
+ decoder: Omit<FixedSizeDecoder<TTo, TSize>, 'decode'>,
588
+ ): FixedSizeDecoder<TTo, TSize>;
589
+ export function createDecoder<TTo>(decoder: Omit<VariableSizeDecoder<TTo>, 'decode'>): VariableSizeDecoder<TTo>;
590
+ export function createDecoder<TTo>(
591
+ decoder: Omit<FixedSizeDecoder<TTo>, 'decode'> | Omit<VariableSizeDecoder<TTo>, 'decode'>,
592
+ ): Decoder<TTo>;
593
+ export function createDecoder<TTo>(
594
+ decoder: Omit<FixedSizeDecoder<TTo>, 'decode'> | Omit<VariableSizeDecoder<TTo>, 'decode'>,
595
+ ): Decoder<TTo> {
596
+ return Object.freeze({
597
+ ...decoder,
598
+ decode: (bytes, offset = 0) => decoder.read(bytes, offset)[0],
599
+ });
600
+ }
601
+
602
+ /**
603
+ * Creates a `Codec` by filling in the missing `encode` and `decode` functions using the provided `write` and `read` functions.
604
+ *
605
+ * This utility combines the behavior of {@link createEncoder} and {@link createDecoder} to produce a fully functional `Codec`.
606
+ * The `encode` method is derived from the `write` function, while the `decode` method is derived from the `read` function.
607
+ *
608
+ * If the `fixedSize` property is provided, a {@link FixedSizeCodec} will be created, otherwise
609
+ * a {@link VariableSizeCodec} will be created.
610
+ *
611
+ * @typeParam TFrom - The type of the value to encode.
612
+ * @typeParam TTo - The type of the decoded value.
613
+ * @typeParam TSize - The fixed size of the encoded value in bytes (for fixed-size codecs).
614
+ *
615
+ * @param codec - A codec object that implements `write` and `read`, but not `encode` or `decode`.
616
+ * - If the codec has a `fixedSize` property, it is treated as a {@link FixedSizeCodec}.
617
+ * - Otherwise, it is treated as a {@link VariableSizeCodec}.
618
+ *
619
+ * @returns A fully functional `Codec` with `write`, `read`, `encode`, and `decode` methods.
620
+ *
621
+ * @example
622
+ * Creating a custom fixed-size codec.
623
+ * ```ts
624
+ * const codec = createCodec({
625
+ * fixedSize: 4,
626
+ * read: (bytes, offset) => {
627
+ * const value = bytes[offset];
628
+ * return [value, offset + 4];
629
+ * },
630
+ * write: (value: number, bytes, offset) => {
631
+ * bytes.set(new Uint8Array([value]), offset);
632
+ * return offset + 4;
633
+ * },
634
+ * });
635
+ *
636
+ * const bytes = codec.encode(42);
637
+ * // 0x2a000000
638
+ * const value = codec.decode(bytes);
639
+ * // 42
640
+ * ```
641
+ *
642
+ * @example
643
+ * Creating a custom variable-size codec:
644
+ * ```ts
645
+ * const codec = createCodec({
646
+ * getSizeFromValue: (value: string) => value.length,
647
+ * read: (bytes, offset) => {
648
+ * const decodedValue = new TextDecoder().decode(bytes.subarray(offset));
649
+ * return [decodedValue, bytes.length];
650
+ * },
651
+ * write: (value: string, bytes, offset) => {
652
+ * const encodedValue = new TextEncoder().encode(value);
653
+ * bytes.set(encodedValue, offset);
654
+ * return offset + encodedValue.length;
655
+ * },
656
+ * });
657
+ *
658
+ * const bytes = codec.encode("hello");
659
+ * // 0x68656c6c6f
660
+ * const value = codec.decode(bytes);
661
+ * // "hello"
662
+ * ```
663
+ *
664
+ * @remarks
665
+ * This function effectively combines the behavior of {@link createEncoder} and {@link createDecoder}.
666
+ * If you only need to encode or decode (but not both), consider using those functions instead.
667
+ *
668
+ * Here are some alternative examples using codec primitives instead of `createCodec`.
669
+ *
670
+ * ```ts
671
+ * // Fixed-size codec for unsigned 32-bit integers.
672
+ * const codec = getU32Codec();
673
+ * const bytes = codec.encode(42);
674
+ * // 0x2a000000
675
+ * const value = codec.decode(bytes);
676
+ * // 42
677
+ *
678
+ * // Variable-size codec for 32-bytes prefixed UTF-8 strings.
679
+ * const codec = addCodecSizePrefix(getUtf8Codec(), getU32Codec());
680
+ * const bytes = codec.encode("hello");
681
+ * // 0x0500000068656c6c6f
682
+ * const value = codec.decode(bytes);
683
+ * // "hello"
684
+ *
685
+ * // Variable-size codec for custom objects.
686
+ * type Person = { name: string; age: number };
687
+ * const codec: Codec<PersonInput, Person> = getStructCodec([
688
+ * ['name', addCodecSizePrefix(getUtf8Codec(), getU32Codec())],
689
+ * ['age', getU32Codec()],
690
+ * ]);
691
+ * const bytes = codec.encode({ name: "Bob", age: 42 });
692
+ * // 0x03000000426f622a000000
693
+ * const value = codec.decode(bytes);
694
+ * // { name: "Bob", age: 42 }
695
+ * ```
696
+ *
697
+ * @see {@link Codec}
698
+ * @see {@link FixedSizeCodec}
699
+ * @see {@link VariableSizeCodec}
700
+ * @see {@link createEncoder}
701
+ * @see {@link createDecoder}
702
+ * @see {@link getStructCodec}
703
+ * @see {@link getU32Codec}
704
+ * @see {@link getUtf8Codec}
705
+ * @see {@link addCodecSizePrefix}
706
+ */
707
+ export function createCodec<TFrom, TTo extends TFrom = TFrom, TSize extends number = number>(
708
+ codec: Omit<FixedSizeCodec<TFrom, TTo, TSize>, 'decode' | 'encode'>,
709
+ ): FixedSizeCodec<TFrom, TTo, TSize>;
710
+ export function createCodec<TFrom, TTo extends TFrom = TFrom>(
711
+ codec: Omit<VariableSizeCodec<TFrom, TTo>, 'decode' | 'encode'>,
712
+ ): VariableSizeCodec<TFrom, TTo>;
713
+ export function createCodec<TFrom, TTo extends TFrom = TFrom>(
714
+ codec:
715
+ | Omit<FixedSizeCodec<TFrom, TTo>, 'decode' | 'encode'>
716
+ | Omit<VariableSizeCodec<TFrom, TTo>, 'decode' | 'encode'>,
717
+ ): Codec<TFrom, TTo>;
718
+ export function createCodec<TFrom, TTo extends TFrom = TFrom>(
719
+ codec:
720
+ | Omit<FixedSizeCodec<TFrom, TTo>, 'decode' | 'encode'>
721
+ | Omit<VariableSizeCodec<TFrom, TTo>, 'decode' | 'encode'>,
722
+ ): Codec<TFrom, TTo> {
723
+ return Object.freeze({
724
+ ...codec,
725
+ decode: (bytes, offset = 0) => codec.read(bytes, offset)[0],
726
+ encode: value => {
727
+ const bytes = new Uint8Array(getEncodedSize(value, codec));
728
+ codec.write(value, bytes, 0);
729
+ return bytes;
730
+ },
731
+ });
732
+ }
733
+
734
+ /**
735
+ * Determines whether the given codec, encoder, or decoder is fixed-size.
736
+ *
737
+ * A fixed-size object is identified by the presence of a `fixedSize` property.
738
+ * If this property exists, the object is considered a {@link FixedSizeCodec},
739
+ * {@link FixedSizeEncoder}, or {@link FixedSizeDecoder}.
740
+ * Otherwise, it is assumed to be a {@link VariableSizeCodec},
741
+ * {@link VariableSizeEncoder}, or {@link VariableSizeDecoder}.
742
+ *
743
+ * @typeParam TFrom - The type of the value to encode.
744
+ * @typeParam TTo - The type of the decoded value.
745
+ * @typeParam TSize - The fixed size of the encoded value in bytes.
746
+ * @returns `true` if the object is fixed-size, `false` otherwise.
747
+ *
748
+ * @example
749
+ * Checking a fixed-size encoder.
750
+ * ```ts
751
+ * const encoder = getU32Encoder();
752
+ * isFixedSize(encoder); // true
753
+ * ```
754
+ *
755
+ * @example
756
+ * Checking a variable-size encoder.
757
+ * ```ts
758
+ * const encoder = addEncoderSizePrefix(getUtf8Encoder(), getU32Encoder());
759
+ * isFixedSize(encoder); // false
760
+ * ```
761
+ *
762
+ * @remarks
763
+ * This function is commonly used to distinguish between fixed-size and variable-size objects at runtime.
764
+ * If you need to enforce this distinction with type assertions, consider using {@link assertIsFixedSize}.
765
+ *
766
+ * @see {@link assertIsFixedSize}
767
+ */
768
+ export function isFixedSize<TFrom, TSize extends number>(
769
+ encoder: FixedSizeEncoder<TFrom, TSize> | VariableSizeEncoder<TFrom>,
770
+ ): encoder is FixedSizeEncoder<TFrom, TSize>;
771
+ export function isFixedSize<TTo, TSize extends number>(
772
+ decoder: FixedSizeDecoder<TTo, TSize> | VariableSizeDecoder<TTo>,
773
+ ): decoder is FixedSizeDecoder<TTo, TSize>;
774
+ export function isFixedSize<TFrom, TTo extends TFrom, TSize extends number>(
775
+ codec: FixedSizeCodec<TFrom, TTo, TSize> | VariableSizeCodec<TFrom, TTo>,
776
+ ): codec is FixedSizeCodec<TFrom, TTo, TSize>;
777
+ export function isFixedSize<TSize extends number>(
778
+ codec: { fixedSize: TSize } | { maxSize?: number },
779
+ ): codec is { fixedSize: TSize };
780
+ export function isFixedSize(codec: { fixedSize: number } | { maxSize?: number }): codec is { fixedSize: number } {
781
+ return 'fixedSize' in codec && typeof codec.fixedSize === 'number';
782
+ }
783
+
784
+ /**
785
+ * Asserts that the given codec, encoder, or decoder is fixed-size.
786
+ *
787
+ * If the object is not fixed-size (i.e., it lacks a `fixedSize` property),
788
+ * this function throws a {@link SolanaError} with the code `SOLANA_ERROR__CODECS__EXPECTED_FIXED_LENGTH`.
789
+ *
790
+ * @typeParam TFrom - The type of the value to encode.
791
+ * @typeParam TTo - The type of the decoded value.
792
+ * @typeParam TSize - The fixed size of the encoded value in bytes.
793
+ * @throws {SolanaError} If the object is not fixed-size.
794
+ *
795
+ * @example
796
+ * Asserting a fixed-size encoder.
797
+ * ```ts
798
+ * const encoder = getU32Encoder();
799
+ * assertIsFixedSize(encoder); // Passes
800
+ * ```
801
+ *
802
+ * @example
803
+ * Attempting to assert a variable-size encoder.
804
+ * ```ts
805
+ * const encoder = addEncoderSizePrefix(getUtf8Encoder(), getU32Encoder());
806
+ * assertIsFixedSize(encoder); // Throws SolanaError
807
+ * ```
808
+ *
809
+ * @remarks
810
+ * This function is the assertion-based counterpart of {@link isFixedSize}.
811
+ * If you only need to check whether an object is fixed-size without throwing an error, use {@link isFixedSize} instead.
812
+ *
813
+ * @see {@link isFixedSize}
814
+ */
815
+ export function assertIsFixedSize<TFrom, TSize extends number>(
816
+ encoder: FixedSizeEncoder<TFrom, TSize> | VariableSizeEncoder<TFrom>,
817
+ ): asserts encoder is FixedSizeEncoder<TFrom, TSize>;
818
+ export function assertIsFixedSize<TTo, TSize extends number>(
819
+ decoder: FixedSizeDecoder<TTo, TSize> | VariableSizeDecoder<TTo>,
820
+ ): asserts decoder is FixedSizeDecoder<TTo, TSize>;
821
+ export function assertIsFixedSize<TFrom, TTo extends TFrom, TSize extends number>(
822
+ codec: FixedSizeCodec<TFrom, TTo, TSize> | VariableSizeCodec<TFrom, TTo>,
823
+ ): asserts codec is FixedSizeCodec<TFrom, TTo, TSize>;
824
+ export function assertIsFixedSize<TSize extends number>(
825
+ codec: { fixedSize: TSize } | { maxSize?: number },
826
+ ): asserts codec is { fixedSize: TSize };
827
+ export function assertIsFixedSize(
828
+ codec: { fixedSize: number } | { maxSize?: number },
829
+ ): asserts codec is { fixedSize: number } {
830
+ if (!isFixedSize(codec)) {
831
+ throw new SolanaError(SOLANA_ERROR__CODECS__EXPECTED_FIXED_LENGTH);
832
+ }
833
+ }
834
+
835
+ /**
836
+ * Determines whether the given codec, encoder, or decoder is variable-size.
837
+ *
838
+ * A variable-size object is identified by the absence of a `fixedSize` property.
839
+ * If this property is missing, the object is considered a {@link VariableSizeCodec},
840
+ * {@link VariableSizeEncoder}, or {@link VariableSizeDecoder}.
841
+ *
842
+ * @typeParam TFrom - The type of the value to encode.
843
+ * @typeParam TTo - The type of the decoded value.
844
+ * @typeParam TSize - The fixed size of the encoded value in bytes.
845
+ * @returns `true` if the object is variable-size, `false` otherwise.
846
+ *
847
+ * @example
848
+ * Checking a variable-size encoder.
849
+ * ```ts
850
+ * const encoder = addEncoderSizePrefix(getUtf8Encoder(), getU32Encoder());
851
+ * isVariableSize(encoder); // true
852
+ * ```
853
+ *
854
+ * @example
855
+ * Checking a fixed-size encoder.
856
+ * ```ts
857
+ * const encoder = getU32Encoder();
858
+ * isVariableSize(encoder); // false
859
+ * ```
860
+ *
861
+ * @remarks
862
+ * This function is the inverse of {@link isFixedSize}.
863
+ *
864
+ * @see {@link isFixedSize}
865
+ * @see {@link assertIsVariableSize}
866
+ */
867
+ export function isVariableSize<TFrom>(encoder: Encoder<TFrom>): encoder is VariableSizeEncoder<TFrom>;
868
+ export function isVariableSize<TTo>(decoder: Decoder<TTo>): decoder is VariableSizeDecoder<TTo>;
869
+ export function isVariableSize<TFrom, TTo extends TFrom>(
870
+ codec: Codec<TFrom, TTo>,
871
+ ): codec is VariableSizeCodec<TFrom, TTo>;
872
+ export function isVariableSize(codec: { fixedSize: number } | { maxSize?: number }): codec is { maxSize?: number };
873
+ export function isVariableSize(codec: { fixedSize: number } | { maxSize?: number }): codec is { maxSize?: number } {
874
+ return !isFixedSize(codec);
875
+ }
876
+
877
+ /**
878
+ * Asserts that the given codec, encoder, or decoder is variable-size.
879
+ *
880
+ * If the object is not variable-size (i.e., it has a `fixedSize` property),
881
+ * this function throws a {@link SolanaError} with the code `SOLANA_ERROR__CODECS__EXPECTED_VARIABLE_LENGTH`.
882
+ *
883
+ * @typeParam TFrom - The type of the value to encode.
884
+ * @typeParam TTo - The type of the decoded value.
885
+ * @typeParam TSize - The fixed size of the encoded value in bytes.
886
+ * @throws {SolanaError} If the object is not variable-size.
887
+ *
888
+ * @example
889
+ * Asserting a variable-size encoder.
890
+ * ```ts
891
+ * const encoder = addEncoderSizePrefix(getUtf8Encoder(), getU32Encoder());
892
+ * assertIsVariableSize(encoder); // Passes
893
+ * ```
894
+ *
895
+ * @example
896
+ * Attempting to assert a fixed-size encoder.
897
+ * ```ts
898
+ * const encoder = getU32Encoder();
899
+ * assertIsVariableSize(encoder); // Throws SolanaError
900
+ * ```
901
+ *
902
+ * @remarks
903
+ * This function is the assertion-based counterpart of {@link isVariableSize}.
904
+ * If you only need to check whether an object is variable-size without throwing an error, use {@link isVariableSize} instead.
905
+ *
906
+ * Also note that this function is the inverse of {@link assertIsFixedSize}.
907
+ *
908
+ * @see {@link isVariableSize}
909
+ * @see {@link assertIsFixedSize}
910
+ */
911
+ export function assertIsVariableSize<TFrom>(encoder: Encoder<TFrom>): asserts encoder is VariableSizeEncoder<TFrom>;
912
+ export function assertIsVariableSize<TTo>(decoder: Decoder<TTo>): asserts decoder is VariableSizeDecoder<TTo>;
913
+ export function assertIsVariableSize<TFrom, TTo extends TFrom>(
914
+ codec: Codec<TFrom, TTo>,
915
+ ): asserts codec is VariableSizeCodec<TFrom, TTo>;
916
+ export function assertIsVariableSize(
917
+ codec: { fixedSize: number } | { maxSize?: number },
918
+ ): asserts codec is { maxSize?: number };
919
+ export function assertIsVariableSize(
920
+ codec: { fixedSize: number } | { maxSize?: number },
921
+ ): asserts codec is { maxSize?: number } {
922
+ if (!isVariableSize(codec)) {
923
+ throw new SolanaError(SOLANA_ERROR__CODECS__EXPECTED_VARIABLE_LENGTH);
924
+ }
925
+ }