Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add .bytes() method to various readables #11104

Merged
merged 15 commits into from
May 17, 2024
Merged
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 ArrayBuffer;
nektro marked this conversation as resolved.
Show resolved Hide resolved
})();
}) 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