@haneullabs/bcs 0.1.0
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/CHANGELOG.md +388 -0
- package/README.md +358 -0
- package/dist/cjs/bcs-type.d.ts +127 -0
- package/dist/cjs/bcs-type.js +386 -0
- package/dist/cjs/bcs-type.js.map +7 -0
- package/dist/cjs/bcs.d.ts +175 -0
- package/dist/cjs/bcs.js +406 -0
- package/dist/cjs/bcs.js.map +7 -0
- package/dist/cjs/index.d.ts +22 -0
- package/dist/cjs/index.js +59 -0
- package/dist/cjs/index.js.map +7 -0
- package/dist/cjs/package.json +5 -0
- package/dist/cjs/reader.d.ts +92 -0
- package/dist/cjs/reader.js +136 -0
- package/dist/cjs/reader.js.map +7 -0
- package/dist/cjs/types.d.ts +28 -0
- package/dist/cjs/types.js +17 -0
- package/dist/cjs/types.js.map +7 -0
- package/dist/cjs/uleb.d.ts +5 -0
- package/dist/cjs/uleb.js +66 -0
- package/dist/cjs/uleb.js.map +7 -0
- package/dist/cjs/utils.d.ts +18 -0
- package/dist/cjs/utils.js +74 -0
- package/dist/cjs/utils.js.map +7 -0
- package/dist/cjs/writer.d.ts +117 -0
- package/dist/cjs/writer.js +196 -0
- package/dist/cjs/writer.js.map +7 -0
- package/dist/esm/bcs-type.d.ts +127 -0
- package/dist/esm/bcs-type.js +366 -0
- package/dist/esm/bcs-type.js.map +7 -0
- package/dist/esm/bcs.d.ts +175 -0
- package/dist/esm/bcs.js +397 -0
- package/dist/esm/bcs.js.map +7 -0
- package/dist/esm/index.d.ts +22 -0
- package/dist/esm/index.js +46 -0
- package/dist/esm/index.js.map +7 -0
- package/dist/esm/package.json +5 -0
- package/dist/esm/reader.d.ts +92 -0
- package/dist/esm/reader.js +116 -0
- package/dist/esm/reader.js.map +7 -0
- package/dist/esm/types.d.ts +28 -0
- package/dist/esm/types.js +1 -0
- package/dist/esm/types.js.map +7 -0
- package/dist/esm/uleb.d.ts +5 -0
- package/dist/esm/uleb.js +46 -0
- package/dist/esm/uleb.js.map +7 -0
- package/dist/esm/utils.d.ts +18 -0
- package/dist/esm/utils.js +54 -0
- package/dist/esm/utils.js.map +7 -0
- package/dist/esm/writer.d.ts +117 -0
- package/dist/esm/writer.js +176 -0
- package/dist/esm/writer.js.map +7 -0
- package/dist/tsconfig.esm.tsbuildinfo +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +73 -0
- package/src/bcs-type.ts +531 -0
- package/src/bcs.ts +543 -0
- package/src/index.ts +82 -0
- package/src/reader.ts +156 -0
- package/src/types.ts +52 -0
- package/src/uleb.ts +61 -0
- package/src/utils.ts +75 -0
- package/src/writer.ts +222 -0
package/src/bcs-type.ts
ADDED
|
@@ -0,0 +1,531 @@
|
|
|
1
|
+
// Copyright (c) Mysten Labs, Inc.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import { fromBase58, fromBase64, toBase58, toBase64, fromHex, toHex } from '@haneullabs/utils';
|
|
5
|
+
import { BcsReader } from './reader.js';
|
|
6
|
+
import { ulebEncode } from './uleb.js';
|
|
7
|
+
import type { BcsWriterOptions } from './writer.js';
|
|
8
|
+
import { BcsWriter } from './writer.js';
|
|
9
|
+
import type { EnumInputShape, EnumOutputShape, JoinString } from './types.js';
|
|
10
|
+
|
|
11
|
+
export interface BcsTypeOptions<T, Input = T, Name extends string = string> {
|
|
12
|
+
name?: Name;
|
|
13
|
+
validate?: (value: Input) => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class BcsType<T, Input = T, const Name extends string = string> {
|
|
17
|
+
$inferType!: T;
|
|
18
|
+
$inferInput!: Input;
|
|
19
|
+
name: Name;
|
|
20
|
+
read: (reader: BcsReader) => T;
|
|
21
|
+
serializedSize: (value: Input, options?: BcsWriterOptions) => number | null;
|
|
22
|
+
validate: (value: Input) => void;
|
|
23
|
+
#write: (value: Input, writer: BcsWriter) => void;
|
|
24
|
+
#serialize: (value: Input, options?: BcsWriterOptions) => Uint8Array<ArrayBuffer>;
|
|
25
|
+
|
|
26
|
+
constructor(
|
|
27
|
+
options: {
|
|
28
|
+
name: Name;
|
|
29
|
+
read: (reader: BcsReader) => T;
|
|
30
|
+
write: (value: Input, writer: BcsWriter) => void;
|
|
31
|
+
serialize?: (value: Input, options?: BcsWriterOptions) => Uint8Array<ArrayBuffer>;
|
|
32
|
+
serializedSize?: (value: Input) => number | null;
|
|
33
|
+
validate?: (value: Input) => void;
|
|
34
|
+
} & BcsTypeOptions<T, Input, Name>,
|
|
35
|
+
) {
|
|
36
|
+
this.name = options.name;
|
|
37
|
+
this.read = options.read;
|
|
38
|
+
this.serializedSize = options.serializedSize ?? (() => null);
|
|
39
|
+
this.#write = options.write;
|
|
40
|
+
this.#serialize =
|
|
41
|
+
options.serialize ??
|
|
42
|
+
((value, options) => {
|
|
43
|
+
const writer = new BcsWriter({
|
|
44
|
+
initialSize: this.serializedSize(value) ?? undefined,
|
|
45
|
+
...options,
|
|
46
|
+
});
|
|
47
|
+
this.#write(value, writer);
|
|
48
|
+
return writer.toBytes();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
this.validate = options.validate ?? (() => {});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
write(value: Input, writer: BcsWriter) {
|
|
55
|
+
this.validate(value);
|
|
56
|
+
this.#write(value, writer);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
serialize(value: Input, options?: BcsWriterOptions) {
|
|
60
|
+
this.validate(value);
|
|
61
|
+
return new SerializedBcs(this, this.#serialize(value, options));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
parse(bytes: Uint8Array): T {
|
|
65
|
+
const reader = new BcsReader(bytes);
|
|
66
|
+
return this.read(reader);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
fromHex(hex: string) {
|
|
70
|
+
return this.parse(fromHex(hex));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
fromBase58(b64: string) {
|
|
74
|
+
return this.parse(fromBase58(b64));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
fromBase64(b64: string) {
|
|
78
|
+
return this.parse(fromBase64(b64));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
transform<T2 = T, Input2 = Input, NewName extends string = Name>({
|
|
82
|
+
name,
|
|
83
|
+
input,
|
|
84
|
+
output,
|
|
85
|
+
validate,
|
|
86
|
+
}: {
|
|
87
|
+
input?: (val: Input2) => Input;
|
|
88
|
+
output?: (value: T) => T2;
|
|
89
|
+
} & BcsTypeOptions<T2, Input2, NewName>) {
|
|
90
|
+
return new BcsType<T2, Input2, NewName>({
|
|
91
|
+
name: (name ?? this.name) as NewName,
|
|
92
|
+
read: (reader) => (output ? output(this.read(reader)) : (this.read(reader) as never)),
|
|
93
|
+
write: (value, writer) => this.#write(input ? input(value) : (value as never), writer),
|
|
94
|
+
serializedSize: (value) => this.serializedSize(input ? input(value) : (value as never)),
|
|
95
|
+
serialize: (value, options) =>
|
|
96
|
+
this.#serialize(input ? input(value) : (value as never), options),
|
|
97
|
+
validate: (value) => {
|
|
98
|
+
validate?.(value);
|
|
99
|
+
this.validate(input ? input(value) : (value as never));
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const SERIALIZED_BCS_BRAND = Symbol.for('@haneullabs/serialized-bcs') as never;
|
|
106
|
+
export function isSerializedBcs(obj: unknown): obj is SerializedBcs<unknown> {
|
|
107
|
+
return !!obj && typeof obj === 'object' && (obj as any)[SERIALIZED_BCS_BRAND] === true;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export class SerializedBcs<T, Input = T> {
|
|
111
|
+
#schema: BcsType<T, Input>;
|
|
112
|
+
#bytes: Uint8Array<ArrayBuffer>;
|
|
113
|
+
|
|
114
|
+
// Used to brand SerializedBcs so that they can be identified, even between multiple copies
|
|
115
|
+
// of the @haneullabs/bcs package are installed
|
|
116
|
+
get [SERIALIZED_BCS_BRAND]() {
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
constructor(schema: BcsType<T, Input>, bytes: Uint8Array<ArrayBuffer>) {
|
|
121
|
+
this.#schema = schema;
|
|
122
|
+
this.#bytes = bytes;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
toBytes() {
|
|
126
|
+
return this.#bytes;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
toHex() {
|
|
130
|
+
return toHex(this.#bytes);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
toBase64() {
|
|
134
|
+
return toBase64(this.#bytes);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
toBase58() {
|
|
138
|
+
return toBase58(this.#bytes);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
parse() {
|
|
142
|
+
return this.#schema.parse(this.#bytes);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function fixedSizeBcsType<T, Input = T, const Name extends string = string>({
|
|
147
|
+
size,
|
|
148
|
+
...options
|
|
149
|
+
}: {
|
|
150
|
+
name: Name;
|
|
151
|
+
size: number;
|
|
152
|
+
read: (reader: BcsReader) => T;
|
|
153
|
+
write: (value: Input, writer: BcsWriter) => void;
|
|
154
|
+
} & BcsTypeOptions<T, Input, Name>) {
|
|
155
|
+
return new BcsType<T, Input, Name>({
|
|
156
|
+
...options,
|
|
157
|
+
serializedSize: () => size,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function uIntBcsType<const Name extends string = string>({
|
|
162
|
+
readMethod,
|
|
163
|
+
writeMethod,
|
|
164
|
+
...options
|
|
165
|
+
}: {
|
|
166
|
+
name: Name;
|
|
167
|
+
size: number;
|
|
168
|
+
readMethod: `read${8 | 16 | 32}`;
|
|
169
|
+
writeMethod: `write${8 | 16 | 32}`;
|
|
170
|
+
maxValue: number;
|
|
171
|
+
} & BcsTypeOptions<number, number, Name>) {
|
|
172
|
+
return fixedSizeBcsType<number, number, Name>({
|
|
173
|
+
...options,
|
|
174
|
+
read: (reader) => reader[readMethod](),
|
|
175
|
+
write: (value, writer) => writer[writeMethod](value),
|
|
176
|
+
validate: (value) => {
|
|
177
|
+
if (value < 0 || value > options.maxValue) {
|
|
178
|
+
throw new TypeError(
|
|
179
|
+
`Invalid ${options.name} value: ${value}. Expected value in range 0-${options.maxValue}`,
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
options.validate?.(value);
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export function bigUIntBcsType<const Name extends string = string>({
|
|
188
|
+
readMethod,
|
|
189
|
+
writeMethod,
|
|
190
|
+
...options
|
|
191
|
+
}: {
|
|
192
|
+
name: Name;
|
|
193
|
+
size: number;
|
|
194
|
+
readMethod: `read${64 | 128 | 256}`;
|
|
195
|
+
writeMethod: `write${64 | 128 | 256}`;
|
|
196
|
+
maxValue: bigint;
|
|
197
|
+
} & BcsTypeOptions<string, string | number | bigint>) {
|
|
198
|
+
return fixedSizeBcsType<string, string | number | bigint, Name>({
|
|
199
|
+
...options,
|
|
200
|
+
read: (reader) => reader[readMethod](),
|
|
201
|
+
write: (value, writer) => writer[writeMethod](BigInt(value)),
|
|
202
|
+
validate: (val) => {
|
|
203
|
+
const value = BigInt(val);
|
|
204
|
+
if (value < 0 || value > options.maxValue) {
|
|
205
|
+
throw new TypeError(
|
|
206
|
+
`Invalid ${options.name} value: ${value}. Expected value in range 0-${options.maxValue}`,
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
options.validate?.(value);
|
|
210
|
+
},
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export function dynamicSizeBcsType<T, Input = T, const Name extends string = string>({
|
|
215
|
+
serialize,
|
|
216
|
+
...options
|
|
217
|
+
}: {
|
|
218
|
+
name: Name;
|
|
219
|
+
read: (reader: BcsReader) => T;
|
|
220
|
+
serialize: (value: Input, options?: BcsWriterOptions) => Uint8Array<ArrayBuffer>;
|
|
221
|
+
} & BcsTypeOptions<T, Input>) {
|
|
222
|
+
const type = new BcsType<T, Input>({
|
|
223
|
+
...options,
|
|
224
|
+
serialize,
|
|
225
|
+
write: (value, writer) => {
|
|
226
|
+
for (const byte of type.serialize(value).toBytes()) {
|
|
227
|
+
writer.write8(byte);
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
return type;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export function stringLikeBcsType<const Name extends string = string>({
|
|
236
|
+
toBytes,
|
|
237
|
+
fromBytes,
|
|
238
|
+
...options
|
|
239
|
+
}: {
|
|
240
|
+
name: Name;
|
|
241
|
+
toBytes: (value: string) => Uint8Array;
|
|
242
|
+
fromBytes: (bytes: Uint8Array) => string;
|
|
243
|
+
serializedSize?: (value: string) => number | null;
|
|
244
|
+
} & BcsTypeOptions<string, string, Name>) {
|
|
245
|
+
return new BcsType<string, string, Name>({
|
|
246
|
+
...options,
|
|
247
|
+
read: (reader) => {
|
|
248
|
+
const length = reader.readULEB();
|
|
249
|
+
const bytes = reader.readBytes(length);
|
|
250
|
+
|
|
251
|
+
return fromBytes(bytes);
|
|
252
|
+
},
|
|
253
|
+
write: (hex, writer) => {
|
|
254
|
+
const bytes = toBytes(hex);
|
|
255
|
+
writer.writeULEB(bytes.length);
|
|
256
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
257
|
+
writer.write8(bytes[i]);
|
|
258
|
+
}
|
|
259
|
+
},
|
|
260
|
+
serialize: (value) => {
|
|
261
|
+
const bytes = toBytes(value);
|
|
262
|
+
const size = ulebEncode(bytes.length);
|
|
263
|
+
const result = new Uint8Array(size.length + bytes.length);
|
|
264
|
+
result.set(size, 0);
|
|
265
|
+
result.set(bytes, size.length);
|
|
266
|
+
|
|
267
|
+
return result;
|
|
268
|
+
},
|
|
269
|
+
validate: (value) => {
|
|
270
|
+
if (typeof value !== 'string') {
|
|
271
|
+
throw new TypeError(`Invalid ${options.name} value: ${value}. Expected string`);
|
|
272
|
+
}
|
|
273
|
+
options.validate?.(value);
|
|
274
|
+
},
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export function lazyBcsType<T, Input>(cb: () => BcsType<T, Input>) {
|
|
279
|
+
let lazyType: BcsType<T, Input> | null = null;
|
|
280
|
+
function getType() {
|
|
281
|
+
if (!lazyType) {
|
|
282
|
+
lazyType = cb();
|
|
283
|
+
}
|
|
284
|
+
return lazyType;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return new BcsType<T, Input>({
|
|
288
|
+
name: 'lazy' as never,
|
|
289
|
+
read: (data) => getType().read(data),
|
|
290
|
+
serializedSize: (value) => getType().serializedSize(value),
|
|
291
|
+
write: (value, writer) => getType().write(value, writer),
|
|
292
|
+
serialize: (value, options) => getType().serialize(value, options).toBytes(),
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
export interface BcsStructOptions<
|
|
297
|
+
T extends Record<string, BcsType<any>>,
|
|
298
|
+
Name extends string = string,
|
|
299
|
+
> extends Omit<
|
|
300
|
+
BcsTypeOptions<
|
|
301
|
+
{
|
|
302
|
+
[K in keyof T]: T[K] extends BcsType<infer U, any> ? U : never;
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
[K in keyof T]: T[K] extends BcsType<any, infer U> ? U : never;
|
|
306
|
+
},
|
|
307
|
+
Name
|
|
308
|
+
>,
|
|
309
|
+
'name'
|
|
310
|
+
> {
|
|
311
|
+
name: Name;
|
|
312
|
+
fields: T;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
export class BcsStruct<
|
|
316
|
+
T extends Record<string, BcsType<any>>,
|
|
317
|
+
const Name extends string = string,
|
|
318
|
+
> extends BcsType<
|
|
319
|
+
{
|
|
320
|
+
[K in keyof T]: T[K] extends BcsType<infer U, any> ? U : never;
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
[K in keyof T]: T[K] extends BcsType<any, infer U> ? U : never;
|
|
324
|
+
},
|
|
325
|
+
Name
|
|
326
|
+
> {
|
|
327
|
+
constructor({ name, fields, ...options }: BcsStructOptions<T, Name>) {
|
|
328
|
+
const canonicalOrder = Object.entries(fields);
|
|
329
|
+
|
|
330
|
+
super({
|
|
331
|
+
name,
|
|
332
|
+
serializedSize: (values) => {
|
|
333
|
+
let total = 0;
|
|
334
|
+
for (const [field, type] of canonicalOrder) {
|
|
335
|
+
const size = type.serializedSize(values[field]);
|
|
336
|
+
if (size == null) {
|
|
337
|
+
return null;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
total += size;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return total;
|
|
344
|
+
},
|
|
345
|
+
read: (reader) => {
|
|
346
|
+
const result: Record<string, unknown> = {};
|
|
347
|
+
for (const [field, type] of canonicalOrder) {
|
|
348
|
+
result[field] = type.read(reader);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return result as never;
|
|
352
|
+
},
|
|
353
|
+
write: (value, writer) => {
|
|
354
|
+
for (const [field, type] of canonicalOrder) {
|
|
355
|
+
type.write(value[field], writer);
|
|
356
|
+
}
|
|
357
|
+
},
|
|
358
|
+
...options,
|
|
359
|
+
validate: (value) => {
|
|
360
|
+
options?.validate?.(value);
|
|
361
|
+
if (typeof value !== 'object' || value == null) {
|
|
362
|
+
throw new TypeError(`Expected object, found ${typeof value}`);
|
|
363
|
+
}
|
|
364
|
+
},
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
export interface BcsEnumOptions<
|
|
370
|
+
T extends Record<string, BcsType<any> | null>,
|
|
371
|
+
Name extends string = string,
|
|
372
|
+
> extends Omit<
|
|
373
|
+
BcsTypeOptions<
|
|
374
|
+
EnumOutputShape<{
|
|
375
|
+
[K in keyof T]: T[K] extends BcsType<infer U, any, any> ? U : true;
|
|
376
|
+
}>,
|
|
377
|
+
EnumInputShape<{
|
|
378
|
+
[K in keyof T]: T[K] extends BcsType<any, infer U, any> ? U : boolean | object | null;
|
|
379
|
+
}>,
|
|
380
|
+
Name
|
|
381
|
+
>,
|
|
382
|
+
'name'
|
|
383
|
+
> {
|
|
384
|
+
name: Name;
|
|
385
|
+
fields: T;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
export class BcsEnum<
|
|
389
|
+
T extends Record<string, BcsType<any> | null>,
|
|
390
|
+
const Name extends string = string,
|
|
391
|
+
> extends BcsType<
|
|
392
|
+
EnumOutputShape<{
|
|
393
|
+
[K in keyof T]: T[K] extends BcsType<infer U, any> ? U : true;
|
|
394
|
+
}>,
|
|
395
|
+
EnumInputShape<{
|
|
396
|
+
[K in keyof T]: T[K] extends BcsType<any, infer U, any> ? U : boolean | object | null;
|
|
397
|
+
}>,
|
|
398
|
+
Name
|
|
399
|
+
> {
|
|
400
|
+
constructor({ fields, ...options }: BcsEnumOptions<T, Name>) {
|
|
401
|
+
const canonicalOrder = Object.entries(fields as object);
|
|
402
|
+
super({
|
|
403
|
+
read: (reader) => {
|
|
404
|
+
const index = reader.readULEB();
|
|
405
|
+
|
|
406
|
+
const enumEntry = canonicalOrder[index];
|
|
407
|
+
if (!enumEntry) {
|
|
408
|
+
throw new TypeError(`Unknown value ${index} for enum ${options.name}`);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const [kind, type] = enumEntry;
|
|
412
|
+
|
|
413
|
+
return {
|
|
414
|
+
[kind]: type?.read(reader) ?? true,
|
|
415
|
+
$kind: kind,
|
|
416
|
+
} as never;
|
|
417
|
+
},
|
|
418
|
+
write: (value, writer) => {
|
|
419
|
+
const [name, val] = Object.entries(value).filter(([name]) =>
|
|
420
|
+
Object.hasOwn(fields, name),
|
|
421
|
+
)[0];
|
|
422
|
+
|
|
423
|
+
for (let i = 0; i < canonicalOrder.length; i++) {
|
|
424
|
+
const [optionName, optionType] = canonicalOrder[i];
|
|
425
|
+
if (optionName === name) {
|
|
426
|
+
writer.writeULEB(i);
|
|
427
|
+
optionType?.write(val, writer);
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
},
|
|
432
|
+
...options,
|
|
433
|
+
validate: (value) => {
|
|
434
|
+
options?.validate?.(value);
|
|
435
|
+
if (typeof value !== 'object' || value == null) {
|
|
436
|
+
throw new TypeError(`Expected object, found ${typeof value}`);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const keys = Object.keys(value).filter(
|
|
440
|
+
(k) => value[k] !== undefined && Object.hasOwn(fields, k),
|
|
441
|
+
);
|
|
442
|
+
|
|
443
|
+
if (keys.length !== 1) {
|
|
444
|
+
throw new TypeError(
|
|
445
|
+
`Expected object with one key, but found ${keys.length} for type ${options.name}}`,
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const [variant] = keys;
|
|
450
|
+
|
|
451
|
+
if (!Object.hasOwn(fields, variant)) {
|
|
452
|
+
throw new TypeError(`Invalid enum variant ${variant}`);
|
|
453
|
+
}
|
|
454
|
+
},
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
export interface BcsTupleOptions<
|
|
460
|
+
T extends readonly BcsType<any>[],
|
|
461
|
+
Name extends string,
|
|
462
|
+
> extends Omit<
|
|
463
|
+
BcsTypeOptions<
|
|
464
|
+
{
|
|
465
|
+
-readonly [K in keyof T]: T[K] extends BcsType<infer T, any> ? T : never;
|
|
466
|
+
},
|
|
467
|
+
{
|
|
468
|
+
[K in keyof T]: T[K] extends BcsType<any, infer T> ? T : never;
|
|
469
|
+
},
|
|
470
|
+
Name
|
|
471
|
+
>,
|
|
472
|
+
'name'
|
|
473
|
+
> {
|
|
474
|
+
name?: Name;
|
|
475
|
+
fields: T;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
export class BcsTuple<
|
|
479
|
+
const T extends readonly BcsType<any>[],
|
|
480
|
+
const Name extends string =
|
|
481
|
+
`(${JoinString<{ [K in keyof T]: T[K] extends BcsType<any, any, infer T> ? T : never }, ', '>})`,
|
|
482
|
+
> extends BcsType<
|
|
483
|
+
{
|
|
484
|
+
-readonly [K in keyof T]: T[K] extends BcsType<infer T, any> ? T : never;
|
|
485
|
+
},
|
|
486
|
+
{
|
|
487
|
+
[K in keyof T]: T[K] extends BcsType<any, infer T> ? T : never;
|
|
488
|
+
},
|
|
489
|
+
Name
|
|
490
|
+
> {
|
|
491
|
+
constructor({ fields, name, ...options }: BcsTupleOptions<T, Name>) {
|
|
492
|
+
super({
|
|
493
|
+
name: name ?? (`(${fields.map((t) => t.name).join(', ')})` as never),
|
|
494
|
+
serializedSize: (values) => {
|
|
495
|
+
let total = 0;
|
|
496
|
+
for (let i = 0; i < fields.length; i++) {
|
|
497
|
+
const size = fields[i].serializedSize(values[i]);
|
|
498
|
+
if (size == null) {
|
|
499
|
+
return null;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
total += size;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
return total;
|
|
506
|
+
},
|
|
507
|
+
read: (reader) => {
|
|
508
|
+
const result: unknown[] = [];
|
|
509
|
+
for (const field of fields) {
|
|
510
|
+
result.push(field.read(reader));
|
|
511
|
+
}
|
|
512
|
+
return result as never;
|
|
513
|
+
},
|
|
514
|
+
write: (value, writer) => {
|
|
515
|
+
for (let i = 0; i < fields.length; i++) {
|
|
516
|
+
fields[i].write(value[i], writer);
|
|
517
|
+
}
|
|
518
|
+
},
|
|
519
|
+
...options,
|
|
520
|
+
validate: (value) => {
|
|
521
|
+
options?.validate?.(value);
|
|
522
|
+
if (!Array.isArray(value)) {
|
|
523
|
+
throw new TypeError(`Expected array, found ${typeof value}`);
|
|
524
|
+
}
|
|
525
|
+
if (value.length !== fields.length) {
|
|
526
|
+
throw new TypeError(`Expected array of length ${fields.length}, found ${value.length}`);
|
|
527
|
+
}
|
|
528
|
+
},
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
}
|