Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
b2e12e7
fix(storage): standardize URL formatting and enhance transport retry
thiyaguk09 May 7, 2026
a55baa3
fix storage transport & retry issues
thiyaguk09 May 7, 2026
d50bc9e
fix
thiyaguk09 May 7, 2026
618abb6
Update handwritten/storage/src/file.ts
thiyaguk09 May 7, 2026
2a2ef77
fix(storage): interceptors test
thiyaguk09 May 7, 2026
65f9f33
fix(storage): standardize URL formatting and enhance transport retry
thiyaguk09 May 7, 2026
1ae557f
refactor(storage): remove Service.ts and migrate logic to StorageTran…
thiyaguk09 May 14, 2026
f3b81f0
Merge remote-tracking branch 'upstream/storage-node-18' into storage-…
thiyaguk09 May 14, 2026
bd33380
fix(storage): standardize URL formatting and enhance transport retry
thiyaguk09 May 7, 2026
cc411a0
refactor(storage): remove Service.ts and migrate logic to StorageTran…
thiyaguk09 May 14, 2026
d8385a4
Merge remote-tracking branch 'upstream/storage-node-18' into storage-…
thiyaguk09 May 14, 2026
128bf0a
fix(storage): standardize URL formatting and enhance transport retry
thiyaguk09 May 7, 2026
9010041
refactor(storage): remove Service.ts and migrate logic to StorageTran…
thiyaguk09 May 14, 2026
cf7060a
Merge remote-tracking branch 'upstream/storage-node-18' into storage-…
thiyaguk09 May 15, 2026
7b6d68b
fix(storage): standardize URL formatting and enhance transport retry
thiyaguk09 May 7, 2026
0e8f067
refactor(storage): remove Service.ts and migrate logic to StorageTran…
thiyaguk09 May 14, 2026
7fe696a
Merge remote-tracking branch 'upstream/storage-node-18' into storage-…
thiyaguk09 May 18, 2026
e399bea
feat: implement robust storage conformance test retry framework with …
thiyaguk09 May 18, 2026
df71511
fix: correct response handler for binary/resumable uploads and improv…
thiyaguk09 May 19, 2026
a2cd00b
fix(storage): standardize URL formatting and enhance transport retry
thiyaguk09 May 7, 2026
72c17d7
refactor(storage): remove Service.ts and migrate logic to StorageTran…
thiyaguk09 May 14, 2026
e506e77
Merge remote-tracking branch 'upstream/storage-node-18' into storage-…
thiyaguk09 May 19, 2026
8eb2d72
fix(storage): standardize URL formatting and enhance transport retry
thiyaguk09 May 7, 2026
eacb087
refactor(storage): remove Service.ts and migrate logic to StorageTran…
thiyaguk09 May 14, 2026
43a0f8c
Merge remote-tracking branch 'upstream/storage-node-18' into storage-…
thiyaguk09 May 21, 2026
e6c81d8
refactor(storage): replace constructor-based type checks with structu…
thiyaguk09 May 25, 2026
683b3f4
fix(storage): standardize URL formatting and enhance transport retry
thiyaguk09 May 7, 2026
0c58a9a
refactor(storage): remove Service.ts and migrate logic to StorageTran…
thiyaguk09 May 14, 2026
0750ae1
Merge remote-tracking branch 'upstream/storage-node-18' into storage-…
thiyaguk09 May 25, 2026
759e2b2
refactor(storage): update storage-transport to return full GaxiosResp…
thiyaguk09 May 26, 2026
b0299e0
fix: update file request URL construction to support custom protocol …
thiyaguk09 May 26, 2026
b5c81a1
fix(storage): standardize URL formatting and enhance transport retry
thiyaguk09 May 7, 2026
fe44861
refactor(storage): remove Service.ts and migrate logic to StorageTran…
thiyaguk09 May 14, 2026
b0ef6a5
Merge remote-tracking branch 'upstream/storage-node-18' into storage-…
thiyaguk09 May 26, 2026
7c3ee50
fix(storage): standardize URL formatting and enhance transport retry
thiyaguk09 May 7, 2026
0a4f5ac
refactor(storage): remove Service.ts and migrate logic to StorageTran…
thiyaguk09 May 14, 2026
1b5a53b
Merge remote-tracking branch 'upstream/storage-node-18' into storage-…
thiyaguk09 May 27, 2026
51efb5b
refactor(storage): introduce ENCRYPTION_ALGORITHM_AES256 constant to …
thiyaguk09 May 27, 2026
6eba12e
fix(storage): merge request headers correctly in file.ts and add miss…
thiyaguk09 May 27, 2026
ef7c4d4
fix(storage): standardize URL formatting and enhance transport retry
thiyaguk09 May 7, 2026
e3288e3
refactor(storage): remove Service.ts and migrate logic to StorageTran…
thiyaguk09 May 14, 2026
5b27416
Merge remote-tracking branch 'upstream/storage-node-18' into storage-…
thiyaguk09 Jun 1, 2026
cd5a5b9
test: add bytes method to mock Gaxios responses in storage tests
thiyaguk09 Jun 1, 2026
eb547c4
fix(storage): standardize URL formatting and enhance transport retry
thiyaguk09 May 7, 2026
1fba172
refactor(storage): remove Service.ts and migrate logic to StorageTran…
thiyaguk09 May 14, 2026
9c364d2
Merge remote-tracking branch 'upstream/storage-node-18' into storage-…
thiyaguk09 Jun 2, 2026
fafab27
fix(storage): standardize URL formatting and enhance transport retry
thiyaguk09 May 7, 2026
d4b912f
refactor(storage): remove Service.ts and migrate logic to StorageTran…
thiyaguk09 May 14, 2026
bb3fa9c
Merge remote-tracking branch 'upstream/storage-node-18' into storage-…
thiyaguk09 Jun 3, 2026
c5be999
fix(storage): standardize URL formatting and enhance transport retry
thiyaguk09 May 7, 2026
c0a6220
refactor(storage): remove Service.ts and migrate logic to StorageTran…
thiyaguk09 May 14, 2026
abf9d50
Merge remote-tracking branch 'upstream/storage-node-18' into storage-…
thiyaguk09 Jun 4, 2026
da48019
Merge remote-tracking branch 'upstream/storage-node-18' into storage-…
thiyaguk09 Jun 5, 2026
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
4 changes: 2 additions & 2 deletions handwritten/storage/src/bucket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3401,13 +3401,13 @@ class Bucket extends ServiceObject<Bucket, BucketMetadata> {
* @returns {Promise<Bucket>}
*/
async restore(options: RestoreOptions): Promise<Bucket> {
const bucket = await this.storageTransport.makeRequest<Bucket>({
const response = await this.storageTransport.makeRequest<Bucket>({
method: 'POST',
url: `${this.baseUrl}/${this.name}/restore`,
queryParameters: options as unknown as StorageQueryParameters,
});

return bucket as Bucket;
return response.data as Bucket;
}

makePrivate(
Expand Down
64 changes: 56 additions & 8 deletions handwritten/storage/src/file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,11 @@ export const STORAGE_POST_POLICY_BASE_URL = 'https://storage.googleapis.com';
*/
const GS_URL_REGEXP = /^gs:\/\/([a-z0-9_.-]+)\/(.+)$/;

/**
* @private
*/
const ENCRYPTION_ALGORITHM_AES256 = 'AES256';

/**
* @private
* This regex will match compressible content types. These are primarily text/*, +json, +text, +xml content types.
Expand Down Expand Up @@ -1393,7 +1398,10 @@ class File extends ServiceObject<File, FileMetadata> {
const headers = new Headers();

if (this.encryptionKey !== undefined) {
headers.set('x-goog-copy-source-encryption-algorithm', 'AES256');
headers.set(
'x-goog-copy-source-encryption-algorithm',
ENCRYPTION_ALGORITHM_AES256,
);
headers.set(
'x-goog-copy-source-encryption-key',
this.encryptionKeyBase64!,
Expand All @@ -1405,7 +1413,12 @@ class File extends ServiceObject<File, FileMetadata> {
}

if (newFile.encryptionKey !== undefined) {
this.setEncryptionKey(newFile.encryptionKey!);
headers.set('x-goog-encryption-algorithm', ENCRYPTION_ALGORITHM_AES256);
headers.set('x-goog-encryption-key', newFile.encryptionKeyBase64 || '');
headers.set(
'x-goog-encryption-key-sha256',
newFile.encryptionKeyHash || '',
);
} else if (options.destinationKmsKeyName !== undefined) {
query.destinationKmsKeyName = options.destinationKmsKeyName;
delete options.destinationKmsKeyName;
Expand Down Expand Up @@ -1639,6 +1652,8 @@ class File extends ServiceObject<File, FileMetadata> {
}

const headers = response.headers;
const isStoredCompressed =
headers.get('x-goog-stored-content-encoding') === 'gzip';
const isCompressed = headers.get('content-encoding') === 'gzip';
const hashes: {crc32c?: string; md5?: string} = {};

Expand All @@ -1652,7 +1667,7 @@ class File extends ServiceObject<File, FileMetadata> {

const transformStreams: Transform[] = [];

if (shouldRunValidation) {
if (shouldRunValidation && !isStoredCompressed) {
// The x-goog-hash header should be set with a crc32c and md5 hash.
// ex: headers.set('x-goog-hash', 'crc32c=xxxx,md5=xxxx')
if (typeof headers.get('x-goog-hash') === 'string') {
Expand Down Expand Up @@ -1721,6 +1736,7 @@ class File extends ServiceObject<File, FileMetadata> {
const headers = {
'Accept-Encoding': 'gzip',
'Cache-Control': 'no-store',
...(this.encryptionKeyHeaders || {}),
} as Headers;

if (rangeRequest) {
Expand All @@ -1735,7 +1751,9 @@ class File extends ServiceObject<File, FileMetadata> {
headers,
queryParameters: query as unknown as StorageQueryParameters,
responseType: 'stream',
};
decompress: options.decompress,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any;

if (options[GCCL_GCS_CMD_KEY]) {
reqOpts[GCCL_GCS_CMD_KEY] = options[GCCL_GCS_CMD_KEY];
Expand Down Expand Up @@ -2426,6 +2444,18 @@ class File extends ServiceObject<File, FileMetadata> {
}
}

get encryptionKeyHeaders(): Record<string, string> | undefined {
if (!this.encryptionKey) {
return undefined;
}

return {
'x-goog-encryption-algorithm': ENCRYPTION_ALGORITHM_AES256,
'x-goog-encryption-key': this.encryptionKey.toString('base64'),
'x-goog-encryption-key-sha256': this.encryptionKeyHash || '',
};
}

/**
* The Storage API allows you to use a custom key for server-side encryption.
*
Expand Down Expand Up @@ -2485,7 +2515,10 @@ class File extends ServiceObject<File, FileMetadata> {
this.encryptionKeyInterceptor = {
resolved: reqOpts => {
reqOpts.headers = new Headers(reqOpts.headers || {});
reqOpts.headers.set('x-goog-encryption-algorithm', 'AES256');
reqOpts.headers.set(
'x-goog-encryption-algorithm',
ENCRYPTION_ALGORITHM_AES256,
);
reqOpts.headers.set('x-goog-encryption-key', this.encryptionKeyBase64!);
reqOpts.headers.set(
'x-goog-encryption-key-sha256',
Expand Down Expand Up @@ -3315,7 +3348,11 @@ class File extends ServiceObject<File, FileMetadata> {
undefined,
callback,
);
const url = `https://${this.storage.apiEndpoint}/storage/v1/b/${this.bucket.name}/o/${encodeURIComponent(this.name)}`;
const baseUrl = this.storage.apiEndpoint.startsWith('http')
? this.storage.apiEndpoint
: `https://${this.storage.apiEndpoint}`;

const url = `${baseUrl}/storage/v1/b/${this.bucket.name}/o/${encodeURIComponent(this.name)}`;

const gaxios = new Gaxios();
const storageInterceptors = this.storage?.interceptors || [];
Expand Down Expand Up @@ -4025,12 +4062,12 @@ class File extends ServiceObject<File, FileMetadata> {
* @returns {Promise<File>}
*/
async restore(options: RestoreOptions): Promise<File> {
const file = await this.storageTransport.makeRequest<File>({
const response = await this.storageTransport.makeRequest<File>({
method: 'POST',
url: `/storage/v1/b/${this.bucket.name}/o/${encodeURIComponent(this.name)}/restore`,
queryParameters: options as unknown as StorageQueryParameters,
});
return file as File;
return response.data as File;
}

rotateEncryptionKey(
Expand Down Expand Up @@ -4562,6 +4599,17 @@ class File extends ServiceObject<File, FileMetadata> {
},
];

const headers: Record<string, string> = {};
if (this.encryptionKey) {
headers['x-goog-encryption-algorithm'] = ENCRYPTION_ALGORITHM_AES256;
headers['x-goog-encryption-key'] = this.encryptionKeyBase64!;
headers['x-goog-encryption-key-sha256'] = this.encryptionKeyHash!;
}
reqOpts.headers = {
...reqOpts.headers,
...headers,
};

this.storageTransport
.makeRequest(reqOpts as StorageRequestOptions, (err, body, resp) => {
if (err) {
Expand Down
62 changes: 45 additions & 17 deletions handwritten/storage/src/nodejs-common/service-object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,23 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {promisifyAll} from '@google-cloud/promisify';
import {EventEmitter} from 'events';
import {util} from './util.js';
import {Bucket} from '../bucket.js';
import {StorageRequestOptions, StorageTransport} from '../storage-transport.js';
import { promisifyAll } from '@google-cloud/promisify';
import { EventEmitter } from 'events';
import { util } from './util.js';
import { StorageRequestOptions, StorageTransport } from '../storage-transport.js';
import {
GaxiosError,
GaxiosInterceptor,
GaxiosOptionsPrepared,
GaxiosResponse,
} from 'gaxios';

function isBucket(parent: any): boolean {
// TODO: remove any suppression during follow up PR to improve type safety.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return parent && typeof (parent as any).getFiles === 'function';
}

export type GetMetadataOptions = object;

export type MetadataResponse<K> = [K, GaxiosResponse];
Expand Down Expand Up @@ -97,7 +102,7 @@ export interface InstanceResponseCallback<T> {
}

// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface CreateOptions {}
export interface CreateOptions { }
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unused-vars
export type CreateResponse<T> = any[];
export interface CreateCallback<T> {
Expand Down Expand Up @@ -207,8 +212,8 @@ class ServiceObject<T, K extends BaseMetadata> extends EventEmitter {
// The ServiceObject didn't redefine the method.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(this as any)[methodName] ===
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(ServiceObject.prototype as any)[methodName] &&
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(ServiceObject.prototype as any)[methodName] &&
// This method isn't wanted.
!config.methods![methodName]
);
Expand Down Expand Up @@ -293,8 +298,10 @@ class ServiceObject<T, K extends BaseMetadata> extends EventEmitter {
(typeof this.methods.delete === 'object' && this.methods.delete) || {};

let url = `${this.baseUrl}/${this.id}`;
if (this.parent instanceof Bucket) {
url = `${this.parent.baseUrl}/${this.parent.id}${url}`;
if (isBucket(this.parent)) {
// TODO: remove any suppression during follow up PR to improve type safety.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
url = `${this.parent.baseUrl}/${(this.parent as any).id}${url}`;
}

this.storageTransport
Expand Down Expand Up @@ -440,20 +447,39 @@ class ServiceObject<T, K extends BaseMetadata> extends EventEmitter {
{};

let url = `${this.baseUrl}/${this.id}`;
if (this.parent instanceof Bucket) {
url = `${this.parent.baseUrl}/${this.parent.id}${url}`;
if (isBucket(this.parent)) {
// TODO: remove any suppression during follow up PR to improve type safety.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
url = `${this.parent.baseUrl}/${(this.parent as any).id}${url}`;
}

// TODO: remove any suppression during follow up PR to improve type safety.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const encryptionHeaders = (this as any).encryptionKeyHeaders || {};
Comment thread
thiyaguk09 marked this conversation as resolved.

const headers = {
...encryptionHeaders,
...methodConfig.reqOpts?.headers,
// TODO: remove any suppression during follow up PR to improve type safety.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
...(options as any).headers,
};

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const query = { ...options } as any;
delete query.headers;

this.storageTransport
.makeRequest<K>(
{
method: 'GET',
responseType: 'json',
url,
...methodConfig.reqOpts,
headers,
queryParameters: {
...methodConfig.reqOpts?.queryParameters,
...options,
...query,
},
},
(err, data, resp) => {
Expand Down Expand Up @@ -498,8 +524,10 @@ class ServiceObject<T, K extends BaseMetadata> extends EventEmitter {
{};

let url = `${this.baseUrl}/${this.name}`;
if (this.parent instanceof Bucket) {
url = `${this.parent.baseUrl}/${this.parent.name}${url}`;
if (isBucket(this.parent)) {
// TODO: remove any suppression during follow up PR to improve type safety.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
url = `${this.parent.baseUrl}/${(this.parent as any).name}${url}`;
}

const body = Object.assign({}, methodConfig.reqOpts?.body, metadata);
Expand Down Expand Up @@ -529,6 +557,6 @@ class ServiceObject<T, K extends BaseMetadata> extends EventEmitter {
}
}

promisifyAll(ServiceObject, {exclude: ['getRequestInterceptors']});
promisifyAll(ServiceObject, { exclude: ['getRequestInterceptors'] });

export {ServiceObject};
export { ServiceObject };
Loading
Loading