@yume-chan/struct 0.0.24 → 2.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 (150) hide show
  1. package/CHANGELOG.md +40 -1
  2. package/LICENSE +1 -1
  3. package/README.md +85 -714
  4. package/esm/bipedal.d.ts +6 -0
  5. package/esm/bipedal.d.ts.map +1 -0
  6. package/esm/bipedal.js +38 -0
  7. package/esm/bipedal.js.map +1 -0
  8. package/esm/buffer.d.ts +30 -0
  9. package/esm/buffer.d.ts.map +1 -0
  10. package/esm/buffer.js +170 -0
  11. package/esm/buffer.js.map +1 -0
  12. package/esm/concat.d.ts +13 -0
  13. package/esm/concat.d.ts.map +1 -0
  14. package/esm/concat.js +10 -0
  15. package/esm/concat.js.map +1 -0
  16. package/esm/extend.d.ts +6 -0
  17. package/esm/extend.d.ts.map +1 -0
  18. package/esm/extend.js +10 -0
  19. package/esm/extend.js.map +1 -0
  20. package/esm/field/factory.d.ts +13 -0
  21. package/esm/field/factory.d.ts.map +1 -0
  22. package/esm/field/factory.js +21 -0
  23. package/esm/field/factory.js.map +1 -0
  24. package/esm/field/index.d.ts +4 -0
  25. package/esm/field/index.d.ts.map +1 -0
  26. package/esm/field/index.js +4 -0
  27. package/esm/field/index.js.map +1 -0
  28. package/esm/field/serialize.d.ts +8 -0
  29. package/esm/field/serialize.d.ts.map +1 -0
  30. package/esm/field/serialize.js +33 -0
  31. package/esm/field/serialize.js.map +1 -0
  32. package/esm/field/types.d.ts +33 -0
  33. package/esm/field/types.d.ts.map +1 -0
  34. package/esm/field/types.js +2 -0
  35. package/esm/field/types.js.map +1 -0
  36. package/esm/index.d.ts +9 -4
  37. package/esm/index.d.ts.map +1 -1
  38. package/esm/index.js +9 -4
  39. package/esm/index.js.map +1 -1
  40. package/esm/number.d.ts +13 -0
  41. package/esm/number.d.ts.map +1 -0
  42. package/esm/number.js +57 -0
  43. package/esm/number.js.map +1 -0
  44. package/esm/{basic/stream.d.ts → readable.d.ts} +9 -3
  45. package/esm/readable.d.ts.map +1 -0
  46. package/esm/readable.js +26 -0
  47. package/esm/readable.js.map +1 -0
  48. package/esm/string.d.ts +16 -0
  49. package/esm/string.d.ts.map +1 -0
  50. package/esm/string.js +13 -0
  51. package/esm/string.js.map +1 -0
  52. package/esm/struct.d.ts +22 -140
  53. package/esm/struct.d.ts.map +1 -1
  54. package/esm/struct.js +109 -227
  55. package/esm/struct.js.map +1 -1
  56. package/esm/types.d.ts +20 -0
  57. package/esm/types.d.ts.map +1 -0
  58. package/esm/types.js +2 -0
  59. package/esm/types.js.map +1 -0
  60. package/esm/utils.d.ts +10 -45
  61. package/esm/utils.d.ts.map +1 -1
  62. package/esm/utils.js +8 -9
  63. package/esm/utils.js.map +1 -1
  64. package/package.json +11 -12
  65. package/src/bipedal.ts +65 -0
  66. package/src/buffer.ts +334 -0
  67. package/src/concat.ts +78 -0
  68. package/src/extend.ts +37 -0
  69. package/src/field/factory.ts +64 -0
  70. package/src/field/index.ts +3 -0
  71. package/src/field/serialize.ts +58 -0
  72. package/src/field/types.ts +48 -0
  73. package/src/index.ts +9 -4
  74. package/src/number.ts +134 -0
  75. package/src/{basic/stream.ts → readable.ts} +31 -4
  76. package/src/string.ts +41 -0
  77. package/src/struct.ts +200 -670
  78. package/src/types.ts +37 -0
  79. package/src/utils.ts +14 -67
  80. package/tsconfig.build.json +6 -1
  81. package/tsconfig.build.tsbuildinfo +1 -1
  82. package/CHANGELOG.json +0 -173
  83. package/esm/basic/definition.d.ts +0 -44
  84. package/esm/basic/definition.d.ts.map +0 -1
  85. package/esm/basic/definition.js +0 -24
  86. package/esm/basic/definition.js.map +0 -1
  87. package/esm/basic/field-value.d.ts +0 -39
  88. package/esm/basic/field-value.d.ts.map +0 -1
  89. package/esm/basic/field-value.js +0 -46
  90. package/esm/basic/field-value.js.map +0 -1
  91. package/esm/basic/index.d.ts +0 -6
  92. package/esm/basic/index.d.ts.map +0 -1
  93. package/esm/basic/index.js +0 -6
  94. package/esm/basic/index.js.map +0 -1
  95. package/esm/basic/options.d.ts +0 -10
  96. package/esm/basic/options.d.ts.map +0 -1
  97. package/esm/basic/options.js +0 -4
  98. package/esm/basic/options.js.map +0 -1
  99. package/esm/basic/stream.d.ts.map +0 -1
  100. package/esm/basic/stream.js +0 -8
  101. package/esm/basic/stream.js.map +0 -1
  102. package/esm/basic/struct-value.d.ts +0 -30
  103. package/esm/basic/struct-value.d.ts.map +0 -1
  104. package/esm/basic/struct-value.js +0 -62
  105. package/esm/basic/struct-value.js.map +0 -1
  106. package/esm/sync-promise.d.ts +0 -13
  107. package/esm/sync-promise.d.ts.map +0 -1
  108. package/esm/sync-promise.js +0 -71
  109. package/esm/sync-promise.js.map +0 -1
  110. package/esm/types/bigint.d.ts +0 -25
  111. package/esm/types/bigint.d.ts.map +0 -1
  112. package/esm/types/bigint.js +0 -46
  113. package/esm/types/bigint.js.map +0 -1
  114. package/esm/types/buffer/base.d.ts +0 -69
  115. package/esm/types/buffer/base.d.ts.map +0 -1
  116. package/esm/types/buffer/base.js +0 -100
  117. package/esm/types/buffer/base.js.map +0 -1
  118. package/esm/types/buffer/fixed-length.d.ts +0 -9
  119. package/esm/types/buffer/fixed-length.d.ts.map +0 -1
  120. package/esm/types/buffer/fixed-length.js +0 -7
  121. package/esm/types/buffer/fixed-length.js.map +0 -1
  122. package/esm/types/buffer/index.d.ts +0 -4
  123. package/esm/types/buffer/index.d.ts.map +0 -1
  124. package/esm/types/buffer/index.js +0 -4
  125. package/esm/types/buffer/index.js.map +0 -1
  126. package/esm/types/buffer/variable-length.d.ts +0 -46
  127. package/esm/types/buffer/variable-length.d.ts.map +0 -1
  128. package/esm/types/buffer/variable-length.js +0 -87
  129. package/esm/types/buffer/variable-length.js.map +0 -1
  130. package/esm/types/index.d.ts +0 -4
  131. package/esm/types/index.d.ts.map +0 -1
  132. package/esm/types/index.js +0 -4
  133. package/esm/types/index.js.map +0 -1
  134. package/esm/types/number.d.ts +0 -28
  135. package/esm/types/number.d.ts.map +0 -1
  136. package/esm/types/number.js +0 -99
  137. package/esm/types/number.js.map +0 -1
  138. package/src/basic/definition.ts +0 -68
  139. package/src/basic/field-value.ts +0 -75
  140. package/src/basic/index.ts +0 -5
  141. package/src/basic/options.ts +0 -19
  142. package/src/basic/struct-value.ts +0 -85
  143. package/src/sync-promise.ts +0 -134
  144. package/src/types/bigint.ts +0 -124
  145. package/src/types/buffer/base.ts +0 -198
  146. package/src/types/buffer/fixed-length.ts +0 -22
  147. package/src/types/buffer/index.ts +0 -3
  148. package/src/types/buffer/variable-length.ts +0 -198
  149. package/src/types/index.ts +0 -3
  150. package/src/types/number.ts +0 -166
