Skip to content

Commit 57b5646

Browse files
authored
Merge pull request #156 from msgpack/decode_multi
introduce decodeMulti()
2 parents 04e2270 + b41a754 commit 57b5646

File tree

7 files changed

+113
-39
lines changed

7 files changed

+113
-39
lines changed

README.md

Lines changed: 32 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,10 @@ deepStrictEqual(decode(encoded), object);
4747
- [`EncodeOptions`](#encodeoptions)
4848
- [`decode(buffer: ArrayLike<number> | BufferSource, options?: DecodeOptions): unknown`](#decodebuffer-arraylikenumber--buffersource-options-decodeoptions-unknown)
4949
- [`DecodeOptions`](#decodeoptions)
50+
- [`decodeMulti(buffer: ArrayLike<number> | BufferSource, options?: DecodeOptions): Generator<unknown, void, unknown>`](#decodemultibuffer-arraylikenumber--buffersource-options-decodeoptions-generatorunknown-void-unknown)
5051
- [`decodeAsync(stream: ReadableStreamLike<ArrayLike<number> | BufferSource>, options?: DecodeAsyncOptions): Promise<unknown>`](#decodeasyncstream-readablestreamlikearraylikenumber--buffersource-options-decodeasyncoptions-promiseunknown)
5152
- [`decodeArrayStream(stream: ReadableStreamLike<ArrayLike<number> | BufferSource>, options?: DecodeAsyncOptions): AsyncIterable<unknown>`](#decodearraystreamstream-readablestreamlikearraylikenumber--buffersource-options-decodeasyncoptions-asynciterableunknown)
52-
- [`decodeStream(stream: ReadableStreamLike<ArrayLike<number> | BufferSource>, options?: DecodeAsyncOptions): AsyncIterable<unknown>`](#decodestreamstream-readablestreamlikearraylikenumber--buffersource-options-decodeasyncoptions-asynciterableunknown)
53+
- [`decodeMultiStream(stream: ReadableStreamLike<ArrayLike<number> | BufferSource>, options?: DecodeAsyncOptions): AsyncIterable<unknown>`](#decodemultistreamstream-readablestreamlikearraylikenumber--buffersource-options-decodeasyncoptions-asynciterableunknown)
5354
- [Reusing Encoder and Decoder instances](#reusing-encoder-and-decoder-instances)
5455
- [Extension Types](#extension-types)
5556
- [ExtensionCodec context](#extensioncodec-context)
@@ -87,7 +88,7 @@ npm install @msgpack/msgpack
8788

8889
### `encode(data: unknown, options?: EncodeOptions): Uint8Array`
8990

90-
It encodes `data` and returns a byte array as `Uint8Array`, throwing errors if `data` is, or includes, a non-serializable object such as a `function` or a `symbol`.
91+
It encodes `data` into a single MessagePack-encoded object, and returns a byte array as `Uint8Array`, throwing errors if `data` is, or includes, a non-serializable object such as a `function` or a `symbol`.
9192

9293
for example:
9394

@@ -98,7 +99,7 @@ const encoded: Uint8Array = encode({ foo: "bar" });
9899
console.log(encoded);
99100
```
100101

101-
If you'd like to convert the uint8array to a NodeJS `Buffer`, use `Buffer.from(arrayBuffer, offset, length)` in order not to copy the underlying `ArrayBuffer`, while `Buffer.from(uint8array)` copies it:
102+
If you'd like to convert an `uint8array` to a NodeJS `Buffer`, use `Buffer.from(arrayBuffer, offset, length)` in order not to copy the underlying `ArrayBuffer`, while `Buffer.from(uint8array)` copies it:
102103

103104
```typescript
104105
import { encode } from "@msgpack/msgpack";
@@ -114,7 +115,7 @@ console.log(buffer);
114115

115116
Name|Type|Default
116117
----|----|----
117-
extensionCodec | ExtensionCodec | `ExtensinCodec.defaultCodec`
118+
extensionCodec | ExtensionCodec | `ExtensionCodec.defaultCodec`
118119
maxDepth | number | `100`
119120
initialBufferSize | number | `2048`
120121
sortKeys | boolean | false
@@ -125,10 +126,12 @@ context | user-defined | -
125126

126127
### `decode(buffer: ArrayLike<number> | BufferSource, options?: DecodeOptions): unknown`
127128

128-
It decodes `buffer` encoded in MessagePack, and returns a decoded object as `unknown`.
129+
It decodes `buffer` that includes a MessagePack-encoded object, and returns the decoded object typed `unknown`.
129130

130131
`buffer` must be an array of bytes, which is typically `Uint8Array` or `ArrayBuffer`. `BufferSource` is defined as `ArrayBuffer | ArrayBufferView`.
131132

133+
In addition, `buffer` can include a single encoded object. If the `buffer` includes extra bytes after an object, it will throw `RangeError`. To decode `buffer` that includes multiple encoded objects, use `decodeMulti()` or `decodeMultiStream()` (recommended) instead.
134+
132135
for example:
133136

134137
```typescript
@@ -155,9 +158,27 @@ context | user-defined | -
155158

156159
You can use `max${Type}Length` to limit the length of each type decoded.
157160

161+
### `decodeMulti(buffer: ArrayLike<number> | BufferSource, options?: DecodeOptions): Generator<unknown, void, unknown>`
162+
163+
It decodes `buffer` that includes multiple MessagePack-encoded objects, and returns decoded objects as a generator. That is, this is a synchronous variant for `decodeMultiStream()`.
164+
165+
This function is not recommended to decode a MessagePack binary via I/O stream including sockets because it's synchronous. Instead, `decodeMultiStream()` decodes it asynchronously, typically spending less time and memory.
166+
167+
for example:
168+
169+
```typescript
170+
import { decode } from "@msgpack/msgpack";
171+
172+
const encoded: Uint8Array;
173+
174+
for (const object of decodeMulti(encoded)) {
175+
console.log(object);
176+
}
177+
```
178+
158179
### `decodeAsync(stream: ReadableStreamLike<ArrayLike<number> | BufferSource>, options?: DecodeAsyncOptions): Promise<unknown>`
159180

160-
It decodes `stream`, where `ReadableStreamLike<T>` is defined as `ReadableStream<T> | AsyncIterable<T>`, in an async iterable of byte arrays, and returns decoded object as `unknown` type, wrapped in `Promise`. This function works asynchronously.
181+
It decodes `stream`, where `ReadableStreamLike<T>` is defined as `ReadableStream<T> | AsyncIterable<T>`, in an async iterable of byte arrays, and returns decoded object as `unknown` type, wrapped in `Promise`. This function works asynchronously. This is an async variant for `decode()`.
161182

162183
`DecodeAsyncOptions` is the same as `DecodeOptions` for `decode()`.
163184

@@ -178,9 +199,7 @@ if (contentType && contentType.startsWith(MSGPACK_TYPE) && response.body != null
178199

179200
### `decodeArrayStream(stream: ReadableStreamLike<ArrayLike<number> | BufferSource>, options?: DecodeAsyncOptions): AsyncIterable<unknown>`
180201

181-
It is alike to `decodeAsync()`, but only accepts an array of items as the input `stream`, and emits the decoded item one by one.
182-
183-
It throws errors when the input is not an array-family.
202+
It is alike to `decodeAsync()`, but only accepts a `stream` that includes an array of items, and emits a decoded item one by one.
184203

185204
for example:
186205

@@ -195,12 +214,11 @@ for await (const item of decodeArrayStream(stream)) {
195214
}
196215
```
197216

217+
### `decodeMultiStream(stream: ReadableStreamLike<ArrayLike<number> | BufferSource>, options?: DecodeAsyncOptions): AsyncIterable<unknown>`
198218

199-
### `decodeStream(stream: ReadableStreamLike<ArrayLike<number> | BufferSource>, options?: DecodeAsyncOptions): AsyncIterable<unknown>`
200-
201-
It is alike to `decodeAsync()` and `decodeArrayStream()`, but the input `stream` consists of independent MessagePack items.
219+
It is alike to `decodeAsync()` and `decodeArrayStream()`, but the input `stream` must consist of multiple MessagePack-encoded items. This is an asynchronous variant for `decodeMulti()`.
202220

203-
In other words, it decodes an unlimited stream and emits an item one by one.
221+
In other words, it could decode an unlimited stream and emits a decoded item one by one.
204222

205223
for example:
206224

@@ -215,21 +233,7 @@ for await (const item of decodeStream(stream)) {
215233
}
216234
```
217235

218-
If you have a multi-values MessagePack binary, you can use `decodeStream()`, but you need to convert it to a stream or an async generator like this:
219-
220-
```typescript
221-
// A function that generates an AsyncGenerator
222-
const createStream = async function* (): AsyncGenerator<Uint8Array> {
223-
yield encoded;
224-
};
225-
226-
const result: Array<unknown> = [];
227-
228-
// Decodes it with for-await
229-
for await (const item of decodeStream(createStream())) {
230-
result.push(item);
231-
}
232-
```
236+
This function is available since v2.4.0; previously it was called as `decodeStream()`.
233237

234238
### Reusing Encoder and Decoder instances
235239

src/Decoder.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,15 @@ export class Decoder<ContextType> {
125125
return object;
126126
}
127127

128+
public *decodeMulti(buffer: ArrayLike<number> | BufferSource): Generator<unknown, void, unknown> {
129+
this.reinitializeState();
130+
this.setBuffer(buffer);
131+
132+
while (this.hasRemaining()) {
133+
yield this.doDecodeSync();
134+
}
135+
}
136+
128137
public async decodeAsync(stream: AsyncIterable<ArrayLike<number> | BufferSource>): Promise<unknown> {
129138
let decoded = false;
130139
let object: unknown;

src/decode.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,10 @@ export type DecodeOptions<ContextType = undefined> = Readonly<
3838
export const defaultDecodeOptions: DecodeOptions = {};
3939

4040
/**
41-
* It decodes a MessagePack-encoded buffer.
41+
* It decodes a single MessagePack object in a buffer.
4242
*
43-
* This is a synchronous decoding function. See other variants for asynchronous decoding: `decodeAsync()`, `decodeStream()`, `decodeArrayStream()`.
43+
* This is a synchronous decoding function.
44+
* See other variants for asynchronous decoding: {@link decodeAsync()}, {@link decodeStream()}, or {@link decodeArrayStream()}.
4445
*/
4546
export function decode<ContextType = undefined>(
4647
buffer: ArrayLike<number> | BufferSource,
@@ -57,3 +58,23 @@ export function decode<ContextType = undefined>(
5758
);
5859
return decoder.decode(buffer);
5960
}
61+
62+
/**
63+
* It decodes multiple MessagePack objects in a buffer.
64+
* This is corresponding to {@link decodeMultiStream()}.
65+
*/
66+
export function decodeMulti<ContextType = undefined>(
67+
buffer: ArrayLike<number> | BufferSource,
68+
options: DecodeOptions<SplitUndefined<ContextType>> = defaultDecodeOptions as any,
69+
): Generator<unknown, void, unknown> {
70+
const decoder = new Decoder(
71+
options.extensionCodec,
72+
(options as typeof options & { context: any }).context,
73+
options.maxStrLength,
74+
options.maxBinLength,
75+
options.maxArrayLength,
76+
options.maxMapLength,
77+
options.maxExtLength,
78+
);
79+
return decoder.decodeMulti(buffer);
80+
}

src/decodeAsync.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export function decodeArrayStream<ContextType>(
4242
return decoder.decodeArrayStream(stream);
4343
}
4444

45-
export function decodeStream<ContextType>(
45+
export function decodeMultiStream<ContextType>(
4646
streamLike: ReadableStreamLike<ArrayLike<number> | BufferSource>,
4747
options: DecodeOptions<SplitUndefined<ContextType>> = defaultDecodeOptions as any,
4848
) {
@@ -60,3 +60,13 @@ export function decodeStream<ContextType>(
6060

6161
return decoder.decodeStream(stream);
6262
}
63+
64+
/**
65+
* @deprecated Use {@link decodeMultiStream()} instead.
66+
*/
67+
export function decodeStream<ContextType>(
68+
streamLike: ReadableStreamLike<ArrayLike<number> | BufferSource>,
69+
options: DecodeOptions<SplitUndefined<ContextType>> = defaultDecodeOptions as any,
70+
) {
71+
return decodeMultiStream(streamLike, options);
72+
}

src/index.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@ export { encode };
55
import type { EncodeOptions } from "./encode";
66
export type { EncodeOptions };
77

8-
import { decode } from "./decode";
9-
export { decode };
8+
import { decode, decodeMulti } from "./decode";
9+
export { decode, decodeMulti };
1010
import type { DecodeOptions } from "./decode";
1111
export { DecodeOptions };
1212

13-
import { decodeAsync, decodeArrayStream, decodeStream } from "./decodeAsync";
14-
export { decodeAsync, decodeArrayStream, decodeStream };
13+
import { decodeAsync, decodeArrayStream, decodeMultiStream, decodeStream } from "./decodeAsync";
14+
export { decodeAsync, decodeArrayStream, decodeMultiStream, decodeStream };
1515

1616
import { Decoder } from "./Decoder";
1717
export { Decoder };

test/decodeMulti.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import assert from "assert";
2+
import { encode, decodeMulti } from "@msgpack/msgpack";
3+
4+
describe("decodeMulti", () => {
5+
it("decodes multiple objects in a single binary", () => {
6+
const items = [
7+
"foo",
8+
10,
9+
{
10+
name: "bar",
11+
},
12+
[1, 2, 3],
13+
];
14+
15+
const encodedItems = items.map((item) => encode(item));
16+
const encoded = new Uint8Array(encodedItems.reduce((p, c) => p + c.byteLength, 0));
17+
let offset = 0;
18+
for (const encodedItem of encodedItems) {
19+
encoded.set(encodedItem, offset);
20+
offset += encodedItem.byteLength;
21+
}
22+
23+
const result: Array<unknown> = [];
24+
25+
for (const item of decodeMulti(encoded)) {
26+
result.push(item);
27+
}
28+
29+
assert.deepStrictEqual(result, items);
30+
});});
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import assert from "assert";
2-
import { encode, decodeStream } from "@msgpack/msgpack";
2+
import { encode, decodeMultiStream } from "@msgpack/msgpack";
33

44
describe("decodeStream", () => {
55
it("decodes stream", async () => {
@@ -20,14 +20,14 @@ describe("decodeStream", () => {
2020

2121
const result: Array<unknown> = [];
2222

23-
for await (const item of decodeStream(createStream())) {
23+
for await (const item of decodeMultiStream(createStream())) {
2424
result.push(item);
2525
}
2626

2727
assert.deepStrictEqual(result, items);
2828
});
2929

30-
it("decodes stream with a single binary", async () => {
30+
it("decodes multiple objects in a single binary stream", async () => {
3131
const items = [
3232
"foo",
3333
10,
@@ -51,7 +51,7 @@ describe("decodeStream", () => {
5151

5252
const result: Array<unknown> = [];
5353

54-
for await (const item of decodeStream(createStream())) {
54+
for await (const item of decodeMultiStream(createStream())) {
5555
result.push(item);
5656
}
5757

0 commit comments

Comments
 (0)