@cloudpss/ubjson 0.3.5 → 0.3.8

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 (59) hide show
  1. package/benchmark-string.js +18 -20
  2. package/benchmark.js +1 -0
  3. package/dist/{constants.d.ts → common/constants.d.ts} +0 -0
  4. package/dist/{constants.js → common/constants.js} +0 -0
  5. package/dist/common/constants.js.map +1 -0
  6. package/dist/common/decoder.d.ts +48 -0
  7. package/dist/{decoder.js → common/decoder.js} +13 -18
  8. package/dist/common/decoder.js.map +1 -0
  9. package/dist/common/encoder.d.ts +74 -0
  10. package/dist/common/encoder.js +282 -0
  11. package/dist/common/encoder.js.map +1 -0
  12. package/dist/common/string-decoder.d.ts +13 -0
  13. package/dist/common/string-decoder.js +106 -0
  14. package/dist/common/string-decoder.js.map +1 -0
  15. package/dist/{string-encoder.d.ts → common/string-encoder.d.ts} +0 -0
  16. package/dist/{string-encoder.js → common/string-encoder.js} +0 -0
  17. package/dist/common/string-encoder.js.map +1 -0
  18. package/dist/encoder.d.ts +10 -2
  19. package/dist/encoder.js +2 -283
  20. package/dist/encoder.js.map +1 -1
  21. package/dist/index.d.ts +4 -2
  22. package/dist/index.js +12 -2
  23. package/dist/index.js.map +1 -1
  24. package/dist/stream/encoder.d.ts +14 -0
  25. package/dist/stream/encoder.js +86 -0
  26. package/dist/stream/encoder.js.map +1 -0
  27. package/dist/stream/index.d.ts +4 -0
  28. package/dist/stream/index.js +7 -0
  29. package/dist/stream/index.js.map +1 -0
  30. package/index.d.ts +7 -0
  31. package/jest.config.js +3 -0
  32. package/package.json +12 -8
  33. package/src/{constants.ts → common/constants.ts} +0 -0
  34. package/src/{decoder.ts → common/decoder.ts} +13 -18
  35. package/src/common/encoder.ts +305 -0
  36. package/src/common/string-decoder.ts +107 -0
  37. package/src/{string-encoder.ts → common/string-encoder.ts} +0 -0
  38. package/src/encoder.ts +4 -305
  39. package/src/index.ts +14 -2
  40. package/src/stream/encoder.ts +83 -0
  41. package/src/stream/index.ts +8 -0
  42. package/tests/decode.js +433 -0
  43. package/{test → tests}/e2e.js +19 -27
  44. package/tests/encode.js +354 -0
  45. package/tests/stream/encode.js +380 -0
  46. package/tests/string-encoding.js +71 -0
  47. package/tests/tsconfig.json +7 -0
  48. package/tsconfig.json +1 -2
  49. package/dist/constants.js.map +0 -1
  50. package/dist/decoder.d.ts +0 -2
  51. package/dist/decoder.js.map +0 -1
  52. package/dist/string-decoder.d.ts +0 -9
  53. package/dist/string-decoder.js +0 -67
  54. package/dist/string-decoder.js.map +0 -1
  55. package/dist/string-encoder.js.map +0 -1
  56. package/src/string-decoder.ts +0 -69
  57. package/test/decode.js +0 -491
  58. package/test/encode.js +0 -432
  59. package/test/string-encoding.js +0 -62
package/src/encoder.ts CHANGED
@@ -1,6 +1,4 @@
1
- /* eslint-disable @typescript-eslint/unbound-method */
2
- import * as constants from './constants.js';
3
- import { StringEncoder } from './string-encoder.js';
1
+ import { EncoderBase } from './common/encoder.js';
4
2
 
5
3
  const INITIAL_SIZE = 1024 * 8; // 8 KiB
6
4
  const MAX_SIZE = 1024 * 1024 * 128; //128 MiB
@@ -8,22 +6,11 @@ const RESIZE_FACTOR = 4;
8
6
  const RESIZE_FACTOR_2 = 2;
