@yume-chan/struct 0.0.19 → 0.0.20

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 (42) hide show
  1. package/CHANGELOG.json +21 -0
  2. package/CHANGELOG.md +11 -1
  3. package/README.md +126 -143
  4. package/esm/basic/definition.d.ts +3 -3
  5. package/esm/basic/definition.d.ts.map +1 -1
  6. package/esm/basic/definition.js.map +1 -1
  7. package/esm/basic/stream.d.ts +11 -6
  8. package/esm/basic/stream.d.ts.map +1 -1
  9. package/esm/basic/stream.js +7 -1
  10. package/esm/basic/stream.js.map +1 -1
  11. package/esm/index.d.ts +1 -0
  12. package/esm/index.d.ts.map +1 -1
  13. package/esm/index.js +1 -0
  14. package/esm/index.js.map +1 -1
  15. package/esm/struct.d.ts +19 -9
  16. package/esm/struct.d.ts.map +1 -1
  17. package/esm/struct.js +57 -25
  18. package/esm/struct.js.map +1 -1
  19. package/esm/sync-promise.js +12 -12
  20. package/esm/sync-promise.js.map +1 -1
  21. package/esm/types/bigint.d.ts +3 -3
  22. package/esm/types/bigint.d.ts.map +1 -1
  23. package/esm/types/bigint.js +1 -1
  24. package/esm/types/bigint.js.map +1 -1
  25. package/esm/types/buffer/base.d.ts +3 -3
  26. package/esm/types/buffer/base.d.ts.map +1 -1
  27. package/esm/types/buffer/base.js +1 -1
  28. package/esm/types/buffer/base.js.map +1 -1
  29. package/esm/types/number.d.ts +3 -3
  30. package/esm/types/number.d.ts.map +1 -1
  31. package/esm/types/number.js +14 -16
  32. package/esm/types/number.js.map +1 -1
  33. package/package.json +6 -6
  34. package/src/basic/definition.ts +3 -6
  35. package/src/basic/stream.ts +17 -6
  36. package/src/index.ts +1 -0
  37. package/src/struct.ts +79 -46
  38. package/src/sync-promise.ts +12 -12
  39. package/src/types/bigint.ts +6 -6
  40. package/src/types/buffer/base.ts +6 -6
  41. package/src/types/number.ts +23 -27
  42. package/tsconfig.build.tsbuildinfo +1 -1
@@ -1,9 +1,6 @@
1
1
  import type { StructFieldValue } from "./field-value.js";
2
2
  import type { StructOptions } from "./options.js";
3
- import type {
4
- StructAsyncDeserializeStream,
5
- StructDeserializeStream,
6
- } from "./stream.js";
3
+ import type { AsyncExactReadable, ExactReadable } from "./stream.js";
7
4
  import type { StructValue } from "./struct-value.js";
8
5
 
9
6
  /**
@@ -60,12 +57,12 @@ export abstract class StructFieldDefinition<
60
57
  */
61
58
  public abstract deserialize(
62
59
  options: Readonly<StructOptions>,
63
- stream: StructDeserializeStream,
60
+ stream: ExactReadable,
64
61
  structValue: StructValue
65
62
  ): StructFieldValue<this>;
66
63
  public abstract deserialize(
67
64
  options: Readonly<StructOptions>,
68
- stream: StructAsyncDeserializeStream,
65
+ stream: AsyncExactReadable,
69
66
  struct: StructValue
70
67
  ): Promise<StructFieldValue<this>>;
71
68
  }
@@ -2,22 +2,33 @@ import type { ValueOrPromise } from "../utils.js";
2
2
 
3
3
  // TODO: allow over reading (returning a `Uint8Array`, an `offset` and a `length`) to avoid copying
4
4
 
