Skip to content
Open
84 changes: 84 additions & 0 deletions handwritten/storage/MIGRATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Storage v7 to v8 Migration Guide - Node.js 18 & Gaxios Update

This guide helps you migrate your application from `@google-cloud/storage` v7 to v8, focusing on the transition to Node.js 18+ as the minimum supported environment and changes introduced by updating the underlying HTTP client, `gaxios`, to version 7.

## Minimum Requirements: Node.js 18

`@google-cloud/storage` v8 now officially requires Node.js 18 or higher. This update aligns the library with modern JavaScript environments.

Specifically, Node.js 18 introduced native global Web APIs (such as `fetch` and `Headers`). Conforming to this standard, the underlying HTTP client, `gaxios` (updated to v7), leverages native `Headers` rather than custom user-land header representations. As a result, `@google-cloud/storage` v8 has transitioned response and request headers to standard native global `Headers` objects.


## Key Breaking Changes for Storage Users

### 1. Response Headers are now `Headers` objects

When you receive a full API response from Storage methods (e.g., via callbacks or promise resolutions that include the response object), the `headers` property of the response is now a standard native `Headers` object (aligned with the Fetch API standard in Node.js 18) rather than a plain JavaScript object.

**Before (Storage v7):**

```js
const [retrievedFile, apiResponse] = await file.get();
const contentType = apiResponse.headers['content-type'];
```

**After (Storage v8):**

```js
const [retrievedFile, apiResponse] = await file.get();
// Accessing headers requires the .get() method
const contentType = apiResponse.headers.get('content-type');
```

### 2. Passing Headers in Options

If you pass custom headers in options to Storage methods (which extend `GaxiosOptions`), you can still pass plain objects, as the Storage library will convert them to standard `Headers` internally for the request. However, if you read them back from the prepared options or response, they will be `Headers` objects.

**Before (Storage v7):**

```js
// Reading request headers back from response metadata returned a plain object
const customHeader = apiResponse.config.headers['x-custom-header'];
```

**After (Storage v8):**

```js
// Reading request headers back from response metadata requires .get()
const customHeader = apiResponse.config.headers.get('x-custom-header');
```

> [!WARNING]
> **Header Value Stringification:** Plain JavaScript objects allow passing non-string values (such as arrays or numbers) which are implicitly processed. However, the native `Headers` constructor strictly converts all values to standard string representations. For example, passing an array of values (e.g., `['val1', 'val2']`) will result in a single comma-separated string (e.g., `'val1, val2'`). Ensure you pre-format or verify your header values before passing them to custom options.


### 3. URL Resolution (`baseURL`)

If you are using custom `baseURL` options or passing relative URLs to methods that accept them, be aware that resolution now strictly follows the standard native `URL` constructor spec (`new URL(url, baseURL)`). This can affect how leading slashes in paths resolve.

**Before (Storage v7):**

Using standard path-joining custom resolution:
- `baseURL`: `https://storage.googleapis.com/storage/v1`
- `url`: `/b/my-bucket`
- Resolved URL: `https://storage.googleapis.com/storage/v1/b/my-bucket`

**After (Storage v8):**

Strictly resolved via the standard native `URL` constructor rules (where a leading slash resolves relative to the root of the host):
- `baseURL`: `https://storage.googleapis.com/storage/v1`
- `url`: `/b/my-bucket`
- Resolved URL: `https://storage.googleapis.com/b/my-bucket` (resolves relative to host root, stripping `storage/v1`)


## Upgrade Instructions

Update your `@google-cloud/storage` dependency to version 8:

```sh
npm install @google-cloud/storage@latest
```

## Troubleshooting

- If you encounter `undefined` when accessing headers on the response object, ensure you are using `apiResponse.headers.get('header-name')`.
Loading