Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion docs/runtime/archive.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ File contents can be:

- **Strings** - Text content
- **Blobs** - Binary data
- **`Bun.file()`** - The bytes of a file on disk
- **ArrayBufferViews** (such as `Uint8Array`) - Raw bytes
- **ArrayBuffers** - Raw binary data

Expand All @@ -68,11 +69,18 @@ const arrayBuffer = new ArrayBuffer(8);
const archive = new Bun.Archive({
"text.txt": "Plain text",
"blob.bin": new Blob([data]),
"file.bin": Bun.file("./file.bin"),
"bytes.bin": new Uint8Array([1, 2, 3, 4]),
"buffer.bin": arrayBuffer,
});
```

Bun copies strings, in-memory Blobs, and buffers into the archive when `new Bun.Archive()` runs. Bun does not read a `Bun.file()` at that time. Bun reads the file off the main thread when you first use the archive, for example with `bytes()` or `Bun.write()`. The archive keeps those bytes. If Bun cannot read the file, that call rejects, and the next call reads the file again.

Bun archives the bytes of the file only. Each entry is a regular file with mode `0644`, whatever the mode of the file on disk. The path has to be a regular file: a directory or a device rejects the call.

`new Bun.Archive()` throws a `TypeError` for a `Bun.file()` of a file descriptor, for `Bun.stdin`, and for an S3 file. Pass `await file.bytes()` for them.

### Writing Archives to Disk

Use `Bun.write()` to write an archive to disk:
Expand Down Expand Up @@ -121,6 +129,13 @@ const tarball = await Bun.file("package.tar.gz").bytes();
const archiveFromFile = new Bun.Archive(tarball);
```

```ts
// From a Bun.file(): Bun reads it off the main thread when you first use the archive
const archiveFromLazyFile = new Bun.Archive(Bun.file("package.tar.gz"));
```

An archive made from a `Bun.file()` reads the file once and keeps those bytes, so `files()` and `extract()` see the same archive. If Bun cannot read the file, the call rejects.

```ts
// From a fetch response
const response = await fetch("https://example.com/archive.tar.gz");
Expand Down Expand Up @@ -230,6 +245,8 @@ try {
Common error scenarios:

- **Corrupted/truncated archives** - `new Bun.Archive()` loads the archive data; Bun may defer errors until read/extract operations
- **Unreadable files** - Bun reads a `Bun.file()` when you first use the archive; a missing file, a directory, or a file you cannot read rejects that call with the system error
- **Files without a path** - `new Bun.Archive()` throws a `TypeError` for a `Bun.file()` of a file descriptor and for an S3 file
- **Permission denied** - `extract()` throws if the target directory is not writable
- **Disk full** - `extract()` throws if there's insufficient space
- **Invalid paths** - Operations throw for malformed file paths
Expand Down Expand Up @@ -378,7 +395,7 @@ async function archiveDirectory(dir: string, compress = false): Promise<Bun.Arch

if (entry.isDirectory()) {
await walk(fullPath, archivePath);
} else {
} else if (entry.isFile()) {
files[archivePath] = Bun.file(fullPath);
}
}
Expand All @@ -392,6 +409,8 @@ const archive = await archiveDirectory("./my-project", true);
await Bun.write("my-project.tar.gz", archive);
```

The walk adds regular files only. It skips symbolic links and other entries that are not regular files.

## Reference

> **Note**: The following type signatures are simplified. See [`packages/bun-types/bun.d.ts`](https://github.com/oven-sh/bun/blob/main/packages/bun-types/bun.d.ts) for the full type definitions.
Expand Down
13 changes: 11 additions & 2 deletions packages/bun-types/bun.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10213,6 +10213,15 @@ declare module "bun" {
* - An object mapping paths to file contents (string, Blob, TypedArray, or ArrayBuffer)
* - A Blob containing existing archive data
* - A TypedArray or ArrayBuffer containing existing archive data
*
* A Blob can be a `Bun.file()` with a path. Bun reads the file off the main
* thread when the archive is first used, not when it is created, and the
* archive keeps those bytes. If the file cannot be read, the method that
* uses the archive rejects. As file contents, only the bytes are archived:
* the entry is a regular file with mode `0644`.
*
* A `Bun.file()` of a file descriptor, `Bun.stdin`, or an S3 file as file
* contents throws a `TypeError`: pass `await file.bytes()`.
*/
type ArchiveInput = Record<string, BlobPart> | Blob | ArrayBufferView | ArrayBufferLike;

Expand Down Expand Up @@ -10391,8 +10400,8 @@ declare module "bun" {
/**
* Create an archive and write it to disk in one operation.
*
* The data streams directly to disk, which is more efficient than creating an
* archive and then writing it separately.
* Bun builds the archive in memory and then writes it. It reads `Bun.file()`
* contents, compresses, and writes off the main thread.
*
* @param path - The file path to write the archive to
* @param data - The input data for the archive (same as `new Archive()`)
Expand Down
12 changes: 12 additions & 0 deletions src/libarchive/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ pub mod lib {
fn archive_write_new() -> *mut Archive;
fn archive_write_free(a: *mut Archive) -> Result;
fn archive_write_close(a: *mut Archive) -> Result;
fn archive_write_fail(a: *mut Archive) -> c_int;
fn archive_filter_bytes(a: *mut Archive, n: c_int) -> la_int64_t;
fn archive_write_set_bytes_in_last_block(a: *mut Archive, bytes: c_int) -> Result;
fn archive_set_error(a: *mut Archive, err: c_int, fmt: *const c_char, ...);
fn archive_write_set_format_pax_restricted(a: *mut Archive) -> Result;
Expand Down Expand Up @@ -414,6 +416,16 @@ pub mod lib {
// SAFETY: self valid.
unsafe { archive_write_close(self.as_mut_ptr()) }
}
/// Abandon the archive: freeing it then skips the close, which zero-pads an open entry.
pub fn write_fail(&self) {
// SAFETY: self valid.
unsafe { archive_write_fail(self.as_mut_ptr()) };
}
/// The bytes given to filter `n` so far. Filter 0 of a writer takes the format's output.
pub fn filter_bytes(&self, n: c_int) -> i64 {
// SAFETY: self valid.
unsafe { archive_filter_bytes(self.as_mut_ptr(), n) }
}
/// Sets the padding unit for the last output block. The default pads it to 10 KiB, and 1
/// disables padding.
pub fn write_set_bytes_in_last_block(&self, bytes: c_int) -> Result {
Expand Down
Loading
Loading