@yume-chan/struct 1.0.0 → 2.0.1

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 (79) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/LICENSE +1 -1
  3. package/esm/bipedal.d.ts +2 -1
  4. package/esm/bipedal.d.ts.map +1 -1
  5. package/esm/bipedal.js +10 -3
  6. package/esm/bipedal.js.map +1 -1
  7. package/esm/buffer.d.ts +20 -11
  8. package/esm/buffer.d.ts.map +1 -1
  9. package/esm/buffer.js +137 -140
  10. package/esm/buffer.js.map +1 -1
  11. package/esm/concat.d.ts +13 -0
  12. package/esm/concat.d.ts.map +1 -0
  13. package/esm/concat.js +10 -0
  14. package/esm/concat.js.map +1 -0
  15. package/esm/extend.d.ts +6 -0
  16. package/esm/extend.d.ts.map +1 -0
  17. package/esm/extend.js +10 -0
  18. package/esm/extend.js.map +1 -0
  19. package/esm/field/factory.d.ts +13 -0
  20. package/esm/field/factory.d.ts.map +1 -0
  21. package/esm/field/factory.js +21 -0
  22. package/esm/field/factory.js.map +1 -0
  23. package/esm/field/index.d.ts +4 -0
  24. package/esm/field/index.d.ts.map +1 -0
  25. package/esm/field/index.js +4 -0
  26. package/esm/field/index.js.map +1 -0
  27. package/esm/field/serialize.d.ts +8 -0
  28. package/esm/field/serialize.d.ts.map +1 -0
  29. package/esm/field/serialize.js +33 -0
  30. package/esm/field/serialize.js.map +1 -0
  31. package/esm/field/types.d.ts +33 -0
  32. package/esm/field/types.d.ts.map +1 -0
  33. package/esm/field/types.js +2 -0
  34. package/esm/field/types.js.map +1 -0
  35. package/esm/index.d.ts +4 -1
  36. package/esm/index.d.ts.map +1 -1
  37. package/esm/index.js +4 -1
  38. package/esm/index.js.map +1 -1
  39. package/esm/number.d.ts +3 -3
  40. package/esm/number.d.ts.map +1 -1
  41. package/esm/number.js +29 -31
  42. package/esm/number.js.map +1 -1
  43. package/esm/readable.d.ts +6 -0
  44. package/esm/readable.d.ts.map +1 -1
  45. package/esm/readable.js +19 -0
  46. package/esm/readable.js.map +1 -1
  47. package/esm/string.d.ts +1 -1
  48. package/esm/string.d.ts.map +1 -1
  49. package/esm/string.js +4 -5
  50. package/esm/string.js.map +1 -1
  51. package/esm/struct.d.ts +20 -22
  52. package/esm/struct.d.ts.map +1 -1
  53. package/esm/struct.js +64 -20
  54. package/esm/struct.js.map +1 -1
  55. package/esm/types.d.ts +18 -0
  56. package/esm/types.d.ts.map +1 -0
  57. package/esm/types.js +2 -0
  58. package/esm/types.js.map +1 -0
  59. package/package.json +6 -6
  60. package/src/bipedal.ts +18 -8
  61. package/src/buffer.ts +236 -162
  62. package/src/concat.ts +78 -0
  63. package/src/extend.ts +37 -0
  64. package/src/field/factory.ts +64 -0
  65. package/src/field/index.ts +3 -0
  66. package/src/field/serialize.ts +58 -0
  67. package/src/field/types.ts +48 -0
  68. package/src/index.ts +4 -1
  69. package/src/number.ts +48 -37
  70. package/src/readable.ts +28 -0
  71. package/src/string.ts +6 -8
  72. package/src/struct.ts +140 -88
  73. package/src/types.ts +34 -0
  74. package/tsconfig.build.tsbuildinfo +1 -1
  75. package/esm/field.d.ts +0 -21
  76. package/esm/field.d.ts.map +0 -1
  77. package/esm/field.js +0 -2
  78. package/esm/field.js.map +0 -1
  79. package/src/field.ts +0 -27
