Skip to content

Commit

Permalink
add .bytes() method to various readables (#11104)
Browse files Browse the repository at this point in the history
Co-authored-by: Jarred Sumner <[email protected]>
Co-authored-by: nektro <[email protected]>
  • Loading branch information
3 people authored May 17, 2024
1 parent ac6eaac commit 5caca9c
Show file tree
Hide file tree
Showing 39 changed files with 659 additions and 68 deletions.
5 changes: 5 additions & 0 deletions bench/snippets/react-dom-render.bun.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ group("new Response(stream).arrayBuffer()", () => {
bench("react-dom/server.bun", async () => await new Response(await renderToReadableStreamBun(<App />)).arrayBuffer());
});

group("new Response(stream).bytes()", () => {
bench("react-dom/server.browser", async () => await new Response(await renderToReadableStream(<App />)).bytes());
bench("react-dom/server.bun", async () => await new Response(await renderToReadableStreamBun(<App />)).bytes());
});

group("new Response(stream).blob()", () => {
bench("react-dom/server.browser", async () => await new Response(await renderToReadableStream(<App />)).blob());
bench("react-dom/server.bun", async () => await new Response(await renderToReadableStreamBun(<App />)).blob());
Expand Down
14 changes: 12 additions & 2 deletions docs/api/binary-data.md
Original file line number Diff line number Diff line change
Expand Up @@ -886,15 +886,25 @@ new Response(stream).arrayBuffer();
Bun.readableStreamToArrayBuffer(stream);
```

#### To `Uint8Array`

```ts
// with Response
new Response(stream).bytes();

// with Bun function
Bun.readableStreamToBytes(stream);
```

#### To `TypedArray`

```ts
// with Response
const buf = await new Response(stream).arrayBuffer();
new Uint8Array(buf);
new Int8Array(buf);

// with Bun function
new Uint8Array(Bun.readableStreamToArrayBuffer(stream));
new Int8Array(Bun.readableStreamToArrayBuffer(stream));
```

#### To `DataView`
Expand Down
1 change: 1 addition & 0 deletions docs/api/file-io.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const foo = Bun.file("foo.txt");
await foo.text(); // contents as a string
await foo.stream(); // contents as ReadableStream
await foo.arrayBuffer(); // contents as ArrayBuffer
await foo.bytes(); // contents as Uint8Array
```

File references can also be created using numerical [file descriptors](https://en.wikipedia.org/wiki/File_descriptor) or `file://` URLs.
Expand Down
6 changes: 4 additions & 2 deletions docs/api/utils.md
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,7 @@ Bun.stringWidth("\u001b[31mhello\u001b[0m", { countAnsiEscapeCodes: true }); //
```

This is useful for:

- Aligning text in a terminal
- Quickly checking if a string contains ANSI escape codes
- Measuring the width of a string in a terminal
Expand Down Expand Up @@ -372,7 +373,6 @@ npm/string-width 95,000 chars ansi+emoji+ascii 3.68 s/iter (3.66 s

{% /details %}


TypeScript definition:

```ts
Expand Down Expand Up @@ -400,7 +400,6 @@ namespace Bun {
}
```


<!-- ## `Bun.enableANSIColors()` -->

## `Bun.fileURLToPath()`
Expand Down Expand Up @@ -603,6 +602,9 @@ stream; // => ReadableStream
await Bun.readableStreamToArrayBuffer(stream);
// => ArrayBuffer

await Bun.readableStreamToBytes(stream);
// => Uint8Array

await Bun.readableStreamToBlob(stream);
// => Blob

Expand Down
1 change: 1 addition & 0 deletions docs/bundler/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -1096,6 +1096,7 @@ const build = await Bun.build({

for (const output of build.outputs) {
await output.arrayBuffer(); // => ArrayBuffer
await output.bytes(); // => Uint8Array
await output.text(); // string
}
```
Expand Down
4 changes: 2 additions & 2 deletions docs/guides/read-file/arraybuffer.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ const buffer = await file.arrayBuffer();

---

The binary content in the `ArrayBuffer` can then be read as a typed array, such as `Uint8Array`.
The binary content in the `ArrayBuffer` can then be read as a typed array, such as `Int8Array`. For `Uint8Array`, use [`.bytes()`](./uint8array).

```ts
const buffer = await file.arrayBuffer();
const bytes = new Uint8Array(buffer);
const bytes = new Int8Array(buffer);

bytes[0];
bytes.length;
Expand Down
5 changes: 2 additions & 3 deletions docs/guides/read-file/uint8array.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,13 @@ name: Read a file to a Uint8Array

The `Bun.file()` function accepts a path and returns a `BunFile` instance. The `BunFile` class extends `Blob` and allows you to lazily read the file in a variety of formats.

To read the file into a `Uint8Array` instance, retrieve the contents of the `BunFile` as an `ArrayBuffer` with `.arrayBuffer()`, then pass it into the `Uint8Array` constructor.
To read the file into a `Uint8Array` instance, retrieve the contents of the `BunFile` with `.bytes()`.

```ts
const path = "/path/to/package.json";
const file = Bun.file(path);

const arrBuffer = await file.arrayBuffer();
const byteArray = new Uint8Array(arrBuffer);
const byteArray = await file.bytes();

byteArray[0]; // first byteArray
byteArray.length; // length of byteArray
Expand Down
11 changes: 11 additions & 0 deletions docs/guides/streams/node-readable-to-uint8array.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
name: Convert a Node.js Readable to an Uint8Array
---

To convert a Node.js `Readable` stream to an `Uint8Array` in Bun, you can create a new `Response` object with the stream as the body, then use `bytes()` to read the stream into an `Uint8Array`.

```ts
import { Readable } from "stream";
const stream = Readable.from(["Hello, ", "world!"]);
const buf = await new Response(stream).bytes();
```
7 changes: 7 additions & 0 deletions docs/guides/streams/to-typedarray.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ const buf = await Bun.readableStreamToArrayBuffer(stream);
const uint8 = new Uint8Array(buf);
```

Additionally, there is a convenience method to convert to `Uint8Array` directly.

```ts
const stream = new ReadableStream();
const uint8 = await Bun.readableStreamToBytes(stream);
```

---

See [Docs > API > Utils](/docs/api/utils#bun-readablestreamto) for documentation on Bun's other `ReadableStream` conversion functions.
20 changes: 19 additions & 1 deletion packages/bun-polyfills/src/modules/bun.ts
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,21 @@ export const readableStreamToArrayBuffer = ((stream: ReadableStream<ArrayBufferV
return sink.end() as ArrayBuffer;
})();
}) satisfies typeof Bun.readableStreamToArrayBuffer;

export const readableStreamToBytes = ((stream: ReadableStream<ArrayBufferView | ArrayBufferLike>): Uint8Array | Promise<Uint8Array> => {
return (async () => {
const sink = new ArrayBufferSink();
sink.start({ asUint8Array: true });
const reader = stream.getReader();
while (true) {
const { done, value } = await reader.read();
if (done) break;
sink.write(value);
}
return sink.end() as Uint8Array;
})();
}) satisfies typeof Bun.readableStreamToBytes;

export const readableStreamToText = (async (stream: ReadableStream<ArrayBufferView | ArrayBuffer>) => {
let result = '';
const reader = stream.pipeThrough(new TextDecoderStream()).getReader(); ReadableStreamDefaultReader
Expand Down Expand Up @@ -445,16 +460,19 @@ export const readableStreamToJSON = (async <T = unknown>(stream: ReadableStream<
}
}) satisfies typeof Bun.readableStreamToJSON;

export const concatArrayBuffers = ((buffers) => {
export const concatArrayBuffers = ((buffers, maxLength = Infinity, asUint8Array = false) => {
let size = 0;
for (const chunk of buffers) size += chunk.byteLength;
size = Math.min(size, maxLength);
const buffer = new ArrayBuffer(size);
const view = new Uint8Array(buffer);
let offset = 0;
for (const chunk of buffers) {
if (offset > size) break;
view.set(new Uint8Array(chunk instanceof ArrayBuffer || chunk instanceof SharedArrayBuffer ? chunk : chunk.buffer), offset);
offset += chunk.byteLength;
}
if (asUint8Array) return view;
return buffer;
}) satisfies typeof Bun.concatArrayBuffers;

Expand Down
40 changes: 39 additions & 1 deletion packages/bun-types/bun.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,19 @@ declare module "bun" {
*/
arrayBuffer(): ArrayBuffer;

/**
* Read from stdout as an Uint8Array
*
* @returns Stdout as an Uint8Array
* @example
*
* ```ts
* const output = await $`echo hello`;
* console.log(output.bytes()); // Uint8Array { byteLength: 6 }
* ```
*/
bytes(): Uint8Array;

/**
* Read from stdout as a Blob
*
Expand Down Expand Up @@ -688,7 +701,17 @@ declare module "bun" {
* This function is faster because it uses uninitialized memory when copying. Since the entire
* length of the buffer is known, it is safe to use uninitialized memory.
*/
function concatArrayBuffers(buffers: Array<ArrayBufferView | ArrayBufferLike>): ArrayBuffer;
function concatArrayBuffers(buffers: Array<ArrayBufferView | ArrayBufferLike>, maxLength?: number): ArrayBuffer;
function concatArrayBuffers(
buffers: Array<ArrayBufferView | ArrayBufferLike>,
maxLength: number,
asUint8Array: false,
): ArrayBuffer;
function concatArrayBuffers(
buffers: Array<ArrayBufferView | ArrayBufferLike>,
maxLength: number,
asUint8Array: true,
): Uint8Array;

/**
* Consume all data from a {@link ReadableStream} until it closes or errors.
Expand All @@ -705,6 +728,21 @@ declare module "bun" {
stream: ReadableStream<ArrayBufferView | ArrayBufferLike>,
): Promise<ArrayBuffer> | ArrayBuffer;

/**
* Consume all data from a {@link ReadableStream} until it closes or errors.
*
* Concatenate the chunks into a single {@link ArrayBuffer}.
*
* Each chunk must be a TypedArray or an ArrayBuffer. If you need to support
* chunks of different types, consider {@link readableStreamToBlob}
*
* @param stream The stream to consume.
* @returns A promise that resolves with the concatenated chunks or the concatenated chunks as a {@link Uint8Array}.
*/
function readableStreamToBytes(
stream: ReadableStream<ArrayBufferView | ArrayBufferLike>,
): Promise<Uint8Array> | Uint8Array;

/**
* Consume all data from a {@link ReadableStream} until it closes or errors.
*
Expand Down
1 change: 1 addition & 0 deletions packages/bun-types/test/globals.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { expectAssignable, expectType } from "./utilities.test";
// FileBlob
expectType<ReadableStream<Uint8Array>>(Bun.file("index.test-d.ts").stream());
expectType<Promise<ArrayBuffer>>(Bun.file("index.test-d.ts").arrayBuffer());
expectType<Promise<Uint8Array>>(Bun.file("index.test-d.ts").bytes());
expectType<Promise<string>>(Bun.file("index.test-d.ts").text());

expectType<number>(Bun.file("index.test-d.ts").size);
Expand Down
6 changes: 3 additions & 3 deletions src/bun.js/api/BunObject.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2418,7 +2418,7 @@ pub const Crypto = struct {
return output_buf.value;
} else {
// Clone to GC-managed memory
return JSC.ArrayBuffer.create(globalThis, output_digest_slice[0..len], .Buffer);
return JSC.ArrayBuffer.createBuffer(globalThis, output_digest_slice[0..len]);
}
}

Expand Down Expand Up @@ -2590,7 +2590,7 @@ pub const Crypto = struct {
return output_buf.value;
} else {
// Clone to GC-managed memory
return JSC.ArrayBuffer.create(globalThis, result, .Buffer);
return JSC.ArrayBuffer.createBuffer(globalThis, result);
}
}

Expand Down Expand Up @@ -2695,7 +2695,7 @@ pub const Crypto = struct {
var out: [Algorithm.digest_length]u8 = undefined;
h.final(&out);
// Clone to GC-managed memory
return JSC.ArrayBuffer.create(globalThis, &out, .Buffer);
return JSC.ArrayBuffer.createBuffer(globalThis, &out);
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/bun.js/api/brotli.zig
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ pub const BrotliEncoder = struct {
defer this.output_lock.unlock();

defer this.output.clearRetainingCapacity();
return JSC.ArrayBuffer.create(this.globalThis, this.output.items, .Buffer);
return JSC.ArrayBuffer.createBuffer(this.globalThis, this.output.items);
}

pub fn runFromJSThread(this: *BrotliEncoder) void {
Expand Down Expand Up @@ -383,7 +383,7 @@ pub const BrotliDecoder = struct {
defer this.output_lock.unlock();

defer this.output.clearRetainingCapacity();
return JSC.ArrayBuffer.create(this.globalThis, this.output.items, .Buffer);
return JSC.ArrayBuffer.createBuffer(this.globalThis, this.output.items);
}

pub fn runFromJSThread(this: *BrotliDecoder) void {
Expand Down
3 changes: 1 addition & 2 deletions src/bun.js/api/server.zig
Original file line number Diff line number Diff line change
Expand Up @@ -4064,10 +4064,9 @@ pub const ServerWebSocket = struct {

fn binaryToJS(this: *const ServerWebSocket, globalThis: *JSC.JSGlobalObject, data: []const u8) JSC.JSValue {
return switch (this.flags.binary_type) {
.Buffer => JSC.ArrayBuffer.create(
.Buffer => JSC.ArrayBuffer.createBuffer(
globalThis,
data,
.Buffer,
),
.Uint8Array => JSC.ArrayBuffer.create(
globalThis,
Expand Down
5 changes: 2 additions & 3 deletions src/bun.js/base.zig
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ pub const ArrayBuffer = extern struct {
return buffer_value;
}

extern fn ArrayBuffer__fromSharedMemfd(fd: i64, globalObject: *JSC.JSGlobalObject, byte_offset: usize, byte_length: usize, total_size: usize) JSC.JSValue;
extern fn ArrayBuffer__fromSharedMemfd(fd: i64, globalObject: *JSC.JSGlobalObject, byte_offset: usize, byte_length: usize, total_size: usize, JSC.JSValue.JSType) JSC.JSValue;
pub const toArrayBufferFromSharedMemfd = ArrayBuffer__fromSharedMemfd;

pub fn toJSBufferFromMemfd(fd: bun.FileDescriptor, globalObject: *JSC.JSGlobalObject) JSC.JSValue {
Expand Down Expand Up @@ -384,11 +384,10 @@ pub const ArrayBuffer = extern struct {
return Stream{ .pos = 0, .buf = this.slice() };
}

pub fn create(globalThis: *JSC.JSGlobalObject, bytes: []const u8, comptime kind: BinaryType) JSValue {
pub fn create(globalThis: *JSC.JSGlobalObject, bytes: []const u8, comptime kind: JSValue.JSType) JSValue {
JSC.markBinding(@src());
return switch (comptime kind) {
.Uint8Array => Bun__createUint8ArrayForCopy(globalThis, bytes.ptr, bytes.len, false),
.Buffer => Bun__createUint8ArrayForCopy(globalThis, bytes.ptr, bytes.len, true),
.ArrayBuffer => Bun__createArrayBufferForCopy(globalThis, bytes.ptr, bytes.len),
else => @compileError("Not implemented yet"),
};
Expand Down
Loading

0 comments on commit 5caca9c

Please sign in to comment.