@yume-chan/struct 0.0.0-20240714132542

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.
Files changed (91) hide show
  1. package/CHANGELOG.md +131 -0
  2. package/LICENSE +21 -0
  3. package/README.md +747 -0
  4. package/esm/basic/definition.d.ts +44 -0
  5. package/esm/basic/definition.d.ts.map +1 -0
  6. package/esm/basic/definition.js +24 -0
  7. package/esm/basic/definition.js.map +1 -0
  8. package/esm/basic/field-value.d.ts +39 -0
  9. package/esm/basic/field-value.d.ts.map +1 -0
  10. package/esm/basic/field-value.js +46 -0
  11. package/esm/basic/field-value.js.map +1 -0
  12. package/esm/basic/index.d.ts +6 -0
  13. package/esm/basic/index.d.ts.map +1 -0
  14. package/esm/basic/index.js +6 -0
  15. package/esm/basic/index.js.map +1 -0
  16. package/esm/basic/options.d.ts +10 -0
  17. package/esm/basic/options.d.ts.map +1 -0
  18. package/esm/basic/options.js +4 -0
  19. package/esm/basic/options.js.map +1 -0
  20. package/esm/basic/stream.d.ts +25 -0
  21. package/esm/basic/stream.d.ts.map +1 -0
  22. package/esm/basic/stream.js +8 -0
  23. package/esm/basic/stream.js.map +1 -0
  24. package/esm/basic/struct-value.d.ts +30 -0
  25. package/esm/basic/struct-value.d.ts.map +1 -0
  26. package/esm/basic/struct-value.js +62 -0
  27. package/esm/basic/struct-value.js.map +1 -0
  28. package/esm/index.d.ts +15 -0
  29. package/esm/index.d.ts.map +1 -0
  30. package/esm/index.js +7 -0
  31. package/esm/index.js.map +1 -0
  32. package/esm/struct.d.ts +151 -0
  33. package/esm/struct.d.ts.map +1 -0
  34. package/esm/struct.js +255 -0
  35. package/esm/struct.js.map +1 -0
  36. package/esm/sync-promise.d.ts +13 -0
  37. package/esm/sync-promise.d.ts.map +1 -0
  38. package/esm/sync-promise.js +71 -0
  39. package/esm/sync-promise.js.map +1 -0
  40. package/esm/types/bigint.d.ts +25 -0
  41. package/esm/types/bigint.d.ts.map +1 -0
  42. package/esm/types/bigint.js +46 -0
  43. package/esm/types/bigint.js.map +1 -0
  44. package/esm/types/buffer/base.d.ts +69 -0
  45. package/esm/types/buffer/base.d.ts.map +1 -0
  46. package/esm/types/buffer/base.js +101 -0
  47. package/esm/types/buffer/base.js.map +1 -0
  48. package/esm/types/buffer/fixed-length.d.ts +9 -0
  49. package/esm/types/buffer/fixed-length.d.ts.map +1 -0
  50. package/esm/types/buffer/fixed-length.js +7 -0
  51. package/esm/types/buffer/fixed-length.js.map +1 -0
  52. package/esm/types/buffer/index.d.ts +4 -0
  53. package/esm/types/buffer/index.d.ts.map +1 -0
  54. package/esm/types/buffer/index.js +4 -0
  55. package/esm/types/buffer/index.js.map +1 -0
  56. package/esm/types/buffer/variable-length.d.ts +46 -0
  57. package/esm/types/buffer/variable-length.d.ts.map +1 -0
  58. package/esm/types/buffer/variable-length.js +88 -0
  59. package/esm/types/buffer/variable-length.js.map +1 -0
  60. package/esm/types/index.d.ts +4 -0
  61. package/esm/types/index.d.ts.map +1 -0
  62. package/esm/types/index.js +4 -0
  63. package/esm/types/index.js.map +1 -0
  64. package/esm/types/number.d.ts +28 -0
  65. package/esm/types/number.d.ts.map +1 -0
  66. package/esm/types/number.js +88 -0
  67. package/esm/types/number.js.map +1 -0
  68. package/esm/utils.d.ts +48 -0
  69. package/esm/utils.d.ts.map +1 -0
  70. package/esm/utils.js +18 -0
  71. package/esm/utils.js.map +1 -0
  72. package/package.json +49 -0
  73. package/src/basic/definition.ts +68 -0
  74. package/src/basic/field-value.ts +71 -0
  75. package/src/basic/index.ts +5 -0
  76. package/src/basic/options.ts +19 -0
  77. package/src/basic/stream.ts +34 -0
  78. package/src/basic/struct-value.ts +85 -0
  79. package/src/index.ts +18 -0
  80. package/src/struct.ts +705 -0
  81. package/src/sync-promise.ts +131 -0
  82. package/src/types/bigint.ts +120 -0
  83. package/src/types/buffer/base.ts +195 -0
  84. package/src/types/buffer/fixed-length.ts +22 -0
  85. package/src/types/buffer/index.ts +3 -0
  86. package/src/types/buffer/variable-length.ts +199 -0
  87. package/src/types/index.ts +3 -0
  88. package/src/types/number.ts +159 -0
  89. package/src/utils.ts +94 -0
  90. package/tsconfig.build.json +3 -0
  91. package/tsconfig.build.tsbuildinfo +1 -0
