# Breaking changes and migration guide

This is the cumulative record of breaking changes, newest first. When skipping
multiple versions, apply every section newer than the installed version in
chronological order.

## 0.9.0

These changes do not include compatibility aliases.

### One runtime-aware package root

Import high-level operations from the package root in every runtime:

```ts
import {
  createArchiveWriter,
  openArchive,
} from "@ismail-elkorchi/bytefold";
```

The following entrypoints were removed:

| Removed | Replacement |
| --- | --- |
| `@ismail-elkorchi/bytefold/archive` | `@ismail-elkorchi/bytefold` |
| `@ismail-elkorchi/bytefold/node` | `@ismail-elkorchi/bytefold` |
| `@ismail-elkorchi/bytefold/deno` | `@ismail-elkorchi/bytefold` |
| `@ismail-elkorchi/bytefold/bun` | `@ismail-elkorchi/bytefold` |
| `@ismail-elkorchi/bytefold/web` | `@ismail-elkorchi/bytefold` |

The package resolver selects the runtime implementation. `openArchive()` now
accepts the same input union everywhere: bytes, `ArrayBuffer`, `Blob`/`File`,
Web readable streams, strings, and URLs. Strings and `file:` URLs address local
files in Node, Deno, and Bun. Browsers accept HTTPS URLs and browser-native
inputs.

Runtime-only helpers were removed:

- Use `openArchive(path)` instead of `zipFromFile()` or `tarFromFile()`.
- Use `createArchiveWriter()` with a Web `WritableStream` instead of
  `zipToFile()` or `tarToFile()`.
- Read the entry returned by `openArchive()` for single-file XZ streams instead
  of the Node facade's XZ-only `extractAll()`.
- On Node 24+, use `Readable.toWeb()`, `Readable.fromWeb()`,
  `Writable.toWeb()`, and `Writable.fromWeb()` instead of bytefold stream
  adapters.

The advanced `@ismail-elkorchi/bytefold/node/zip` entrypoint remains available
for ZIP filesystem extraction.

### Safety profiles

Use the shared `SafetyProfile` values:

| Before | Now |
| --- | --- |
| `profile` | `safetyProfile` |
| `"compat"` | `"compatible"` |
| `"agent"` | `"untrusted"` |
| `isStrict` | Choose `"strict"` or `"untrusted"` |

Choose the profile when opening or constructing a reader. `audit()`,
`assertSafe()`, entry-open, and extraction calls no longer accept a second
strictness override.

```ts
const reader = await openArchive(input, {
  safetyProfile: "untrusted",
  limits: { maxTotalUncompressedBytes: 512 * 1024 * 1024 },
});
```

### Reports and errors

- Audit reports use `isSafe` instead of `ok`.
- Normalization reports use `isSuccessful` instead of `ok`.
- Summary fields use explicit names such as `entryCount`, `warningCount`,
  `errorCount`, and `outputEntryCount`.
- Compression capabilities use `canCompress`, `canDecompress`, and
  `limitations`.
- Reports are plain JSON-safe objects and do not have report-specific
  `toJSON()` methods.
- Reports and serialized errors no longer contain `schemaVersion`.
- Error JSON uses `message`; the duplicate `hint` field was removed.

The separately versioned JSON Schema files and
`BYTEFOLD_REPORT_SCHEMA_VERSION` were removed. TypeScript exports are the
library contract. Applications that need runtime validation should validate
their own boundary.

### Imports

The package root exposes high-level archive and compression operations. Import
advanced format APIs from their subpaths:

```ts
import { openArchive } from "@ismail-elkorchi/bytefold";
import { ZipError, ZipReader } from "@ismail-elkorchi/bytefold/zip";
import { TarWriter } from "@ismail-elkorchi/bytefold/tar";
```

Node-specific ZIP classes use `@ismail-elkorchi/bytefold/node/zip`.

The `@ismail-elkorchi/bytefold/support` entrypoint was removed. Use
`getCompressionCapabilities()` to inspect the current runtime and consult
[`SPEC.md`](SPEC.md) for format coverage.

### Reader lifecycle and capabilities

Every unified reader has an asynchronous `close()` method:

