feat(archive): change API to constructor-based with S3 support (#25940)

## Summary
- Change Archive API from `Bun.Archive.from(data)` to `new
Bun.Archive(data, options?)`
- Change compression options from `{ gzip: true }` to `{ compress:
"gzip", level?: number }`
- Default to no compression when no options provided
- Use `{ compress: "gzip" }` to enable gzip compression (level 6 by
default)
- Add Archive support for S3 and local file writes via `Bun.write()`

## New API

```typescript
// Create archive - defaults to uncompressed tar
const archive = new Bun.Archive({
  "hello.txt": "Hello, World!",
  "data.json": JSON.stringify({ foo: "bar" }),
});

// Enable gzip compression
const compressed = new Bun.Archive(files, { compress: "gzip" });

// Gzip with custom level (1-12)
const maxCompression = new Bun.Archive(files, { compress: "gzip", level: 12 });

// Write to local file
await Bun.write("archive.tar", archive);           // uncompressed by default
await Bun.write("archive.tar.gz", compressed);     // gzipped

// Write to S3
await client.write("archive.tar.gz", compressed);          // S3Client.write()
await Bun.write("s3://bucket/archive.tar.gz", compressed); // S3 URL
await s3File.write(compressed);                            // s3File.write()

// Get bytes/blob (uses compression setting from constructor)
const bytes = await archive.bytes();
const blob = await archive.blob();
```

## TypeScript Types

```typescript
type ArchiveCompression = "gzip";

type ArchiveOptions = {
  compress?: "gzip";
  level?: number;  // 1-12, default 6 when gzip enabled
};
```

## Test plan
- [x] 98 archive tests pass
- [x] S3 integration tests updated to new API
- [x] TypeScript types updated
- [x] Documentation updated with new examples

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
This commit is contained in:
Dylan Conway
2026-01-12 14:54:21 -08:00
committed by GitHub
parent 461ad886bd
commit 959169dfaf
8 changed files with 779 additions and 336 deletions

View File

@@ -750,7 +750,7 @@ declare module "bun" {
*/
function write(
destination: BunFile | S3File | PathLike,
input: Blob | NodeJS.TypedArray | ArrayBufferLike | string | BlobPart[],
input: Blob | NodeJS.TypedArray | ArrayBufferLike | string | BlobPart[] | Archive,
options?: {
/**
* If writing to a PathLike, set the permissions of the file.
@@ -6975,15 +6975,44 @@ declare module "bun" {
/**
* Compression format for archive output.
* - `"gzip"` - Compress with gzip
* - `true` - Same as `"gzip"`
* - `false` - Explicitly disable compression (no compression)
* - `undefined` - No compression (default behavior when omitted)
*
* Both `false` and `undefined` result in no compression; `false` can be used
* to explicitly indicate "no compression" in code where the intent should be clear.
* Currently only `"gzip"` is supported.
*/
type ArchiveCompression = "gzip" | boolean;
type ArchiveCompression = "gzip";
/**
* Options for creating an Archive instance.
*
* By default, archives are not compressed. Use `{ compress: "gzip" }` to enable compression.
*
* @example
* ```ts
* // No compression (default)
* new Bun.Archive(data);
*
* // Enable gzip with default level (6)
* new Bun.Archive(data, { compress: "gzip" });
*
* // Specify compression level
* new Bun.Archive(data, { compress: "gzip", level: 9 });
* ```
*/
interface ArchiveOptions {
/**
* Compression algorithm to use.
* Currently only "gzip" is supported.
* If not specified, no compression is applied.
*/
compress?: ArchiveCompression;
/**
* Compression level (1-12). Only applies when `compress` is set.
* - 1: Fastest compression, lowest ratio
* - 6: Default balance of speed and ratio
* - 12: Best compression ratio, slowest
*
* @default 6
*/
level?: number;
}
/**
* Options for extracting archive contents.
@@ -7031,7 +7060,7 @@ declare module "bun" {
* @example
* **Create an archive from an object:**
* ```ts
* const archive = Bun.Archive.from({
* const archive = new Bun.Archive({
* "hello.txt": "Hello, World!",
* "data.json": JSON.stringify({ foo: "bar" }),
* "binary.bin": new Uint8Array([1, 2, 3, 4]),
@@ -7039,9 +7068,20 @@ declare module "bun" {
* ```
*
* @example
* **Create a gzipped archive:**
* ```ts
* const archive = new Bun.Archive({
* "hello.txt": "Hello, World!",
* }, { compress: "gzip" });
*
* // Or with a specific compression level (1-12)
* const archive = new Bun.Archive(data, { compress: "gzip", level: 9 });
* ```
*
* @example
* **Extract an archive to disk:**
* ```ts
* const archive = Bun.Archive.from(tarballBytes);
* const archive = new Bun.Archive(tarballBytes);
* const entryCount = await archive.extract("./output");
* console.log(`Extracted ${entryCount} entries`);
* ```
@@ -7049,7 +7089,7 @@ declare module "bun" {
* @example
* **Get archive contents as a Map of File objects:**
* ```ts
* const archive = Bun.Archive.from(tarballBytes);
* const archive = new Bun.Archive(tarballBytes);
* const entries = await archive.files();
* for (const [path, file] of entries) {
* console.log(path, await file.text());
@@ -7062,36 +7102,50 @@ declare module "bun" {
* await Bun.Archive.write("bundle.tar.gz", {
* "src/index.ts": sourceCode,
* "package.json": packageJson,
* }, "gzip");
* }, { compress: "gzip" });
* ```
*/
export class Archive {
/**
* Create an `Archive` instance from input data.
*
* By default, archives are not compressed. Use `{ compress: "gzip" }` to enable compression.
*
* @param data - The input data for the archive:
* - **Object**: Creates a new tarball with the object's keys as file paths and values as file contents
* - **Blob/TypedArray/ArrayBuffer**: Wraps existing archive data (tar or tar.gz)
*
* @returns A new `Archive` instance
* @param options - Optional archive options including compression settings.
* Defaults to no compression if omitted.
*
* @example
* **From an object (creates new tarball):**
* **From an object (creates uncompressed tarball):**
* ```ts
* const archive = Bun.Archive.from({
* const archive = new Bun.Archive({
* "hello.txt": "Hello, World!",
* "nested/file.txt": "Nested content",
* });
* ```
*
* @example
* **With gzip compression:**
* ```ts
* const archive = new Bun.Archive(data, { compress: "gzip" });
* ```
*
* @example
* **With explicit gzip compression level:**
* ```ts
* const archive = new Bun.Archive(data, { compress: "gzip", level: 12 });
* ```
*
* @example
* **From existing archive data:**
* ```ts
* const response = await fetch("https://example.com/package.tar.gz");
* const archive = Bun.Archive.from(await response.blob());
* const archive = new Bun.Archive(await response.blob());
* ```
*/
static from(data: ArchiveInput): Archive;
constructor(data: ArchiveInput, options?: ArchiveOptions);
/**
* Create and write an archive directly to disk in one operation.
@@ -7100,8 +7154,8 @@ declare module "bun" {
* as it streams the data directly to disk.
*
* @param path - The file path to write the archive to
* @param data - The input data for the archive (same as `Archive.from()`)
* @param compress - Optional compression: `"gzip"`, `true` for gzip, or `false`/`undefined` for none
* @param data - The input data for the archive (same as `new Archive()`)
* @param options - Optional archive options including compression settings
*
* @returns A promise that resolves when the write is complete
*
@@ -7117,10 +7171,10 @@ declare module "bun" {
* @example
* **Write gzipped tarball:**
* ```ts
* await Bun.Archive.write("output.tar.gz", files, "gzip");
* await Bun.Archive.write("output.tar.gz", files, { compress: "gzip" });
* ```
*/
static write(path: string, data: ArchiveInput | Archive, compress?: ArchiveCompression): Promise<void>;
static write(path: string, data: ArchiveInput | Archive, options?: ArchiveOptions): Promise<void>;
/**
* Extract the archive contents to a directory on disk.
@@ -7136,7 +7190,7 @@ declare module "bun" {
* @example
* **Extract all entries:**
* ```ts
* const archive = Bun.Archive.from(tarballBytes);
* const archive = new Bun.Archive(tarballBytes);
* const count = await archive.extract("./extracted");
* console.log(`Extracted ${count} entries`);
* ```
@@ -7166,42 +7220,48 @@ declare module "bun" {
/**
* Get the archive contents as a `Blob`.
*
* @param compress - Optional compression: `"gzip"`, `true` for gzip, or `false`/`undefined` for none
* Uses the compression settings specified when the Archive was created.
*
* @returns A promise that resolves with the archive data as a Blob
*
* @example
* **Get uncompressed tarball:**
* **Get tarball as Blob:**
* ```ts
* const archive = new Bun.Archive(data);
* const blob = await archive.blob();
* ```
*
* @example
* **Get gzipped tarball:**
* **Get gzipped tarball as Blob:**
* ```ts
* const gzippedBlob = await archive.blob("gzip");
* const archive = new Bun.Archive(data, { compress: "gzip" });
* const gzippedBlob = await archive.blob();
* ```
*/
blob(compress?: ArchiveCompression): Promise<Blob>;
blob(): Promise<Blob>;
/**
* Get the archive contents as a `Uint8Array`.
*
* @param compress - Optional compression: `"gzip"`, `true` for gzip, or `false`/`undefined` for none
* Uses the compression settings specified when the Archive was created.
*
* @returns A promise that resolves with the archive data as a Uint8Array
*
* @example
* **Get uncompressed tarball bytes:**
* **Get tarball bytes:**
* ```ts
* const archive = new Bun.Archive(data);
* const bytes = await archive.bytes();
* ```
*
* @example
* **Get gzipped tarball bytes:**
* ```ts
* const gzippedBytes = await archive.bytes("gzip");
* const archive = new Bun.Archive(data, { compress: "gzip" });
* const gzippedBytes = await archive.bytes();
* ```
*/
bytes(compress?: ArchiveCompression): Promise<Uint8Array<ArrayBuffer>>;
bytes(): Promise<Uint8Array<ArrayBuffer>>;
/**
* Get the archive contents as a `Map` of `File` objects.

View File

@@ -609,7 +609,17 @@ declare module "bun" {
* });
*/
write(
data: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer | Request | Response | BunFile | S3File | Blob,
data:
| string
| ArrayBufferView
| ArrayBuffer
| SharedArrayBuffer
| Request
| Response
| BunFile
| S3File
| Blob
| Archive,
options?: S3Options,
): Promise<number>;
@@ -920,7 +930,8 @@ declare module "bun" {
| BunFile
| S3File
| Blob
| File,
| File
| Archive,
options?: S3Options,
): Promise<number>;
@@ -970,7 +981,8 @@ declare module "bun" {
| BunFile
| S3File
| Blob
| File,
| File
| Archive,
options?: S3Options,
): Promise<number>;