}
+ *
+ * @example
+ * ```
+ * const {Storage} = require('@google-cloud/storage');
+ * const storage = new Storage();
+ * const bucket = storage.bucket('albums');
+ *
+ * bucket.getFiles(function(err, files) {
+ * if (!err) {
+ * // files is an array of File objects.
+ * }
+ * });
+ *
+ * //-
+ * // If your bucket has versioning enabled, you can get all of your files
+ * // scoped to their generation.
+ * //-
+ * bucket.getFiles({
+ * versions: true
+ * }, function(err, files) {
+ * // Each file is scoped to its generation.
+ * });
+ *
+ * //-
+ * // To control how many API requests are made and page through the results
+ * // manually, set `autoPaginate` to `false`.
+ * //-
+ * const callback = function(err, files, nextQuery, apiResponse) {
+ * if (nextQuery) {
+ * // More results exist.
+ * bucket.getFiles(nextQuery, callback);
+ * }
+ *
+ * // The `metadata` property is populated for you with the metadata at the
+ * // time of fetching.
+ * files[0].metadata;
+ *
+ * // However, in cases where you are concerned the metadata could have
+ * // changed, use the `getMetadata` method.
+ * files[0].getMetadata(function(err, metadata) {});
+ * };
+ *
+ * bucket.getFiles({
+ * autoPaginate: false
+ * }, callback);
+ *
+ * //-
+ * // If the callback is omitted, we'll return a Promise.
+ * //-
+ * bucket.getFiles().then(function(data) {
+ * const files = data[0];
+ * });
+ *
+ * ```
+ * @example
+ * Simulating a File System
With `autoPaginate: false`, it's possible to iterate over files which incorporate a common structure using a delimiter.
Consider the following remote objects:
- "a"
- "a/b/c/d"
- "b/d/e"
Using a delimiter of `/` will return a single file, "a".
`apiResponse.prefixes` will return the "sub-directories" that were found:
- "a/"
- "b/"
+ * ```
+ * bucket.getFiles({
+ * autoPaginate: false,
+ * delimiter: '/'
+ * }, function(err, files, nextQuery, apiResponse) {
+ * // files = [
+ * // {File} // File object for file "a"
+ * // ]
+ *
+ * // apiResponse.prefixes = [
+ * // 'a/',
+ * // 'b/'
+ * // ]
+ * });
+ * ```
+ *
+ * @example
+ * Using prefixes, it's now possible to simulate a file system with follow-up requests.
+ * ```
+ * bucket.getFiles({
+ * autoPaginate: false,
+ * delimiter: '/',
+ * prefix: 'a/'
+ * }, function(err, files, nextQuery, apiResponse) {
+ * // No files found within "directory" a.
+ * // files = []
+ *
+ * // However, a "sub-directory" was found.
+ * // This prefix can be used to continue traversing the "file system".
+ * // apiResponse.prefixes = [
+ * // 'a/b/'
+ * // ]
+ * });
+ * ```
+ *
+ * @example include:samples/files.js
+ * region_tag:storage_list_files
+ * Another example:
+ *
+ * @example include:samples/files.js
+ * region_tag:storage_list_files_with_prefix
+ * Example of listing files, filtered by a prefix:
+ */
+ getFiles(
+ queryOrCallback?: GetFilesOptions | GetFilesCallback,
+ callback?: GetFilesCallback
+ ): void | Promise {
+ let query = typeof queryOrCallback === 'object' ? queryOrCallback : {};
+ if (!callback) {
+ callback = queryOrCallback as GetFilesCallback;
+ }
+ query = Object.assign({}, query);
+ if (
+ query.fields &&
+ query.autoPaginate &&
+ !query.fields.includes('nextPageToken')
+ ) {
+ query.fields = `${query.fields},nextPageToken`;
+ }
+
+ this.request(
+ {
+ uri: '/o',
+ qs: query,
+ },
+ (err, resp) => {
+ if (err) {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ (callback as any)(err, null, null, resp);
+ return;
+ }
+
+ const itemsArray = resp.items ? resp.items : [];
+ const files = itemsArray.map((file: FileMetadata) => {
+ const options = {} as FileOptions;
+
+ if (query.fields) {
+ const fileInstance = file;
+ return fileInstance;
+ }
+
+ if (query.versions) {
+ options.generation = file.generation;
+ }
+
+ if (file.kmsKeyName) {
+ options.kmsKeyName = file.kmsKeyName;
+ }
+
+ const fileInstance = this.file(file.name!, options);
+ fileInstance.metadata = file;
+
+ return fileInstance;
+ });
+
+ let nextQuery: object | null = null;
+ if (resp.nextPageToken) {
+ nextQuery = Object.assign({}, query, {
+ pageToken: resp.nextPageToken,
+ });
+ }
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ (callback as any)(null, files, nextQuery, resp);
+ }
+ );
+ }
+
+ getLabels(options?: GetLabelsOptions): Promise;
+ getLabels(callback: GetLabelsCallback): void;
+ getLabels(options: GetLabelsOptions, callback: GetLabelsCallback): void;
+ /**
+ * @deprecated
+ * @typedef {object} GetLabelsOptions Configuration options for Bucket#getLabels().
+ * @param {string} [userProject] The ID of the project which will be
+ * billed for the request.
+ */
+ /**
+ * @deprecated
+ * @typedef {array} GetLabelsResponse
+ * @property {object} 0 Object of labels currently set on this bucket.
+ */
+ /**
+ * @deprecated
+ * @callback GetLabelsCallback
+ * @param {?Error} err Request error, if any.
+ * @param {object} labels Object of labels currently set on this bucket.
+ */
+ /**
+ * @deprecated Use getMetadata directly.
+ * Get the labels currently set on this bucket.
+ *
+ * @param {object} [options] Configuration options.
+ * @param {string} [options.userProject] The ID of the project which will be
+ * billed for the request.
+ * @param {GetLabelsCallback} [callback] Callback function.
+ * @returns {Promise}
+ *
+ * @example
+ * ```
+ * const {Storage} = require('@google-cloud/storage');
+ * const storage = new Storage();
+ * const bucket = storage.bucket('albums');
+ *
+ * bucket.getLabels(function(err, labels) {
+ * if (err) {
+ * // Error handling omitted.
+ * }
+ *
+ * // labels = {
+ * // label: 'labelValue',
+ * // ...
+ * // }
+ * });
+ *
+ * //-
+ * // If the callback is omitted, we'll return a Promise.
+ * //-
+ * bucket.getLabels().then(function(data) {
+ * const labels = data[0];
+ * });
+ * ```
+ */
+ getLabels(
+ optionsOrCallback?: GetLabelsOptions | GetLabelsCallback,
+ callback?: GetLabelsCallback
+ ): Promise | void {
+ let options: GetLabelsOptions = {};
+ if (typeof optionsOrCallback === 'function') {
+ callback = optionsOrCallback;
+ } else if (optionsOrCallback) {
+ options = optionsOrCallback;
+ }
+
+ this.getMetadata(
+ options,
+ (err: ApiError | null, metadata: BucketMetadata | undefined) => {
+ if (err) {
+ callback!(err, null);
+ return;
+ }
+
+ callback!(null, metadata?.labels || {});
+ }
+ );
+ }
+
+ getNotifications(
+ options?: GetNotificationsOptions
+ ): Promise;
+ getNotifications(callback: GetNotificationsCallback): void;
+ getNotifications(
+ options: GetNotificationsOptions,
+ callback: GetNotificationsCallback
+ ): void;
+ /**
+ * @typedef {object} GetNotificationsOptions Configuration options for Bucket#getNotification().
+ * @property {string} [userProject] The ID of the project which will be
+ * billed for the request.
+ */
+ /**
+ * @callback GetNotificationsCallback
+ * @param {?Error} err Request error, if any.
+ * @param {Notification[]} notifications Array of {@link Notification}
+ * instances.
+ * @param {object} apiResponse The full API response.
+ */
+ /**
+ * @typedef {array} GetNotificationsResponse
+ * @property {Notification[]} 0 Array of {@link Notification} instances.
+ * @property {object} 1 The full API response.
+ */
+ /**
+ * Retrieves a list of notification subscriptions for a given bucket.
+ *
+ * See {@link https://cloud.google.com/storage/docs/json_api/v1/notifications/list| Notifications: list}
+ *
+ * @param {GetNotificationsOptions} [options] Configuration options.
+ * @param {string} [options.userProject] The ID of the project which will be
+ * billed for the request.
+ * @param {GetNotificationsCallback} [callback] Callback function.
+ * @returns {Promise}
+ *
+ * @example
+ * ```
+ * const {Storage} = require('@google-cloud/storage');
+ * const storage = new Storage();
+ * const bucket = storage.bucket('my-bucket');
+ *
+ * bucket.getNotifications(function(err, notifications, apiResponse) {
+ * if (!err) {
+ * // notifications is an array of Notification objects.
+ * }
+ * });
+ *
+ * //-
+ * // If the callback is omitted, we'll return a Promise.
+ * //-
+ * bucket.getNotifications().then(function(data) {
+ * const notifications = data[0];
+ * const apiResponse = data[1];
+ * });
+ *
+ * ```
+ * @example include:samples/listNotifications.js
+ * region_tag:storage_list_bucket_notifications
+ * Another example:
+ */
+ getNotifications(
+ optionsOrCallback?: GetNotificationsOptions | GetNotificationsCallback,
+ callback?: GetNotificationsCallback
+ ): Promise | void {
+ let options: GetNotificationsOptions = {};
+ if (typeof optionsOrCallback === 'function') {
+ callback = optionsOrCallback;
+ } else if (optionsOrCallback) {
+ options = optionsOrCallback;
+ }
+
+ this.request(
+ {
+ uri: '/notificationConfigs',
+ qs: options,
+ },
+ (err, resp) => {
+ if (err) {
+ callback!(err, null, resp);
+ return;
+ }
+ const itemsArray = resp.items ? resp.items : [];
+ const notifications = itemsArray.map(
+ (notification: NotificationMetadata) => {
+ const notificationInstance = this.notification(notification.id!);
+ notificationInstance.metadata = notification;
+ return notificationInstance;
+ }
+ );
+
+ callback!(null, notifications, resp);
+ }
+ );
+ }
+
+ getSignedUrl(cfg: GetBucketSignedUrlConfig): Promise;
+ getSignedUrl(
+ cfg: GetBucketSignedUrlConfig,
+ callback: GetSignedUrlCallback
+ ): void;
+ /**
+ * @typedef {array} GetSignedUrlResponse
+ * @property {object} 0 The signed URL.
+ */
+ /**
+ * @callback GetSignedUrlCallback
+ * @param {?Error} err Request error, if any.
+ * @param {object} url The signed URL.
+ */
+ /**
+ * @typedef {object} GetBucketSignedUrlConfig
+ * @property {string} action Only listing objects within a bucket (HTTP: GET) is supported for bucket-level signed URLs.
+ * @property {*} expires A timestamp when this link will expire. Any value
+ * given is passed to `new Date()`.
+ * Note: 'v4' supports maximum duration of 7 days (604800 seconds) from now.
+ * @property {string} [version='v2'] The signing version to use, either
+ * 'v2' or 'v4'.
+ * @property {boolean} [virtualHostedStyle=false] Use virtual hosted-style
+ * URLs ('https://mybucket.storage.googleapis.com/...') instead of path-style
+ * ('https://storage.googleapis.com/mybucket/...'). Virtual hosted-style URLs
+ * should generally be preferred instead of path-style URL.
+ * Currently defaults to `false` for path-style, although this may change in a
+ * future major-version release.
+ * @property {string} [cname] The cname for this bucket, i.e.,
+ * "https://cdn.example.com".
+ * See [reference]{@link https://cloud.google.com/storage/docs/access-control/signed-urls#example}
+ * @property {object} [extensionHeaders] If these headers are used, the
+ * server will check to make sure that the client provides matching
+ * values. See {@link https://cloud.google.com/storage/docs/access-control/signed-urls#about-canonical-extension-headers| Canonical extension headers}
+ * for the requirements of this feature, most notably:
+ * - The header name must be prefixed with `x-goog-`
+ * - The header name must be all lowercase
+ *
+ * Note: Multi-valued header passed as an array in the extensionHeaders
+ * object is converted into a string, delimited by `,` with
+ * no space. Requests made using the signed URL will need to
+ * delimit multi-valued headers using a single `,` as well, or
+ * else the server will report a mismatched signature.
+ * @property {object} [queryParams] Additional query parameters to include
+ * in the signed URL.
+ */
+ /**
+ * Get a signed URL to allow limited time access to a bucket.
+ *
+ * In Google Cloud Platform environments, such as Cloud Functions and App
+ * Engine, you usually don't provide a `keyFilename` or `credentials` during
+ * instantiation. In those environments, we call the
+ * {@link https://cloud.google.com/iam/docs/reference/credentials/rest/v1/projects.serviceAccounts/signBlob| signBlob API}
+ * to create a signed URL. That API requires either the
+ * `https://www.googleapis.com/auth/iam` or
+ * `https://www.googleapis.com/auth/cloud-platform` scope, so be sure they are
+ * enabled.
+ *
+ * See {@link https://cloud.google.com/storage/docs/access-control/signed-urls| Signed URLs Reference}
+ *
+ * @throws {Error} if an expiration timestamp from the past is given.
+ *
+ * @param {GetBucketSignedUrlConfig} config Configuration object.
+ * @param {string} config.action Currently only supports "list" (HTTP: GET).
+ * @param {*} config.expires A timestamp when this link will expire. Any value
+ * given is passed to `new Date()`.
+ * Note: 'v4' supports maximum duration of 7 days (604800 seconds) from now.
+ * @param {string} [config.version='v2'] The signing version to use, either
+ * 'v2' or 'v4'.
+ * @param {boolean} [config.virtualHostedStyle=false] Use virtual hosted-style
+ * URLs ('https://mybucket.storage.googleapis.com/...') instead of path-style
+ * ('https://storage.googleapis.com/mybucket/...'). Virtual hosted-style URLs
+ * should generally be preferred instead of path-style URL.
+ * Currently defaults to `false` for path-style, although this may change in a
+ * future major-version release.
+ * @param {string} [config.cname] The cname for this bucket, i.e.,
+ * "https://cdn.example.com".
+ * See [reference]{@link https://cloud.google.com/storage/docs/access-control/signed-urls#example}
+ * @param {object} [config.extensionHeaders] If these headers are used, the
+ * server will check to make sure that the client provides matching
+ * values. See {@link https://cloud.google.com/storage/docs/access-control/signed-urls#about-canonical-extension-headers| Canonical extension headers}
+ * for the requirements of this feature, most notably:
+ * - The header name must be prefixed with `x-goog-`
+ * - The header name must be all lowercase
+ *
+ * Note: Multi-valued header passed as an array in the extensionHeaders
+ * object is converted into a string, delimited by `,` with
+ * no space. Requests made using the signed URL will need to
+ * delimit multi-valued headers using a single `,` as well, or
+ * else the server will report a mismatched signature.
+ * @property {object} [config.queryParams] Additional query parameters to include
+ * in the signed URL.
+ * @param {GetSignedUrlCallback} [callback] Callback function.
+ * @returns {Promise}
+ *
+ * @example
+ * ```
+ * const {Storage} = require('@google-cloud/storage');
+ * const storage = new Storage();
+ * const myBucket = storage.bucket('my-bucket');
+ *
+ * //-
+ * // Generate a URL that allows temporary access to list files in a bucket.
+ * //-
+ * const request = require('request');
+ *
+ * const config = {
+ * action: 'list',
+ * expires: '03-17-2025'
+ * };
+ *
+ * bucket.getSignedUrl(config, function(err, url) {
+ * if (err) {
+ * console.error(err);
+ * return;
+ * }
+ *
+ * // The bucket is now available to be listed from this URL.
+ * request(url, function(err, resp) {
+ * // resp.statusCode = 200
+ * });
+ * });
+ *
+ * //-
+ * // If the callback is omitted, we'll return a Promise.
+ * //-
+ * bucket.getSignedUrl(config).then(function(data) {
+ * const url = data[0];
+ * });
+ * ```
+ */
+ getSignedUrl(
+ cfg: GetBucketSignedUrlConfig,
+ callback?: GetSignedUrlCallback
+ ): void | Promise {
+ const method = BucketActionToHTTPMethod[cfg.action];
+
+ const signConfig: SignerGetSignedUrlConfig = {
+ method,
+ expires: cfg.expires,
+ version: cfg.version,
+ cname: cfg.cname,
+ extensionHeaders: cfg.extensionHeaders || {},
+ queryParams: cfg.queryParams || {},
+ host: cfg.host,
+ signingEndpoint: cfg.signingEndpoint,
+ };
+
+ if (!this.signer) {
+ this.signer = new URLSigner(
+ this.storage.authClient,
+ this,
+ undefined,
+ this.storage
+ );
+ }
+
+ this.signer
+ .getSignedUrl(signConfig)
+ .then(signedUrl => callback!(null, signedUrl), callback!);
+ }
+
+ lock(metageneration: number | string): Promise;
+ lock(metageneration: number | string, callback: BucketLockCallback): void;
+ /**
+ * @callback BucketLockCallback
+ * @param {?Error} err Request error, if any.
+ * @param {object} apiResponse The full API response.
+ */
+ /**
+ * Lock a previously-defined retention policy. This will prevent changes to
+ * the policy.
+ *
+ * @throws {Error} if a metageneration is not provided.
+ *
+ * @param {number|string} metageneration The bucket's metageneration. This is
+ * accessible from calling {@link File#getMetadata}.
+ * @param {BucketLockCallback} [callback] Callback function.
+ * @returns {Promise}
+ *
+ * @example
+ * ```
+ * const storage = require('@google-cloud/storage')();
+ * const bucket = storage.bucket('albums');
+ *
+ * const metageneration = 2;
+ *
+ * bucket.lock(metageneration, function(err, apiResponse) {});
+ *
+ * //-
+ * // If the callback is omitted, we'll return a Promise.
+ * //-
+ * bucket.lock(metageneration).then(function(data) {
+ * const apiResponse = data[0];
+ * });
+ * ```
+ */
+ lock(
+ metageneration: number | string,
+ callback?: BucketLockCallback
+ ): Promise | void {
+ const metatype = typeof metageneration;
+ if (metatype !== 'number' && metatype !== 'string') {
+ throw new Error(BucketExceptionMessages.METAGENERATION_NOT_PROVIDED);
+ }
+
+ this.request(
+ {
+ method: 'POST',
+ uri: '/lockRetentionPolicy',
+ qs: {
+ ifMetagenerationMatch: metageneration,
+ },
+ },
+ callback!
+ );
+ }
+
+ /**
+ * @typedef {object} RestoreOptions Options for Bucket#restore(). See an
+ * {@link https://cloud.google.com/storage/docs/json_api/v1/buckets/restore#resource| Object resource}.
+ * @param {number} [generation] If present, selects a specific revision of this object.
+ * @param {string} [projection] Specifies the set of properties to return. If used, must be 'full' or 'noAcl'.
+ */
+ /**
+ * Restores a soft-deleted bucket
+ * @param {RestoreOptions} options Restore options.
+ * @returns {Promise}
+ */
+ async restore(options: RestoreOptions): Promise {
+ const [bucket] = await this.request({
+ method: 'POST',
+ uri: '/restore',
+ qs: options,
+ });
+
+ return bucket as Bucket;
+ }
+
+ makePrivate(
+ options?: MakeBucketPrivateOptions
+ ): Promise;
+ makePrivate(callback: MakeBucketPrivateCallback): void;
+ makePrivate(
+ options: MakeBucketPrivateOptions,
+ callback: MakeBucketPrivateCallback
+ ): void;
+ /**
+ * @typedef {array} MakeBucketPrivateResponse
+ * @property {File[]} 0 List of files made private.
+ */
+ /**
+ * @callback MakeBucketPrivateCallback
+ * @param {?Error} err Request error, if any.
+ * @param {File[]} files List of files made private.
+ */
+ /**
+ * @typedef {object} MakeBucketPrivateOptions
+ * @property {boolean} [includeFiles=false] Make each file in the bucket
+ * private.
+ * @property {Metadata} [metadata] Define custom metadata properties to define
+ * along with the operation.
+ * @property {boolean} [force] Queue errors occurred while making files
+ * private until all files have been processed.
+ * @property {string} [userProject] The ID of the project which will be
+ * billed for the request.
+ */
+ /**
+ * Make the bucket listing private.
+ *
+ * You may also choose to make the contents of the bucket private by
+ * specifying `includeFiles: true`. This will automatically run
+ * {@link File#makePrivate} for every file in the bucket.
+ *
+ * When specifying `includeFiles: true`, use `force: true` to delay execution
+ * of your callback until all files have been processed. By default, the
+ * callback is executed after the first error. Use `force` to queue such
+ * errors until all files have been processed, after which they will be
+ * returned as an array as the first argument to your callback.
+ *
+ * NOTE: This may cause the process to be long-running and use a high number
+ * of requests. Use with caution.
+ *
+ * See {@link https://cloud.google.com/storage/docs/json_api/v1/buckets/patch| Buckets: patch API Documentation}
+ *
+ * @param {MakeBucketPrivateOptions} [options] Configuration options.
+ * @param {boolean} [options.includeFiles=false] Make each file in the bucket
+ * private.
+ * @param {Metadata} [options.metadata] Define custom metadata properties to define
+ * along with the operation.
+ * @param {boolean} [options.force] Queue errors occurred while making files
+ * private until all files have been processed.
+ * @param {string} [options.userProject] The ID of the project which will be
+ * billed for the request.
+ * @param {MakeBucketPrivateCallback} [callback] Callback function.
+ * @returns {Promise}
+ *
+ * @example
+ * ```
+ * const {Storage} = require('@google-cloud/storage');
+ * const storage = new Storage();
+ * const bucket = storage.bucket('albums');
+ *
+ * //-
+ * // Make the bucket private.
+ * //-
+ * bucket.makePrivate(function(err) {});
+ *
+ * //-
+ * // Make the bucket and its contents private.
+ * //-
+ * const opts = {
+ * includeFiles: true
+ * };
+ *
+ * bucket.makePrivate(opts, function(err, files) {
+ * // `err`:
+ * // The first error to occur, otherwise null.
+ * //
+ * // `files`:
+ * // Array of files successfully made private in the bucket.
+ * });
+ *
+ * //-
+ * // Make the bucket and its contents private, using force to suppress errors
+ * // until all files have been processed.
+ * //-
+ * const opts = {
+ * includeFiles: true,
+ * force: true
+ * };
+ *
+ * bucket.makePrivate(opts, function(errors, files) {
+ * // `errors`:
+ * // Array of errors if any occurred, otherwise null.
+ * //
+ * // `files`:
+ * // Array of files successfully made private in the bucket.
+ * });
+ *
+ * //-
+ * // If the callback is omitted, we'll return a Promise.
+ * //-
+ * bucket.makePrivate(opts).then(function(data) {
+ * const files = data[0];
+ * });
+ * ```
+ */
+ makePrivate(
+ optionsOrCallback?: MakeBucketPrivateOptions | MakeBucketPrivateCallback,
+ callback?: MakeBucketPrivateCallback
+ ): Promise | void {
+ const options: MakeBucketPrivateRequest =
+ typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
+ callback =
+ typeof optionsOrCallback === 'function' ? optionsOrCallback : callback;
+
+ options.private = true;
+
+ const query: MetadataOptions = {
+ predefinedAcl: 'projectPrivate',
+ };
+
+ if (options.userProject) {
+ query.userProject = options.userProject;
+ }
+
+ if (options.preconditionOpts?.ifGenerationMatch) {
+ query.ifGenerationMatch = options.preconditionOpts.ifGenerationMatch;
+ }
+
+ if (options.preconditionOpts?.ifGenerationNotMatch) {
+ query.ifGenerationNotMatch =
+ options.preconditionOpts.ifGenerationNotMatch;
+ }
+
+ if (options.preconditionOpts?.ifMetagenerationMatch) {
+ query.ifMetagenerationMatch =
+ options.preconditionOpts.ifMetagenerationMatch;
+ }
+
+ if (options.preconditionOpts?.ifMetagenerationNotMatch) {
+ query.ifMetagenerationNotMatch =
+ options.preconditionOpts.ifMetagenerationNotMatch;
+ }
+
+ // You aren't allowed to set both predefinedAcl & acl properties on a bucket
+ // so acl must explicitly be nullified.
+ const metadata = {...options.metadata, acl: null};
+
+ this.setMetadata(metadata, query, (err: Error | null | undefined) => {
+ if (err) {
+ callback!(err);
+ }
+ const internalCall = () => {
+ if (options.includeFiles) {
+ return promisify(
+ this.makeAllFilesPublicPrivate_
+ ).call(this, options);
+ }
+ return Promise.resolve([] as File[]);
+ };
+ internalCall()
+ .then(files => callback!(null, files))
+ .catch(callback!);
+ });
+ }
+
+ makePublic(
+ options?: MakeBucketPublicOptions
+ ): Promise;
+ makePublic(callback: MakeBucketPublicCallback): void;
+ makePublic(
+ options: MakeBucketPublicOptions,
+ callback: MakeBucketPublicCallback
+ ): void;
+ /**
+ * @typedef {object} MakeBucketPublicOptions
+ * @property {boolean} [includeFiles=false] Make each file in the bucket
+ * private.
+ * @property {boolean} [force] Queue errors occurred while making files
+ * private until all files have been processed.
+ */
+ /**
+ * @callback MakeBucketPublicCallback
+ * @param {?Error} err Request error, if any.
+ * @param {File[]} files List of files made public.
+ */
+ /**
+ * @typedef {array} MakeBucketPublicResponse
+ * @property {File[]} 0 List of files made public.
+ */
+ /**
+ * Make the bucket publicly readable.
+ *
+ * You may also choose to make the contents of the bucket publicly readable by
+ * specifying `includeFiles: true`. This will automatically run
+ * {@link File#makePublic} for every file in the bucket.
+ *
+ * When specifying `includeFiles: true`, use `force: true` to delay execution
+ * of your callback until all files have been processed. By default, the
+ * callback is executed after the first error. Use `force` to queue such
+ * errors until all files have been processed, after which they will be
+ * returned as an array as the first argument to your callback.
+ *
+ * NOTE: This may cause the process to be long-running and use a high number
+ * of requests. Use with caution.
+ *
+ * See {@link https://cloud.google.com/storage/docs/json_api/v1/buckets/patch| Buckets: patch API Documentation}
+ *
+ * @param {MakeBucketPublicOptions} [options] Configuration options.
+ * @param {boolean} [options.includeFiles=false] Make each file in the bucket
+ * private.
+ * @param {boolean} [options.force] Queue errors occurred while making files
+ * private until all files have been processed.
+ * @param {MakeBucketPublicCallback} [callback] Callback function.
+ * @returns {Promise}
+ *
+ * @example
+ * ```
+ * const {Storage} = require('@google-cloud/storage');
+ * const storage = new Storage();
+ * const bucket = storage.bucket('albums');
+ *
+ * //-
+ * // Make the bucket publicly readable.
+ * //-
+ * bucket.makePublic(function(err) {});
+ *
+ * //-
+ * // Make the bucket and its contents publicly readable.
+ * //-
+ * const opts = {
+ * includeFiles: true
+ * };
+ *
+ * bucket.makePublic(opts, function(err, files) {
+ * // `err`:
+ * // The first error to occur, otherwise null.
+ * //
+ * // `files`:
+ * // Array of files successfully made public in the bucket.
+ * });
+ *
+ * //-
+ * // Make the bucket and its contents publicly readable, using force to
+ * // suppress errors until all files have been processed.
+ * //-
+ * const opts = {
+ * includeFiles: true,
+ * force: true
+ * };
+ *
+ * bucket.makePublic(opts, function(errors, files) {
+ * // `errors`:
+ * // Array of errors if any occurred, otherwise null.
+ * //
+ * // `files`:
+ * // Array of files successfully made public in the bucket.
+ * });
+ *
+ * //-
+ * // If the callback is omitted, we'll return a Promise.
+ * //-
+ * bucket.makePublic(opts).then(function(data) {
+ * const files = data[0];
+ * });
+ * ```
+ */
+ makePublic(
+ optionsOrCallback?: MakeBucketPublicOptions | MakeBucketPublicCallback,
+ callback?: MakeBucketPublicCallback
+ ): Promise | void {
+ const options =
+ typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
+ callback =
+ typeof optionsOrCallback === 'function' ? optionsOrCallback : callback;
+
+ const req = {public: true, ...options};
+
+ this.acl
+ .add({
+ entity: 'allUsers',
+ role: 'READER',
+ })
+ .then(() => {
+ return this.acl.default!.add({
+ entity: 'allUsers',
+ role: 'READER',
+ });
+ })
+ .then(() => {
+ if (req.includeFiles) {
+ return promisify(
+ this.makeAllFilesPublicPrivate_
+ ).call(this, req);
+ }
+ return [];
+ })
+ .then(files => callback!(null, files), callback);
+ }
+
+ /**
+ * Get a reference to a Cloud Pub/Sub Notification.
+ *
+ * @param {string} id ID of notification.
+ * @returns {Notification}
+ * @see Notification
+ *
+ * @example
+ * ```
+ * const {Storage} = require('@google-cloud/storage');
+ * const storage = new Storage();
+ * const bucket = storage.bucket('my-bucket');
+ * const notification = bucket.notification('1');
+ * ```
+ */
+ notification(id: string): Notification {
+ if (!id) {
+ throw new Error(BucketExceptionMessages.SUPPLY_NOTIFICATION_ID);
+ }
+
+ return new Notification(this, id);
+ }
+
+ removeRetentionPeriod(
+ options?: SetBucketMetadataOptions
+ ): Promise;
+ removeRetentionPeriod(callback: SetBucketMetadataCallback): void;
+ removeRetentionPeriod(
+ options: SetBucketMetadataOptions,
+ callback: SetBucketMetadataCallback
+ ): void;
+ /**
+ * Remove an already-existing retention policy from this bucket, if it is not
+ * locked.
+ *
+ * @param {SetBucketMetadataCallback} [callback] Callback function.
+ * @param {SetBucketMetadataOptions} [options] Options, including precondition options
+ * @returns {Promise}
+ *
+ * @example
+ * ```
+ * const storage = require('@google-cloud/storage')();
+ * const bucket = storage.bucket('albums');
+ *
+ * bucket.removeRetentionPeriod(function(err, apiResponse) {});
+ *
+ * //-
+ * // If the callback is omitted, we'll return a Promise.
+ * //-
+ * bucket.removeRetentionPeriod().then(function(data) {
+ * const apiResponse = data[0];
+ * });
+ * ```
+ */
+ removeRetentionPeriod(
+ optionsOrCallback?: SetBucketMetadataOptions | SetBucketMetadataCallback,
+ callback?: SetBucketMetadataCallback
+ ): Promise | void {
+ const options =
+ typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
+ callback =
+ typeof optionsOrCallback === 'function' ? optionsOrCallback : callback;
+
+ this.setMetadata(
+ {
+ retentionPolicy: null,
+ },
+ options,
+ callback!
+ );
+ }
+
+ request(reqOpts: DecorateRequestOptions): Promise;
+ request(
+ reqOpts: DecorateRequestOptions,
+ callback: BodyResponseCallback
+ ): void;
+ /**
+ * Makes request and applies userProject query parameter if necessary.
+ *
+ * @private
+ *
+ * @param {object} reqOpts - The request options.
+ * @param {function} callback - The callback function.
+ */
+ request(
+ reqOpts: DecorateRequestOptions,
+ callback?: BodyResponseCallback
+ ): void | Promise {
+ if (this.userProject && (!reqOpts.qs || !reqOpts.qs.userProject)) {
+ reqOpts.qs = {...reqOpts.qs, userProject: this.userProject};
+ }
+ return super.request(reqOpts, callback!);
+ }
+
+ setLabels(
+ labels: Labels,
+ options?: SetLabelsOptions
+ ): Promise;
+ setLabels(labels: Labels, callback: SetLabelsCallback): void;
+ setLabels(
+ labels: Labels,
+ options: SetLabelsOptions,
+ callback: SetLabelsCallback
+ ): void;
+ /**
+ * @deprecated
+ * @typedef {array} SetLabelsResponse
+ * @property {object} 0 The bucket metadata.
+ */
+ /**
+ * @deprecated
+ * @callback SetLabelsCallback
+ * @param {?Error} err Request error, if any.
+ * @param {object} metadata The bucket metadata.
+ */
+ /**
+ * @deprecated
+ * @typedef {object} SetLabelsOptions Configuration options for Bucket#setLabels().
+ * @property {string} [userProject] The ID of the project which will be
+ * billed for the request.
+ */
+ /**
+ * @deprecated Use setMetadata directly.
+ * Set labels on the bucket.
+ *
+ * This makes an underlying call to {@link Bucket#setMetadata}, which
+ * is a PATCH request. This means an individual label can be overwritten, but
+ * unmentioned labels will not be touched.
+ *
+ * @param {object} labels Labels to set on the bucket.
+ * @param {SetLabelsOptions} [options] Configuration options.
+ * @param {string} [options.userProject] The ID of the project which will be
+ * billed for the request.
+ * @param {SetLabelsCallback} [callback] Callback function.
+ * @returns {Promise}
+ *
+ * @example
+ * ```
+ * const {Storage} = require('@google-cloud/storage');
+ * const storage = new Storage();
+ * const bucket = storage.bucket('albums');
+ *
+ * const labels = {
+ * labelone: 'labelonevalue',
+ * labeltwo: 'labeltwovalue'
+ * };
+ *
+ * bucket.setLabels(labels, function(err, metadata) {
+ * if (!err) {
+ * // Labels set successfully.
+ * }
+ * });
+ *
+ * //-
+ * // If the callback is omitted, we'll return a Promise.
+ * //-
+ * bucket.setLabels(labels).then(function(data) {
+ * const metadata = data[0];
+ * });
+ * ```
+ */
+ setLabels(
+ labels: Labels,
+ optionsOrCallback?: SetLabelsOptions | SetLabelsCallback,
+ callback?: SetLabelsCallback
+ ): Promise | void {
+ const options =
+ typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
+ callback =
+ typeof optionsOrCallback === 'function' ? optionsOrCallback : callback;
+
+ callback = callback || util.noop;
+
+ this.setMetadata({labels}, options, callback);
+ }
+
+ setMetadata(
+ metadata: BucketMetadata,
+ options?: SetMetadataOptions
+ ): Promise>;
+ setMetadata(
+ metadata: BucketMetadata,
+ callback: MetadataCallback
+ ): void;
+ setMetadata(
+ metadata: BucketMetadata,
+ options: SetMetadataOptions,
+ callback: MetadataCallback
+ ): void;
+ setMetadata(
+ metadata: BucketMetadata,
+ optionsOrCallback: SetMetadataOptions | MetadataCallback,
+ cb?: MetadataCallback
+ ): Promise> | void {
+ const options =
+ typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
+ cb =
+ typeof optionsOrCallback === 'function'
+ ? (optionsOrCallback as MetadataCallback)
+ : cb;
+
+ this.disableAutoRetryConditionallyIdempotent_(
+ this.methods.setMetadata,
+ AvailableServiceObjectMethods.setMetadata,
+ options
+ );
+
+ super
+ .setMetadata(metadata, options)
+ .then(resp => cb!(null, ...resp))
+ .catch(cb!)
+ .finally(() => {
+ this.storage.retryOptions.autoRetry = this.instanceRetryValue;
+ });
+ }
+
+ setRetentionPeriod(
+ duration: number,
+ options?: SetBucketMetadataOptions
+ ): Promise;
+ setRetentionPeriod(
+ duration: number,
+ callback: SetBucketMetadataCallback
+ ): void;
+ setRetentionPeriod(
+ duration: number,
+ options: SetBucketMetadataOptions,
+ callback: SetBucketMetadataCallback
+ ): void;
+ /**
+ * Lock all objects contained in the bucket, based on their creation time. Any
+ * attempt to overwrite or delete objects younger than the retention period
+ * will result in a `PERMISSION_DENIED` error.
+ *
+ * An unlocked retention policy can be modified or removed from the bucket via
+ * {@link File#removeRetentionPeriod} and {@link File#setRetentionPeriod}. A
+ * locked retention policy cannot be removed or shortened in duration for the
+ * lifetime of the bucket. Attempting to remove or decrease period of a locked
+ * retention policy will result in a `PERMISSION_DENIED` error. You can still
+ * increase the policy.
+ *
+ * @param {*} duration In seconds, the minimum retention time for all objects
+ * contained in this bucket.
+ * @param {SetBucketMetadataCallback} [callback] Callback function.
+ * @param {SetBucketMetadataCallback} [options] Options, including precondition options.
+ * @returns {Promise}
+ *
+ * @example
+ * ```
+ * const storage = require('@google-cloud/storage')();
+ * const bucket = storage.bucket('albums');
+ *
+ * const DURATION_SECONDS = 15780000; // 6 months.
+ *
+ * //-
+ * // Lock the objects in this bucket for 6 months.
+ * //-
+ * bucket.setRetentionPeriod(DURATION_SECONDS, function(err, apiResponse) {});
+ *
+ * //-
+ * // If the callback is omitted, we'll return a Promise.
+ * //-
+ * bucket.setRetentionPeriod(DURATION_SECONDS).then(function(data) {
+ * const apiResponse = data[0];
+ * });
+ * ```
+ */
+ setRetentionPeriod(
+ duration: number,
+ optionsOrCallback?: SetBucketMetadataOptions | SetBucketMetadataCallback,
+ callback?: SetBucketMetadataCallback
+ ): Promise | void {
+ const options =
+ typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
+ callback =
+ typeof optionsOrCallback === 'function' ? optionsOrCallback : callback;
+ this.setMetadata(
+ {
+ retentionPolicy: {
+ retentionPeriod: duration.toString(),
+ },
+ },
+ options,
+ callback!
+ );
+ }
+
+ setCorsConfiguration(
+ corsConfiguration: Cors[],
+ options?: SetBucketMetadataOptions
+ ): Promise;
+ setCorsConfiguration(
+ corsConfiguration: Cors[],
+ callback: SetBucketMetadataCallback
+ ): void;
+ setCorsConfiguration(
+ corsConfiguration: Cors[],
+ options: SetBucketMetadataOptions,
+ callback: SetBucketMetadataCallback
+ ): void;
+ /**
+ *
+ * @typedef {object} Cors
+ * @property {number} [maxAgeSeconds] The number of seconds the browser is
+ * allowed to make requests before it must repeat the preflight request.
+ * @property {string[]} [method] HTTP method allowed for cross origin resource
+ * sharing with this bucket.
+ * @property {string[]} [origin] an origin allowed for cross origin resource
+ * sharing with this bucket.
+ * @property {string[]} [responseHeader] A header allowed for cross origin
+ * resource sharing with this bucket.
+ */
+ /**
+ * This can be used to set the CORS configuration on the bucket.
+ *
+ * The configuration will be overwritten with the value passed into this.
+ *
+ * @param {Cors[]} corsConfiguration The new CORS configuration to set
+ * @param {number} [corsConfiguration.maxAgeSeconds] The number of seconds the browser is
+ * allowed to make requests before it must repeat the preflight request.
+ * @param {string[]} [corsConfiguration.method] HTTP method allowed for cross origin resource
+ * sharing with this bucket.
+ * @param {string[]} [corsConfiguration.origin] an origin allowed for cross origin resource
+ * sharing with this bucket.
+ * @param {string[]} [corsConfiguration.responseHeader] A header allowed for cross origin
+ * resource sharing with this bucket.
+ * @param {SetBucketMetadataCallback} [callback] Callback function.
+ * @param {SetBucketMetadataOptions} [options] Options, including precondition options.
+ * @returns {Promise}
+ *
+ * @example
+ * ```
+ * const storage = require('@google-cloud/storage')();
+ * const bucket = storage.bucket('albums');
+ *
+ * const corsConfiguration = [{maxAgeSeconds: 3600}]; // 1 hour
+ * bucket.setCorsConfiguration(corsConfiguration);
+ *
+ * //-
+ * // If the callback is omitted, we'll return a Promise.
+ * //-
+ * bucket.setCorsConfiguration(corsConfiguration).then(function(data) {
+ * const apiResponse = data[0];
+ * });
+ * ```
+ */
+ setCorsConfiguration(
+ corsConfiguration: Cors[],
+ optionsOrCallback?: SetBucketMetadataOptions | SetBucketMetadataCallback,
+ callback?: SetBucketMetadataCallback
+ ): Promise | void {
+ const options =
+ typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
+ callback =
+ typeof optionsOrCallback === 'function' ? optionsOrCallback : callback;
+ this.setMetadata(
+ {
+ cors: corsConfiguration,
+ },
+ options,
+ callback!
+ );
+ }
+
+ setStorageClass(
+ storageClass: string,
+ options?: SetBucketStorageClassOptions
+ ): Promise;
+ setStorageClass(
+ storageClass: string,
+ callback: SetBucketStorageClassCallback
+ ): void;
+ setStorageClass(
+ storageClass: string,
+ options: SetBucketStorageClassOptions,
+ callback: SetBucketStorageClassCallback
+ ): void;
+ /**
+ * @typedef {object} SetBucketStorageClassOptions
+ * @property {string} [userProject] - The ID of the project which will be
+ * billed for the request.
+ */
+ /**
+ * @callback SetBucketStorageClassCallback
+ * @param {?Error} err Request error, if any.
+ */
+ /**
+ * Set the default storage class for new files in this bucket.
+ *
+ * See {@link https://cloud.google.com/storage/docs/storage-classes| Storage Classes}
+ *
+ * @param {string} storageClass The new storage class. (`standard`,
+ * `nearline`, `coldline`, or `archive`).
+ * **Note:** The storage classes `multi_regional`, `regional`, and
+ * `durable_reduced_availability` are now legacy and will be deprecated in
+ * the future.
+ * @param {object} [options] Configuration options.
+ * @param {string} [options.userProject] - The ID of the project which will be
+ * billed for the request.
+ * @param {SetStorageClassCallback} [callback] Callback function.
+ * @returns {Promise}
+ *
+ * @example
+ * ```
+ * const {Storage} = require('@google-cloud/storage');
+ * const storage = new Storage();
+ * const bucket = storage.bucket('albums');
+ *
+ * bucket.setStorageClass('nearline', function(err, apiResponse) {
+ * if (err) {
+ * // Error handling omitted.
+ * }
+ *
+ * // The storage class was updated successfully.
+ * });
+ *
+ * //-
+ * // If the callback is omitted, we'll return a Promise.
+ * //-
+ * bucket.setStorageClass('nearline').then(function() {});
+ * ```
+ */
+ setStorageClass(
+ storageClass: string,
+ optionsOrCallback?:
+ | SetBucketStorageClassOptions
+ | SetBucketStorageClassCallback,
+ callback?: SetBucketStorageClassCallback
+ ): Promise | void {
+ const options =
+ typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
+ callback =
+ typeof optionsOrCallback === 'function' ? optionsOrCallback : callback;
+
+ // In case we get input like `storageClass`, convert to `storage_class`.
+ storageClass = storageClass
+ .replace(/-/g, '_')
+ .replace(/([a-z])([A-Z])/g, (_, low, up) => {
+ return low + '_' + up;
+ })
+ .toUpperCase();
+
+ this.setMetadata({storageClass}, options, callback!);
+ }
+
+ /**
+ * Set a user project to be billed for all requests made from this Bucket
+ * object and any files referenced from this Bucket object.
+ *
+ * @param {string} userProject The user project.
+ *
+ * @example
+ * ```
+ * const {Storage} = require('@google-cloud/storage');
+ * const storage = new Storage();
+ * const bucket = storage.bucket('albums');
+ *
+ * bucket.setUserProject('grape-spaceship-123');
+ * ```
+ */
+ setUserProject(userProject: string) {
+ this.userProject = userProject;
+
+ const methods = [
+ 'create',
+ 'delete',
+ 'exists',
+ 'get',
+ 'getMetadata',
+ 'setMetadata',
+ ];
+ methods.forEach(method => {
+ const methodConfig = this.methods[method];
+ if (typeof methodConfig === 'object') {
+ if (typeof methodConfig.reqOpts === 'object') {
+ Object.assign(methodConfig.reqOpts.qs, {userProject});
+ } else {
+ methodConfig.reqOpts = {
+ qs: {userProject},
+ };
+ }
+ }
+ });
+ }
+
+ upload(pathString: string, options?: UploadOptions): Promise;
+ upload(
+ pathString: string,
+ options: UploadOptions,
+ callback: UploadCallback
+ ): void;
+ upload(pathString: string, callback: UploadCallback): void;
+ /**
+ * @typedef {object} UploadOptions Configuration options for Bucket#upload().
+ * @property {string|File} [destination] The place to save
+ * your file. If given a string, the file will be uploaded to the bucket
+ * using the string as a filename. When given a File object, your local
+ * file will be uploaded to the File object's bucket and under the File
+ * object's name. Lastly, when this argument is omitted, the file is uploaded
+ * to your bucket using the name of the local file.
+ * @property {string} [encryptionKey] A custom encryption key. See
+ * {@link https://cloud.google.com/storage/docs/encryption#customer-supplied| Customer-supplied Encryption Keys}.
+ * @property {boolean} [gzip] Automatically gzip the file. This will set
+ * `options.metadata.contentEncoding` to `gzip`.
+ * @property {string} [kmsKeyName] The name of the Cloud KMS key that will
+ * be used to encrypt the object. Must be in the format:
+ * `projects/my-project/locations/location/keyRings/my-kr/cryptoKeys/my-key`.
+ * @property {object} [metadata] See an
+ * {@link https://cloud.google.com/storage/docs/json_api/v1/objects/insert#request_properties_JSON| Objects: insert request body}.
+ * @property {string} [offset] The starting byte of the upload stream, for
+ * resuming an interrupted upload. Defaults to 0.
+ * @property {string} [predefinedAcl] Apply a predefined set of access
+ * controls to this object.
+ *
+ * Acceptable values are:
+ * - **`authenticatedRead`** - Object owner gets `OWNER` access, and
+ * `allAuthenticatedUsers` get `READER` access.
+ *
+ * - **`bucketOwnerFullControl`** - Object owner gets `OWNER` access, and
+ * project team owners get `OWNER` access.
+ *
+ * - **`bucketOwnerRead`** - Object owner gets `OWNER` access, and project
+ * team owners get `READER` access.
+ *
+ * - **`private`** - Object owner gets `OWNER` access.
+ *
+ * - **`projectPrivate`** - Object owner gets `OWNER` access, and project
+ * team members get access according to their roles.
+ *
+ * - **`publicRead`** - Object owner gets `OWNER` access, and `allUsers`
+ * get `READER` access.
+ * @property {boolean} [private] Make the uploaded file private. (Alias for
+ * `options.predefinedAcl = 'private'`)
+ * @property {boolean} [public] Make the uploaded file public. (Alias for
+ * `options.predefinedAcl = 'publicRead'`)
+ * @property {boolean} [resumable=true] Resumable uploads are automatically
+ * enabled and must be shut off explicitly by setting to false.
+ * @property {number} [timeout=60000] Set the HTTP request timeout in
+ * milliseconds. This option is not available for resumable uploads.
+ * Default: `60000`
+ * @property {string} [uri] The URI for an already-created resumable
+ * upload. See {@link File#createResumableUpload}.
+ * @property {string} [userProject] The ID of the project which will be
+ * billed for the request.
+ * @property {string|boolean} [validation] Possible values: `"md5"`,
+ * `"crc32c"`, or `false`. By default, data integrity is validated with an
+ * MD5 checksum for maximum reliability. CRC32c will provide better
+ * performance with less reliability. You may also choose to skip
+ * validation completely, however this is **not recommended**.
+ */
+ /**
+ * @typedef {array} UploadResponse
+ * @property {object} 0 The uploaded {@link File}.
+ * @property {object} 1 The full API response.
+ */
+ /**
+ * @callback UploadCallback
+ * @param {?Error} err Request error, if any.
+ * @param {object} file The uploaded {@link File}.
+ * @param {object} apiResponse The full API response.
+ */
+ /**
+ * Upload a file to the bucket. This is a convenience method that wraps
+ * {@link File#createWriteStream}.
+ *
+ * Resumable uploads are enabled by default
+ *
+ * See {@link https://cloud.google.com/storage/docs/json_api/v1/how-tos/upload#uploads| Upload Options (Simple or Resumable)}
+ * See {@link https://cloud.google.com/storage/docs/json_api/v1/objects/insert| Objects: insert API Documentation}
+ *
+ * @param {string} pathString The fully qualified path to the file you
+ * wish to upload to your bucket.
+ * @param {UploadOptions} [options] Configuration options.
+ * @param {string|File} [options.destination] The place to save
+ * your file. If given a string, the file will be uploaded to the bucket
+ * using the string as a filename. When given a File object, your local
+ * file will be uploaded to the File object's bucket and under the File
+ * object's name. Lastly, when this argument is omitted, the file is uploaded
+ * to your bucket using the name of the local file.
+ * @param {string} [options.encryptionKey] A custom encryption key. See
+ * {@link https://cloud.google.com/storage/docs/encryption#customer-supplied| Customer-supplied Encryption Keys}.
+ * @param {boolean} [options.gzip] Automatically gzip the file. This will set
+ * `options.metadata.contentEncoding` to `gzip`.
+ * @param {string} [options.kmsKeyName] The name of the Cloud KMS key that will
+ * be used to encrypt the object. Must be in the format:
+ * `projects/my-project/locations/location/keyRings/my-kr/cryptoKeys/my-key`.
+ * @param {object} [options.metadata] See an
+ * {@link https://cloud.google.com/storage/docs/json_api/v1/objects/insert#request_properties_JSON| Objects: insert request body}.
+ * @param {string} [options.offset] The starting byte of the upload stream, for
+ * resuming an interrupted upload. Defaults to 0.
+ * @param {string} [options.predefinedAcl] Apply a predefined set of access
+ * controls to this object.
+ * Acceptable values are:
+ * - **`authenticatedRead`** - Object owner gets `OWNER` access, and
+ * `allAuthenticatedUsers` get `READER` access.
+ *
+ * - **`bucketOwnerFullControl`** - Object owner gets `OWNER` access, and
+ * project team owners get `OWNER` access.
+ *
+ * - **`bucketOwnerRead`** - Object owner gets `OWNER` access, and project
+ * team owners get `READER` access.
+ *
+ * - **`private`** - Object owner gets `OWNER` access.
+ *
+ * - **`projectPrivate`** - Object owner gets `OWNER` access, and project
+ * team members get access according to their roles.
+ *
+ * - **`publicRead`** - Object owner gets `OWNER` access, and `allUsers`
+ * get `READER` access.
+ * @param {boolean} [options.private] Make the uploaded file private. (Alias for
+ * `options.predefinedAcl = 'private'`)
+ * @param {boolean} [options.public] Make the uploaded file public. (Alias for
+ * `options.predefinedAcl = 'publicRead'`)
+ * @param {boolean} [options.resumable=true] Resumable uploads are automatically
+ * enabled and must be shut off explicitly by setting to false.
+ * @param {number} [options.timeout=60000] Set the HTTP request timeout in
+ * milliseconds. This option is not available for resumable uploads.
+ * Default: `60000`
+ * @param {string} [options.uri] The URI for an already-created resumable
+ * upload. See {@link File#createResumableUpload}.
+ * @param {string} [options.userProject] The ID of the project which will be
+ * billed for the request.
+ * @param {string|boolean} [options.validation] Possible values: `"md5"`,
+ * `"crc32c"`, or `false`. By default, data integrity is validated with an
+ * MD5 checksum for maximum reliability. CRC32c will provide better
+ * performance with less reliability. You may also choose to skip
+ * validation completely, however this is **not recommended**.
+ * @param {UploadCallback} [callback] Callback function.
+ * @returns {Promise}
+ *
+ * @example
+ * ```
+ * const {Storage} = require('@google-cloud/storage');
+ * const storage = new Storage();
+ * const bucket = storage.bucket('albums');
+ *
+ * //-
+ * // Upload a file from a local path.
+ * //-
+ * bucket.upload('/local/path/image.png', function(err, file, apiResponse) {
+ * // Your bucket now contains:
+ * // - "image.png" (with the contents of `/local/path/image.png')
+ *
+ * // `file` is an instance of a File object that refers to your new file.
+ * });
+ *
+ *
+ * //-
+ * // It's not always that easy. You will likely want to specify the filename
+ * // used when your new file lands in your bucket.
+ * //
+ * // You may also want to set metadata or customize other options.
+ * //-
+ * const options = {
+ * destination: 'new-image.png',
+ * validation: 'crc32c',
+ * metadata: {
+ * metadata: {
+ * event: 'Fall trip to the zoo'
+ * }
+ * }
+ * };
+ *
+ * bucket.upload('local-image.png', options, function(err, file) {
+ * // Your bucket now contains:
+ * // - "new-image.png" (with the contents of `local-image.png')
+ *
+ * // `file` is an instance of a File object that refers to your new file.
+ * });
+ *
+ * //-
+ * // You can also have a file gzip'd on the fly.
+ * //-
+ * bucket.upload('index.html', { gzip: true }, function(err, file) {
+ * // Your bucket now contains:
+ * // - "index.html" (automatically compressed with gzip)
+ *
+ * // Downloading the file with `file.download` will automatically decode
+ * the
+ * // file.
+ * });
+ *
+ * //-
+ * // You may also re-use a File object, {File}, that references
+ * // the file you wish to create or overwrite.
+ * //-
+ * const options = {
+ * destination: bucket.file('existing-file.png'),
+ * resumable: false
+ * };
+ *
+ * bucket.upload('local-img.png', options, function(err, newFile) {
+ * // Your bucket now contains:
+ * // - "existing-file.png" (with the contents of `local-img.png')
+ *
+ * // Note:
+ * // The `newFile` parameter is equal to `file`.
+ * });
+ *
+ * //-
+ * // To use
+ * //
+ * // Customer-supplied Encryption Keys, provide the `encryptionKey`
+ * option.
+ * //-
+ * const crypto = require('crypto');
+ * const encryptionKey = crypto.randomBytes(32);
+ *
+ * bucket.upload('img.png', {
+ * encryptionKey: encryptionKey
+ * }, function(err, newFile) {
+ * // `img.png` was uploaded with your custom encryption key.
+ *
+ * // `newFile` is already configured to use the encryption key when making
+ * // operations on the remote object.
+ *
+ * // However, to use your encryption key later, you must create a `File`
+ * // instance with the `key` supplied:
+ * const file = bucket.file('img.png', {
+ * encryptionKey: encryptionKey
+ * });
+ *
+ * // Or with `file#setEncryptionKey`:
+ * const file = bucket.file('img.png');
+ * file.setEncryptionKey(encryptionKey);
+ * });
+ *
+ * //-
+ * // If the callback is omitted, we'll return a Promise.
+ * //-
+ * bucket.upload('local-image.png').then(function(data) {
+ * const file = data[0];
+ * });
+ *
+ * To upload a file from a URL, use {@link File#createWriteStream}.
+ *
+ * ```
+ * @example include:samples/files.js
+ * region_tag:storage_upload_file
+ * Another example:
+ *
+ * @example include:samples/encryption.js
+ * region_tag:storage_upload_encrypted_file
+ * Example of uploading an encrypted file:
+ */
+ upload(
+ pathString: string,
+ optionsOrCallback?: UploadOptions | UploadCallback,
+ callback?: UploadCallback
+ ): Promise | void {
+ const upload = (numberOfRetries: number | undefined) => {
+ const returnValue = AsyncRetry(
+ async (bail: (err: Error) => void) => {
+ await new Promise((resolve, reject) => {
+ if (
+ numberOfRetries === 0 &&
+ newFile?.storage?.retryOptions?.autoRetry
+ ) {
+ newFile.storage.retryOptions.autoRetry = false;
+ }
+ const writable = newFile.createWriteStream(options);
+ if (options.onUploadProgress) {
+ writable.on('progress', options.onUploadProgress);
+ }
+ fs.createReadStream(pathString)
+ .on('error', bail)
+ .pipe(writable)
+ .on('error', err => {
+ if (
+ this.storage.retryOptions.autoRetry &&
+ this.storage.retryOptions.retryableErrorFn!(err)
+ ) {
+ return reject(err);
+ } else {
+ return bail(err);
+ }
+ })
+ .on('finish', () => {
+ return resolve();
+ });
+ });
+ },
+ {
+ retries: numberOfRetries,
+ factor: this.storage.retryOptions.retryDelayMultiplier,
+ maxTimeout: this.storage.retryOptions.maxRetryDelay! * 1000, //convert to milliseconds
+ maxRetryTime: this.storage.retryOptions.totalTimeout! * 1000, //convert to milliseconds
+ }
+ );
+
+ if (!callback) {
+ return returnValue;
+ } else {
+ return returnValue
+ .then(() => {
+ if (callback) {
+ return callback!(null, newFile, newFile.metadata);
+ }
+ })
+ .catch(callback);
+ }
+ };
+
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ if ((global as any)['GCLOUD_SANDBOX_ENV']) {
+ return;
+ }
+
+ let options =
+ typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
+ callback =
+ typeof optionsOrCallback === 'function' ? optionsOrCallback : callback;
+
+ options = Object.assign(
+ {
+ metadata: {},
+ },
+ options
+ );
+
+ // Do not retry if precondition option ifGenerationMatch is not set
+ // because this is a file operation
+ let maxRetries = this.storage.retryOptions.maxRetries;
+ if (
+ (options?.preconditionOpts?.ifGenerationMatch === undefined &&
+ this.instancePreconditionOpts?.ifGenerationMatch === undefined &&
+ this.storage.retryOptions.idempotencyStrategy ===
+ IdempotencyStrategy.RetryConditional) ||
+ this.storage.retryOptions.idempotencyStrategy ===
+ IdempotencyStrategy.RetryNever
+ ) {
+ maxRetries = 0;
+ }
+
+ let newFile: File;
+ if (options.destination instanceof File) {
+ newFile = options.destination;
+ } else if (
+ options.destination !== null &&
+ typeof options.destination === 'string'
+ ) {
+ // Use the string as the name of the file.
+ newFile = this.file(options.destination, {
+ encryptionKey: options.encryptionKey,
+ kmsKeyName: options.kmsKeyName,
+ preconditionOpts: this.instancePreconditionOpts,
+ });
+ } else {
+ // Resort to using the name of the incoming file.
+ const destination = path.basename(pathString);
+ newFile = this.file(destination, {
+ encryptionKey: options.encryptionKey,
+ kmsKeyName: options.kmsKeyName,
+ preconditionOpts: this.instancePreconditionOpts,
+ });
+ }
+
+ upload(maxRetries);
+ }
+
+ makeAllFilesPublicPrivate_(
+ options?: MakeAllFilesPublicPrivateOptions
+ ): Promise;
+ makeAllFilesPublicPrivate_(callback: MakeAllFilesPublicPrivateCallback): void;
+ makeAllFilesPublicPrivate_(
+ options: MakeAllFilesPublicPrivateOptions,
+ callback: MakeAllFilesPublicPrivateCallback
+ ): void;
+ /**
+ * @private
+ *
+ * @typedef {object} MakeAllFilesPublicPrivateOptions
+ * @property {boolean} [force] Suppress errors until all files have been
+ * processed.
+ * @property {boolean} [private] Make files private.
+ * @property {boolean} [public] Make files public.
+ * @property {string} [userProject] The ID of the project which will be
+ * billed for the request.
+ */
+ /**
+ * @private
+ *
+ * @callback SetBucketMetadataCallback
+ * @param {?Error} err Request error, if any.
+ * @param {File[]} files Files that were updated.
+ */
+ /**
+ * @typedef {array} MakeAllFilesPublicPrivateResponse
+ * @property {File[]} 0 List of files affected.
+ */
+ /**
+ * Iterate over all of a bucket's files, calling `file.makePublic()` (public)
+ * or `file.makePrivate()` (private) on each.
+ *
+ * Operations are performed in parallel, up to 10 at once. The first error
+ * breaks the loop, and will execute the provided callback with it. Specify
+ * `{ force: true }` to suppress the errors.
+ *
+ * @private
+ *
+ * @param {MakeAllFilesPublicPrivateOptions} [options] Configuration options.
+ * @param {boolean} [options.force] Suppress errors until all files have been
+ * processed.
+ * @param {boolean} [options.private] Make files private.
+ * @param {boolean} [options.public] Make files public.
+ * @param {string} [options.userProject] The ID of the project which will be
+ * billed for the request.
+
+ * @param {MakeAllFilesPublicPrivateCallback} callback Callback function.
+ *
+ * @return {Promise}
+ */
+ makeAllFilesPublicPrivate_(
+ optionsOrCallback?:
+ | MakeAllFilesPublicPrivateOptions
+ | MakeAllFilesPublicPrivateCallback,
+ callback?: MakeAllFilesPublicPrivateCallback
+ ): Promise | void {
+ const MAX_PARALLEL_LIMIT = 10;
+ const errors = [] as Error[];
+ const updatedFiles = [] as File[];
+
+ const options =
+ typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
+ callback =
+ typeof optionsOrCallback === 'function' ? optionsOrCallback : callback;
+
+ const processFile = async (file: File) => {
+ try {
+ await (options.public ? file.makePublic() : file.makePrivate(options));
+ updatedFiles.push(file);
+ } catch (e) {
+ if (!options.force) {
+ throw e;
+ }
+ errors.push(e as Error);
+ }
+ };
+
+ this.getFiles(options)
+ .then(([files]) => {
+ const limit = pLimit(MAX_PARALLEL_LIMIT);
+ const promises = files.map(file => {
+ return limit(() => processFile(file));
+ });
+ return Promise.all(promises);
+ })
+ .then(
+ () => callback!(errors.length > 0 ? errors : null, updatedFiles),
+ err => callback!(err, updatedFiles)
+ );
+ }
+
+ getId(): string {
+ return this.id!;
+ }
+
+ disableAutoRetryConditionallyIdempotent_(
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ coreOpts: any,
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ methodType: AvailableServiceObjectMethods,
+ localPreconditionOptions?: PreconditionOptions
+ ): void {
+ if (
+ typeof coreOpts === 'object' &&
+ coreOpts?.reqOpts?.qs?.ifMetagenerationMatch === undefined &&
+ localPreconditionOptions?.ifMetagenerationMatch === undefined &&
+ (methodType === AvailableServiceObjectMethods.setMetadata ||
+ methodType === AvailableServiceObjectMethods.delete) &&
+ this.storage.retryOptions.idempotencyStrategy ===
+ IdempotencyStrategy.RetryConditional
+ ) {
+ this.storage.retryOptions.autoRetry = false;
+ } else if (
+ this.storage.retryOptions.idempotencyStrategy ===
+ IdempotencyStrategy.RetryNever
+ ) {
+ this.storage.retryOptions.autoRetry = false;
+ }
+ }
+}
+
+/*! Developer Documentation
+ *
+ * These methods can be auto-paginated.
+ */
+paginator.extend(Bucket, 'getFiles');
+
+/*! Developer Documentation
+ *
+ * All async methods (except for streams) will return a Promise in the event
+ * that a callback is omitted.
+ */
+promisifyAll(Bucket, {
+ exclude: ['cloudStorageURI', 'request', 'file', 'notification', 'restore'],
+});
+
+/**
+ * Reference to the {@link Bucket} class.
+ * @name module:@google-cloud/storage.Bucket
+ * @see Bucket
+ */
+export {Bucket};
diff --git a/handwritten/storage/src/channel.ts b/handwritten/storage/src/channel.ts
new file mode 100644
index 00000000000..ee0c10984b4
--- /dev/null
+++ b/handwritten/storage/src/channel.ts
@@ -0,0 +1,126 @@
+// Copyright 2019 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.
+
+import {BaseMetadata, ServiceObject, util} from './nodejs-common/index.js';
+import {promisifyAll} from '@google-cloud/promisify';
+
+import {Storage} from './storage.js';
+
+export interface StopCallback {
+ (err: Error | null, apiResponse?: unknown): void;
+}
+
+/**
+ * Create a channel object to interact with a Cloud Storage channel.
+ *
+ * See {@link https://cloud.google.com/storage/docs/object-change-notification| Object Change Notification}
+ *
+ * @class
+ *
+ * @param {string} id The ID of the channel.
+ * @param {string} resourceId The resource ID of the channel.
+ *
+ * @example
+ * ```
+ * const {Storage} = require('@google-cloud/storage');
+ * const storage = new Storage();
+ * const channel = storage.channel('id', 'resource-id');
+ * ```
+ */
+class Channel extends ServiceObject {
+ constructor(storage: Storage, id: string, resourceId: string) {
+ const config = {
+ parent: storage,
+ baseUrl: '/channels',
+
+ // An ID shouldn't be included in the API requests.
+ // RE:
+ // https://github.com/GoogleCloudPlatform/google-cloud-node/issues/1145
+ id: '',
+
+ methods: {
+ // Only need `request`.
+ },
+ };
+
+ super(config);
+
+ this.metadata.id = id;
+ this.metadata.resourceId = resourceId;
+ }
+
+ stop(): Promise;
+ stop(callback: StopCallback): void;
+ /**
+ * @typedef {array} StopResponse
+ * @property {object} 0 The full API response.
+ */
+ /**
+ * @callback StopCallback
+ * @param {?Error} err Request error, if any.
+ * @param {object} apiResponse The full API response.
+ */
+ /**
+ * Stop this channel.
+ *
+ * @param {StopCallback} [callback] Callback function.
+ * @returns {Promise}
+ *
+ * @example
+ * ```
+ * const {Storage} = require('@google-cloud/storage');
+ * const storage = new Storage();
+ * const channel = storage.channel('id', 'resource-id');
+ * channel.stop(function(err, apiResponse) {
+ * if (!err) {
+ * // Channel stopped successfully.
+ * }
+ * });
+ *
+ * //-
+ * // If the callback is omitted, we'll return a Promise.
+ * //-
+ * channel.stop().then(function(data) {
+ * const apiResponse = data[0];
+ * });
+ * ```
+ */
+ stop(callback?: StopCallback): Promise | void {
+ callback = callback || util.noop;
+ this.request(
+ {
+ method: 'POST',
+ uri: '/stop',
+ json: this.metadata,
+ },
+ (err, apiResponse) => {
+ callback!(err, apiResponse);
+ }
+ );
+ }
+}
+
+/*! Developer Documentation
+ *
+ * All async methods (except for streams) will return a Promise in the event
+ * that a callback is omitted.
+ */
+promisifyAll(Channel);
+
+/**
+ * Reference to the {@link Channel} class.
+ * @name module:@google-cloud/storage.Channel
+ * @see Channel
+ */
+export {Channel};
diff --git a/handwritten/storage/src/crc32c.ts b/handwritten/storage/src/crc32c.ts
new file mode 100644
index 00000000000..929cc1d5a95
--- /dev/null
+++ b/handwritten/storage/src/crc32c.ts
@@ -0,0 +1,339 @@
+// Copyright 2022 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.
+
+import {PathLike, createReadStream} from 'fs';
+
+/**
+ * Ported from {@link https://github.com/google/crc32c/blob/21fc8ef30415a635e7351ffa0e5d5367943d4a94/src/crc32c_portable.cc#L16-L59 github.com/google/crc32c}
+ */
+const CRC32C_EXTENSIONS = [
+ 0x00000000, 0xf26b8303, 0xe13b70f7, 0x1350f3f4, 0xc79a971f, 0x35f1141c,
+ 0x26a1e7e8, 0xd4ca64eb, 0x8ad958cf, 0x78b2dbcc, 0x6be22838, 0x9989ab3b,
+ 0x4d43cfd0, 0xbf284cd3, 0xac78bf27, 0x5e133c24, 0x105ec76f, 0xe235446c,
+ 0xf165b798, 0x030e349b, 0xd7c45070, 0x25afd373, 0x36ff2087, 0xc494a384,
+ 0x9a879fa0, 0x68ec1ca3, 0x7bbcef57, 0x89d76c54, 0x5d1d08bf, 0xaf768bbc,
+ 0xbc267848, 0x4e4dfb4b, 0x20bd8ede, 0xd2d60ddd, 0xc186fe29, 0x33ed7d2a,
+ 0xe72719c1, 0x154c9ac2, 0x061c6936, 0xf477ea35, 0xaa64d611, 0x580f5512,
+ 0x4b5fa6e6, 0xb93425e5, 0x6dfe410e, 0x9f95c20d, 0x8cc531f9, 0x7eaeb2fa,
+ 0x30e349b1, 0xc288cab2, 0xd1d83946, 0x23b3ba45, 0xf779deae, 0x05125dad,
+ 0x1642ae59, 0xe4292d5a, 0xba3a117e, 0x4851927d, 0x5b016189, 0xa96ae28a,
+ 0x7da08661, 0x8fcb0562, 0x9c9bf696, 0x6ef07595, 0x417b1dbc, 0xb3109ebf,
+ 0xa0406d4b, 0x522bee48, 0x86e18aa3, 0x748a09a0, 0x67dafa54, 0x95b17957,
+ 0xcba24573, 0x39c9c670, 0x2a993584, 0xd8f2b687, 0x0c38d26c, 0xfe53516f,
+ 0xed03a29b, 0x1f682198, 0x5125dad3, 0xa34e59d0, 0xb01eaa24, 0x42752927,
+ 0x96bf4dcc, 0x64d4cecf, 0x77843d3b, 0x85efbe38, 0xdbfc821c, 0x2997011f,
+ 0x3ac7f2eb, 0xc8ac71e8, 0x1c661503, 0xee0d9600, 0xfd5d65f4, 0x0f36e6f7,
+ 0x61c69362, 0x93ad1061, 0x80fde395, 0x72966096, 0xa65c047d, 0x5437877e,
+ 0x4767748a, 0xb50cf789, 0xeb1fcbad, 0x197448ae, 0x0a24bb5a, 0xf84f3859,
+ 0x2c855cb2, 0xdeeedfb1, 0xcdbe2c45, 0x3fd5af46, 0x7198540d, 0x83f3d70e,
+ 0x90a324fa, 0x62c8a7f9, 0xb602c312, 0x44694011, 0x5739b3e5, 0xa55230e6,
+ 0xfb410cc2, 0x092a8fc1, 0x1a7a7c35, 0xe811ff36, 0x3cdb9bdd, 0xceb018de,
+ 0xdde0eb2a, 0x2f8b6829, 0x82f63b78, 0x709db87b, 0x63cd4b8f, 0x91a6c88c,
+ 0x456cac67, 0xb7072f64, 0xa457dc90, 0x563c5f93, 0x082f63b7, 0xfa44e0b4,
+ 0xe9141340, 0x1b7f9043, 0xcfb5f4a8, 0x3dde77ab, 0x2e8e845f, 0xdce5075c,
+ 0x92a8fc17, 0x60c37f14, 0x73938ce0, 0x81f80fe3, 0x55326b08, 0xa759e80b,
+ 0xb4091bff, 0x466298fc, 0x1871a4d8, 0xea1a27db, 0xf94ad42f, 0x0b21572c,
+ 0xdfeb33c7, 0x2d80b0c4, 0x3ed04330, 0xccbbc033, 0xa24bb5a6, 0x502036a5,
+ 0x4370c551, 0xb11b4652, 0x65d122b9, 0x97baa1ba, 0x84ea524e, 0x7681d14d,
+ 0x2892ed69, 0xdaf96e6a, 0xc9a99d9e, 0x3bc21e9d, 0xef087a76, 0x1d63f975,
+ 0x0e330a81, 0xfc588982, 0xb21572c9, 0x407ef1ca, 0x532e023e, 0xa145813d,
+ 0x758fe5d6, 0x87e466d5, 0x94b49521, 0x66df1622, 0x38cc2a06, 0xcaa7a905,
+ 0xd9f75af1, 0x2b9cd9f2, 0xff56bd19, 0x0d3d3e1a, 0x1e6dcdee, 0xec064eed,
+ 0xc38d26c4, 0x31e6a5c7, 0x22b65633, 0xd0ddd530, 0x0417b1db, 0xf67c32d8,
+ 0xe52cc12c, 0x1747422f, 0x49547e0b, 0xbb3ffd08, 0xa86f0efc, 0x5a048dff,
+ 0x8ecee914, 0x7ca56a17, 0x6ff599e3, 0x9d9e1ae0, 0xd3d3e1ab, 0x21b862a8,
+ 0x32e8915c, 0xc083125f, 0x144976b4, 0xe622f5b7, 0xf5720643, 0x07198540,
+ 0x590ab964, 0xab613a67, 0xb831c993, 0x4a5a4a90, 0x9e902e7b, 0x6cfbad78,
+ 0x7fab5e8c, 0x8dc0dd8f, 0xe330a81a, 0x115b2b19, 0x020bd8ed, 0xf0605bee,
+ 0x24aa3f05, 0xd6c1bc06, 0xc5914ff2, 0x37faccf1, 0x69e9f0d5, 0x9b8273d6,
+ 0x88d28022, 0x7ab90321, 0xae7367ca, 0x5c18e4c9, 0x4f48173d, 0xbd23943e,
+ 0xf36e6f75, 0x0105ec76, 0x12551f82, 0xe03e9c81, 0x34f4f86a, 0xc69f7b69,
+ 0xd5cf889d, 0x27a40b9e, 0x79b737ba, 0x8bdcb4b9, 0x988c474d, 0x6ae7c44e,
+ 0xbe2da0a5, 0x4c4623a6, 0x5f16d052, 0xad7d5351,
+] as const;
+
+const CRC32C_EXTENSION_TABLE = new Int32Array(CRC32C_EXTENSIONS);
+
+/** An interface for CRC32C hashing and validation */
+interface CRC32CValidator {
+ /**
+ * A method returning the CRC32C as a base64-encoded string.
+ *
+ * @example
+ * Hashing the string 'data' should return 'rth90Q=='
+ *
+ * ```js
+ * const buffer = Buffer.from('data');
+ * crc32c.update(buffer);
+ * crc32c.toString(); // 'rth90Q=='
+ * ```
+ **/
+ toString: () => string;
+ /**
+ * A method validating a base64-encoded CRC32C string.
+ *
+ * @example
+ * Should return `true` if the value matches, `false` otherwise
+ *
+ * ```js
+ * const buffer = Buffer.from('data');
+ * crc32c.update(buffer);
+ * crc32c.validate('DkjKuA=='); // false
+ * crc32c.validate('rth90Q=='); // true
+ * ```
+ */
+ validate: (value: string) => boolean;
+ /**
+ * A method for passing `Buffer`s for CRC32C generation.
+ *
+ * @example
+ * Hashing buffers from 'some ' and 'text\n'
+ *
+ * ```js
+ * const buffer1 = Buffer.from('some ');
+ * crc32c.update(buffer1);
+ *
+ * const buffer2 = Buffer.from('text\n');
+ * crc32c.update(buffer2);
+ *
+ * crc32c.toString(); // 'DkjKuA=='
+ * ```
+ */
+ update: (data: Buffer) => void;
+}
+
+/** A function that generates a CRC32C Validator */
+interface CRC32CValidatorGenerator {
+ /** Should return a new, ready-to-use `CRC32CValidator` */
+ (): CRC32CValidator;
+}
+
+const CRC32C_DEFAULT_VALIDATOR_GENERATOR: CRC32CValidatorGenerator = () =>
+ new CRC32C();
+
+const CRC32C_EXCEPTION_MESSAGES = {
+ INVALID_INIT_BASE64_RANGE: (l: number) =>
+ `base64-encoded data expected to equal 4 bytes, not ${l}`,
+ INVALID_INIT_BUFFER_LENGTH: (l: number) =>
+ `Buffer expected to equal 4 bytes, not ${l}`,
+ INVALID_INIT_INTEGER: (l: number) =>
+ `Number expected to be a safe, unsigned 32-bit integer, not ${l}`,
+} as const;
+
+class CRC32C implements CRC32CValidator {
+ /** Current CRC32C value */
+ #crc32c = 0;
+
+ /**
+ * Constructs a new `CRC32C` object.
+ *
+ * Reconstruction is recommended via the `CRC32C.from` static method.
+ *
+ * @param initialValue An initial CRC32C value - a signed 32-bit integer.
+ */
+ constructor(initialValue = 0) {
+ this.#crc32c = initialValue;
+ }
+
+ /**
+ * Calculates a CRC32C from a provided buffer.
+ *
+ * Implementation inspired from:
+ * - {@link https://github.com/google/crc32c/blob/21fc8ef30415a635e7351ffa0e5d5367943d4a94/src/crc32c_portable.cc github.com/google/crc32c}
+ * - {@link https://github.com/googleapis/python-crc32c/blob/a595e758c08df445a99c3bf132ee8e80a3ec4308/src/google_crc32c/python.py github.com/googleapis/python-crc32c}
+ * - {@link https://github.com/googleapis/java-storage/pull/1376/files github.com/googleapis/java-storage}
+ *
+ * @param data The `Buffer` to generate the CRC32C from
+ */
+ update(data: Buffer) {
+ let current = this.#crc32c ^ 0xffffffff;
+
+ for (const d of data) {
+ const tablePoly = CRC32C.CRC32C_EXTENSION_TABLE[(d ^ current) & 0xff];
+ current = tablePoly ^ (current >>> 8);
+ }
+
+ this.#crc32c = current ^ 0xffffffff;
+ }
+
+ /**
+ * Validates a provided input to the current CRC32C value.
+ *
+ * @param input A Buffer, `CRC32C`-compatible object, base64-encoded data (string), or signed 32-bit integer
+ */
+ validate(input: Buffer | CRC32CValidator | string | number): boolean {
+ if (typeof input === 'number') {
+ return input === this.#crc32c;
+ } else if (typeof input === 'string') {
+ return input === this.toString();
+ } else if (Buffer.isBuffer(input)) {
+ return Buffer.compare(input, this.toBuffer()) === 0;
+ } else {
+ // `CRC32C`-like object
+ return input.toString() === this.toString();
+ }
+ }
+
+ /**
+ * Returns a `Buffer` representation of the CRC32C value
+ */
+ toBuffer(): Buffer {
+ const buffer = Buffer.alloc(4);
+ buffer.writeInt32BE(this.#crc32c);
+
+ return buffer;
+ }
+
+ /**
+ * Returns a JSON-compatible, base64-encoded representation of the CRC32C value.
+ *
+ * See {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify `JSON#stringify`}
+ */
+ toJSON(): string {
+ return this.toString();
+ }
+
+ /**
+ * Returns a base64-encoded representation of the CRC32C value.
+ *
+ * See {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/toString `Object#toString`}
+ */
+ toString(): string {
+ return this.toBuffer().toString('base64');
+ }
+
+ /**
+ * Returns the `number` representation of the CRC32C value as a signed 32-bit integer
+ *
+ * See {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/valueOf `Object#valueOf`}
+ */
+ valueOf(): number {
+ return this.#crc32c;
+ }
+
+ static readonly CRC32C_EXTENSIONS = CRC32C_EXTENSIONS;
+ static readonly CRC32C_EXTENSION_TABLE = CRC32C_EXTENSION_TABLE;
+
+ /**
+ * Generates a `CRC32C` from a compatible buffer format.
+ *
+ * @param value 4-byte `ArrayBufferView`/`Buffer`/`TypedArray`
+ */
+ private static fromBuffer(
+ value: ArrayBuffer | ArrayBufferView | Buffer
+ ): CRC32C {
+ let buffer: Buffer;
+
+ if (Buffer.isBuffer(value)) {
+ buffer = value;
+ } else if ('buffer' in value) {
+ // `ArrayBufferView`
+ buffer = Buffer.from(value.buffer);
+ } else {
+ // `ArrayBuffer`
+ buffer = Buffer.from(value);
+ }
+
+ if (buffer.byteLength !== 4) {
+ throw new RangeError(
+ CRC32C_EXCEPTION_MESSAGES.INVALID_INIT_BUFFER_LENGTH(buffer.byteLength)
+ );
+ }
+
+ return new CRC32C(buffer.readInt32BE());
+ }
+
+ static async fromFile(file: PathLike) {
+ const crc32c = new CRC32C();
+
+ await new Promise((resolve, reject) => {
+ createReadStream(file)
+ .on('data', (d: string | Buffer) => {
+ if (typeof d === 'string') {
+ crc32c.update(Buffer.from(d));
+ } else {
+ crc32c.update(d);
+ }
+ })
+ .on('end', () => resolve())
+ .on('error', reject);
+ });
+
+ return crc32c;
+ }
+
+ /**
+ * Generates a `CRC32C` from 4-byte base64-encoded data (string).
+ *
+ * @param value 4-byte base64-encoded data (string)
+ */
+ private static fromString(value: string): CRC32C {
+ const buffer = Buffer.from(value, 'base64');
+
+ if (buffer.byteLength !== 4) {
+ throw new RangeError(
+ CRC32C_EXCEPTION_MESSAGES.INVALID_INIT_BASE64_RANGE(buffer.byteLength)
+ );
+ }
+
+ return this.fromBuffer(buffer);
+ }
+
+ /**
+ * Generates a `CRC32C` from a safe, unsigned 32-bit integer.
+ *
+ * @param value an unsigned 32-bit integer
+ */
+ private static fromNumber(value: number): CRC32C {
+ if (!Number.isSafeInteger(value) || value > 2 ** 32 || value < -(2 ** 32)) {
+ throw new RangeError(
+ CRC32C_EXCEPTION_MESSAGES.INVALID_INIT_INTEGER(value)
+ );
+ }
+
+ return new CRC32C(value);
+ }
+
+ /**
+ * Generates a `CRC32C` from a variety of compatable types.
+ * Note: strings are treated as input, not as file paths to read from.
+ *
+ * @param value A number, 4-byte `ArrayBufferView`/`Buffer`/`TypedArray`, or 4-byte base64-encoded data (string)
+ */
+ static from(
+ value: ArrayBuffer | ArrayBufferView | CRC32CValidator | string | number
+ ): CRC32C {
+ if (typeof value === 'number') {
+ return this.fromNumber(value);
+ } else if (typeof value === 'string') {
+ return this.fromString(value);
+ } else if ('byteLength' in value) {
+ // `ArrayBuffer` | `Buffer` | `ArrayBufferView`
+ return this.fromBuffer(value);
+ } else {
+ // `CRC32CValidator`/`CRC32C`-like
+ return this.fromString(value.toString());
+ }
+ }
+}
+
+export {
+ CRC32C,
+ CRC32C_DEFAULT_VALIDATOR_GENERATOR,
+ CRC32C_EXCEPTION_MESSAGES,
+ CRC32C_EXTENSIONS,
+ CRC32C_EXTENSION_TABLE,
+ CRC32CValidator,
+ CRC32CValidatorGenerator,
+};
diff --git a/handwritten/storage/src/file.ts b/handwritten/storage/src/file.ts
new file mode 100644
index 00000000000..7a85ae96ea3
--- /dev/null
+++ b/handwritten/storage/src/file.ts
@@ -0,0 +1,4640 @@
+// Copyright 2019 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.
+
+import {
+ BodyResponseCallback,
+ DecorateRequestOptions,
+ GetConfig,
+ Interceptor,
+ MetadataCallback,
+ ServiceObject,
+ SetMetadataResponse,
+ util,
+} from './nodejs-common/index.js';
+import {promisifyAll} from '@google-cloud/promisify';
+
+import * as crypto from 'crypto';
+import * as fs from 'fs';
+import mime from 'mime';
+import * as resumableUpload from './resumable-upload.js';
+import {Writable, Readable, pipeline, Transform, PipelineSource} from 'stream';
+import * as zlib from 'zlib';
+import * as http from 'http';
+
+import {
+ ExceptionMessages,
+ IdempotencyStrategy,
+ PreconditionOptions,
+ Storage,
+} from './storage.js';
+import {AvailableServiceObjectMethods, Bucket} from './bucket.js';
+import {Acl, AclMetadata} from './acl.js';
+import {
+ GetSignedUrlResponse,
+ SigningError,
+ GetSignedUrlCallback,
+ URLSigner,
+ SignerGetSignedUrlConfig,
+ Query,
+} from './signer.js';
+import {
+ ResponseBody,
+ ApiError,
+ Duplexify,
+ GCCL_GCS_CMD_KEY,
+} from './nodejs-common/util.js';
+import duplexify from 'duplexify';
+import {
+ normalize,
+ objectKeyToLowercase,
+ unicodeJSONStringify,
+ formatAsUTCISO,
+ PassThroughShim,
+} from './util.js';
+import {CRC32C, CRC32CValidatorGenerator} from './crc32c.js';
+import {HashStreamValidator} from './hash-stream-validator.js';
+import {URL} from 'url';
+
+import AsyncRetry from 'async-retry';
+import {
+ BaseMetadata,
+ DeleteCallback,
+ DeleteOptions,
+ GetResponse,
+ InstanceResponseCallback,
+ RequestResponse,
+ SetMetadataOptions,
+} from './nodejs-common/service-object.js';
+import * as r from 'teeny-request';
+
+export type GetExpirationDateResponse = [Date];
+export interface GetExpirationDateCallback {
+ (
+ err: Error | null,
+ expirationDate?: Date | null,
+ apiResponse?: unknown
+ ): void;
+}
+
+export interface PolicyDocument {
+ string: string;
+ base64: string;
+ signature: string;
+}
+
+export type SaveData =
+ | string
+ | Buffer
+ | Uint8Array
+ | PipelineSource;
+
+export type GenerateSignedPostPolicyV2Response = [PolicyDocument];
+
+export interface GenerateSignedPostPolicyV2Callback {
+ (err: Error | null, policy?: PolicyDocument): void;
+}
+
+export interface GenerateSignedPostPolicyV2Options {
+ equals?: string[] | string[][];
+ expires: string | number | Date;
+ startsWith?: string[] | string[][];
+ acl?: string;
+ successRedirect?: string;
+ successStatus?: string;
+ contentLengthRange?: {min?: number; max?: number};
+ /**
+ * @example
+ * 'https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/'
+ */
+ signingEndpoint?: string;
+}
+
+export interface PolicyFields {
+ [key: string]: string;
+}
+
+export interface GenerateSignedPostPolicyV4Options {
+ expires: string | number | Date;
+ bucketBoundHostname?: string;
+ virtualHostedStyle?: boolean;
+ conditions?: object[];
+ fields?: PolicyFields;
+ /**
+ * @example
+ * 'https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/'
+ */
+ signingEndpoint?: string;
+}
+
+export interface GenerateSignedPostPolicyV4Callback {
+ (err: Error | null, output?: SignedPostPolicyV4Output): void;
+}
+
+export type GenerateSignedPostPolicyV4Response = [SignedPostPolicyV4Output];
+
+export interface SignedPostPolicyV4Output {
+ url: string;
+ fields: PolicyFields;
+}
+
+export interface GetSignedUrlConfig
+ extends Pick {
+ action: 'read' | 'write' | 'delete' | 'resumable';
+ version?: 'v2' | 'v4';
+ virtualHostedStyle?: boolean;
+ cname?: string;
+ contentMd5?: string;
+ contentType?: string;
+ expires: string | number | Date;
+ accessibleAt?: string | number | Date;
+ extensionHeaders?: http.OutgoingHttpHeaders;
+ promptSaveAs?: string;
+ responseDisposition?: string;
+ responseType?: string;
+ queryParams?: Query;
+}
+
+export interface GetFileMetadataOptions {
+ userProject?: string;
+}
+
+export type GetFileMetadataResponse = [FileMetadata, unknown];
+
+export interface GetFileMetadataCallback {
+ (err: Error | null, metadata?: FileMetadata, apiResponse?: unknown): void;
+}
+
+export interface GetFileOptions extends GetConfig {
+ userProject?: string;
+ generation?: number;
+ restoreToken?: string;
+ softDeleted?: boolean;
+}
+
+export type GetFileResponse = [File, unknown];
+
+export interface GetFileCallback {
+ (err: Error | null, file?: File, apiResponse?: unknown): void;
+}
+
+export interface FileExistsOptions {
+ userProject?: string;
+}
+
+export type FileExistsResponse = [boolean];
+
+export interface FileExistsCallback {
+ (err: Error | null, exists?: boolean): void;
+}
+
+export interface DeleteFileOptions {
+ ignoreNotFound?: boolean;
+ userProject?: string;
+}
+
+export type DeleteFileResponse = [unknown];
+
+export interface DeleteFileCallback {
+ (err: Error | null, apiResponse?: unknown): void;
+}
+
+export type PredefinedAcl =
+ | 'authenticatedRead'
+ | 'bucketOwnerFullControl'
+ | 'bucketOwnerRead'
+ | 'private'
+ | 'projectPrivate'
+ | 'publicRead';
+
+type PublicResumableUploadOptions =
+ | 'chunkSize'
+ | 'highWaterMark'
+ | 'isPartialUpload'
+ | 'metadata'
+ | 'origin'
+ | 'offset'
+ | 'predefinedAcl'
+ | 'private'
+ | 'public'
+ | 'uri'
+ | 'userProject';
+
+export interface CreateResumableUploadOptions
+ extends Pick {
+ /**
+ * A CRC32C to resume from when continuing a previous upload. It is recommended
+ * to capture the `crc32c` event from previous upload sessions to provide in
+ * subsequent requests in order to accurately track the upload. This is **required**
+ * when validating a final portion of the uploaded object.
+ *
+ * @see {@link CRC32C.from} for possible values.
+ */
+ resumeCRC32C?: Parameters<(typeof CRC32C)['from']>[0];
+ preconditionOpts?: PreconditionOptions;
+ [GCCL_GCS_CMD_KEY]?: resumableUpload.UploadConfig[typeof GCCL_GCS_CMD_KEY];
+}
+
+export type CreateResumableUploadResponse = [string];
+
+export interface CreateResumableUploadCallback {
+ (err: Error | null, uri?: string): void;
+}
+
+export interface CreateWriteStreamOptions extends CreateResumableUploadOptions {
+ contentType?: string;
+ gzip?: string | boolean;
+ resumable?: boolean;
+ timeout?: number;
+ validation?: string | boolean;
+}
+
+export interface MakeFilePrivateOptions {
+ metadata?: FileMetadata;
+ strict?: boolean;
+ userProject?: string;
+ preconditionOpts?: PreconditionOptions;
+}
+
+export type MakeFilePrivateResponse = [unknown];
+
+export type MakeFilePrivateCallback = SetFileMetadataCallback;
+
+export interface IsPublicCallback {
+ (err: Error | null, resp?: boolean): void;
+}
+
+export type IsPublicResponse = [boolean];
+
+export type MakeFilePublicResponse = [unknown];
+
+export interface MakeFilePublicCallback {
+ (err?: Error | null, apiResponse?: unknown): void;
+}
+
+interface MoveFileAtomicQuery {
+ userProject?: string;
+ ifGenerationMatch?: number | string;
+ ifGenerationNotMatch?: number | string;
+ ifMetagenerationMatch?: number | string;
+ ifMetagenerationNotMatch?: number | string;
+}
+
+export type MoveResponse = [unknown];
+
+export interface MoveCallback {
+ (
+ err: Error | null,
+ destinationFile?: File | null,
+ apiResponse?: unknown
+ ): void;
+}
+
+export interface MoveOptions {
+ userProject?: string;
+ preconditionOpts?: PreconditionOptions;
+}
+
+export type MoveFileAtomicOptions = MoveOptions;
+export type MoveFileAtomicCallback = MoveCallback;
+export type MoveFileAtomicResponse = MoveResponse;
+
+export type RenameOptions = MoveOptions;
+export type RenameResponse = MoveResponse;
+export type RenameCallback = MoveCallback;
+
+export type RotateEncryptionKeyOptions = string | Buffer | EncryptionKeyOptions;
+
+export interface EncryptionKeyOptions {
+ encryptionKey?: string | Buffer;
+ kmsKeyName?: string;
+ preconditionOpts?: PreconditionOptions;
+}
+
+export type RotateEncryptionKeyCallback = CopyCallback;
+
+export type RotateEncryptionKeyResponse = CopyResponse;
+
+export enum ActionToHTTPMethod {
+ read = 'GET',
+ write = 'PUT',
+ delete = 'DELETE',
+ resumable = 'POST',
+}
+
+/**
+ * @deprecated - no longer used
+ */
+export const STORAGE_POST_POLICY_BASE_URL = 'https://storage.googleapis.com';
+
+/**
+ * @private
+ */
+const GS_URL_REGEXP = /^gs:\/\/([a-z0-9_.-]+)\/(.+)$/;
+
+/**
+ * @private
+ * This regex will match compressible content types. These are primarily text/*, +json, +text, +xml content types.
+ * This was based off of mime-db and may periodically need to be updated if new compressible content types become
+ * standards.
+ */
+const COMPRESSIBLE_MIME_REGEX = new RegExp(
+ [
+ /^text\/|application\/ecmascript|application\/javascript|application\/json/,
+ /|application\/postscript|application\/rtf|application\/toml|application\/vnd.dart/,
+ /|application\/vnd.ms-fontobject|application\/wasm|application\/x-httpd-php|application\/x-ns-proxy-autoconfig/,
+ /|application\/x-sh(?!ockwave-flash)|application\/x-tar|application\/x-virtualbox-hdd|application\/x-virtualbox-ova|application\/x-virtualbox-ovf/,
+ /|^application\/x-virtualbox-vbox$|application\/x-virtualbox-vdi|application\/x-virtualbox-vhd|application\/x-virtualbox-vmdk/,
+ /|application\/xml|application\/xml-dtd|font\/otf|font\/ttf|image\/bmp|image\/vnd.adobe.photoshop|image\/vnd.microsoft.icon/,
+ /|image\/vnd.ms-dds|image\/x-icon|image\/x-ms-bmp|message\/rfc822|model\/gltf-binary|\+json|\+text|\+xml|\+yaml/,
+ ]
+ .map(r => r.source)
+ .join(''),
+ 'i'
+);
+
+export interface FileOptions {
+ crc32cGenerator?: CRC32CValidatorGenerator;
+ encryptionKey?: string | Buffer;
+ generation?: number | string;
+ restoreToken?: string;
+ kmsKeyName?: string;
+ preconditionOpts?: PreconditionOptions;
+ userProject?: string;
+}
+
+export interface CopyOptions {
+ cacheControl?: string;
+ contentEncoding?: string;
+ contentType?: string;
+ contentDisposition?: string;
+ destinationKmsKeyName?: string;
+ metadata?: {
+ [key: string]: string | boolean | number | null;
+ };
+ predefinedAcl?: string;
+ token?: string;
+ userProject?: string;
+ preconditionOpts?: PreconditionOptions;
+}
+
+export type CopyResponse = [File, unknown];
+
+export interface CopyCallback {
+ (err: Error | null, file?: File | null, apiResponse?: unknown): void;
+}
+
+export type DownloadResponse = [Buffer];
+
+export type DownloadCallback = (
+ err: RequestError | null,
+ contents: Buffer
+) => void;
+
+export interface DownloadOptions extends CreateReadStreamOptions {
+ destination?: string;
+ encryptionKey?: string | Buffer;
+}
+
+interface CopyQuery {
+ sourceGeneration?: number;
+ rewriteToken?: string;
+ userProject?: string;
+ destinationKmsKeyName?: string;
+ destinationPredefinedAcl?: string;
+ ifGenerationMatch?: number | string;
+ ifGenerationNotMatch?: number | string;
+ ifMetagenerationMatch?: number | string;
+ ifMetagenerationNotMatch?: number | string;
+}
+
+interface FileQuery {
+ alt: string;
+ generation?: number;
+ userProject?: string;
+}
+
+export interface CreateReadStreamOptions {
+ userProject?: string;
+ validation?: 'md5' | 'crc32c' | false | true;
+ start?: number;
+ end?: number;
+ decompress?: boolean;
+ [GCCL_GCS_CMD_KEY]?: string;
+}
+
+export interface SaveOptions extends CreateWriteStreamOptions {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ onUploadProgress?: (progressEvent: any) => void;
+}
+
+export interface SaveCallback {
+ (err?: Error | null): void;
+}
+
+export interface SetFileMetadataOptions {
+ userProject?: string;
+}
+
+export interface SetFileMetadataCallback {
+ (err?: Error | null, apiResponse?: unknown): void;
+}
+
+export type SetFileMetadataResponse = [unknown];
+
+export type SetStorageClassResponse = [unknown];
+
+export interface SetStorageClassOptions {
+ userProject?: string;
+ preconditionOpts?: PreconditionOptions;
+}
+
+export interface SetStorageClassCallback {
+ (err?: Error | null, apiResponse?: unknown): void;
+}
+
+export interface RestoreOptions extends PreconditionOptions {
+ generation: number;
+ restoreToken?: string;
+ projection?: 'full' | 'noAcl';
+}
+
+export interface FileMetadata extends BaseMetadata {
+ acl?: AclMetadata[] | null;
+ bucket?: string;
+ cacheControl?: string;
+ componentCount?: number;
+ contentDisposition?: string;
+ contentEncoding?: string;
+ contentLanguage?: string;
+ contentType?: string;
+ crc32c?: string;
+ customerEncryption?: {
+ encryptionAlgorithm?: string;
+ keySha256?: string;
+ };
+ customTime?: string;
+ eventBasedHold?: boolean | null;
+ readonly eventBasedHoldReleaseTime?: string;
+ generation?: string | number;
+ restoreToken?: string;
+ hardDeleteTime?: string;
+ kmsKeyName?: string;
+ md5Hash?: string;
+ mediaLink?: string;
+ metadata?: {
+ [key: string]: string | boolean | number | null;
+ };
+ metageneration?: string | number;
+ name?: string;
+ owner?: {
+ entity?: string;
+ entityId?: string;
+ };
+ retention?: {
+ retainUntilTime?: string;
+ mode?: string;
+ } | null;
+ retentionExpirationTime?: string;
+ size?: string | number;
+ softDeleteTime?: string;
+ storageClass?: string;
+ temporaryHold?: boolean | null;
+ timeCreated?: string;
+ timeDeleted?: string;
+ timeStorageClassUpdated?: string;
+ updated?: string;
+}
+
+export class RequestError extends Error {
+ code?: string;
+ errors?: Error[];
+}
+
+const SEVEN_DAYS = 7 * 24 * 60 * 60;
+const GS_UTIL_URL_REGEX = /(gs):\/\/([a-z0-9_.-]+)\/(.+)/g;
+const HTTPS_PUBLIC_URL_REGEX =
+ /(https):\/\/(storage\.googleapis\.com)\/([a-z0-9_.-]+)\/(.+)/g;
+
+export enum FileExceptionMessages {
+ EXPIRATION_TIME_NA = 'An expiration time is not available.',
+ DESTINATION_NO_NAME = 'Destination file should have a name.',
+ INVALID_VALIDATION_FILE_RANGE = 'Cannot use validation with file ranges (start/end).',
+ MD5_NOT_AVAILABLE = 'MD5 verification was specified, but is not available for the requested object. MD5 is not available for composite objects.',
+ EQUALS_CONDITION_TWO_ELEMENTS = 'Equals condition must be an array of 2 elements.',
+ STARTS_WITH_TWO_ELEMENTS = 'StartsWith condition must be an array of 2 elements.',
+ CONTENT_LENGTH_RANGE_MIN_MAX = 'ContentLengthRange must have numeric min & max fields.',
+ DOWNLOAD_MISMATCH = 'The downloaded data did not match the data from the server. To be sure the content is the same, you should download the file again.',
+ UPLOAD_MISMATCH_DELETE_FAIL = `The uploaded data did not match the data from the server.
+ As a precaution, we attempted to delete the file, but it was not successful.
+ To be sure the content is the same, you should try removing the file manually,
+ then uploading the file again.
+ \n\nThe delete attempt failed with this message:\n\n `,
+ UPLOAD_MISMATCH = `The uploaded data did not match the data from the server.
+ As a precaution, the file has been deleted.
+ To be sure the content is the same, you should try uploading the file again.`,
+ MD5_RESUMED_UPLOAD = 'MD5 cannot be used with a continued resumable upload as MD5 cannot be extended from an existing value',
+ MISSING_RESUME_CRC32C_FINAL_UPLOAD = 'The CRC32C is missing for the final portion of a resumed upload, which is required for validation. Please provide `resumeCRC32C` if validation is required, or disable `validation`.',
+}
+
+/**
+ * A File object is created from your {@link Bucket} object using
+ * {@link Bucket#file}.
+ *
+ * @class
+ */
+class File extends ServiceObject {
+ acl: Acl;
+ crc32cGenerator: CRC32CValidatorGenerator;
+ bucket: Bucket;
+ storage: Storage;
+ kmsKeyName?: string;
+ userProject?: string;
+ signer?: URLSigner;
+ name: string;
+
+ generation?: number;
+ restoreToken?: string;
+ parent!: Bucket;
+
+ private encryptionKey?: string | Buffer;
+ private encryptionKeyBase64?: string;
+ private encryptionKeyHash?: string;
+ private encryptionKeyInterceptor?: Interceptor;
+ private instanceRetryValue?: boolean;
+ instancePreconditionOpts?: PreconditionOptions;
+
+ /**
+ * Cloud Storage uses access control lists (ACLs) to manage object and
+ * bucket access. ACLs are the mechanism you use to share objects with other
+ * users and allow other users to access your buckets and objects.
+ *
+ * An ACL consists of one or more entries, where each entry grants permissions
+ * to an entity. Permissions define the actions that can be performed against
+ * an object or bucket (for example, `READ` or `WRITE`); the entity defines
+ * who the permission applies to (for example, a specific user or group of
+ * users).
+ *
+ * The `acl` object on a File instance provides methods to get you a list of
+ * the ACLs defined on your bucket, as well as set, update, and delete them.
+ *
+ * See {@link http://goo.gl/6qBBPO| About Access Control lists}
+ *
+ * @name File#acl
+ * @mixes Acl
+ *
+ * @example
+ * ```
+ * const {Storage} = require('@google-cloud/storage');
+ * const storage = new Storage();
+ * const myBucket = storage.bucket('my-bucket');
+ *
+ * const file = myBucket.file('my-file');
+ * //-
+ * // Make a file publicly readable.
+ * //-
+ * const options = {
+ * entity: 'allUsers',
+ * role: storage.acl.READER_ROLE
+ * };
+ *
+ * file.acl.add(options, function(err, aclObject) {});
+ *
+ * //-
+ * // If the callback is omitted, we'll return a Promise.
+ * //-
+ * file.acl.add(options).then(function(data) {
+ * const aclObject = data[0];
+ * const apiResponse = data[1];
+ * });
+ * ```
+ */
+ /**
+ * The API-formatted resource description of the file.
+ *
+ * Note: This is not guaranteed to be up-to-date when accessed. To get the
+ * latest record, call the `getMetadata()` method.
+ *
+ * @name File#metadata
+ * @type {object}
+ */
+ /**
+ * The file's name.
+ * @name File#name
+ * @type {string}
+ */
+ /**
+ * @callback Crc32cGeneratorToStringCallback
+ * A method returning the CRC32C as a base64-encoded string.
+ *
+ * @returns {string}
+ *
+ * @example
+ * Hashing the string 'data' should return 'rth90Q=='
+ *
+ * ```js
+ * const buffer = Buffer.from('data');
+ * crc32c.update(buffer);
+ * crc32c.toString(); // 'rth90Q=='
+ * ```
+ **/
+ /**
+ * @callback Crc32cGeneratorValidateCallback
+ * A method validating a base64-encoded CRC32C string.
+ *
+ * @param {string} [value] base64-encoded CRC32C string to validate
+ * @returns {boolean}
+ *
+ * @example
+ * Should return `true` if the value matches, `false` otherwise
+ *
+ * ```js
+ * const buffer = Buffer.from('data');
+ * crc32c.update(buffer);
+ * crc32c.validate('DkjKuA=='); // false
+ * crc32c.validate('rth90Q=='); // true
+ * ```
+ **/
+ /**
+ * @callback Crc32cGeneratorUpdateCallback
+ * A method for passing `Buffer`s for CRC32C generation.
+ *
+ * @param {Buffer} [data] data to update CRC32C value with
+ * @returns {undefined}
+ *
+ * @example
+ * Hashing buffers from 'some ' and 'text\n'
+ *
+ * ```js
+ * const buffer1 = Buffer.from('some ');
+ * crc32c.update(buffer1);
+ *
+ * const buffer2 = Buffer.from('text\n');
+ * crc32c.update(buffer2);
+ *
+ * crc32c.toString(); // 'DkjKuA=='
+ * ```
+ **/
+ /**
+ * @typedef {object} CRC32CValidator
+ * @property {Crc32cGeneratorToStringCallback}
+ * @property {Crc32cGeneratorValidateCallback}
+ * @property {Crc32cGeneratorUpdateCallback}
+ */
+ /**
+ * @callback Crc32cGeneratorCallback
+ * @returns {CRC32CValidator}
+ */
+ /**
+ * @typedef {object} FileOptions Options passed to the File constructor.
+ * @property {string} [encryptionKey] A custom encryption key.
+ * @property {number} [generation] Generation to scope the file to.
+ * @property {string} [kmsKeyName] Cloud KMS Key used to encrypt this
+ * object, if the object is encrypted by such a key. Limited availability;
+ * usable only by enabled projects.
+ * @property {string} [userProject] The ID of the project which will be
+ * billed for all requests made from File object.
+ * @property {Crc32cGeneratorCallback} [callback] A function that generates a CRC32C Validator. Defaults to {@link CRC32C}
+ */
+ /**
+ * Constructs a file object.
+ *
+ * @param {Bucket} bucket The Bucket instance this file is
+ * attached to.
+ * @param {string} name The name of the remote file.
+ * @param {FileOptions} [options] Configuration options.
+ * @example
+ * ```
+ * const {Storage} = require('@google-cloud/storage');
+ * const storage = new Storage();
+ * const myBucket = storage.bucket('my-bucket');
+ *
+ * const file = myBucket.file('my-file');
+ * ```
+ */
+ constructor(bucket: Bucket, name: string, options: FileOptions = {}) {
+ const requestQueryObject: {
+ generation?: number;
+ userProject?: string;
+ ifGenerationMatch?: number;
+ ifGenerationNotMatch?: number;
+ ifMetagenerationMatch?: number;
+ ifMetagenerationNotMatch?: number;
+ } = {};
+
+ let generation: number;
+ if (options.generation !== null) {
+ if (typeof options.generation === 'string') {
+ generation = Number(options.generation);
+ } else {
+ generation = options.generation!;
+ }
+ if (!isNaN(generation)) {
+ requestQueryObject.generation = generation;
+ }
+ }
+
+ Object.assign(requestQueryObject, options.preconditionOpts);
+
+ const userProject = options.userProject || bucket.userProject;
+ if (typeof userProject === 'string') {
+ requestQueryObject.userProject = userProject;
+ }
+
+ const methods = {
+ /**
+ * @typedef {array} DeleteFileResponse
+ * @property {object} 0 The full API response.
+ */
+ /**
+ * @callback DeleteFileCallback
+ * @param {?Error} err Request error, if any.
+ * @param {object} apiResponse The full API response.
+ */
+ /**
+ * Delete the file.
+ *
+ * See {@link https://cloud.google.com/storage/docs/json_api/v1/objects/delete| Objects: delete API Documentation}
+ *
+ * @method File#delete
+ * @param {object} [options] Configuration options.
+ * @param {boolean} [options.ignoreNotFound = false] Ignore an error if
+ * the file does not exist.
+ * @param {string} [options.userProject] The ID of the project which will be
+ * billed for the request.
+ * @param {DeleteFileCallback} [callback] Callback function.
+ * @returns {Promise}
+ *
+ * @example
+ * ```
+ * const {Storage} = require('@google-cloud/storage');
+ * const storage = new Storage();
+ * const myBucket = storage.bucket('my-bucket');
+ *
+ * const file = myBucket.file('my-file');
+ * file.delete(function(err, apiResponse) {});
+ *
+ * //-
+ * // If the callback is omitted, we'll return a Promise.
+ * //-
+ * file.delete().then(function(data) {
+ * const apiResponse = data[0];
+ * });
+ *
+ * ```
+ * @example include:samples/files.js
+ * region_tag:storage_delete_file
+ * Another example:
+ */
+ delete: {
+ reqOpts: {
+ qs: requestQueryObject,
+ },
+ },
+ /**
+ * @typedef {array} FileExistsResponse
+ * @property {boolean} 0 Whether the {@link File} exists.
+ */
+ /**
+ * @callback FileExistsCallback
+ * @param {?Error} err Request error, if any.
+ * @param {boolean} exists Whether the {@link File} exists.
+ */
+ /**
+ * Check if the file exists.
+ *
+ * @method File#exists
+ * @param {options} [options] Configuration options.
+ * @param {string} [options.userProject] The ID of the project which will be
+ * billed for the request.
+ * @param {FileExistsCallback} [callback] Callback function.
+ * @returns {Promise}
+ *
+ * @example
+ * ```
+ * const {Storage} = require('@google-cloud/storage');
+ * const storage = new Storage();
+ * const myBucket = storage.bucket('my-bucket');
+ *
+ * const file = myBucket.file('my-file');
+ *
+ * file.exists(function(err, exists) {});
+ *
+ * //-
+ * // If the callback is omitted, we'll return a Promise.
+ * //-
+ * file.exists().then(function(data) {
+ * const exists = data[0];
+ * });
+ * ```
+ */
+ exists: {
+ reqOpts: {
+ qs: requestQueryObject,
+ },
+ },
+ /**
+ * @typedef {array} GetFileResponse
+ * @property {File} 0 The {@link File}.
+ * @property {object} 1 The full API response.
+ */
+ /**
+ * @callback GetFileCallback
+ * @param {?Error} err Request error, if any.
+ * @param {File} file The {@link File}.
+ * @param {object} apiResponse The full API response.
+ */
+ /**
+ * Get a file object and its metadata if it exists.
+ *
+ * @method File#get
+ * @param {options} [options] Configuration options.
+ * @param {string} [options.userProject] The ID of the project which will be
+ * billed for the request.
+ * @param {number} [options.generation] The generation number to get
+ * @param {string} [options.restoreToken] If this is a soft-deleted object in an HNS-enabled bucket, returns the restore token which will
+ * be necessary to restore it if there's a name conflict with another object.
+ * @param {boolean} [options.softDeleted] If true, returns the soft-deleted object.
+ Object `generation` is required if `softDeleted` is set to True.
+ * @param {GetFileCallback} [callback] Callback function.
+ * @returns {Promise}
+ *
+ * @example
+ * ```
+ * const {Storage} = require('@google-cloud/storage');
+ * const storage = new Storage();
+ * const myBucket = storage.bucket('my-bucket');
+ *
+ * const file = myBucket.file('my-file');
+ *
+ * file.get(function(err, file, apiResponse) {
+ * // file.metadata` has been populated.
+ * });
+ *
+ * //-
+ * // If the callback is omitted, we'll return a Promise.
+ * //-
+ * file.get().then(function(data) {
+ * const file = data[0];
+ * const apiResponse = data[1];
+ * });
+ * ```
+ */
+ get: {
+ reqOpts: {
+ qs: requestQueryObject,
+ },
+ },
+ /**
+ * @typedef {array} GetFileMetadataResponse
+ * @property {object} 0 The {@link File} metadata.
+ * @property {object} 1 The full API response.
+ */
+ /**
+ * @callback GetFileMetadataCallback
+ * @param {?Error} err Request error, if any.
+ * @param {object} metadata The {@link File} metadata.
+ * @param {object} apiResponse The full API response.
+ */
+ /**
+ * Get the file's metadata.
+ *
+ * See {@link https://cloud.google.com/storage/docs/json_api/v1/objects/get| Objects: get API Documentation}
+ *
+ * @method File#getMetadata
+ * @param {object} [options] Configuration options.
+ * @param {string} [options.userProject] The ID of the project which will be
+ * billed for the request.
+ * @param {GetFileMetadataCallback} [callback] Callback function.
+ * @returns {Promise}
+ *
+ * @example
+ * ```
+ * const {Storage} = require('@google-cloud/storage');
+ * const storage = new Storage();
+ * const myBucket = storage.bucket('my-bucket');
+ *
+ * const file = myBucket.file('my-file');
+ *
+ * file.getMetadata(function(err, metadata, apiResponse) {});
+ *
+ * //-
+ * // If the callback is omitted, we'll return a Promise.
+ * //-
+ * file.getMetadata().then(function(data) {
+ * const metadata = data[0];
+ * const apiResponse = data[1];
+ * });
+ *
+ * ```
+ * @example include:samples/files.js
+ * region_tag:storage_get_metadata
+ * Another example:
+ */
+ getMetadata: {
+ reqOpts: {
+ qs: requestQueryObject,
+ },
+ },
+ /**
+ * @typedef {object} SetFileMetadataOptions Configuration options for File#setMetadata().
+ * @param {string} [userProject] The ID of the project which will be billed for the request.
+ */
+ /**
+ * @callback SetFileMetadataCallback
+ * @param {?Error} err Request error, if any.
+ * @param {object} apiResponse The full API response.
+ */
+ /**
+ * @typedef {array} SetFileMetadataResponse
+ * @property {object} 0 The full API response.
+ */
+ /**
+ * Merge the given metadata with the current remote file's metadata. This
+ * will set metadata if it was previously unset or update previously set
+ * metadata. To unset previously set metadata, set its value to null.
+ *
+ * You can set custom key/value pairs in the metadata key of the given
+ * object, however the other properties outside of this object must adhere
+ * to the {@link https://goo.gl/BOnnCK| official API documentation}.
+ *
+ *
+ * See the examples below for more information.
+ *
+ * See {@link https://cloud.google.com/storage/docs/json_api/v1/objects/patch| Objects: patch API Documentation}
+ *
+ * @method File#setMetadata
+ * @param {object} [metadata] The metadata you wish to update.
+ * @param {SetFileMetadataOptions} [options] Configuration options.
+ * @param {SetFileMetadataCallback} [callback] Callback function.
+ * @returns {Promise}
+ *
+ * @example
+ * ```
+ * const {Storage} = require('@google-cloud/storage');
+ * const storage = new Storage();
+ * const myBucket = storage.bucket('my-bucket');
+ *
+ * const file = myBucket.file('my-file');
+ *
+ * const metadata = {
+ * contentType: 'application/x-font-ttf',
+ * metadata: {
+ * my: 'custom',
+ * properties: 'go here'
+ * }
+ * };
+ *
+ * file.setMetadata(metadata, function(err, apiResponse) {});
+ *
+ * // Assuming current metadata = { hello: 'world', unsetMe: 'will do' }
+ * file.setMetadata({
+ * metadata: {
+ * abc: '123', // will be set.
+ * unsetMe: null, // will be unset (deleted).
+ * hello: 'goodbye' // will be updated from 'world' to 'goodbye'.
+ * }
+ * }, function(err, apiResponse) {
+ * // metadata should now be { abc: '123', hello: 'goodbye' }
+ * });
+ *
+ * //-
+ * // Set a temporary hold on this file from its bucket's retention period
+ * // configuration.
+ * //
+ * file.setMetadata({
+ * temporaryHold: true
+ * }, function(err, apiResponse) {});
+ *
+ * //-
+ * // Alternatively, you may set a temporary hold. This will follow the
+ * // same behavior as an event-based hold, with the exception that the
+ * // bucket's retention policy will not renew for this file from the time
+ * // the hold is released.
+ * //-
+ * file.setMetadata({
+ * eventBasedHold: true
+ * }, function(err, apiResponse) {});
+ *
+ * //-
+ * // If the callback is omitted, we'll return a Promise.
+ * //-
+ * file.setMetadata(metadata).then(function(data) {
+ * const apiResponse = data[0];
+ * });
+ * ```
+ */
+ setMetadata: {
+ reqOpts: {
+ qs: requestQueryObject,
+ },
+ },
+ };
+
+ super({
+ parent: bucket,
+ baseUrl: '/o',
+ id: encodeURIComponent(name),
+ methods,
+ });
+
+ this.bucket = bucket;
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ this.storage = (bucket as any).parent as Storage;
+
+ // @TODO Can this duplicate code from above be avoided?
+ if (options.generation !== null) {
+ let generation: number;
+ if (typeof options.generation === 'string') {
+ generation = Number(options.generation);
+ } else {
+ generation = options.generation!;
+ }
+ if (!isNaN(generation)) {
+ this.generation = generation;
+ }
+ }
+ this.kmsKeyName = options.kmsKeyName;
+ this.userProject = userProject;
+
+ this.name = name;
+
+ if (options.encryptionKey) {
+ this.setEncryptionKey(options.encryptionKey);
+ }
+
+ this.acl = new Acl({
+ request: this.request.bind(this),
+ pathPrefix: '/acl',
+ });
+
+ this.crc32cGenerator =
+ options.crc32cGenerator || this.bucket.crc32cGenerator;
+
+ this.instanceRetryValue = this.storage?.retryOptions?.autoRetry;
+ this.instancePreconditionOpts = options?.preconditionOpts;
+ }
+
+ /**
+ * The object's Cloud Storage URI (`gs://`)
+ *
+ * @example
+ * ```ts
+ * const {Storage} = require('@google-cloud/storage');
+ * const storage = new Storage();
+ * const bucket = storage.bucket('my-bucket');
+ * const file = bucket.file('image.png');
+ *
+ * // `gs://my-bucket/image.png`
+ * const href = file.cloudStorageURI.href;
+ * ```
+ */
+ get cloudStorageURI(): URL {
+ const uri = this.bucket.cloudStorageURI;
+
+ uri.pathname = this.name;
+
+ return uri;
+ }
+
+ /**
+ * A helper method for determining if a request should be retried based on preconditions.
+ * This should only be used for methods where the idempotency is determined by
+ * `ifGenerationMatch`
+ * @private
+ *
+ * A request should not be retried under the following conditions:
+ * - if precondition option `ifGenerationMatch` is not set OR
+ * - if `idempotencyStrategy` is set to `RetryNever`
+ */
+ private shouldRetryBasedOnPreconditionAndIdempotencyStrat(
+ options?: PreconditionOptions
+ ): boolean {
+ return !(
+ (options?.ifGenerationMatch === undefined &&
+ this.instancePreconditionOpts?.ifGenerationMatch === undefined &&
+ this.storage.retryOptions.idempotencyStrategy ===
+ IdempotencyStrategy.RetryConditional) ||
+ this.storage.retryOptions.idempotencyStrategy ===
+ IdempotencyStrategy.RetryNever
+ );
+ }
+
+ copy(
+ destination: string | Bucket | File,
+ options?: CopyOptions
+ ): Promise;
+ copy(destination: string | Bucket | File, callback: CopyCallback): void;
+ copy(
+ destination: string | Bucket | File,
+ options: CopyOptions,
+ callback: CopyCallback
+ ): void;
+ /**
+ * @typedef {array} CopyResponse
+ * @property {File} 0 The copied {@link File}.
+ * @property {object} 1 The full API response.
+ */
+ /**
+ * @callback CopyCallback
+ * @param {?Error} err Request error, if any.
+ * @param {File} copiedFile The copied {@link File}.
+ * @param {object} apiResponse The full API response.
+ */
+ /**
+ * @typedef {object} CopyOptions Configuration options for File#copy(). See an
+ * {@link https://cloud.google.com/storage/docs/json_api/v1/objects#resource| Object resource}.
+ * @property {string} [cacheControl] The cacheControl setting for the new file.
+ * @property {string} [contentEncoding] The contentEncoding setting for the new file.
+ * @property {string} [contentType] The contentType setting for the new file.
+ * @property {string} [destinationKmsKeyName] Resource name of the Cloud
+ * KMS key, of the form
+ * `projects/my-project/locations/location/keyRings/my-kr/cryptoKeys/my-key`,
+ * that will be used to encrypt the object. Overwrites the object
+ * metadata's `kms_key_name` value, if any.
+ * @property {Metadata} [metadata] Metadata to specify on the copied file.
+ * @property {string} [predefinedAcl] Set the ACL for the new file.
+ * @property {string} [token] A previously-returned `rewriteToken` from an
+ * unfinished rewrite request.
+ * @property {string} [userProject] The ID of the project which will be
+ * billed for the request.
+ */
+ /**
+ * Copy this file to another file. By default, this will copy the file to the
+ * same bucket, but you can choose to copy it to another Bucket by providing
+ * a Bucket or File object or a URL starting with "gs://".
+ * The generation of the file will not be preserved.
+ *
+ * See {@link https://cloud.google.com/storage/docs/json_api/v1/objects/rewrite| Objects: rewrite API Documentation}
+ *
+ * @throws {Error} If the destination file is not provided.
+ *
+ * @param {string|Bucket|File} destination Destination file.
+ * @param {CopyOptions} [options] Configuration options. See an
+ * @param {CopyCallback} [callback] Callback function.
+ * @returns {Promise}
+ *
+ * @example
+ * ```
+ * const {Storage} = require('@google-cloud/storage');
+ * const storage = new Storage();
+ *
+ * //-
+ * // You can pass in a variety of types for the destination.
+ * //
+ * // For all of the below examples, assume we are working with the following
+ * // Bucket and File objects.
+ * //-
+ * const bucket = storage.bucket('my-bucket');
+ * const file = bucket.file('my-image.png');
+ *
+ * //-
+ * // If you pass in a string for the destination, the file is copied to its
+ * // current bucket, under the new name provided.
+ * //-
+ * file.copy('my-image-copy.png', function(err, copiedFile, apiResponse) {
+ * // `my-bucket` now contains:
+ * // - "my-image.png"
+ * // - "my-image-copy.png"
+ *
+ * // `copiedFile` is an instance of a File object that refers to your new
+ * // file.
+ * });
+ *
+ * //-
+ * // If you pass in a string starting with "gs://" for the destination, the
+ * // file is copied to the other bucket and under the new name provided.
+ * //-
+ * const newLocation = 'gs://another-bucket/my-image-copy.png';
+ * file.copy(newLocation, function(err, copiedFile, apiResponse) {
+ * // `my-bucket` still contains:
+ * // - "my-image.png"
+ * //
+ * // `another-bucket` now contains:
+ * // - "my-image-copy.png"
+ *
+ * // `copiedFile` is an instance of a File object that refers to your new
+ * // file.
+ * });
+ *
+ * //-
+ * // If you pass in a Bucket object, the file will be copied to that bucket
+ * // using the same name.
+ * //-
+ * const anotherBucket = storage.bucket('another-bucket');
+ * file.copy(anotherBucket, function(err, copiedFile, apiResponse) {
+ * // `my-bucket` still contains:
+ * // - "my-image.png"
+ * //
+ * // `another-bucket` now contains:
+ * // - "my-image.png"
+ *
+ * // `copiedFile` is an instance of a File object that refers to your new
+ * // file.
+ * });
+ *
+ * //-
+ * // If you pass in a File object, you have complete control over the new
+ * // bucket and filename.
+ * //-
+ * const anotherFile = anotherBucket.file('my-awesome-image.png');
+ * file.copy(anotherFile, function(err, copiedFile, apiResponse) {
+ * // `my-bucket` still contains:
+ * // - "my-image.png"
+ * //
+ * // `another-bucket` now contains:
+ * // - "my-awesome-image.png"
+ *
+ * // Note:
+ * // The `copiedFile` parameter is equal to `anotherFile`.
+ * });
+ *
+ * //-
+ * // If the callback is omitted, we'll return a Promise.
+ * //-
+ * file.copy(newLocation).then(function(data) {
+ * const newFile = data[0];
+ * const apiResponse = data[1];
+ * });
+ *
+ * ```
+ * @example include:samples/files.js
+ * region_tag:storage_copy_file
+ * Another example:
+ */
+ copy(
+ destination: string | Bucket | File,
+ optionsOrCallback?: CopyOptions | CopyCallback,
+ callback?: CopyCallback
+ ): Promise | void {
+ const noDestinationError = new Error(
+ FileExceptionMessages.DESTINATION_NO_NAME
+ );
+
+ if (!destination) {
+ throw noDestinationError;
+ }
+
+ let options: CopyOptions = {};
+ if (typeof optionsOrCallback === 'function') {
+ callback = optionsOrCallback;
+ } else if (optionsOrCallback) {
+ options = {...optionsOrCallback};
+ }
+
+ callback = callback || util.noop;
+
+ let destBucket: Bucket;
+ let destName: string;
+ let newFile: File;
+
+ if (typeof destination === 'string') {
+ const parsedDestination = GS_URL_REGEXP.exec(destination);
+ if (parsedDestination !== null && parsedDestination.length === 3) {
+ destBucket = this.storage.bucket(parsedDestination[1]);
+ destName = parsedDestination[2];
+ } else {
+ destBucket = this.bucket;
+ destName = destination;
+ }
+ } else if (destination instanceof Bucket) {
+ destBucket = destination;
+ destName = this.name;
+ } else if (destination instanceof File) {
+ destBucket = destination.bucket;
+ destName = destination.name;
+ newFile = destination;
+ } else {
+ throw noDestinationError;
+ }
+
+ const query = {} as CopyQuery;
+ if (this.generation !== undefined) {
+ query.sourceGeneration = this.generation;
+ }
+ if (options.token !== undefined) {
+ query.rewriteToken = options.token;
+ }
+ if (options.userProject !== undefined) {
+ query.userProject = options.userProject;
+ delete options.userProject;
+ }
+ if (options.predefinedAcl !== undefined) {
+ query.destinationPredefinedAcl = options.predefinedAcl;
+ delete options.predefinedAcl;
+ }
+
+ newFile = newFile! || destBucket.file(destName);
+
+ const headers: {[index: string]: string | undefined} = {};
+
+ if (this.encryptionKey !== undefined) {
+ headers['x-goog-copy-source-encryption-algorithm'] = 'AES256';
+ headers['x-goog-copy-source-encryption-key'] = this.encryptionKeyBase64;
+ headers['x-goog-copy-source-encryption-key-sha256'] =
+ this.encryptionKeyHash;
+ }
+
+ if (newFile.encryptionKey !== undefined) {
+ this.setEncryptionKey(newFile.encryptionKey!);
+ } else if (options.destinationKmsKeyName !== undefined) {
+ query.destinationKmsKeyName = options.destinationKmsKeyName;
+ delete options.destinationKmsKeyName;
+ } else if (newFile.kmsKeyName !== undefined) {
+ query.destinationKmsKeyName = newFile.kmsKeyName;
+ }
+
+ if (query.destinationKmsKeyName) {
+ this.kmsKeyName = query.destinationKmsKeyName;
+
+ const keyIndex = this.interceptors.indexOf(
+ this.encryptionKeyInterceptor!
+ );
+ if (keyIndex > -1) {
+ this.interceptors.splice(keyIndex, 1);
+ }
+ }
+
+ if (
+ !this.shouldRetryBasedOnPreconditionAndIdempotencyStrat(
+ options?.preconditionOpts
+ )
+ ) {
+ this.storage.retryOptions.autoRetry = false;
+ }
+
+ if (options.preconditionOpts?.ifGenerationMatch !== undefined) {
+ query.ifGenerationMatch = options.preconditionOpts?.ifGenerationMatch;
+ delete options.preconditionOpts;
+ }
+
+ this.request(
+ {
+ method: 'POST',
+ uri: `/rewriteTo/b/${destBucket.name}/o/${encodeURIComponent(
+ newFile.name
+ )}`,
+ qs: query,
+ json: options,
+ headers,
+ },
+ (err, resp) => {
+ this.storage.retryOptions.autoRetry = this.instanceRetryValue;
+ if (err) {
+ callback!(err, null, resp);
+ return;
+ }
+
+ if (resp.rewriteToken) {
+ const options = {
+ token: resp.rewriteToken,
+ } as CopyOptions;
+
+ if (query.userProject) {
+ options.userProject = query.userProject;
+ }
+
+ if (query.destinationKmsKeyName) {
+ options.destinationKmsKeyName = query.destinationKmsKeyName;
+ }
+
+ this.copy(newFile, options, callback!);
+ return;
+ }
+
+ callback!(null, newFile, resp);
+ }
+ );
+ }
+
+ /**
+ * @typedef {object} CreateReadStreamOptions Configuration options for File#createReadStream.
+ * @property {string} [userProject] The ID of the project which will be
+ * billed for the request.
+ * @property {string|boolean} [validation] Possible values: `"md5"`,
+ * `"crc32c"`, or `false`. By default, data integrity is validated with a
+ * CRC32c checksum. You may use MD5 if preferred, but that hash is not
+ * supported for composite objects. An error will be raised if MD5 is
+ * specified but is not available. You may also choose to skip validation
+ * completely, however this is **not recommended**.
+ * @property {number} [start] A byte offset to begin the file's download
+ * from. Default is 0. NOTE: Byte ranges are inclusive; that is,
+ * `options.start = 0` and `options.end = 999` represent the first 1000
+ * bytes in a file or object. NOTE: when specifying a byte range, data
+ * integrity is not available.
+ * @property {number} [end] A byte offset to stop reading the file at.
+ * NOTE: Byte ranges are inclusive; that is, `options.start = 0` and
+ * `options.end = 999` represent the first 1000 bytes in a file or object.
+ * NOTE: when specifying a byte range, data integrity is not available.
+ * @property {boolean} [decompress=true] Disable auto decompression of the
+ * received data. By default this option is set to `true`.
+ * Applicable in cases where the data was uploaded with
+ * `gzip: true` option. See {@link File#createWriteStream}.
+ */
+ /**
+ * Create a readable stream to read the contents of the remote file. It can be
+ * piped to a writable stream or listened to for 'data' events to read a
+ * file's contents.
+ *
+ * In the unlikely event there is a mismatch between what you downloaded and
+ * the version in your Bucket, your error handler will receive an error with
+ * code "CONTENT_DOWNLOAD_MISMATCH". If you receive this error, the best
+ * recourse is to try downloading the file again.
+ *
+ * NOTE: Readable streams will emit the `end` event when the file is fully
+ * downloaded.
+ *
+ * @param {CreateReadStreamOptions} [options] Configuration options.
+ * @returns {ReadableStream}
+ *
+ * @example
+ * ```
+ * //-
+ * // Downloading a File
+ * //
+ * // The example below demonstrates how we can reference a remote file, then
+ * // pipe its contents to a local file. This is effectively creating a local
+ * // backup of your remote data.
+ * //-
+ * const {Storage} = require('@google-cloud/storage');
+ * const storage = new Storage();
+ * const bucket = storage.bucket('my-bucket');
+ *
+ * const fs = require('fs');
+ * const remoteFile = bucket.file('image.png');
+ * const localFilename = '/Users/stephen/Photos/image.png';
+ *
+ * remoteFile.createReadStream()
+ * .on('error', function(err) {})
+ * .on('response', function(response) {
+ * // Server connected and responded with the specified status and headers.
+ * })
+ * .on('end', function() {
+ * // The file is fully downloaded.
+ * })
+ * .pipe(fs.createWriteStream(localFilename));
+ *
+ * //-
+ * // To limit the downloaded data to only a byte range, pass an options
+ * // object.
+ * //-
+ * const logFile = myBucket.file('access_log');
+ * logFile.createReadStream({
+ * start: 10000,
+ * end: 20000
+ * })
+ * .on('error', function(err) {})
+ * .pipe(fs.createWriteStream('/Users/stephen/logfile.txt'));
+ *
+ * //-
+ * // To read a tail byte range, specify only `options.end` as a negative
+ * // number.
+ * //-
+ * const logFile = myBucket.file('access_log');
+ * logFile.createReadStream({
+ * end: -100
+ * })
+ * .on('error', function(err) {})
+ * .pipe(fs.createWriteStream('/Users/stephen/logfile.txt'));
+ * ```
+ */
+ createReadStream(options: CreateReadStreamOptions = {}): Readable {
+ options = Object.assign({decompress: true}, options);
+ const rangeRequest =
+ typeof options.start === 'number' || typeof options.end === 'number';
+ const tailRequest = options.end! < 0;
+
+ let validateStream: HashStreamValidator | undefined = undefined;
+ let request: r.Request | undefined = undefined;
+
+ const throughStream = new PassThroughShim();
+
+ let crc32c = true;
+ let md5 = false;
+
+ if (typeof options.validation === 'string') {
+ const value = options.validation.toLowerCase().trim();
+
+ crc32c = value === 'crc32c';
+ md5 = value === 'md5';
+ } else if (options.validation === false) {
+ crc32c = false;
+ }
+
+ const shouldRunValidation = !rangeRequest && (crc32c || md5);
+
+ if (rangeRequest) {
+ if (
+ typeof options.validation === 'string' ||
+ options.validation === true
+ ) {
+ throw new Error(FileExceptionMessages.INVALID_VALIDATION_FILE_RANGE);
+ }
+ // Range requests can't receive data integrity checks.
+ crc32c = false;
+ md5 = false;
+ }
+
+ const onComplete = (err: Error | null) => {
+ if (err) {
+ // There is an issue with node-fetch 2.x that if the stream errors the underlying socket connection is not closed.
+ // This causes a memory leak, so cleanup the sockets manually here by destroying the agent.
+ if (request?.agent) {
+ request.agent.destroy();
+ }
+ throughStream.destroy(err);
+ }
+ };
+
+ // We listen to the response event from the request stream so that we
+ // can...
+ //
+ // 1) Intercept any data from going to the user if an error occurred.
+ // 2) Calculate the hashes from the http.IncomingMessage response
+ // stream,
+ // which will return the bytes from the source without decompressing
+ // gzip'd content. We then send it through decompressed, if
+ // applicable, to the user.
+ const onResponse = (
+ err: Error | null,
+ _body: ResponseBody,
+ rawResponseStream: unknown
+ ) => {
+ if (err) {
+ // Get error message from the body.
+ this.getBufferFromReadable(rawResponseStream as Readable).then(body => {
+ err.message = body.toString('utf8');
+ throughStream.destroy(err);
+ });
+
+ return;
+ }
+
+ request = (rawResponseStream as r.Response).request;
+ const headers = (rawResponseStream as ResponseBody).toJSON().headers;
+ const isCompressed = headers['content-encoding'] === 'gzip';
+ const hashes: {crc32c?: string; md5?: string} = {};
+
+ // The object is safe to validate if:
+ // 1. It was stored gzip and returned to us gzip OR
+ // 2. It was never stored as gzip
+ const safeToValidate =
+ (headers['x-goog-stored-content-encoding'] === 'gzip' &&
+ isCompressed) ||
+ headers['x-goog-stored-content-encoding'] === 'identity';
+
+ const transformStreams: Transform[] = [];
+
+ if (shouldRunValidation) {
+ // The x-goog-hash header should be set with a crc32c and md5 hash.
+ // ex: headers['x-goog-hash'] = 'crc32c=xxxx,md5=xxxx'
+ if (typeof headers['x-goog-hash'] === 'string') {
+ headers['x-goog-hash']
+ .split(',')
+ .forEach((hashKeyValPair: string) => {
+ const delimiterIndex = hashKeyValPair.indexOf('=');
+ const hashType = hashKeyValPair.substring(0, delimiterIndex);
+ const hashValue = hashKeyValPair.substring(delimiterIndex + 1);
+ hashes[hashType as 'crc32c' | 'md5'] = hashValue;
+ });
+ }
+
+ validateStream = new HashStreamValidator({
+ crc32c,
+ md5,
+ crc32cGenerator: this.crc32cGenerator,
+ crc32cExpected: hashes.crc32c,
+ md5Expected: hashes.md5,
+ });
+ }
+
+ if (md5 && !hashes.md5) {
+ const hashError = new RequestError(
+ FileExceptionMessages.MD5_NOT_AVAILABLE
+ );
+ hashError.code = 'MD5_NOT_AVAILABLE';
+ throughStream.destroy(hashError);
+ return;
+ }
+
+ if (safeToValidate && shouldRunValidation && validateStream) {
+ transformStreams.push(validateStream);
+ }
+
+ if (isCompressed && options.decompress) {
+ transformStreams.push(zlib.createGunzip());
+ }
+
+ pipeline(
+ rawResponseStream as Readable,
+ ...(transformStreams as [Transform]),
+ throughStream,
+ onComplete
+ );
+ };
+
+ // Authenticate the request, then pipe the remote API request to the stream
+ // returned to the user.
+ const makeRequest = () => {
+ const query: FileQuery = {alt: 'media'};
+
+ if (this.generation) {
+ query.generation = this.generation;
+ }
+
+ if (options.userProject) {
+ query.userProject = options.userProject;
+ }
+
+ interface Headers {
+ [index: string]: string;
+ }
+
+ const headers = {
+ 'Accept-Encoding': 'gzip',
+ 'Cache-Control': 'no-store',
+ } as Headers;
+
+ if (rangeRequest) {
+ const start = typeof options.start === 'number' ? options.start : '0';
+ const end = typeof options.end === 'number' ? options.end : '';
+
+ headers.Range = `bytes=${tailRequest ? end : `${start}-${end}`}`;
+ }
+
+ const reqOpts: DecorateRequestOptions = {
+ uri: '',
+ headers,
+ qs: query,
+ };
+
+ if (options[GCCL_GCS_CMD_KEY]) {
+ reqOpts[GCCL_GCS_CMD_KEY] = options[GCCL_GCS_CMD_KEY];
+ }
+
+ this.requestStream(reqOpts)
+ .on('error', err => {
+ throughStream.destroy(err);
+ })
+ .on('response', res => {
+ throughStream.emit('response', res);
+ util.handleResp(null, res, null, onResponse);
+ })
+ .resume();
+ };
+ throughStream.on('reading', makeRequest);
+
+ return throughStream;
+ }
+
+ createResumableUpload(
+ options?: CreateResumableUploadOptions
+ ): Promise;
+ createResumableUpload(
+ options: CreateResumableUploadOptions,
+ callback: CreateResumableUploadCallback
+ ): void;
+ createResumableUpload(callback: CreateResumableUploadCallback): void;
+ /**
+ * @callback CreateResumableUploadCallback
+ * @param {?Error} err Request error, if any.
+ * @param {string} uri The resumable upload's unique session URI.
+ */
+ /**
+ * @typedef {array} CreateResumableUploadResponse
+ * @property {string} 0 The resumable upload's unique session URI.
+ */
+ /**
+ * @typedef {object} CreateResumableUploadOptions
+ * @property {object} [metadata] Metadata to set on the file.
+ * @property {number} [offset] The starting byte of the upload stream for resuming an interrupted upload.
+ * @property {string} [origin] Origin header to set for the upload.
+ * @property {string} [predefinedAcl] Apply a predefined set of access
+ * controls to this object.
+ *
+ * Acceptable values are:
+ * - **`authenticatedRead`** - Object owner gets `OWNER` access, and
+ * `allAuthenticatedUsers` get `READER` access.
+ *
+ * - **`bucketOwnerFullControl`** - Object owner gets `OWNER` access, and
+ * project team owners get `OWNER` access.
+ *
+ * - **`bucketOwnerRead`** - Object owner gets `OWNER` access, and project
+ * team owners get `READER` access.
+ *
+ * - **`private`** - Object owner gets `OWNER` access.
+ *
+ * - **`projectPrivate`** - Object owner gets `OWNER` access, and project
+ * team members get access according to their roles.
+ *
+ * - **`publicRead`** - Object owner gets `OWNER` access, and `allUsers`
+ * get `READER` access.
+ * @property {boolean} [private] Make the uploaded file private. (Alias for
+ * `options.predefinedAcl = 'private'`)
+ * @property {boolean} [public] Make the uploaded file public. (Alias for
+ * `options.predefinedAcl = 'publicRead'`)
+ * @property {string} [userProject] The ID of the project which will be
+ * billed for the request.
+ * @property {string} [chunkSize] Create a separate request per chunk. This
+ * value is in bytes and should be a multiple of 256 KiB (2^18).
+ * {@link https://cloud.google.com/storage/docs/performing-resumable-uploads#chunked-upload| We recommend using at least 8 MiB for the chunk size.}
+ */
+ /**
+ * Create a unique resumable upload session URI. This is the first step when
+ * performing a resumable upload.
+ *
+ * See the {@link https://cloud.google.com/storage/docs/json_api/v1/how-tos/resumable-upload| Resumable upload guide}
+ * for more on how the entire process works.
+ *
+ * Note
+ *
+ * If you are just looking to perform a resumable upload without worrying
+ * about any of the details, see {@link File#createWriteStream}. Resumable
+ * uploads are performed by default.
+ *
+ * See {@link https://cloud.google.com/storage/docs/json_api/v1/how-tos/resumable-upload| Resumable upload guide}
+ *
+ * @param {CreateResumableUploadOptions} [options] Configuration options.
+ * @param {CreateResumableUploadCallback} [callback] Callback function.
+ * @returns {Promise}
+ *
+ * @example
+ * ```
+ * const {Storage} = require('@google-cloud/storage');
+ * const storage = new Storage();
+ * const myBucket = storage.bucket('my-bucket');
+ *
+ * const file = myBucket.file('my-file');
+ * file.createResumableUpload(function(err, uri) {
+ * if (!err) {
+ * // `uri` can be used to PUT data to.
+ * }
+ * });
+ *
+ * //-
+ * // If the callback is omitted, we'll return a Promise.
+ * //-
+ * file.createResumableUpload().then(function(data) {
+ * const uri = data[0];
+ * });
+ * ```
+ */
+ createResumableUpload(
+ optionsOrCallback?:
+ | CreateResumableUploadOptions
+ | CreateResumableUploadCallback,
+ callback?: CreateResumableUploadCallback
+ ): void | Promise {
+ const options =
+ typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
+ callback =
+ typeof optionsOrCallback === 'function' ? optionsOrCallback : callback;
+
+ const retryOptions = this.storage.retryOptions;
+ if (
+ (options?.preconditionOpts?.ifGenerationMatch === undefined &&
+ this.instancePreconditionOpts?.ifGenerationMatch === undefined &&
+ this.storage.retryOptions.idempotencyStrategy ===
+ IdempotencyStrategy.RetryConditional) ||
+ this.storage.retryOptions.idempotencyStrategy ===
+ IdempotencyStrategy.RetryNever
+ ) {
+ retryOptions.autoRetry = false;
+ }
+
+ resumableUpload.createURI(
+ {
+ authClient: this.storage.authClient,
+ apiEndpoint: this.storage.apiEndpoint,
+ bucket: this.bucket.name,
+ customRequestOptions: this.getRequestInterceptors().reduce(
+ (reqOpts, interceptorFn) => interceptorFn(reqOpts),
+ {}
+ ),
+ file: this.name,
+ generation: this.generation,
+ key: this.encryptionKey,
+ kmsKeyName: this.kmsKeyName,
+ metadata: options.metadata,
+ offset: options.offset,
+ origin: options.origin,
+ predefinedAcl: options.predefinedAcl,
+ private: options.private,
+ public: options.public,
+ userProject: options.userProject || this.userProject,
+ retryOptions: retryOptions,
+ params: options?.preconditionOpts || this.instancePreconditionOpts,
+ universeDomain: this.bucket.storage.universeDomain,
+ useAuthWithCustomEndpoint: this.storage.useAuthWithCustomEndpoint,
+ [GCCL_GCS_CMD_KEY]: options[GCCL_GCS_CMD_KEY],
+ },
+ callback!
+ );
+ this.storage.retryOptions.autoRetry = this.instanceRetryValue;
+ }
+
+ /**
+ * @typedef {object} CreateWriteStreamOptions Configuration options for File#createWriteStream().
+ * @property {string} [contentType] Alias for
+ * `options.metadata.contentType`. If set to `auto`, the file name is used
+ * to determine the contentType.
+ * @property {string|boolean} [gzip] If true, automatically gzip the file.
+ * If set to `auto`, the contentType is used to determine if the file
+ * should be gzipped. This will set `options.metadata.contentEncoding` to
+ * `gzip` if necessary.
+ * @property {object} [metadata] See the examples below or
+ * {@link https://cloud.google.com/storage/docs/json_api/v1/objects/insert#request_properties_JSON| Objects: insert request body}
+ * for more details.
+ * @property {number} [offset] The starting byte of the upload stream, for
+ * resuming an interrupted upload. Defaults to 0.
+ * @property {string} [predefinedAcl] Apply a predefined set of access
+ * controls to this object.
+ *
+ * Acceptable values are:
+ * - **`authenticatedRead`** - Object owner gets `OWNER` access, and
+ * `allAuthenticatedUsers` get `READER` access.
+ *
+ * - **`bucketOwnerFullControl`** - Object owner gets `OWNER` access, and
+ * project team owners get `OWNER` access.
+ *
+ * - **`bucketOwnerRead`** - Object owner gets `OWNER` access, and project
+ * team owners get `READER` access.
+ *
+ * - **`private`** - Object owner gets `OWNER` access.
+ *
+ * - **`projectPrivate`** - Object owner gets `OWNER` access, and project
+ * team members get access according to their roles.
+ *
+ * - **`publicRead`** - Object owner gets `OWNER` access, and `allUsers`
+ * get `READER` access.
+ * @property {boolean} [private] Make the uploaded file private. (Alias for
+ * `options.predefinedAcl = 'private'`)
+ * @property {boolean} [public] Make the uploaded file public. (Alias for
+ * `options.predefinedAcl = 'publicRead'`)
+ * @property {boolean} [resumable] Force a resumable upload. NOTE: When
+ * working with streams, the file format and size is unknown until it's
+ * completely consumed. Because of this, it's best for you to be explicit
+ * for what makes sense given your input.
+ * @property {number} [timeout=60000] Set the HTTP request timeout in
+ * milliseconds. This option is not available for resumable uploads.
+ * Default: `60000`
+ * @property {string} [uri] The URI for an already-created resumable
+ * upload. See {@link File#createResumableUpload}.
+ * @property {string} [userProject] The ID of the project which will be
+ * billed for the request.
+ * @property {string|boolean} [validation] Possible values: `"md5"`,
+ * `"crc32c"`, or `false`. By default, data integrity is validated with a
+ * CRC32c checksum. You may use MD5 if preferred, but that hash is not
+ * supported for composite objects. An error will be raised if MD5 is
+ * specified but is not available. You may also choose to skip validation
+ * completely, however this is **not recommended**. In addition to specifying
+ * validation type, providing `metadata.crc32c` or `metadata.md5Hash` will
+ * cause the server to perform validation in addition to client validation.
+ * NOTE: Validation is automatically skipped for objects that were
+ * uploaded using the `gzip` option and have already compressed content.
+ */
+ /**
+ * Create a writable stream to overwrite the contents of the file in your
+ * bucket.
+ *
+ * A File object can also be used to create files for the first time.
+ *
+ * Resumable uploads are automatically enabled and must be shut off explicitly
+ * by setting `options.resumable` to `false`.
+ *
+ *
+ *
+ * There is some overhead when using a resumable upload that can cause
+ * noticeable performance degradation while uploading a series of small
+ * files. When uploading files less than 10MB, it is recommended that the
+ * resumable feature is disabled.
+ *
+ *
+ * NOTE: Writable streams will emit the `finish` event when the file is fully
+ * uploaded.
+ *
+ * See {@link https://cloud.google.com/storage/docs/json_api/v1/how-tos/upload Upload Options (Simple or Resumable)}
+ * See {@link https://cloud.google.com/storage/docs/json_api/v1/objects/insert Objects: insert API Documentation}
+ *
+ * @param {CreateWriteStreamOptions} [options] Configuration options.
+ * @returns {WritableStream}
+ *
+ * @example
+ * ```
+ * const fs = require('fs');
+ * const {Storage} = require('@google-cloud/storage');
+ * const storage = new Storage();
+ * const myBucket = storage.bucket('my-bucket');
+ *
+ * const file = myBucket.file('my-file');
+ *
+ * //-
+ * // Uploading a File
+ * //
+ * // Now, consider a case where we want to upload a file to your bucket. You
+ * // have the option of using {@link Bucket#upload}, but that is just
+ * // a convenience method which will do the following.
+ * //-
+ * fs.createReadStream('/Users/stephen/Photos/birthday-at-the-zoo/panda.jpg')
+ * .pipe(file.createWriteStream())
+ * .on('error', function(err) {})
+ * .on('finish', function() {
+ * // The file upload is complete.
+ * });
+ *
+ * //-
+ * // Uploading a File with gzip compression
+ * //-
+ * fs.createReadStream('/Users/stephen/site/index.html')
+ * .pipe(file.createWriteStream({ gzip: true }))
+ * .on('error', function(err) {})
+ * .on('finish', function() {
+ * // The file upload is complete.
+ * });
+ *
+ * //-
+ * // Downloading the file with `createReadStream` will automatically decode
+ * // the file.
+ * //-
+ *
+ * //-
+ * // Uploading a File with Metadata
+ * //
+ * // One last case you may run into is when you want to upload a file to your
+ * // bucket and set its metadata at the same time. Like above, you can use
+ * // {@link Bucket#upload} to do this, which is just a wrapper around
+ * // the following.
+ * //-
+ * fs.createReadStream('/Users/stephen/Photos/birthday-at-the-zoo/panda.jpg')
+ * .pipe(file.createWriteStream({
+ * metadata: {
+ * contentType: 'image/jpeg',
+ * metadata: {
+ * custom: 'metadata'
+ * }
+ * }
+ * }))
+ * .on('error', function(err) {})
+ * .on('finish', function() {
+ * // The file upload is complete.
+ * });
+ * ```
+ *
+ * //-
+ * // Continuing a Resumable Upload
+ * //
+ * // One can capture a `uri` from a resumable upload to reuse later.
+ * // Additionally, for validation, one can also capture and pass `crc32c`.
+ * //-
+ * let uri: string | undefined = undefined;
+ * let resumeCRC32C: string | undefined = undefined;
+ *
+ * fs.createWriteStream()
+ * .on('uri', link => {uri = link})
+ * .on('crc32', crc32c => {resumeCRC32C = crc32c});
+ *
+ * // later...
+ * fs.createWriteStream({uri, resumeCRC32C});
+ */
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ createWriteStream(options: CreateWriteStreamOptions = {}): Writable {
+ options.metadata ??= {};
+
+ if (options.contentType) {
+ options!.metadata!.contentType = options.contentType;
+ }
+
+ if (
+ !options!.metadata!.contentType ||
+ options!.metadata!.contentType === 'auto'
+ ) {
+ const detectedContentType = mime.getType(this.name);
+ if (detectedContentType) {
+ options!.metadata!.contentType = detectedContentType;
+ }
+ }
+
+ let gzip = options.gzip;
+
+ if (gzip === 'auto') {
+ gzip = COMPRESSIBLE_MIME_REGEX.test(options!.metadata!.contentType || '');
+ }
+
+ if (gzip) {
+ options!.metadata!.contentEncoding = 'gzip';
+ }
+
+ let crc32c = true;
+ let md5 = false;
+
+ if (typeof options.validation === 'string') {
+ options.validation = options.validation.toLowerCase();
+ crc32c = options.validation === 'crc32c';
+ md5 = options.validation === 'md5';
+ } else if (options.validation === false) {
+ crc32c = false;
+ md5 = false;
+ }
+
+ if (options.offset) {
+ if (md5) {
+ throw new RangeError(FileExceptionMessages.MD5_RESUMED_UPLOAD);
+ }
+
+ if (crc32c && !options.isPartialUpload && !options.resumeCRC32C) {
+ throw new RangeError(
+ FileExceptionMessages.MISSING_RESUME_CRC32C_FINAL_UPLOAD
+ );
+ }
+ }
+
+ /**
+ * A callback for determining when the underlying pipeline is complete.
+ * It's possible the pipeline callback could error before the write stream
+ * calls `final` so by default this will destroy the write stream unless the
+ * write stream sets this callback via its `final` handler.
+ * @param error An optional error
+ */
+ let pipelineCallback: (error?: Error | null) => void = error => {
+ writeStream.destroy(error || undefined);
+ };
+
+ // A stream for consumer to write to
+ const writeStream = new Writable({
+ final(cb) {
+ // Set the pipeline callback to this callback so the pipeline's results
+ // can be populated to the consumer
+ pipelineCallback = cb;
+
+ emitStream.end();
+ },
+ write(chunk, encoding, cb) {
+ emitStream.write(chunk, encoding, cb);
+ },
+ });
+ // If the write stream, which is returned to the caller, catches an error we need to make sure that
+ // at least one of the streams in the pipeline below gets notified so that they
+ // all get cleaned up / destroyed.
+ writeStream.once('error', e => {
+ emitStream.destroy(e);
+ });
+ // If the write stream is closed, cleanup the pipeline below by calling destroy on one of the streams.
+ writeStream.once('close', () => {
+ emitStream.destroy();
+ });
+
+ const transformStreams: Transform[] = [];
+
+ if (gzip) {
+ transformStreams.push(zlib.createGzip());
+ }
+
+ const emitStream = new PassThroughShim();
+
+ // If `writeStream` is destroyed before the `writing` event, `emitStream` will not have any listeners. This prevents an unhandled error.
+ const noop = () => {};
+ emitStream.on('error', noop);
+
+ let hashCalculatingStream: HashStreamValidator | null = null;
+
+ if (crc32c || md5) {
+ const crc32cInstance = options.resumeCRC32C
+ ? CRC32C.from(options.resumeCRC32C)
+ : undefined;
+
+ hashCalculatingStream = new HashStreamValidator({
+ crc32c,
+ crc32cInstance,
+ md5,
+ crc32cGenerator: this.crc32cGenerator,
+ updateHashesOnly: true,
+ });
+
+ transformStreams.push(hashCalculatingStream);
+ }
+
+ const fileWriteStream = duplexify();
+ let fileWriteStreamMetadataReceived = false;
+
+ // Handing off emitted events to users
+ emitStream.on('reading', () => writeStream.emit('reading'));
+ emitStream.on('writing', () => writeStream.emit('writing'));
+ fileWriteStream.on('uri', evt => writeStream.emit('uri', evt));
+ fileWriteStream.on('progress', evt => writeStream.emit('progress', evt));
+ fileWriteStream.on('response', resp => writeStream.emit('response', resp));
+ fileWriteStream.once('metadata', () => {
+ fileWriteStreamMetadataReceived = true;
+ });
+
+ writeStream.once('writing', () => {
+ if (options.resumable === false) {
+ this.startSimpleUpload_(fileWriteStream, options);
+ } else {
+ this.startResumableUpload_(fileWriteStream, options);
+ }
+
+ // remove temporary noop listener as we now create a pipeline that handles the errors
+ emitStream.removeListener('error', noop);
+
+ pipeline(
+ emitStream,
+ ...(transformStreams as [Transform]),
+ fileWriteStream,
+ async e => {
+ if (e) {
+ return pipelineCallback(e);
+ }
+
+ // If this is a partial upload, we don't expect final metadata yet.
+ if (options.isPartialUpload) {
+ // Emit CRC32c for this completed chunk if hash validation is active.
+ if (hashCalculatingStream?.crc32c) {
+ writeStream.emit('crc32c', hashCalculatingStream.crc32c);
+ }
+ // Resolve the pipeline for this *partial chunk*.
+ return pipelineCallback();
+ }
+
+ // We want to make sure we've received the metadata from the server in order
+ // to properly validate the object's integrity. Depending on the type of upload,
+ // the stream could close before the response is returned.
+ if (!fileWriteStreamMetadataReceived) {
+ try {
+ await new Promise((resolve, reject) => {
+ fileWriteStream.once('metadata', resolve);
+ fileWriteStream.once('error', reject);
+ });
+ } catch (e) {
+ return pipelineCallback(e as Error);
+ }
+ }
+
+ // Emit the local CRC32C value for future validation, if validation is enabled.
+ if (hashCalculatingStream?.crc32c) {
+ writeStream.emit('crc32c', hashCalculatingStream.crc32c);
+ }
+
+ try {
+ // Metadata may not be ready if the upload is a partial upload,
+ // nothing to validate yet.
+ const metadataNotReady = options.isPartialUpload && !this.metadata;
+
+ if (hashCalculatingStream && !metadataNotReady) {
+ await this.#validateIntegrity(hashCalculatingStream, {
+ crc32c,
+ md5,
+ });
+ }
+
+ pipelineCallback();
+ } catch (e) {
+ pipelineCallback(e as Error);
+ }
+ }
+ );
+ });
+
+ return writeStream;
+ }
+
+ /**
+ * Delete the object.
+ *
+ * @param {function=} callback - The callback function.
+ * @param {?error} callback.err - An error returned while making this request.
+ * @param {object} callback.apiResponse - The full API response.
+ */
+ delete(options?: DeleteOptions): Promise<[r.Response]>;
+ delete(options: DeleteOptions, callback: DeleteCallback): void;
+ delete(callback: DeleteCallback): void;
+ delete(
+ optionsOrCallback?: DeleteOptions | DeleteCallback,
+ cb?: DeleteCallback
+ ): Promise<[r.Response]> | void {
+ const options =
+ typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
+ cb = typeof optionsOrCallback === 'function' ? optionsOrCallback : cb;
+
+ this.disableAutoRetryConditionallyIdempotent_(
+ this.methods.delete,
+ AvailableServiceObjectMethods.delete,
+ options
+ );
+
+ super
+ .delete(options)
+ .then(resp => cb!(null, ...resp))
+ .catch(cb!)
+ .finally(() => {
+ this.storage.retryOptions.autoRetry = this.instanceRetryValue;
+ });
+ }
+
+ download(options?: DownloadOptions): Promise;
+ download(options: DownloadOptions, callback: DownloadCallback): void;
+ download(callback: DownloadCallback): void;
+ /**
+ * @typedef {array} DownloadResponse
+ * @property [0] The contents of a File.
+ */
+ /**
+ * @callback DownloadCallback
+ * @param err Request error, if any.
+ * @param contents The contents of a File.
+ */
+ /**
+ * Convenience method to download a file into memory or to a local
+ * destination.
+ *
+ * @param {object} [options] Configuration options. The arguments match those
+ * passed to {@link File#createReadStream}.
+ * @param {string} [options.destination] Local file path to write the file's
+ * contents to.
+ * @param {string} [options.userProject] The ID of the project which will be
+ * billed for the request.
+ * @param {DownloadCallback} [callback] Callback function.
+ * @returns {Promise}
+ *
+ * @example
+ * ```
+ * const {Storage} = require('@google-cloud/storage');
+ * const storage = new Storage();
+ * const myBucket = storage.bucket('my-bucket');
+ *
+ * const file = myBucket.file('my-file');
+ *
+ * //-
+ * // Download a file into memory. The contents will be available as the
+ * second
+ * // argument in the demonstration below, `contents`.
+ * //-
+ * file.download(function(err, contents) {});
+ *
+ * //-
+ * // Download a file to a local destination.
+ * //-
+ * file.download({
+ * destination: '/Users/me/Desktop/file-backup.txt'
+ * }, function(err) {});
+ *
+ * //-
+ * // If the callback is omitted, we'll return a Promise.
+ * //-
+ * file.download().then(function(data) {
+ * const contents = data[0];
+ * });
+ *
+ * ```
+ * @example include:samples/files.js
+ * region_tag:storage_download_file
+ * Another example:
+ *
+ * @example include:samples/encryption.js
+ * region_tag:storage_download_encrypted_file
+ * Example of downloading an encrypted file:
+ *
+ * @example include:samples/requesterPays.js
+ * region_tag:storage_download_file_requester_pays
+ * Example of downloading a file where the requester pays:
+ */
+ download(
+ optionsOrCallback?: DownloadOptions | DownloadCallback,
+ cb?: DownloadCallback
+ ): Promise | void {
+ let options: DownloadOptions;
+ if (typeof optionsOrCallback === 'function') {
+ cb = optionsOrCallback as DownloadCallback;
+ options = {};
+ } else {
+ options = Object.assign({}, optionsOrCallback);
+ }
+
+ let called = false;
+ const callback = ((...args) => {
+ if (!called) cb!(...args);
+ called = true;
+ }) as DownloadCallback;
+
+ const destination = options.destination;
+ delete options.destination;
+
+ if (options.encryptionKey) {
+ this.setEncryptionKey(options.encryptionKey);
+ delete options.encryptionKey;
+ }
+
+ const fileStream = this.createReadStream(options);
+ let receivedData = false;
+
+ if (destination) {
+ fileStream
+ .on('error', callback)
+ .once('data', data => {
+ receivedData = true;
+ // We know that the file exists the server - now we can truncate/write to a file
+ const writable = fs.createWriteStream(destination);
+ writable.write(data);
+ fileStream
+ .pipe(writable)
+ .on('error', (err: Error) => {
+ callback(err, Buffer.from(''));
+ })
+ .on('finish', () => {
+ callback(null, data);
+ });
+ })
+ .on('end', () => {
+ // In the case of an empty file no data will be received before the end event fires
+ if (!receivedData) {
+ const data = Buffer.alloc(0);
+
+ try {
+ fs.writeFileSync(destination, data);
+ callback(null, data);
+ } catch (e) {
+ callback(e as Error, data);
+ }
+ }
+ });
+ } else {
+ this.getBufferFromReadable(fileStream)
+ .then(contents => callback?.(null, contents))
+ .catch(callback as (err: RequestError) => void);
+ }
+ }
+
+ /**
+ * The Storage API allows you to use a custom key for server-side encryption.
+ *
+ * See {@link https://cloud.google.com/storage/docs/encryption#customer-supplied| Customer-supplied Encryption Keys}
+ *
+ * @param {string|buffer} encryptionKey An AES-256 encryption key.
+ * @returns {File}
+ *
+ * @example
+ * ```
+ * const crypto = require('crypto');
+ * const {Storage} = require('@google-cloud/storage');
+ * const storage = new Storage();
+ * const myBucket = storage.bucket('my-bucket');
+ *
+ * const encryptionKey = crypto.randomBytes(32);
+ *
+ * const fileWithCustomEncryption = myBucket.file('my-file');
+ * fileWithCustomEncryption.setEncryptionKey(encryptionKey);
+ *
+ * const fileWithoutCustomEncryption = myBucket.file('my-file');
+ *
+ * fileWithCustomEncryption.save('data', function(err) {
+ * // Try to download with the File object that hasn't had
+ * // `setEncryptionKey()` called:
+ * fileWithoutCustomEncryption.download(function(err) {
+ * // We will receive an error:
+ * // err.message === 'Bad Request'
+ *
+ * // Try again with the File object we called `setEncryptionKey()` on:
+ * fileWithCustomEncryption.download(function(err, contents) {
+ * // contents.toString() === 'data'
+ * });
+ * });
+ * });
+ *
+ * ```
+ * @example include:samples/encryption.js
+ * region_tag:storage_upload_encrypted_file
+ * Example of uploading an encrypted file:
+ *
+ * @example include:samples/encryption.js
+ * region_tag:storage_download_encrypted_file
+ * Example of downloading an encrypted file:
+ */
+ setEncryptionKey(encryptionKey: string | Buffer) {
+ this.encryptionKey = encryptionKey;
+ this.encryptionKeyBase64 = Buffer.from(encryptionKey as string).toString(
+ 'base64'
+ );
+ this.encryptionKeyHash = crypto
+ .createHash('sha256')
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ .update(this.encryptionKeyBase64, 'base64' as any)
+ .digest('base64');
+
+ this.encryptionKeyInterceptor = {
+ request: reqOpts => {
+ reqOpts.headers = reqOpts.headers || {};
+ reqOpts.headers['x-goog-encryption-algorithm'] = 'AES256';
+ reqOpts.headers['x-goog-encryption-key'] = this.encryptionKeyBase64;
+ reqOpts.headers['x-goog-encryption-key-sha256'] =
+ this.encryptionKeyHash;
+ return reqOpts as DecorateRequestOptions;
+ },
+ };
+
+ this.interceptors.push(this.encryptionKeyInterceptor!);
+
+ return this;
+ }
+
+ /**
+ * Gets a reference to a Cloud Storage {@link File} file from the provided URL in string format.
+ * @param {string} publicUrlOrGsUrl the URL as a string. Must be of the format gs://bucket/file
+ * or https://storage.googleapis.com/bucket/file.
+ * @param {Storage} storageInstance an instance of a Storage object.
+ * @param {FileOptions} [options] Configuration options
+ * @returns {File}
+ */
+ static from(
+ publicUrlOrGsUrl: string,
+ storageInstance: Storage,
+ options?: FileOptions
+ ): File {
+ const gsMatches = [...publicUrlOrGsUrl.matchAll(GS_UTIL_URL_REGEX)];
+ const httpsMatches = [...publicUrlOrGsUrl.matchAll(HTTPS_PUBLIC_URL_REGEX)];
+
+ if (gsMatches.length > 0) {
+ const bucket = new Bucket(storageInstance, gsMatches[0][2]);
+ return new File(bucket, gsMatches[0][3], options);
+ } else if (httpsMatches.length > 0) {
+ const bucket = new Bucket(storageInstance, httpsMatches[0][3]);
+ return new File(bucket, httpsMatches[0][4], options);
+ } else {
+ throw new Error(
+ 'URL string must be of format gs://bucket/file or https://storage.googleapis.com/bucket/file'
+ );
+ }
+ }
+
+ get(options?: GetFileOptions): Promise>;
+ get(callback: InstanceResponseCallback): void;
+ get(options: GetFileOptions, callback: InstanceResponseCallback): void;
+ get(
+ optionsOrCallback?: GetFileOptions | InstanceResponseCallback,
+ cb?: InstanceResponseCallback
+ ): Promise> | void {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const options: any =
+ typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
+ cb =
+ typeof optionsOrCallback === 'function'
+ ? (optionsOrCallback as InstanceResponseCallback)
+ : cb;
+
+ super
+ .get(options)
+ .then(resp => cb!(null, ...resp))
+ .catch(cb!);
+ }
+
+ getExpirationDate(): Promise;
+ getExpirationDate(callback: GetExpirationDateCallback): void;
+ /**
+ * @typedef {array} GetExpirationDateResponse
+ * @property {date} 0 A Date object representing the earliest time this file's
+ * retention policy will expire.
+ */
+ /**
+ * @callback GetExpirationDateCallback
+ * @param {?Error} err Request error, if any.
+ * @param {date} expirationDate A Date object representing the earliest time
+ * this file's retention policy will expire.
+ */
+ /**
+ * If this bucket has a retention policy defined, use this method to get a
+ * Date object representing the earliest time this file will expire.
+ *
+ * @param {GetExpirationDateCallback} [callback] Callback function.
+ * @returns {Promise}
+ *
+ * @example
+ * ```
+ * const storage = require('@google-cloud/storage')();
+ * const myBucket = storage.bucket('my-bucket');
+ *
+ * const file = myBucket.file('my-file');
+ *
+ * file.getExpirationDate(function(err, expirationDate) {
+ * // expirationDate is a Date object.
+ * });
+ * ```
+ */
+ getExpirationDate(
+ callback?: GetExpirationDateCallback
+ ): void | Promise {
+ this.getMetadata(
+ (err: ApiError | null, metadata: FileMetadata, apiResponse: unknown) => {
+ if (err) {
+ callback!(err, null, apiResponse);
+ return;
+ }
+
+ if (!metadata.retentionExpirationTime) {
+ const error = new Error(FileExceptionMessages.EXPIRATION_TIME_NA);
+ callback!(error, null, apiResponse);
+ return;
+ }
+
+ callback!(
+ null,
+ new Date(metadata.retentionExpirationTime),
+ apiResponse
+ );
+ }
+ );
+ }
+
+ generateSignedPostPolicyV2(
+ options: GenerateSignedPostPolicyV2Options
+ ): Promise;
+ generateSignedPostPolicyV2(
+ options: GenerateSignedPostPolicyV2Options,
+ callback: GenerateSignedPostPolicyV2Callback
+ ): void;
+ generateSignedPostPolicyV2(
+ callback: GenerateSignedPostPolicyV2Callback
+ ): void;
+ /**
+ * @typedef {array} GenerateSignedPostPolicyV2Response
+ * @property {object} 0 The document policy.
+ */
+ /**
+ * @callback GenerateSignedPostPolicyV2Callback
+ * @param {?Error} err Request error, if any.
+ * @param {object} policy The document policy.
+ */
+ /**
+ * Get a signed policy document to allow a user to upload data with a POST
+ * request.
+ *
+ * In Google Cloud Platform environments, such as Cloud Functions and App
+ * Engine, you usually don't provide a `keyFilename` or `credentials` during
+ * instantiation. In those environments, we call the
+ * {@link https://cloud.google.com/iam/docs/reference/credentials/rest/v1/projects.serviceAccounts/signBlob| signBlob API}
+ * to create a signed policy. That API requires either the
+ * `https://www.googleapis.com/auth/iam` or
+ * `https://www.googleapis.com/auth/cloud-platform` scope, so be sure they are
+ * enabled.
+ *
+ * See {@link https://cloud.google.com/storage/docs/xml-api/post-object-v2| POST Object with the V2 signing process}
+ *
+ * @throws {Error} If an expiration timestamp from the past is given.
+ * @throws {Error} If options.equals has an array with less or more than two
+ * members.
+ * @throws {Error} If options.startsWith has an array with less or more than two
+ * members.
+ *
+ * @param {object} options Configuration options.
+ * @param {array|array[]} [options.equals] Array of request parameters and
+ * their expected value (e.g. [['$', '']]). Values are
+ * translated into equality constraints in the conditions field of the
+ * policy document (e.g. ['eq', '$', '']). If only one
+ * equality condition is to be specified, options.equals can be a one-
+ * dimensional array (e.g. ['$', '']).
+ * @param {*} options.expires - A timestamp when this policy will expire. Any
+ * value given is passed to `new Date()`.
+ * @param {array|array[]} [options.startsWith] Array of request parameters and
+ * their expected prefixes (e.g. [['$', '']). Values are
+ * translated into starts-with constraints in the conditions field of the
+ * policy document (e.g. ['starts-with', '$', '']). If only
+ * one prefix condition is to be specified, options.startsWith can be a
+ * one- dimensional array (e.g. ['$', '']).
+ * @param {string} [options.acl] ACL for the object from possibly predefined
+ * ACLs.
+ * @param {string} [options.successRedirect] The URL to which the user client
+ * is redirected if the upload is successful.
+ * @param {string} [options.successStatus] - The status of the Google Storage
+ * response if the upload is successful (must be string).
+ * @param {object} [options.contentLengthRange]
+ * @param {number} [options.contentLengthRange.min] Minimum value for the
+ * request's content length.
+ * @param {number} [options.contentLengthRange.max] Maximum value for the
+ * request's content length.
+ * @param {GenerateSignedPostPolicyV2Callback} [callback] Callback function.
+ * @returns {Promise}
+ *
+ * @example
+ * ```
+ * const {Storage} = require('@google-cloud/storage');
+ * const storage = new Storage();
+ * const myBucket = storage.bucket('my-bucket');
+ *
+ * const file = myBucket.file('my-file');
+ * const options = {
+ * equals: ['$Content-Type', 'image/jpeg'],
+ * expires: '10-25-2022',
+ * contentLengthRange: {
+ * min: 0,
+ * max: 1024
+ * }
+ * };
+ *
+ * file.generateSignedPostPolicyV2(options, function(err, policy) {
+ * // policy.string: the policy document in plain text.
+ * // policy.base64: the policy document in base64.
+ * // policy.signature: the policy signature in base64.
+ * });
+ *
+ * //-
+ * // If the callback is omitted, we'll return a Promise.
+ * //-
+ * file.generateSignedPostPolicyV2(options).then(function(data) {
+ * const policy = data[0];
+ * });
+ * ```
+ */
+ generateSignedPostPolicyV2(
+ optionsOrCallback?:
+ | GenerateSignedPostPolicyV2Options
+ | GenerateSignedPostPolicyV2Callback,
+ cb?: GenerateSignedPostPolicyV2Callback
+ ): void | Promise {
+ const args = normalize(
+ optionsOrCallback,
+ cb
+ );
+ let options = args.options;
+ const callback = args.callback;
+ const expires = new Date(
+ (options as GenerateSignedPostPolicyV2Options).expires
+ );
+
+ if (isNaN(expires.getTime())) {
+ throw new Error(ExceptionMessages.EXPIRATION_DATE_INVALID);
+ }
+
+ if (expires.valueOf() < Date.now()) {
+ throw new Error(ExceptionMessages.EXPIRATION_DATE_PAST);
+ }
+
+ options = Object.assign({}, options);
+
+ const conditions = [
+ ['eq', '$key', this.name],
+ {
+ bucket: this.bucket.name,
+ },
+ ] as object[];
+
+ if (Array.isArray(options.equals)) {
+ if (!Array.isArray((options.equals as string[][])[0])) {
+ options.equals = [options.equals as string[]];
+ }
+ (options.equals as string[][]).forEach(condition => {
+ if (!Array.isArray(condition) || condition.length !== 2) {
+ throw new Error(FileExceptionMessages.EQUALS_CONDITION_TWO_ELEMENTS);
+ }
+ conditions.push(['eq', condition[0], condition[1]]);
+ });
+ }
+
+ if (Array.isArray(options.startsWith)) {
+ if (!Array.isArray((options.startsWith as string[][])[0])) {
+ options.startsWith = [options.startsWith as string[]];
+ }
+ (options.startsWith as string[][]).forEach(condition => {
+ if (!Array.isArray(condition) || condition.length !== 2) {
+ throw new Error(FileExceptionMessages.STARTS_WITH_TWO_ELEMENTS);
+ }
+ conditions.push(['starts-with', condition[0], condition[1]]);
+ });
+ }
+
+ if (options.acl) {
+ conditions.push({
+ acl: options.acl,
+ });
+ }
+
+ if (options.successRedirect) {
+ conditions.push({
+ success_action_redirect: options.successRedirect,
+ });
+ }
+
+ if (options.successStatus) {
+ conditions.push({
+ success_action_status: options.successStatus,
+ });
+ }
+
+ if (options.contentLengthRange) {
+ const min = options.contentLengthRange.min;
+ const max = options.contentLengthRange.max;
+ if (typeof min !== 'number' || typeof max !== 'number') {
+ throw new Error(FileExceptionMessages.CONTENT_LENGTH_RANGE_MIN_MAX);
+ }
+ conditions.push(['content-length-range', min, max]);
+ }
+
+ const policy = {
+ expiration: expires.toISOString(),
+ conditions,
+ };
+
+ const policyString = JSON.stringify(policy);
+ const policyBase64 = Buffer.from(policyString).toString('base64');
+
+ this.storage.authClient.sign(policyBase64, options.signingEndpoint).then(
+ signature => {
+ callback(null, {
+ string: policyString,
+ base64: policyBase64,
+ signature,
+ });
+ },
+ err => {
+ callback(new SigningError(err.message));
+ }
+ );
+ }
+
+ generateSignedPostPolicyV4(
+ options: GenerateSignedPostPolicyV4Options
+ ): Promise;
+ generateSignedPostPolicyV4(
+ options: GenerateSignedPostPolicyV4Options,
+ callback: GenerateSignedPostPolicyV4Callback
+ ): void;
+ generateSignedPostPolicyV4(
+ callback: GenerateSignedPostPolicyV4Callback
+ ): void;
+ /**
+ * @typedef {object} SignedPostPolicyV4Output
+ * @property {string} url The request URL.
+ * @property {object} fields The form fields to include in the POST request.
+ */
+ /**
+ * @typedef {array} GenerateSignedPostPolicyV4Response
+ * @property {SignedPostPolicyV4Output} 0 An object containing the request URL and form fields.
+ */
+ /**
+ * @callback GenerateSignedPostPolicyV4Callback
+ * @param {?Error} err Request error, if any.
+ * @param {SignedPostPolicyV4Output} output An object containing the request URL and form fields.
+ */
+ /**
+ * Get a v4 signed policy document to allow a user to upload data with a POST
+ * request.
+ *
+ * In Google Cloud Platform environments, such as Cloud Functions and App
+ * Engine, you usually don't provide a `keyFilename` or `credentials` during
+ * instantiation. In those environments, we call the
+ * {@link https://cloud.google.com/iam/docs/reference/credentials/rest/v1/projects.serviceAccounts/signBlob| signBlob API}
+ * to create a signed policy. That API requires either the
+ * `https://www.googleapis.com/auth/iam` or
+ * `https://www.googleapis.com/auth/cloud-platform` scope, so be sure they are
+ * enabled.
+ *
+ * See {@link https://cloud.google.com/storage/docs/xml-api/post-object#policydocument| Policy Document Reference}
+ *
+ * @param {object} options Configuration options.
+ * @param {Date|number|string} options.expires - A timestamp when this policy will expire. Any
+ * value given is passed to `new Date()`.
+ * @param {boolean} [config.virtualHostedStyle=false] Use virtual hosted-style
+ * URLs ('https://mybucket.storage.googleapis.com/...') instead of path-style
+ * ('https://storage.googleapis.com/mybucket/...'). Virtual hosted-style URLs
+ * should generally be preferred instead of path-style URL.
+ * Currently defaults to `false` for path-style, although this may change in a
+ * future major-version release.
+ * @param {string} [config.bucketBoundHostname] The bucket-bound hostname to return in
+ * the result, e.g. "https://cdn.example.com".
+ * @param {object} [config.fields] [Form fields]{@link https://cloud.google.com/storage/docs/xml-api/post-object#policydocument}
+ * to include in the signed policy. Any fields with key beginning with 'x-ignore-'
+ * will not be included in the policy to be signed.
+ * @param {object[]} [config.conditions] [Conditions]{@link https://cloud.google.com/storage/docs/authentication/signatures#policy-document}
+ * to include in the signed policy. All fields given in `config.fields` are
+ * automatically included in the conditions array, adding the same entry
+ * in both `fields` and `conditions` will result in duplicate entries.
+ *
+ * @param {GenerateSignedPostPolicyV4Callback} [callback] Callback function.
+ * @returns {Promise}
+ *
+ * @example
+ * ```
+ * const {Storage} = require('@google-cloud/storage');
+ * const storage = new Storage();
+ * const myBucket = storage.bucket('my-bucket');
+ *
+ * const file = myBucket.file('my-file');
+ * const options = {
+ * expires: '10-25-2022',
+ * conditions: [
+ * ['eq', '$Content-Type', 'image/jpeg'],
+ * ['content-length-range', 0, 1024],
+ * ],
+ * fields: {
+ * acl: 'public-read',
+ * 'x-goog-meta-foo': 'bar',
+ * 'x-ignore-mykey': 'data'
+ * }
+ * };
+ *
+ * file.generateSignedPostPolicyV4(options, function(err, response) {
+ * // response.url The request URL
+ * // response.fields The form fields (including the signature) to include
+ * // to be used to upload objects by HTML forms.
+ * });
+ *
+ * //-
+ * // If the callback is omitted, we'll return a Promise.
+ * //-
+ * file.generateSignedPostPolicyV4(options).then(function(data) {
+ * const response = data[0];
+ * // response.url The request URL
+ * // response.fields The form fields (including the signature) to include
+ * // to be used to upload objects by HTML forms.
+ * });
+ * ```
+ */
+ generateSignedPostPolicyV4(
+ optionsOrCallback?:
+ | GenerateSignedPostPolicyV4Options
+ | GenerateSignedPostPolicyV4Callback,
+ cb?: GenerateSignedPostPolicyV4Callback
+ ): void | Promise {
+ const args = normalize<
+ GenerateSignedPostPolicyV4Options,
+ GenerateSignedPostPolicyV4Callback
+ >(optionsOrCallback, cb);
+ let options = args.options;
+ const callback = args.callback;
+ const expires = new Date(
+ (options as GenerateSignedPostPolicyV4Options).expires
+ );
+
+ if (isNaN(expires.getTime())) {
+ throw new Error(ExceptionMessages.EXPIRATION_DATE_INVALID);
+ }
+
+ if (expires.valueOf() < Date.now()) {
+ throw new Error(ExceptionMessages.EXPIRATION_DATE_PAST);
+ }
+
+ if (expires.valueOf() - Date.now() > SEVEN_DAYS * 1000) {
+ throw new Error(
+ `Max allowed expiration is seven days (${SEVEN_DAYS} seconds).`
+ );
+ }
+
+ options = Object.assign({}, options);
+ let fields = Object.assign({}, options.fields);
+
+ const now = new Date();
+ const nowISO = formatAsUTCISO(now, true);
+ const todayISO = formatAsUTCISO(now);
+
+ const sign = async () => {
+ const {client_email} = await this.storage.authClient.getCredentials();
+ const credential = `${client_email}/${todayISO}/auto/storage/goog4_request`;
+
+ fields = {
+ ...fields,
+ bucket: this.bucket.name,
+ key: this.name,
+ 'x-goog-date': nowISO,
+ 'x-goog-credential': credential,
+ 'x-goog-algorithm': 'GOOG4-RSA-SHA256',
+ };
+
+ const conditions = options.conditions || [];
+
+ Object.entries(fields).forEach(([key, value]) => {
+ if (!key.startsWith('x-ignore-')) {
+ conditions.push({[key]: value});
+ }
+ });
+
+ delete fields.bucket;
+
+ const expiration = formatAsUTCISO(expires, true, '-', ':');
+
+ const policy = {
+ conditions,
+ expiration,
+ };
+
+ const policyString = unicodeJSONStringify(policy);
+ const policyBase64 = Buffer.from(policyString).toString('base64');
+
+ try {
+ const signature = await this.storage.authClient.sign(
+ policyBase64,
+ options.signingEndpoint
+ );
+ const signatureHex = Buffer.from(signature, 'base64').toString('hex');
+ const universe = this.parent.storage.universeDomain;
+ fields['policy'] = policyBase64;
+ fields['x-goog-signature'] = signatureHex;
+
+ let url: string;
+
+ const EMULATOR_HOST = process.env.STORAGE_EMULATOR_HOST;
+
+ if (this.storage.customEndpoint && typeof EMULATOR_HOST === 'string') {
+ url = `${this.storage.apiEndpoint}/${this.bucket.name}`;
+ } else if (this.storage.customEndpoint) {
+ url = this.storage.apiEndpoint;
+ } else if (options.virtualHostedStyle) {
+ url = `https://${this.bucket.name}.storage.${universe}/`;
+ } else if (options.bucketBoundHostname) {
+ url = `${options.bucketBoundHostname}/`;
+ } else {
+ url = `https://storage.${universe}/${this.bucket.name}/`;
+ }
+
+ return {
+ url,
+ fields,
+ };
+ } catch (err) {
+ throw new SigningError((err as Error).message);
+ }
+ };
+
+ sign().then(res => callback!(null, res), callback!);
+ }
+
+ getSignedUrl(cfg: GetSignedUrlConfig): Promise;
+ getSignedUrl(cfg: GetSignedUrlConfig, callback: GetSignedUrlCallback): void;
+ /**
+ * @typedef {array} GetSignedUrlResponse
+ * @property {object} 0 The signed URL.
+ */
+ /**
+ * @callback GetSignedUrlCallback
+ * @param {?Error} err Request error, if any.
+ * @param {object} url The signed URL.
+ */
+ /**
+ * Get a signed URL to allow limited time access to the file.
+ *
+ * In Google Cloud Platform environments, such as Cloud Functions and App
+ * Engine, you usually don't provide a `keyFilename` or `credentials` during
+ * instantiation. In those environments, we call the
+ * {@link https://cloud.google.com/iam/docs/reference/credentials/rest/v1/projects.serviceAccounts/signBlob| signBlob API}
+ * to create a signed URL. That API requires either the
+ * `https://www.googleapis.com/auth/iam` or
+ * `https://www.googleapis.com/auth/cloud-platform` scope, so be sure they are
+ * enabled.
+ *
+ * See {@link https://cloud.google.com/storage/docs/access-control/signed-urls| Signed URLs Reference}
+ *
+ * @throws {Error} if an expiration timestamp from the past is given.
+ *
+ * @param {object} config Configuration object.
+ * @param {string} config.action "read" (HTTP: GET), "write" (HTTP: PUT), or
+ * "delete" (HTTP: DELETE), "resumable" (HTTP: POST).
+ * When using "resumable", the header `X-Goog-Resumable: start` has
+ * to be sent when making a request with the signed URL.
+ * @param {*} config.expires A timestamp when this link will expire. Any value
+ * given is passed to `new Date()`.
+ * Note: 'v4' supports maximum duration of 7 days (604800 seconds) from now.
+ * See [reference]{@link https://cloud.google.com/storage/docs/access-control/signed-urls#example}
+ * @param {string} [config.version='v2'] The signing version to use, either
+ * 'v2' or 'v4'.
+ * @param {boolean} [config.virtualHostedStyle=false] Use virtual hosted-style
+ * URLs (e.g. 'https://mybucket.storage.googleapis.com/...') instead of path-style
+ * (e.g. 'https://storage.googleapis.com/mybucket/...'). Virtual hosted-style URLs
+ * should generally be preferred instead of path-style URL.
+ * Currently defaults to `false` for path-style, although this may change in a
+ * future major-version release.
+ * @param {string} [config.cname] The cname for this bucket, i.e.,
+ * "https://cdn.example.com".
+ * @param {string} [config.contentMd5] The MD5 digest value in base64. Just like
+ * if you provide this, the client must provide this HTTP header with this same
+ * value in its request, so to if this parameter is not provided here,
+ * the client must not provide any value for this HTTP header in its request.
+ * @param {string} [config.contentType] Just like if you provide this, the client
+ * must provide this HTTP header with this same value in its request, so to if
+ * this parameter is not provided here, the client must not provide any value
+ * for this HTTP header in its request.
+ * @param {object} [config.extensionHeaders] If these headers are used, the
+ * server will check to make sure that the client provides matching
+ * values. See {@link https://cloud.google.com/storage/docs/access-control/signed-urls#about-canonical-extension-headers| Canonical extension headers}
+ * for the requirements of this feature, most notably:
+ * - The header name must be prefixed with `x-goog-`
+ * - The header name must be all lowercase
+ *
+ * Note: Multi-valued header passed as an array in the extensionHeaders
+ * object is converted into a string, delimited by `,` with
+ * no space. Requests made using the signed URL will need to
+ * delimit multi-valued headers using a single `,` as well, or
+ * else the server will report a mismatched signature.
+ * @param {object} [config.queryParams] Additional query parameters to include
+ * in the signed URL.
+ * @param {string} [config.promptSaveAs] The filename to prompt the user to
+ * save the file as when the signed url is accessed. This is ignored if
+ * `config.responseDisposition` is set.
+ * @param {string} [config.responseDisposition] The
+ * {@link http://goo.gl/yMWxQV| response-content-disposition parameter} of the
+ * signed url.
+ * @param {*} [config.accessibleAt=Date.now()] A timestamp when this link became usable. Any value
+ * given is passed to `new Date()`.
+ * Note: Use for 'v4' only.
+ * @param {string} [config.responseType] The response-content-type parameter
+ * of the signed url.
+ * @param {GetSignedUrlCallback} [callback] Callback function.
+ * @returns {Promise}
+ *
+ * @example
+ * ```
+ * const {Storage} = require('@google-cloud/storage');
+ * const storage = new Storage();
+ * const myBucket = storage.bucket('my-bucket');
+ *
+ * const file = myBucket.file('my-file');
+ *
+ * //-
+ * // Generate a URL that allows temporary access to download your file.
+ * //-
+ * const request = require('request');
+ *
+ * const config = {
+ * action: 'read',
+ * expires: '03-17-2025',
+ * };
+ *
+ * file.getSignedUrl(config, function(err, url) {
+ * if (err) {
+ * console.error(err);
+ * return;
+ * }
+ *
+ * // The file is now available to read from this URL.
+ * request(url, function(err, resp) {
+ * // resp.statusCode = 200
+ * });
+ * });
+ *
+ * //-
+ * // Generate a URL that allows temporary access to download your file.
+ * // Access will begin at accessibleAt and end at expires.
+ * //-
+ * const request = require('request');
+ *
+ * const config = {
+ * action: 'read',
+ * expires: '03-17-2025',
+ * accessibleAt: '03-13-2025'
+ * };
+ *
+ * file.getSignedUrl(config, function(err, url) {
+ * if (err) {
+ * console.error(err);
+ * return;
+ * }
+ *
+ * // The file will be available to read from this URL from 03-13-2025 to 03-17-2025.
+ * request(url, function(err, resp) {
+ * // resp.statusCode = 200
+ * });
+ * });
+ *
+ * //-
+ * // Generate a URL to allow write permissions. This means anyone with this
+ * URL
+ * // can send a POST request with new data that will overwrite the file.
+ * //-
+ * file.getSignedUrl({
+ * action: 'write',
+ * expires: '03-17-2025'
+ * }, function(err, url) {
+ * if (err) {
+ * console.error(err);
+ * return;
+ * }
+ *
+ * // The file is now available to be written to.
+ * const writeStream = request.put(url);
+ * writeStream.end('New data');
+ *
+ * writeStream.on('complete', function(resp) {
+ * // Confirm the new content was saved.
+ * file.download(function(err, fileContents) {
+ * console.log('Contents:', fileContents.toString());
+ * // Contents: New data
+ * });
+ * });
+ * });
+ *
+ * //-
+ * // If the callback is omitted, we'll return a Promise.
+ * //-
+ * file.getSignedUrl(config).then(function(data) {
+ * const url = data[0];
+ * });
+ *
+ * ```
+ * @example include:samples/files.js
+ * region_tag:storage_generate_signed_url
+ * Another example:
+ */
+ getSignedUrl(
+ cfg: GetSignedUrlConfig,
+ callback?: GetSignedUrlCallback
+ ): void | Promise {
+ const method = ActionToHTTPMethod[cfg.action];
+ const extensionHeaders = objectKeyToLowercase(cfg.extensionHeaders || {});
+ if (cfg.action === 'resumable') {
+ extensionHeaders['x-goog-resumable'] = 'start';
+ }
+
+ const queryParams = Object.assign({}, cfg.queryParams);
+ if (typeof cfg.responseType === 'string') {
+ queryParams['response-content-type'] = cfg.responseType!;
+ }
+ if (typeof cfg.promptSaveAs === 'string') {
+ queryParams['response-content-disposition'] =
+ 'attachment; filename="' + cfg.promptSaveAs + '"';
+ }
+ if (typeof cfg.responseDisposition === 'string') {
+ queryParams['response-content-disposition'] = cfg.responseDisposition!;
+ }
+ if (this.generation) {
+ queryParams['generation'] = this.generation.toString();
+ }
+
+ const signConfig: SignerGetSignedUrlConfig = {
+ method,
+ expires: cfg.expires,
+ accessibleAt: cfg.accessibleAt,
+ extensionHeaders,
+ queryParams,
+ contentMd5: cfg.contentMd5,
+ contentType: cfg.contentType,
+ host: cfg.host,
+ };
+
+ if (cfg.cname) {
+ signConfig.cname = cfg.cname;
+ }
+
+ if (cfg.version) {
+ signConfig.version = cfg.version;
+ }
+
+ if (cfg.virtualHostedStyle) {
+ signConfig.virtualHostedStyle = cfg.virtualHostedStyle;
+ }
+
+ if (!this.signer) {
+ this.signer = new URLSigner(
+ this.storage.authClient,
+ this.bucket,
+ this,
+ this.storage
+ );
+ }
+
+ this.signer
+ .getSignedUrl(signConfig)
+ .then(signedUrl => callback!(null, signedUrl), callback!);
+ }
+
+ isPublic(): Promise;
+ isPublic(callback: IsPublicCallback): void;
+ /**
+ * @callback IsPublicCallback
+ * @param {?Error} err Request error, if any.
+ * @param {boolean} resp Whether file is public or not.
+ */
+ /**
+ * @typedef {array} IsPublicResponse
+ * @property {boolean} 0 Whether file is public or not.
+ */
+ /**
+ * Check whether this file is public or not by sending
+ * a HEAD request without credentials.
+ * No errors from the server indicates that the current
+ * file is public.
+ * A 403-Forbidden error {@link https://cloud.google.com/storage/docs/json_api/v1/status-codes#403_Forbidden}
+ * indicates that file is private.
+ * Any other non 403 error is propagated to user.
+ *
+ * @param {IsPublicCallback} [callback] Callback function.
+ * @returns {Promise}
+ *
+ * @example
+ * ```
+ * const {Storage} = require('@google-cloud/storage');
+ * const storage = new Storage();
+ * const myBucket = storage.bucket('my-bucket');
+ *
+ * const file = myBucket.file('my-file');
+ *
+ * //-
+ * // Check whether the file is publicly accessible.
+ * //-
+ * file.isPublic(function(err, resp) {
+ * if (err) {
+ * console.error(err);
+ * return;
+ * }
+ * console.log(`the file ${file.id} is public: ${resp}`) ;
+ * })
+ * //-
+ * // If the callback is omitted, we'll return a Promise.
+ * //-
+ * file.isPublic().then(function(data) {
+ * const resp = data[0];
+ * });
+ * ```
+ */
+
+ isPublic(callback?: IsPublicCallback): Promise | void {
+ // Build any custom headers based on the defined interceptors on the parent
+ // storage object and this object
+ const storageInterceptors = this.storage?.interceptors || [];
+ const fileInterceptors = this.interceptors || [];
+ const allInterceptors = storageInterceptors.concat(fileInterceptors);
+ const headers = allInterceptors.reduce((acc, curInterceptor) => {
+ const currentHeaders = curInterceptor.request({
+ uri: `${this.storage.apiEndpoint}/${
+ this.bucket.name
+ }/${encodeURIComponent(this.name)}`,
+ });
+
+ Object.assign(acc, currentHeaders.headers);
+ return acc;
+ }, {});
+
+ util.makeRequest(
+ {
+ method: 'GET',
+ uri: `${this.storage.apiEndpoint}/${
+ this.bucket.name
+ }/${encodeURIComponent(this.name)}`,
+ headers,
+ },
+ {
+ retryOptions: this.storage.retryOptions,
+ },
+ (err: Error | ApiError | null) => {
+ if (err) {
+ const apiError = err as ApiError;
+ if (apiError.code === 403) {
+ callback!(null, false);
+ } else {
+ callback!(err);
+ }
+ } else {
+ callback!(null, true);
+ }
+ }
+ );
+ }
+
+ makePrivate(
+ options?: MakeFilePrivateOptions
+ ): Promise;
+ makePrivate(callback: MakeFilePrivateCallback): void;
+ makePrivate(
+ options: MakeFilePrivateOptions,
+ callback: MakeFilePrivateCallback
+ ): void;
+ /**
+ * @typedef {object} MakeFilePrivateOptions Configuration options for File#makePrivate().
+ * @property {Metadata} [metadata] Define custom metadata properties to define
+ * along with the operation.
+ * @property {boolean} [strict] If true, set the file to be private to
+ * only the owner user. Otherwise, it will be private to the project.
+ * @property {string} [userProject] The ID of the project which will be
+ * billed for the request.
+ */
+ /**
+ * @callback MakeFilePrivateCallback
+ * @param {?Error} err Request error, if any.
+ * @param {object} apiResponse The full API response.
+ */
+ /**
+ * @typedef {array} MakeFilePrivateResponse
+ * @property {object} 0 The full API response.
+ */
+ /**
+ * Make a file private to the project and remove all other permissions.
+ * Set `options.strict` to true to make the file private to only the owner.
+ *
+ * See {@link https://cloud.google.com/storage/docs/json_api/v1/objects/patch| Objects: patch API Documentation}
+ *
+ * @param {MakeFilePrivateOptions} [options] Configuration options.
+ * @param {MakeFilePrivateCallback} [callback] Callback function.
+ * @returns {Promise}
+ *
+ * @example
+ * ```
+ * const {Storage} = require('@google-cloud/storage');
+ * const storage = new Storage();
+ * const myBucket = storage.bucket('my-bucket');
+ *
+ * const file = myBucket.file('my-file');
+ *
+ * //-
+ * // Set the file private so only project maintainers can see and modify it.
+ * //-
+ * file.makePrivate(function(err) {});
+ *
+ * //-
+ * // Set the file private so only the owner can see and modify it.
+ * //-
+ * file.makePrivate({ strict: true }, function(err) {});
+ *
+ * //-
+ * // If the callback is omitted, we'll return a Promise.
+ * //-
+ * file.makePrivate().then(function(data) {
+ * const apiResponse = data[0];
+ * });
+ * ```
+ */
+ makePrivate(
+ optionsOrCallback?: MakeFilePrivateOptions | MakeFilePrivateCallback,
+ callback?: MakeFilePrivateCallback
+ ): Promise | void {
+ const options =
+ typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
+ callback =
+ typeof optionsOrCallback === 'function' ? optionsOrCallback : callback;
+
+ const query = {
+ predefinedAcl: options.strict ? 'private' : 'projectPrivate',
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ } as any;
+
+ if (options.preconditionOpts?.ifMetagenerationMatch !== undefined) {
+ query.ifMetagenerationMatch =
+ options.preconditionOpts?.ifMetagenerationMatch;
+ delete options.preconditionOpts;
+ }
+
+ if (options.userProject) {
+ query.userProject = options.userProject;
+ }
+
+ // You aren't allowed to set both predefinedAcl & acl properties on a file,
+ // so acl must explicitly be nullified, destroying all previous acls on the
+ // file.
+ const metadata = {...options.metadata, acl: null};
+
+ this.setMetadata(metadata, query, callback!);
+ }
+
+ makePublic(): Promise;
+ makePublic(callback: MakeFilePublicCallback): void;
+ /**
+ * @typedef {array} MakeFilePublicResponse
+ * @property {object} 0 The full API response.
+ */
+ /**
+ * @callback MakeFilePublicCallback
+ * @param {?Error} err Request error, if any.
+ * @param {object} apiResponse The full API response.
+ */
+ /**
+ * Set a file to be publicly readable and maintain all previous permissions.
+ *
+ * See {@link https://cloud.google.com/storage/docs/json_api/v1/objectAccessControls/insert| ObjectAccessControls: insert API Documentation}
+ *
+ * @param {MakeFilePublicCallback} [callback] Callback function.
+ * @returns {Promise}
+ *
+ * @example
+ * ```
+ * const {Storage} = require('@google-cloud/storage');
+ * const storage = new Storage();
+ * const myBucket = storage.bucket('my-bucket');
+ *
+ * const file = myBucket.file('my-file');
+ *
+ * file.makePublic(function(err, apiResponse) {});
+ *
+ * //-
+ * // If the callback is omitted, we'll return a Promise.
+ * //-
+ * file.makePublic().then(function(data) {
+ * const apiResponse = data[0];
+ * });
+ *
+ * ```
+ * @example include:samples/files.js
+ * region_tag:storage_make_public
+ * Another example:
+ */
+ makePublic(
+ callback?: MakeFilePublicCallback
+ ): Promise | void {
+ callback = callback || util.noop;
+ this.acl.add(
+ {
+ entity: 'allUsers',
+ role: 'READER',
+ },
+ (err, acl, resp) => {
+ callback!(err, resp);
+ }
+ );
+ }
+
+ /**
+ * The public URL of this File
+ * Use {@link File#makePublic} to enable anonymous access via the returned URL.
+ *
+ * @returns {string}
+ *
+ * @example
+ * ```
+ * const {Storage} = require('@google-cloud/storage');
+ * const storage = new Storage();
+ * const bucket = storage.bucket('albums');
+ * const file = bucket.file('my-file');
+ *
+ * // publicUrl will be "https://storage.googleapis.com/albums/my-file"
+ * const publicUrl = file.publicUrl();
+ * ```
+ */
+ publicUrl(): string {
+ return `${this.storage.apiEndpoint}/${
+ this.bucket.name
+ }/${encodeURIComponent(this.name)}`;
+ }
+
+ moveFileAtomic(
+ destination: string | File,
+ options?: MoveFileAtomicOptions
+ ): Promise;
+ moveFileAtomic(
+ destination: string | File,
+ callback: MoveFileAtomicCallback
+ ): void;
+ moveFileAtomic(
+ destination: string | File,
+ options: MoveFileAtomicOptions,
+ callback: MoveFileAtomicCallback
+ ): void;
+ /**
+ * @typedef {array} MoveFileAtomicResponse
+ * @property {File} 0 The moved {@link File}.
+ * @property {object} 1 The full API response.
+ */
+ /**
+ * @callback MoveFileAtomicCallback
+ * @param {?Error} err Request error, if any.
+ * @param {File} movedFile The moved {@link File}.
+ * @param {object} apiResponse The full API response.
+ */
+ /**
+ * @typedef {object} MoveFileAtomicOptions Configuration options for File#moveFileAtomic(). See an
+ * {@link https://cloud.google.com/storage/docs/json_api/v1/objects#resource| Object resource}.
+ * @property {string} [userProject] The ID of the project which will be
+ * billed for the request.
+ * @property {object} [preconditionOpts] Precondition options.
+ * @property {number} [preconditionOpts.ifGenerationMatch] Makes the operation conditional on whether the object's current generation matches the given value.
+ */
+ /**
+ * Move this file within the same bucket.
+ * The source object must exist and be a live object.
+ * The source and destination object IDs must be different.
+ * Overwriting the destination object is allowed by default, but can be prevented
+ * using preconditions.
+ * If the destination path includes non-existent parent folders, they will be created.
+ *
+ * See {@link https://cloud.google.com/storage/docs/json_api/v1/objects/move| Objects: move API Documentation}
+ *
+ * @throws {Error} If the destination file is not provided.
+ *
+ * @param {string|File} destination Destination file name or File object within the same bucket..
+ * @param {MoveFileAtomicOptions} [options] Configuration options. See an
+ * @param {MoveFileAtomicCallback} [callback] Callback function.
+ * @returns {Promise}
+ *
+ * @example
+ * ```
+ * const {Storage} = require('@google-cloud/storage');
+ * const storage = new Storage();
+ *
+ * //-
+ * // Assume 'my-bucket' is a bucket.
+ * //-
+ * const bucket = storage.bucket('my-bucket');
+ * const file = bucket.file('my-image.png');
+ *
+ * //-
+ * // If you pass in a string for the destination, the file is copied to its
+ * // current bucket, under the new name provided.
+ * //-
+ * file.moveFileAtomic('moved-image.png', function(err, movedFile, apiResponse) {
+ * // `my-bucket` now contains:
+ * // - "moved-image.png"
+ *
+ * // `movedFile` is an instance of a File object that refers to your new
+ * // file.
+ * });
+ *
+ * //-
+ * // Move the file to a subdirectory, creating parent folders if necessary.
+ * //-
+ * file.moveFileAtomic('new-folder/subfolder/moved-image.png', function(err, movedFile, apiResponse) {
+ * // `my-bucket` now contains:
+ * // - "new-folder/subfolder/moved-image.png"
+ * });
+ *
+ * //-
+ * // Prevent overwriting an existing destination object using preconditions.
+ * //-
+ * file.moveFileAtomic('existing-destination.png', {
+ * preconditionOpts: {
+ * ifGenerationMatch: 0 // Fails if the destination object exists.
+ * }
+ * }, function(err, movedFile, apiResponse) {
+ * if (err) {
+ * // Handle the error (e.g., the destination object already exists).
+ * } else {
+ * // Move successful.
+ * }
+ * });
+ *
+ * //-
+ * // If the callback is omitted, we'll return a Promise.
+ * //-
+ * file.moveFileAtomic('moved-image.png).then(function(data) {
+ * const newFile = data[0];
+ * const apiResponse = data[1];
+ * });
+ *
+ * ```
+ * @example include:samples/files.js
+ * region_tag:storage_move_file
+ * Another example:
+ */
+ moveFileAtomic(
+ destination: string | File,
+ optionsOrCallback?: MoveFileAtomicOptions | MoveFileAtomicCallback,
+ callback?: MoveFileAtomicCallback
+ ): Promise | void {
+ const noDestinationError = new Error(
+ FileExceptionMessages.DESTINATION_NO_NAME
+ );
+
+ if (!destination) {
+ throw noDestinationError;
+ }
+
+ let options: MoveFileAtomicOptions = {};
+ if (typeof optionsOrCallback === 'function') {
+ callback = optionsOrCallback;
+ } else if (optionsOrCallback) {
+ options = {...optionsOrCallback};
+ }
+
+ callback = callback || util.noop;
+
+ let destName: string;
+ let newFile: File;
+
+ if (typeof destination === 'string') {
+ const parsedDestination = GS_URL_REGEXP.exec(destination);
+ if (parsedDestination !== null && parsedDestination.length === 3) {
+ destName = parsedDestination[2];
+ } else {
+ destName = destination;
+ }
+ } else if (destination instanceof File) {
+ destName = destination.name;
+ newFile = destination;
+ } else {
+ throw noDestinationError;
+ }
+
+ newFile = newFile! || this.bucket.file(destName);
+
+ if (
+ !this.shouldRetryBasedOnPreconditionAndIdempotencyStrat(
+ options?.preconditionOpts
+ )
+ ) {
+ this.storage.retryOptions.autoRetry = false;
+ }
+ const query = {} as MoveFileAtomicQuery;
+ if (options.userProject !== undefined) {
+ query.userProject = options.userProject;
+ delete options.userProject;
+ }
+ if (options.preconditionOpts?.ifGenerationMatch !== undefined) {
+ query.ifGenerationMatch = options.preconditionOpts?.ifGenerationMatch;
+ delete options.preconditionOpts;
+ }
+
+ this.request(
+ {
+ method: 'POST',
+ uri: `/moveTo/o/${encodeURIComponent(newFile.name)}`,
+ qs: query,
+ json: options,
+ },
+ (err, resp) => {
+ this.storage.retryOptions.autoRetry = this.instanceRetryValue;
+ if (err) {
+ callback!(err, null, resp);
+ return;
+ }
+
+ callback!(null, newFile, resp);
+ }
+ );
+ }
+
+ move(
+ destination: string | Bucket | File,
+ options?: MoveOptions
+ ): Promise;
+ move(destination: string | Bucket | File, callback: MoveCallback): void;
+ move(
+ destination: string | Bucket | File,
+ options: MoveOptions,
+ callback: MoveCallback
+ ): void;
+ /**
+ * @typedef {array} MoveResponse
+ * @property {File} 0 The destination File.
+ * @property {object} 1 The full API response.
+ */
+ /**
+ * @callback MoveCallback
+ * @param {?Error} err Request error, if any.
+ * @param {?File} destinationFile The destination File.
+ * @param {object} apiResponse The full API response.
+ */
+ /**
+ * @typedef {object} MoveOptions Configuration options for File#move(). See an
+ * {@link https://cloud.google.com/storage/docs/json_api/v1/objects#resource| Object resource}.
+ * @param {string} [userProject] The ID of the project which will be
+ * billed for the request.
+ */
+ /**
+ * Move this file to another location. By default, this will rename the file
+ * and keep it in the same bucket, but you can choose to move it to another
+ * Bucket by providing a Bucket or File object or a URL beginning with
+ * "gs://".
+ *
+ * **Warning**:
+ * There is currently no atomic `move` method in the Cloud Storage API,
+ * so this method is a composition of {@link File#copy} (to the new
+ * location) and {@link File#delete} (from the old location). While
+ * unlikely, it is possible that an error returned to your callback could be
+ * triggered from either one of these API calls failing, which could leave a
+ * duplicate file lingering. The error message will indicate what operation
+ * has failed.
+ *
+ * See {@link https://cloud.google.com/storage/docs/json_api/v1/objects/copy| Objects: copy API Documentation}
+ *
+ * @throws {Error} If the destination file is not provided.
+ *
+ * @param {string|Bucket|File} destination Destination file.
+ * @param {MoveCallback} [callback] Callback function.
+ * @returns {Promise}
+ *
+ * @example
+ * ```
+ * const {Storage} = require('@google-cloud/storage');
+ * const storage = new Storage();
+ * //-
+ * // You can pass in a variety of types for the destination.
+ * //
+ * // For all of the below examples, assume we are working with the following
+ * // Bucket and File objects.
+ * //-
+ * const bucket = storage.bucket('my-bucket');
+ * const file = bucket.file('my-image.png');
+ *
+ * //-
+ * // If you pass in a string for the destination, the file is moved to its
+ * // current bucket, under the new name provided.
+ * //-
+ * file.move('my-image-new.png', function(err, destinationFile, apiResponse) {
+ * // `my-bucket` no longer contains:
+ * // - "my-image.png"
+ * // but contains instead:
+ * // - "my-image-new.png"
+ *
+ * // `destinationFile` is an instance of a File object that refers to your
+ * // new file.
+ * });
+ *
+ * //-
+ * // If you pass in a string starting with "gs://" for the destination, the
+ * // file is copied to the other bucket and under the new name provided.
+ * //-
+ * const newLocation = 'gs://another-bucket/my-image-new.png';
+ * file.move(newLocation, function(err, destinationFile, apiResponse) {
+ * // `my-bucket` no longer contains:
+ * // - "my-image.png"
+ * //
+ * // `another-bucket` now contains:
+ * // - "my-image-new.png"
+ *
+ * // `destinationFile` is an instance of a File object that refers to your
+ * // new file.
+ * });
+ *
+ * //-
+ * // If you pass in a Bucket object, the file will be moved to that bucket
+ * // using the same name.
+ * //-
+ * const anotherBucket = gcs.bucket('another-bucket');
+ *
+ * file.move(anotherBucket, function(err, destinationFile, apiResponse) {
+ * // `my-bucket` no longer contains:
+ * // - "my-image.png"
+ * //
+ * // `another-bucket` now contains:
+ * // - "my-image.png"
+ *
+ * // `destinationFile` is an instance of a File object that refers to your
+ * // new file.
+ * });
+ *
+ * //-
+ * // If you pass in a File object, you have complete control over the new
+ * // bucket and filename.
+ * //-
+ * const anotherFile = anotherBucket.file('my-awesome-image.png');
+ *
+ * file.move(anotherFile, function(err, destinationFile, apiResponse) {
+ * // `my-bucket` no longer contains:
+ * // - "my-image.png"
+ * //
+ * // `another-bucket` now contains:
+ * // - "my-awesome-image.png"
+ *
+ * // Note:
+ * // The `destinationFile` parameter is equal to `anotherFile`.
+ * });
+ *
+ * //-
+ * // If the callback is omitted, we'll return a Promise.
+ * //-
+ * file.move('my-image-new.png').then(function(data) {
+ * const destinationFile = data[0];
+ * const apiResponse = data[1];
+ * });
+ *
+ * ```
+ * @example include:samples/files.js
+ * region_tag:storage_move_file
+ * Another example:
+ */
+ move(
+ destination: string | Bucket | File,
+ optionsOrCallback?: MoveOptions | MoveCallback,
+ callback?: MoveCallback
+ ): Promise | void {
+ const options =
+ typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
+ callback =
+ typeof optionsOrCallback === 'function' ? optionsOrCallback : callback;
+
+ callback = callback || util.noop;
+
+ this.copy(destination, options, (err, destinationFile, copyApiResponse) => {
+ if (err) {
+ err.message = 'file#copy failed with an error - ' + err.message;
+ callback!(err, null, copyApiResponse);
+ return;
+ }
+
+ if (
+ this.name !== destinationFile!.name ||
+ this.bucket.name !== destinationFile!.bucket.name
+ ) {
+ this.delete(options, (err, apiResponse) => {
+ if (err) {
+ err.message = 'file#delete failed with an error - ' + err.message;
+ callback!(err, destinationFile, apiResponse);
+ return;
+ }
+ callback!(null, destinationFile, copyApiResponse);
+ });
+ } else {
+ callback!(null, destinationFile, copyApiResponse);
+ }
+ });
+ }
+
+ rename(
+ destinationFile: string | File,
+ options?: RenameOptions
+ ): Promise;
+ rename(destinationFile: string | File, callback: RenameCallback): void;
+ rename(
+ destinationFile: string | File,
+ options: RenameOptions,
+ callback: RenameCallback
+ ): void;
+ /**
+ * @typedef {array} RenameResponse
+ * @property {File} 0 The destination File.
+ * @property {object} 1 The full API response.
+ */
+ /**
+ * @callback RenameCallback
+ * @param {?Error} err Request error, if any.
+ * @param {?File} destinationFile The destination File.
+ * @param {object} apiResponse The full API response.
+ */
+ /**
+ * @typedef {object} RenameOptions Configuration options for File#move(). See an
+ * {@link https://cloud.google.com/storage/docs/json_api/v1/objects#resource| Object resource}.
+ * @param {string} [userProject] The ID of the project which will be
+ * billed for the request.
+ */
+ /**
+ * Rename this file.
+ *
+ * **Warning**:
+ * There is currently no atomic `rename` method in the Cloud Storage API,
+ * so this method is an alias of {@link File#move}, which in turn is a
+ * composition of {@link File#copy} (to the new location) and
+ * {@link File#delete} (from the old location). While
+ * unlikely, it is possible that an error returned to your callback could be
+ * triggered from either one of these API calls failing, which could leave a
+ * duplicate file lingering. The error message will indicate what operation
+ * has failed.
+ *
+ * @param {string|File} destinationFile Destination file.
+ * @param {RenameCallback} [callback] Callback function.
+ * @returns {Promise}
+ *
+ * @example
+ * ```
+ * const {Storage} = require('@google-cloud/storage');
+ * const storage = new Storage();
+ *
+ * //-
+ * // You can pass in a string or a File object.
+ * //
+ * // For all of the below examples, assume we are working with the following
+ * // Bucket and File objects.
+ * //-
+ *
+ * const bucket = storage.bucket('my-bucket');
+ * const file = bucket.file('my-image.png');
+ *
+ * //-
+ * // You can pass in a string for the destinationFile.
+ * //-
+ * file.rename('renamed-image.png', function(err, renamedFile, apiResponse) {
+ * // `my-bucket` no longer contains:
+ * // - "my-image.png"
+ * // but contains instead:
+ * // - "renamed-image.png"
+ *
+ * // `renamedFile` is an instance of a File object that refers to your
+ * // renamed file.
+ * });
+ *
+ * //-
+ * // You can pass in a File object.
+ * //-
+ * const anotherFile = anotherBucket.file('my-awesome-image.png');
+ *
+ * file.rename(anotherFile, function(err, renamedFile, apiResponse) {
+ * // `my-bucket` no longer contains:
+ * // - "my-image.png"
+ *
+ * // Note:
+ * // The `renamedFile` parameter is equal to `anotherFile`.
+ * });
+ *
+ * //-
+ * // If the callback is omitted, we'll return a Promise.
+ * //-
+ * file.rename('my-renamed-image.png').then(function(data) {
+ * const renamedFile = data[0];
+ * const apiResponse = data[1];
+ * });
+ * ```
+ */
+ rename(
+ destinationFile: string | File,
+ optionsOrCallback?: RenameOptions | RenameCallback,
+ callback?: RenameCallback
+ ): Promise | void {
+ const options =
+ typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
+ callback =
+ typeof optionsOrCallback === 'function' ? optionsOrCallback : callback;
+
+ callback = callback || util.noop;
+
+ this.move(destinationFile, options, callback);
+ }
+
+ /**
+ * @typedef {object} RestoreOptions Options for File#restore(). See an
+ * {@link https://cloud.google.com/storage/docs/json_api/v1/objects#resource| Object resource}.
+ * @param {string} [userProject] The ID of the project which will be
+ * billed for the request.
+ * @param {number} [generation] If present, selects a specific revision of this object.
+ * @param {string} [restoreToken] Returns an option that must be specified when getting a soft-deleted object from an HNS-enabled
+ * bucket that has a naming and generation conflict with another object in the same bucket.
+ * @param {string} [projection] Specifies the set of properties to return. If used, must be 'full' or 'noAcl'.
+ * @param {string | number} [ifGenerationMatch] Request proceeds if the generation of the target resource
+ * matches the value used in the precondition.
+ * If the values don't match, the request fails with a 412 Precondition Failed response.
+ * @param {string | number} [ifGenerationNotMatch] Request proceeds if the generation of the target resource does
+ * not match the value used in the precondition. If the values match, the request fails with a 304 Not Modified response.
+ * @param {string | number} [ifMetagenerationMatch] Request proceeds if the meta-generation of the target resource
+ * matches the value used in the precondition.
+ * If the values don't match, the request fails with a 412 Precondition Failed response.
+ * @param {string | number} [ifMetagenerationNotMatch] Request proceeds if the meta-generation of the target resource does
+ * not match the value used in the precondition. If the values match, the request fails with a 304 Not Modified response.
+ */
+ /**
+ * Restores a soft-deleted file
+ * @param {RestoreOptions} options Restore options.
+ * @returns {Promise}
+ */
+ async restore(options: RestoreOptions): Promise {
+ const [file] = await this.request({
+ method: 'POST',
+ uri: '/restore',
+ qs: options,
+ });
+
+ return file as File;
+ }
+
+ request(reqOpts: DecorateRequestOptions): Promise;
+ request(
+ reqOpts: DecorateRequestOptions,
+ callback: BodyResponseCallback
+ ): void;
+ /**
+ * Makes request and applies userProject query parameter if necessary.
+ *
+ * @private
+ *
+ * @param {object} reqOpts - The request options.
+ * @param {function} callback - The callback function.
+ */
+ request(
+ reqOpts: DecorateRequestOptions,
+ callback?: BodyResponseCallback
+ ): void | Promise {
+ return this.parent.request.call(this, reqOpts, callback!);
+ }
+
+ rotateEncryptionKey(
+ options?: RotateEncryptionKeyOptions
+ ): Promise;
+ rotateEncryptionKey(callback: RotateEncryptionKeyCallback): void;
+ rotateEncryptionKey(
+ options: RotateEncryptionKeyOptions,
+ callback: RotateEncryptionKeyCallback
+ ): void;
+ /**
+ * @callback RotateEncryptionKeyCallback
+ * @extends CopyCallback
+ */
+ /**
+ * @typedef RotateEncryptionKeyResponse
+ * @extends CopyResponse
+ */
+ /**
+ * @param {string|buffer|object} RotateEncryptionKeyOptions Configuration options
+ * for File#rotateEncryptionKey().
+ * If a string or Buffer is provided, it is interpreted as an AES-256,
+ * customer-supplied encryption key. If you'd like to use a Cloud KMS key
+ * name, you must specify an options object with the property name:
+ * `kmsKeyName`.
+ * @param {string|buffer} [options.encryptionKey] An AES-256 encryption key.
+ * @param {string} [options.kmsKeyName] A Cloud KMS key name.
+ */
+ /**
+ * This method allows you to update the encryption key associated with this
+ * file.
+ *
+ * See {@link https://cloud.google.com/storage/docs/encryption#customer-supplied| Customer-supplied Encryption Keys}
+ *
+ * @param {RotateEncryptionKeyOptions} [options] - Configuration options.
+ * @param {RotateEncryptionKeyCallback} [callback]
+ * @returns {Promise}
+ *
+ * @example include:samples/encryption.js
+ * region_tag:storage_rotate_encryption_key
+ * Example of rotating the encryption key for this file:
+ */
+ rotateEncryptionKey(
+ optionsOrCallback?:
+ | RotateEncryptionKeyOptions
+ | RotateEncryptionKeyCallback,
+ callback?: RotateEncryptionKeyCallback
+ ): Promise | void {
+ callback =
+ typeof optionsOrCallback === 'function' ? optionsOrCallback : callback;
+ let options: EncryptionKeyOptions = {};
+ if (
+ typeof optionsOrCallback === 'string' ||
+ optionsOrCallback instanceof Buffer
+ ) {
+ options = {
+ encryptionKey: optionsOrCallback,
+ };
+ } else if (typeof optionsOrCallback === 'object') {
+ options = optionsOrCallback as EncryptionKeyOptions;
+ }
+
+ const newFile = this.bucket.file(this.id!, options);
+ const copyOptions =
+ options.preconditionOpts?.ifGenerationMatch !== undefined
+ ? {preconditionOpts: options.preconditionOpts}
+ : {};
+ this.copy(newFile, copyOptions, callback!);
+ }
+
+ save(data: SaveData, options?: SaveOptions): Promise;
+ save(data: SaveData, callback: SaveCallback): void;
+ save(data: SaveData, options: SaveOptions, callback: SaveCallback): void;
+ /**
+ * @typedef {object} SaveOptions
+ * @extends CreateWriteStreamOptions
+ */
+ /**
+ * @callback SaveCallback
+ * @param {?Error} err Request error, if any.
+ */
+ /**
+ * Write strings or buffers to a file.
+ *
+ * *This is a convenience method which wraps {@link File#createWriteStream}.*
+ * To upload arbitrary data to a file, please use {@link File#createWriteStream} directly.
+ *
+ * Resumable uploads are automatically enabled and must be shut off explicitly
+ * by setting `options.resumable` to `false`.
+ *
+ * Multipart uploads with retryable error codes will be retried 3 times with exponential backoff.
+ *
+ *
+ * There is some overhead when using a resumable upload that can cause
+ * noticeable performance degradation while uploading a series of small
+ * files. When uploading files less than 10MB, it is recommended that the
+ * resumable feature is disabled.
+ *
+ *
+ * @param {SaveData} data The data to write to a file.
+ * @param {SaveOptions} [options] See {@link File#createWriteStream}'s `options`
+ * parameter.
+ * @param {SaveCallback} [callback] Callback function.
+ * @returns {Promise}
+ *
+ * @example
+ * ```
+ * const {Storage} = require('@google-cloud/storage');
+ * const storage = new Storage();
+ * const myBucket = storage.bucket('my-bucket');
+ *
+ * const file = myBucket.file('my-file');
+ * const contents = 'This is the contents of the file.';
+ *
+ * file.save(contents, function(err) {
+ * if (!err) {
+ * // File written successfully.
+ * }
+ * });
+ *
+ * //-
+ * // If the callback is omitted, we'll return a Promise.
+ * //-
+ * file.save(contents).then(function() {});
+ * ```
+ */
+ save(
+ data: SaveData,
+ optionsOrCallback?: SaveOptions | SaveCallback,
+ callback?: SaveCallback
+ ): Promise | void {
+ // tslint:enable:no-any
+ callback =
+ typeof optionsOrCallback === 'function' ? optionsOrCallback : callback;
+ const options =
+ typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
+
+ let maxRetries = this.storage.retryOptions.maxRetries;
+ if (
+ !this.shouldRetryBasedOnPreconditionAndIdempotencyStrat(
+ options?.preconditionOpts
+ )
+ ) {
+ maxRetries = 0;
+ }
+ const returnValue = AsyncRetry(
+ async (bail: (err: Error) => void) => {
+ return new Promise((resolve, reject) => {
+ if (maxRetries === 0) {
+ this.storage.retryOptions.autoRetry = false;
+ }
+ const writable = this.createWriteStream(options);
+
+ if (options.onUploadProgress) {
+ writable.on('progress', options.onUploadProgress);
+ }
+
+ const handleError = (err: Error) => {
+ if (
+ this.storage.retryOptions.autoRetry &&
+ this.storage.retryOptions.retryableErrorFn!(err)
+ ) {
+ return reject(err);
+ }
+
+ return bail(err);
+ };
+
+ if (
+ typeof data === 'string' ||
+ Buffer.isBuffer(data) ||
+ data instanceof Uint8Array
+ ) {
+ writable
+ .on('error', handleError)
+ .on('finish', () => resolve())
+ .end(data);
+ } else {
+ pipeline(data, writable, err => {
+ if (err) {
+ if (typeof data !== 'function') {
+ // Only PipelineSourceFunction can be retried. Async-iterables
+ // and Readable streams can only be consumed once.
+ return bail(err);
+ }
+
+ handleError(err);
+ } else {
+ resolve();
+ }
+ });
+ }
+ });
+ },
+ {
+ retries: maxRetries,
+ factor: this.storage.retryOptions.retryDelayMultiplier,
+ maxTimeout: this.storage.retryOptions.maxRetryDelay! * 1000, //convert to milliseconds
+ maxRetryTime: this.storage.retryOptions.totalTimeout! * 1000, //convert to milliseconds
+ }
+ );
+ if (!callback) {
+ return returnValue;
+ } else {
+ return returnValue
+ .then(() => {
+ if (callback) {
+ return callback();
+ }
+ })
+ .catch(callback);
+ }
+ }
+
+ setMetadata(
+ metadata: FileMetadata,
+ options?: SetMetadataOptions
+ ): Promise>;
+ setMetadata(
+ metadata: FileMetadata,
+ callback: MetadataCallback
+ ): void;
+ setMetadata(
+ metadata: FileMetadata,
+ options: SetMetadataOptions,
+ callback: MetadataCallback
+ ): void;
+ setMetadata(
+ metadata: FileMetadata,
+ optionsOrCallback: SetMetadataOptions | MetadataCallback,
+ cb?: MetadataCallback
+ ): Promise> | void {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const options: any =
+ typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
+ cb =
+ typeof optionsOrCallback === 'function'
+ ? (optionsOrCallback as MetadataCallback)
+ : cb;
+
+ this.disableAutoRetryConditionallyIdempotent_(
+ this.methods.setMetadata,
+ AvailableServiceObjectMethods.setMetadata,
+ options
+ );
+
+ super
+ .setMetadata(metadata, options)
+ .then(resp => cb!(null, ...resp))
+ .catch(cb!)
+ .finally(() => {
+ this.storage.retryOptions.autoRetry = this.instanceRetryValue;
+ });
+ }
+
+ setStorageClass(
+ storageClass: string,
+ options?: SetStorageClassOptions
+ ): Promise;
+ setStorageClass(
+ storageClass: string,
+ options: SetStorageClassOptions,
+ callback: SetStorageClassCallback
+ ): void;
+ setStorageClass(
+ storageClass: string,
+ callback?: SetStorageClassCallback
+ ): void;
+ /**
+ * @typedef {array} SetStorageClassResponse
+ * @property {object} 0 The full API response.
+ */
+ /**
+ * @typedef {object} SetStorageClassOptions Configuration options for File#setStorageClass().
+ * @property {string} [userProject] The ID of the project which will be
+ * billed for the request.
+ */
+ /**
+ * @callback SetStorageClassCallback
+ * @param {?Error} err Request error, if any.
+ * @param {object} apiResponse The full API response.
+ */
+ /**
+ * Set the storage class for this file.
+ *
+ * See {@link https://cloud.google.com/storage/docs/per-object-storage-class| Per-Object Storage Class}
+ * See {@link https://cloud.google.com/storage/docs/storage-classes| Storage Classes}
+ *
+ * @param {string} storageClass The new storage class. (`standard`,
+ * `nearline`, `coldline`, or `archive`)
+ * **Note:** The storage classes `multi_regional` and `regional`
+ * are now legacy and will be deprecated in the future.
+ * @param {SetStorageClassOptions} [options] Configuration options.
+ * @param {string} [options.userProject] The ID of the project which will be
+ * billed for the request.
+ * @param {SetStorageClassCallback} [callback] Callback function.
+ * @returns {Promise}
+ *
+ * @example
+ * ```
+ * file.setStorageClass('nearline', function(err, apiResponse) {
+ * if (err) {
+ * // Error handling omitted.
+ * }
+ *
+ * // The storage class was updated successfully.
+ * });
+ *
+ * //-
+ * // If the callback is omitted, we'll return a Promise.
+ * //-
+ * file.setStorageClass('nearline').then(function() {});
+ * ```
+ */
+ setStorageClass(
+ storageClass: string,
+ optionsOrCallback?: SetStorageClassOptions | SetStorageClassCallback,
+ callback?: SetStorageClassCallback
+ ): Promise | void {
+ callback =
+ typeof optionsOrCallback === 'function' ? optionsOrCallback : callback;
+ const options =
+ typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
+
+ const req = {
+ ...options,
+ // In case we get input like `storageClass`, convert to `storage_class`.
+ storageClass: storageClass
+ .replace(/-/g, '_')
+ .replace(/([a-z])([A-Z])/g, (_, low, up) => {
+ return low + '_' + up;
+ })
+ .toUpperCase(),
+ };
+
+ this.copy(this, req, (err, file, apiResponse) => {
+ if (err) {
+ callback!(err, apiResponse!);
+ return;
+ }
+
+ this.metadata = file!.metadata;
+
+ callback!(null, apiResponse!);
+ });
+ }
+
+ /**
+ * Set a user project to be billed for all requests made from this File
+ * object.
+ *
+ * @param {string} userProject The user project.
+ *
+ * @example
+ * ```
+ * const {Storage} = require('@google-cloud/storage');
+ * const storage = new Storage();
+ * const bucket = storage.bucket('albums');
+ * const file = bucket.file('my-file');
+ *
+ * file.setUserProject('grape-spaceship-123');
+ * ```
+ */
+ setUserProject(userProject: string): void {
+ this.bucket.setUserProject.call(this, userProject);
+ }
+
+ /**
+ * This creates a resumable-upload upload stream.
+ *
+ * @param {Duplexify} stream - Duplexify stream of data to pipe to the file.
+ * @param {object=} options - Configuration object.
+ *
+ * @private
+ */
+ startResumableUpload_(
+ dup: Duplexify,
+ options: CreateResumableUploadOptions = {}
+ ): void {
+ options.metadata ??= {};
+
+ const retryOptions = this.storage.retryOptions;
+ if (
+ !this.shouldRetryBasedOnPreconditionAndIdempotencyStrat(
+ options.preconditionOpts
+ )
+ ) {
+ retryOptions.autoRetry = false;
+ }
+ const cfg = {
+ authClient: this.storage.authClient,
+ apiEndpoint: this.storage.apiEndpoint,
+ bucket: this.bucket.name,
+ customRequestOptions: this.getRequestInterceptors().reduce(
+ (reqOpts, interceptorFn) => interceptorFn(reqOpts),
+ {}
+ ),
+ file: this.name,
+ generation: this.generation,
+ isPartialUpload: options.isPartialUpload,
+ key: this.encryptionKey,
+ kmsKeyName: this.kmsKeyName,
+ metadata: options.metadata,
+ offset: options.offset,
+ predefinedAcl: options.predefinedAcl,
+ private: options.private,
+ public: options.public,
+ uri: options.uri,
+ userProject: options.userProject || this.userProject,
+ retryOptions: {...retryOptions},
+ params: options?.preconditionOpts || this.instancePreconditionOpts,
+ chunkSize: options?.chunkSize,
+ highWaterMark: options?.highWaterMark,
+ universeDomain: this.bucket.storage.universeDomain,
+ [GCCL_GCS_CMD_KEY]: options[GCCL_GCS_CMD_KEY],
+ };
+
+ let uploadStream: resumableUpload.Upload;
+
+ try {
+ uploadStream = resumableUpload.upload(cfg);
+ } catch (error) {
+ dup.destroy(error as Error);
+ this.storage.retryOptions.autoRetry = this.instanceRetryValue;
+ return;
+ }
+
+ uploadStream
+ .on('response', resp => {
+ dup.emit('response', resp);
+ })
+ .on('uri', uri => {
+ dup.emit('uri', uri);
+ })
+ .on('metadata', metadata => {
+ this.metadata = metadata;
+ dup.emit('metadata');
+ })
+ .on('finish', () => {
+ dup.emit('complete');
+ })
+ .on('progress', evt => dup.emit('progress', evt));
+
+ dup.setWritable(uploadStream);
+ this.storage.retryOptions.autoRetry = this.instanceRetryValue;
+ }
+
+ /**
+ * Takes a readable stream and pipes it to a remote file. Unlike
+ * `startResumableUpload_`, which uses the resumable upload technique, this
+ * method uses a simple upload (all or nothing).
+ *
+ * @param {Duplexify} dup - Duplexify stream of data to pipe to the file.
+ * @param {object=} options - Configuration object.
+ *
+ * @private
+ */
+ startSimpleUpload_(
+ dup: Duplexify,
+ options: CreateWriteStreamOptions = {}
+ ): void {
+ options.metadata ??= {};
+
+ const apiEndpoint = this.storage.apiEndpoint;
+ const bucketName = this.bucket.name;
+ const uri = `${apiEndpoint}/upload/storage/v1/b/${bucketName}/o`;
+
+ const reqOpts: DecorateRequestOptions = {
+ qs: {
+ name: this.name,
+ },
+ uri: uri,
+ [GCCL_GCS_CMD_KEY]: options[GCCL_GCS_CMD_KEY],
+ };
+
+ if (this.generation !== undefined) {
+ reqOpts.qs.ifGenerationMatch = this.generation;
+ }
+
+ if (this.kmsKeyName !== undefined) {
+ reqOpts.qs.kmsKeyName = this.kmsKeyName;
+ }
+
+ if (typeof options.timeout === 'number') {
+ reqOpts.timeout = options.timeout;
+ }
+
+ if (options.userProject || this.userProject) {
+ reqOpts.qs.userProject = options.userProject || this.userProject;
+ }
+
+ if (options.predefinedAcl) {
+ reqOpts.qs.predefinedAcl = options.predefinedAcl;
+ } else if (options.private) {
+ reqOpts.qs.predefinedAcl = 'private';
+ } else if (options.public) {
+ reqOpts.qs.predefinedAcl = 'publicRead';
+ }
+
+ Object.assign(
+ reqOpts.qs,
+ this.instancePreconditionOpts,
+ options.preconditionOpts
+ );
+
+ util.makeWritableStream(dup, {
+ makeAuthenticatedRequest: (reqOpts: object) => {
+ this.request(reqOpts as DecorateRequestOptions, (err, body, resp) => {
+ if (err) {
+ dup.destroy(err);
+ return;
+ }
+
+ this.metadata = body;
+ dup.emit('metadata', body);
+ dup.emit('response', resp);
+ dup.emit('complete');
+ });
+ },
+ metadata: options.metadata,
+ request: reqOpts,
+ });
+ }
+
+ disableAutoRetryConditionallyIdempotent_(
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ coreOpts: any,
+ methodType: AvailableServiceObjectMethods,
+ localPreconditionOptions?: PreconditionOptions
+ ): void {
+ if (
+ (typeof coreOpts === 'object' &&
+ coreOpts?.reqOpts?.qs?.ifGenerationMatch === undefined &&
+ localPreconditionOptions?.ifGenerationMatch === undefined &&
+ methodType === AvailableServiceObjectMethods.delete &&
+ this.storage.retryOptions.idempotencyStrategy ===
+ IdempotencyStrategy.RetryConditional) ||
+ this.storage.retryOptions.idempotencyStrategy ===
+ IdempotencyStrategy.RetryNever
+ ) {
+ this.storage.retryOptions.autoRetry = false;
+ }
+
+ if (
+ (typeof coreOpts === 'object' &&
+ coreOpts?.reqOpts?.qs?.ifMetagenerationMatch === undefined &&
+ localPreconditionOptions?.ifMetagenerationMatch === undefined &&
+ methodType === AvailableServiceObjectMethods.setMetadata &&
+ this.storage.retryOptions.idempotencyStrategy ===
+ IdempotencyStrategy.RetryConditional) ||
+ this.storage.retryOptions.idempotencyStrategy ===
+ IdempotencyStrategy.RetryNever
+ ) {
+ this.storage.retryOptions.autoRetry = false;
+ }
+ }
+
+ private async getBufferFromReadable(readable: Readable): Promise {
+ const buf = [];
+ for await (const chunk of readable) {
+ buf.push(chunk);
+ }
+
+ return Buffer.concat(buf);
+ }
+
+ /**
+ *
+ * @param hashCalculatingStream
+ * @param verify
+ * @returns {boolean} Returns `true` if valid, throws with error otherwise
+ */
+ async #validateIntegrity(
+ hashCalculatingStream: HashStreamValidator,
+ verify: {crc32c?: boolean; md5?: boolean} = {}
+ ) {
+ const metadata = this.metadata;
+
+ // If we're doing validation, assume the worst
+ let dataMismatch = !!(verify.crc32c || verify.md5);
+
+ if (verify.crc32c && metadata.crc32c) {
+ dataMismatch = !hashCalculatingStream.test('crc32c', metadata.crc32c);
+ }
+
+ if (verify.md5 && metadata.md5Hash) {
+ dataMismatch = !hashCalculatingStream.test('md5', metadata.md5Hash);
+ }
+
+ if (dataMismatch) {
+ const errors: Error[] = [];
+ let code = '';
+ let message = '';
+
+ try {
+ await this.delete();
+
+ if (verify.md5 && !metadata.md5Hash) {
+ code = 'MD5_NOT_AVAILABLE';
+ message = FileExceptionMessages.MD5_NOT_AVAILABLE;
+ } else {
+ code = 'FILE_NO_UPLOAD';
+ message = FileExceptionMessages.UPLOAD_MISMATCH;
+ }
+ } catch (e) {
+ const error = e as Error;
+
+ code = 'FILE_NO_UPLOAD_DELETE';
+ message = `${FileExceptionMessages.UPLOAD_MISMATCH_DELETE_FAIL}${error.message}`;
+
+ errors.push(error);
+ }
+
+ const error = new RequestError(message);
+ error.code = code;
+ error.errors = errors;
+
+ throw error;
+ }
+
+ return true;
+ }
+}
+
+/*! Developer Documentation
+ *
+ * All async methods (except for streams) will return a Promise in the event
+ * that a callback is omitted.
+ */
+promisifyAll(File, {
+ exclude: [
+ 'cloudStorageURI',
+ 'publicUrl',
+ 'request',
+ 'save',
+ 'setEncryptionKey',
+ 'shouldRetryBasedOnPreconditionAndIdempotencyStrat',
+ 'getBufferFromReadable',
+ 'restore',
+ ],
+});
+
+/**
+ * Reference to the {@link File} class.
+ * @name module:@google-cloud/storage.File
+ * @see File
+ */
+export {File};
diff --git a/handwritten/storage/src/hash-stream-validator.ts b/handwritten/storage/src/hash-stream-validator.ts
new file mode 100644
index 00000000000..9e9f67d330c
--- /dev/null
+++ b/handwritten/storage/src/hash-stream-validator.ts
@@ -0,0 +1,161 @@
+// Copyright 2022 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.
+
+import {createHash, Hash} from 'crypto';
+import {Transform} from 'stream';
+
+import {
+ CRC32CValidatorGenerator,
+ CRC32C_DEFAULT_VALIDATOR_GENERATOR,
+ CRC32CValidator,
+} from './crc32c.js';
+import {FileExceptionMessages, RequestError} from './file.js';
+
+interface HashStreamValidatorOptions {
+ /** Enables CRC32C calculation. To validate a provided value use `crc32cExpected`. */
+ crc32c: boolean;
+ /** Enables MD5 calculation. To validate a provided value use `md5Expected`. */
+ md5: boolean;
+ /** A CRC32C instance for validation. To validate a provided value use `crc32cExpected`. */
+ crc32cInstance: CRC32CValidator;
+ /** Set a custom CRC32C generator. Used if `crc32cInstance` has not been provided. */
+ crc32cGenerator: CRC32CValidatorGenerator;
+ /** Sets the expected CRC32C value to verify once all data has been consumed. Also sets the `crc32c` option to `true` */
+ crc32cExpected?: string;
+ /** Sets the expected MD5 value to verify once all data has been consumed. Also sets the `md5` option to `true` */
+ md5Expected?: string;
+ /** Indicates whether or not to run a validation check or only update the hash values */
+ updateHashesOnly?: boolean;
+}
+class HashStreamValidator extends Transform {
+ readonly crc32cEnabled: boolean;
+ readonly md5Enabled: boolean;
+ readonly crc32cExpected: string | undefined;
+ readonly md5Expected: string | undefined;
+ readonly updateHashesOnly: boolean = false;
+
+ #crc32cHash?: CRC32CValidator = undefined;
+ #md5Hash?: Hash = undefined;
+ #md5Digest = '';
+
+ constructor(options: Partial = {}) {
+ super();
+
+ this.crc32cEnabled = !!options.crc32c;
+ this.md5Enabled = !!options.md5;
+ this.updateHashesOnly = !!options.updateHashesOnly;
+ this.crc32cExpected = options.crc32cExpected;
+ this.md5Expected = options.md5Expected;
+
+ if (this.crc32cEnabled) {
+ if (options.crc32cInstance) {
+ this.#crc32cHash = options.crc32cInstance;
+ } else {
+ const crc32cGenerator =
+ options.crc32cGenerator || CRC32C_DEFAULT_VALIDATOR_GENERATOR;
+
+ this.#crc32cHash = crc32cGenerator();
+ }
+ }
+
+ if (this.md5Enabled) {
+ this.#md5Hash = createHash('md5');
+ }
+ }
+
+ /**
+ * Return the current CRC32C value, if available.
+ */
+ get crc32c() {
+ return this.#crc32cHash?.toString();
+ }
+
+ /**
+ * Return the calculated MD5 value, if available.
+ */
+ get md5Digest(): string | undefined {
+ if (this.#md5Hash && !this.#md5Digest) {
+ this.#md5Digest = this.#md5Hash.digest('base64');
+ }
+ return this.#md5Digest;
+ }
+
+ _flush(callback: (error?: Error | null | undefined) => void) {
+ // Triggers the getter logic to finalize and cache the MD5 digest
+ this.md5Digest;
+
+ if (this.updateHashesOnly) {
+ callback();
+ return;
+ }
+
+ // If we're doing validation, assume the worst-- a data integrity
+ // mismatch. If not, these tests won't be performed, and we can assume
+ // the best.
+ // We must check if the server decompressed the data on serve because hash
+ // validation is not possible in this case.
+ let failed = this.crc32cEnabled || this.md5Enabled;
+
+ if (this.crc32cEnabled && this.crc32cExpected) {
+ failed = !this.test('crc32c', this.crc32cExpected);
+ }
+
+ if (this.md5Enabled && this.md5Expected) {
+ failed = !this.test('md5', this.md5Expected);
+ }
+
+ if (failed) {
+ const mismatchError = new RequestError(
+ FileExceptionMessages.DOWNLOAD_MISMATCH
+ );
+ mismatchError.code = 'CONTENT_DOWNLOAD_MISMATCH';
+
+ callback(mismatchError);
+ } else {
+ callback();
+ }
+ }
+
+ _transform(
+ chunk: Buffer,
+ encoding: BufferEncoding,
+ callback: (e?: Error) => void
+ ) {
+ this.push(chunk, encoding);
+
+ try {
+ if (this.#crc32cHash) this.#crc32cHash.update(chunk);
+ if (this.#md5Hash) this.#md5Hash.update(chunk);
+ callback();
+ } catch (e) {
+ callback(e as Error);
+ }
+ }
+
+ test(hash: 'crc32c' | 'md5', sum: Buffer | string): boolean {
+ const check = Buffer.isBuffer(sum) ? sum.toString('base64') : sum;
+
+ if (hash === 'crc32c' && this.#crc32cHash) {
+ return this.#crc32cHash.validate(check);
+ }
+
+ if (hash === 'md5' && this.#md5Hash) {
+ return this.#md5Digest === check;
+ }
+
+ return false;
+ }
+}
+
+export {HashStreamValidator, HashStreamValidatorOptions};
diff --git a/handwritten/storage/src/hmacKey.ts b/handwritten/storage/src/hmacKey.ts
new file mode 100644
index 00000000000..9b79daa5e8c
--- /dev/null
+++ b/handwritten/storage/src/hmacKey.ts
@@ -0,0 +1,419 @@
+// Copyright 2019 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.
+
+import {
+ ServiceObject,
+ Methods,
+ MetadataCallback,
+ SetMetadataResponse,
+} from './nodejs-common/index.js';
+import {
+ BaseMetadata,
+ SetMetadataOptions,
+} from './nodejs-common/service-object.js';
+import {IdempotencyStrategy, Storage} from './storage.js';
+import {promisifyAll} from '@google-cloud/promisify';
+
+export interface HmacKeyOptions {
+ projectId?: string;
+}
+
+export interface HmacKeyMetadata extends BaseMetadata {
+ accessId?: string;
+ etag?: string;
+ projectId?: string;
+ serviceAccountEmail?: string;
+ state?: string;
+ timeCreated?: string;
+ updated?: string;
+}
+
+export interface SetHmacKeyMetadataOptions {
+ /**
+ * This parameter is currently ignored.
+ */
+ userProject?: string;
+}
+
+export interface SetHmacKeyMetadata {
+ state?: 'ACTIVE' | 'INACTIVE';
+ etag?: string;
+}
+
+export interface HmacKeyMetadataCallback {
+ (err: Error | null, metadata?: HmacKeyMetadata, apiResponse?: unknown): void;
+}
+
+export type HmacKeyMetadataResponse = [HmacKeyMetadata, unknown];
+
+/**
+ * The API-formatted resource description of the HMAC key.
+ *
+ * Note: This is not guaranteed to be up-to-date when accessed. To get the
+ * latest record, call the `getMetadata()` method.
+ *
+ * @name HmacKey#metadata
+ * @type {object}
+ */
+/**
+ * An HmacKey object contains metadata of an HMAC key created from a
+ * service account through the {@link Storage} client using
+ * {@link Storage#createHmacKey}.
+ *
+ * See {@link https://cloud.google.com/storage/docs/authentication/hmackeys| HMAC keys documentation}
+ *
+ * @class
+ */
+export class HmacKey extends ServiceObject {
+ /**
+ * A reference to the {@link Storage} associated with this {@link HmacKey}
+ * instance.
+ * @name HmacKey#storage
+ * @type {Storage}
+ */
+ storage: Storage;
+ private instanceRetryValue?: boolean;
+
+ /**
+ * @typedef {object} HmacKeyOptions
+ * @property {string} [projectId] The project ID of the project that owns
+ * the service account of the requested HMAC key. If not provided,
+ * the project ID used to instantiate the Storage client will be used.
+ */
+ /**
+ * Constructs an HmacKey object.
+ *
+ * Note: this only create a local reference to an HMAC key, to create
+ * an HMAC key, use {@link Storage#createHmacKey}.
+ *
+ * @param {Storage} storage The Storage instance this HMAC key is
+ * attached to.
+ * @param {string} accessId The unique accessId for this HMAC key.
+ * @param {HmacKeyOptions} options Constructor configurations.
+ * @example
+ * ```
+ * const {Storage} = require('@google-cloud/storage');
+ * const storage = new Storage();
+ * const hmacKey = storage.hmacKey('access-id');
+ * ```
+ */
+ constructor(storage: Storage, accessId: string, options?: HmacKeyOptions) {
+ const methods = {
+ /**
+ * @typedef {object} DeleteHmacKeyOptions
+ * @property {string} [userProject] This parameter is currently ignored.
+ */
+ /**
+ * @typedef {array} DeleteHmacKeyResponse
+ * @property {object} 0 The full API response.
+ */
+ /**
+ * @callback DeleteHmacKeyCallback
+ * @param {?Error} err Request error, if any.
+ * @param {object} apiResponse The full API response.
+ */
+ /**
+ * Deletes an HMAC key.
+ * Key state must be set to `INACTIVE` prior to deletion.
+ * Caution: HMAC keys cannot be recovered once you delete them.
+ *
+ * The authenticated user must have `storage.hmacKeys.delete` permission for the project in which the key exists.
+ *
+ * @method HmacKey#delete
+ * @param {DeleteHmacKeyOptions} [options] Configuration options.
+ * @param {DeleteHmacKeyCallback} [callback] Callback function.
+ * @returns {Promise}
+ *
+ * @example
+ * ```
+ * const {Storage} = require('@google-cloud/storage');
+ * const storage = new Storage();
+ *
+ * //-
+ * // Delete HMAC key after making the key inactive.
+ * //-
+ * const hmacKey = storage.hmacKey('ACCESS_ID');
+ * hmacKey.setMetadata({state: 'INACTIVE'}, (err, hmacKeyMetadata) => {
+ * if (err) {
+ * // The request was an error.
+ * console.error(err);
+ * return;
+ * }
+ * hmacKey.delete((err) => {
+ * if (err) {
+ * console.error(err);
+ * return;
+ * }
+ * // The HMAC key is deleted.
+ * });
+ * });
+ *
+ * //-
+ * // If the callback is omitted, a promise is returned.
+ * //-
+ * const hmacKey = storage.hmacKey('ACCESS_ID');
+ * hmacKey
+ * .setMetadata({state: 'INACTIVE'})
+ * .then(() => {
+ * return hmacKey.delete();
+ * });
+ * ```
+ */
+ delete: true,
+ /**
+ * @callback GetHmacKeyCallback
+ * @param {?Error} err Request error, if any.
+ * @param {HmacKey} hmacKey this {@link HmacKey} instance.
+ * @param {object} apiResponse The full API response.
+ */
+ /**
+ * @typedef {array} GetHmacKeyResponse
+ * @property {HmacKey} 0 This {@link HmacKey} instance.
+ * @property {object} 1 The full API response.
+ */
+ /**
+ * @typedef {object} GetHmacKeyOptions
+ * @property {string} [userProject] This parameter is currently ignored.
+ */
+ /**
+ * Retrieves and populate an HMAC key's metadata, and return
+ * this {@link HmacKey} instance.
+ *
+ * HmacKey.get() does not give the HMAC key secret, as
+ * it is only returned on creation.
+ *
+ * The authenticated user must have `storage.hmacKeys.get` permission
+ * for the project in which the key exists.
+ *
+ * @method HmacKey#get
+ * @param {GetHmacKeyOptions} [options] Configuration options.
+ * @param {GetHmacKeyCallback} [callback] Callback function.
+ * @returns {Promise}
+ *
+ * @example
+ * ```
+ * const {Storage} = require('@google-cloud/storage');
+ * const storage = new Storage();
+ *
+ * //-
+ * // Get the HmacKey's Metadata.
+ * //-
+ * storage.hmacKey('ACCESS_ID')
+ * .get((err, hmacKey) => {
+ * if (err) {
+ * // The request was an error.
+ * console.error(err);
+ * return;
+ * }
+ * // do something with the returned HmacKey object.
+ * });
+ *
+ * //-
+ * // If the callback is omitted, a promise is returned.
+ * //-
+ * storage.hmacKey('ACCESS_ID')
+ * .get()
+ * .then((data) => {
+ * const hmacKey = data[0];
+ * });
+ * ```
+ */
+ get: true,
+ /**
+ * @typedef {object} GetHmacKeyMetadataOptions
+ * @property {string} [userProject] This parameter is currently ignored.
+ */
+ /**
+ * Retrieves and populate an HMAC key's metadata, and return
+ * the HMAC key's metadata as an object.
+ *
+ * HmacKey.getMetadata() does not give the HMAC key secret, as
+ * it is only returned on creation.
+ *
+ * The authenticated user must have `storage.hmacKeys.get` permission
+ * for the project in which the key exists.
+ *
+ * @method HmacKey#getMetadata
+ * @param {GetHmacKeyMetadataOptions} [options] Configuration options.
+ * @param {HmacKeyMetadataCallback} [callback] Callback function.
+ * @returns {Promise}
+ *
+ * @example
+ * ```
+ * const {Storage} = require('@google-cloud/storage');
+ * const storage = new Storage();
+ *
+ * //-
+ * // Get the HmacKey's metadata and populate to the metadata property.
+ * //-
+ * storage.hmacKey('ACCESS_ID')
+ * .getMetadata((err, hmacKeyMetadata) => {
+ * if (err) {
+ * // The request was an error.
+ * console.error(err);
+ * return;
+ * }
+ * console.log(hmacKeyMetadata);
+ * });
+ *
+ * //-
+ * // If the callback is omitted, a promise is returned.
+ * //-
+ * storage.hmacKey('ACCESS_ID')
+ * .getMetadata()
+ * .then((data) => {
+ * const hmacKeyMetadata = data[0];
+ * console.log(hmacKeyMetadata);
+ * });
+ * ```
+ */
+ getMetadata: true,
+ /**
+ * @typedef {object} SetHmacKeyMetadata Subset of {@link HmacKeyMetadata} to update.
+ * @property {string} state New state of the HmacKey. Either 'ACTIVE' or 'INACTIVE'.
+ * @property {string} [etag] Include an etag from a previous get HMAC key request
+ * to perform safe read-modify-write.
+ */
+ /**
+ * @typedef {object} SetHmacKeyMetadataOptions
+ * @property {string} [userProject] This parameter is currently ignored.
+ */
+ /**
+ * @callback HmacKeyMetadataCallback
+ * @param {?Error} err Request error, if any.
+ * @param {HmacKeyMetadata} metadata The updated {@link HmacKeyMetadata} object.
+ * @param {object} apiResponse The full API response.
+ */
+ /**
+ * @typedef {array} HmacKeyMetadataResponse
+ * @property {HmacKeyMetadata} 0 The updated {@link HmacKeyMetadata} object.
+ * @property {object} 1 The full API response.
+ */
+ /**
+ * Updates the state of an HMAC key. See {@link SetHmacKeyMetadata} for
+ * valid states.
+ *
+ * @method HmacKey#setMetadata
+ * @param {SetHmacKeyMetadata} metadata The new metadata.
+ * @param {SetHmacKeyMetadataOptions} [options] Configuration options.
+ * @param {HmacKeyMetadataCallback} [callback] Callback function.
+ * @returns {Promise}
+ *
+ * @example
+ * ```
+ * const {Storage} = require('@google-cloud/storage');
+ * const storage = new Storage();
+ *
+ * const metadata = {
+ * state: 'INACTIVE',
+ * };
+ *
+ * storage.hmacKey('ACCESS_ID')
+ * .setMetadata(metadata, (err, hmacKeyMetadata) => {
+ * if (err) {
+ * // The request was an error.
+ * console.error(err);
+ * return;
+ * }
+ * console.log(hmacKeyMetadata);
+ * });
+ *
+ * //-
+ * // If the callback is omitted, a promise is returned.
+ * //-
+ * storage.hmacKey('ACCESS_ID')
+ * .setMetadata(metadata)
+ * .then((data) => {
+ * const hmacKeyMetadata = data[0];
+ * console.log(hmacKeyMetadata);
+ * });
+ * ```
+ */
+ setMetadata: {
+ reqOpts: {
+ method: 'PUT',
+ },
+ },
+ } as Methods;
+
+ const projectId = (options && options.projectId) || storage.projectId;
+
+ super({
+ parent: storage,
+ id: accessId,
+ baseUrl: `/projects/${projectId}/hmacKeys`,
+ methods,
+ });
+
+ this.storage = storage;
+ this.instanceRetryValue = storage.retryOptions.autoRetry;
+ }
+
+ /**
+ * Set the metadata for this object.
+ *
+ * @param {object} metadata - The metadata to set on this object.
+ * @param {object=} options - Configuration options.
+ * @param {function=} callback - The callback function.
+ * @param {?error} callback.err - An error returned while making this request.
+ * @param {object} callback.apiResponse - The full API response.
+ */
+ setMetadata(
+ metadata: HmacKeyMetadata,
+ options?: SetMetadataOptions
+ ): Promise>;
+ setMetadata(
+ metadata: HmacKeyMetadata,
+ callback: MetadataCallback
+ ): void;
+ setMetadata(
+ metadata: HmacKeyMetadata,
+ options: SetMetadataOptions,
+ callback: MetadataCallback
+ ): void;
+ setMetadata(
+ metadata: HmacKeyMetadata,
+ optionsOrCallback: SetMetadataOptions | MetadataCallback,
+ cb?: MetadataCallback
+ ): Promise> | void {
+ // ETag preconditions are not currently supported. Retries should be disabled if the idempotency strategy is not set to RetryAlways
+ if (
+ this.storage.retryOptions.idempotencyStrategy !==
+ IdempotencyStrategy.RetryAlways
+ ) {
+ this.storage.retryOptions.autoRetry = false;
+ }
+ const options =
+ typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
+ cb =
+ typeof optionsOrCallback === 'function'
+ ? (optionsOrCallback as MetadataCallback)
+ : cb;
+
+ super
+ .setMetadata(metadata, options)
+ .then(resp => cb!(null, ...resp))
+ .catch(cb!)
+ .finally(() => {
+ this.storage.retryOptions.autoRetry = this.instanceRetryValue;
+ });
+ }
+}
+
+/*! Developer Documentation
+ *
+ * All async methods (except for streams) will return a Promise in the event
+ * that a callback is omitted.
+ */
+promisifyAll(HmacKey);
diff --git a/handwritten/storage/src/iam.ts b/handwritten/storage/src/iam.ts
new file mode 100644
index 00000000000..8f6ee5d76d3
--- /dev/null
+++ b/handwritten/storage/src/iam.ts
@@ -0,0 +1,497 @@
+// Copyright 2019 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.
+
+import {
+ BodyResponseCallback,
+ DecorateRequestOptions,
+} from './nodejs-common/index.js';
+import {promisifyAll} from '@google-cloud/promisify';
+
+import {Bucket} from './bucket.js';
+import {normalize} from './util.js';
+
+export interface GetPolicyOptions {
+ userProject?: string;
+ requestedPolicyVersion?: number;
+}
+
+export type GetPolicyResponse = [Policy, unknown];
+
+/**
+ * @callback GetPolicyCallback
+ * @param {?Error} err Request error, if any.
+ * @param {object} acl The policy.
+ * @param {object} apiResponse The full API response.
+ */
+export interface GetPolicyCallback {
+ (err?: Error | null, acl?: Policy, apiResponse?: unknown): void;
+}
+
+/**
+ * @typedef {object} SetPolicyOptions
+ * @param {string} [userProject] The ID of the project which will be
+ * billed for the request.
+ */
+export interface SetPolicyOptions {
+ userProject?: string;
+}
+
+/**
+ * @typedef {array} SetPolicyResponse
+ * @property {object} 0 The policy.
+ * @property {object} 1 The full API response.
+ */
+export type SetPolicyResponse = [Policy, unknown];
+
+/**
+ * @callback SetPolicyCallback
+ * @param {?Error} err Request error, if any.
+ * @param {object} acl The policy.
+ * @param {object} apiResponse The full API response.
+ */
+export interface SetPolicyCallback {
+ (err?: Error | null, acl?: Policy, apiResponse?: object): void;
+}
+
+export interface Policy {
+ bindings: PolicyBinding[];
+ version?: number;
+ etag?: string;
+}
+
+export interface PolicyBinding {
+ role: string;
+ members: string[];
+ condition?: Expr;
+}
+
+export interface Expr {
+ title?: string;
+ description?: string;
+ expression: string;
+}
+
+/**
+ * @typedef {array} TestIamPermissionsResponse
+ * @property {object} 0 A subset of permissions that the caller is allowed.
+ * @property {object} 1 The full API response.
+ */
+export type TestIamPermissionsResponse = [{[key: string]: boolean}, unknown];
+
+/**
+ * @callback TestIamPermissionsCallback
+ * @param {?Error} err Request error, if any.
+ * @param {object} acl A subset of permissions that the caller is allowed.
+ * @param {object} apiResponse The full API response.
+ */
+export interface TestIamPermissionsCallback {
+ (
+ err?: Error | null,
+ acl?: {[key: string]: boolean} | null,
+ apiResponse?: unknown
+ ): void;
+}
+
+/**
+ * @typedef {object} TestIamPermissionsOptions Configuration options for Iam#testPermissions().
+ * @param {string} [userProject] The ID of the project which will be
+ * billed for the request.
+ */
+export interface TestIamPermissionsOptions {
+ userProject?: string;
+}
+
+interface GetPolicyRequest {
+ userProject?: string;
+ optionsRequestedPolicyVersion?: number;
+}
+
+export enum IAMExceptionMessages {
+ POLICY_OBJECT_REQUIRED = 'A policy object is required.',
+ PERMISSIONS_REQUIRED = 'Permissions are required.',
+}
+
+/**
+ * Get and set IAM policies for your Cloud Storage bucket.
+ *
+ * See {@link https://cloud.google.com/storage/docs/access-control/iam#short_title_iam_management| Cloud Storage IAM Management}
+ * See {@link https://cloud.google.com/iam/docs/granting-changing-revoking-access| Granting, Changing, and Revoking Access}
+ * See {@link https://cloud.google.com/iam/docs/understanding-roles| IAM Roles}
+ *
+ * @constructor Iam
+ *
+ * @param {Bucket} bucket The parent instance.
+ * @example
+ * ```
+ * const {Storage} = require('@google-cloud/storage');
+ * const storage = new Storage();
+ * const bucket = storage.bucket('my-bucket');
+ * // bucket.iam
+ * ```
+ */
+class Iam {
+ private request_: (
+ reqOpts: DecorateRequestOptions,
+ callback: BodyResponseCallback
+ ) => void;
+ private resourceId_: string;
+
+ constructor(bucket: Bucket) {
+ this.request_ = bucket.request.bind(bucket);
+ this.resourceId_ = 'buckets/' + bucket.getId();
+ }
+
+ getPolicy(options?: GetPolicyOptions): Promise;
+ getPolicy(options: GetPolicyOptions, callback: GetPolicyCallback): void;
+ getPolicy(callback: GetPolicyCallback): void;
+ /**
+ * @typedef {object} GetPolicyOptions Requested options for IAM#getPolicy().
+ * @property {number} [requestedPolicyVersion] The version of IAM policies to
+ * request. If a policy with a condition is requested without setting
+ * this, the server will return an error. This must be set to a value
+ * of 3 to retrieve IAM policies containing conditions. This is to
+ * prevent client code that isn't aware of IAM conditions from
+ * interpreting and modifying policies incorrectly. The service might
+ * return a policy with version lower than the one that was requested,
+ * based on the feature syntax in the policy fetched.
+ * See {@link https://cloud.google.com/iam/docs/policies#versions| IAM Policy versions}
+ * @property {string} [userProject] The ID of the project which will be
+ * billed for the request.
+ */
+ /**
+ * @typedef {array} GetPolicyResponse
+ * @property {Policy} 0 The policy.
+ * @property {object} 1 The full API response.
+ */
+ /**
+ * @typedef {object} Policy
+ * @property {PolicyBinding[]} policy.bindings Bindings associate members with roles.
+ * @property {string} [policy.etag] Etags are used to perform a read-modify-write.
+ * @property {number} [policy.version] The syntax schema version of the Policy.
+ * To set an IAM policy with conditional binding, this field must be set to
+ * 3 or greater.
+ * See {@link https://cloud.google.com/iam/docs/policies#versions| IAM Policy versions}
+ */
+ /**
+ * @typedef {object} PolicyBinding
+ * @property {string} role Role that is assigned to members.
+ * @property {string[]} members Specifies the identities requesting access for the bucket.
+ * @property {Expr} [condition] The condition that is associated with this binding.
+ */
+ /**
+ * @typedef {object} Expr
+ * @property {string} [title] An optional title for the expression, i.e. a
+ * short string describing its purpose. This can be used e.g. in UIs
+ * which allow to enter the expression.
+ * @property {string} [description] An optional description of the
+ * expression. This is a longer text which describes the expression,
+ * e.g. when hovered over it in a UI.
+ * @property {string} expression Textual representation of an expression in
+ * Common Expression Language syntax. The application context of the
+ * containing message determines which well-known feature set of CEL
+ * is supported.The condition that is associated with this binding.
+ *
+ * @see [Condition] https://cloud.google.com/storage/docs/access-control/iam#conditions
+ */
+ /**
+ * Get the IAM policy.
+ *
+ * @param {GetPolicyOptions} [options] Request options.
+ * @param {GetPolicyCallback} [callback] Callback function.
+ * @returns {Promise}
+ *
+ * See {@link https://cloud.google.com/storage/docs/json_api/v1/buckets/getIamPolicy| Buckets: setIamPolicy API Documentation}
+ *
+ * @example
+ * ```
+ * const {Storage} = require('@google-cloud/storage');
+ * const storage = new Storage();
+ * const bucket = storage.bucket('my-bucket');
+ *
+ * bucket.iam.getPolicy(
+ * {requestedPolicyVersion: 3},
+ * function(err, policy, apiResponse) {
+ *
+ * },
+ * );
+ *
+ * //-
+ * // If the callback is omitted, we'll return a Promise.
+ * //-
+ * bucket.iam.getPolicy({requestedPolicyVersion: 3})
+ * .then(function(data) {
+ * const policy = data[0];
+ * const apiResponse = data[1];
+ * });
+ *
+ * ```
+ * @example include:samples/iam.js
+ * region_tag:storage_view_bucket_iam_members
+ * Example of retrieving a bucket's IAM policy:
+ */
+ getPolicy(
+ optionsOrCallback?: GetPolicyOptions | GetPolicyCallback,
+ callback?: GetPolicyCallback
+ ): Promise | void {
+ const {options, callback: cb} = normalize<
+ GetPolicyOptions,
+ GetPolicyCallback
+ >(optionsOrCallback, callback);
+
+ const qs: GetPolicyRequest = {};
+ if (options.userProject) {
+ qs.userProject = options.userProject;
+ }
+
+ if (
+ options.requestedPolicyVersion !== null &&
+ options.requestedPolicyVersion !== undefined
+ ) {
+ qs.optionsRequestedPolicyVersion = options.requestedPolicyVersion;
+ }
+
+ this.request_(
+ {
+ uri: '/iam',
+ qs,
+ },
+ cb!
+ );
+ }
+
+ setPolicy(
+ policy: Policy,
+ options?: SetPolicyOptions
+ ): Promise;
+ setPolicy(policy: Policy, callback: SetPolicyCallback): void;
+ setPolicy(
+ policy: Policy,
+ options: SetPolicyOptions,
+ callback: SetPolicyCallback
+ ): void;
+ /**
+ * Set the IAM policy.
+ *
+ * @throws {Error} If no policy is provided.
+ *
+ * @param {Policy} policy The policy.
+ * @param {SetPolicyOptions} [options] Configuration options.
+ * @param {SetPolicyCallback} callback Callback function.
+ * @returns {Promise}
+ *
+ * See {@link https://cloud.google.com/storage/docs/json_api/v1/buckets/setIamPolicy| Buckets: setIamPolicy API Documentation}
+ * See {@link https://cloud.google.com/iam/docs/understanding-roles| IAM Roles}
+ *
+ * @example
+ * ```
+ * const {Storage} = require('@google-cloud/storage');
+ * const storage = new Storage();
+ * const bucket = storage.bucket('my-bucket');
+ *
+ * const myPolicy = {
+ * bindings: [
+ * {
+ * role: 'roles/storage.admin',
+ * members:
+ * ['serviceAccount:myotherproject@appspot.gserviceaccount.com']
+ * }
+ * ]
+ * };
+ *
+ * bucket.iam.setPolicy(myPolicy, function(err, policy, apiResponse) {});
+ *
+ * //-
+ * // If the callback is omitted, we'll return a Promise.
+ * //-
+ * bucket.iam.setPolicy(myPolicy).then(function(data) {
+ * const policy = data[0];
+ * const apiResponse = data[1];
+ * });
+ *
+ * ```
+ * @example include:samples/iam.js
+ * region_tag:storage_add_bucket_iam_member
+ * Example of adding to a bucket's IAM policy:
+ *
+ * @example include:samples/iam.js
+ * region_tag:storage_remove_bucket_iam_member
+ * Example of removing from a bucket's IAM policy:
+ */
+ setPolicy(
+ policy: Policy,
+ optionsOrCallback?: SetPolicyOptions | SetPolicyCallback,
+ callback?: SetPolicyCallback
+ ): Promise | void {
+ if (policy === null || typeof policy !== 'object') {
+ throw new Error(IAMExceptionMessages.POLICY_OBJECT_REQUIRED);
+ }
+
+ const {options, callback: cb} = normalize<
+ SetPolicyOptions,
+ SetPolicyCallback
+ >(optionsOrCallback, callback);
+
+ let maxRetries;
+ if (policy.etag === undefined) {
+ maxRetries = 0;
+ }
+
+ this.request_(
+ {
+ method: 'PUT',
+ uri: '/iam',
+ maxRetries,
+ json: Object.assign(
+ {
+ resourceId: this.resourceId_,
+ },
+ policy
+ ),
+ qs: options,
+ },
+ cb
+ );
+ }
+
+ testPermissions(
+ permissions: string | string[],
+ options?: TestIamPermissionsOptions
+ ): Promise;
+ testPermissions(
+ permissions: string | string[],
+ callback: TestIamPermissionsCallback
+ ): void;
+ testPermissions(
+ permissions: string | string[],
+ options: TestIamPermissionsOptions,
+ callback: TestIamPermissionsCallback
+ ): void;
+ /**
+ * Test a set of permissions for a resource.
+ *
+ * @throws {Error} If permissions are not provided.
+ *
+ * @param {string|string[]} permissions The permission(s) to test for.
+ * @param {TestIamPermissionsOptions} [options] Configuration object.
+ * @param {TestIamPermissionsCallback} [callback] Callback function.
+ * @returns {Promise}
+ *
+ * See {@link https://cloud.google.com/storage/docs/json_api/v1/buckets/testIamPermissions| Buckets: testIamPermissions API Documentation}
+ *
+ * @example
+ * ```
+ * const {Storage} = require('@google-cloud/storage');
+ * const storage = new Storage();
+ * const bucket = storage.bucket('my-bucket');
+ *
+ * //-
+ * // Test a single permission.
+ * //-
+ * const test = 'storage.buckets.delete';
+ *
+ * bucket.iam.testPermissions(test, function(err, permissions, apiResponse) {
+ * console.log(permissions);
+ * // {
+ * // "storage.buckets.delete": true
+ * // }
+ * });
+ *
+ * //-
+ * // Test several permissions at once.
+ * //-
+ * const tests = [
+ * 'storage.buckets.delete',
+ * 'storage.buckets.get'
+ * ];
+ *
+ * bucket.iam.testPermissions(tests, function(err, permissions) {
+ * console.log(permissions);
+ * // {
+ * // "storage.buckets.delete": false,
+ * // "storage.buckets.get": true
+ * // }
+ * });
+ *
+ * //-
+ * // If the callback is omitted, we'll return a Promise.
+ * //-
+ * bucket.iam.testPermissions(test).then(function(data) {
+ * const permissions = data[0];
+ * const apiResponse = data[1];
+ * });
+ * ```
+ */
+ testPermissions(
+ permissions: string | string[],
+ optionsOrCallback?: TestIamPermissionsOptions | TestIamPermissionsCallback,
+ callback?: TestIamPermissionsCallback
+ ): Promise | void {
+ if (!Array.isArray(permissions) && typeof permissions !== 'string') {
+ throw new Error(IAMExceptionMessages.PERMISSIONS_REQUIRED);
+ }
+
+ const {options, callback: cb} = normalize<
+ TestIamPermissionsOptions,
+ TestIamPermissionsCallback
+ >(optionsOrCallback, callback);
+
+ const permissionsArray = Array.isArray(permissions)
+ ? permissions
+ : [permissions];
+
+ const req = Object.assign(
+ {
+ permissions: permissionsArray,
+ },
+ options
+ );
+
+ this.request_(
+ {
+ uri: '/iam/testPermissions',
+ qs: req,
+ useQuerystring: true,
+ },
+ (err, resp) => {
+ if (err) {
+ cb!(err, null, resp);
+ return;
+ }
+
+ const availablePermissions = Array.isArray(resp.permissions)
+ ? resp.permissions
+ : [];
+
+ const permissionsHash = permissionsArray.reduce(
+ (acc: {[index: string]: boolean}, permission) => {
+ acc[permission] = availablePermissions.indexOf(permission) > -1;
+ return acc;
+ },
+ {}
+ );
+
+ cb!(null, permissionsHash, resp);
+ }
+ );
+ }
+}
+
+/*! Developer Documentation
+ *
+ * All async methods (except for streams) will return a Promise in the event
+ * that a callback is omitted.
+ */
+promisifyAll(Iam);
+
+export {Iam};
diff --git a/handwritten/storage/src/index.ts b/handwritten/storage/src/index.ts
new file mode 100644
index 00000000000..32d2728bdeb
--- /dev/null
+++ b/handwritten/storage/src/index.ts
@@ -0,0 +1,272 @@
+// Copyright 2019 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.
+
+/**
+ * The `@google-cloud/storage` package has a single named export which is the
+ * {@link Storage} (ES6) class, which should be instantiated with `new`.
+ *
+ * See {@link Storage} and {@link ClientConfig} for client methods and
+ * configuration options.
+ *
+ * @module {Storage} @google-cloud/storage
+ * @alias nodejs-storage
+ *
+ * @example
+ * Install the client library with npm:
+ * ```
+ * npm install --save @google-cloud/storage
+ * ```
+ *
+ * @example
+ * Import the client library
+ * ```
+ * const {Storage} = require('@google-cloud/storage');
+ * ```
+ *
+ * @example
+ * Create a client that uses Application
+ * Default Credentials (ADC):
+ * ```
+ * const storage = new Storage();
+ * ```
+ *
+ * @example
+ * Create a client with explicit
+ * credentials:
+ * ```
+ * const storage = new Storage({ projectId:
+ * 'your-project-id', keyFilename: '/path/to/keyfile.json'
+ * });
+ * ```
+ *
+ * @example include:samples/quickstart.js
+ * region_tag:storage_quickstart
+ * Full quickstart example:
+ */
+export {ApiError} from './nodejs-common/index.js';
+export {
+ BucketCallback,
+ BucketOptions,
+ CreateBucketQuery,
+ CreateBucketRequest,
+ CreateBucketResponse,
+ CreateHmacKeyCallback,
+ CreateHmacKeyOptions,
+ CreateHmacKeyResponse,
+ GetBucketsCallback,
+ GetBucketsRequest,
+ GetBucketsResponse,
+ GetHmacKeysCallback,
+ GetHmacKeysOptions,
+ GetHmacKeysResponse,
+ GetServiceAccountCallback,
+ GetServiceAccountOptions,
+ GetServiceAccountResponse,
+ HmacKeyResourceResponse,
+ IdempotencyStrategy,
+ PreconditionOptions,
+ RETRYABLE_ERR_FN_DEFAULT,
+ ServiceAccount,
+ Storage,
+ StorageOptions,
+} from './storage.js';
+export {
+ AclMetadata,
+ AccessControlObject,
+ AclOptions,
+ AddAclCallback,
+ AddAclOptions,
+ AddAclResponse,
+ GetAclCallback,
+ GetAclOptions,
+ GetAclResponse,
+ RemoveAclCallback,
+ RemoveAclOptions,
+ RemoveAclResponse,
+ UpdateAclCallback,
+ UpdateAclOptions,
+ UpdateAclResponse,
+} from './acl.js';
+export {
+ Bucket,
+ BucketExistsCallback,
+ BucketExistsOptions,
+ BucketExistsResponse,
+ BucketLockCallback,
+ BucketLockResponse,
+ BucketMetadata,
+ CombineCallback,
+ CombineOptions,
+ CombineResponse,
+ CreateChannelCallback,
+ CreateChannelConfig,
+ CreateChannelOptions,
+ CreateChannelResponse,
+ CreateNotificationCallback,
+ CreateNotificationOptions,
+ CreateNotificationResponse,
+ DeleteBucketCallback,
+ DeleteBucketOptions,
+ DeleteBucketResponse,
+ DeleteFilesCallback,
+ DeleteFilesOptions,
+ DeleteLabelsCallback,
+ DeleteLabelsResponse,
+ DisableRequesterPaysCallback,
+ DisableRequesterPaysResponse,
+ EnableRequesterPaysCallback,
+ EnableRequesterPaysResponse,
+ GetBucketCallback,
+ GetBucketMetadataCallback,
+ GetBucketMetadataOptions,
+ GetBucketMetadataResponse,
+ GetBucketOptions,
+ GetBucketResponse,
+ GetBucketSignedUrlConfig,
+ GetFilesCallback,
+ GetFilesOptions,
+ GetFilesResponse,
+ GetLabelsCallback,
+ GetLabelsOptions,
+ GetLabelsResponse,
+ GetNotificationsCallback,
+ GetNotificationsOptions,
+ GetNotificationsResponse,
+ Labels,
+ LifecycleAction,
+ LifecycleCondition,
+ LifecycleRule,
+ MakeBucketPrivateCallback,
+ MakeBucketPrivateOptions,
+ MakeBucketPrivateResponse,
+ MakeBucketPublicCallback,
+ MakeBucketPublicOptions,
+ MakeBucketPublicResponse,
+ SetBucketMetadataCallback,
+ SetBucketMetadataOptions,
+ SetBucketMetadataResponse,
+ SetBucketStorageClassCallback,
+ SetBucketStorageClassOptions,
+ SetLabelsCallback,
+ SetLabelsOptions,
+ SetLabelsResponse,
+ UploadCallback,
+ UploadOptions,
+ UploadResponse,
+} from './bucket.js';
+export * from './crc32c.js';
+export {Channel, StopCallback} from './channel.js';
+export {
+ CopyCallback,
+ CopyOptions,
+ CopyResponse,
+ CreateReadStreamOptions,
+ CreateResumableUploadCallback,
+ CreateResumableUploadOptions,
+ CreateResumableUploadResponse,
+ CreateWriteStreamOptions,
+ DeleteFileCallback,
+ DeleteFileOptions,
+ DeleteFileResponse,
+ DownloadCallback,
+ DownloadOptions,
+ DownloadResponse,
+ EncryptionKeyOptions,
+ File,
+ FileExistsCallback,
+ FileExistsOptions,
+ FileExistsResponse,
+ FileMetadata,
+ FileOptions,
+ GetExpirationDateCallback,
+ GetExpirationDateResponse,
+ GetFileCallback,
+ GetFileMetadataCallback,
+ GetFileMetadataOptions,
+ GetFileMetadataResponse,
+ GetFileOptions,
+ GetFileResponse,
+ GenerateSignedPostPolicyV2Callback,
+ GenerateSignedPostPolicyV2Options,
+ GenerateSignedPostPolicyV2Response,
+ GenerateSignedPostPolicyV4Callback,
+ GenerateSignedPostPolicyV4Options,
+ GenerateSignedPostPolicyV4Response,
+ GetSignedUrlConfig,
+ MakeFilePrivateCallback,
+ MakeFilePrivateOptions,
+ MakeFilePrivateResponse,
+ MakeFilePublicCallback,
+ MakeFilePublicResponse,
+ MoveCallback,
+ MoveOptions,
+ MoveResponse,
+ MoveFileAtomicOptions,
+ MoveFileAtomicCallback,
+ MoveFileAtomicResponse,
+ PolicyDocument,
+ PolicyFields,
+ PredefinedAcl,
+ RotateEncryptionKeyCallback,
+ RotateEncryptionKeyOptions,
+ RotateEncryptionKeyResponse,
+ SaveCallback,
+ SaveData,
+ SaveOptions,
+ SetFileMetadataCallback,
+ SetFileMetadataOptions,
+ SetFileMetadataResponse,
+ SetStorageClassCallback,
+ SetStorageClassOptions,
+ SetStorageClassResponse,
+ SignedPostPolicyV4Output,
+} from './file.js';
+export * from './hash-stream-validator.js';
+export {
+ HmacKey,
+ HmacKeyMetadata,
+ HmacKeyMetadataCallback,
+ HmacKeyMetadataResponse,
+ SetHmacKeyMetadata,
+ SetHmacKeyMetadataOptions,
+} from './hmacKey.js';
+export {
+ GetPolicyCallback,
+ GetPolicyOptions,
+ GetPolicyResponse,
+ Iam,
+ Policy,
+ SetPolicyCallback,
+ SetPolicyOptions,
+ SetPolicyResponse,
+ TestIamPermissionsCallback,
+ TestIamPermissionsOptions,
+ TestIamPermissionsResponse,
+} from './iam.js';
+export {
+ DeleteNotificationCallback,
+ DeleteNotificationOptions,
+ GetNotificationCallback,
+ GetNotificationMetadataCallback,
+ GetNotificationMetadataOptions,
+ GetNotificationMetadataResponse,
+ GetNotificationOptions,
+ GetNotificationResponse,
+ Notification,
+ NotificationMetadata,
+} from './notification.js';
+export {GetSignedUrlCallback, GetSignedUrlResponse} from './signer.js';
+export * from './transfer-manager.js';
diff --git a/handwritten/storage/src/nodejs-common/index.ts b/handwritten/storage/src/nodejs-common/index.ts
new file mode 100644
index 00000000000..89ed3ea815e
--- /dev/null
+++ b/handwritten/storage/src/nodejs-common/index.ts
@@ -0,0 +1,50 @@
+/*!
+ * Copyright 2022 Google LLC. All Rights Reserved.
+ *
+ * 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.
+ */
+export {GoogleAuthOptions} from 'google-auth-library';
+
+export {
+ Service,
+ ServiceConfig,
+ ServiceOptions,
+ StreamRequestOptions,
+} from './service.js';
+
+export {
+ BaseMetadata,
+ DeleteCallback,
+ ExistsCallback,
+ GetConfig,
+ InstanceResponseCallback,
+ Interceptor,
+ MetadataCallback,
+ MetadataResponse,
+ Methods,
+ ResponseCallback,
+ ServiceObject,
+ ServiceObjectConfig,
+ ServiceObjectParent,
+ SetMetadataResponse,
+} from './service-object.js';
+
+export {
+ Abortable,
+ AbortableDuplex,
+ ApiError,
+ BodyResponseCallback,
+ DecorateRequestOptions,
+ ResponseBody,
+ util,
+} from './util.js';
diff --git a/handwritten/storage/src/nodejs-common/service-object.ts b/handwritten/storage/src/nodejs-common/service-object.ts
new file mode 100644
index 00000000000..a4e369a3f5e
--- /dev/null
+++ b/handwritten/storage/src/nodejs-common/service-object.ts
@@ -0,0 +1,621 @@
+/*!
+ * Copyright 2022 Google LLC. All Rights Reserved.
+ *
+ * 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.
+ */
+import {promisifyAll} from '@google-cloud/promisify';
+import {EventEmitter} from 'events';
+import * as r from 'teeny-request';
+
+import {StreamRequestOptions} from './service.js';
+import {
+ ApiError,
+ BodyResponseCallback,
+ DecorateRequestOptions,
+ ResponseBody,
+ util,
+} from './util.js';
+
+export type RequestResponse = [unknown, r.Response];
+
+export interface ServiceObjectParent {
+ interceptors: Interceptor[];
+ getRequestInterceptors(): Function[];
+ requestStream(reqOpts: DecorateRequestOptions): r.Request;
+ request(
+ reqOpts: DecorateRequestOptions,
+ callback: BodyResponseCallback
+ ): void;
+}
+
+export interface Interceptor {
+ request(opts: r.Options): DecorateRequestOptions;
+}
+
+export type GetMetadataOptions = object;
+
+export type MetadataResponse = [K, r.Response];
+export type MetadataCallback = (
+ err: Error | null,
+ metadata?: K,
+ apiResponse?: r.Response
+) => void;
+
+export type ExistsOptions = object;
+export interface ExistsCallback {
+ (err: Error | null, exists?: boolean): void;
+}
+
+export interface ServiceObjectConfig {
+ /**
+ * The base URL to make API requests to.
+ */
+ baseUrl?: string;
+
+ /**
+ * The method which creates this object.
+ */
+ createMethod?: Function;
+
+ /**
+ * The identifier of the object. For example, the name of a Storage bucket or
+ * Pub/Sub topic.
+ */
+ id?: string;
+
+ /**
+ * A map of each method name that should be inherited.
+ */
+ methods?: Methods;
+
+ /**
+ * The parent service instance. For example, an instance of Storage if the
+ * object is Bucket.
+ */
+ parent: ServiceObjectParent;
+
+ /**
+ * Override of projectId, used to allow access to resources in another project.
+ * For example, a BigQuery dataset in another project to which the user has been
+ * granted permission.
+ */
+ projectId?: string;
+}
+
+export interface Methods {
+ [methodName: string]: {reqOpts?: r.CoreOptions} | boolean;
+}
+
+export interface InstanceResponseCallback {
+ (err: ApiError | null, instance?: T | null, apiResponse?: r.Response): void;
+}
+
+// eslint-disable-next-line @typescript-eslint/no-empty-interface
+export interface CreateOptions {}
+// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unused-vars
+export type CreateResponse = any[];
+export interface CreateCallback