@mxjp/binary 1.0.0

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.
@@ -0,0 +1,216 @@
1
+ import { asUint8Array } from "./bytes.js";
2
+
3
+ export class Serializer {
4
+ #littleEndian: boolean;
5
+ #byteLength: number;
6
+ #parts: Part[];
7
+
8
+ constructor(littleEndian?: boolean) {
9
+ this.#littleEndian = littleEndian ?? true;
10
+ if (typeof this.#littleEndian !== "boolean") {
11
+ throw new TypeError();
12
+ }
13
+ this.#byteLength = 0;
14
+ this.#parts = [];
15
+ }
16
+
17
+ get byteLength(): number {
18
+ return this.#byteLength;
19
+ }
20
+
21
+ #push(byteLength: number, serializeFn: SerializePartFn): void {
22
+ this.#parts.push({ l: byteLength, s: serializeFn });
23
+ this.#byteLength += byteLength;
24
+ }
25
+
26
+ /** Serialize an unsigned 8 bit int. */
27
+ u8(value: number): void {
28
+ if (typeof value !== "number") {
29
+ throw new TypeError();
30
+ }
31
+ if (!Number.isSafeInteger(value) || value < 0 || value > 0xFF) {
32
+ throw new RangeError();
33
+ }
34
+ this.#push(1, (ctx, byteOffset) => {
35
+ ctx.array[byteOffset] = value;
36
+ });
37
+ }
38
+
39
+ /** Serialize an unsigned 16 bit int in the serializer's endianess. */
40
+ u16(value: number): void {
41
+ if (typeof value !== "number") {
42
+ throw new TypeError();
43
+ }
44
+ if (!Number.isSafeInteger(value) || value < 0 || value > 0xFF_FF) {
45
+ throw new RangeError();
46
+ }
47
+ this.#push(2, (ctx, byteOffset) => {
48
+ ctx.view.setUint16(byteOffset, value, this.#littleEndian);
49
+ });
50
+ }
51
+
52
+ /** Serialize an unsigned 32 bit int in the serializer's endianess. */
53
+ u32(value: number): void {
54
+ if (typeof value !== "number") {
55
+ throw new TypeError();
56
+ }
57
+ if (!Number.isSafeInteger(value) || value < 0 || value > 0xFF_FF_FF_FF) {
58
+ throw new RangeError();
59
+ }
60
+ this.#push(4, (ctx, byteOffset) => {
61
+ ctx.view.setUint32(byteOffset, value, this.#littleEndian);
62
+ });
63
+ }
64
+
65
+ /** Serialize an unsigned 64 bit int in the serializer's endianess. */
66
+ u64(value: number): void {
67
+ if (typeof value !== "number") {
68
+ throw new TypeError();
69
+ }
70
+ if (!Number.isSafeInteger(value) || value < 0) {
71
+ throw new RangeError();
72
+ }
73
+ this.#push(8, (ctx, byteOffset) => {
74
+ ctx.view.setBigUint64(byteOffset, BigInt(value), this.#littleEndian);
75
+ });
76
+ }
77
+
78
+ /** Serialize an unsigned 64 bit int in the serializer's endianess. */
79
+ bigU64(value: bigint): void {
80
+ if (typeof value !== "bigint") {
81
+ throw new TypeError();
82
+ }
83
+ if (value < 0n || value > 0xFF_FF_FF_FF_FF_FF_FF_FFn) {
84
+ throw new RangeError();
85
+ }
86
+ this.#push(8, (ctx, byteOffset) => {
87
+ ctx.view.setBigUint64(byteOffset, value, this.#littleEndian);
88
+ });
89
+ }
90
+
91
+ /** Serialize a 32-bit IEEE 754 floating point number in the serializer's endianess. */
92
+ f32(value: number): void {
93
+ if (typeof value !== "number") {
94
+ throw new TypeError();
95
+ }
96
+ this.#push(4, (ctx, byteOffset) => {
97
+ ctx.view.setFloat32(byteOffset, value, this.#littleEndian);
98
+ });
99
+ }
100
+
101
+ /** Serialize a 64-bit IEEE 754 floating point number in the serializer's endianess. */
102
+ f64(value: number): void {
103
+ if (typeof value !== "number") {
104
+ throw new TypeError();
105
+ }
106
+ this.#push(8, (ctx, byteOffset) => {
107
+ ctx.view.setFloat64(byteOffset, value, this.#littleEndian);
108
+ });
109
+ }
110
+
111
+ /**
112
+ * Serialize a boolean.
113
+ *
114
+ * + `false` is represented as `0x00`.
115
+ * + `true` is represented as `0x01`.
116
+ */
117
+ bool(value: boolean): void {
118
+ if (typeof value !== "boolean") {
119
+ throw new TypeError();
120
+ }
121
+ this.u8(value ? 0x01 : 0x00);
122
+ }
123
+
124
+ /**
125
+ * Serialize an optional value.
126
+ *
127
+ * + `null` and `undefined` are serialized using {@link boolean `boolean(false)`}.
128
+ * + All other values are serialized using {@link boolean `boolean(true)`} followed by immediately calling the specified function.
129
+ */
130
+ option<T>(value: T | null | undefined, fn: SerializeFn<T>): void {
131
+ if (value === null || value === undefined) {
132
+ this.bool(false);
133
+ } else {
134
+ this.bool(true);
135
+ fn(value, this);
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Serialize bytes by copying them when {@link serialize serializing}.
141
+ */
142
+ unsafeBytes(value: ArrayBuffer | Uint8Array<ArrayBuffer>): void {
143
+ const bytes = asUint8Array(value);
144
+ this.#push(value.byteLength, (ctx, byteOffset) => {
145
+ ctx.array.set(bytes, byteOffset);
146
+ });
147
+ }
148
+
149
+ /**
150
+ * Serialize bytes by copying them when {@link serialize serializing} prefixed by their byte length.
151
+ *
152
+ * See {@link unsafeBytes}.
153
+ */
154
+ prefixedUnsafeBytes(prefix: SerializePrefixFn, value: ArrayBuffer | Uint8Array<ArrayBuffer>): void {
155
+ const bytes = asUint8Array(value);
156
+ prefix.call(this, value.byteLength);
157
+ this.unsafeBytes(bytes);
158
+ }
159
+
160
+ /**
161
+ * Serialize a utf-8 encoded string.
162
+ */
163
+ utf8(value: string): void {
164
+ this.unsafeBytes(new TextEncoder().encode(value));
165
+ }
166
+
167
+ /**
168
+ * Serialize a utf-8 encoded string prefixed by it's byte length.
169
+ */
170
+ prefixedUtf8(prefix: SerializePrefixFn, value: string): void {
171
+ this.prefixedUnsafeBytes(prefix, new TextEncoder().encode(value));
172
+ }
173
+
174
+ /**
175
+ * Write all parts in this serializer to an array buffer.
176
+ */
177
+ serialize(): ArrayBuffer {
178
+ const buffer = new ArrayBuffer(this.#byteLength);
179
+ const context: SerializeContext = {
180
+ buffer,
181
+ array: new Uint8Array(buffer),
182
+ view: new DataView(buffer),
183
+ };
184
+ const parts = this.#parts;
185
+ let byteOffset = 0;
186
+ for (let i = 0; i < parts.length; i++) {
187
+ const part = parts[i];
188
+ part.s(context, byteOffset);
189
+ byteOffset += part.l;
190
+ }
191
+ return buffer;
192
+ }
193
+ }
194
+
195
+ interface Part {
196
+ l: number;
197
+ s: SerializePartFn;
198
+ }
199
+
200
+ interface SerializeContext {
201
+ buffer: ArrayBuffer;
202
+ array: Uint8Array<ArrayBuffer>;
203
+ view: DataView<ArrayBuffer>;
204
+ }
205
+
206
+ interface SerializePartFn {
207
+ (ctx: SerializeContext, byteOffset: number): void;
208
+ }
209
+
210
+ export interface SerializePrefixFn {
211
+ (this: Serializer, value: number): void;
212
+ }
213
+
214
+ export interface SerializeFn<T> {
215
+ (value: T, serializer: Serializer): void;
216
+ }