@cloudpss/ubjson 0.5.39 → 0.5.41

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 (92) hide show
  1. package/dist/base/decoder.d.ts +1 -0
  2. package/dist/base/decoder.d.ts.map +1 -0
  3. package/dist/base/encoder.d.ts +5 -1
  4. package/dist/base/encoder.d.ts.map +1 -0
  5. package/dist/base/encoder.js +50 -188
  6. package/dist/base/encoder.js.map +1 -1
  7. package/dist/decoder.d.ts +1 -0
  8. package/dist/decoder.d.ts.map +1 -0
  9. package/dist/encoder.d.ts +7 -1
  10. package/dist/encoder.d.ts.map +1 -0
  11. package/dist/encoder.js +24 -15
  12. package/dist/encoder.js.map +1 -1
  13. package/dist/helper/constants.d.ts +1 -0
  14. package/dist/helper/constants.d.ts.map +1 -0
  15. package/dist/helper/decode-ae.d.ts +50 -0
  16. package/dist/helper/decode-ae.d.ts.map +1 -0
  17. package/dist/helper/decode-ae.js +584 -0
  18. package/dist/helper/decode-ae.js.map +1 -0
  19. package/dist/helper/decode.d.ts +6 -3
  20. package/dist/helper/decode.d.ts.map +1 -0
  21. package/dist/helper/decode.js +33 -20
  22. package/dist/helper/decode.js.map +1 -1
  23. package/dist/helper/encode.d.ts +19 -1
  24. package/dist/helper/encode.d.ts.map +1 -0
  25. package/dist/helper/encode.js +138 -16
  26. package/dist/helper/encode.js.map +1 -1
  27. package/dist/helper/errors.d.ts +1 -0
  28. package/dist/helper/errors.d.ts.map +1 -0
  29. package/dist/helper/string-decoder.d.ts +5 -2
  30. package/dist/helper/string-decoder.d.ts.map +1 -0
  31. package/dist/helper/string-decoder.js +10 -38
  32. package/dist/helper/string-decoder.js.map +1 -1
  33. package/dist/helper/string-encoder.d.ts +1 -0
  34. package/dist/helper/string-encoder.d.ts.map +1 -0
  35. package/dist/helper/utils.d.ts +1 -0
  36. package/dist/helper/utils.d.ts.map +1 -0
  37. package/dist/index.d.ts +5 -2
  38. package/dist/index.d.ts.map +1 -0
  39. package/dist/index.js +4 -4
  40. package/dist/index.js.map +1 -1
  41. package/dist/options.d.ts +6 -0
  42. package/dist/options.d.ts.map +1 -0
  43. package/dist/options.js +2 -0
  44. package/dist/options.js.map +1 -0
  45. package/dist/rxjs/decoder.d.ts +1 -0
  46. package/dist/rxjs/decoder.d.ts.map +1 -0
  47. package/dist/rxjs/decoder.js +66 -40
  48. package/dist/rxjs/decoder.js.map +1 -1
  49. package/dist/rxjs/encoder.d.ts +3 -1
  50. package/dist/rxjs/encoder.d.ts.map +1 -0
  51. package/dist/rxjs/encoder.js +2 -2
  52. package/dist/rxjs/encoder.js.map +1 -1
  53. package/dist/rxjs/index.d.ts +2 -0
  54. package/dist/rxjs/index.d.ts.map +1 -0
  55. package/dist/stream/decoder.d.ts +1 -0
  56. package/dist/stream/decoder.d.ts.map +1 -0
  57. package/dist/stream/encoder.d.ts +4 -2
  58. package/dist/stream/encoder.d.ts.map +1 -0
  59. package/dist/stream/encoder.js +2 -2
  60. package/dist/stream/encoder.js.map +1 -1
  61. package/dist/stream/index.d.ts +6 -3
  62. package/dist/stream/index.d.ts.map +1 -0
  63. package/dist/stream/index.js +6 -6
  64. package/dist/stream/index.js.map +1 -1
  65. package/dist/stream-helper/decoder.d.ts +1 -0
  66. package/dist/stream-helper/decoder.d.ts.map +1 -0
  67. package/dist/stream-helper/encoder.d.ts +5 -2
  68. package/dist/stream-helper/encoder.d.ts.map +1 -0
  69. package/dist/stream-helper/encoder.js +33 -31
  70. package/dist/stream-helper/encoder.js.map +1 -1
  71. package/jest.config.js +1 -1
  72. package/package.json +3 -4
  73. package/src/base/encoder.ts +58 -174
  74. package/src/encoder.ts +27 -15
  75. package/src/helper/decode-ae.ts +621 -0
  76. package/src/helper/decode.ts +36 -23
  77. package/src/helper/encode.ts +141 -15
  78. package/src/helper/string-decoder.ts +10 -39
  79. package/src/index.ts +7 -4
  80. package/src/options.ts +5 -0
  81. package/src/rxjs/decoder.ts +66 -40
  82. package/src/rxjs/encoder.ts +3 -2
  83. package/src/rxjs/index.ts +1 -0
  84. package/src/stream/encoder.ts +4 -3
  85. package/src/stream/index.ts +8 -6
  86. package/src/stream-helper/encoder.ts +39 -34
  87. package/tests/.utils.js +1 -0
  88. package/tests/e2e/stream.js +13 -1
  89. package/tests/encode.js +8 -0
  90. package/tests/string-encoding.js +1 -10
  91. package/tests/tsconfig.json +1 -7
  92. package/tsconfig.json +1 -1
