@yume-chan/struct 0.0.13 → 0.0.16

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