@ckb-ccc/core 1.22.1 → 1.23.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 +61 -0
- package/dist/advanced.d.mts +1 -1
- package/dist/advanced.mjs +1 -1
- package/dist/{advancedBarrel-Daj_LJkz.d.mts → advancedBarrel-BBbQZ2OW.d.mts} +480 -379
- package/dist/advancedBarrel-BBbQZ2OW.d.mts.map +1 -0
- package/dist/advancedBarrel.d.mts +1 -1
- package/dist/advancedBarrel.mjs +1 -1
- package/dist/barrel-Dz1WLXRX.mjs +7 -0
- package/dist/barrel-Dz1WLXRX.mjs.map +1 -0
- package/dist/barrel.d.mts +2 -2
- package/dist/barrel.mjs +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +1 -1
- package/dist/transformers-BHt442Nd.mjs +2 -0
- package/dist/transformers-BHt442Nd.mjs.map +1 -0
- package/dist.commonjs/advanced.d.ts +1 -1
- package/dist.commonjs/advanced.js +1 -1
- package/dist.commonjs/{advancedBarrel-CJL3rfuW.d.ts → advancedBarrel-BS4zFcFI.d.ts} +480 -379
- package/dist.commonjs/advancedBarrel-BS4zFcFI.d.ts.map +1 -0
- package/dist.commonjs/advancedBarrel.d.ts +1 -1
- package/dist.commonjs/advancedBarrel.js +1 -1
- package/dist.commonjs/{barrel--PaV2IZx.js → barrel-CZYID7jh.js} +10 -10
- package/dist.commonjs/barrel-CZYID7jh.js.map +1 -0
- package/dist.commonjs/barrel.d.ts +2 -2
- package/dist.commonjs/barrel.js +1 -1
- package/dist.commonjs/index.d.ts +2 -2
- package/dist.commonjs/index.js +1 -1
- package/dist.commonjs/transformers-CYUULrnt.js +2 -0
- package/dist.commonjs/transformers-CYUULrnt.js.map +1 -0
- package/package.json +1 -1
- package/src/address/index.ts +32 -4
- package/src/ckb/transaction.ts +59 -27
- package/src/client/cache/memory.advanced.ts +18 -3
- package/src/client/client.ts +41 -2
- package/src/client/clientPublicMainnet.ts +9 -2
- package/src/client/clientPublicTestnet.ts +9 -2
- package/src/client/jsonRpc/client.ts +7 -7
- package/src/codec/codec.ts +51 -27
- package/src/codec/entity.ts +26 -16
- package/src/molecule/codec.ts +437 -202
- package/dist/advancedBarrel-Daj_LJkz.d.mts.map +0 -1
- package/dist/barrel-pRBtHOAW.mjs +0 -7
- package/dist/barrel-pRBtHOAW.mjs.map +0 -1
- package/dist/transformers-lavDTtAW.mjs +0 -2
- package/dist/transformers-lavDTtAW.mjs.map +0 -1
- package/dist.commonjs/advancedBarrel-CJL3rfuW.d.ts.map +0 -1
- package/dist.commonjs/barrel--PaV2IZx.js.map +0 -1
- package/dist.commonjs/transformers-VBXQGQKe.js +0 -2
- package/dist.commonjs/transformers-VBXQGQKe.js.map +0 -1
package/src/molecule/codec.ts
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
+
Bytes,
|
|
4
5
|
bytesConcat,
|
|
5
6
|
bytesConcatTo,
|
|
6
7
|
bytesFrom,
|
|
7
8
|
BytesLike,
|
|
8
9
|
} from "../bytes/index.js";
|
|
9
10
|
import {
|
|
11
|
+
ChildContext,
|
|
10
12
|
Codec,
|
|
11
13
|
CodecLike,
|
|
14
|
+
ContextType,
|
|
12
15
|
DecodedType,
|
|
13
16
|
EncodableType,
|
|
14
17
|
} from "../codec/codec.js";
|
|
@@ -53,19 +56,123 @@ function getMessage(e: unknown): string {
|
|
|
53
56
|
return e instanceof Error ? e.message : String(e);
|
|
54
57
|
}
|
|
55
58
|
|
|
59
|
+
function isPositiveSafeInteger(val: unknown): val is number {
|
|
60
|
+
return typeof val === "number" && Number.isSafeInteger(val) && val > 0;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export type TableDecodeContext = {
|
|
64
|
+
isExtraFieldIgnored?: boolean;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Verifies a table or dynvec header and returns item boundaries.
|
|
69
|
+
* For N items/fields, returns an array of length N + 1:
|
|
70
|
+
* [offset_0, offset_1, ..., offset_{N-1}, totalSize].
|
|
71
|
+
*/
|
|
72
|
+
function verifyAndExtractOffsets(
|
|
73
|
+
value: Bytes,
|
|
74
|
+
prefix: string,
|
|
75
|
+
fieldCountConstraint?: {
|
|
76
|
+
expected: number;
|
|
77
|
+
allowExtra: boolean;
|
|
78
|
+
},
|
|
79
|
+
): number[] {
|
|
80
|
+
if (value.byteLength < 4) {
|
|
81
|
+
throw new Error(
|
|
82
|
+
`${prefix}: too short buffer, expected at least 4 bytes, but got ${value.byteLength}`,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
const totalSize = uint32From(value.subarray(0, 4));
|
|
86
|
+
if (totalSize !== value.byteLength) {
|
|
87
|
+
throw new Error(
|
|
88
|
+
`${prefix}: invalid buffer size, expected ${totalSize}, but got ${value.byteLength}`,
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (totalSize !== 4 && totalSize < 8) {
|
|
93
|
+
throw new Error(
|
|
94
|
+
`${prefix}: invalid header, buffer too short for first offset, got ${totalSize} bytes`,
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const firstOffset = totalSize === 4 ? 4 : uint32From(value.subarray(4, 8));
|
|
99
|
+
if (totalSize !== 4) {
|
|
100
|
+
if (firstOffset < 8) {
|
|
101
|
+
throw new Error(
|
|
102
|
+
`${prefix}: invalid first offset, expected at least 8, but got ${firstOffset}`,
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
if (firstOffset % 4 !== 0) {
|
|
106
|
+
throw new Error(
|
|
107
|
+
`${prefix}: invalid first offset alignment, expected multiple of 4, but got ${firstOffset}`,
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
if (firstOffset > totalSize) {
|
|
111
|
+
throw new Error(
|
|
112
|
+
`${prefix}: invalid first offset, offset ${firstOffset} exceeds total size ${totalSize}`,
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const fieldCount = (firstOffset - 4) / 4;
|
|
118
|
+
if (
|
|
119
|
+
fieldCountConstraint &&
|
|
120
|
+
(fieldCount < fieldCountConstraint.expected ||
|
|
121
|
+
(fieldCount > fieldCountConstraint.expected &&
|
|
122
|
+
!fieldCountConstraint.allowExtra))
|
|
123
|
+
) {
|
|
124
|
+
throw new Error(
|
|
125
|
+
`${prefix}: invalid field count, expected ${fieldCountConstraint.expected}, but got ${fieldCount}`,
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
if (fieldCount === 0) {
|
|
129
|
+
return [totalSize];
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const offsets = [firstOffset];
|
|
133
|
+
let previous = firstOffset;
|
|
134
|
+
for (let i = 1; i < fieldCount; i++) {
|
|
135
|
+
const current = uint32From(value.subarray(4 + i * 4, 8 + i * 4));
|
|
136
|
+
if (current > totalSize) {
|
|
137
|
+
throw new Error(
|
|
138
|
+
`${prefix}: invalid offset, offset[${i}] = ${current} exceeds total size ${totalSize}`,
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
if (current < previous) {
|
|
142
|
+
throw new Error(
|
|
143
|
+
`${prefix}: invalid offset order, offset[${i}] (${current}) is less than offset[${i - 1}] (${previous})`,
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
offsets.push(current);
|
|
147
|
+
previous = current;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
offsets.push(totalSize);
|
|
151
|
+
return offsets;
|
|
152
|
+
}
|
|
153
|
+
|
|
56
154
|
/**
|
|
57
155
|
* Vector with fixed size item codec
|
|
58
156
|
* @param itemCodec fixed-size vector item codec
|
|
59
157
|
*/
|
|
60
|
-
export function fixedItemVec<Encodable, Decoded>(
|
|
61
|
-
itemCodec: CodecLike<Encodable, Decoded>,
|
|
62
|
-
): Codec<Array<Encodable>, Array<Decoded>> {
|
|
158
|
+
export function fixedItemVec<Encodable, Decoded, Context>(
|
|
159
|
+
itemCodec: CodecLike<Encodable, Decoded, Context>,
|
|
160
|
+
): Codec<Array<Encodable>, Array<Decoded>, ContextType<typeof itemCodec>> {
|
|
63
161
|
const itemByteLength = itemCodec.byteLength;
|
|
64
162
|
if (itemByteLength === undefined) {
|
|
65
163
|
throw new Error("fixedItemVec: itemCodec requires a byte length");
|
|
66
164
|
}
|
|
165
|
+
if (!isPositiveSafeInteger(itemByteLength)) {
|
|
166
|
+
throw new Error(
|
|
167
|
+
`fixedItemVec: itemCodec byteLength must be a positive safe integer, but got ${String(itemByteLength)}`,
|
|
168
|
+
);
|
|
169
|
+
}
|
|
67
170
|
|
|
68
|
-
return Codec.from
|
|
171
|
+
return Codec.from<
|
|
172
|
+
Array<Encodable>,
|
|
173
|
+
Array<Decoded>,
|
|
174
|
+
ContextType<typeof itemCodec>
|
|
175
|
+
>({
|
|
69
176
|
encode(userDefinedItems) {
|
|
70
177
|
try {
|
|
71
178
|
const concatted: number[] = [];
|
|
@@ -115,10 +222,14 @@ export function fixedItemVec<Encodable, Decoded>(
|
|
|
115
222
|
* Vector with dynamic size item codec, you can create a recursive vector with this function
|
|
116
223
|
* @param itemCodec the vector item codec. It can be fixed-size or dynamic-size.
|
|
117
224
|
*/
|
|
118
|
-
export function dynItemVec<Encodable, Decoded>(
|
|
119
|
-
itemCodec: CodecLike<Encodable, Decoded>,
|
|
120
|
-
): Codec<Array<Encodable>, Array<Decoded>> {
|
|
121
|
-
return Codec.from
|
|
225
|
+
export function dynItemVec<Encodable, Decoded, Context>(
|
|
226
|
+
itemCodec: CodecLike<Encodable, Decoded, Context>,
|
|
227
|
+
): Codec<Array<Encodable>, Array<Decoded>, ContextType<typeof itemCodec>> {
|
|
228
|
+
return Codec.from<
|
|
229
|
+
Array<Encodable>,
|
|
230
|
+
Array<Decoded>,
|
|
231
|
+
ContextType<typeof itemCodec>
|
|
232
|
+
>({
|
|
122
233
|
encode(userDefinedItems) {
|
|
123
234
|
try {
|
|
124
235
|
let offset = 4 + userDefinedItems.length * 4;
|
|
@@ -140,28 +251,8 @@ export function dynItemVec<Encodable, Decoded>(
|
|
|
140
251
|
},
|
|
141
252
|
decode(buffer, config) {
|
|
142
253
|
const value = bytesFrom(buffer);
|
|
143
|
-
|
|
144
|
-
throw new Error(
|
|
145
|
-
`dynItemVec: too short buffer, expected at least 4 bytes, but got ${value.byteLength}`,
|
|
146
|
-
);
|
|
147
|
-
}
|
|
148
|
-
const byteLength = uint32From(value.subarray(0, 4));
|
|
149
|
-
if (byteLength !== value.byteLength) {
|
|
150
|
-
throw new Error(
|
|
151
|
-
`dynItemVec: invalid buffer size, expected ${byteLength}, but got ${value.byteLength}`,
|
|
152
|
-
);
|
|
153
|
-
}
|
|
254
|
+
const offsets = verifyAndExtractOffsets(value, "dynItemVec");
|
|
154
255
|
|
|
155
|
-
if (byteLength === 4) {
|
|
156
|
-
return [];
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
const offset = uint32From(value.subarray(4, 8));
|
|
160
|
-
const itemCount = (offset - 4) / 4;
|
|
161
|
-
const offsets = Array.from(new Array(itemCount), (_, index) =>
|
|
162
|
-
uint32From(value.subarray(4 + index * 4, 8 + index * 4)),
|
|
163
|
-
);
|
|
164
|
-
offsets.push(byteLength);
|
|
165
256
|
try {
|
|
166
257
|
const decodedArray: Array<Decoded> = [];
|
|
167
258
|
for (let index = 0; index < offsets.length - 1; index++) {
|
|
@@ -182,9 +273,9 @@ export function dynItemVec<Encodable, Decoded>(
|
|
|
182
273
|
* General vector codec, if `itemCodec` is fixed size type, it will create a fixvec codec, otherwise a dynvec codec will be created.
|
|
183
274
|
* @param itemCodec
|
|
184
275
|
*/
|
|
185
|
-
export function vector<Encodable, Decoded>(
|
|
186
|
-
itemCodec: CodecLike<Encodable, Decoded>,
|
|
187
|
-
): Codec<Array<Encodable>, Array<Decoded>> {
|
|
276
|
+
export function vector<Encodable, Decoded, Context>(
|
|
277
|
+
itemCodec: CodecLike<Encodable, Decoded, Context>,
|
|
278
|
+
): Codec<Array<Encodable>, Array<Decoded>, ContextType<typeof itemCodec>> {
|
|
188
279
|
if (itemCodec.byteLength !== undefined) {
|
|
189
280
|
return fixedItemVec(itemCodec);
|
|
190
281
|
}
|
|
@@ -198,10 +289,18 @@ export function vector<Encodable, Decoded>(
|
|
|
198
289
|
* - if it's not empty, just serialize the inner item (the size is same as the inner item's size).
|
|
199
290
|
* @param innerCodec
|
|
200
291
|
*/
|
|
201
|
-
export function option<Encodable, Decoded>(
|
|
202
|
-
innerCodec: CodecLike<Encodable, Decoded>,
|
|
203
|
-
): Codec<
|
|
204
|
-
|
|
292
|
+
export function option<Encodable, Decoded, Context>(
|
|
293
|
+
innerCodec: CodecLike<Encodable, Decoded, Context>,
|
|
294
|
+
): Codec<
|
|
295
|
+
Encodable | undefined | null,
|
|
296
|
+
Decoded | undefined,
|
|
297
|
+
ContextType<typeof innerCodec>
|
|
298
|
+
> {
|
|
299
|
+
return Codec.from<
|
|
300
|
+
Encodable | undefined | null,
|
|
301
|
+
Decoded | undefined,
|
|
302
|
+
ContextType<typeof innerCodec>
|
|
303
|
+
>({
|
|
205
304
|
encode(userDefinedOrNull) {
|
|
206
305
|
if (userDefinedOrNull == null) {
|
|
207
306
|
return bytesFrom([]);
|
|
@@ -230,10 +329,10 @@ export function option<Encodable, Decoded>(
|
|
|
230
329
|
* Wrap the encoded value with a fixed-length buffer
|
|
231
330
|
* @param codec
|
|
232
331
|
*/
|
|
233
|
-
export function byteVec<Encodable, Decoded>(
|
|
234
|
-
codec: CodecLike<Encodable, Decoded>,
|
|
235
|
-
): Codec<Encodable, Decoded
|
|
236
|
-
return Codec.from({
|
|
332
|
+
export function byteVec<Encodable, Decoded, Context>(
|
|
333
|
+
codec: CodecLike<Encodable, Decoded, Context>,
|
|
334
|
+
): Codec<Encodable, Decoded, ContextType<typeof codec>> {
|
|
335
|
+
return Codec.from<Encodable, Decoded, ContextType<typeof codec>>({
|
|
237
336
|
encode(userDefined) {
|
|
238
337
|
try {
|
|
239
338
|
const payload = bytesFrom(codec.encode(userDefined));
|
|
@@ -266,13 +365,15 @@ export function byteVec<Encodable, Decoded>(
|
|
|
266
365
|
}
|
|
267
366
|
|
|
268
367
|
export type EncodableRecordOptionalKeys<
|
|
269
|
-
T extends Record<string, CodecLike<any, any>>,
|
|
368
|
+
T extends Record<string, CodecLike<any, any, any>>,
|
|
270
369
|
> = {
|
|
271
370
|
[K in keyof T]: Extract<EncodableType<T[K]>, undefined> extends never
|
|
272
371
|
? never
|
|
273
372
|
: K;
|
|
274
373
|
}[keyof T];
|
|
275
|
-
export type EncodableRecord<
|
|
374
|
+
export type EncodableRecord<
|
|
375
|
+
T extends Record<string, CodecLike<any, any, any>>,
|
|
376
|
+
> = {
|
|
276
377
|
[key in keyof Pick<T, EncodableRecordOptionalKeys<T>>]+?: EncodableType<
|
|
277
378
|
T[key]
|
|
278
379
|
>;
|
|
@@ -281,30 +382,33 @@ export type EncodableRecord<T extends Record<string, CodecLike<any, any>>> = {
|
|
|
281
382
|
};
|
|
282
383
|
|
|
283
384
|
export type DecodedRecordOptionalKeys<
|
|
284
|
-
T extends Record<string, CodecLike<any, any>>,
|
|
385
|
+
T extends Record<string, CodecLike<any, any, any>>,
|
|
285
386
|
> = {
|
|
286
387
|
[K in keyof T]: Extract<DecodedType<T[K]>, undefined> extends never
|
|
287
388
|
? never
|
|
288
389
|
: K;
|
|
289
390
|
}[keyof T];
|
|
290
|
-
export type DecodedRecord<T extends Record<string, CodecLike<any, any>>> =
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
391
|
+
export type DecodedRecord<T extends Record<string, CodecLike<any, any, any>>> =
|
|
392
|
+
{
|
|
393
|
+
[key in keyof Pick<T, DecodedRecordOptionalKeys<T>>]+?: DecodedType<T[key]>;
|
|
394
|
+
} & {
|
|
395
|
+
[key in keyof Omit<T, DecodedRecordOptionalKeys<T>>]: DecodedType<T[key]>;
|
|
396
|
+
};
|
|
295
397
|
|
|
296
398
|
/**
|
|
297
399
|
* Table is a dynamic-size type. It can be considered as a dynvec but the length is fixed.
|
|
298
400
|
* @param codecLayout
|
|
299
401
|
*/
|
|
300
402
|
export function table<
|
|
301
|
-
T extends Record<string, CodecLike<any, any>>,
|
|
403
|
+
T extends Record<string, CodecLike<any, any, any>>,
|
|
302
404
|
Encodable extends EncodableRecord<T>,
|
|
303
405
|
Decoded extends DecodedRecord<T>,
|
|
304
|
-
>(
|
|
406
|
+
>(
|
|
407
|
+
codecLayout: T,
|
|
408
|
+
): Codec<Encodable, Decoded, ChildContext<T> & TableDecodeContext> {
|
|
305
409
|
const keys = Object.keys(codecLayout);
|
|
306
410
|
|
|
307
|
-
return Codec.from({
|
|
411
|
+
return Codec.from<Encodable, Decoded, ChildContext<T> & TableDecodeContext>({
|
|
308
412
|
encode(object) {
|
|
309
413
|
let offset = 4 + keys.length * 4;
|
|
310
414
|
const header: number[] = [];
|
|
@@ -324,67 +428,34 @@ export function table<
|
|
|
324
428
|
const packedTotalSize = uint32To(header.length + body.length + 4);
|
|
325
429
|
return bytesConcat(packedTotalSize, header, body);
|
|
326
430
|
},
|
|
327
|
-
decode(buffer,
|
|
431
|
+
decode(buffer, context) {
|
|
328
432
|
const value = bytesFrom(buffer);
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
}
|
|
334
|
-
const
|
|
335
|
-
const headerLength = uint32From(value.subarray(4, 8));
|
|
336
|
-
const actualFieldCount = (headerLength - 4) / 4;
|
|
337
|
-
|
|
338
|
-
if (byteLength !== value.byteLength) {
|
|
339
|
-
throw new Error(
|
|
340
|
-
`table: invalid buffer size, expected ${byteLength}, but got ${value.byteLength}`,
|
|
341
|
-
);
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
if (actualFieldCount < keys.length) {
|
|
345
|
-
throw new Error(
|
|
346
|
-
`table: invalid field count, expected ${keys.length}, but got ${actualFieldCount}`,
|
|
347
|
-
);
|
|
348
|
-
}
|
|
433
|
+
const schemaKeyCount = keys.length;
|
|
434
|
+
const offsets = verifyAndExtractOffsets(value, "table", {
|
|
435
|
+
expected: schemaKeyCount,
|
|
436
|
+
allowExtra: context?.isExtraFieldIgnored === true,
|
|
437
|
+
});
|
|
438
|
+
const result: Record<string, unknown> = {};
|
|
349
439
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
`table: invalid field count, expected ${keys.length}, but got ${actualFieldCount}, and extra fields are not allowed in the current configuration. If you want to ignore extra fields, set isExtraFieldIgnored to true.`,
|
|
353
|
-
);
|
|
354
|
-
}
|
|
355
|
-
const offsets = keys.map((_, index) =>
|
|
356
|
-
uint32From(value.subarray(4 + index * 4, 8 + index * 4)),
|
|
357
|
-
);
|
|
358
|
-
// If there are extra fields, add the last offset to the offsets array
|
|
359
|
-
if (actualFieldCount > keys.length) {
|
|
360
|
-
offsets.push(
|
|
361
|
-
uint32From(value.subarray(4 + keys.length * 4, 8 + keys.length * 4)),
|
|
362
|
-
);
|
|
363
|
-
} else {
|
|
364
|
-
// If there are no extra fields, add the byte length to the offsets array
|
|
365
|
-
offsets.push(byteLength);
|
|
366
|
-
}
|
|
367
|
-
const object = {};
|
|
368
|
-
for (let i = 0; i < offsets.length - 1; i++) {
|
|
440
|
+
for (let i = 0; i < schemaKeyCount; i++) {
|
|
441
|
+
const key = keys[i];
|
|
369
442
|
const start = offsets[i];
|
|
370
443
|
const end = offsets[i + 1];
|
|
371
|
-
const
|
|
372
|
-
const codec = codecLayout[field];
|
|
373
|
-
const payload = value.subarray(start, end);
|
|
444
|
+
const itemBuffer = value.subarray(start, end);
|
|
374
445
|
try {
|
|
375
|
-
|
|
376
|
-
Object.assign(object, { [field]: codec.decode(payload, config) });
|
|
446
|
+
result[key] = codecLayout[key].decode(itemBuffer, context);
|
|
377
447
|
} catch (e: unknown) {
|
|
378
|
-
throw new Error(`table.${
|
|
448
|
+
throw new Error(`table.${key} - ${getMessage(e)}`, { cause: e });
|
|
379
449
|
}
|
|
380
450
|
}
|
|
381
|
-
|
|
451
|
+
|
|
452
|
+
return result as Decoded;
|
|
382
453
|
},
|
|
383
454
|
});
|
|
384
455
|
}
|
|
385
456
|
|
|
386
457
|
export type UnionEncodable<
|
|
387
|
-
T extends Record<string, CodecLike<any, any>>,
|
|
458
|
+
T extends Record<string, CodecLike<any, any, any>>,
|
|
388
459
|
K extends keyof T = keyof T,
|
|
389
460
|
> = K extends unknown
|
|
390
461
|
? {
|
|
@@ -393,7 +464,7 @@ export type UnionEncodable<
|
|
|
393
464
|
}
|
|
394
465
|
: never;
|
|
395
466
|
export type UnionDecoded<
|
|
396
|
-
T extends Record<string, CodecLike<any, any>>,
|
|
467
|
+
T extends Record<string, CodecLike<any, any, any>>,
|
|
397
468
|
K extends keyof T = keyof T,
|
|
398
469
|
> = K extends unknown
|
|
399
470
|
? {
|
|
@@ -403,7 +474,7 @@ export type UnionDecoded<
|
|
|
403
474
|
: never;
|
|
404
475
|
|
|
405
476
|
export type UnionMatchHandlers<
|
|
406
|
-
CodecType extends CodecLike<any, UnionDecoded<any, any
|
|
477
|
+
CodecType extends CodecLike<any, UnionDecoded<any, any>, any>,
|
|
407
478
|
Result,
|
|
408
479
|
> = {
|
|
409
480
|
[T in DecodedType<CodecType>["type"]]: (
|
|
@@ -411,150 +482,295 @@ export type UnionMatchHandlers<
|
|
|
411
482
|
) => Result;
|
|
412
483
|
};
|
|
413
484
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
* Otherwise, it falls back to a dynamic-size codec.
|
|
419
|
-
*
|
|
420
|
-
* Serialization format:
|
|
421
|
-
* 1. 4-byte little-endian unsigned integer for the variant index.
|
|
422
|
-
* 2. Encoded bytes of the selected variant.
|
|
423
|
-
*
|
|
424
|
-
* @typeParam T
|
|
425
|
-
* A record mapping variant names to codecs.
|
|
426
|
-
* @param codecLayout
|
|
427
|
-
* An object whose keys are variant names and values are codecs for each variant.
|
|
428
|
-
* @param fields
|
|
429
|
-
* Optional mapping from variant names to custom numeric IDs. If omitted, the index
|
|
430
|
-
* of each variant in `codecLayout` is used as its ID.
|
|
431
|
-
*
|
|
432
|
-
*
|
|
433
|
-
* @example
|
|
434
|
-
* // Dynamic union without custom numeric IDs
|
|
435
|
-
* union({ cafe: Uint8, bee: Uint16 })
|
|
436
|
-
*
|
|
437
|
-
* // Dynamic union with custom numeric IDs
|
|
438
|
-
* union({ cafe: Uint8, bee: Uint16 }, { cafe: 0xcafe, bee: 0xbee })
|
|
439
|
-
*
|
|
440
|
-
* // Fixed-size union without custom numeric IDs
|
|
441
|
-
* const PaddedUint8 = struct({ data : u8, padding : u8 })
|
|
442
|
-
* union({ cafe: PaddedUint8, bee: Uint16 });
|
|
443
|
-
*
|
|
444
|
-
* // Fixed-size union with custom numeric IDs
|
|
445
|
-
* union({ cafe: PaddedUint8, bee: Uint16 }, { cafe: 0xcafe, bee: 0xbee })
|
|
446
|
-
*/
|
|
447
|
-
|
|
448
|
-
export function union<T extends Record<string, CodecLike<any, any>>>(
|
|
485
|
+
function validateUnionFields<
|
|
486
|
+
T extends Record<string, CodecLike<any, any, any>>,
|
|
487
|
+
>(
|
|
488
|
+
prefix: string,
|
|
449
489
|
codecLayout: T,
|
|
450
490
|
fields?: Record<keyof T, number | undefined | null>,
|
|
451
|
-
):
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
491
|
+
): void {
|
|
492
|
+
if (!fields) {
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
const layoutKeys = new Set(Object.keys(codecLayout));
|
|
496
|
+
const fieldKeys = new Set(Object.keys(fields));
|
|
497
|
+
for (const key of fieldKeys) {
|
|
498
|
+
if (!layoutKeys.has(key)) {
|
|
499
|
+
throw new Error(
|
|
500
|
+
`${prefix}: unexpected field id for unknown key '${key}'`,
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
const seenIds = new Map<number, string>();
|
|
505
|
+
for (const [key, id] of Object.entries(fields)) {
|
|
506
|
+
if (id === undefined || id === null) {
|
|
507
|
+
throw new Error(`${prefix}: field id for '${key}' is required`);
|
|
508
|
+
}
|
|
458
509
|
if (
|
|
459
|
-
|
|
460
|
-
|
|
510
|
+
typeof id !== "number" ||
|
|
511
|
+
!Number.isInteger(id) ||
|
|
512
|
+
id < 0 ||
|
|
513
|
+
id > 0xffffffff
|
|
461
514
|
) {
|
|
462
|
-
|
|
463
|
-
byteLength = firstLen + 4;
|
|
515
|
+
throw new Error(`${prefix}: invalid field id ${id} for key '${key}'`);
|
|
464
516
|
}
|
|
517
|
+
if (seenIds.has(id)) {
|
|
518
|
+
throw new Error(
|
|
519
|
+
`${prefix}: duplicate field id ${id} for keys '${seenIds.get(id)}' and '${key}'`,
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
seenIds.set(id, key);
|
|
465
523
|
}
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
): UnionEncodable<T> {
|
|
470
|
-
if ("type" in encodable && "value" in encodable) {
|
|
471
|
-
return encodable;
|
|
524
|
+
for (const key of layoutKeys) {
|
|
525
|
+
if (!fieldKeys.has(key)) {
|
|
526
|
+
throw new Error(`${prefix}: missing field id for key '${key}'`);
|
|
472
527
|
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
473
530
|
|
|
474
|
-
|
|
531
|
+
function extractUnionEncodable<
|
|
532
|
+
T extends Record<string, CodecLike<any, any, any>>,
|
|
533
|
+
>(
|
|
534
|
+
encodable: UnionEncodable<T> | { inner: UnionEncodable<T> },
|
|
535
|
+
): UnionEncodable<T> {
|
|
536
|
+
if ("type" in encodable && "value" in encodable) {
|
|
537
|
+
return encodable;
|
|
475
538
|
}
|
|
476
539
|
|
|
477
|
-
return
|
|
540
|
+
return encodable.inner;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
function createUnionCodec<T extends Record<string, CodecLike<any, any, any>>>(
|
|
544
|
+
prefix: string,
|
|
545
|
+
codecLayout: T,
|
|
546
|
+
fields: Record<keyof T, number | undefined | null> | undefined,
|
|
547
|
+
payloadByteLength?: number,
|
|
548
|
+
): Codec<
|
|
549
|
+
UnionEncodable<T> | { inner: UnionEncodable<T> },
|
|
550
|
+
UnionDecoded<T>,
|
|
551
|
+
ChildContext<T>
|
|
552
|
+
> {
|
|
553
|
+
const keys = Object.keys(codecLayout);
|
|
554
|
+
const fieldIds = new Map<string, number>();
|
|
555
|
+
const fieldsById = new Map<number, string>();
|
|
556
|
+
for (const [index, key] of keys.entries()) {
|
|
557
|
+
const fieldId = fields ? (fields[key] as number) : index;
|
|
558
|
+
fieldIds.set(key, fieldId);
|
|
559
|
+
fieldsById.set(fieldId, key);
|
|
560
|
+
}
|
|
561
|
+
const byteLength =
|
|
562
|
+
payloadByteLength === undefined ? undefined : payloadByteLength + 4;
|
|
563
|
+
|
|
564
|
+
return Codec.from<
|
|
565
|
+
UnionEncodable<T> | { inner: UnionEncodable<T> },
|
|
566
|
+
UnionDecoded<T>,
|
|
567
|
+
ChildContext<T>
|
|
568
|
+
>({
|
|
478
569
|
byteLength,
|
|
479
570
|
encode(encodable) {
|
|
480
|
-
const { type, value } =
|
|
571
|
+
const { type, value } = extractUnionEncodable(encodable);
|
|
481
572
|
const typeStr = type.toString();
|
|
482
573
|
const codec = codecLayout[typeStr];
|
|
483
574
|
if (!codec) {
|
|
484
575
|
throw new Error(
|
|
485
|
-
|
|
576
|
+
`${prefix}: invalid type, expected ${keys.toString()}, but got ${typeStr}`,
|
|
486
577
|
);
|
|
487
578
|
}
|
|
488
|
-
const fieldId =
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
if (fieldId < 0) {
|
|
492
|
-
throw new Error(`union: invalid field id ${fieldId} of ${typeStr}`);
|
|
579
|
+
const fieldId = fieldIds.get(typeStr);
|
|
580
|
+
if (fieldId === undefined) {
|
|
581
|
+
throw new Error(`${prefix}: invalid field id -1 of ${typeStr}`);
|
|
493
582
|
}
|
|
494
583
|
const header = uint32To(fieldId);
|
|
495
584
|
try {
|
|
496
585
|
const body = codec.encode(value);
|
|
586
|
+
if (
|
|
587
|
+
payloadByteLength !== undefined &&
|
|
588
|
+
body.byteLength !== payloadByteLength
|
|
589
|
+
) {
|
|
590
|
+
throw new Error(
|
|
591
|
+
`${prefix}: variant '${typeStr}' encoded ${body.byteLength} bytes, expected ${payloadByteLength}`,
|
|
592
|
+
);
|
|
593
|
+
}
|
|
497
594
|
return bytesConcat(header, body);
|
|
498
595
|
} catch (e: unknown) {
|
|
499
|
-
throw new Error(
|
|
596
|
+
throw new Error(`${prefix}.(${typeStr}) - ${getMessage(e)}`, {
|
|
597
|
+
cause: e,
|
|
598
|
+
});
|
|
500
599
|
}
|
|
501
600
|
},
|
|
502
601
|
decode(buffer, config) {
|
|
503
602
|
const value = bytesFrom(buffer);
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
const field = (() => {
|
|
508
|
-
if (!fields) {
|
|
509
|
-
return keys[fieldIndex];
|
|
510
|
-
}
|
|
511
|
-
const entry = Object.entries(fields).find(
|
|
512
|
-
([, id]) => id === fieldIndex,
|
|
603
|
+
if (byteLength === undefined && value.byteLength < 4) {
|
|
604
|
+
throw new Error(
|
|
605
|
+
`${prefix}: too short buffer, expected at least 4 bytes for union tag, but got ${value.byteLength}`,
|
|
513
606
|
);
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
607
|
+
}
|
|
608
|
+
const fieldIndex = uint32From(value.subarray(0, 4));
|
|
609
|
+
const field = fieldsById.get(fieldIndex);
|
|
517
610
|
if (!field) {
|
|
518
|
-
if (!fields) {
|
|
519
|
-
throw new Error(
|
|
520
|
-
`union: unknown union field index ${fieldIndex}, only ${keys.toString()} are allowed`,
|
|
521
|
-
);
|
|
522
|
-
}
|
|
523
|
-
const fieldKeys = Object.keys(fields);
|
|
524
611
|
throw new Error(
|
|
525
|
-
|
|
612
|
+
`${prefix}: unknown union field index ${fieldIndex}, only ${keys.toString()} are allowed`,
|
|
526
613
|
);
|
|
527
614
|
}
|
|
528
615
|
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
616
|
+
try {
|
|
617
|
+
return {
|
|
618
|
+
type: field,
|
|
619
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
620
|
+
value: codecLayout[field].decode(value.subarray(4), config),
|
|
621
|
+
} as UnionDecoded<T>;
|
|
622
|
+
} catch (e: unknown) {
|
|
623
|
+
throw new Error(`${prefix}.(${field}) - ${getMessage(e)}`, {
|
|
624
|
+
cause: e,
|
|
625
|
+
});
|
|
626
|
+
}
|
|
534
627
|
},
|
|
535
628
|
});
|
|
536
629
|
}
|
|
537
630
|
|
|
631
|
+
/**
|
|
632
|
+
* Constructs a union codec that can serialize and deserialize values tagged with a type identifier.
|
|
633
|
+
*
|
|
634
|
+
* In accordance with the Molecule specification, `union` is always a dynamic-size type,
|
|
635
|
+
* regardless of whether its variants have identical fixed lengths.
|
|
636
|
+
*
|
|
637
|
+
* If you need a fixed-size union type compatible with fixed containers like `struct`,
|
|
638
|
+
* `array`, or `vector` (as FixVec), use {@link fixedUnion} instead.
|
|
639
|
+
*
|
|
640
|
+
* Serialization format:
|
|
641
|
+
* 1. 4-byte little-endian unsigned integer for the variant ItemId (tag).
|
|
642
|
+
* 2. Encoded bytes of the selected variant.
|
|
643
|
+
*
|
|
644
|
+
* @typeParam T
|
|
645
|
+
* A record mapping variant names to codecs.
|
|
646
|
+
* @param codecLayout
|
|
647
|
+
* An object whose keys are variant names and values are codecs for each variant.
|
|
648
|
+
* @param fields
|
|
649
|
+
* Optional mapping from variant names to custom numeric IDs. If omitted, the index
|
|
650
|
+
* of each variant in `codecLayout` is used as its ID.
|
|
651
|
+
*
|
|
652
|
+
* @example
|
|
653
|
+
* // Union without custom numeric IDs
|
|
654
|
+
* union({ cafe: Uint8, bee: Uint16 });
|
|
655
|
+
*
|
|
656
|
+
* // Union with custom numeric IDs
|
|
657
|
+
* union({ cafe: Uint8, bee: Uint16 }, { cafe: 0xcafe, bee: 0xbee });
|
|
658
|
+
*
|
|
659
|
+
* // Fixed-size union (explicit extension):
|
|
660
|
+
* fixedUnion({ cafe: Uint16, bee: Uint16 });
|
|
661
|
+
*/
|
|
662
|
+
export function union<T extends Record<string, CodecLike<any, any, any>>>(
|
|
663
|
+
codecLayout: T,
|
|
664
|
+
fields?: Record<keyof T, number | undefined | null>,
|
|
665
|
+
): Codec<
|
|
666
|
+
UnionEncodable<T> | { inner: UnionEncodable<T> },
|
|
667
|
+
UnionDecoded<T>,
|
|
668
|
+
ChildContext<T>
|
|
669
|
+
> {
|
|
670
|
+
if (Object.keys(codecLayout).length === 0) {
|
|
671
|
+
throw new Error("union: must have at least one variant");
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
validateUnionFields("union", codecLayout, fields);
|
|
675
|
+
return createUnionCodec("union", codecLayout, fields);
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/**
|
|
679
|
+
* FixedUnion is a fixed-size union type where all variants must be fixed-size types of identical byte length.
|
|
680
|
+
* The total byteLength is `payloadByteLength + 4` (4-byte uint32 ItemId tag).
|
|
681
|
+
*
|
|
682
|
+
* Compatibility note:
|
|
683
|
+
* `fixedUnion` is a CCC extension for scenarios requiring fixed memory layouts (such as embedding
|
|
684
|
+
* inside `mol.struct`, `mol.array`, or `mol.vector` as FixVec). While the standalone wire format
|
|
685
|
+
* (4-byte tag + variant payload) is binary-compatible with a union encoding, composing it into
|
|
686
|
+
* `struct`, `array`, or FixVec is not part of the standard upstream Molecule union schema specification.
|
|
687
|
+
*
|
|
688
|
+
* Migration guide:
|
|
689
|
+
* ```typescript
|
|
690
|
+
* // Preserve legacy equal-length union fixed container layout (FixVec):
|
|
691
|
+
* const Legacy = mol.vector(mol.fixedUnion(layout));
|
|
692
|
+
*
|
|
693
|
+
* // Use canonical Molecule union vector (DynVec):
|
|
694
|
+
* const Canonical = mol.vector(mol.union(layout));
|
|
695
|
+
* ```
|
|
696
|
+
*
|
|
697
|
+
* @param codecLayout an object mapping variant names to fixed-size codecs of identical byteLength
|
|
698
|
+
* @param fields optional mapping from variant names to custom numeric IDs
|
|
699
|
+
*/
|
|
700
|
+
export function fixedUnion<T extends Record<string, CodecLike<any, any, any>>>(
|
|
701
|
+
codecLayout: T,
|
|
702
|
+
fields?: Record<keyof T, number | undefined | null>,
|
|
703
|
+
): Codec<
|
|
704
|
+
UnionEncodable<T> | { inner: UnionEncodable<T> },
|
|
705
|
+
UnionDecoded<T>,
|
|
706
|
+
ChildContext<T>
|
|
707
|
+
> {
|
|
708
|
+
const entries = Object.entries(codecLayout);
|
|
709
|
+
if (entries.length === 0) {
|
|
710
|
+
throw new Error("fixedUnion: must have at least one variant");
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
let payloadByteLength: number | undefined;
|
|
714
|
+
for (const [key, codec] of entries) {
|
|
715
|
+
if (codec.byteLength === undefined) {
|
|
716
|
+
throw new Error(`fixedUnion: variant '${key}' must be a fixed-size type`);
|
|
717
|
+
}
|
|
718
|
+
if (!isPositiveSafeInteger(codec.byteLength)) {
|
|
719
|
+
throw new Error(
|
|
720
|
+
`fixedUnion: variant '${key}' byteLength must be a positive safe integer, but got ${String(codec.byteLength)}`,
|
|
721
|
+
);
|
|
722
|
+
}
|
|
723
|
+
if (payloadByteLength === undefined) {
|
|
724
|
+
payloadByteLength = codec.byteLength;
|
|
725
|
+
} else if (codec.byteLength !== payloadByteLength) {
|
|
726
|
+
throw new Error(
|
|
727
|
+
`fixedUnion: all variants must have the same byteLength (${payloadByteLength}), but variant '${key}' has ${codec.byteLength}`,
|
|
728
|
+
);
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
validateUnionFields("fixedUnion", codecLayout, fields);
|
|
733
|
+
|
|
734
|
+
if (payloadByteLength! > Number.MAX_SAFE_INTEGER - 4) {
|
|
735
|
+
throw new Error("fixedUnion: total byteLength exceeds safe integer limit");
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
return createUnionCodec("fixedUnion", codecLayout, fields, payloadByteLength);
|
|
739
|
+
}
|
|
740
|
+
|
|
538
741
|
/**
|
|
539
742
|
* Struct is a fixed-size type: all fields in struct are fixed-size and it has a fixed quantity of fields.
|
|
540
743
|
* The size of a struct is the sum of all fields' size.
|
|
541
744
|
* @param codecLayout a object contains all fields' codec
|
|
542
745
|
*/
|
|
543
746
|
export function struct<
|
|
544
|
-
T extends Record<string, CodecLike<any, any>>,
|
|
747
|
+
T extends Record<string, CodecLike<any, any, any>>,
|
|
545
748
|
Encodable extends EncodableRecord<T>,
|
|
546
749
|
Decoded extends DecodedRecord<T>,
|
|
547
|
-
>(codecLayout: T): Codec<Encodable, Decoded
|
|
548
|
-
const codecArray = Object.values(codecLayout);
|
|
750
|
+
>(codecLayout: T): Codec<Encodable, Decoded, ChildContext<T>> {
|
|
549
751
|
const keys = Object.keys(codecLayout);
|
|
752
|
+
if (keys.length === 0) {
|
|
753
|
+
throw new Error("struct: must have at least one field");
|
|
754
|
+
}
|
|
550
755
|
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
756
|
+
let byteLength = 0;
|
|
757
|
+
for (const [key, codec] of Object.entries(codecLayout)) {
|
|
758
|
+
if (codec.byteLength === undefined) {
|
|
759
|
+
throw new Error(`struct: field '${key}' must be fixed-size`);
|
|
760
|
+
}
|
|
761
|
+
if (!isPositiveSafeInteger(codec.byteLength)) {
|
|
762
|
+
throw new Error(
|
|
763
|
+
`struct: field '${key}' byteLength must be a positive safe integer, but got ${String(codec.byteLength)}`,
|
|
764
|
+
);
|
|
765
|
+
}
|
|
766
|
+
byteLength += codec.byteLength;
|
|
767
|
+
if (!Number.isSafeInteger(byteLength)) {
|
|
768
|
+
throw new Error("struct: total byteLength exceeds safe integer limit");
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
return Codec.from<Encodable, Decoded, ChildContext<T>>({
|
|
773
|
+
byteLength,
|
|
558
774
|
encode(object) {
|
|
559
775
|
const bytes: number[] = [];
|
|
560
776
|
for (const key of keys) {
|
|
@@ -593,16 +809,35 @@ export function struct<
|
|
|
593
809
|
* @param itemCodec the fixed-size array item codec
|
|
594
810
|
* @param itemCount
|
|
595
811
|
*/
|
|
596
|
-
export function array<Encodable, Decoded>(
|
|
597
|
-
itemCodec: CodecLike<Encodable, Decoded>,
|
|
812
|
+
export function array<Encodable, Decoded, Context>(
|
|
813
|
+
itemCodec: CodecLike<Encodable, Decoded, Context>,
|
|
598
814
|
itemCount: number,
|
|
599
|
-
): Codec<Array<Encodable>, Array<Decoded>> {
|
|
815
|
+
): Codec<Array<Encodable>, Array<Decoded>, ContextType<typeof itemCodec>> {
|
|
600
816
|
if (itemCodec.byteLength === undefined) {
|
|
601
817
|
throw new Error("array: itemCodec requires a byte length");
|
|
602
818
|
}
|
|
819
|
+
if (!isPositiveSafeInteger(itemCodec.byteLength)) {
|
|
820
|
+
throw new Error(
|
|
821
|
+
`array: itemCodec byteLength must be a positive safe integer, but got ${String(itemCodec.byteLength)}`,
|
|
822
|
+
);
|
|
823
|
+
}
|
|
824
|
+
if (!isPositiveSafeInteger(itemCount)) {
|
|
825
|
+
throw new Error(
|
|
826
|
+
`array: itemCount must be a positive safe integer, but got ${String(itemCount)}`,
|
|
827
|
+
);
|
|
828
|
+
}
|
|
603
829
|
const byteLength = itemCodec.byteLength * itemCount;
|
|
830
|
+
if (!Number.isSafeInteger(byteLength)) {
|
|
831
|
+
throw new Error(
|
|
832
|
+
`array: total byteLength exceeds safe integer limit, got ${String(byteLength)}`,
|
|
833
|
+
);
|
|
834
|
+
}
|
|
604
835
|
|
|
605
|
-
return Codec.from
|
|
836
|
+
return Codec.from<
|
|
837
|
+
Array<Encodable>,
|
|
838
|
+
Array<Decoded>,
|
|
839
|
+
ContextType<typeof itemCodec>
|
|
840
|
+
>({
|
|
606
841
|
byteLength,
|
|
607
842
|
encode(items) {
|
|
608
843
|
try {
|
|
@@ -618,7 +853,7 @@ export function array<Encodable, Decoded>(
|
|
|
618
853
|
},
|
|
619
854
|
decode(buffer, config) {
|
|
620
855
|
const value = bytesFrom(buffer);
|
|
621
|
-
if (value.byteLength
|
|
856
|
+
if (value.byteLength !== byteLength) {
|
|
622
857
|
throw new Error(
|
|
623
858
|
`array: invalid buffer size, expected ${byteLength}, but got ${value.byteLength}`,
|
|
624
859
|
);
|