package/src/struct.ts ADDED
@@ -0,0 +1,705 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+
3
+ import type {
4
+ AsyncExactReadable,
5
+ ExactReadable,
6
+ StructFieldDefinition,
7
+ StructFieldValue,
8
+ StructOptions,
9
+ } from "./basic/index.js";
10
+ import {
11
+ ExactReadableEndedError,
12
+ STRUCT_VALUE_SYMBOL,
13
+ StructDefaultOptions,
14
+ StructValue,
15
+ isStructValueInit,
16
+ } from "./basic/index.js";
17
+ import { SyncPromise } from "./sync-promise.js";
18
+ import type {
19
+ BufferFieldConverter,
20
+ FixedLengthBufferLikeFieldOptions,
21
+ LengthField,
22
+ VariableLengthBufferLikeFieldOptions,
23
+ } from "./types/index.js";
24
+ import {
25
+ BigIntFieldDefinition,
26
+ BigIntFieldVariant,
27
+ FixedLengthBufferLikeFieldDefinition,
28
+ NumberFieldDefinition,
29
+ NumberFieldVariant,
30
+ StringBufferFieldConverter,
31
+ Uint8ArrayBufferFieldConverter,
32
+ VariableLengthBufferLikeFieldDefinition,
33
+ } from "./types/index.js";
34
+ import type { Evaluate, Identity, Overwrite, ValueOrPromise } from "./utils.js";
35
+
36
+ export interface StructLike<TValue> {
37
+ deserialize(stream: ExactReadable | AsyncExactReadable): Promise<TValue>;
38
+ }
39
+
40
+ /**
41
+ * Extract the value type of the specified `Struct`
42
+ */
43
+ export type StructValueType<T extends StructLike<unknown>> = Awaited<
44
+ ReturnType<T["deserialize"]>
45
+ >;
46
+
47
+ /**
48
+ * Create a new `Struct` type with `TDefinition` appended
49
+ */
50
+ type AddFieldDescriptor<
51
+ TFields extends object,
52
+ TOmitInitKey extends PropertyKey,
53
+ TExtra extends object,
54
+ TPostDeserialized,
55
+ TFieldName extends PropertyKey,
56
+ TDefinition extends StructFieldDefinition<unknown, unknown, PropertyKey>,
57
+ > = Identity<
58
+ Struct<
59
+ // Merge two types
60
+ // Evaluate immediately to optimize editor hover tooltip
61
+ Evaluate<TFields & Record<TFieldName, TDefinition["TValue"]>>,
62
+ // Merge two `TOmitInitKey`s
63
+ TOmitInitKey | TDefinition["TOmitInitKey"],
64
+ TExtra,
65
+ TPostDeserialized
66
+ >
67
+ >;
68
+
69
+ /**
70
+ * Overload methods to add an array buffer like field
71
+ */
72
+ interface ArrayBufferLikeFieldCreator<
73
+ TFields extends object,
74
+ TOmitInitKey extends PropertyKey,
75
+ TExtra extends object,
76
+ TPostDeserialized,
77
+ > {
78
+ /**
79
+ * Append a fixed-length array buffer like field to the `Struct`
80
+ *
81
+ * @param name Name of the field
82
+ * @param type `Array.SubType.ArrayBuffer` or `Array.SubType.String`
83
+ * @param options Fixed-length array options
84
+ * @param typeScriptType Type of the field in TypeScript.
85
+ * For example, if this field is a string, you can declare it as a string enum or literal union.
86
+ */
87
+ <
88
+ TName extends PropertyKey,
89
+ TType extends BufferFieldConverter<unknown, unknown>,
90
+ TTypeScriptType = TType["TTypeScriptType"],
91
+ >(
92
+ name: TName,
93
+ type: TType,
94
+ options: FixedLengthBufferLikeFieldOptions,
95
+ typeScriptType?: TTypeScriptType,
96
+ ): AddFieldDescriptor<
97
+ TFields,
98
+ TOmitInitKey,
99
+ TExtra,
100
+ TPostDeserialized,
101
+ TName,
102
+ FixedLengthBufferLikeFieldDefinition<
103
+ TType,
104
+ FixedLengthBufferLikeFieldOptions
105
+ >
106
+ >;
107
+
108
+ /**
109
+ * Append a variable-length array buffer like field to the `Struct`
110
+ */
111
+ <
112
+ TName extends PropertyKey,
113
+ TType extends BufferFieldConverter<unknown, unknown>,
114
+ TOptions extends VariableLengthBufferLikeFieldOptions<TFields>,
115
+ TTypeScriptType = TType["TTypeScriptType"],
116
+ >(
117
+ name: TName,
118
+ type: TType,
119
+ options: TOptions,
120
+ typeScriptType?: TTypeScriptType,
121
+ ): AddFieldDescriptor<
122
+ TFields,
123
+ TOmitInitKey,
124
+ TExtra,
125
+ TPostDeserialized,
126
+ TName,
127
+ VariableLengthBufferLikeFieldDefinition<TType, TOptions>
128
+ >;
129
+ }
130
+
131
+ /**
132
+ * Similar to `ArrayBufferLikeFieldCreator`, but bind to `TType`
133
+ */
134
+ interface BoundArrayBufferLikeFieldDefinitionCreator<
135
+ TFields extends object,
136
+ TOmitInitKey extends PropertyKey,
137
+ TExtra extends object,
138
+ TPostDeserialized,
139
+ TType extends BufferFieldConverter<unknown, unknown>,
140
+ > {
141
+ <TName extends PropertyKey, TTypeScriptType = TType["TTypeScriptType"]>(
142
+ name: TName,
143
+ options: FixedLengthBufferLikeFieldOptions,
144
+ typeScriptType?: TTypeScriptType,
145
+ ): AddFieldDescriptor<
146
+ TFields,
147
+ TOmitInitKey,
148
+ TExtra,
149
+ TPostDeserialized,
150
+ TName,
151
+ FixedLengthBufferLikeFieldDefinition<
152
+ TType,
153
+ FixedLengthBufferLikeFieldOptions,
154
+ TTypeScriptType
155
+ >
156
+ >;
157
+
158
+ <
159
+ TName extends PropertyKey,
160
+ TOptions extends VariableLengthBufferLikeFieldOptions<
161
+ TFields,
162
+ LengthField<TFields>
163
+ >,
164
+ TTypeScriptType = TType["TTypeScriptType"],
165
+ >(
166
+ name: TName,
167
+ options: TOptions,
168
+ typeScriptType?: TTypeScriptType,
169
+ ): AddFieldDescriptor<
170
+ TFields,
171
+ TOmitInitKey,
172
+ TExtra,
173
+ TPostDeserialized,
174
+ TName,
175
+ VariableLengthBufferLikeFieldDefinition<
176
+ TType,
177
+ TOptions,
178
+ TTypeScriptType
179
+ >
180
+ >;
181
+ }
182
+
183
+ export type StructPostDeserialized<TFields, TPostDeserialized> = (
184
+ this: TFields,
185
+ object: TFields,
186
+ ) => TPostDeserialized;
187
+
188
+ export type StructDeserializedResult<
189
+ TFields extends object,
190
+ TExtra extends object,
191
+ TPostDeserialized,
192
+ > = TPostDeserialized extends undefined
193
+ ? Overwrite<TExtra, TFields>
194
+ : TPostDeserialized;
195
+
196
+ export class StructDeserializeError extends Error {
197
+ constructor(message: string) {
198
+ super(message);
199
+ Object.setPrototypeOf(this, new.target.prototype);
200
+ }
201
+ }
202
+
203
+ export class StructNotEnoughDataError extends StructDeserializeError {
204
+ constructor() {
205
+ super(
206
+ "The underlying readable was ended before the struct was fully deserialized",
207
+ );
208
+ }
209
+ }
210
+
211
+ export class StructEmptyError extends StructDeserializeError {
212
+ constructor() {
213
+ super("The underlying readable doesn't contain any more struct");
214
+ }
215
+ }
216
+
217
+ interface StructDefinition<
218
+ TFields extends object,
219
+ TOmitInitKey extends PropertyKey,
220
+ TExtra extends object,
221
+ > {
222
+ readonly TFields: TFields;
223
+
224
+ readonly TOmitInitKey: TOmitInitKey;
225
+
226
+ readonly TExtra: TExtra;
227
+
228
+ readonly TInit: Evaluate<Omit<TFields, TOmitInitKey>>;
229
+ }
230
+
231
+ export class Struct<
232
+ TFields extends object = Record<never, never>,
233
+ TOmitInitKey extends PropertyKey = never,
234
+ TExtra extends object = Record<never, never>,
235
+ TPostDeserialized = undefined,
236
+ > implements
237
+ StructLike<
238
+ StructDeserializedResult<TFields, TExtra, TPostDeserialized>
239
+ >
240
+ {
241
+ readonly TFields!: TFields;
242
+
243
+ readonly TOmitInitKey!: TOmitInitKey;
244
+
245
+ readonly TExtra!: TExtra;
246
+
247
+ readonly TInit!: Evaluate<Omit<TFields, TOmitInitKey>>;
248
+
249
+ readonly TDeserializeResult!: StructDeserializedResult<
250
+ TFields,
251
+ TExtra,
252
+ TPostDeserialized
253
+ >;
254
+
255
+ readonly options: Readonly<StructOptions>;
256
+
257
+ #size = 0;
258
+ /**
259
+ * Gets the static size (exclude fields that can change size at runtime)
260
+ */
261
+ get size() {
262
+ return this.#size;
263
+ }
264
+
265
+ #fields: [
266
+ name: PropertyKey,
267
+ definition: StructFieldDefinition<unknown, unknown, PropertyKey>,
268
+ ][] = [];
269
+ get fields(): readonly [
270
+ name: PropertyKey,
271
+ definition: StructFieldDefinition<unknown, unknown, PropertyKey>,
272
+ ][] {
273
+ return this.#fields;
274
+ }
275
+
276
+ #extra: Record<PropertyKey, unknown> = {};
277
+
278
+ #postDeserialized?: StructPostDeserialized<never, unknown> | undefined;
279
+
280
+ constructor(options?: Partial<Readonly<StructOptions>>) {
281
+ this.options = { ...StructDefaultOptions, ...options };
282
+ }
283
+
284
+ /**
285
+ * Appends a `StructFieldDefinition` to the `Struct
286
+ */
287
+ field<
288
+ TName extends PropertyKey,
289
+ TDefinition extends StructFieldDefinition<
290
+ unknown,
291
+ unknown,
292
+ PropertyKey
293
+ >,
294
+ >(
295
+ name: TName,
296
+ definition: TDefinition,
297
+ ): AddFieldDescriptor<
298
+ TFields,
299
+ TOmitInitKey,
300
+ TExtra,
301
+ TPostDeserialized,
302
+ TName,
303
+ TDefinition
304
+ > {
305
+ for (const field of this.#fields) {
306
+ if (field[0] === name) {
307
+ // Convert Symbol to string
308
+ const nameString = String(name);
309
+ throw new Error(
310
+ `This struct already have a field with name '${nameString}'`,
311
+ );
312
+ }
313
+ }
314
+
315
+ this.#fields.push([name, definition]);
316
+
317
+ const size = definition.getSize();
318
+ this.#size += size;
319
+
320
+ // Force cast `this` to another type
321
+ return this as never;
322
+ }
323
+
324
+ /**
325
+ * Merges (flats) another `Struct`'s fields and extra fields into this one.
326
+ *
327
+ * `other`'s `postDeserialize` will be ignored.
328
+ */
329
+ concat<TOther extends StructDefinition<object, PropertyKey, object>>(
330
+ other: TOther,
331
+ ): Struct<
332
+ TFields & TOther["TFields"],
333
+ TOmitInitKey | TOther["TOmitInitKey"],
334
+ TExtra & TOther["TExtra"],
335
+ TPostDeserialized
336
+ > {
337
+ if (!(other instanceof Struct)) {
338
+ throw new TypeError("The other value must be a `Struct` instance");
339
+ }
340
+
341
+ for (const field of other.#fields) {
342
+ this.#fields.push(field);
343
+ }
344
+ this.#size += other.#size;
345
+ Object.defineProperties(
346
+ this.#extra,
347
+ Object.getOwnPropertyDescriptors(other.#extra),
348
+ );
349
+ return this as never;
350
+ }
351
+
352
+ #number<
353
+ TName extends PropertyKey,
354
+ TType extends NumberFieldVariant = NumberFieldVariant,
355
+ TTypeScriptType = number,
356
+ >(name: TName, type: TType, typeScriptType?: TTypeScriptType) {
357
+ return this.field(
358
+ name,
359
+ new NumberFieldDefinition(type, typeScriptType),
360
+ );
361
+ }
362
+
363
+ /**
364
+ * Appends an `int8` field to the `Struct`
365
+ */
366
+ int8<TName extends PropertyKey, TTypeScriptType = number>(
367
+ name: TName,
368
+ typeScriptType?: TTypeScriptType,
369
+ ) {
370
+ return this.#number(name, NumberFieldVariant.Int8, typeScriptType);
371
+ }
372
+
373
+ /**
374
+ * Appends an `uint8` field to the `Struct`
375
+ */
376
+ uint8<TName extends PropertyKey, TTypeScriptType = number>(
377
+ name: TName,
378
+ typeScriptType?: TTypeScriptType,
379
+ ) {
380
+ return this.#number(name, NumberFieldVariant.Uint8, typeScriptType);
381
+ }
382
+
383
+ /**
384
+ * Appends an `int16` field to the `Struct`
385
+ */
386
+ int16<TName extends PropertyKey, TTypeScriptType = number>(
387
+ name: TName,
388
+ typeScriptType?: TTypeScriptType,
389
+ ) {
390
+ return this.#number(name, NumberFieldVariant.Int16, typeScriptType);
391
+ }
392
+
393
+ /**
394
+ * Appends an `uint16` field to the `Struct`
395
+ */
396
+ uint16<TName extends PropertyKey, TTypeScriptType = number>(
397
+ name: TName,
398
+ typeScriptType?: TTypeScriptType,
399
+ ) {
400
+ return this.#number(name, NumberFieldVariant.Uint16, typeScriptType);
401
+ }
402
+
403
+ /**
404
+ * Appends an `int32` field to the `Struct`
405
+ */
406
+ int32<TName extends PropertyKey, TTypeScriptType = number>(
407
+ name: TName,
408
+ typeScriptType?: TTypeScriptType,
409
+ ) {
410
+ return this.#number(name, NumberFieldVariant.Int32, typeScriptType);
411
+ }
412
+
413
+ /**
414
+ * Appends an `uint32` field to the `Struct`
415
+ */
416
+ uint32<TName extends PropertyKey, TTypeScriptType = number>(
417
+ name: TName,
418
+ typeScriptType?: TTypeScriptType,
419
+ ) {
420
+ return this.#number(name, NumberFieldVariant.Uint32, typeScriptType);
421
+ }
422
+
423
+ #bigint<
424
+ TName extends PropertyKey,
425
+ TType extends BigIntFieldVariant = BigIntFieldVariant,
426
+ TTypeScriptType = TType["TTypeScriptType"],
427
+ >(name: TName, type: TType, typeScriptType?: TTypeScriptType) {
428
+ return this.field(
429
+ name,
430
+ new BigIntFieldDefinition(type, typeScriptType),
431
+ );
432
+ }
433
+
434
+ /**
435
+ * Appends an `int64` field to the `Struct`
436
+ *
437
+ * Requires native `BigInt` support
438
+ */
439
+ int64<
440
+ TName extends PropertyKey,
441
+ TTypeScriptType = BigIntFieldVariant["TTypeScriptType"],
442
+ >(name: TName, typeScriptType?: TTypeScriptType) {
443
+ return this.#bigint(name, BigIntFieldVariant.Int64, typeScriptType);
444
+ }
445
+
446
+ /**
447
+ * Appends an `uint64` field to the `Struct`
448
+ *
449
+ * Requires native `BigInt` support
450
+ */
451
+ uint64<
452
+ TName extends PropertyKey,
453
+ TTypeScriptType = BigIntFieldVariant["TTypeScriptType"],
454
+ >(name: TName, typeScriptType?: TTypeScriptType) {
455
+ return this.#bigint(name, BigIntFieldVariant.Uint64, typeScriptType);
456
+ }
457
+
458
+ #arrayBufferLike: ArrayBufferLikeFieldCreator<
459
+ TFields,
460
+ TOmitInitKey,
461
+ TExtra,
462
+ TPostDeserialized
463
+ > = (
464
+ name: PropertyKey,
465
+ type: BufferFieldConverter,
466
+ options:
467
+ | FixedLengthBufferLikeFieldOptions
468
+ | VariableLengthBufferLikeFieldOptions,
469
+ ): never => {
470
+ if ("length" in options) {
471
+ return this.field(
472
+ name,
473
+ new FixedLengthBufferLikeFieldDefinition(type, options),
474
+ ) as never;
475
+ } else {
476
+ return this.field(
477
+ name,
478
+ new VariableLengthBufferLikeFieldDefinition(type, options),
479
+ ) as never;
480
+ }
481
+ };
482
+
483
+ uint8Array: BoundArrayBufferLikeFieldDefinitionCreator<
484
+ TFields,
485
+ TOmitInitKey,
486
+ TExtra,
487
+ TPostDeserialized,
488
+ Uint8ArrayBufferFieldConverter
489
+ > = (
490
+ name: PropertyKey,
491
+ options: unknown,
492
+ typeScriptType: unknown,
493
+ ): never => {
494
+ return this.#arrayBufferLike(
495
+ name,
496
+ Uint8ArrayBufferFieldConverter.Instance,
497
+ options as never,
498
+ typeScriptType,
499
+ ) as never;
500
+ };
501
+
502
+ string: BoundArrayBufferLikeFieldDefinitionCreator<
503
+ TFields,
504
+ TOmitInitKey,
505
+ TExtra,
506
+ TPostDeserialized,
507
+ StringBufferFieldConverter
508
+ > = (
509
+ name: PropertyKey,
510
+ options: unknown,
511
+ typeScriptType: unknown,
512
+ ): never => {
513
+ return this.#arrayBufferLike(
514
+ name,
515
+ StringBufferFieldConverter.Instance,
516
+ options as never,
517
+ typeScriptType,
518
+ ) as never;
519
+ };
520
+
521
+ /**
522
+ * Adds some extra properties into every `Struct` value.
523
+ *
524
+ * Extra properties will not affect serialize or deserialize process.
525
+ *
526
+ * Multiple calls to `extra` will merge all properties together.
527
+ *
528
+ * @param value
529
+ * An object containing properties to be added to the result value. Accessors and methods are also allowed.
530
+ */
531
+ extra<
532
+ T extends Record<
533
+ // This trick disallows any keys that are already in `TValue`
534
+ Exclude<keyof T, Exclude<keyof T, keyof TFields>>,
535
+ never
536
+ >,
537
+ >(
538
+ value: T & ThisType<Overwrite<Overwrite<TExtra, T>, TFields>>,
539
+ ): Struct<TFields, TOmitInitKey, Overwrite<TExtra, T>, TPostDeserialized> {
540
+ Object.defineProperties(
541
+ this.#extra,
542
+ Object.getOwnPropertyDescriptors(value),
543
+ );
544
+ return this as never;
545
+ }
546
+
547
+ /**
548
+ * Registers (or replaces) a custom callback to be run after deserialized.
549
+ *
550
+ * A callback returning `never` (always throw an error)
551
+ * will also change the return type of `deserialize` to `never`.
552
+ */
553
+ postDeserialize(
554
+ callback: StructPostDeserialized<TFields, never>,
555
+ ): Struct<TFields, TOmitInitKey, TExtra, never>;
556
+ /**
557
+ * Registers (or replaces) a custom callback to be run after deserialized.
558
+ *
559
+ * A callback returning `void` means it modify the result object in-place
560
+ * (or doesn't modify it at all), so `deserialize` will still return the result object.
561
+ */
562
+ postDeserialize(
563
+ callback?: StructPostDeserialized<TFields, void>,
564
+ ): Struct<TFields, TOmitInitKey, TExtra, undefined>;
565
+ /**
566
+ * Registers (or replaces) a custom callback to be run after deserialized.
567
+ *
568
+ * A callback returning anything other than `undefined`
569
+ * will `deserialize` to return that object instead.
570
+ */
571
+ postDeserialize<TPostSerialize>(
572
+ callback?: StructPostDeserialized<TFields, TPostSerialize>,
573
+ ): Struct<TFields, TOmitInitKey, TExtra, TPostSerialize>;
574
+ postDeserialize(callback?: StructPostDeserialized<TFields, unknown>) {
575
+ this.#postDeserialized = callback;
576
+ return this as never;
577
+ }
578
+
579
+ /**
580
+ * Deserialize a struct value from `stream`.
581
+ */
582
+ deserialize(
583
+ stream: ExactReadable,
584
+ ): StructDeserializedResult<TFields, TExtra, TPostDeserialized>;
585
+ deserialize(
586
+ stream: AsyncExactReadable,
587
+ ): Promise<StructDeserializedResult<TFields, TExtra, TPostDeserialized>>;
588
+ deserialize(
589
+ stream: ExactReadable | AsyncExactReadable,
590
+ ): ValueOrPromise<
591
+ StructDeserializedResult<TFields, TExtra, TPostDeserialized>
592
+ > {
593
+ const structValue = new StructValue(this.#extra);
594
+
595
+ let promise = SyncPromise.resolve();
596
+
597
+ const startPosition = stream.position;
598
+ for (const [name, definition] of this.#fields) {
599
+ promise = promise
600
+ .then(() =>
601
+ definition.deserialize(this.options, stream, structValue),
602
+ )
603
+ .then(
604
+ (fieldValue) => {
605
+ structValue.set(name, fieldValue);
606
+ },
607
+ (e) => {
608
+ if (!(e instanceof ExactReadableEndedError)) {
609
+ throw e;
610
+ }
611
+
612
+ if (stream.position === startPosition) {
613
+ throw new StructEmptyError();
614
+ } else {
615
+ throw new StructNotEnoughDataError();
616
+ }
617
+ },
618
+ );
619
+ }
620
+
621
+ return promise
622
+ .then(() => {
623
+ const value = structValue.value;
624
+
625
+ // Run `postDeserialized`
626
+ if (this.#postDeserialized) {
627
+ const override = this.#postDeserialized.call(
628
+ value as never,
629
+ value as never,
630
+ );
631
+ // If it returns a new value, use that as result
632
+ // Otherwise it only inspects/mutates the object in place.
633
+ if (override !== undefined) {
634
+ return override as never;
635
+ }
636
+ }
637
+
638
+ return value as never;
639
+ })
640
+ .valueOrPromise();
641
+ }
642
+
643
+ /**
644
+ * Serialize a struct value to a buffer.
645
+ * @param init Fields of the struct
646
+ * @param output The buffer to serialize the struct to. It must be large enough to hold the entire struct. If not provided, a new buffer will be created.
647
+ * @returns A view of `output` that contains the serialized struct, or a new buffer if `output` is not provided.
648
+ */
649
+ serialize(
650
+ init: Evaluate<Omit<TFields, TOmitInitKey>>,
651
+ output?: Uint8Array,
652
+ ): Uint8Array {
653
+ let structValue: StructValue;
654
+ if (isStructValueInit(init)) {
655
+ structValue = init[STRUCT_VALUE_SYMBOL];
656
+ for (const [key, value] of Object.entries(init)) {
657
+ const fieldValue = structValue.get(key);
658
+ if (fieldValue) {
659
+ fieldValue.set(value);
660
+ }
661
+ }
662
+ } else {
663
+ structValue = new StructValue({});
664
+ for (const [name, definition] of this.#fields) {
665
+ const fieldValue = definition.create(
666
+ this.options,
667
+ structValue,
668
+ (init as Record<PropertyKey, unknown>)[name],
669
+ );
670
+ structValue.set(name, fieldValue);
671
+ }
672
+ }
673
+
674
+ let structSize = 0;
675
+ const fieldsInfo: {
676
+ fieldValue: StructFieldValue<any>;
677
+ size: number;
678
+ }[] = [];
679
+
680
+ for (const [name] of this.#fields) {
681
+ const fieldValue = structValue.get(name);
682
+ const size = fieldValue.getSize();
683
+ fieldsInfo.push({ fieldValue, size });
684
+ structSize += size;
685
+ }
686
+
687
+ if (!output) {
688
+ output = new Uint8Array(structSize);
689
+ } else if (output.length < structSize) {
690
+ throw new TypeError("Output buffer is too small");
691
+ }
692
+
693
+ let offset = 0;
694
+ for (const { fieldValue, size } of fieldsInfo) {
695
+ fieldValue.serialize(output, offset);
696
+ offset += size;
697
+ }
698
+
699
+ if (output.length !== structSize) {
700
+ return output.subarray(0, structSize);
701
+ } else {
702
+ return output;
703
+ }
704
+ }
705
+ }