package/src/struct.ts CHANGED
@@ -1,202 +1,50 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
-
1
+ import { bipedal } from "./bipedal.js";
3
2
  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";
3
+ Field,
4
+ FieldByobSerializeContext,
5
+ FieldDefaultSerializeContext,
6
+ FieldDeserializeContext,
7
+ FieldDeserializer,
8
+ } from "./field/index.js";
9
+ import type { AsyncExactReadable } from "./readable.js";
10
+ import { ExactReadableEndedError } from "./readable.js";
18
11
  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"]>
12
+ StructDeserializer,
13
+ StructSerializeContext,
14
+ StructSerializer,
15
+ } from "./types.js";
16
+
17
+ export type StructField =
18
+ | Field<unknown, string, unknown, unknown>
19
+ | (StructSerializer<unknown> & StructDeserializer<unknown>);
20
+
21
+ export type StructFields = Record<string, StructField>;
22
+
23
+ export type FieldsValue<T extends StructFields> = {
24
+ [K in keyof T]: T[K] extends FieldDeserializer<infer U, unknown>
25
+ ? U
26
+ : never;
27
+ };
28
+
29
+ export type FieldOmitInit<T extends StructField> =
30
+ T extends Field<unknown, infer U, unknown, unknown>
31
+ ? string extends U
32
+ ? never
33
+ : U
34
+ : never;
35
+
36
+ export type FieldsOmitInits<T extends StructFields> = {
37
+ [K in keyof T]: FieldOmitInit<T[K]>;
38
+ }[keyof T];
39
+
40
+ export type FieldsInit<T extends StructFields> = Omit<
41
+ FieldsValue<T>,
42
+ FieldsOmitInits<T>
45
43
  >;
