@yume-chan/struct 0.0.5 → 0.0.10

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