Skip to content
This repository was archived by the owner on Mar 3, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 9 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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ Samples are in the [`samples/`](https://github.com/googleapis/nodejs-storage/tre
| Get HMAC SA Key Metadata. | [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/hmacKeyGet.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/hmacKeyGet.js,samples/README.md) |
| List HMAC SA Keys Metadata. | [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/hmacKeysList.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/hmacKeysList.js,samples/README.md) |
| List Buckets | [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/listBuckets.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/listBuckets.js,samples/README.md) |
| List Buckets Partial Success | [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/listBucketsPartialSuccess.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/listBucketsPartialSuccess.js,samples/README.md) |
| List Files | [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/listFiles.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/listFiles.js,samples/README.md) |
| List Files By Prefix | [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/listFilesByPrefix.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/listFilesByPrefix.js,samples/README.md) |
| List Files Paginate | [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/listFilesPaginate.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/listFilesPaginate.js,samples/README.md) |
Expand Down
18 changes: 18 additions & 0 deletions samples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ objects to users via direct download.
* [Get HMAC SA Key Metadata.](#get-hmac-sa-key-metadata.)
* [List HMAC SA Keys Metadata.](#list-hmac-sa-keys-metadata.)
* [List Buckets](#list-buckets)
* [List Buckets Partial Success](#list-buckets-partial-success)
* [List Files](#list-files)
* [List Files By Prefix](#list-files-by-prefix)
* [List Files Paginate](#list-files-paginate)
Expand Down Expand Up @@ -1421,6 +1422,23 @@ __Usage:__



### List Buckets Partial Success

View the [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/listBucketsPartialSuccess.js).

[![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/listBucketsPartialSuccess.js,samples/README.md)

__Usage:__


`node samples/listBucketsPartialSuccess.js`


-----




### List Files

View the [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/listFiles.js).
Expand Down
57 changes: 57 additions & 0 deletions samples/listBucketsPartialSuccess.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/**
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

function main() {
// [START storage_list_buckets_partial_success]
Comment thread
thiyaguk09 marked this conversation as resolved.
// Imports the Google Cloud client library
const {Storage} = require('@google-cloud/storage');

// Creates a client
const storage = new Storage();

async function listBucketsPartialSuccess() {
const option = {
returnPartialSuccess: true,
maxResults: 5,
};
const [buckets, nextQuery, apiResponse] = await storage.getBuckets(option);

if (nextQuery && nextQuery.pageToken) {
console.log(`Next Page Token: ${nextQuery.pageToken}`);
}

console.log('\nBuckets:');
buckets.forEach(bucket => {
if (bucket.unreachable === true) {
console.log(`${bucket.name} (unreachable: ${bucket.unreachable})`);
} else {
console.log(`${bucket.name}`);
}
});

if (apiResponse.unreachable && apiResponse.unreachable.length > 0) {
console.log('\nUnreachable Buckets:');
apiResponse.unreachable.forEach(item => {
console.log(item);
});
}
}

listBucketsPartialSuccess().catch(console.error);
// [END storage_list_buckets_partial_success]
}

main(...process.argv.slice(2));
7 changes: 7 additions & 0 deletions src/bucket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -838,6 +838,13 @@ class Bucket extends ServiceObject<Bucket, BucketMetadata> {
private instanceRetryValue?: boolean;
instancePreconditionOpts?: PreconditionOptions;

/**
* Indicates whether this Bucket object is a placeholder for an item
* that the API failed to retrieve (unreachable) due to partial failure.
* Consumers must check this flag before accessing other properties.
*/
unreachable?: boolean;
Comment thread
thiyaguk09 marked this conversation as resolved.
Outdated

constructor(storage: Storage, name: string, options?: BucketOptions) {
options = options || {};

Expand Down
34 changes: 33 additions & 1 deletion src/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ export interface GetBucketsRequest {
userProject?: string;
softDeleted?: boolean;
generation?: number;
returnPartialSuccess?: boolean;
}

export interface HmacKeyResourceResponse {
Expand Down Expand Up @@ -1325,6 +1326,7 @@ export class Storage extends Service {
cb
);
options.project = options.project || this.projectId;
const returnPartialSuccess = options.returnPartialSuccess || false;

this.request(
{
Expand All @@ -1338,17 +1340,47 @@ export class Storage extends Service {
}

const itemsArray = resp.items ? resp.items : [];
const unreachableArray = resp.unreachable ? resp.unreachable : [];

const buckets = itemsArray.map((bucket: BucketMetadata) => {
const bucketInstance = this.bucket(bucket.id!);
bucketInstance.metadata = bucket;

if (returnPartialSuccess) {
Comment thread
thiyaguk09 marked this conversation as resolved.
Outdated
const unreachableBucketId = `projects/_/buckets/${bucket.id}`;
if (unreachableArray.includes(unreachableBucketId)) {
bucketInstance.unreachable = true;
}
}
return bucketInstance;
});

let unreachableBuckets: Bucket[] = [];
if (returnPartialSuccess && unreachableArray.length > 0) {
Comment thread
thiyaguk09 marked this conversation as resolved.
Outdated
unreachableBuckets = unreachableArray
Comment thread
thiyaguk09 marked this conversation as resolved.
Outdated
.map((fullPath: string) => {
const name = fullPath.split('/').pop();
if (!name) return null;
const exists = new Set(buckets.map((b: Bucket) => b.name)).has(
name
);
if (!exists) {
const placeholder = this.bucket(name);
placeholder.unreachable = true;
placeholder.metadata = {};
return placeholder;
}
return null;
})
.filter(Boolean) as Bucket[];
}
const results = [...buckets, ...unreachableBuckets];

const nextQuery = resp.nextPageToken
? Object.assign({}, options, {pageToken: resp.nextPageToken})
: null;

callback(null, buckets, nextQuery, resp);
callback(null, results, nextQuery, resp);
}
);
}
Expand Down
110 changes: 110 additions & 0 deletions test/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1177,6 +1177,116 @@ describe('Storage', () => {
done();
});
});

it('should return unreachable when returnPartialSuccess is true', done => {
const unreachableList = ['projects/_/buckets/fail-bucket'];
const itemsList = [{id: 'fake-bucket-name'}];
const resp = {items: itemsList, unreachable: unreachableList};

storage.request = (
reqOpts: DecorateRequestOptions,
callback: Function
) => {
assert.strictEqual(reqOpts.qs.returnPartialSuccess, true);
callback(null, resp);
};

storage.getBuckets(
{returnPartialSuccess: true},
(err: Error, buckets: Bucket[], nextQuery: {}, apiResponse: {}) => {
assert.ifError(err);
assert.strictEqual(buckets.length, 2);

const reachableBucket = buckets.find(
b => b.name === 'fake-bucket-name'
);
assert.ok(reachableBucket);
assert.strictEqual(reachableBucket!.unreachable, undefined);

const unreachableBucket = buckets.find(b => b.name === 'fail-bucket');
assert.ok(unreachableBucket);
assert.strictEqual(unreachableBucket!.unreachable, true);
assert.deepStrictEqual(apiResponse, resp);
done();
}
);
});

it('should not return unreachable when returnPartialSuccess is false and unreachable items exist', done => {
const unreachableList = ['projects/_/buckets/fail-bucket'];
const itemsList = [{id: 'fake-bucket-name'}];
const resp = {items: itemsList, unreachable: unreachableList};

storage.request = (
reqOpts: DecorateRequestOptions,
callback: Function
) => {
assert.strictEqual(reqOpts.qs.returnPartialSuccess, false);
callback(null, resp);
};

storage.getBuckets(
{returnPartialSuccess: false},
(err: Error, buckets: Bucket[], nextQuery: {}, apiResponse: {}) => {
assert.ifError(err);
assert.strictEqual(buckets.length, itemsList.length);
assert.deepStrictEqual(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(apiResponse as any).unreachable,
unreachableList
);
done();
}
);
});

it('should handle partial failure with zero reachable buckets', done => {
const unreachableList = ['projects/_/buckets/fail-bucket'];
const resp = {items: [], unreachable: unreachableList};

storage.request = (
reqOpts: DecorateRequestOptions,
callback: Function
) => {
callback(null, resp);
};

storage.getBuckets(
{returnPartialSuccess: true},
(err: Error, buckets: Bucket[]) => {
assert.ifError(err);
assert.strictEqual(buckets.length, 1);
assert.deepStrictEqual(buckets[0].name, 'fail-bucket');
assert.strictEqual(buckets[0].unreachable, true);
assert.deepStrictEqual(buckets[0].metadata, {});
done();
}
);
});

it('should handle API success where zero items and zero unreachable items are returned', done => {
const resp = {items: [], unreachable: []};

storage.request = (
reqOpts: DecorateRequestOptions,
callback: Function
) => {
if (reqOpts.qs.returnPartialSuccess !== undefined) {
assert.strictEqual(reqOpts.qs.returnPartialSuccess, true);
}
callback(null, resp);
};

storage.getBuckets(
{returnPartialSuccess: true},
(err: Error, buckets: Bucket[], nextQuery: {}, apiResponse: {}) => {
assert.ifError(err);
assert.strictEqual(buckets.length, 0);
assert.deepStrictEqual(apiResponse, resp);
done();
}
);
});
});

describe('getHmacKeys', () => {
Expand Down
Loading