5
- export interface StructDeserializeStream {
5
+ export class ExactReadableEndedError extends Error {
6
+ public constructor() {
7
+ super("ExactReadable ended");
8
+ Object.setPrototypeOf(this, new.target.prototype);
9
+ }
10
+ }
11
+
12
+ export interface ExactReadable {
13
+ readonly position: number;
14
+
6
15
  /**
7
16
  * Read data from the underlying data source.
8
17
  *
9
18
  * The stream must return exactly `length` bytes or data. If that's not possible
10
- * (due to end of file or other error condition), it must throw an error.
19
+ * (due to end of file or other error condition), it must throw an {@link ExactReadableEndedError}.
11
20
  */
12
- read(length: number): Uint8Array;
21
+ readExactly(length: number): Uint8Array;
13
22
  }
14
23
 
15
- export interface StructAsyncDeserializeStream {
24
+ export interface AsyncExactReadable {
25
+ readonly position: number;
26
+
16
27
  /**
17
28
  * Read data from the underlying data source.
18
29
  *
19
30
  * The stream must return exactly `length` bytes or data. If that's not possible
20
- * (due to end of file or other error condition), it must throw an error.
31
+ * (due to end of file or other error condition), it must throw an {@link ExactReadableEndedError}.
21
32
  */
22
- read(length: number): ValueOrPromise<Uint8Array>;
33
+ readExactly(length: number): ValueOrPromise<Uint8Array>;
23
34
  }
package/src/index.ts CHANGED
@@ -13,5 +13,6 @@ declare global {
13
13
  export * from "./basic/index.js";
14
14
  export * from "./struct.js";
15
15
  export { Struct as default } from "./struct.js";
16
+ export * from "./sync-promise.js";
16
17
  export * from "./types/index.js";
17
18
  export * from "./utils.js";
package/src/struct.ts CHANGED
@@ -1,11 +1,12 @@
1
1
  import type {
2
- StructAsyncDeserializeStream,
3
- StructDeserializeStream,
2
+ AsyncExactReadable,
3
+ ExactReadable,
4
4
  StructFieldDefinition,
5
5
  StructFieldValue,
6
6
  StructOptions,
7
7
  } from "./basic/index.js";
8
8
  import {
9
+ ExactReadableEndedError,
9
10
  STRUCT_VALUE_SYMBOL,
10
11
  StructDefaultOptions,
11
12
  StructValue,
@@ -30,9 +31,7 @@ import {
30
31
  import type { Evaluate, Identity, Overwrite, ValueOrPromise } from "./utils.js";
31
32
 
32
33
  export interface StructLike<TValue> {
33
- deserialize(
34
- stream: StructDeserializeStream | StructAsyncDeserializeStream
35
- ): Promise<TValue>;
34
+ deserialize(stream: ExactReadable | AsyncExactReadable): Promise<TValue>;
36
35
  }
37
36
 
38
37
  /**
@@ -192,6 +191,27 @@ export type StructDeserializedResult<
192
191
  ? Overwrite<TExtra, TFields>
193
192
  : TPostDeserialized;
194
193
 
194
+ export class StructDeserializeError extends Error {
195
+ public constructor(message: string) {
196
+ super(message);
197
+ Object.setPrototypeOf(this, new.target.prototype);
198
+ }
199
+ }
200
+
201
+ export class StructNotEnoughDataError extends StructDeserializeError {
202
+ public constructor() {
203
+ super(
204
+ "The underlying readable was ended before the struct was fully deserialized"
205
+ );
206
+ }
207
+ }
208
+
209
+ export class StructEmptyError extends StructDeserializeError {
210
+ public constructor() {
211
+ super("The underlying readable doesn't contain any more struct");
212
+ }
213
+ }
214
+
195
215
  export class Struct<
196
216
  TFields extends object = Record<never, never>,
197
217
  TOmitInitKey extends PropertyKey = never,
@@ -218,22 +238,28 @@ export class Struct<
218
238
 
219
239
  public readonly options: Readonly<StructOptions>;
220
240
 
221
- private _size = 0;
241
+ #size = 0;
222
242
  /**
223
243
  * Gets the static size (exclude fields that can change size at runtime)
224
244
  */
225
245
  public get size() {
226
- return this._size;
246
+ return this.#size;
227
247
  }
228
248
 
229
- private _fields: [
249
+ #fields: [
230
250
  name: PropertyKey,
231
251
  definition: StructFieldDefinition<any, any, any>
232
252
  ][] = [];
253
+ public get fields(): readonly [
254
+ name: PropertyKey,
255
+ definition: StructFieldDefinition<any, any, any>
256
+ ][] {
257
+ return this.#fields;
258
+ }
233
259
 
234
- private _extra: Record<PropertyKey, unknown> = {};
260
+ #extra: Record<PropertyKey, unknown> = {};
235
261
 
236
- private _postDeserialized?: StructPostDeserialized<any, any> | undefined;
262
+ #postDeserialized?: StructPostDeserialized<any, any> | undefined;
237
263
 
238
264
  public constructor(options?: Partial<Readonly<StructOptions>>) {
239
265
  this.options = { ...StructDefaultOptions, ...options };
@@ -256,20 +282,20 @@ export class Struct<
256
282
  TName,
257
283
  TDefinition
258
284
  > {
259
- for (const field of this._fields) {
285
+ for (const field of this.#fields) {
260
286
  if (field[0] === name) {
287
+ // Convert Symbol to string
288
+ const nameString = String(name);
261
289
  throw new Error(
262
- `This struct already have a field with name '${String(
263
- name
264
- )}'`
290
+ `This struct already have a field with name '${nameString}'`
265
291
  );
266
292
  }
267
293
  }
268
294
 
269
- this._fields.push([name, definition]);
295
+ this.#fields.push([name, definition]);
270
296
 
271
297
  const size = definition.getSize();
272
- this._size += size;
298
+ this.#size += size;
273
299
 
274
300
  // Force cast `this` to another type
275
301
  return this as any;
@@ -278,7 +304,7 @@ export class Struct<
278
304
  /**
279
305
  * Merges (flats) another `Struct`'s fields and extra fields into this one.
280
306
  */
281
- public fields<TOther extends Struct<any, any, any, any>>(
307
+ public concat<TOther extends Struct<any, any, any, any>>(
282
308
  other: TOther
283
309
  ): Struct<
284
310
  TFields & TOther["TFields"],
@@ -286,13 +312,13 @@ export class Struct<
286
312
  TExtra & TOther["TExtra"],
287
313
  TPostDeserialized
288
314
  > {
289
- for (const field of other._fields) {
290
- this._fields.push(field);
315
+ for (const field of other.#fields) {
316
+ this.#fields.push(field);
291
317
  }
292
- this._size += other._size;
318
+ this.#size += other.#size;
293
319
  Object.defineProperties(
294
- this._extra,
295
- Object.getOwnPropertyDescriptors(other._extra)
320
+ this.#extra,
321
+ Object.getOwnPropertyDescriptors(other.#extra)
296
322
  );
297
323
  return this as any;
298
324
  }
@@ -478,7 +504,7 @@ export class Struct<
478
504
  value: T & ThisType<Overwrite<Overwrite<TExtra, T>, TFields>>
479
505
  ): Struct<TFields, TOmitInitKey, Overwrite<TExtra, T>, TPostDeserialized> {
480
506
  Object.defineProperties(
481
- this._extra,
507
+ this.#extra,
482
508
  Object.getOwnPropertyDescriptors(value)
483
509
  );
484
510
  return this as any;
@@ -512,7 +538,7 @@ export class Struct<
512
538
  callback?: StructPostDeserialized<TFields, TPostSerialize>
513
539
  ): Struct<TFields, TOmitInitKey, TExtra, TPostSerialize>;
514
540
  public postDeserialize(callback?: StructPostDeserialized<TFields, any>) {
515
- this._postDeserialized = callback;
541
+ this.#postDeserialized = callback;
516
542
  return this as any;
517
543
  }
518
544
 
@@ -520,44 +546,51 @@ export class Struct<
520
546
  * Deserialize a struct value from `stream`.
521
547
  */
522
548
  public deserialize(
523
- stream: StructDeserializeStream
549
+ stream: ExactReadable
524
550
  ): StructDeserializedResult<TFields, TExtra, TPostDeserialized>;
525
551
  public deserialize(
526
- stream: StructAsyncDeserializeStream
552
+ stream: AsyncExactReadable
527
553
  ): Promise<StructDeserializedResult<TFields, TExtra, TPostDeserialized>>;
528
554
  public deserialize(
529
- stream: StructDeserializeStream | StructAsyncDeserializeStream
555
+ stream: ExactReadable | AsyncExactReadable
530
556
  ): ValueOrPromise<
531
557
  StructDeserializedResult<TFields, TExtra, TPostDeserialized>
532
558
  > {
533
- const structValue = new StructValue(this._extra);
559
+ const structValue = new StructValue(this.#extra);
534
560
 
535
561
  let promise = SyncPromise.resolve();
536
562
 
537
- for (const [name, definition] of this._fields) {
563
+ const startPosition = stream.position;
564
+ for (const [name, definition] of this.#fields) {
538
565
  promise = promise
539
566
  .then(() =>
540
- definition.deserialize(
541
- this.options,
542
- stream as any,
543
- structValue
544
- )
567
+ definition.deserialize(this.options, stream, structValue)
545
568
  )
546
- .then((fieldValue) => {
547
- structValue.set(name, fieldValue);
548
- });
569
+ .then(
570
+ (fieldValue) => {
571
+ structValue.set(name, fieldValue);
572
+ },
573
+ (e) => {
574
+ if (!(e instanceof ExactReadableEndedError)) {
575
+ throw e;
576
+ }
577
+
578
+ if (stream.position === startPosition) {
579
+ throw new StructEmptyError();
580
+ } else {
581
+ throw new StructNotEnoughDataError();
582
+ }
583
+ }
584
+ );
549
585
  }
550
586
 
551
587
  return promise
552
588
  .then(() => {
553
- const object = structValue.value;
589
+ const value = structValue.value;
554
590
 
555
591
  // Run `postDeserialized`
556
- if (this._postDeserialized) {
557
- const override = this._postDeserialized.call(
558
- object,
559
- object
560
- );
592
+ if (this.#postDeserialized) {
593
+ const override = this.#postDeserialized.call(value, value);
561
594
  // If it returns a new value, use that as result
562
595
  // Otherwise it only inspects/mutates the object in place.
563
596
  if (override !== undefined) {
@@ -565,7 +598,7 @@ export class Struct<
565
598
  }
566
599
  }
567
600
 
568
- return object;
601
+ return value;
569
602
  })
570
603
  .valueOrPromise();
571
604
  }
@@ -590,7 +623,7 @@ export class Struct<
590
623
  }
591
624
  } else {
592
625
  structValue = new StructValue({});
593
- for (const [name, definition] of this._fields) {
626
+ for (const [name, definition] of this.#fields) {
594
627
  const fieldValue = definition.create(
595
628
  this.options,
596
629
  structValue,
@@ -603,7 +636,7 @@ export class Struct<
603
636
  let structSize = 0;
604
637
  const fieldsInfo: { fieldValue: StructFieldValue; size: number }[] = [];
605
638
 
606
- for (const [name] of this._fields) {
639
+ for (const [name] of this.#fields) {
607
640
  const fieldValue = structValue.get(name);
608
641
  const size = fieldValue.getSize();
609
642
  fieldsInfo.push({ fieldValue, size });
@@ -55,10 +55,10 @@ export const SyncPromise: SyncPromiseStatic = {
55
55
  };
56
56
 
57
57
  class PendingSyncPromise<T> implements SyncPromise<T> {
58
- private promise: PromiseLike<T>;
58
+ #promise: PromiseLike<T>;
59
59
 
60
60
  public constructor(promise: PromiseLike<T>) {
61
- this.promise = promise;
61
+ this.#promise = promise;
62
62
  }
63
63
 
64
64
  public then<TResult1 = T, TResult2 = never>(
@@ -72,20 +72,20 @@ class PendingSyncPromise<T> implements SyncPromise<T> {
72
72
  | undefined
73
73
  ) {
74
74
  return new PendingSyncPromise<TResult1 | TResult2>(
75
- this.promise.then(onfulfilled, onrejected)
75
+ this.#promise.then(onfulfilled, onrejected)
76
76
  );
77
77
  }
78
78
 
79
79
  public valueOrPromise(): T | PromiseLike<T> {
80
- return this.promise;
80
+ return this.#promise;
81
81
  }
82
82
  }
83
83
 
84
84
  class ResolvedSyncPromise<T> implements SyncPromise<T> {
85
- private value: T;
85
+ #value: T;
86
86
 
87
87
  public constructor(value: T) {
88
- this.value = value;
88
+ this.#value = value;
89
89
  }
90
90
 
91
91
  public then<TResult1 = T>(
@@ -97,19 +97,19 @@ class ResolvedSyncPromise<T> implements SyncPromise<T> {
97
97
  if (!onfulfilled) {
98
98
  return this as any;
99
99
  }
100
- return SyncPromise.try(() => onfulfilled(this.value));
100
+ return SyncPromise.try(() => onfulfilled(this.#value));
101
101
  }
102
102
 
103
103
  public valueOrPromise(): T | PromiseLike<T> {
104
- return this.value;
104
+ return this.#value;
105
105
  }
106
106
  }
107
107
 
108
108
  class RejectedSyncPromise<T> implements SyncPromise<T> {
109
- private reason: any;
109
+ #reason: any;
110
110
 
111
111
  public constructor(reason: any) {
112
- this.reason = reason;
112
+ this.#reason = reason;
113
113
  }
114
114
 
115
115
  public then<TResult1 = T, TResult2 = never>(
@@ -125,10 +125,10 @@ class RejectedSyncPromise<T> implements SyncPromise<T> {
125
125
  if (!onrejected) {
126
126
  return this as any;
127
127
  }
128
- return SyncPromise.try(() => onrejected(this.reason));
128
+ return SyncPromise.try(() => onrejected(this.#reason));
129
129
  }
130
130
 
131
131
  public valueOrPromise(): T | PromiseLike<T> {
132
- throw this.reason;
132
+ throw this.#reason;
133
133
  }
134
134
  }
@@ -6,8 +6,8 @@ import {
6
6
  } from "@yume-chan/dataview-bigint-polyfill/esm/fallback.js";
7
7
 
8
8
  import type {
9
- StructAsyncDeserializeStream,
10
- StructDeserializeStream,
9
+ AsyncExactReadable,
10
+ ExactReadable,
11
11
  StructOptions,
12
12
  StructValue,
13
13
  } from "../basic/index.js";
@@ -86,21 +86,21 @@ export class BigIntFieldDefinition<
86
86
 
87
87
  public override deserialize(
88
88
  options: Readonly<StructOptions>,
89
- stream: StructDeserializeStream,
89
+ stream: ExactReadable,
90
90
  struct: StructValue
91
91
  ): BigIntFieldValue<this>;
92
92
  public override deserialize(
93
93
  options: Readonly<StructOptions>,
94
- stream: StructAsyncDeserializeStream,
94
+ stream: AsyncExactReadable,
95
95
  struct: StructValue
96
96
  ): Promise<BigIntFieldValue<this>>;
97
97
  public override deserialize(
98
98
  options: Readonly<StructOptions>,
99
- stream: StructDeserializeStream | StructAsyncDeserializeStream,
99
+ stream: ExactReadable | AsyncExactReadable,
100
100
  struct: StructValue
101
101
  ): ValueOrPromise<BigIntFieldValue<this>> {
102
102
  return SyncPromise.try(() => {
103
- return stream.read(this.getSize());
103
+ return stream.readExactly(this.getSize());
104
104
  })
105
105
  .then((array) => {
106
106
  const view = new DataView(
@@ -1,6 +1,6 @@
1
1
  import type {
2
- StructAsyncDeserializeStream,
3
- StructDeserializeStream,
2
+ AsyncExactReadable,
3
+ ExactReadable,
4
4
  StructOptions,
5
5
  StructValue,
6
6
  } from "../../basic/index.js";
@@ -127,17 +127,17 @@ export abstract class BufferLikeFieldDefinition<
127
127
 
128
128
  public override deserialize(
129
129
  options: Readonly<StructOptions>,
130
- stream: StructDeserializeStream,
130
+ stream: ExactReadable,
131
131
  struct: StructValue
132
132
  ): BufferLikeFieldValue<this>;
133
133
  public override deserialize(
134
134
  options: Readonly<StructOptions>,
135
- stream: StructAsyncDeserializeStream,
135
+ stream: AsyncExactReadable,
136
136
  struct: StructValue
137
137
  ): Promise<BufferLikeFieldValue<this>>;
138
138
  public override deserialize(
139
139
  options: Readonly<StructOptions>,
140
- stream: StructDeserializeStream | StructAsyncDeserializeStream,
140
+ stream: ExactReadable | AsyncExactReadable,
141
141
  struct: StructValue
142
142
  ): ValueOrPromise<BufferLikeFieldValue<this>> {
143
143
  return SyncPromise.try(() => {
@@ -145,7 +145,7 @@ export abstract class BufferLikeFieldDefinition<
145
145
  if (size === 0) {
146
146
  return EMPTY_UINT8_ARRAY;
147
147
  } else {
148
- return stream.read(size);
148
+ return stream.readExactly(size);
149
149
  }
150
150
  })
151
151
  .then((array) => {
@@ -1,6 +1,6 @@
1
1
  import type {
2
- StructAsyncDeserializeStream,
3
- StructDeserializeStream,
2
+ AsyncExactReadable,
3
+ ExactReadable,
4
4
  StructOptions,
5
5
  StructValue,
6
6
  } from "../basic/index.js";
@@ -49,15 +49,12 @@ export namespace NumberFieldType {
49
49
  signed: false,
50
50
  size: 2,
51
51
  deserialize(array, littleEndian) {
52
- // PERF: Chrome's `DataView#getUint16` uses inefficient operations,
53
- // including branching, bit extending and 32-bit bit swapping.
54
- // The best way should use 16-bit bit rotation and conditional move,
55
- // like LLVM does for code similar to the below one.
56
- // This code is much faster on V8, but the actual generated assembly is unknown.
57
- return (
58
- (((array[1]! << 8) | array[0]!) * (littleEndian as any)) |
59
- (((array[0]! << 8) | array[1]!) * (!littleEndian as any))
60
- );
52
+ // PERF: Creating many `DataView`s over small buffers is 90% slower
53
+ // than this. Even if the `DataView` is cached, `DataView#getUint16`
54
+ // is still 1% slower than this.
55
+ const a = (array[1]! << 8) | array[0]!;
56
+ const b = (array[0]! << 8) | array[1]!;
57
+ return littleEndian ? a : b;
61
58
  },
62
59
  serialize(dataView, offset, value, littleEndian) {
63
60
  dataView.setUint16(offset, value, littleEndian);
@@ -92,18 +89,17 @@ export namespace NumberFieldType {
92
89
  signed: true,
93
90
  size: 4,
94
91
  deserialize(array, littleEndian) {
95
- return (
96
- (((array[3]! << 24) |
97
- (array[2]! << 16) |
98
- (array[1]! << 8) |
99
- array[0]!) *
100
- (littleEndian as any)) |
101
- (((array[0]! << 24) |
102
- (array[1]! << 16) |
103
- (array[2]! << 8) |
104
- array[3]!) *
105
- (!littleEndian as any))
106
- );
92
+ const a =
93
+ (array[3]! << 24) |
94
+ (array[2]! << 16) |
95
+ (array[1]! << 8) |
96
+ array[0]!;
97
+ const b =
98
+ (array[0]! << 24) |
99
+ (array[1]! << 16) |
100
+ (array[2]! << 8) |
101
+ array[3]!;
102
+ return littleEndian ? a : b;
107
103
  },
108
104
  serialize(dataView, offset, value, littleEndian) {
109
105
  dataView.setInt32(offset, value, littleEndian);
@@ -137,21 +133,21 @@ export class NumberFieldDefinition<
137
133
 
138
134
  public override deserialize(
139
135
  options: Readonly<StructOptions>,
140
- stream: StructDeserializeStream,
136
+ stream: ExactReadable,
141
137
  struct: StructValue
142
138
  ): NumberFieldValue<this>;
143
139
  public override deserialize(
144
140
  options: Readonly<StructOptions>,
145
- stream: StructAsyncDeserializeStream,
141
+ stream: AsyncExactReadable,
146
142
  struct: StructValue
147
143
  ): Promise<NumberFieldValue<this>>;
148
144
  public override deserialize(
149
145
  options: Readonly<StructOptions>,
150
- stream: StructDeserializeStream | StructAsyncDeserializeStream,
146
+ stream: ExactReadable | AsyncExactReadable,
151
147
  struct: StructValue
152
148
  ): ValueOrPromise<NumberFieldValue<this>> {
153
149
  return SyncPromise.try(() => {
154
- return stream.read(this.getSize());
150
+ return stream.readExactly(this.getSize());
155
151
  })
156
152
  .then((array) => {
157
153
  const value = this.type.deserialize(