@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/package.json +6 -5
- package/src/array.ts +300 -0
- package/src/assertions.ts +16 -0
- package/src/bit-array.ts +203 -0
- package/src/boolean.ts +163 -0
- package/src/bytes.ts +115 -0
- package/src/constant.ts +135 -0
- package/src/discriminated-union.ts +384 -0
- package/src/enum-helpers.ts +102 -0
- package/src/enum.ts +309 -0
- package/src/hidden-prefix.ts +180 -0
- package/src/hidden-suffix.ts +180 -0
- package/src/index.ts +30 -0
- package/src/literal-union.ts +249 -0
- package/src/map.ts +285 -0
- package/src/nullable.ts +356 -0
- package/src/pattern-match.ts +312 -0
- package/src/predicate.ts +214 -0
- package/src/set.ts +206 -0
- package/src/struct.ts +239 -0
- package/src/tuple.ts +228 -0
- package/src/union.ts +257 -0
- package/src/unit.ts +111 -0
- package/src/utils.ts +32 -0
package/src/nullable.ts
ADDED
|
@@ -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
|
+
}
|