46
44
 
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
45
  export class StructDeserializeError extends Error {
197
46
  constructor(message: string) {
198
47
  super(message);
199
- Object.setPrototypeOf(this, new.target.prototype);
200
48
  }
201
49
  }
202
50
 
@@ -214,497 +62,179 @@ export class StructEmptyError extends StructDeserializeError {
214
62
  }
215
63
  }
216
64
 
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>>;
65
+ export type ExtraToIntersection<
66
+ Extra extends Record<PropertyKey, unknown> | undefined,
67
+ > = Extra extends undefined ? unknown : Extra;
68
+
69
+ export interface Struct<
70
+ Fields extends StructFields,
71
+ Extra extends Record<PropertyKey, unknown> | undefined = undefined,
72
+ PostDeserialize = FieldsValue<Fields> & Extra,
73
+ > extends StructSerializer<FieldsInit<Fields>>,
74
+ StructDeserializer<PostDeserialize> {
75
+ littleEndian: boolean;
76
+ fields: Fields;
77
+ extra: Extra;
229
78
  }
230
79
 
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
- );
80
+ /* #__NO_SIDE_EFFECTS__ */
81
+ export function struct<
82
+ Fields extends Record<
83
+ string,
84
+ | Field<unknown, string, Partial<FieldsValue<Fields>>, unknown>
85
+ | (StructSerializer<unknown> & StructDeserializer<unknown>)
86
+ >,
87
+ Extra extends Record<PropertyKey, unknown> | undefined = undefined,
88
+ PostDeserialize = FieldsValue<Fields> & ExtraToIntersection<Extra>,
89
+ >(
90
+ fields: Fields,
91
+ options: {
92
+ littleEndian: boolean;
93
+ extra?: (Extra & ThisType<FieldsValue<Fields>>) | undefined;
94
+ postDeserialize?:
95
+ | ((
96
+ this: FieldsValue<Fields> & ExtraToIntersection<Extra>,
97
+ value: FieldsValue<Fields> & ExtraToIntersection<Extra>,
98
+ ) => PostDeserialize)
99
+ | undefined;
100
+ },
101
+ ): Struct<Fields, Extra, PostDeserialize> {
102
+ const fieldList = Object.entries(fields);
103
+ const size = fieldList.reduce((sum, [, field]) => sum + field.size, 0);
104
+
105
+ const littleEndian = options.littleEndian;
106
+ const extra = options.extra
107
+ ? Object.getOwnPropertyDescriptors(options.extra)
108
+ : undefined;
109
+
110
+ return {
111
+ littleEndian,
112
+ type: "byob",
113
+ fields,
114
+ size,
115
+ extra: options.extra,
116
+ serialize(
117
+ source: FieldsInit<Fields>,
118
+ bufferOrContext?: Uint8Array | StructSerializeContext,
119
+ ): Uint8Array | number {
120
+ const temp: Record<string, unknown> = { ...source };
121
+
122
+ for (const [key, field] of fieldList) {
123
+ if (key in temp && "init" in field) {
124
+ const result = field.init?.(temp[key], temp as never);
125
+ if (result !== undefined) {
126
+ temp[key] = result;
127
+ }
128
+ }
312
129
  }
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
130
 
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;
131
+ const sizes = new Array<number>(fieldList.length);
132
+ const buffers = new Array<Uint8Array | undefined>(fieldList.length);
133
+ {
134
+ const context: FieldDefaultSerializeContext = { littleEndian };
135
+ for (const [index, [key, field]] of fieldList.entries()) {
136
+ if (field.type === "byob") {
137
+ sizes[index] = field.size;
138
+ } else {
139
+ buffers[index] = field.serialize(temp[key], context);
140
+ sizes[index] = buffers[index].length;
635
141
  }
636
142
  }
143
+ }
637
144
 
638
- return value as never;
639
- })
640
- .valueOrPromise();
641
- }
145
+ const size = sizes.reduce((sum, size) => sum + size, 0);
642
146
 
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);
147
+ let externalBuffer: boolean;
148
+ let buffer: Uint8Array;
149
+ let index: number;
150
+ if (bufferOrContext instanceof Uint8Array) {
151
+ if (bufferOrContext.length < size) {
152
+ throw new Error("Buffer too small");
660
153
  }
154
+ externalBuffer = true;
155
+ buffer = bufferOrContext;
156
+ index = 0;
157
+ } else if (
158
+ typeof bufferOrContext === "object" &&
159
+ "buffer" in bufferOrContext
160
+ ) {
161
+ externalBuffer = true;
162
+ buffer = bufferOrContext.buffer;
163
+ index = bufferOrContext.index ?? 0;
164
+ if (buffer.length - index < size) {
165
+ throw new Error("Buffer too small");
166
+ }
167
+ } else {
168
+ externalBuffer = false;
169
+ buffer = new Uint8Array(size);
170
+ index = 0;
661
171
  }
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
172
 