@@ -1,9 +1,8 @@
1
1
  import { constants } from './constants.js';
2
2
  import { UnexpectedEofError, unsupportedType } from './errors.js';
3
- import { decode, decodeKey } from './string-decoder.js';
3
+ import { decode } from './string-decoder.js';
4
4
  import { toUint8Array } from './utils.js';
5
-
6
- const { fromEntries } = Object;
5
+ const { defineProperty } = Object;
7
6
  const { fromCharCode } = String;
8
7
 
9
8
  /** 数据包装 */
@@ -82,7 +81,7 @@ export function readLength(cursor: DecodeCursor): number {
82
81
  break;
83
82
  }
84
83
  default:
85
- throw new Error(`Unexpected marker '${String.fromCharCode(marker)}'(${marker}) for int length`);
84
+ throw new Error(`Unexpected marker '${fromCharCode(marker)}'(${marker}) for int length`);
86
85
  }
87
86
  if (length < 0) {
88
87
  throw new Error('Invalid length');
@@ -161,6 +160,21 @@ export function read(cursor: DecodeCursor): unknown {
161
160
  return readData(cursor, marker);
162
161
  }
163
162
 
163
+ /** 读取优化对象数据 */
164
+ function readObjectOptimizedData(cursor: DecodeCursor, marker: OptimizedFormatMarkers): unknown {
165
+ const { count, type } = marker;
166
+ const object: Record<string, unknown> = {};
167
+ for (let i = 0; i < count; i++) {
168
+ const key = readKey(cursor);
169
+ const value = readData(cursor, type ?? readMarker(cursor));
170
+ if (key === '__proto__') {
171
+ defineProperty(object, key, { value, enumerable: true, configurable: true, writable: true });
172
+ }
173
+ object[key] = value;
174
+ }
175
+ return object;
176
+ }
177
+
164
178
  /** 根据标签读取后续数据 */
165
179
  export function readData(cursor: DecodeCursor, marker: number): unknown {
166
180
  // 按照出现频率排序
@@ -178,32 +192,29 @@ export function readData(cursor: DecodeCursor, marker: number): unknown {
178
192
  const markers = readOptimizedFormatMarkers(cursor);
179
193
  if (markers == null) {
180
194
  // 直到 '}'
181
- const object: Array<[string, unknown]> = [];
195
+ const object: Record<string, unknown> = {};
182
196
  while (readMarker(cursor) !== constants.OBJECT_END) {
183
197
  cursor.offset--;
184
- object.push([readKey(cursor), read(cursor)]);
198
+ const key = readKey(cursor);
199
+ const value = read(cursor);
200
+ if (key === '__proto__') {
201
+ defineProperty(object, key, { value, enumerable: true, configurable: true, writable: true });
202
+ }
203
+ object[key] = value;
185
204
  }
186
- return fromEntries(object);
205
+ return object;
187
206
  }
188
-
189
- const { count, type } = markers;
190
- const object: Array<[string, unknown]> = [];
191
- object.length = count;
192
- for (let i = 0; i < count; i++) {
193
- const key = readKey(cursor);
194
- const value = readData(cursor, type ?? readMarker(cursor));
195
- object[i] = [key, value];
196
- }
197
- return fromEntries(object);
207
+ return readObjectOptimizedData(cursor, markers);
198
208
  }
199
209
  case constants.ARRAY: {
200
210
  const markers = readOptimizedFormatMarkers(cursor);
201
211
  if (markers == null) {
202
212
  const array = [];
203
- // 直到 ']'
204
- while (readMarker(cursor) !== constants.ARRAY_END) {
205
- cursor.offset--;
206
- array.push(read(cursor));
213
+ for (;;) {
214
+ const marker = readMarker(cursor);
215
+ // 直到 ']'
216
+ if (marker === constants.ARRAY_END) break;
217
+ array.push(readData(cursor, marker));
207
218
  }
208
219
  return array;
209
220
  }
@@ -336,11 +347,13 @@ export function readKey(cursor: DecodeCursor): string {
336
347
  cursor.offset = end;
337
348
  const { data } = cursor;
338
349
  if (end > data.length) cursor.eof();
339
- return decodeKey(data, begin, end);
350
+ return decode(data, begin, end);
340
351
  }
341
352
 
353
+ /** Optimized Format 数据 */
354
+ export type OptimizedFormatMarkers = { type?: number; count: number };
342
355
  /** 读取 Optimized Format 数据 */
343
- export function readOptimizedFormatMarkers(cursor: DecodeCursor): { type?: number; count: number } | undefined {
356
+ export function readOptimizedFormatMarkers(cursor: DecodeCursor): OptimizedFormatMarkers | undefined {
344
357
  let type;
345
358
  let count;
346
359
  switch (readMarker(cursor)) {
@@ -35,12 +35,14 @@ export function writeMarker(cursor: EncodeCursor, marker: constants): void {
35
35
  cursor.data[cursor.length++] = marker;
36
36
  }
37
37
 
38
+ export const I8_MASK = constants.INT8 << 8;
39
+ export const U8_MASK = constants.UINT8 << 8;
38
40
  /** 写入长度 */
39
41
  export function writeLength(cursor: EncodeCursor, length: number): void {
40
42
  if (length < 0x80) {
41
43
  cursor.ensureCapacity(2);
42
- cursor.data[cursor.length++] = constants.INT8;
43
- cursor.data[cursor.length++] = length;
44
+ cursor.view.setUint16(cursor.length, I8_MASK | length);
45
+ cursor.length += 2;
44
46
  } else if (length < 0x8000) {
45
47
  cursor.ensureCapacity(3);
46
48
  cursor.data[cursor.length++] = constants.INT16;
@@ -61,29 +63,153 @@ export function writeLength(cursor: EncodeCursor, length: number): void {
61
63
  }
62
64
  }
63
65
 
66
+ /** 写入数字 */
67
+ export function writeNumber(cursor: EncodeCursor, value: number): void {
68
+ // eslint-disable-next-line unicorn/prefer-math-trunc
69
+ if (value >> 0 === value) {
70
+ if (value >= 0 && value <= 0xff) {
71
+ cursor.ensureCapacity(2);
72
+ const { length } = cursor;
73
+ cursor.view.setUint16(length, U8_MASK | value);
74
+ cursor.length = length + 2;
75
+ } else if (value < 0x80 && value >= -0x80) {
76
+ cursor.ensureCapacity(2);
77
+ const { length } = cursor;
78
+ cursor.view.setUint16(length, I8_MASK | (value & 0xff));
79
+ cursor.length = length + 2;
80
+ } else if (value < 0x8000 && value >= -0x8000) {
81
+ cursor.ensureCapacity(3);
82
+ const { length } = cursor;
83
+ cursor.data[length] = constants.INT16;
84
+ cursor.view.setInt16(length + 1, value);
85
+ cursor.length = length + 3;
86
+ } else {
87
+ // must be 32 bit
88
+ cursor.ensureCapacity(5);
89
+ const { length } = cursor;
90
+ cursor.data[length] = constants.INT32;
91
+ cursor.view.setInt32(length + 1, value);
92
+ cursor.length = length + 5;
93
+ }
94
+ } else if (Number.isNaN(value) || Math.fround(value) === value) {
95
+ // 如果不会损失精度,使用 32 位浮点
96
+ cursor.ensureCapacity(5);
97
+ const { length } = cursor;
98
+ cursor.data[length] = constants.FLOAT32;
99
+ cursor.view.setFloat32(length + 1, value);
100
+ cursor.length = length + 5;
101
+ } else {
102
+ cursor.ensureCapacity(9);
103
+ const { length } = cursor;
104
+ cursor.data[length] = constants.FLOAT64;
105
+ cursor.view.setFloat64(length + 1, value);
106
+ cursor.length = length + 9;
107
+ }
108
+ return;
109
+ }
110
+
111
+ /** TypedArray 类型 */
112
+ export type TypedArrayType =
113
+ | constants.UINT8
114
+ | constants.INT8
115
+ | constants.INT16
116
+ | constants.INT32
117
+ | constants.INT64
118
+ | constants.FLOAT32
119
+ | constants.FLOAT64;
120
+ const T_ARR_HEADER = (type: TypedArrayType): number =>
121
+ (constants.ARRAY << 24) | (constants.TYPE_MARKER << 16) | (type << 8) | constants.COUNT_MARKER;
122
+ export const U8_ARR_HEADER = T_ARR_HEADER(constants.UINT8);
123
+ export const I8_ARR_HEADER = T_ARR_HEADER(constants.INT8);
124
+ export const I16_ARR_HEADER = T_ARR_HEADER(constants.INT16);
125
+ export const I32_ARR_HEADER = T_ARR_HEADER(constants.INT32);
126
+ export const I64_ARR_HEADER = T_ARR_HEADER(constants.INT64);
127
+ export const F32_ARR_HEADER = T_ARR_HEADER(constants.FLOAT32);
128
+ export const F64_ARR_HEADER = T_ARR_HEADER(constants.FLOAT64);
129
+
64
130
  /** 写入 TypedArray 前导,包括 marker 和长度 */
65
- export function writeTypedArrayHeader(cursor: EncodeCursor, value: ArrayBufferView): void {
131
+ export function writeTypedArrayHeader(cursor: EncodeCursor, value: ArrayBufferView): TypedArrayType {
66
132
  // ARRAY(1) + TYPE_MARKER(1) + TYPE(1) + COUNT_MARKER(1) + COUNT(MIN2 MAX5) + DATA
67
133
  cursor.ensureCapacity(9);
68
- cursor.data[cursor.length++] = constants.ARRAY;
69
- cursor.data[cursor.length++] = constants.TYPE_MARKER;
134
+ let type: TypedArrayType;
135
+ const { length } = cursor;
70
136
  if (value instanceof Uint8Array) {
71
- cursor.data[cursor.length++] = constants.UINT8;
137
+ cursor.view.setUint32(length, U8_ARR_HEADER);
138
+ type = constants.UINT8;
72
139
  } else if (value instanceof Float64Array) {
73
- cursor.data[cursor.length++] = constants.FLOAT64;
140
+ cursor.view.setUint32(length, F64_ARR_HEADER);
141
+ type = constants.FLOAT64;
74
142
  } else if (value instanceof Int32Array) {
75
- cursor.data[cursor.length++] = constants.INT32;
143
+ cursor.view.setUint32(length, I32_ARR_HEADER);
144
+ type = constants.INT32;
145
+ } else if (value instanceof BigInt64Array) {
146
+ cursor.view.setUint32(length, I64_ARR_HEADER);
147
+ type = constants.INT64;
148
+ } else if (value instanceof Float32Array) {
149
+ cursor.view.setUint32(length, F32_ARR_HEADER);
150
+ type = constants.FLOAT32;
76
151
  } else if (value instanceof Int8Array) {
77
- cursor.data[cursor.length++] = constants.INT8;
152
+ cursor.view.setUint32(length, I8_ARR_HEADER);
153
+ type = constants.INT8;
78
154
  } else if (value instanceof Int16Array) {
79
- cursor.data[cursor.length++] = constants.INT16;
80
- } else if (value instanceof Float32Array) {
81
- cursor.data[cursor.length++] = constants.FLOAT32;
82
- } else if (value instanceof BigInt64Array) {
83
- cursor.data[cursor.length++] = constants.INT64;
155
+ cursor.view.setUint32(length, I16_ARR_HEADER);
156
+ type = constants.INT16;
84
157
  } else {
85
158
  unsupportedView(value);
86
159
  }
87
- cursor.data[cursor.length++] = constants.COUNT_MARKER;
160
+ cursor.length = length + 4;
88
161
  writeLength(cursor, value.length);
162
+ return type;
163
+ }
164
+ /** 写入 TypedArray */
165
+ export function writeTypedArray(cursor: EncodeCursor, value: ArrayBufferView): void {
166
+ cursor.ensureCapacity(9 + value.byteLength);
167
+ const type = writeTypedArrayHeader(cursor, value);
168
+ writeTypedArrayData(cursor, type, value);
169
+ }
170
+ /** 写入 TypedArray 数据 */
171
+ export function writeTypedArrayData(cursor: EncodeCursor, type: TypedArrayType, value: ArrayBufferView): void {
172
+ const { byteLength } = value;
173
+ cursor.ensureCapacity(byteLength);
174
+ let pointer = cursor.length;
175
+ cursor.length = pointer + byteLength;
176
+ if (type === constants.UINT8 || type === constants.INT8) {
177
+ // fast path for typed arrays with `BYTES_PER_ELEMENT` of 1
178
+ cursor.data.set(value as Uint8Array | Int8Array, pointer);
179
+ return;
180
+ }
181
+
182
+ const { view } = cursor;
183
+ if (type === constants.FLOAT64) {
184
+ const arrayLength = byteLength / 8;
185
+ for (let i = 0; i < arrayLength; i++) {
186
+ view.setFloat64(pointer, (value as Float64Array)[i]!);
187
+ pointer += 8;
188
+ }
189
+ } else if (type === constants.INT32) {
190
+ const arrayLength = byteLength / 4;
191
+ for (let i = 0; i < arrayLength; i++) {
192
+ view.setInt32(pointer, (value as Int32Array)[i]!);
193
+ pointer += 4;
194
+ }
195
+ } else if (type === constants.INT64) {
196
+ const arrayLength = byteLength / 8;
197
+ for (let i = 0; i < arrayLength; i++) {
198
+ view.setBigInt64(pointer, (value as BigInt64Array)[i]!);
199
+ pointer += 8;
200
+ }
201
+ } else if (type === constants.FLOAT32) {
202
+ const arrayLength = byteLength / 4;
203
+ for (let i = 0; i < arrayLength; i++) {
204
+ view.setFloat32(pointer, (value as Float32Array)[i]!);
205
+ pointer += 4;
206
+ }
207
+ } else {
208
+ (type) satisfies constants.INT16;
209
+ const arrayLength = byteLength / 2;
210
+ for (let i = 0; i < arrayLength; i++) {
211
+ view.setInt16(pointer, (value as Int16Array)[i]!);
212
+ pointer += 2;
213
+ }
214
+ }
89
215
  }
@@ -53,19 +53,19 @@ export function jsDecode(bytes: Uint8Array, begin: number, end: number): string
53
53
  }
54
54
 
55
55
  /** 解码 Ascii */
56
- function longStringInJS(buf: Uint8Array, begin: number, length: number): string | undefined {
57
- const bytes = Array.from<number>({ length });
56
+ export function longStringInJS(buf: Uint8Array, begin: number, length: number): string | undefined {
57
+ const bytes = [];
58
58
  for (let i = 0; i < length; i++) {
59
59
  const byte = buf[begin++]!;
60
- if ((byte & 0x80) > 0) {
60
+ if (byte & 0x80) {
61
61
  return;
62
62
  }
63
- bytes[i] = byte;
63
+ bytes.push(byte);
64
64
  }
65
65
  return fromCharCode(...bytes);
66
66
  }
67
67
  /** 解码 Ascii */
68
- function shortStringInJS(buf: Uint8Array, begin: number, length: number): string | undefined {
68
+ export function shortStringInJS(buf: Uint8Array, begin: number, length: number): string | undefined {
69
69
  if (length < 4) {
70
70
  if (length < 2) {
71
71
  if (length === 0) return '';
@@ -198,8 +198,12 @@ export function decode(data: Uint8Array, begin: number, end: number): string {
198
198
  const result = shortStringInJS(data, begin, length);
199
199
  if (result != null) return result;
200
200
  }
201
- // 只有小字符串有优化价值,见 benchmark-string.js
201
+ // 只有小字符串有优化价值
202
202
  if (length < TEXT_DECODER_THRESHOLD) {
203
+ // if (length < 32) {
204
+ // const result = longStringInJS(data, begin, length);
205
+ // if (result != null) return result;
206
+ // }
203
207
  // 为小字符串优化
204
208
  return jsDecode(data, begin, end);
205
209
  }
@@ -207,42 +211,9 @@ export function decode(data: Uint8Array, begin: number, end: number): string {
207
211
  return nativeDecode(data, begin, end);
208
212
  }
209
213
 
210
- const KEY_CACHE = Array.from<{ value: string; buffer: Uint8Array } | undefined>({ length: 4096 });
211
-
212
- /** 字符串解码,使用缓存 */
213
- export function decodeKey(data: Uint8Array, begin: number, end: number): string {
214
- const length = end - begin;
215
- const cacheKey =
216
- ((length << 5) ^ (length > 1 ? data[begin]! & (data[begin + 1]! << 8) : length > 0 ? data[begin]! : 0)) & 0xfff;
217
- let entry = KEY_CACHE[cacheKey];
218
- if (entry != null && entry.buffer.byteLength === length) {
219
- let i = 0;
220
- for (; i < length; i++) {
221
- if (entry.buffer[i] !== data[begin + i]) break;
222
- }
223
- if (i === length) return entry.value;
224
- }
225
-
226
- let str = length < 16 ? shortStringInJS(data, begin, length) : longStringInJS(data, begin, length);
227
- if (str == null) {
228
- // 只有小字符串有优化价值,见 benchmark-string.js
229
- if (length < TEXT_DECODER_THRESHOLD) {
230
- // 为小字符串优化
231
- str = jsDecode(data, begin, end);
232
- } else {
233
- // 使用系统解码
234
- str = nativeDecode(data, begin, end);
235
- }
236
- }
237
- entry = { value: str, buffer: data.slice(begin, end) };
238
- KEY_CACHE[cacheKey] = entry;
239
- return str;
240
- }
241
-
242
214
  /** 重设环境 */
243
215
  export function resetEnv(): void {
244
216
  TEXT_DECODER = typeof TextDecoder == 'function' ? new TextDecoder('utf8', { ignoreBOM: true, fatal: false }) : null;
245
217
  TEXT_DECODER_THRESHOLD = TEXT_DECODER == null ? 0xffff_ffff : 16;
246
- KEY_CACHE.fill(undefined);
247
218
  }
248
219
  resetEnv();
package/src/index.ts CHANGED
@@ -1,15 +1,18 @@
1
1
  import { getEncoder } from './encoder.js';
2
2
  import { Decoder } from './decoder.js';
3
+ import type { EncodeOptions } from './options.js';
4
+
3
5
  export { UnexpectedEofError as UnexpectedEof } from './helper/errors.js';
6
+ export type { EncodeOptions };
4
7
 
5
8
  /** 编码为 UBJSON */
6
- export function encode(value: unknown): Uint8Array {
7
- return getEncoder().encode(value);
9
+ export function encode(value: unknown, options?: EncodeOptions): Uint8Array {
10
+ return getEncoder(options).encode(value);
8
11
  }
9
12
 
10
13
  /** 编码为 UBJSON */
11
- export function encodeMany(value: Iterable<unknown>): Uint8Array {
12
- return getEncoder().encodeMany(value);
14
+ export function encodeMany(value: Iterable<unknown>, options?: EncodeOptions): Uint8Array {
15
+ return getEncoder(options).encodeMany(value);
13
16
  }
14
17
 
15
18
  /** 解码 UBJSON */
package/src/options.ts ADDED
@@ -0,0 +1,5 @@
1
+ /** 序列化选项 */
2
+ export interface EncodeOptions {
3
+ /** 序列化对象时排序属性 */
4
+ sortObjectKeys?: boolean;
5
+ }
@@ -1,69 +1,95 @@
1
1
  import { Observable, type OperatorFunction } from 'rxjs';
2
- import { StreamDecoderHelper, kEof, UnexpectedEof } from '../stream-helper/decoder.js';
2
+ import { UnexpectedEof } from '../stream-helper/decoder.js';
3
3
  import { toUint8Array } from '../helper/utils.js';
4
+ import { read } from '../helper/decode-ae.js';
4
5
 
5
- const DEFAULT_BUFFER_SIZE = 16 * 1024 * 1024; // 16 MiB
6
6
  const EMPTY_BUFFER = new Uint8Array(0);
7
+ const EMPTY_VIEW = new DataView(EMPTY_BUFFER.buffer);
7
8
 
8
9
  /** 流式解码 UBJSON */
9
10
  export function decode(): OperatorFunction<BinaryData, unknown> {
10
11
  return (observable) => {
11
12
  return new Observable<unknown>((subscriber) => {
12
- let chunkSize = DEFAULT_BUFFER_SIZE;
13
- let buffer = new Uint8Array(chunkSize);
14
- let begin = 0;
15
- let end = 0;
13
+ const data = EMPTY_BUFFER;
14
+ const cursor = {
15
+ view: EMPTY_VIEW,
16
+ data,
17
+ capacity: 0,
18
+ size: 0,
19
+ offset: 0,
20
+ };
21
+ /** reader 返回的还需接收的字节数 */
22
+ let required = 1;
23
+ let reader = read(cursor);
16
24
  return observable.subscribe({
17
25
  next(value) {
18
- const data = toUint8Array(value);
19
- if (buffer.length >= end + data.length) {
20
- buffer.set(data, end);
21
- end += data.length;
26
+ const chunk = toUint8Array(value);
27
+ const chunkSize = chunk.byteLength;
28
+ if (cursor.capacity - cursor.size < chunkSize) {
29
+ // 当前缓冲区不足,需要扩容
30
+ const newSize = Math.max(
31
+ // 不缩小缓冲区
32
+ cursor.capacity,
33
+ // 扩大缓冲区到足够容纳 2 倍当前数据
34
+ chunkSize * 2 + cursor.size - cursor.offset,
35
+ );
36
+ if (newSize > cursor.capacity) {
37
+ // 需要增大缓冲区
38
+ const newData = new Uint8Array(newSize);
39
+ newData.set(cursor.data.subarray(cursor.offset, cursor.size), 0);
40
+ newData.set(chunk, cursor.size - cursor.offset);
41
+ cursor.data = newData;
42
+ cursor.view = new DataView(newData.buffer, newData.byteOffset, newData.byteLength);
43
+ cursor.capacity = newSize;
44
+ } else {
45
+ // 无需增大缓冲区,直接移动数据
46
+ cursor.data.copyWithin(0, cursor.offset, cursor.size);
47
+ cursor.data.set(chunk, cursor.size - cursor.offset);
48
+ }
49
+ cursor.size = cursor.size - cursor.offset + chunkSize;
50
+ cursor.offset = 0;
22
51
  } else {
23
- chunkSize = Math.max(chunkSize, data.length * 2 + end - begin);
24
- const allocSize = Math.max(chunkSize, data.length + end - begin);
25
- const newBuffer =
26
- allocSize > buffer.byteLength
27
- ? new Uint8Array(Math.max(chunkSize, data.length + end - begin))
28
- : buffer;
29
- newBuffer.set(buffer.subarray(begin, end), 0);
30
- newBuffer.set(data, end - begin);
31
- buffer = newBuffer;
32
- end = end - begin + data.length;
33
- begin = 0;
52
+ // 当前缓冲区足够,直接写入
53
+ cursor.data.set(chunk, cursor.size);
54
+ cursor.size += chunkSize;
34
55
  }
35
- while (end - begin > 0) {
36
- try {
37
- const helper = new StreamDecoderHelper(buffer.slice(begin, end));
38
- const result = helper.decode();
39
- if (result !== undefined) {
40
- subscriber.next(result);
41
- }
42
- begin += helper.readLength;
43
- } catch (ex) {
44
- if (ex === kEof) {
45
- return;
56
+
57
+ required -= chunkSize;
58
+ // 未读够数据,继续等待
59
+ if (required > 0) return;
60
+
61
+ try {
62
+ for (;;) {
63
+ const result = reader.next();
64
+ if (result.done) {
65
+ // 读取完成,新建 reader 读取下一个值
66
+ subscriber.next(result.value);
67
+ reader = read(cursor);
68
+ if (cursor.offset === cursor.size) {
69
+ break;
70
+ }
46
71
  } else {
47
- subscriber.error(ex as Error);
48
- buffer = EMPTY_BUFFER;
49
- return;
72
+ required = result.value;
73
+ break;
50
74
  }
51
75
  }
76
+ } catch (ex) {
77
+ subscriber.error(ex);
52
78
  }
53
- // 完全消费了 Buffer 内容,重置 Buffer
54
- begin = end = 0;
55
79
  },
56
80
  error(err) {
57
81
  subscriber.error(err);
58
- buffer = EMPTY_BUFFER;
82
+ cursor.data = EMPTY_BUFFER;
83
+ cursor.view = EMPTY_VIEW;
59
84
  },
60
85
  complete() {
61
- if (end - begin > 0) {
86
+ if (cursor.size > cursor.offset) {
62
87
  subscriber.error(new UnexpectedEof());
63
88
  } else {
64
89
  subscriber.complete();
65
90
  }
66
- buffer = EMPTY_BUFFER;
91
+ cursor.data = EMPTY_BUFFER;
92
+ cursor.view = EMPTY_VIEW;
67
93
  },
68
94
  });
69
95
  });
@@ -1,11 +1,12 @@
1
1
  import { Observable, type OperatorFunction } from 'rxjs';
2
2
  import { StreamEncoderHelper } from '../stream-helper/encoder.js';
3
+ import type { EncodeOptions } from '../options.js';
3
4
 
4
5
  /** 流式编码 UBJSON */
5
- export function encode(): OperatorFunction<unknown, Uint8Array> {
6
+ export function encode(options?: EncodeOptions): OperatorFunction<unknown, Uint8Array> {
6
7
  return (observable) => {
7
8
  return new Observable<Uint8Array>((subscriber) => {
8
- const helper = new StreamEncoderHelper((chunk: Uint8Array): void => subscriber.next(chunk));
9
+ const helper = new StreamEncoderHelper(options, (chunk: Uint8Array): void => subscriber.next(chunk));
9
10
  const sub = observable.subscribe({
10
11
  next(value) {
11
12
  try {
package/src/rxjs/index.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export { encode } from './encoder.js';
2
2
  export { decode } from './decoder.js';
3
3
  export { UnexpectedEofError as UnexpectedEof } from '../helper/errors.js';
4
+ export type { EncodeOptions } from '../options.js';
@@ -1,14 +1,15 @@
1
1
  import { Transform, type TransformCallback } from 'node:stream';
2
2
  import { StreamEncoderHelper } from '../stream-helper/encoder.js';
3
+ import type { EncodeOptions } from '../options.js';
3
4
 
4
5
  /** 流式编码 UBJSON */
5
6
  export class StreamEncoder extends Transform {
6
- constructor() {
7
+ constructor(options?: EncodeOptions) {
7
8
  super({
8
9
  readableObjectMode: false,
9
10
  writableObjectMode: true,
10
11
  });
11
- this.helper = new StreamEncoderHelper((binary) => this.push(binary));
12
+ this.helper = new StreamEncoderHelper(options, (binary) => this.push(binary));
12
13
  }
13
14
 
14
15
  private readonly helper;
@@ -24,7 +25,7 @@ export class StreamEncoder extends Transform {
24
25
  }
25
26
 
26
27
  /** @inheritdoc */
27
- override _destroy(error: Error | null, callback: (error?: Error | null | undefined) => void): void {
28
+ override _destroy(error: Error | null, callback: (error?: Error | null) => void): void {
28
29
  this.helper.destroy();
29
30
  super._destroy(error, callback);
30
31
  }
@@ -1,22 +1,24 @@
1
1
  import { Readable, type Transform } from 'node:stream';
2
2
  import { StreamEncoder } from './encoder.js';
3
3
  import { StreamDecoder } from './decoder.js';
4
+ import type { EncodeOptions } from '../options.js';
4
5
 
5
6
  export { UnexpectedEofError as UnexpectedEof } from '../helper/errors.js';
7
+ export type { EncodeOptions };
6
8
 
7
9
  /** 编码为 UBJSON */
8
- export function encode(value: unknown): Readable {
10
+ export function encode(value: unknown, options?: EncodeOptions): Readable {
9
11
  if (value == null) {
10
12
  return Readable.from([value === null ? 'Z' : 'N'], { objectMode: false });
11
13
  }
12
- const encoder = new StreamEncoder();
14
+ const encoder = new StreamEncoder(options);
13
15
  encoder.write(value);
14
16
  encoder.end();
15
17
  return encoder;
16
18
  }
17
19
  /** 编码为 UBJSON */
18
- export function encodeMany(value: AsyncIterable<unknown>): Readable {
19
- const encoder = new StreamEncoder();
20
+ export function encodeMany(value: AsyncIterable<unknown>, options?: EncodeOptions): Readable {
21
+ const encoder = new StreamEncoder(options);
20
22
  void (async () => {
21
23
  try {
22
24
  for await (const v of value) {
@@ -32,8 +34,8 @@ export function encodeMany(value: AsyncIterable<unknown>): Readable {
32
34
  }
33
35
 
34
36
  /** 编码为 UBJSON */
35
- export function encoder(): Transform {
36
- return new StreamEncoder();
37
+ export function encoder(options?: EncodeOptions): Transform {
38
+ return new StreamEncoder(options);
37
39
  }
38
40
 
39
41
  /** 解码 UBJSON */