package/src/concat.ts ADDED
@@ -0,0 +1,78 @@
1
+ import type { FieldsValue, Struct, StructFields } from "./struct.js";
2
+ import { struct } from "./struct.js";
3
+
4
+ type UnionToIntersection<U> = (
5
+ U extends unknown ? (x: U) => void : never
6
+ ) extends (x: infer I) => void
7
+ ? I
8
+ : never;
9
+
10
+ type As<T, U> = T extends infer V extends U ? V : never;
11
+
12
+ export type ConcatFields<
13
+ T extends Struct<
14
+ StructFields,
15
+ Record<PropertyKey, unknown> | undefined,
16
+ unknown
17
+ >[],
18
+ > = As<UnionToIntersection<T[number]["fields"]>, StructFields>;
19
+
20
+ type ConcatFieldValues<
21
+ T extends Struct<
22
+ StructFields,
23
+ Record<PropertyKey, unknown> | undefined,
24
+ unknown
25
+ >[],
26
+ > = FieldsValue<ConcatFields<T>>;
27
+
28
+ type ExtraToUnion<Extra extends Record<PropertyKey, unknown> | undefined> =
29
+ Extra extends undefined ? never : Extra;
30
+
31
+ export type ConcatExtras<
32
+ T extends Struct<
33
+ StructFields,
34
+ Record<PropertyKey, unknown> | undefined,
35
+ unknown
36
+ >[],
37
+ > = As<
38
+ UnionToIntersection<ExtraToUnion<T[number]["extra"]>>,
39
+ Record<PropertyKey, unknown>
40
+ >;
41
+
42
+ /* #__NO_SIDE_EFFECTS__ */
43
+ export function concat<
44
+ T extends Struct<
45
+ StructFields,
46
+ Record<PropertyKey, unknown> | undefined,
47
+ unknown
48
+ >[],
49
+ PostDeserialize = ConcatFieldValues<T> & ConcatExtras<T>,
50
+ >(
51
+ options: {
52
+ littleEndian: boolean;
53
+ postDeserialize?: (
54
+ this: ConcatFieldValues<T> & ConcatExtras<T>,
55
+ value: ConcatFieldValues<T> & ConcatExtras<T>,
56
+ ) => PostDeserialize;
57
+ },
58
+ ...structs: T
59
+ ): Struct<ConcatFields<T>, ConcatExtras<T>, PostDeserialize> {
60
+ return struct(
61
+ structs.reduce(
62
+ (fields, struct) => Object.assign(fields, struct.fields),
63
+ {},
64
+ ) as never,
65
+ {
66
+ littleEndian: options.littleEndian,
67
+ postDeserialize: options.postDeserialize,
68
+ extra: structs.reduce(
69
+ (extras, struct) =>
70
+ Object.defineProperties(
71
+ extras,
72
+ Object.getOwnPropertyDescriptors(struct.extra),
73
+ ),
74
+ {},
75
+ ) as never,
76
+ },
77
+ ) as never;
78
+ }
package/src/extend.ts ADDED
@@ -0,0 +1,37 @@
1
+ import type {
2
+ ExtraToIntersection,
3
+ FieldsValue,
4
+ Struct,
5
+ StructFields,
6
+ } from "./struct.js";
7
+ import { struct } from "./struct.js";
8
+
9
+ /* #__NO_SIDE_EFFECTS__ */
10
+ export function extend<
11
+ Base extends Struct<
12
+ StructFields,
13
+ Record<PropertyKey, unknown> | undefined,
14
+ unknown
15
+ >,
16
+ Fields extends StructFields,
17
+ PostDeserialize = FieldsValue<Base["fields"] & Fields> &
18
+ ExtraToIntersection<Base["extra"]>,
19
+ >(
20
+ base: Base,
21
+ fields: Fields,
22
+ options?: {
23
+ littleEndian?: boolean | undefined;
24
+ postDeserialize?: (
25
+ this: FieldsValue<Base["fields"] & Fields> &
26
+ ExtraToIntersection<Base["extra"]>,
27
+ value: FieldsValue<Base["fields"] & Fields> &
28
+ ExtraToIntersection<Base["extra"]>,
29
+ ) => PostDeserialize;
30
+ },
31
+ ): Struct<Base["fields"] & Fields, Base["extra"], PostDeserialize> {
32
+ return struct(Object.assign({}, base.fields, fields), {
33
+ littleEndian: options?.littleEndian ?? base.littleEndian,
34
+ extra: base.extra as never,
35
+ postDeserialize: options?.postDeserialize,
36
+ }) as never;
37
+ }
@@ -0,0 +1,64 @@
1
+ import type { BipedalGenerator } from "../bipedal.js";
2
+ import { bipedal } from "../bipedal.js";
3
+ import type { AsyncExactReadable } from "../readable.js";
4
+
5
+ import type {
6
+ ByobFieldSerializer,
7
+ DefaultFieldSerializer,
8
+ } from "./serialize.js";
9
+ import { byobFieldSerializer, defaultFieldSerializer } from "./serialize.js";
10
+ import type { Field, FieldDeserializeContext, FieldOptions } from "./types.js";
11
+
12
+ export type BipedalFieldDeserializer<T, D> = BipedalGenerator<
13
+ undefined,
14
+ T,
15
+ [reader: AsyncExactReadable, context: FieldDeserializeContext<D>]
16
+ >;
17
+
18
+ // eslint-disable-next-line @typescript-eslint/max-params
19
+ function _field<T, OmitInit extends string, D, Raw = T>(
20
+ size: number,
21
+ type: "default",
22
+ serialize: DefaultFieldSerializer<Raw>,
23
+ deserialize: BipedalFieldDeserializer<T, D>,
24
+ options?: FieldOptions<T, OmitInit, D, Raw>,
25
+ ): Field<T, OmitInit, D, Raw>;
26
+ // eslint-disable-next-line @typescript-eslint/max-params
27
+ function _field<T, OmitInit extends string, D, Raw = T>(
28
+ size: number,
29
+ type: "byob",
30
+ serialize: ByobFieldSerializer<Raw>,
31
+ deserialize: BipedalFieldDeserializer<T, D>,
32
+ options?: FieldOptions<T, OmitInit, D, Raw>,
33
+ ): Field<T, OmitInit, D, Raw>;
34
+ /* #__NO_SIDE_EFFECTS__ */
35
+ // eslint-disable-next-line @typescript-eslint/max-params
36
+ function _field<T, OmitInit extends string, D, Raw = T>(
37
+ size: number,
38
+ type: "default" | "byob",
39
+ serialize: DefaultFieldSerializer<Raw> | ByobFieldSerializer<Raw>,
40
+ deserialize: BipedalFieldDeserializer<T, D>,
41
+ options?: FieldOptions<T, OmitInit, D, Raw>,
42
+ ): Field<T, OmitInit, D, Raw> {
43
+ const field: Field<T, OmitInit, D, Raw> = {
44
+ size,
45
+ type: type,
46
+ serialize:
47
+ type === "default"
48
+ ? defaultFieldSerializer(
49
+ serialize as DefaultFieldSerializer<Raw>,
50
+ )
51
+ : byobFieldSerializer(
52
+ size,
53
+ serialize as ByobFieldSerializer<Raw>,
54
+ ),
55
+ deserialize: bipedal(deserialize) as never,
56
+ omitInit: options?.omitInit,
57
+ };
58
+ if (options?.init) {
59
+ field.init = options.init;
60
+ }
61
+ return field;
62
+ }
63
+
64
+ export const field = _field;
@@ -0,0 +1,3 @@
1
+ export * from "./factory.js";
2
+ export * from "./serialize.js";
3
+ export * from "./types.js";
@@ -0,0 +1,58 @@
1
+ import type {
2
+ FieldByobSerializeContext,
3
+ FieldDefaultSerializeContext,
4
+ FieldSerializer,
5
+ } from "./types.js";
6
+
7
+ export type DefaultFieldSerializer<T> = (
8
+ source: T,
9
+ context: FieldDefaultSerializeContext,
10
+ ) => Uint8Array;
11
+
12
+ /* Adapt default field serializer to universal field serializer */
13
+ export function defaultFieldSerializer<T>(
14
+ serializer: DefaultFieldSerializer<T>,
15
+ ): FieldSerializer<T>["serialize"] {
16
+ return (
17
+ source,
18
+ context: FieldDefaultSerializeContext | FieldByobSerializeContext,
19
+ ): never => {
20
+ if ("buffer" in context) {
21
+ const buffer = serializer(source, context);
22
+ context.buffer.set(buffer, context.index);
23
+ return buffer.length as never;
24
+ } else {
25
+ return serializer(source, context) as never;
26
+ }
27
+ };
28
+ }
29
+
30
+ export type ByobFieldSerializer<T> = (
31
+ source: T,
32
+ context: FieldByobSerializeContext & { index: number },
33
+ ) => void;
34
+
35
+ /* Adapt byob field serializer to universal field serializer */
36
+ export function byobFieldSerializer<T>(
37
+ size: number,
38
+ serializer: ByobFieldSerializer<T>,
39
+ ): FieldSerializer<T>["serialize"] {
40
+ return (
41
+ source,
42
+ context: FieldDefaultSerializeContext | FieldByobSerializeContext,
43
+ ): never => {
44
+ if ("buffer" in context) {
45
+ context.index ??= 0;
46
+ serializer(source, context as never);
47
+ return size as never;
48
+ } else {
49
+ const buffer = new Uint8Array(size);
50
+ serializer(source, {
51
+ buffer,
52
+ index: 0,
53
+ littleEndian: context.littleEndian,
54
+ });
55
+ return buffer as never;
56
+ }
57
+ };
58
+ }
@@ -0,0 +1,48 @@
1
+ import type { MaybePromiseLike } from "@yume-chan/async";
2
+
3
+ import type { AsyncExactReadable, ExactReadable } from "../readable.js";
4
+
5
+ export interface FieldDefaultSerializeContext {
6
+ littleEndian: boolean;
7
+ }
8
+
9
+ export interface FieldByobSerializeContext
10
+ extends FieldDefaultSerializeContext {
11
+ buffer: Uint8Array;
12
+ index?: number;
13
+ }
14
+
15
+ export interface FieldSerializer<T> {
16
+ type: "default" | "byob";
17
+ size: number;
18
+
19
+ serialize(source: T, context: FieldDefaultSerializeContext): Uint8Array;
20
+ serialize(source: T, context: FieldByobSerializeContext): number;
21
+ }
22
+
23
+ export interface Field<T, OmitInit extends string, D, Raw = T>
24
+ extends FieldSerializer<Raw>,
25
+ FieldDeserializer<T, D> {
26
+ omitInit: OmitInit | undefined;
27
+
28
+ init?(value: T, dependencies: D): Raw | undefined;
29
+ }
30
+
31
+ export interface FieldDeserializeContext<D> {
32
+ littleEndian: boolean;
33
+ dependencies: D;
34
+ }
35
+
36
+ export interface FieldDeserializer<T, D> {
37
+ deserialize(reader: ExactReadable, context: FieldDeserializeContext<D>): T;
38
+ deserialize(
39
+ reader: AsyncExactReadable,
40
+ context: FieldDeserializeContext<D>,
41
+ ): MaybePromiseLike<T>;
42
+ }
43
+
44
+ export interface FieldOptions<T, OmitInit extends string, D, Raw = T> {
45
+ omitInit?: OmitInit;
46
+ dependencies?: D;
47
+ init?: (value: T, dependencies: D) => Raw | undefined;
48
+ }
package/src/index.ts CHANGED
@@ -12,9 +12,12 @@ declare global {
12
12
 
13
13
  export * from "./bipedal.js";
14
14
  export * from "./buffer.js";
15
- export * from "./field.js";
15
+ export * from "./concat.js";
16
+ export * from "./extend.js";
17
+ export * from "./field/index.js";
16
18
  export * from "./number.js";
17
19
  export * from "./readable.js";
18
20
  export * from "./string.js";
19
21
  export * from "./struct.js";
22
+ export * from "./types.js";
20
23
  export * from "./utils.js";
package/src/number.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { MaybePromiseLike } from "@yume-chan/async";
1
2
  import {
2
3
  getInt16,
3
4
  getInt32,
@@ -14,110 +15,120 @@ import {
14
15
  setUint64,
15
16
  } from "@yume-chan/no-data-view";
16
17
 
17
- import { bipedal } from "./bipedal.js";
18
- import type { Field } from "./field.js";
18
+ import type {
19
+ Field,
20
+ FieldByobSerializeContext,
21
+ FieldDeserializeContext,
22
+ } from "./field/index.js";
23
+ import { field } from "./field/index.js";
24
+ import type { AsyncExactReadable } from "./readable.js";
19
25
 
20
- export interface NumberField<T> extends Field<T, never, never> {
21
- <const U>(infer?: U): Field<U, never, never>;
26
+ export interface NumberField<T> extends Field<T, never, never, T> {
27
+ <const U>(infer?: U): Field<U, never, never, T>;
22
28
  }
23
29
 
24
30
  /* #__NO_SIDE_EFFECTS__ */
25
- function factory<T>(
31
+ function number<T>(
26
32
  size: number,
27
- serialize: Field<T, never, never>["serialize"],
28
- deserialize: Field<T, never, never>["deserialize"],
29
- ): NumberField<T> {
30
- const result = () => result;
31
- result.size = size;
32
- result.serialize = serialize;
33
- result.deserialize = deserialize;
34
- return result as never;
33
+ serialize: (
34
+ source: T,
35
+ context: FieldByobSerializeContext & { index: number },
36
+ ) => void,
37
+ deserialize: (
38
+ then: <U>(value: MaybePromiseLike<U>) => Iterable<unknown, U, unknown>,
39
+ reader: AsyncExactReadable,
40
+ context: FieldDeserializeContext<never>,
41
+ ) => Generator<unknown, T, unknown>,
42
+ ) {
43
+ const fn: NumberField<T> = (() => fn) as never;
44
+ Object.assign(fn, field(size, "byob", serialize, deserialize));
45
+ return fn;
35
46
  }
36
47
 
37
- export const u8 = factory<number>(
48
+ export const u8: NumberField<number> = number(
38
49
  1,
39
50
  (value, { buffer, index }) => {
40
51
  buffer[index] = value;
41
52
  },
42
- bipedal(function* (then, { reader }) {
53
+ function* (then, reader) {
43
54
  const data = yield* then(reader.readExactly(1));
44
55
  return data[0]!;
45
- }),
56
+ },
46
57
  );
47
58
 
48
- export const s8 = factory<number>(
59
+ export const s8: NumberField<number> = number(
49
60
  1,
50
61
  (value, { buffer, index }) => {
51
62
  buffer[index] = value;
52
63
  },
53
- bipedal(function* (then, { reader }) {
64
+ function* (then, reader) {
54
65
  const data = yield* then(reader.readExactly(1));
55
66
  return getInt8(data, 0);
56
- }),
67
+ },
57
68
  );
58
69
 
59
- export const u16 = factory<number>(
70
+ export const u16: NumberField<number> = number(
60
71
  2,
61
72
  (value, { buffer, index, littleEndian }) => {
62
73
  setUint16(buffer, index, value, littleEndian);
63
74
  },
64
- bipedal(function* (then, { reader, littleEndian }) {
75
+ function* (then, reader, { littleEndian }) {
65
76
  const data = yield* then(reader.readExactly(2));
66
77
  return getUint16(data, 0, littleEndian);
67
- }),
78
+ },
68
79
  );
69
80
 
70
- export const s16 = factory<number>(
81
+ export const s16: NumberField<number> = number(
71
82
  2,
72
83
  (value, { buffer, index, littleEndian }) => {
73
84
  setInt16(buffer, index, value, littleEndian);
74
85
  },
75
- bipedal(function* (then, { reader, littleEndian }) {
86
+ function* (then, reader, { littleEndian }) {
76
87
  const data = yield* then(reader.readExactly(2));
77
88
  return getInt16(data, 0, littleEndian);
78
- }),
89
+ },
79
90
  );
80
91
 
81
- export const u32 = factory<number>(
92
+ export const u32: NumberField<number> = number(
82
93
  4,
83
94
  (value, { buffer, index, littleEndian }) => {
84
95
  setUint32(buffer, index, value, littleEndian);
85
96
  },
86
- bipedal(function* (then, { reader, littleEndian }) {
97
+ function* (then, reader, { littleEndian }) {
87
98
  const data = yield* then(reader.readExactly(4));
88
99
  return getUint32(data, 0, littleEndian);
89
- }),
100
+ },
90
101
  );
91
102
 
92
- export const s32 = factory<number>(
103
+ export const s32: NumberField<number> = number(
93
104
  4,
94
105
  (value, { buffer, index, littleEndian }) => {
95
106
  setInt32(buffer, index, value, littleEndian);
96
107
  },
97
- bipedal(function* (then, { reader, littleEndian }) {
108
+ function* (then, reader, { littleEndian }) {
98
109
  const data = yield* then(reader.readExactly(4));
99
110
  return getInt32(data, 0, littleEndian);
100
- }),
111
+ },
101
112
  );
102
113
 
103
- export const u64 = factory<bigint>(
114
+ export const u64: NumberField<bigint> = number(
104
115
  8,
105
116
  (value, { buffer, index, littleEndian }) => {
106
117
  setUint64(buffer, index, value, littleEndian);
107
118
  },
108
- bipedal(function* (then, { reader, littleEndian }) {
119
+ function* (then, reader, { littleEndian }) {
109
120
  const data = yield* then(reader.readExactly(8));
110
121
  return getUint64(data, 0, littleEndian);
111
- }),
122
+ },
112
123
  );
113
124
 
114
- export const s64 = factory<bigint>(
125
+ export const s64: NumberField<bigint> = number(
115
126
  8,
116
127
  (value, { buffer, index, littleEndian }) => {
117
128
  setInt64(buffer, index, value, littleEndian);
118
129
  },
119
- bipedal(function* (then, { reader, littleEndian }) {
130
+ function* (then, reader, { littleEndian }) {
120
131
  const data = yield* then(reader.readExactly(8));
121
132
  return getInt64(data, 0, littleEndian);
122
- }),
133
+ },
123
134
  );
package/src/readable.ts CHANGED
@@ -20,6 +20,34 @@ export interface ExactReadable {
20
20
  readExactly(length: number): Uint8Array;
21
21
  }
22
22
 
23
+ export class Uint8ArrayExactReadable implements ExactReadable {
24
+ #data: Uint8Array;
25
+ #position: number;
26
+
27
+ get position() {
28
+ return this.#position;
29
+ }
30
+
31
+ constructor(data: Uint8Array) {
32
+ this.#data = data;
33
+ this.#position = 0;
34
+ }
35
+
36
+ readExactly(length: number): Uint8Array {
37
+ if (this.#position + length > this.#data.length) {
38
+ throw new ExactReadableEndedError();
39
+ }
40
+
41
+ const result = this.#data.subarray(
42
+ this.#position,
43
+ this.#position + length,
44
+ );
45
+
46
+ this.#position += length;
47
+ return result;
48
+ }
49
+ }
50
+
23
51
  export interface AsyncExactReadable {
24
52
  readonly position: number;
25
53
 
package/src/string.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { BufferLengthConverter } from "./buffer.js";
2
2
  import { buffer } from "./buffer.js";
3
- import type { Field } from "./field.js";
3
+ import type { Field } from "./field/index.js";
4
4
  import { decodeUtf8, encodeUtf8 } from "./utils.js";
5
5
 
6
6
  export interface String {
@@ -25,19 +25,17 @@ export interface String {
25
25
  ): Field<string, KOmitInit, KS>;
26
26
  }
27
27
 
28
- // Rollup doesn't support `/* #__NO_SIDE_EFFECTS__ */ export const a = () => {}
29
- /* #__NO_SIDE_EFFECTS__ */
30
- function _string(
28
+ // Prettier will move the annotation and make it invalid
29
+ // prettier-ignore
30
+ export const string: String = (/* #__NO_SIDE_EFFECTS__ */ (
31
31
  lengthOrField: string | number | BufferLengthConverter<string, unknown>,
32
32
  ): Field<string, string, Record<string, unknown>> & {
33
33
  as: <T>(infer: T) => Field<T, string, Record<string, unknown>>;
34
- } {
34
+ } => {
35
35
  const field = buffer(lengthOrField as never, {
36
36
  convert: decodeUtf8,
37
37
  back: encodeUtf8,
38
38
  });
39
39
  (field as never as { as: unknown }).as = () => field;
40
40
  return field as never;
41
- }
42
-
43
- export const string: String = _string as never;
41
+ }) as never;