674
- let structSize = 0;
675
- const fieldsInfo: {
676
- fieldValue: StructFieldValue<any>;
677
- size: number;
678
- }[] = [];
173
+ const context = {
174
+ buffer,
175
+ index,
176
+ littleEndian,
177
+ } satisfies FieldByobSerializeContext;
178
+ for (const [index, [key, field]] of fieldList.entries()) {
179
+ if (buffers[index]) {
180
+ buffer.set(buffers[index], context.index);
181
+ } else {
182
+ field.serialize(temp[key], context);
183
+ }
184
+ context.index += sizes[index]!;
185
+ }
679
186
 
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
- }
187
+ if (externalBuffer) {
188
+ return size;
189
+ } else {
190
+ return buffer;
191
+ }
192
+ },
193
+ deserialize: bipedal(function* (
194
+ this: Struct<Fields, Extra, PostDeserialize>,
195
+ then,
196
+ reader: AsyncExactReadable,
197
+ ) {
198
+ const startPosition = reader.position;
199
+
200
+ const result = {} as Record<string, unknown>;
201
+ const context: FieldDeserializeContext<
202
+ Partial<FieldsValue<Fields>>
203
+ > = {
204
+ dependencies: result as never,
205
+ littleEndian: littleEndian,
206
+ };
207
+
208
+ try {
209
+ for (const [key, field] of fieldList) {
210
+ result[key] = yield* then(
211
+ field.deserialize(reader, context),
212
+ );
213
+ }
214
+ } catch (e) {
215
+ if (!(e instanceof ExactReadableEndedError)) {
216
+ throw e;
217
+ }
686
218
 
687
- if (!output) {
688
- output = new Uint8Array(structSize);
689
- } else if (output.length < structSize) {
690
- throw new TypeError("Output buffer is too small");
691
- }
219
+ if (reader.position === startPosition) {
220
+ throw new StructEmptyError();
221
+ } else {
222
+ throw new StructNotEnoughDataError();
223
+ }
224
+ }
692
225
 
693
- const dataView = new DataView(
694
- output.buffer,
695
- output.byteOffset,
696
- output.byteLength,
697
- );
698
- let offset = 0;
699
- for (const { fieldValue, size } of fieldsInfo) {
700
- fieldValue.serialize(dataView, output, offset);
701
- offset += size;
702
- }
226
+ if (extra) {
227
+ Object.defineProperties(result, extra);
228
+ }
703
229
 
704
- if (output.length !== structSize) {
705
- return output.subarray(0, structSize);
706
- } else {
707
- return output;
708
- }
709
- }
230
+ if (options.postDeserialize) {
231
+ return options.postDeserialize.call(
232
+ result as never,
233
+ result as never,
234
+ );
235
+ } else {
236
+ return result;
237
+ }
238
+ }),
239
+ } as never;
710
240
  }