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