@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.
- package/CHANGELOG.md +131 -0
- package/LICENSE +21 -0
- package/README.md +747 -0
- package/esm/basic/definition.d.ts +44 -0
- package/esm/basic/definition.d.ts.map +1 -0
- package/esm/basic/definition.js +24 -0
- package/esm/basic/definition.js.map +1 -0
- package/esm/basic/field-value.d.ts +39 -0
- package/esm/basic/field-value.d.ts.map +1 -0
- package/esm/basic/field-value.js +46 -0
- package/esm/basic/field-value.js.map +1 -0
- package/esm/basic/index.d.ts +6 -0
- package/esm/basic/index.d.ts.map +1 -0
- package/esm/basic/index.js +6 -0
- package/esm/basic/index.js.map +1 -0
- package/esm/basic/options.d.ts +10 -0
- package/esm/basic/options.d.ts.map +1 -0
- package/esm/basic/options.js +4 -0
- package/esm/basic/options.js.map +1 -0
- package/esm/basic/stream.d.ts +25 -0
- package/esm/basic/stream.d.ts.map +1 -0
- package/esm/basic/stream.js +8 -0
- package/esm/basic/stream.js.map +1 -0
- package/esm/basic/struct-value.d.ts +30 -0
- package/esm/basic/struct-value.d.ts.map +1 -0
- package/esm/basic/struct-value.js +62 -0
- package/esm/basic/struct-value.js.map +1 -0
- package/esm/index.d.ts +15 -0
- package/esm/index.d.ts.map +1 -0
- package/esm/index.js +7 -0
- package/esm/index.js.map +1 -0
- package/esm/struct.d.ts +151 -0
- package/esm/struct.d.ts.map +1 -0
- package/esm/struct.js +255 -0
- package/esm/struct.js.map +1 -0
- package/esm/sync-promise.d.ts +13 -0
- package/esm/sync-promise.d.ts.map +1 -0
- package/esm/sync-promise.js +71 -0
- package/esm/sync-promise.js.map +1 -0
- package/esm/types/bigint.d.ts +25 -0
- package/esm/types/bigint.d.ts.map +1 -0
- package/esm/types/bigint.js +46 -0
- package/esm/types/bigint.js.map +1 -0
- package/esm/types/buffer/base.d.ts +69 -0
- package/esm/types/buffer/base.d.ts.map +1 -0
- package/esm/types/buffer/base.js +101 -0
- package/esm/types/buffer/base.js.map +1 -0
- package/esm/types/buffer/fixed-length.d.ts +9 -0
- package/esm/types/buffer/fixed-length.d.ts.map +1 -0
- package/esm/types/buffer/fixed-length.js +7 -0
- package/esm/types/buffer/fixed-length.js.map +1 -0
- package/esm/types/buffer/index.d.ts +4 -0
- package/esm/types/buffer/index.d.ts.map +1 -0
- package/esm/types/buffer/index.js +4 -0
- package/esm/types/buffer/index.js.map +1 -0
- package/esm/types/buffer/variable-length.d.ts +46 -0
- package/esm/types/buffer/variable-length.d.ts.map +1 -0
- package/esm/types/buffer/variable-length.js +88 -0
- package/esm/types/buffer/variable-length.js.map +1 -0
- package/esm/types/index.d.ts +4 -0
- package/esm/types/index.d.ts.map +1 -0
- package/esm/types/index.js +4 -0
- package/esm/types/index.js.map +1 -0
- package/esm/types/number.d.ts +28 -0
- package/esm/types/number.d.ts.map +1 -0
- package/esm/types/number.js +88 -0
- package/esm/types/number.js.map +1 -0
- package/esm/utils.d.ts +48 -0
- package/esm/utils.d.ts.map +1 -0
- package/esm/utils.js +18 -0
- package/esm/utils.js.map +1 -0
- package/package.json +49 -0
- package/src/basic/definition.ts +68 -0
- package/src/basic/field-value.ts +71 -0
- package/src/basic/index.ts +5 -0
- package/src/basic/options.ts +19 -0
- package/src/basic/stream.ts +34 -0
- package/src/basic/struct-value.ts +85 -0
- package/src/index.ts +18 -0
- package/src/struct.ts +705 -0
- package/src/sync-promise.ts +131 -0
- package/src/types/bigint.ts +120 -0
- package/src/types/buffer/base.ts +195 -0
- package/src/types/buffer/fixed-length.ts +22 -0
- package/src/types/buffer/index.ts +3 -0
- package/src/types/buffer/variable-length.ts +199 -0
- package/src/types/index.ts +3 -0
- package/src/types/number.ts +159 -0
- package/src/utils.ts +94 -0
- package/tsconfig.build.json +3 -0
- 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
|
+
}
|