```ts
const reader = await openArchive(input);
try {
  // Read, audit, or normalize.
} finally {
  await reader.close();
}
```

`normalizeToWritable` is optional and now indicates a real capability. Check it
before use. Single-file gzip, bzip2, XZ, Brotli, and Zstandard readers do not
advertise normalization.

`ArchiveEntry.raw` and test-only resource-default inspection exports were
removed.

### Archive writers

`createArchiveWriter()` accepts archive/container formats only:

- `zip`
- `tar`
- `tgz` and `tar.gz`
- `tar.zst`
- `tar.br`

Use `createCompressor()` for raw gzip, Brotli, or Zstandard output. Bzip2 and XZ
remain read-only.

Every entry requires a source. Pass an empty byte array for metadata-only
entries:

```ts
await writer.add("folder/", new Uint8Array(), { type: "directory" });
```

Writer options are flat and selected from the literal format:

```ts
createArchiveWriter("zip", output, { defaultMethod: 8 });
createArchiveWriter("tar", output, { isDeterministic: true });
createArchiveWriter("tar.gz", output, {
  isDeterministic: true,
  compression: { level: 6 },
});
```

Encryption, progress callbacks, and other advanced ZIP controls remain on the
`/zip` API.

Writers now expose `abort(reason?)`. Use it after a source or entry failure so
the output is released without central-directory, footer, or end-of-archive
records:

```ts
const writer = createArchiveWriter("tar.gz", output);
try {
  await writer.add("artifact.bin", source);
  await writer.close();
} catch (error) {
  await writer.abort(error);
  throw error;
}
```

After `abort()` or `close()` succeeds, later terminal calls are no-ops and later
`add()` calls reject.

### Compression

- Replace `CompressionOptions` with `CompressorOptions` or
  `DecompressorOptions`.
- `createCompressor()` and `createDecompressor()` return
  `ReadableWritablePair<Uint8Array, Uint8Array>`.
- Direct decompression uses `maxOutputBytes` or
  `limits.maxTotalDecompressedBytes`, not the archive-level
  `maxTotalUncompressedBytes` option.
- All decompression ceilings throw `COMPRESSION_RESOURCE_LIMIT`.
- Invalid transform options throw `COMPRESSION_INVALID_OPTIONS`.
- Corrupt native-backend input throws `COMPRESSION_BAD_DATA`.
- `CompressionMethod` accepts registered numeric method IDs;
  `BuiltInCompressionMethod` is the built-in `0 | 8 | 9 | 93` union.

Decompressor `limits` accepts only fields relevant to decompression. Direct
scalar options take precedence over the corresponding value inside `limits`.

### Before upgrading

Search the application for:

```text
profile
isStrict
.ok
schemaVersion
.hint
ArchiveEntry.raw
CompressionOptions
createArchiveWriter("gz"
createArchiveWriter("br"
createArchiveWriter("zst"
bytefold/support
bytefold/archive
bytefold/node
bytefold/deno
bytefold/bun
bytefold/web
zipFromFile
tarFromFile
zipToFile
tarToFile
```

Then run the application's type checker and exercise archive rejection paths,
not only successful round trips.

## 0.4.0

Error JSON `context` stopped duplicating top-level fields. Read these values from
the error itself rather than `error.context`:

- `name`
- `code`
- `message`
- `entryName`
- `method`
- `offset`
- `algorithm`

At the time, `schemaVersion` and `hint` also moved out of `context`; the
unreleased changes above remove those two fields entirely.

## 0.3.0

Public option names changed:

| Before 0.3.0 | 0.3.0 replacement |
| --- | --- |
| `strict` | `isStrict` |
| `storeEntries` | `shouldStoreEntries` |
| `deterministic` | `isDeterministic` |
| `forceZip64` | `shouldForceZip64` |
| `allowSymlinks` | `shouldAllowSymlinks` |
| `preserveComments` | `shouldPreserveComments` |
| `preserveTrailingBytes` | `shouldPreserveTrailingBytes` |
| `http.snapshot` | `http.snapshotPolicy` |
| `seekable` | `sinkSeekabilityPolicy` |

The unreleased changes above subsequently replace `isStrict` with the shared
`safetyProfile` option.
