@atproto/common 0.4.2 → 0.4.3

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # @atproto/common
2
2
 
3
+ ## 0.4.3
4
+
5
+ ### Patch Changes
6
+
7
+ - [#2770](https://github.com/bluesky-social/atproto/pull/2770) [`a07b21151`](https://github.com/bluesky-social/atproto/commit/a07b21151f1850340c4b7797ebb11521b1a6cdf3) Thanks [@matthieusieben](https://github.com/matthieusieben)! - add streamToNodeBuffer utility to convert Uint8Array (async) iterables to Buffer
8
+
9
+ - Updated dependencies [[`a07b21151`](https://github.com/bluesky-social/atproto/commit/a07b21151f1850340c4b7797ebb11521b1a6cdf3), [`a07b21151`](https://github.com/bluesky-social/atproto/commit/a07b21151f1850340c4b7797ebb11521b1a6cdf3), [`eb20ff64a`](https://github.com/bluesky-social/atproto/commit/eb20ff64a2d8e3061c652e1e247bf9b0fe3c41a6)]:
10
+ - @atproto/common-web@0.3.1
11
+
3
12
  ## 0.4.2
4
13
 
5
14
  ### Patch Changes
package/dist/streams.d.ts CHANGED
@@ -1,10 +1,11 @@
1
1
  /// <reference types="node" />
2
2
  /// <reference types="node" />
3
- import { Stream, Readable, Transform, TransformCallback } from 'stream';
3
+ import { Duplex, Readable, Stream, Transform, TransformCallback } from 'node:stream';
4
4
  export declare const forwardStreamErrors: (...streams: Stream[]) => void;
5
5
  export declare const cloneStream: (stream: Readable) => Readable;
6
6
  export declare const streamSize: (stream: Readable) => Promise<number>;
7
- export declare const streamToBytes: (stream: Readable) => Promise<Uint8Array>;
7
+ export declare const streamToBytes: (stream: AsyncIterable<Uint8Array>) => Promise<Uint8Array>;
8
+ export declare const streamToNodeBuffer: (stream: Iterable<Uint8Array> | AsyncIterable<Uint8Array>) => Promise<Buffer>;
8
9
  export declare const byteIterableToStream: (iter: AsyncIterable<Uint8Array>) => Readable;
9
10
  export declare const bytesToStream: (bytes: Uint8Array) => Readable;
10
11
  export declare class MaxSizeChecker extends Transform {
@@ -14,4 +15,11 @@ export declare class MaxSizeChecker extends Transform {
14
15
  constructor(maxSize: number, createError: () => Error);
15
16
  _transform(chunk: Uint8Array, _enc: BufferEncoding, cb: TransformCallback): void;
16
17
  }
18
+ export declare function decodeStream(stream: Readable, contentEncoding?: string): Readable;
19
+ export declare function decodeStream(stream: AsyncIterable<Uint8Array>, contentEncoding?: string): AsyncIterable<Uint8Array> | Readable;
20
+ /**
21
+ * Create a series of decoding streams based on the content-encoding header. The
22
+ * resulting streams should be piped together to decode the content.
23
+ */
24
+ export declare function createDecoders(contentEncoding?: string): Duplex[];
17
25
  //# sourceMappingURL=streams.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"streams.d.ts","sourceRoot":"","sources":["../src/streams.ts"],"names":[],"mappings":";;AAAA,OAAO,EACL,MAAM,EACN,QAAQ,EAER,SAAS,EACT,iBAAiB,EAClB,MAAM,QAAQ,CAAA;AAEf,eAAO,MAAM,mBAAmB,eAAgB,MAAM,EAAE,SAOvD,CAAA;AAED,eAAO,MAAM,WAAW,WAAY,QAAQ,KAAG,QAI9C,CAAA;AAED,eAAO,MAAM,UAAU,WAAkB,QAAQ,KAAG,QAAQ,MAAM,CAMjE,CAAA;AAED,eAAO,MAAM,aAAa,WAAkB,QAAQ,KAAG,QAAQ,UAAU,CAMxE,CAAA;AAED,eAAO,MAAM,oBAAoB,SACzB,cAAc,UAAU,CAAC,KAC9B,QAEF,CAAA;AAED,eAAO,MAAM,aAAa,UAAW,UAAU,KAAG,QAKjD,CAAA;AAED,qBAAa,cAAe,SAAQ,SAAS;IAGlC,OAAO,EAAE,MAAM;IACf,WAAW,EAAE,MAAM,KAAK;IAHjC,SAAS,SAAI;gBAEJ,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,MAAM,KAAK;IAIjC,UAAU,CAAC,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,cAAc,EAAE,EAAE,EAAE,iBAAiB;CAQ1E"}
1
+ {"version":3,"file":"streams.d.ts","sourceRoot":"","sources":["../src/streams.ts"],"names":[],"mappings":";;AAAA,OAAO,EACL,MAAM,EAGN,QAAQ,EACR,MAAM,EACN,SAAS,EACT,iBAAiB,EAClB,MAAM,aAAa,CAAA;AAGpB,eAAO,MAAM,mBAAmB,eAAgB,MAAM,EAAE,SAOvD,CAAA;AAED,eAAO,MAAM,WAAW,WAAY,QAAQ,KAAG,QAI9C,CAAA;AAED,eAAO,MAAM,UAAU,WAAkB,QAAQ,KAAG,QAAQ,MAAM,CAMjE,CAAA;AAED,eAAO,MAAM,aAAa,WAAkB,cAAc,UAAU,CAAC,wBAInB,CAAA;AAGlD,eAAO,MAAM,kBAAkB,WACrB,SAAS,UAAU,CAAC,GAAG,cAAc,UAAU,CAAC,KACvD,QAAQ,MAAM,CAYhB,CAAA;AAED,eAAO,MAAM,oBAAoB,SACzB,cAAc,UAAU,CAAC,KAC9B,QAEF,CAAA;AAED,eAAO,MAAM,aAAa,UAAW,UAAU,KAAG,QAKjD,CAAA;AAED,qBAAa,cAAe,SAAQ,SAAS;IAGlC,OAAO,EAAE,MAAM;IACf,WAAW,EAAE,MAAM,KAAK;IAHjC,SAAS,SAAI;gBAEJ,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,MAAM,KAAK;IAIjC,UAAU,CAAC,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,cAAc,EAAE,EAAE,EAAE,iBAAiB;CAQ1E;AAED,wBAAgB,YAAY,CAC1B,MAAM,EAAE,QAAQ,EAChB,eAAe,CAAC,EAAE,MAAM,GACvB,QAAQ,CAAA;AACX,wBAAgB,YAAY,CAC1B,MAAM,EAAE,aAAa,CAAC,UAAU,CAAC,EACjC,eAAe,CAAC,EAAE,MAAM,GACvB,aAAa,CAAC,UAAU,CAAC,GAAG,QAAQ,CAAA;AAUvC;;;GAGG;AACH,wBAAgB,cAAc,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAcjE"}
package/dist/streams.js CHANGED
@@ -1,7 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.MaxSizeChecker = exports.bytesToStream = exports.byteIterableToStream = exports.streamToBytes = exports.streamSize = exports.cloneStream = exports.forwardStreamErrors = void 0;
4
- const stream_1 = require("stream");
3
+ exports.createDecoders = exports.decodeStream = exports.MaxSizeChecker = exports.bytesToStream = exports.byteIterableToStream = exports.streamToNodeBuffer = exports.streamToBytes = exports.streamSize = exports.cloneStream = exports.forwardStreamErrors = void 0;
4
+ const node_stream_1 = require("node:stream");
5
+ const node_zlib_1 = require("node:zlib");
5
6
  const forwardStreamErrors = (...streams) => {
6
7
  for (let i = 1; i < streams.length; ++i) {
7
8
  const prev = streams[i - 1];
@@ -11,7 +12,7 @@ const forwardStreamErrors = (...streams) => {
11
12
  };
12
13
  exports.forwardStreamErrors = forwardStreamErrors;
13
14
  const cloneStream = (stream) => {
14
- const passthrough = new stream_1.PassThrough();
15
+ const passthrough = new node_stream_1.PassThrough();
15
16
  (0, exports.forwardStreamErrors)(stream, passthrough);
16
17
  return stream.pipe(passthrough);
17
18
  };
@@ -19,31 +20,45 @@ exports.cloneStream = cloneStream;
19
20
  const streamSize = async (stream) => {
20
21
  let size = 0;
21
22
  for await (const chunk of stream) {
22
- size += chunk.length;
23
+ size += Buffer.byteLength(chunk);
23
24
  }
24
25
  return size;
25
26
  };
26
27
  exports.streamSize = streamSize;
27
- const streamToBytes = async (stream) => {
28
- const bufs = [];
29
- for await (const bytes of stream) {
30
- bufs.push(bytes);
28
+ const streamToBytes = async (stream) =>
29
+ // @NOTE Though Buffer is a sub-class of Uint8Array, we have observed
30
+ // inconsistencies when using a Buffer in place of Uint8Array. For this
31
+ // reason, we convert the Buffer to a Uint8Array.
32
+ new Uint8Array(await (0, exports.streamToNodeBuffer)(stream));
33
+ exports.streamToBytes = streamToBytes;
34
+ // streamToBuffer identifier name already taken by @atproto/common-web
35
+ const streamToNodeBuffer = async (stream) => {
36
+ const chunks = [];
37
+ let totalLength = 0; // keep track of total length for Buffer.concat
38
+ for await (const chunk of stream) {
39
+ if (chunk instanceof Uint8Array) {
40
+ chunks.push(chunk);
41
+ totalLength += Buffer.byteLength(chunk);
42
+ }
43
+ else {
44
+ throw new TypeError('expected Uint8Array');
45
+ }
31
46
  }
32
- return new Uint8Array(Buffer.concat(bufs));
47
+ return Buffer.concat(chunks, totalLength);
33
48
  };
34
- exports.streamToBytes = streamToBytes;
49
+ exports.streamToNodeBuffer = streamToNodeBuffer;
35
50
  const byteIterableToStream = (iter) => {
36
- return stream_1.Readable.from(iter, { objectMode: false });
51
+ return node_stream_1.Readable.from(iter, { objectMode: false });
37
52
  };
38
53
  exports.byteIterableToStream = byteIterableToStream;
39
54
  const bytesToStream = (bytes) => {
40
- const stream = new stream_1.Readable();
55
+ const stream = new node_stream_1.Readable();
41
56
  stream.push(bytes);
42
57
  stream.push(null);
43
58
  return stream;
44
59
  };
45
60
  exports.bytesToStream = bytesToStream;
46
- class MaxSizeChecker extends stream_1.Transform {
61
+ class MaxSizeChecker extends node_stream_1.Transform {
47
62
  constructor(maxSize, createError) {
48
63
  super();
49
64
  Object.defineProperty(this, "maxSize", {
@@ -76,4 +91,50 @@ class MaxSizeChecker extends stream_1.Transform {
76
91
  }
77
92
  }
78
93
  exports.MaxSizeChecker = MaxSizeChecker;
94
+ function decodeStream(stream, contentEncoding) {
95
+ const decoders = createDecoders(contentEncoding);
96
+ if (decoders.length === 0)
97
+ return stream;
98
+ return (0, node_stream_1.pipeline)([stream, ...decoders], () => { });
99
+ }
100
+ exports.decodeStream = decodeStream;
101
+ /**
102
+ * Create a series of decoding streams based on the content-encoding header. The
103
+ * resulting streams should be piped together to decode the content.
104
+ */
105
+ function createDecoders(contentEncoding) {
106
+ const decoders = [];
107
+ if (contentEncoding) {
108
+ const encodings = contentEncoding.split(',');
109
+ for (const encoding of encodings) {
110
+ const normalizedEncoding = normalizeEncoding(encoding);
111
+ if (normalizedEncoding === 'identity')
112
+ continue;
113
+ decoders.push(createDecoder(normalizedEncoding));
114
+ }
115
+ }
116
+ return decoders.reverse();
117
+ }
118
+ exports.createDecoders = createDecoders;
119
+ function normalizeEncoding(encoding) {
120
+ // https://www.rfc-editor.org/rfc/rfc7231#section-3.1.2.1
121
+ // > All content-coding values are case-insensitive...
122
+ return encoding.trim().toLowerCase();
123
+ }
124
+ function createDecoder(normalizedEncoding) {
125
+ switch (normalizedEncoding) {
126
+ // https://www.rfc-editor.org/rfc/rfc9112.html#section-7.2
127
+ case 'gzip':
128
+ case 'x-gzip':
129
+ return (0, node_zlib_1.createGunzip)();
130
+ case 'deflate':
131
+ return (0, node_zlib_1.createInflate)();
132
+ case 'br':
133
+ return (0, node_zlib_1.createBrotliDecompress)();
134
+ case 'identity':
135
+ return new node_stream_1.PassThrough();
136
+ default:
137
+ throw new TypeError(`Unsupported content-encoding: "${normalizedEncoding}"`);
138
+ }
139
+ }
79
140
  //# sourceMappingURL=streams.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"streams.js","sourceRoot":"","sources":["../src/streams.ts"],"names":[],"mappings":";;;AAAA,mCAMe;AAER,MAAM,mBAAmB,GAAG,CAAC,GAAG,OAAiB,EAAE,EAAE;IAC1D,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,EAAE,CAAC;QACxC,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAA;QAC3B,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;QAEvB,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAA;IACtD,CAAC;AACH,CAAC,CAAA;AAPY,QAAA,mBAAmB,uBAO/B;AAEM,MAAM,WAAW,GAAG,CAAC,MAAgB,EAAY,EAAE;IACxD,MAAM,WAAW,GAAG,IAAI,oBAAW,EAAE,CAAA;IACrC,IAAA,2BAAmB,EAAC,MAAM,EAAE,WAAW,CAAC,CAAA;IACxC,OAAO,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;AACjC,CAAC,CAAA;AAJY,QAAA,WAAW,eAIvB;AAEM,MAAM,UAAU,GAAG,KAAK,EAAE,MAAgB,EAAmB,EAAE;IACpE,IAAI,IAAI,GAAG,CAAC,CAAA;IACZ,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QACjC,IAAI,IAAI,KAAK,CAAC,MAAM,CAAA;IACtB,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC,CAAA;AANY,QAAA,UAAU,cAMtB;AAEM,MAAM,aAAa,GAAG,KAAK,EAAE,MAAgB,EAAuB,EAAE;IAC3E,MAAM,IAAI,GAAa,EAAE,CAAA;IACzB,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QACjC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IAClB,CAAC;IACD,OAAO,IAAI,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAA;AAC5C,CAAC,CAAA;AANY,QAAA,aAAa,iBAMzB;AAEM,MAAM,oBAAoB,GAAG,CAClC,IAA+B,EACrB,EAAE;IACZ,OAAO,iBAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,CAAA;AACnD,CAAC,CAAA;AAJY,QAAA,oBAAoB,wBAIhC;AAEM,MAAM,aAAa,GAAG,CAAC,KAAiB,EAAY,EAAE;IAC3D,MAAM,MAAM,GAAG,IAAI,iBAAQ,EAAE,CAAA;IAC7B,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IAClB,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACjB,OAAO,MAAM,CAAA;AACf,CAAC,CAAA;AALY,QAAA,aAAa,iBAKzB;AAED,MAAa,cAAe,SAAQ,kBAAS;IAE3C,YACS,OAAe,EACf,WAAwB;QAE/B,KAAK,EAAE,CAAA;QAHP;;;;mBAAO,OAAO;WAAQ;QACtB;;;;mBAAO,WAAW;WAAa;QAHjC;;;;mBAAY,CAAC;WAAA;IAMb,CAAC;IACD,UAAU,CAAC,KAAiB,EAAE,IAAoB,EAAE,EAAqB;QACvE,IAAI,CAAC,SAAS,IAAI,KAAK,CAAC,MAAM,CAAA;QAC9B,IAAI,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;YAClC,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAA;QACxB,CAAC;aAAM,CAAC;YACN,EAAE,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;QACjB,CAAC;IACH,CAAC;CACF;AAhBD,wCAgBC"}
1
+ {"version":3,"file":"streams.js","sourceRoot":"","sources":["../src/streams.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atproto/common",
3
- "version": "0.4.2",
3
+ "version": "0.4.3",
4
4
  "license": "MIT",
5
5
  "description": "Shared web-platform-friendly code for atproto libraries",
6
6
  "keywords": [
@@ -20,7 +20,7 @@
20
20
  "iso-datestring-validator": "^2.2.2",
21
21
  "multiformats": "^9.9.0",
22
22
  "pino": "^8.21.0",
23
- "@atproto/common-web": "^0.3.0"
23
+ "@atproto/common-web": "^0.3.1"
24
24
  },
25
25
  "devDependencies": {
26
26
  "jest": "^28.1.2",
package/src/streams.ts CHANGED
@@ -1,10 +1,13 @@
1
1
  import {
2
- Stream,
3
- Readable,
2
+ Duplex,
4
3
  PassThrough,
4
+ pipeline,
5
+ Readable,
6
+ Stream,
5
7
  Transform,
6
8
  TransformCallback,
7
- } from 'stream'
9
+ } from 'node:stream'
10
+ import { createBrotliDecompress, createGunzip, createInflate } from 'node:zlib'
8
11
 
9
12
  export const forwardStreamErrors = (...streams: Stream[]) => {
10
13
  for (let i = 1; i < streams.length; ++i) {
@@ -24,17 +27,32 @@ export const cloneStream = (stream: Readable): Readable => {
24
27
  export const streamSize = async (stream: Readable): Promise<number> => {
25
28
  let size = 0
26
29
  for await (const chunk of stream) {
27
- size += chunk.length
30
+ size += Buffer.byteLength(chunk)
28
31
  }
29
32
  return size
30
33
  }
31
34
 
32
- export const streamToBytes = async (stream: Readable): Promise<Uint8Array> => {
33
- const bufs: Buffer[] = []
34
- for await (const bytes of stream) {
35
- bufs.push(bytes)
35
+ export const streamToBytes = async (stream: AsyncIterable<Uint8Array>) =>
36
+ // @NOTE Though Buffer is a sub-class of Uint8Array, we have observed
37
+ // inconsistencies when using a Buffer in place of Uint8Array. For this
38
+ // reason, we convert the Buffer to a Uint8Array.
39
+ new Uint8Array(await streamToNodeBuffer(stream))
40
+
41
+ // streamToBuffer identifier name already taken by @atproto/common-web
42
+ export const streamToNodeBuffer = async (
43
+ stream: Iterable<Uint8Array> | AsyncIterable<Uint8Array>,
44
+ ): Promise<Buffer> => {
45
+ const chunks: Uint8Array[] = []
46
+ let totalLength = 0 // keep track of total length for Buffer.concat
47
+ for await (const chunk of stream) {
48
+ if (chunk instanceof Uint8Array) {
49
+ chunks.push(chunk)
50
+ totalLength += Buffer.byteLength(chunk)
51
+ } else {
52
+ throw new TypeError('expected Uint8Array')
53
+ }
36
54
  }
37
- return new Uint8Array(Buffer.concat(bufs))
55
+ return Buffer.concat(chunks, totalLength)
38
56
  }
39
57
 
40
58
  export const byteIterableToStream = (
@@ -67,3 +85,65 @@ export class MaxSizeChecker extends Transform {
67
85
  }
68
86
  }
69
87
  }
88
+
89
+ export function decodeStream(
90
+ stream: Readable,
91
+ contentEncoding?: string,
92
+ ): Readable
93
+ export function decodeStream(
94
+ stream: AsyncIterable<Uint8Array>,
95
+ contentEncoding?: string,
96
+ ): AsyncIterable<Uint8Array> | Readable
97
+ export function decodeStream(
98
+ stream: Readable | AsyncIterable<Uint8Array>,
99
+ contentEncoding?: string,
100
+ ): Readable | AsyncIterable<Uint8Array> {
101
+ const decoders = createDecoders(contentEncoding)
102
+ if (decoders.length === 0) return stream
103
+ return pipeline([stream as Readable, ...decoders], () => {}) as Duplex
104
+ }
105
+
106
+ /**
107
+ * Create a series of decoding streams based on the content-encoding header. The
108
+ * resulting streams should be piped together to decode the content.
109
+ */
110
+ export function createDecoders(contentEncoding?: string): Duplex[] {
111
+ const decoders: Duplex[] = []
112
+
113
+ if (contentEncoding) {
114
+ const encodings = contentEncoding.split(',')
115
+ for (const encoding of encodings) {
116
+ const normalizedEncoding = normalizeEncoding(encoding)
117
+ if (normalizedEncoding === 'identity') continue
118
+
119
+ decoders.push(createDecoder(normalizedEncoding))
120
+ }
121
+ }
122
+
123
+ return decoders.reverse()
124
+ }
125
+
126
+ function normalizeEncoding(encoding: string) {
127
+ // https://www.rfc-editor.org/rfc/rfc7231#section-3.1.2.1
128
+ // > All content-coding values are case-insensitive...
129
+ return encoding.trim().toLowerCase()
130
+ }
131
+
132
+ function createDecoder(normalizedEncoding: string): Duplex {
133
+ switch (normalizedEncoding) {
134
+ // https://www.rfc-editor.org/rfc/rfc9112.html#section-7.2
135
+ case 'gzip':
136
+ case 'x-gzip':
137
+ return createGunzip()
138
+ case 'deflate':
139
+ return createInflate()
140
+ case 'br':
141
+ return createBrotliDecompress()
142
+ case 'identity':
143
+ return new PassThrough()
144
+ default:
145
+ throw new TypeError(
146
+ `Unsupported content-encoding: "${normalizedEncoding}"`,
147
+ )
148
+ }
149
+ }
@@ -61,14 +61,41 @@ describe('streams', () => {
61
61
  })
62
62
  })
63
63
 
64
- describe('streamToBytes', () => {
64
+ describe('streamToNodeBuffer', () => {
65
65
  it('converts stream to byte array', async () => {
66
66
  const stream = Readable.from(Buffer.from('foo'))
67
- const bytes = await streams.streamToBytes(stream)
67
+ const bytes = await streams.streamToNodeBuffer(stream)
68
68
 
69
69
  expect(bytes[0]).toBe('f'.charCodeAt(0))
70
70
  expect(bytes[1]).toBe('o'.charCodeAt(0))
71
71
  expect(bytes[2]).toBe('o'.charCodeAt(0))
72
+ expect(bytes.length).toBe(3)
73
+ })
74
+
75
+ it('converts async iterable to byte array', async () => {
76
+ const iterable = (async function* () {
77
+ yield Buffer.from('b')
78
+ yield Buffer.from('a')
79
+ yield new Uint8Array(['r'.charCodeAt(0)])
80
+ })()
81
+ const bytes = await streams.streamToNodeBuffer(iterable)
82
+
83
+ expect(bytes[0]).toBe('b'.charCodeAt(0))
84
+ expect(bytes[1]).toBe('a'.charCodeAt(0))
85
+ expect(bytes[2]).toBe('r'.charCodeAt(0))
86
+ expect(bytes.length).toBe(3)
87
+ })
88
+
89
+ it('throws error for non Uint8Array chunks', async () => {
90
+ const iterable: AsyncIterable<any> = (async function* () {
91
+ yield Buffer.from('b')
92
+ yield Buffer.from('a')
93
+ yield 'r'
94
+ })()
95
+
96
+ await expect(streams.streamToNodeBuffer(iterable)).rejects.toThrow(
97
+ 'expected Uint8Array',
98
+ )
72
99
  })
73
100
  })
74
101