9
7
 
10
8
  /** 编码至 ubjson */
11
- class Encoder {
12
- /** 字符串编码 */
13
- private stringEncoder = new StringEncoder();
14
- /** 当前写指针位置 */
15
- private length = 0;
16
- /** 数据 */
17
- private buffer!: Uint8Array;
18
- /** buffer 的 DataView */
19
- private view!: DataView;
20
-
21
- constructor(readonly value: unknown) {}
22
-
9
+ export class Encoder extends EncoderBase {
23
10
  /**
24
11
  * 确保 buffer 还有 capacity 的空闲空间
25
12
  */
26
- private ensureCapacity(capacity: number): void {
13
+ protected ensureCapacity(capacity: number): void {
27
14
  if (this.buffer == null) {
28
15
  if (capacity > MAX_SIZE) {
29
16
  // 超过最大尺寸限制
@@ -62,6 +49,7 @@ class Encoder {
62
49
  this.buffer = newBuffer;
63
50
  this.view = new DataView(newBuffer.buffer);
64
51
  }
52
+
65
53
  /** 获取写入结果 */
66
54
  encode(): Uint8Array {
67
55
  if (ArrayBuffer.isView(this.value)) {
@@ -87,293 +75,4 @@ class Encoder {
87
75
  return this.buffer.slice(0, this.length);
88
76
  }
89
77
  }
90
- /** 写入一个对象 */
91
- private write(value: unknown): void {
92
- switch (typeof value) {
93
- case 'undefined':
94
- return this.writeNoOp();
95
- case 'boolean':
96
- return this.writeBoolean(value);
97
- case 'number':
98
- return this.writeNumber(value);
99
- case 'string':
100
- if (value.length === 1 && value.charCodeAt(0) < 255) {
101
- // 1 byte string
102
- return this.writeChar(value);
103
- }
104
- return this.writeString(value);
105
- case 'object': {
106
- if (value === null) {
107
- return this.writeNull();
108
- }
109
- if (Array.isArray(value)) {
110
- return this.writeArray(value);
111
- }
112
- if (ArrayBuffer.isView(value)) {
113
- return this.writeTypedArray(value);
114
- }
115
- return this.writeObject(value as Record<string, unknown>);
116
- }
117
- }
118
- throw new Error(`Unsupported type ${Object.prototype.toString.call(value)}`);
119
- }
120
- /** 写入 marker */
121
- private writeMarker(marker: number): void {
122
- this.ensureCapacity(1);
123
- this.view.setUint8(this.length, marker);
124
- this.length += 1;
125
- }
126
-
127
- /** 写入 marker */
128
- private writeNull(): void {
129
- this.writeMarker(constants.NULL);
130
- }
131
-
132
- /** writeNoOp */
133
- private writeNoOp(): void {
134
- this.writeMarker(constants.NO_OP);
135
- }
136
-
137
- /** writeBoolean */
138
- private writeBoolean(value: boolean): void {
139
- this.writeMarker(value ? constants.TRUE : constants.FALSE);
140
- }
141
-
142
- /** writeInt8 */
143
- private writeInt8(value: number): void {
144
- this.writeMarker(constants.INT8);
145
- this.writeInt8Data(value);
146
- }
147
-
148
- /** writeInt8Data */
149
- private writeInt8Data(value: number): void {
150
- this.ensureCapacity(1);
151
- this.view.setInt8(this.length, value);
152
- this.length += 1;
153
- }
154
-
155
- /** writeUint8 */
156
- private writeUint8(value: number): void {
157
- this.writeMarker(constants.UINT8);
158
- this.writeUint8Data(value);
159
- }
160
-
161
- /** writeUint8Data */
162
- private writeUint8Data(value: number): void {
163
- this.ensureCapacity(1);
164
- this.view.setUint8(this.length, value);
165
- this.length += 1;
166
- }
167
-
168
- /** writeInt16 */
169
- private writeInt16(value: number): void {
170
- this.writeMarker(constants.INT16);
171
- this.writeInt16Data(value);
172
- }
173
-
174
- /** writeInt16Data */
175
- private writeInt16Data(value: number): void {
176
- this.ensureCapacity(2);
177
- this.view.setInt16(this.length, value);
178
- this.length += 2;
179
- }
180
-
181
- /** writeInt32 */
182
- private writeInt32(value: number): void {
183
- this.writeMarker(constants.INT32);
184
- this.writeInt32Data(value);
185
- }
186
-
187
- /** writeInt32Data */
188
- private writeInt32Data(value: number): void {
189
- this.ensureCapacity(4);
190
- this.view.setInt32(this.length, value);
191
- this.length += 4;
192
- }
193
-
194
- /** writeFloat32 */
195
- private writeFloat32(value: number): void {
196
- this.writeMarker(constants.FLOAT32);
197
- this.writeFloat32Data(value);
198
- }
199
-
200
- /** writeFloat32Data */
201
- private writeFloat32Data(value: number): void {
202
- this.ensureCapacity(4);
203
- this.view.setFloat32(this.length, value);
204
- this.length += 4;
205
- }
206
-
207
- /** writeFloat64 */
208
- private writeFloat64(value: number): void {
209
- this.writeMarker(constants.FLOAT64);
210
- this.writeFloat64Data(value);
211
- }
212
-
213
- /** writeFloat64Data */
214
- private writeFloat64Data(value: number): void {
215
- this.ensureCapacity(8);
216
- this.view.setFloat64(this.length, value);
217
- this.length += 8;
218
- }
219
-
220
- /** writeChar */
221
- private writeChar(value: string): void {
222
- this.writeMarker(constants.CHAR);
223
- this.writeCharData(value);
224
- }
225
-
226
- /** writeCharData */
227
- private writeCharData(value: string): void {
228
- this.ensureCapacity(1);
229
- this.view.setUint8(this.length, value.charCodeAt(0));
230
- this.length += 1;
231
- }
232
-
233
- /** writeString */
234
- private writeString(value: string): void {
235
- this.writeMarker(constants.STRING);
236
- this.writeStringData(value);
237
- }
238
-
239
- /** writeStringData */
240
- private writeStringData(value: string): void {
241
- // 优化小字符串
242
- if (value.length < 32) {
243
- const buf = this.stringEncoder.encode(value);
244
- this.writeInt(buf.length);
245
- this.ensureCapacity(buf.length);
246
- this.buffer.set(buf, this.length);
247
- this.length += buf.length;
248
- return;
249
- }
250
- if (this.stringEncoder.encodeInto != null) {
251
- const maxUsage = value.length * 3;
252
- const currentPos = this.length;
253
- // 先写入最大大小
254
- const lengthWriter = this.writeInt(maxUsage);
255
- this.ensureCapacity(maxUsage);
256
- // 写入文本数据
257
- const { written } = this.stringEncoder.encodeInto(value, this.buffer.subarray(this.length));
258
- // 回溯,写入实际大小
259
- this.length = currentPos;
260
- lengthWriter.call(this, written as number);
261
- // 移动指针到写入末尾
262
- this.length += written as number;
263
- return;
264
- }
265
- const data = this.stringEncoder.encode(value);
266
- this.writeInt(data.length);
267
- this.ensureCapacity(data.length);
268
- this.buffer.set(data, this.length);
269
- this.length += data.length;
270
- }
271
-
272
- /**
273
- * 写入整形数字,选取合适的大小
274
- *
275
- * @throws 无法在 int32 范围内表示
276
- */
277
- private writeInt(value: number): (this: this, value: number) => void {
278
- value = value | 0;
279
- if (value >= -128 && value <= 127) {
280
- this.writeInt8(value);
281
- return this.writeInt8;
282
- }
283
- if (value >= -32768 && value <= 32767) {
284
- this.writeInt16(value);
285
- return this.writeInt16;
286
- }
287
- if (value >= -2147483648 && value <= 2147483647) {
288
- this.writeInt32(value);
289
- return this.writeInt32;
290
- }
291
- /* c8 ignore next 2 */
292
- throw new Error(`Unsupported int value ${value}: out of range`);
293
- }
294
-
295
- /** 写入数字,选取合适的大小 */
296
- private writeNumber(value: number): void {
297
- if (Number.isInteger(value) && value >= -2147483648 && value <= 2147483647) {
298
- if (value >= 0 && value <= 255) return this.writeUint8(value);
299
- this.writeInt(value);
300
- return;
301
- }
302
- if (!(Number.isNaN(value) || Math.fround(value) === value)) {
303
- return this.writeFloat64(value);
304
- }
305
- // 如果不会损失精度,则使用 32 位浮点
306
- return this.writeFloat32(value);
307
- }
308
-
309
- /** writeObject */
310
- private writeObject(value: Record<string, unknown>): void {
311
- this.writeMarker(constants.OBJECT);
312
- // 生成稳定的结果以便 hash 计算
313
- for (const key of Object.keys(value).sort()) {
314
- const element = value[key];
315
- if (element === undefined) continue;
316
- this.writeStringData(key);
317
- this.write(element);
318
- }
319
- this.writeMarker(constants.OBJECT_END);
320
- }
321
-
322
- /** writeArray */
323
- private writeArray(value: unknown[]): void {
324
- this.writeMarker(constants.ARRAY);
325
- for (const v of value) {
326
- // 在数组中 undefined 也被视作 null 进行序列化
327
- if (v == null) this.writeNull();
328
- else this.write(v);
329
- }
330
- this.writeMarker(constants.ARRAY_END);
331
- }
332
-
333
- /** writeArray */
334
- private writeTypedArray(value: ArrayBufferView): void {
335
- this.writeMarker(constants.ARRAY);
336
- this.writeMarker(constants.TYPE_MARKER);
337
- if (value instanceof Uint8Array || value instanceof Int8Array) {
338
- // fast path for typed arrays with `BYTES_PER_ELEMENT` of 1
339
- this.writeMarker(value instanceof Uint8Array ? constants.UINT8 : constants.INT8);
340
- this.writeMarker(constants.COUNT_MARKER);
341
- this.writeInt(value.length);
342
- this.ensureCapacity(value.byteLength);
343
- this.buffer.set(value, this.length);
344
- this.length += value.byteLength;
345
- return;
346
- }
347
- /** 用于写入的 setter */
348
- let setValue;
349
- if (value instanceof Int16Array) {
350
- this.writeMarker(constants.INT16);
351
- setValue = this.view.setInt16;
352
- } else if (value instanceof Int32Array) {
353
- this.writeMarker(constants.INT32);
354
- setValue = this.view.setInt32;
355
- } else if (value instanceof Float32Array) {
356
- this.writeMarker(constants.FLOAT32);
357
- setValue = this.view.setFloat32;
358
- } else if (value instanceof Float64Array) {
359
- this.writeMarker(constants.FLOAT64);
360
- setValue = this.view.setFloat64;
361
- } else {
362
- throw new Error(`Unsupported typed array type ${Object.prototype.toString.call(value)}`);
363
- }
364
- this.writeMarker(constants.COUNT_MARKER);
365
- this.writeInt(value.length);
366
- this.ensureCapacity(value.byteLength);
367
- // 不要在前面 bind,this.ensureCapacity 有可能导致 this.view 指向新的对象
368
- for (const v of value) {
369
- setValue.call(this.view, this.length, v);
370
- this.length += value.BYTES_PER_ELEMENT;
371
- }
372
- }
373
- }
374
-
375
- /** 编码 */
376
- export function encode(value: unknown): Uint8Array {
377
- const encoder = new Encoder(value);
378
- return encoder.encode();
379
78
  }
package/src/index.ts CHANGED
@@ -1,2 +1,14 @@
1
- export { encode } from './encoder.js';
2
- export { decode } from './decoder.js';
1
+ import { Encoder } from './encoder.js';
2
+ import { Decoder } from './common/decoder.js';
3
+
4
+ /** 编码为 UBJSON */
5
+ export function encode(value: unknown): Uint8Array {
6
+ const encoder = new Encoder(value);
7
+ return encoder.encode();
8
+ }
9
+
10
+ /** 解码 UBJSON */
11
+ export function decode(value: Uint8Array): unknown {
12
+ const decoder = new Decoder(value);
13
+ return decoder.decode();
14
+ }
@@ -0,0 +1,83 @@
1
+ import { Readable } from 'node:stream';
2
+ import { EncoderBase } from '../common/encoder.js';
3
+
4
+ const BLOCK_SIZE = 1024 * 8; // 8 KiB
5
+ const MAX_SIZE = 1024 * 1024 * 256; //256 MiB
6
+
7
+ /** 流式编码 UBJSON */
8
+ export class StreamEncoder extends EncoderBase {
9
+ protected stream!: Readable;
10
+ /**
11
+ * 确保 buffer 还有 capacity 的空闲空间
12
+ */
13
+ protected ensureCapacity(capacity: number): void {
14
+ if (capacity > MAX_SIZE) {
15
+ // 超过最大尺寸限制
16
+ throw new Error('Buffer has exceed max size');
17
+ }
18
+ if (this.buffer == null) {
19
+ this.buffer = new Uint8Array(capacity);
20
+ this.view = new DataView(this.buffer.buffer);
21
+ return;
22
+ }
23
+ if (capacity < 0) {
24
+ // 结束流
25
+ this.stream.push(this.buffer.subarray(0, this.length));
26
+ this.buffer = new Uint8Array(0);
27
+ this.view = new DataView(this.buffer.buffer);
28
+ this.length = 0;
29
+ this.stream.push(null);
30
+ return;
31
+ }
32
+ // 无需扩容
33
+ if (this.buffer.byteLength >= this.length + capacity) return;
34
+
35
+ // 提交目前的数据
36
+ this.stream.push(this.buffer.subarray(0, this.length));
37
+
38
+ // 重新分配缓冲区
39
+ if (capacity < BLOCK_SIZE) capacity = BLOCK_SIZE;
40
+ this.buffer = new Uint8Array(capacity);
41
+ this.view = new DataView(this.buffer.buffer);
42
+ this.length = 0;
43
+ }
44
+
45
+ protected currentPos: Array<string | number> = [];
46
+ /** 获取写入结果 */
47
+ encode(): Readable {
48
+ if (typeof this.value != 'object' || this.value == null || ArrayBuffer.isView(this.value)) {
49
+ this.stream = new Readable({
50
+ objectMode: false,
51
+ construct: (callback) => {
52
+ this.ensureCapacity(20);
53
+ callback();
54
+ },
55
+ read: () => {
56
+ try {
57
+ this.write(this.value);
58
+ this.ensureCapacity(-1);
59
+ } catch (ex) {
60
+ this.stream.destroy(ex as Error);
61
+ }
62
+ },
63
+ });
64
+ } else {
65
+ this.stream = new Readable({
66
+ objectMode: false,
67
+ construct: (callback) => {
68
+ this.ensureCapacity(BLOCK_SIZE);
69
+ callback();
70
+ },
71
+ read: () => {
72
+ try {
73
+ this.write(this.value);
74
+ this.ensureCapacity(-1);
75
+ } catch (ex) {
76
+ this.stream.destroy(ex as Error);
77
+ }
78
+ },
79
+ });
80
+ }
81
+ return this.stream;
82
+ }
83
+ }
@@ -0,0 +1,8 @@
1
+ import type { Readable } from 'node:stream';
2
+ import { StreamEncoder } from './encoder.js';
3
+
4
+ /** 编码为 UBJSON */
5
+ export function encode(value: unknown): Readable {
6
+ const encoder = new StreamEncoder(value);
7
+ return encoder.encode();
8
+ }