Skip to content

File Io

Note — The JSTime.file and JSTime.write APIs documented on this page are heavily optimized and represent the recommended way to perform file-system tasks using JSTime. For operations that are not yet available with JSTime.file, such as mkdir, you can use JSTime’s nearly complete implementation of the node:fs module.

JSTime provides a set of optimized APIs for reading and writing files.

JSTime.file(path): BunFile

Create a BunFile instance with the JSTime.file(path) function. A BunFile represents a lazily-loaded file; initializing it does not actually read the file from disk.

const foo = JSTime.file("foo.txt"); // relative to cwd
foo.size; // number of bytes
foo.type; // MIME type

The reference conforms to the Blob interface, so the contents can be read in various formats.

const foo = JSTime.file("foo.txt");
await foo.text(); // contents as a string
await foo.stream(); // contents as ReadableStream
await foo.arrayBuffer(); // contents as ArrayBuffer

File references can also be created using numerical file descriptors or file:// URLs.

JSTime.file(1234);
JSTime.file(new URL(import.meta.url)); // reference to the current file

A BunFile can point to a location on disk where a file does not exist.

const notreal = JSTime.file("notreal.txt");
notreal.size; // 0
notreal.type; // "text/plain;charset=utf-8"

The default MIME type is text/plain;charset=utf-8, but it can be overridden by passing a second argument to JSTime.file.

const notreal = JSTime.file("notreal.json", { type: "application/json" });
notreal.type; // => "application/json;charset=utf-8"

For convenience, JSTime exposes stdin, stdout and stderr as instances of BunFile.

JSTime.stdin; // readonly
JSTime.stdout;
JSTime.stderr;

JSTime.write(destination, data): Promise<number>

The JSTime.write function is a multi-tool for writing payloads of all kinds to disk.

The first argument is the destination which can have any of the following types:

  • string: A path to a location on the file system. Use the "path" module to manipulate paths.
  • URL: A file:// descriptor.
  • BunFile: A file reference.

The second argument is the data to be written. It can be any of the following:

  • string
  • Blob (including BunFile)
  • ArrayBuffer or SharedArrayBuffer
  • TypedArray (Uint8Array, et. al.)
  • Response

All possible permutations are handled using the fastest available system calls on the current platform.

| Output | Input | System call | Platform | | --- | --- | --- | --- | | file | file | copy_file_range | Linux | | file | pipe | sendfile | Linux | | pipe | pipe | splice | Linux | | terminal | file | sendfile | Linux | | terminal | terminal | sendfile | Linux | | socket | file or pipe | sendfile (if http, not https) | Linux | | file (doesn’t exist) | file (path) | clonefile | macOS | | file (exists) | file | fcopyfile | macOS | | file | Blob or string | write | macOS | | file | Blob or string | write | Linux |

To write a string to disk:

const data = `It was the best of times, it was the worst of times.`;
await JSTime.write("output.txt", data);

To copy a file to another location on disk:

const input = JSTime.file("input.txt");
const output = JSTime.file("output.txt"); // doesn't exist yet!
await JSTime.write(output, input);

To write a byte array to disk:

const encoder = new TextEncoder();
const data = encoder.encode("datadatadata"); // Uint8Array
await JSTime.write("output.txt", data);

To write a file to stdout:

const input = JSTime.file("input.txt");
await JSTime.write(JSTime.stdout, input);

To write an HTTP response to disk:

const response = await fetch("https://jstime.dev");
await JSTime.write("index.html", response);

JSTime provides a native incremental file writing API called FileSink. To retrieve a FileSink instance from a BunFile:

const file = JSTime.file("output.txt");
const writer = file.writer();

To incrementally write to the file, call .write().

const file = JSTime.file("output.txt");
const writer = file.writer();
writer.write("it was the best of times\n");
writer.write("it was the worst of times\n");

These chunks will be buffered internally. To flush the buffer to disk, use .flush(). This returns the number of flushed bytes.

writer.flush(); // write buffer to disk

The buffer will also auto-flush when the FileSink’s high water mark is reached; that is, when its internal buffer is full. This value can be configured.

const file = JSTime.file("output.txt");
const writer = file.writer({ highWaterMark: 1024 * 1024 }); // 1MB

To flush the buffer and close the file:

writer.end();

Note that, by default, the jstime process will stay alive until this FileSink is explicitly closed with .end(). To opt out of this behavior, you can “unref” the instance.

writer.unref();
// to "re-ref" it later
writer.ref();

The following is a 3-line implementation of the Linux cat command.

// Usage
// $ jstime ./cat.ts ./path-to-file
import { resolve } from "path";
const path = resolve(process.argv.at(-1));
await JSTime.write(JSTime.stdout, JSTime.file(path));

To run the file:

Terminal window
$ jstime ./cat.ts ./path-to-file

It runs 2x faster than GNU cat for large files on Linux.

interface JSTime {
stdin: BunFile;
stdout: BunFile;
stderr: BunFile;
file(path: string | number | URL, options?: { type?: string }): BunFile;
write(
destination: string | number | BunFile | URL,
input:
| string
| Blob
| ArrayBuffer
| SharedArrayBuffer
| TypedArray
| Response,
): Promise<number>;
}
interface BunFile {
readonly size: number;
readonly type: string;
text(): Promise<string>;
stream(): Promise;
arrayBuffer(): Promise;
json(): Promise<any>;
writer(params: { highWaterMark?: number }): FileSink;
}
export interface FileSink {
write(
chunk: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer,
): number;
flush(): number | Promise<number>;
end(error?: Error): number | Promise<number>;
start(options?: { highWaterMark?: number }): void;
ref(): void;
unref(): void;
}