diff --git a/core/generator/gapic-generator-typescript/baselines/pubsub-api-dump-esm/api.json.baseline b/core/generator/gapic-generator-typescript/baselines/pubsub-api-dump-esm/api.json.baseline index 6de2dfd0024e..c72641534d0a 100644 --- a/core/generator/gapic-generator-typescript/baselines/pubsub-api-dump-esm/api.json.baseline +++ b/core/generator/gapic-generator-typescript/baselines/pubsub-api-dump-esm/api.json.baseline @@ -5105,6 +5105,7 @@ ] } ], + "resumableUploads": [], "longRunning": [], "diregapicLRO": [], "streaming": [], @@ -10252,6 +10253,7 @@ ] } ], + "resumableUploads": [], "longRunning": [], "diregapicLRO": [], "streaming": [], @@ -16698,6 +16700,7 @@ ] } ], + "resumableUploads": [], "longRunning": [], "diregapicLRO": [], "streaming": [ diff --git a/core/generator/gapic-generator-typescript/baselines/pubsub-api-dump/api.json.baseline b/core/generator/gapic-generator-typescript/baselines/pubsub-api-dump/api.json.baseline index 6de2dfd0024e..c72641534d0a 100644 --- a/core/generator/gapic-generator-typescript/baselines/pubsub-api-dump/api.json.baseline +++ b/core/generator/gapic-generator-typescript/baselines/pubsub-api-dump/api.json.baseline @@ -5105,6 +5105,7 @@ ] } ], + "resumableUploads": [], "longRunning": [], "diregapicLRO": [], "streaming": [], @@ -10252,6 +10253,7 @@ ] } ], + "resumableUploads": [], "longRunning": [], "diregapicLRO": [], "streaming": [], @@ -16698,6 +16700,7 @@ ] } ], + "resumableUploads": [], "longRunning": [], "diregapicLRO": [], "streaming": [ diff --git a/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/.OwlBot.yaml.baseline b/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/.OwlBot.yaml.baseline new file mode 100644 index 000000000000..7feea79e8531 --- /dev/null +++ b/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/.OwlBot.yaml.baseline @@ -0,0 +1,19 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +deep-copy-regex: + - source: /google/samples/resumable/google-samples-resumable-nodejs + dest: /owl-bot-staging/google-samples-resumable + +api-name: resumable \ No newline at end of file diff --git a/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/.babelrc.json.baseline b/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/.babelrc.json.baseline new file mode 100644 index 000000000000..940d91b0a07b --- /dev/null +++ b/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/.babelrc.json.baseline @@ -0,0 +1,19 @@ +{ + "presets": [ + "@babel/preset-typescript", + "@babel/env" + ], + "plugins": [ + [ + "replace-import-extension", + { + "extMapping": { + ".js": ".cjs" + } + } + ], + "./node_modules/gapic-tools/build/src/replaceESMMockingLib.js", + "./node_modules/gapic-tools/build/src/replaceImportMetaUrl.js", + "./node_modules/gapic-tools/build/src/toggleESMFlagVariable.js" + ] +} diff --git a/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/.gitignore.baseline b/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/.gitignore.baseline new file mode 100644 index 000000000000..b391f5f9cd1a --- /dev/null +++ b/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/.gitignore.baseline @@ -0,0 +1,15 @@ +**/*.log +**/node_modules +/.coverage +/coverage +/.nyc_output +/docs/ +/out/ +/build/ +system-test/secrets.js +system-test/*key.json +*.lock +.DS_Store +package-lock.json +__pycache__ +esm/**/*.d.ts \ No newline at end of file diff --git a/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/.jsdoc.cjs.baseline b/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/.jsdoc.cjs.baseline new file mode 100644 index 000000000000..de732583739e --- /dev/null +++ b/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/.jsdoc.cjs.baseline @@ -0,0 +1,54 @@ +// Copyright 2026 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 +// +// https://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. +// +// ** This file is automatically generated by gapic-generator-typescript. ** +// ** https://github.com/googleapis/gapic-generator-typescript ** +// ** All changes to this file may be overwritten. ** + + +module.exports = { + opts: { + readme: './README.md', + package: './package.json', + template: './node_modules/jsdoc-fresh', + recurse: true, + verbose: true, + destination: './docs/' + }, + plugins: [ + 'plugins/markdown', + 'jsdoc-region-tag' + ], + source: { + excludePattern: '(^|\\/|\\\\)[._]', + include: [ + 'build/src', + 'protos' + ], + includePattern: '\\.js$' + }, + templates: { + copyright: 'Copyright 2026 Google LLC', + includeDate: false, + sourceFiles: false, + systemName: 'resumable', + theme: 'lumen', + default: { + outputSourceFiles: false + } + }, + markdown: { + idInHeadings: true + } +}; diff --git a/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/.nycrc.baseline b/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/.nycrc.baseline new file mode 100644 index 000000000000..81a95fc94b00 --- /dev/null +++ b/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/.nycrc.baseline @@ -0,0 +1,24 @@ +{ + "report-dir": "./.coverage", + "reporter": ["text", "lcov"], + "exclude": [ + "**/*-test", + "**/.coverage", + "**/apis", + "**/benchmark", + "**/conformance", + "**/docs", + "**/samples", + "**/scripts", + "**/protos", + "**/test", + "**/*.d.ts", + ".jsdoc.js", + "**/.jsdoc.js", + "karma.conf.js", + "webpack-tests.config.js", + "webpack.config.js" + ], + "exclude-after-remap": false, + "all": true +} \ No newline at end of file diff --git a/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/README.md.baseline b/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/README.md.baseline new file mode 100644 index 000000000000..582e319d972b --- /dev/null +++ b/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/README.md.baseline @@ -0,0 +1,108 @@ +[//]: # "This README.md file is auto-generated, all changes to this file will be lost." +[//]: # "The comments you see below are used to generate those parts of the template in later states." +Google Cloud Platform logo + +# [Resumable: Nodejs Client][homepage] + +[//]: # "releaseLevel" + +[![npm version](https://img.shields.io/npm/v/resumable.svg)](https://www.npmjs.org/package/resumable) + +Resumable client for Node.js + +[//]: # "partials.introduction" + +A comprehensive list of changes in each version may be found in +[the CHANGELOG][homepage_changelog]. + +* [Resumable Nodejs Client API Reference](https://cloud.google.com/nodejs/docs/reference/resumable/latest) + + +Read more about the client libraries for Cloud APIs, including the older +Google APIs Client Libraries, in [Client Libraries Explained][explained]. + +[explained]: https://cloud.google.com/apis/docs/client-libraries-explained + +**Table of contents:** + +* [Quickstart](#quickstart) + * [Before you begin](#before-you-begin) + * [Installing the client library](#installing-the-client-library) + +* [Versioning](#versioning) +* [Contributing](#contributing) +* [License](#license) + +## Quickstart +### Before you begin + +1. [Select or create a Cloud Platform project][projects]. +1. [Enable billing for your project][billing]. +1. [Enable the Resumable API][enable_api]. +1. [Set up authentication][auth] so you can access the + API from your local workstation. +### Installing the client library + +```bash +npm install resumable +``` + +[//]: # "partials.body" + +## Samples + +Samples are in the [`samples/`][homepage_samples] directory. Each sample's `README.md` has instructions for running its sample. + +[//]: # "samples" + +## Supported Node.js Versions + +Our client libraries follow the [Node.js release schedule](https://github.com/nodejs/release#release-schedule). +Libraries are compatible with all current _active_ and _maintenance_ versions of +Node.js. +If you are using an end-of-life version of Node.js, we recommend that you update +as soon as possible to an actively supported LTS version. + +Google's client libraries support legacy versions of Node.js runtimes on a +best-efforts basis with the following warnings: + +* Legacy versions are not tested in continuous integration. +* Some security patches and features cannot be backported. +* Dependencies cannot be kept up-to-date. + +Client libraries targeting some end-of-life versions of Node.js are available, and +can be installed through npm [dist-tags](https://docs.npmjs.com/cli/dist-tag). +The dist-tags follow the naming convention `legacy-(version)`. +For example, `npm install resumable@legacy-8` installs client libraries +for versions compatible with Node.js 8. + +## Versioning + +This library follows [Semantic Versioning](http://semver.org/). + +More Information: [Google Cloud Platform Launch Stages][launch_stages] + +[launch_stages]: https://cloud.google.com/terms/launch-stages + +## Contributing + +Contributions welcome! See the [Contributing Guide](https://github.com/googleapis/google-cloud-node/blob/main/CONTRIBUTING.md). + +Please note that this `README.md` +and a variety of configuration files in this repository (including `.nycrc` and `tsconfig.json`) +are generated from a central template. + +## License + +Apache Version 2.0 + +See [LICENSE](https://github.com/googleapis/google-cloud-node/blob/main/LICENSE) + +[shell_img]: https://gstatic.com/cloudssh/images/open-btn.png +[projects]: https://console.cloud.google.com/project +[billing]: https://support.google.com/cloud/answer/6293499#enable-billing +[enable_api]: https://console.cloud.google.com/flows/enableapi?apiid=resumable.googleapis.com +[auth]: https://cloud.google.com/docs/authentication/external/set-up-adc-local +[homepage_samples]: https://github.com/googleapis/google-cloud-node/blob/main/packages/google-samples-resumable/samples +[homepage_changelog]: https://github.com/googleapis/google-cloud-node/blob/main/packages/google-samples-resumable/CHANGELOG.md +[homepage]: https://github.com/googleapis/google-cloud-node/blob/main/packages/google-samples-resumable diff --git a/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/esm/src/index.ts.baseline b/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/esm/src/index.ts.baseline new file mode 100644 index 000000000000..3bb734bf4631 --- /dev/null +++ b/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/esm/src/index.ts.baseline @@ -0,0 +1,26 @@ +// Copyright 2026 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 +// +// https://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. +// +// ** This file is automatically generated by gapic-generator-typescript. ** +// ** https://github.com/googleapis/gapic-generator-typescript ** +// ** All changes to this file may be overwritten. ** + +import * as v1 from './v1/index.js'; +const ResumableUploadServiceClient = v1.ResumableUploadServiceClient; +type ResumableUploadServiceClient = v1.ResumableUploadServiceClient; +export {v1, ResumableUploadServiceClient}; +export default {v1, ResumableUploadServiceClient}; +// @ts-ignore +import * as protos from '../../protos/protos.js'; +export {protos}; diff --git a/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/esm/src/json-helper.cjs.baseline b/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/esm/src/json-helper.cjs.baseline new file mode 100644 index 000000000000..3c1fc730201e --- /dev/null +++ b/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/esm/src/json-helper.cjs.baseline @@ -0,0 +1,20 @@ +// Copyright 2024 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. + +/* eslint-disable node/no-missing-require */ +function getJSON(path) { + return require(path); +} + +exports.getJSON = getJSON; diff --git a/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/esm/src/v1/index.ts.baseline b/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/esm/src/v1/index.ts.baseline new file mode 100644 index 000000000000..3659293a6283 --- /dev/null +++ b/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/esm/src/v1/index.ts.baseline @@ -0,0 +1,19 @@ +// Copyright 2026 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 +// +// https://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. +// +// ** This file is automatically generated by gapic-generator-typescript. ** +// ** https://github.com/googleapis/gapic-generator-typescript ** +// ** All changes to this file may be overwritten. ** + +export {ResumableUploadServiceClient} from './resumable_upload_service_client.js'; diff --git a/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/esm/src/v1/resumable_upload_service_client.ts.baseline b/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/esm/src/v1/resumable_upload_service_client.ts.baseline new file mode 100644 index 000000000000..c50be8d86055 --- /dev/null +++ b/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/esm/src/v1/resumable_upload_service_client.ts.baseline @@ -0,0 +1,623 @@ +// Copyright 2026 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 +// +// https://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. +// +// ** This file is automatically generated by gapic-generator-typescript. ** +// ** https://github.com/googleapis/gapic-generator-typescript ** +// ** All changes to this file may be overwritten. ** + +/* global window */ +import * as gax from 'google-gax'; +import type { + Callback, + CallOptions, + Descriptors, + ClientOptions, +} from 'google-gax'; + +// @ts-ignore +import type * as protos from '../../../protos/protos.js'; +import * as resumable_upload_service_client_config from './resumable_upload_service_client_config.json'; +import fs from 'fs'; +import path from 'path'; +import {fileURLToPath} from 'url'; +import {getJSON} from '../json-helper.cjs'; +// @ts-ignore +const dirname = path.dirname(fileURLToPath(import.meta.url)); + +/** + * Client JSON configuration object, loaded from + * `src/v1/resumable_upload_service_client_config.json`. + * This file defines retry strategy and timeouts for all API methods in this library. + */ +const gapicConfig = getJSON( + path.join(dirname, 'resumable_upload_service_client_config.json'), +); + +const jsonProtos = getJSON( + path.join(dirname, '..', '..', '..', 'protos/protos.json'), +); +import {loggingUtils as logging, decodeAnyProtosInArray} from 'google-gax'; +const version = getJSON( + path.join(dirname, '..', '..', '..', '..', 'package.json'), +).version; + +/** + * @class + * @memberof v1 + */ +export class ResumableUploadServiceClient { + private _terminated = false; + private _opts: ClientOptions; + private _providedCustomServicePath: boolean; + private _gaxModule: typeof gax | typeof gax.fallback; + private _gaxGrpc: gax.GrpcClient | gax.fallback.GrpcClient; + private _fallbackRest?: gax.GrpcClient | gax.fallback.GrpcClient; + private _protos: {}; + private _defaults: {[method: string]: gax.CallSettings}; + private _universeDomain: string; + private _servicePath: string; + private _log = logging.log('resumable'); + auth: gax.GoogleAuth; + descriptors: Descriptors = { + page: {}, + stream: {}, + longrunning: {}, + batching: {}, + resumableUpload: {}, + }; + warn: (code: string, message: string, warnType?: string) => void; + innerApiCalls: {[name: string]: Function}; + resumableUploadServiceStub?: Promise<{[name: string]: Function}>; + + /** + * Construct an instance of ResumableUploadServiceClient. + * + * @param {object} [options] - The configuration object. + * The options accepted by the constructor are described in detail + * in [this document](https://github.com/googleapis/gax-nodejs/blob/main/client-libraries.md#creating-the-client-instance). + * The common options are: + * @param {object} [options.credentials] - Credentials object. + * @param {string} [options.credentials.client_email] + * @param {string} [options.credentials.private_key] + * @param {string} [options.email] - Account email address. Required when + * using a .pem or .p12 keyFilename. + * @param {string} [options.keyFilename] - Full path to the a .json, .pem, or + * .p12 key downloaded from the Google Developers Console. If you provide + * a path to a JSON file, the projectId option below is not necessary. + * NOTE: .pem and .p12 require you to specify options.email as well. + * @param {number} [options.port] - The port on which to connect to + * the remote host. + * @param {string} [options.projectId] - The project ID from the Google + * Developer's Console, e.g. 'grape-spaceship-123'. We will also check + * the environment variable GCLOUD_PROJECT for your project ID. If your + * app is running in an environment which supports + * {@link https://cloud.google.com/docs/authentication/application-default-credentials Application Default Credentials}, + * your project ID will be detected automatically. + * @param {string} [options.apiEndpoint] - The domain name of the + * API remote host. + * @param {gax.ClientConfig} [options.clientConfig] - Client configuration override. + * Follows the structure of {@link gapicConfig}. + * @param {boolean | "rest"} [options.fallback] - Use HTTP fallback mode. + * Pass "rest" to use HTTP/1.1 REST API instead of gRPC. + * For more information, please check the + * {@link https://github.com/googleapis/gax-nodejs/blob/main/client-libraries.md#http11-rest-api-mode documentation}. + * @param {gax} [gaxInstance]: loaded instance of `google-gax`. Useful if you + * need to avoid loading the default gRPC version and want to use the fallback + * HTTP implementation. Load only fallback version and pass it to the constructor: + * ``` + * const gax = require('google-gax/build/src/fallback'); // avoids loading google-gax with gRPC + * const client = new ResumableUploadServiceClient({fallback: 'rest'}, gax); + * ``` + */ + constructor( + opts?: ClientOptions, + gaxInstance?: typeof gax | typeof gax.fallback, + ) { + // Ensure that options include all the required fields. + const staticMembers = this + .constructor as typeof ResumableUploadServiceClient; + if ( + opts?.universe_domain && + opts?.universeDomain && + opts?.universe_domain !== opts?.universeDomain + ) { + throw new Error( + 'Please set either universe_domain or universeDomain, but not both.', + ); + } + const universeDomainEnvVar = + typeof process === 'object' && typeof process.env === 'object' + ? process.env['GOOGLE_CLOUD_UNIVERSE_DOMAIN'] + : undefined; + this._universeDomain = + opts?.universeDomain ?? + opts?.universe_domain ?? + universeDomainEnvVar ?? + 'googleapis.com'; + this._servicePath = 'resumable.' + this._universeDomain; + const servicePath = + opts?.servicePath || opts?.apiEndpoint || this._servicePath; + this._providedCustomServicePath = !!( + opts?.servicePath || opts?.apiEndpoint + ); + const port = opts?.port || staticMembers.port; + const clientConfig = opts?.clientConfig ?? {}; + const fallback = + opts?.fallback ?? + (typeof window !== 'undefined' && typeof window?.fetch === 'function'); + opts = Object.assign({servicePath, port, clientConfig, fallback}, opts); + + // If scopes are unset in options and we're connecting to a non-default endpoint, set scopes just in case. + if (servicePath !== this._servicePath && !('scopes' in opts)) { + opts['scopes'] = staticMembers.scopes; + } + + // Load google-gax module synchronously if needed + if (!gaxInstance) { + gaxInstance = gax as typeof gax; + } + + // Choose either gRPC or proto-over-HTTP implementation of google-gax. + this._gaxModule = opts.fallback ? gaxInstance.fallback : gaxInstance; + + // Create a `gaxGrpc` object, with any grpc-specific options sent to the client. + this._gaxGrpc = new this._gaxModule.GrpcClient(opts); + + // This service contains resumable upload methods, which are HTTPS-only. + // Make sure the REST transport is available even when the client was + // configured for gRPC. + this._fallbackRest = opts.fallback + ? this._gaxGrpc + : new gaxInstance.fallback.GrpcClient({...opts, fallback: true}); + + // Save options to use in initialize() method. + this._opts = opts; + + // Save the auth object to the client, for use by other methods. + this.auth = this._gaxGrpc.auth as gax.GoogleAuth; + + // Set useJWTAccessWithScope on the auth object. + this.auth.useJWTAccessWithScope = true; + + // Set defaultServicePath on the auth object. + this.auth.defaultServicePath = this._servicePath; + + // Set the default scopes in auth client if needed. + if (servicePath === this._servicePath) { + this.auth.defaultScopes = staticMembers.scopes; + } + + // Add ESM headers + const isEsm = true; + const isEsmString = isEsm ? '-esm' : '-cjs'; + // Determine the client header string. + const clientHeader = [`gax/${this._gaxModule.version}`, `gapic/${version}`]; + if (typeof process === 'object' && 'versions' in process) { + clientHeader.push(`gl-node/{process.versions.node}${isEsmString}`); + } else { + clientHeader.push(`gl-web/${this._gaxModule.version}`); + } + if (!opts.fallback) { + clientHeader.push(`grpc/${this._gaxGrpc.grpcVersion}`); + } else if (opts.fallback === 'rest') { + clientHeader.push(`rest/${this._gaxGrpc.grpcVersion}`); + } + if (opts.libName && opts.libVersion) { + clientHeader.push(`${opts.libName}/${opts.libVersion}`); + } + + // Load the applicable protos. + this._protos = this._gaxGrpc.loadProtoJSON( + jsonProtos as gax.protobuf.INamespace, + ); + + // Some methods on this API support resumable uploads; provide + // descriptors for these methods. + this.descriptors.resumableUpload = { + createResumableUpload: new this._gaxModule.ResumableUploadDescriptor( + '/resumable/upload', + ), + }; + + // Put together the default options sent with requests. + this._defaults = this._gaxGrpc.constructSettings( + 'google.samples.resumable.v1.ResumableUploadService', + gapicConfig as gax.ClientConfig, + opts.clientConfig || {}, + {'x-goog-api-client': clientHeader.join(' ')}, + ); + + // Set up a dictionary of "inner API calls"; the core implementation + // of calling the API is handled in `google-gax`, with this code + // merely providing the destination and request information. + this.innerApiCalls = {}; + + // Add a warn function to the client constructor so it can be easily tested. + this.warn = this._gaxModule.warn; + } + + /** + * Initialize the client. + * Performs asynchronous operations (such as authentication) and prepares the client. + * This function will be called automatically when any class method is called for the + * first time, but if you need to initialize it before calling an actual method, + * feel free to call initialize() directly. + * + * You can await on this method if you want to make sure the client is initialized. + * + * @returns {Promise} A promise that resolves to an authenticated service stub. + */ + initialize() { + // If the client stub promise is already initialized, return immediately. + if (this.resumableUploadServiceStub) { + return this.resumableUploadServiceStub; + } + + // Put together the "service stub" for + // google.samples.resumable.v1.ResumableUploadService. + this.resumableUploadServiceStub = this._gaxGrpc.createStub( + this._opts.fallback + ? (this._protos as protobuf.Root).lookupService( + 'google.samples.resumable.v1.ResumableUploadService', + ) + : // eslint-disable-next-line @typescript-eslint/no-explicit-any + (this._protos as any).google.samples.resumable.v1 + .ResumableUploadService, + this._opts, + this._providedCustomServicePath, + ) as Promise<{[method: string]: Function}>; + + // Iterate over each of the methods that the service provides + // and create an API call method for each. + const resumableUploadServiceStubMethods = ['getUploadStatus']; + for (const methodName of resumableUploadServiceStubMethods) { + const callPromise = this.resumableUploadServiceStub.then( + stub => + (...args: Array<{}>) => { + if (this._terminated) { + return Promise.reject('The client has already been closed.'); + } + const func = stub[methodName]; + return func.apply(stub, args); + }, + (err: Error | null | undefined) => () => { + throw err; + }, + ); + + const descriptor = undefined; + const apiCall = this._gaxModule.createApiCall( + callPromise, + this._defaults[methodName], + descriptor, + this._opts.fallback, + ); + + this.innerApiCalls[methodName] = apiCall; + } + + // Resumable upload methods do not use the gRPC/REST service stub; the + // The ResumableUploadSession performs its own HTTP requests. + this.innerApiCalls['createResumableUpload'] = this._gaxModule.createApiCall( + this._gaxModule.resumableUploadStub, + this._defaults['createResumableUpload'], + this.descriptors.resumableUpload!['createResumableUpload'], + this._opts.fallback, + ); + + return this.resumableUploadServiceStub; + } + + /** + * The DNS address for this API service. + * @deprecated Use the apiEndpoint method of the client instance. + * @returns {string} The DNS address for this service. + */ + static get servicePath() { + if ( + typeof process === 'object' && + typeof process.emitWarning === 'function' + ) { + process.emitWarning( + 'Static servicePath is deprecated, please use the instance method instead.', + 'DeprecationWarning', + ); + } + return 'resumable.googleapis.com'; + } + + /** + * The DNS address for this API service - same as servicePath, + * exists for compatibility reasons. + * @deprecated Use the apiEndpoint method of the client instance. + * @returns {string} The DNS address for this service. + */ + static get apiEndpoint() { + if ( + typeof process === 'object' && + typeof process.emitWarning === 'function' + ) { + process.emitWarning( + 'Static apiEndpoint is deprecated, please use the instance method instead.', + 'DeprecationWarning', + ); + } + return 'resumable.googleapis.com'; + } + + /** + * The DNS address for this API service. + * @returns {string} The DNS address for this service. + */ + get apiEndpoint() { + return this._servicePath; + } + + get universeDomain() { + return this._universeDomain; + } + + /** + * The port for this API service. + * @returns {number} The default port for this service. + */ + static get port() { + return 443; + } + + /** + * The scopes needed to make gRPC calls for every method defined + * in this service. + * @returns {string[]} List of default scopes. + */ + static get scopes() { + return ['https://www.googleapis.com/auth/cloud-platform']; + } + + getProjectId(): Promise; + getProjectId(callback: Callback): void; + /** + * Return the project ID used by this class. + * @returns {Promise} A promise that resolves to string containing the project ID. + */ + getProjectId( + callback?: Callback, + ): Promise | void { + if (callback) { + this.auth.getProjectId(callback); + return; + } + return this.auth.getProjectId(); + } + + // ------------------- + // -- Service calls -- + // ------------------- + /** + * Returns the status of a previously created upload session. + * + * @param {Object} request + * The request object that will be sent. + * @param {string} request.uploadUrl + * @param {object} [options] + * Call options. See {@link https://googleapis.dev/nodejs/google-gax/latest/interfaces/CallOptions.html|CallOptions} for more details. + * @returns {Promise} - The promise which resolves to an array. + * The first element of the array is an object representing {@link protos.google.samples.resumable.v1.GetUploadStatusResponse|GetUploadStatusResponse}. + * Please see the {@link https://github.com/googleapis/gax-nodejs/blob/master/client-libraries.md#regular-methods | documentation } + * for more details and examples. + * @example include:samples/generated/v1/resumable_upload_service.get_upload_status.js + * region_tag:resumable_v1_generated_ResumableUploadService_GetUploadStatus_async + */ + getUploadStatus( + request?: protos.google.samples.resumable.v1.IGetUploadStatusRequest, + options?: CallOptions, + ): Promise< + [ + protos.google.samples.resumable.v1.IGetUploadStatusResponse, + protos.google.samples.resumable.v1.IGetUploadStatusRequest | undefined, + {} | undefined, + ] + >; + getUploadStatus( + request: protos.google.samples.resumable.v1.IGetUploadStatusRequest, + options: CallOptions, + callback: Callback< + protos.google.samples.resumable.v1.IGetUploadStatusResponse, + | protos.google.samples.resumable.v1.IGetUploadStatusRequest + | null + | undefined, + {} | null | undefined + >, + ): void; + getUploadStatus( + request: protos.google.samples.resumable.v1.IGetUploadStatusRequest, + callback: Callback< + protos.google.samples.resumable.v1.IGetUploadStatusResponse, + | protos.google.samples.resumable.v1.IGetUploadStatusRequest + | null + | undefined, + {} | null | undefined + >, + ): void; + getUploadStatus( + request?: protos.google.samples.resumable.v1.IGetUploadStatusRequest, + optionsOrCallback?: + | CallOptions + | Callback< + protos.google.samples.resumable.v1.IGetUploadStatusResponse, + | protos.google.samples.resumable.v1.IGetUploadStatusRequest + | null + | undefined, + {} | null | undefined + >, + callback?: Callback< + protos.google.samples.resumable.v1.IGetUploadStatusResponse, + | protos.google.samples.resumable.v1.IGetUploadStatusRequest + | null + | undefined, + {} | null | undefined + >, + ): Promise< + [ + protos.google.samples.resumable.v1.IGetUploadStatusResponse, + protos.google.samples.resumable.v1.IGetUploadStatusRequest | undefined, + {} | undefined, + ] + > | void { + request = request || {}; + let options: CallOptions; + if (typeof optionsOrCallback === 'function' && callback === undefined) { + callback = optionsOrCallback; + options = {}; + } else { + options = optionsOrCallback as CallOptions; + } + options = options || {}; + options.otherArgs = options.otherArgs || {}; + options.otherArgs.headers = options.otherArgs.headers || {}; + options.otherArgs.headers['x-goog-request-params'] = + this._gaxModule.routingHeader.fromParams({ + upload_url: request.uploadUrl ?? '', + }); + this.initialize().catch(err => { + throw err; + }); + this._log.info('getUploadStatus request %j', request); + const wrappedCallback: + | Callback< + protos.google.samples.resumable.v1.IGetUploadStatusResponse, + | protos.google.samples.resumable.v1.IGetUploadStatusRequest + | null + | undefined, + {} | null | undefined + > + | undefined = callback + ? (error, response, options, rawResponse) => { + this._log.info('getUploadStatus response %j', response); + callback!(error, response, options, rawResponse); + } + : undefined; + return this.innerApiCalls + .getUploadStatus(request, options, wrappedCallback) + ?.then( + ([response, options, rawResponse]: [ + protos.google.samples.resumable.v1.IGetUploadStatusResponse, + ( + | protos.google.samples.resumable.v1.IGetUploadStatusRequest + | undefined + ), + {} | undefined, + ]) => { + this._log.info('getUploadStatus response %j', response); + return [response, options, rawResponse]; + }, + ) + .catch((error: any) => { + if ( + error && + 'statusDetails' in error && + error.statusDetails instanceof Array + ) { + const protos = this._gaxModule.protobuf.Root.fromJSON( + jsonProtos, + ) as unknown as gax.protobuf.Type; + error.statusDetails = decodeAnyProtosInArray( + error.statusDetails, + protos, + ); + } + throw error; + }); + } + /** + * Creates a {@link gax.ResumableSource} backed by a local file. + * + * This delegates to google-gax so generated clients do not depend on the + * Node.js `fs` module at import time. The source can be passed as the + * `uploadSource` parameter of {@link gax.ResumableUploadSession#start}. + * + * @param {string} filePath - Path to the local file to upload. + * @returns {gax.ResumableSource} A seekable upload source for the file. + */ + getResumableSource(filePath: string): gax.ResumableSource { + const gaxModule = this._gaxModule as typeof gax; + return gaxModule.resumableSourceFromFile(filePath); + } + /** + * Creates a resumable upload session. + * + * @param {object} [request] - The request object. + * @param {object} [options] - Optional parameters. The upload source, chunk + * size, progress callback, and resume URL are passed to + * {@link gax.ResumableUploadSession#start} instead. + * @returns {Promise} A resumable upload session. + * Call `.start(uploadParams)` with the source to upload, then await + * `.finished()` for the final RPC response. + */ + createResumableUpload( + request?: protos.google.samples.resumable.v1.ICreateResumableUploadRequest, + options?: CallOptions, + ): Promise { + request = request || {}; + options = options || {}; + if (!this._opts.fallback && this._opts.sslCreds) { + return Promise.reject( + new this._gaxModule.GoogleError( + 'Resumable upload methods require HTTP(S) authentication and ' + + 'cannot be used with gRPC channel credentials. Configure the ' + + 'client without `sslCreds`, or use `fallback: true`.', + ), + ); + } + this.initialize().catch(err => { + throw err; + }); + this._log.info('createResumableUpload request %j', request); + return ( + this.innerApiCalls['createResumableUpload'](request, { + ...options, + resumableUpload: { + auth: this._fallbackRest!.auth as gax.GoogleAuth, + servicePath: this._opts.servicePath ?? this._servicePath, + servicePort: this._opts.port || 443, + protocol: this._opts.protocol || 'https', + rpc: this._gaxModule.protobuf.Root.fromJSON(jsonProtos).lookupService( + 'google.samples.resumable.v1.ResumableUploadService', + ).methods['CreateResumableUpload'], + request, + uploadPrefix: '/resumable/upload', + numericEnums: this._opts.numericEnums, + minifyJson: this._opts.minifyJson, + }, + }) as Promise<[gax.ResumableUploadSession]> + ).then(([session]) => session); + } + + /** + * Terminate the gRPC channel and close the client. + * + * The client will no longer be usable and all future behavior is undefined. + * @returns {Promise} A promise that resolves when the client is closed. + */ + close(): Promise { + if (this.resumableUploadServiceStub && !this._terminated) { + return this.resumableUploadServiceStub.then(stub => { + this._log.info('ending gRPC channel'); + this._terminated = true; + stub.close(); + }); + } + return Promise.resolve(); + } +} diff --git a/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/esm/src/v1/resumable_upload_service_client_config.json.baseline b/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/esm/src/v1/resumable_upload_service_client_config.json.baseline new file mode 100644 index 000000000000..de3f9fed6441 --- /dev/null +++ b/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/esm/src/v1/resumable_upload_service_client_config.json.baseline @@ -0,0 +1,34 @@ +{ + "interfaces": { + "google.samples.resumable.v1.ResumableUploadService": { + "retry_codes": { + "non_idempotent": [], + "idempotent": [ + "DEADLINE_EXCEEDED", + "UNAVAILABLE" + ] + }, + "retry_params": { + "default": { + "initial_retry_delay_millis": 100, + "retry_delay_multiplier": 1.3, + "max_retry_delay_millis": 60000, + "initial_rpc_timeout_millis": 60000, + "rpc_timeout_multiplier": 1, + "max_rpc_timeout_millis": 60000, + "total_timeout_millis": 600000 + } + }, + "methods": { + "CreateResumableUpload": { + "retry_codes_name": "non_idempotent", + "retry_params_name": "default" + }, + "GetUploadStatus": { + "retry_codes_name": "non_idempotent", + "retry_params_name": "default" + } + } + } + } +} diff --git a/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/esm/src/v1/resumable_upload_service_proto_list.json.baseline b/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/esm/src/v1/resumable_upload_service_proto_list.json.baseline new file mode 100644 index 000000000000..4acc3ac38ace --- /dev/null +++ b/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/esm/src/v1/resumable_upload_service_proto_list.json.baseline @@ -0,0 +1,3 @@ +[ + "../../protos/google/samples/resumable/v1/resumable.proto" +] diff --git a/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/esm/system-test/fixtures/sample/src/index.cjs.baseline b/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/esm/system-test/fixtures/sample/src/index.cjs.baseline new file mode 100644 index 000000000000..0bad3604de88 --- /dev/null +++ b/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/esm/system-test/fixtures/sample/src/index.cjs.baseline @@ -0,0 +1,27 @@ +// Copyright 2026 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 +// +// https://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. +// +// ** This file is automatically generated by gapic-generator-typescript. ** +// ** https://github.com/googleapis/gapic-generator-typescript ** +// ** All changes to this file may be overwritten. ** + + +/* eslint-disable node/no-missing-require, no-unused-vars, no-undef */ +const resumable = require('resumable'); + +function main() { + const resumableUploadServiceClient = new resumable.ResumableUploadServiceClient(); +} + +main(); diff --git a/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/esm/system-test/fixtures/sample/src/index.js.baseline b/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/esm/system-test/fixtures/sample/src/index.js.baseline new file mode 100644 index 000000000000..db497b0f574d --- /dev/null +++ b/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/esm/system-test/fixtures/sample/src/index.js.baseline @@ -0,0 +1,27 @@ +// Copyright 2026 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 +// +// https://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. +// +// ** This file is automatically generated by gapic-generator-typescript. ** +// ** https://github.com/googleapis/gapic-generator-typescript ** +// ** All changes to this file may be overwritten. ** + + +/* eslint-disable node/no-missing-require, no-unused-vars, no-undef */ +import * as resumable from 'resumable'; + +function main() { + const resumableUploadServiceClient = new resumable.ResumableUploadServiceClient(); +} + +main(); diff --git a/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/esm/system-test/fixtures/sample/src/index.ts.baseline b/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/esm/system-test/fixtures/sample/src/index.ts.baseline new file mode 100644 index 000000000000..7320067b9a2b --- /dev/null +++ b/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/esm/system-test/fixtures/sample/src/index.ts.baseline @@ -0,0 +1,35 @@ +/* eslint-disable node/no-missing-require, no-unused-vars, no-undef */ +// Copyright 2026 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 +// +// https://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. +// +// ** This file is automatically generated by gapic-generator-typescript. ** +// ** https://github.com/googleapis/gapic-generator-typescript ** +// ** All changes to this file may be overwritten. ** + +import {ResumableUploadServiceClient} from 'resumable'; + +// check that the client class type name can be used +function doStuffWithResumableUploadServiceClient( + client: ResumableUploadServiceClient, +) { + client.close(); +} + +function main() { + // check that the client instance can be created + const resumableUploadServiceClient = new ResumableUploadServiceClient(); + doStuffWithResumableUploadServiceClient(resumableUploadServiceClient); +} + +main(); diff --git a/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/esm/system-test/install.ts.baseline b/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/esm/system-test/install.ts.baseline new file mode 100644 index 000000000000..77ea836f8af2 --- /dev/null +++ b/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/esm/system-test/install.ts.baseline @@ -0,0 +1,60 @@ +// Copyright 2026 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 +// +// https://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. +// +// ** This file is automatically generated by gapic-generator-typescript. ** +// ** https://github.com/googleapis/gapic-generator-typescript ** +// ** All changes to this file may be overwritten. ** + +import {packNTest} from 'pack-n-play'; +import {readFileSync} from 'fs'; +import {describe, it} from 'mocha'; + +describe('📦 pack-n-play test', () => { + it('TypeScript', async function () { + this.timeout(300000); + await packNTest({ + packageDir: process.cwd(), + sample: { + description: 'TypeScript user can use the type definitions', + ts: readFileSync( + './esm/system-test/fixtures/sample/src/index.ts', + ).toString(), + }, + }); + }); + + it('ESM module', async function () { + this.timeout(300000); + await packNTest({ + sample: { + description: 'Should be able to import using ESM', + esm: readFileSync( + './esm/system-test/fixtures/sample/src/index.js', + ).toString(), + }, + }); + }); + + it('CJS module', async function () { + this.timeout(300000); + await packNTest({ + sample: { + description: 'Should be able to import using CJS', + cjs: readFileSync( + './esm/system-test/fixtures/sample/src/index.cjs', + ).toString(), + }, + }); + }); +}); diff --git a/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/esm/test/gapic_resumable_upload_service_v1.ts.baseline b/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/esm/test/gapic_resumable_upload_service_v1.ts.baseline new file mode 100644 index 000000000000..f9fdbf6e2ce8 --- /dev/null +++ b/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/esm/test/gapic_resumable_upload_service_v1.ts.baseline @@ -0,0 +1,419 @@ +// Copyright 2026 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 +// +// https://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. +// +// ** This file is automatically generated by gapic-generator-typescript. ** +// ** https://github.com/googleapis/gapic-generator-typescript ** +// ** All changes to this file may be overwritten. ** + +// @ts-ignore +import * as protos from '../../protos/protos.js'; +import assert from 'assert'; +import * as sinon from 'sinon'; +import {SinonStub} from 'sinon'; +import {describe, it} from 'mocha'; +import * as resumableuploadserviceModule from '../src/index.js'; + +import {protobuf} from 'google-gax'; +import fs from 'fs'; +import path from 'path'; +import {fileURLToPath} from 'url'; + +// @ts-ignore +const dirname = path.dirname(fileURLToPath(import.meta.url)); +// Dynamically loaded proto JSON is needed to get the type information +// to fill in default values for request objects +const root = protobuf.Root.fromJSON( + JSON.parse( + fs.readFileSync( + path.join(dirname, '..', '..', 'protos/protos.json'), + 'utf8', + ), + ), +); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getTypeDefaultValue(typeName: string, fields: string[]) { + let type = root.lookupType(typeName) as protobuf.Type; + for (const field of fields.slice(0, -1)) { + type = type?.fields[field]?.resolvedType as protobuf.Type; + } + return type?.fields[fields[fields.length - 1]]?.defaultValue ?? null; +} + +function generateSampleMessage(instance: T) { + const filledObject = ( + instance.constructor as typeof protobuf.Message + ).toObject(instance as protobuf.Message, {defaults: true}); + return (instance.constructor as typeof protobuf.Message).fromObject( + filledObject, + ) as T; +} + +function stubSimpleCall(response?: ResponseType, error?: Error) { + return error + ? sinon.stub().rejects(error) + : sinon.stub().resolves([response]); +} + +function stubSimpleCallWithCallback( + response?: ResponseType, + error?: Error, +) { + return error + ? sinon.stub().callsArgWith(2, error) + : sinon.stub().callsArgWith(2, null, response); +} + +describe('v1.ResumableUploadServiceClient', () => { + describe('Common methods', () => { + it('has apiEndpoint', () => { + const client = + new resumableuploadserviceModule.v1.ResumableUploadServiceClient(); + const apiEndpoint = client.apiEndpoint; + assert.strictEqual(apiEndpoint, 'resumable.googleapis.com'); + }); + + it('has universeDomain', () => { + const client = + new resumableuploadserviceModule.v1.ResumableUploadServiceClient(); + const universeDomain = client.universeDomain; + assert.strictEqual(universeDomain, 'googleapis.com'); + }); + + if ( + typeof process === 'object' && + typeof process.emitWarning === 'function' + ) { + it('throws DeprecationWarning if static servicePath is used', () => { + const stub = sinon.stub(process, 'emitWarning'); + const servicePath = + resumableuploadserviceModule.v1.ResumableUploadServiceClient + .servicePath; + assert.strictEqual(servicePath, 'resumable.googleapis.com'); + assert(stub.called); + stub.restore(); + }); + + it('throws DeprecationWarning if static apiEndpoint is used', () => { + const stub = sinon.stub(process, 'emitWarning'); + const apiEndpoint = + resumableuploadserviceModule.v1.ResumableUploadServiceClient + .apiEndpoint; + assert.strictEqual(apiEndpoint, 'resumable.googleapis.com'); + assert(stub.called); + stub.restore(); + }); + } + it('sets apiEndpoint according to universe domain camelCase', () => { + const client = + new resumableuploadserviceModule.v1.ResumableUploadServiceClient({ + universeDomain: 'example.com', + }); + const servicePath = client.apiEndpoint; + assert.strictEqual(servicePath, 'resumable.example.com'); + }); + + it('sets apiEndpoint according to universe domain snakeCase', () => { + const client = + new resumableuploadserviceModule.v1.ResumableUploadServiceClient({ + universe_domain: 'example.com', + }); + const servicePath = client.apiEndpoint; + assert.strictEqual(servicePath, 'resumable.example.com'); + }); + + if (typeof process === 'object' && 'env' in process) { + describe('GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variable', () => { + it('sets apiEndpoint from environment variable', () => { + const saved = process.env['GOOGLE_CLOUD_UNIVERSE_DOMAIN']; + process.env['GOOGLE_CLOUD_UNIVERSE_DOMAIN'] = 'example.com'; + const client = + new resumableuploadserviceModule.v1.ResumableUploadServiceClient(); + const servicePath = client.apiEndpoint; + assert.strictEqual(servicePath, 'resumable.example.com'); + if (saved) { + process.env['GOOGLE_CLOUD_UNIVERSE_DOMAIN'] = saved; + } else { + delete process.env['GOOGLE_CLOUD_UNIVERSE_DOMAIN']; + } + }); + + it('value configured in code has priority over environment variable', () => { + const saved = process.env['GOOGLE_CLOUD_UNIVERSE_DOMAIN']; + process.env['GOOGLE_CLOUD_UNIVERSE_DOMAIN'] = 'example.com'; + const client = + new resumableuploadserviceModule.v1.ResumableUploadServiceClient({ + universeDomain: 'configured.example.com', + }); + const servicePath = client.apiEndpoint; + assert.strictEqual(servicePath, 'resumable.configured.example.com'); + if (saved) { + process.env['GOOGLE_CLOUD_UNIVERSE_DOMAIN'] = saved; + } else { + delete process.env['GOOGLE_CLOUD_UNIVERSE_DOMAIN']; + } + }); + }); + } + it('does not allow setting both universeDomain and universe_domain', () => { + assert.throws(() => { + new resumableuploadserviceModule.v1.ResumableUploadServiceClient({ + universe_domain: 'example.com', + universeDomain: 'example.net', + }); + }); + }); + + it('has port', () => { + const port = + resumableuploadserviceModule.v1.ResumableUploadServiceClient.port; + assert(port); + assert(typeof port === 'number'); + }); + + it('should create a client with no option', () => { + const client = + new resumableuploadserviceModule.v1.ResumableUploadServiceClient(); + assert(client); + }); + + it('should create a client with gRPC fallback', () => { + const client = + new resumableuploadserviceModule.v1.ResumableUploadServiceClient({ + fallback: true, + }); + assert(client); + }); + + it('has initialize method and supports deferred initialization', async () => { + const client = + new resumableuploadserviceModule.v1.ResumableUploadServiceClient({ + credentials: {client_email: 'bogus', private_key: 'bogus'}, + projectId: 'bogus', + }); + assert.strictEqual(client.resumableUploadServiceStub, undefined); + await client.initialize(); + assert(client.resumableUploadServiceStub); + }); + + it('has close method for the initialized client', done => { + const client = + new resumableuploadserviceModule.v1.ResumableUploadServiceClient({ + credentials: {client_email: 'bogus', private_key: 'bogus'}, + projectId: 'bogus', + }); + client.initialize().catch(err => { + throw err; + }); + assert(client.resumableUploadServiceStub); + client + .close() + .then(() => { + done(); + }) + .catch(err => { + throw err; + }); + }); + + it('has close method for the non-initialized client', done => { + const client = + new resumableuploadserviceModule.v1.ResumableUploadServiceClient({ + credentials: {client_email: 'bogus', private_key: 'bogus'}, + projectId: 'bogus', + }); + assert.strictEqual(client.resumableUploadServiceStub, undefined); + client + .close() + .then(() => { + done(); + }) + .catch(err => { + throw err; + }); + }); + + it('has getProjectId method', async () => { + const fakeProjectId = 'fake-project-id'; + const client = + new resumableuploadserviceModule.v1.ResumableUploadServiceClient({ + credentials: {client_email: 'bogus', private_key: 'bogus'}, + projectId: 'bogus', + }); + client.auth.getProjectId = sinon.stub().resolves(fakeProjectId); + const result = await client.getProjectId(); + assert.strictEqual(result, fakeProjectId); + assert((client.auth.getProjectId as SinonStub).calledWithExactly()); + }); + + it('has getProjectId method with callback', async () => { + const fakeProjectId = 'fake-project-id'; + const client = + new resumableuploadserviceModule.v1.ResumableUploadServiceClient({ + credentials: {client_email: 'bogus', private_key: 'bogus'}, + projectId: 'bogus', + }); + client.auth.getProjectId = sinon + .stub() + .callsArgWith(0, null, fakeProjectId); + const promise = new Promise((resolve, reject) => { + client.getProjectId((err?: Error | null, projectId?: string | null) => { + if (err) { + reject(err); + } else { + resolve(projectId); + } + }); + }); + const result = await promise; + assert.strictEqual(result, fakeProjectId); + }); + }); + + describe('getUploadStatus', () => { + it('invokes getUploadStatus without error', async () => { + const client = + new resumableuploadserviceModule.v1.ResumableUploadServiceClient({ + credentials: {client_email: 'bogus', private_key: 'bogus'}, + projectId: 'bogus', + }); + await client.initialize(); + const request = generateSampleMessage( + new protos.google.samples.resumable.v1.GetUploadStatusRequest(), + ); + const defaultValue1 = getTypeDefaultValue( + '.google.samples.resumable.v1.GetUploadStatusRequest', + ['uploadUrl'], + ); + request.uploadUrl = defaultValue1; + const expectedHeaderRequestParams = `upload_url=${defaultValue1 ?? ''}`; + const expectedResponse = generateSampleMessage( + new protos.google.samples.resumable.v1.GetUploadStatusResponse(), + ); + client.innerApiCalls.getUploadStatus = stubSimpleCall(expectedResponse); + const [response] = await client.getUploadStatus(request); + assert.deepStrictEqual(response, expectedResponse); + const actualRequest = ( + client.innerApiCalls.getUploadStatus as SinonStub + ).getCall(0).args[0]; + assert.deepStrictEqual(actualRequest, request); + const actualHeaderRequestParams = ( + client.innerApiCalls.getUploadStatus as SinonStub + ).getCall(0).args[1].otherArgs.headers['x-goog-request-params']; + assert(actualHeaderRequestParams.includes(expectedHeaderRequestParams)); + }); + + it('invokes getUploadStatus without error using callback', async () => { + const client = + new resumableuploadserviceModule.v1.ResumableUploadServiceClient({ + credentials: {client_email: 'bogus', private_key: 'bogus'}, + projectId: 'bogus', + }); + await client.initialize(); + const request = generateSampleMessage( + new protos.google.samples.resumable.v1.GetUploadStatusRequest(), + ); + const defaultValue1 = getTypeDefaultValue( + '.google.samples.resumable.v1.GetUploadStatusRequest', + ['uploadUrl'], + ); + request.uploadUrl = defaultValue1; + const expectedHeaderRequestParams = `upload_url=${defaultValue1 ?? ''}`; + const expectedResponse = generateSampleMessage( + new protos.google.samples.resumable.v1.GetUploadStatusResponse(), + ); + client.innerApiCalls.getUploadStatus = + stubSimpleCallWithCallback(expectedResponse); + const promise = new Promise((resolve, reject) => { + client.getUploadStatus( + request, + ( + err?: Error | null, + result?: protos.google.samples.resumable.v1.IGetUploadStatusResponse | null, + ) => { + if (err) { + reject(err); + } else { + resolve(result); + } + }, + ); + }); + const response = await promise; + assert.deepStrictEqual(response, expectedResponse); + const actualRequest = ( + client.innerApiCalls.getUploadStatus as SinonStub + ).getCall(0).args[0]; + assert.deepStrictEqual(actualRequest, request); + const actualHeaderRequestParams = ( + client.innerApiCalls.getUploadStatus as SinonStub + ).getCall(0).args[1].otherArgs.headers['x-goog-request-params']; + assert(actualHeaderRequestParams.includes(expectedHeaderRequestParams)); + }); + + it('invokes getUploadStatus with error', async () => { + const client = + new resumableuploadserviceModule.v1.ResumableUploadServiceClient({ + credentials: {client_email: 'bogus', private_key: 'bogus'}, + projectId: 'bogus', + }); + await client.initialize(); + const request = generateSampleMessage( + new protos.google.samples.resumable.v1.GetUploadStatusRequest(), + ); + const defaultValue1 = getTypeDefaultValue( + '.google.samples.resumable.v1.GetUploadStatusRequest', + ['uploadUrl'], + ); + request.uploadUrl = defaultValue1; + const expectedHeaderRequestParams = `upload_url=${defaultValue1 ?? ''}`; + const expectedError = new Error('expected'); + client.innerApiCalls.getUploadStatus = stubSimpleCall( + undefined, + expectedError, + ); + await assert.rejects(client.getUploadStatus(request), expectedError); + const actualRequest = ( + client.innerApiCalls.getUploadStatus as SinonStub + ).getCall(0).args[0]; + assert.deepStrictEqual(actualRequest, request); + const actualHeaderRequestParams = ( + client.innerApiCalls.getUploadStatus as SinonStub + ).getCall(0).args[1].otherArgs.headers['x-goog-request-params']; + assert(actualHeaderRequestParams.includes(expectedHeaderRequestParams)); + }); + + it('invokes getUploadStatus with closed client', async () => { + const client = + new resumableuploadserviceModule.v1.ResumableUploadServiceClient({ + credentials: {client_email: 'bogus', private_key: 'bogus'}, + projectId: 'bogus', + }); + await client.initialize(); + const request = generateSampleMessage( + new protos.google.samples.resumable.v1.GetUploadStatusRequest(), + ); + const defaultValue1 = getTypeDefaultValue( + '.google.samples.resumable.v1.GetUploadStatusRequest', + ['uploadUrl'], + ); + request.uploadUrl = defaultValue1; + const expectedError = new Error('The client has already been closed.'); + client.close().catch(err => { + throw err; + }); + await assert.rejects(client.getUploadStatus(request), expectedError); + }); + }); +}); diff --git a/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/package.json b/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/package.json new file mode 100644 index 000000000000..ac4f9bc1f502 --- /dev/null +++ b/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/package.json @@ -0,0 +1,108 @@ +{ + "name": "resumable", + "version": "0.1.0", + "description": "Resumable client for Node.js", + "repository": { + "type": "git", + "directory": "packages/google-samples-resumable", + "url": "https://github.com/googleapis/google-cloud-node.git" + }, + "license": "Apache-2.0", + "homepage": "https://github.com/googleapis/google-cloud-node/tree/main/packages/google-samples-resumable", + "author": "Google LLC", + "main": "./build/cjs/src/index.cjs", + "types": "./build/cjs/src/index.d.ts", + "type": "module", + "exports": { + ".": { + "import": { + "types": "./build/esm/src/index.d.ts", + "default": "./build/esm/src/index.js" + }, + "require": { + "types": "./build/cjs/src/index.d.ts", + "default": "./build/cjs/src/index.cjs" + } + }, + "./build/protos/protos": { + "import": { + "types": "./build/protos/protos/protos.d.ts", + "default": "./build/protos/protos/protos.js" + }, + "require": { + "types": "./build/protos/protos/protos.d.ts", + "default": "./build/protos/protos/protos.cjs" + } + } + }, + "files": [ + "build/esm", + "build/cjs", + "build/protos", + "!build/esm/**/*.map", + "!build/cjs/**/*.map" + ], + "keywords": [ + "google apis client", + "google api client", + "google apis", + "google api", + "google", + "google cloud platform", + "google cloud", + "cloud", + "google resumable", + "resumable", + "resumable upload service" + ], + "scripts": { + "clean": "gts clean", + "compile-protos": "compileProtos esm/src --esm ", + "docs": "jsdoc -c .jsdoc.cjs", + "postpack": "minifyProtoJson build/cjs && minifyProtoJson build/esm", + "fix": "gts fix", + "lint": "gts check", + "prepare": "npm run compile-protos && npm run compile", + "system-test:cjs": "c8 mocha --config ../../.mocharc.cjs --no-parallel build/cjs/system-test", + "system-test:esm": "c8 mocha --config ../../.mocharc.cjs --no-parallel build/esm/system-test", + "system-test": "npm run system-test:esm && npm run system-test:cjs", + "test:cjs": "c8 mocha --config ../../.mocharc.cjs build/cjs/test", + "test:esm": "c8 mocha --config ../../.mocharc.cjs build/esm/test", + "test": "npm run test:cjs && npm run test:esm", + "compile:esm": "tsc -p ./tsconfig.esm.json && cp -r esm/src/json-helper.cjs build/esm/src/json-helper.cjs", + "babel": "babel esm --out-dir build/cjs --ignore \"esm/**/*.d.ts\" --extensions \".ts\" --out-file-extension .cjs --copy-files", + "compile:cjs": "tsc -p ./tsconfig.json && npm run babel", + "compile": "npm run compile:esm && rm -rf esm/src/json-helper.d.cts && npm run compile:cjs && rm -rf build/protos && cp -r protos build/protos", + "samples-test": "cd samples/ && npm link ../ && npm i && npm test" + }, + "dependencies": { + "google-gax": "^6.0.0" + }, + "devDependencies": { + "@babel/cli": "^7.28.3", + "@babel/core": "^7.28.5", + "@babel/preset-env": "^7.28.5", + "@babel/preset-typescript": "^7.28.5", + "@types/mocha": "^10.0.10", + "@types/node": "^22.18.12", + "@types/sinon": "^20.0.0", + "babel-plugin-replace-import-extension": "^1.1.5", + "c8": "^10.1.3", + "gapic-tools": "^2.0.0", + "gts": "^6.0.2", + "jsdoc": "^4.0.5", + "jsdoc-region-tag": "^5.0.0", + "jsdoc-fresh": "^6.0.0", + "long": "^5.3.2", + "mocha": "^11.7.4", + "typescript": "5.8.3", + "pack-n-play": "^5.0.0", + "sinon": "^20.0.0", + "ts-loader": "^8.4.0", + "webpack": "^5.102.1", + "webpack-cli": "^6.0.1" + }, + "engines": { + "node": ">=22" + } +} diff --git a/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/package.json.baseline b/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/package.json.baseline new file mode 120000 index 000000000000..2ff8622f1722 --- /dev/null +++ b/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/package.json.baseline @@ -0,0 +1 @@ +package.json \ No newline at end of file diff --git a/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/protos/google/samples/resumable/v1/resumable.proto.baseline b/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/protos/google/samples/resumable/v1/resumable.proto.baseline new file mode 100644 index 000000000000..c5980d756f9d --- /dev/null +++ b/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/protos/google/samples/resumable/v1/resumable.proto.baseline @@ -0,0 +1,72 @@ +// Copyright 2026 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. + +// Synthetic fixture used by the baseline tests to exercise resumable upload +// method generation via the resumable_upload_methods generator parameter. + +syntax = "proto3"; + +package google.samples.resumable.v1; + +import "google/api/annotations.proto"; +import "google/api/client.proto"; + +option csharp_namespace = "Google.Samples.Resumable.V1"; +option go_package = "google.golang.org/genproto/googleapis/samples/resumable/v1;resumable"; +option java_multiple_files = true; +option java_outer_classname = "ResumableProto"; +option java_package = "com.google.samples.resumable.v1"; +option php_namespace = "Google\\Samples\\Resumable\\V1"; +option ruby_package = "Google::Samples::Resumable::V1"; + +service ResumableUploadService { + option (google.api.default_host) = "resumable.googleapis.com"; + option (google.api.oauth_scopes) = + "https://www.googleapis.com/auth/cloud-platform"; + + // Creates a resumable upload session. + rpc CreateResumableUpload(CreateResumableUploadRequest) + returns (CreateResumableUploadResponse) { + option (google.api.http) = { + post: "/v1/uploads" + body: "*" + }; + } + + // Returns the status of a previously created upload session. + rpc GetUploadStatus(GetUploadStatusRequest) + returns (GetUploadStatusResponse) { + option (google.api.http) = { + get: "/v1/uploads/{upload_url}" + }; + } +} + +message CreateResumableUploadRequest { + string name = 1; + string description = 2; +} + +message CreateResumableUploadResponse { + string upload_url = 1; + string status = 2; +} + +message GetUploadStatusRequest { + string upload_url = 1; +} + +message GetUploadStatusResponse { + string status = 1; +} diff --git a/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/samples/generated/v1/resumable_upload_service.create_resumable_upload.js.baseline b/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/samples/generated/v1/resumable_upload_service.create_resumable_upload.js.baseline new file mode 100644 index 000000000000..67188e48e219 --- /dev/null +++ b/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/samples/generated/v1/resumable_upload_service.create_resumable_upload.js.baseline @@ -0,0 +1,65 @@ +// Copyright 2026 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 +// +// https://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. +// +// ** This file is automatically generated by gapic-generator-typescript. ** +// ** https://github.com/googleapis/gapic-generator-typescript ** +// ** All changes to this file may be overwritten. ** + + + +'use strict'; + +function main() { + // [START resumable_v1_generated_ResumableUploadService_CreateResumableUpload_async] + /** + * This snippet has been automatically generated and should be regarded as a code template only. + * It will require modifications to work. + * It may require correct/in-range values for request initialization. + * TODO(developer): Uncomment these variables before running the sample. + */ + /** + */ + // const name = 'abc123' + /** + */ + // const description = 'abc123' + + // Imports the Resumable library + const {ResumableUploadServiceClient} = require('resumable').v1; + + // Instantiates a client + const resumableClient = new ResumableUploadServiceClient(); + + async function callCreateResumableUpload() { + // Construct request + const request = { + }; + + // Run request + const session = await resumableClient.createResumableUpload(request); + const uploadSource = resumableClient.getResumableSource('/path/to/file'); + await session.start({uploadSource}); + const response = await session.finished(); + console.log(response); + } + + callCreateResumableUpload(); + // [END resumable_v1_generated_ResumableUploadService_CreateResumableUpload_async] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/samples/generated/v1/resumable_upload_service.get_upload_status.js.baseline b/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/samples/generated/v1/resumable_upload_service.get_upload_status.js.baseline new file mode 100644 index 000000000000..1bc4435b7075 --- /dev/null +++ b/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/samples/generated/v1/resumable_upload_service.get_upload_status.js.baseline @@ -0,0 +1,59 @@ +// Copyright 2026 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 +// +// https://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. +// +// ** This file is automatically generated by gapic-generator-typescript. ** +// ** https://github.com/googleapis/gapic-generator-typescript ** +// ** All changes to this file may be overwritten. ** + + + +'use strict'; + +function main() { + // [START resumable_v1_generated_ResumableUploadService_GetUploadStatus_async] + /** + * This snippet has been automatically generated and should be regarded as a code template only. + * It will require modifications to work. + * It may require correct/in-range values for request initialization. + * TODO(developer): Uncomment these variables before running the sample. + */ + /** + */ + // const uploadUrl = 'abc123' + + // Imports the Resumable library + const {ResumableUploadServiceClient} = require('resumable').v1; + + // Instantiates a client + const resumableClient = new ResumableUploadServiceClient(); + + async function callGetUploadStatus() { + // Construct request + const request = { + }; + + // Run request + const response = await resumableClient.getUploadStatus(request); + console.log(response); + } + + callGetUploadStatus(); + // [END resumable_v1_generated_ResumableUploadService_GetUploadStatus_async] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/tsconfig.esm.json.baseline b/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/tsconfig.esm.json.baseline new file mode 100644 index 000000000000..9ea46f074fa2 --- /dev/null +++ b/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/tsconfig.esm.json.baseline @@ -0,0 +1,27 @@ +{ + "extends": "./node_modules/gts/tsconfig-google.json", + "compilerOptions": { + "rootDir": ".", + "outDir": "build", + "resolveJsonModule": true, + "module": "es2020", + "moduleResolution": "node", + "esModuleInterop": true, + "sourceMap": false, + "allowJs": true, + "lib": [ + "es2020", + "DOM" + ] + }, + "include": [ + "esm/src/*.ts", + "esm/src/**/*.ts", + "esm/test/*.ts", + "esm/test/**/*.ts", + "esm/system-test/*.ts", + "esm/src/**/*.json", + "protos/protos.json", + "esm/src/*.cjs" + ] +} diff --git a/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/tsconfig.json.baseline b/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/tsconfig.json.baseline new file mode 100644 index 000000000000..110f6eccd41b --- /dev/null +++ b/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/tsconfig.json.baseline @@ -0,0 +1,32 @@ +{ + "extends": "./node_modules/gts/tsconfig-google.json", + "compilerOptions": { + "rootDir": ".", + "resolveJsonModule": true, + "moduleResolution": "node", + "allowJs": true, + "esModuleInterop": true, + "sourceMap": false, + "target": "esnext", + "module": "CommonJS", + "declaration": true, + "strict": true, + "isolatedModules": false, + "emitDeclarationOnly": true, + "lib": [ + "es2023", + "dom" + ] + }, + "include": [ + "esm/src/*.ts", + "esm/src/**/*.ts", + "esm/test/*.ts", + "esm/test/**/*.ts", + "esm/src/**/*.json", + "esm/system-test/*.ts", + "esm/src/*.cjs", + "samples/**/*.json", + "protos/protos.json" + ] +} diff --git a/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/webpack.config.cjs.baseline b/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/webpack.config.cjs.baseline new file mode 100644 index 000000000000..0a553e060843 --- /dev/null +++ b/core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/webpack.config.cjs.baseline @@ -0,0 +1,64 @@ +// Copyright 2021 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 +// +// https://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. + +const path = require('path'); + +module.exports = { + entry: './src/index.ts', + output: { + library: 'ResumableUploadService', + filename: './resumable-upload-service.js', + }, + node: { + child_process: 'empty', + fs: 'empty', + crypto: 'empty', + }, + resolve: { + alias: { + '../../../package.json': path.resolve(__dirname, 'package.json'), + }, + extensions: ['.js', '.json', '.ts'], + }, + module: { + rules: [ + { + test: /\.tsx?$/, + use: 'ts-loader', + exclude: /node_modules/ + }, + { + test: /node_modules[\\/]@grpc[\\/]grpc-js/, + use: 'null-loader' + }, + { + test: /node_modules[\\/]grpc/, + use: 'null-loader' + }, + { + test: /node_modules[\\/]retry-request/, + use: 'null-loader' + }, + { + test: /node_modules[\\/]https?-proxy-agent/, + use: 'null-loader' + }, + { + test: /node_modules[\\/]gtoken/, + use: 'null-loader' + }, + ], + }, + mode: 'production', +}; diff --git a/core/generator/gapic-generator-typescript/baselines/resumable-upload/.OwlBot.yaml.baseline b/core/generator/gapic-generator-typescript/baselines/resumable-upload/.OwlBot.yaml.baseline new file mode 100644 index 000000000000..7feea79e8531 --- /dev/null +++ b/core/generator/gapic-generator-typescript/baselines/resumable-upload/.OwlBot.yaml.baseline @@ -0,0 +1,19 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +deep-copy-regex: + - source: /google/samples/resumable/google-samples-resumable-nodejs + dest: /owl-bot-staging/google-samples-resumable + +api-name: resumable \ No newline at end of file diff --git a/core/generator/gapic-generator-typescript/baselines/resumable-upload/.gitignore.baseline b/core/generator/gapic-generator-typescript/baselines/resumable-upload/.gitignore.baseline new file mode 100644 index 000000000000..d4f03a0df2e8 --- /dev/null +++ b/core/generator/gapic-generator-typescript/baselines/resumable-upload/.gitignore.baseline @@ -0,0 +1,14 @@ +**/*.log +**/node_modules +/.coverage +/coverage +/.nyc_output +/docs/ +/out/ +/build/ +system-test/secrets.js +system-test/*key.json +*.lock +.DS_Store +package-lock.json +__pycache__ diff --git a/core/generator/gapic-generator-typescript/baselines/resumable-upload/.jsdoc.js.baseline b/core/generator/gapic-generator-typescript/baselines/resumable-upload/.jsdoc.js.baseline new file mode 100644 index 000000000000..4b3b51f5549b --- /dev/null +++ b/core/generator/gapic-generator-typescript/baselines/resumable-upload/.jsdoc.js.baseline @@ -0,0 +1,55 @@ +// Copyright 2026 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 +// +// https://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. +// +// ** This file is automatically generated by gapic-generator-typescript. ** +// ** https://github.com/googleapis/gapic-generator-typescript ** +// ** All changes to this file may be overwritten. ** + +'use strict'; + +module.exports = { + opts: { + readme: './README.md', + package: './package.json', + template: './node_modules/jsdoc-fresh', + recurse: true, + verbose: true, + destination: './docs/' + }, + plugins: [ + 'plugins/markdown', + 'jsdoc-region-tag' + ], + source: { + excludePattern: '(^|\\/|\\\\)[._]', + include: [ + 'build/src', + 'protos' + ], + includePattern: '\\.js$' + }, + templates: { + copyright: 'Copyright 2026 Google LLC', + includeDate: false, + sourceFiles: false, + systemName: 'resumable', + theme: 'lumen', + default: { + outputSourceFiles: false + } + }, + markdown: { + idInHeadings: true + } +}; diff --git a/core/generator/gapic-generator-typescript/baselines/resumable-upload/.nycrc.baseline b/core/generator/gapic-generator-typescript/baselines/resumable-upload/.nycrc.baseline new file mode 100644 index 000000000000..81a95fc94b00 --- /dev/null +++ b/core/generator/gapic-generator-typescript/baselines/resumable-upload/.nycrc.baseline @@ -0,0 +1,24 @@ +{ + "report-dir": "./.coverage", + "reporter": ["text", "lcov"], + "exclude": [ + "**/*-test", + "**/.coverage", + "**/apis", + "**/benchmark", + "**/conformance", + "**/docs", + "**/samples", + "**/scripts", + "**/protos", + "**/test", + "**/*.d.ts", + ".jsdoc.js", + "**/.jsdoc.js", + "karma.conf.js", + "webpack-tests.config.js", + "webpack.config.js" + ], + "exclude-after-remap": false, + "all": true +} \ No newline at end of file diff --git a/core/generator/gapic-generator-typescript/baselines/resumable-upload/README.md.baseline b/core/generator/gapic-generator-typescript/baselines/resumable-upload/README.md.baseline new file mode 100644 index 000000000000..582e319d972b --- /dev/null +++ b/core/generator/gapic-generator-typescript/baselines/resumable-upload/README.md.baseline @@ -0,0 +1,108 @@ +[//]: # "This README.md file is auto-generated, all changes to this file will be lost." +[//]: # "The comments you see below are used to generate those parts of the template in later states." +Google Cloud Platform logo + +# [Resumable: Nodejs Client][homepage] + +[//]: # "releaseLevel" + +[![npm version](https://img.shields.io/npm/v/resumable.svg)](https://www.npmjs.org/package/resumable) + +Resumable client for Node.js + +[//]: # "partials.introduction" + +A comprehensive list of changes in each version may be found in +[the CHANGELOG][homepage_changelog]. + +* [Resumable Nodejs Client API Reference](https://cloud.google.com/nodejs/docs/reference/resumable/latest) + + +Read more about the client libraries for Cloud APIs, including the older +Google APIs Client Libraries, in [Client Libraries Explained][explained]. + +[explained]: https://cloud.google.com/apis/docs/client-libraries-explained + +**Table of contents:** + +* [Quickstart](#quickstart) + * [Before you begin](#before-you-begin) + * [Installing the client library](#installing-the-client-library) + +* [Versioning](#versioning) +* [Contributing](#contributing) +* [License](#license) + +## Quickstart +### Before you begin + +1. [Select or create a Cloud Platform project][projects]. +1. [Enable billing for your project][billing]. +1. [Enable the Resumable API][enable_api]. +1. [Set up authentication][auth] so you can access the + API from your local workstation. +### Installing the client library + +```bash +npm install resumable +``` + +[//]: # "partials.body" + +## Samples + +Samples are in the [`samples/`][homepage_samples] directory. Each sample's `README.md` has instructions for running its sample. + +[//]: # "samples" + +## Supported Node.js Versions + +Our client libraries follow the [Node.js release schedule](https://github.com/nodejs/release#release-schedule). +Libraries are compatible with all current _active_ and _maintenance_ versions of +Node.js. +If you are using an end-of-life version of Node.js, we recommend that you update +as soon as possible to an actively supported LTS version. + +Google's client libraries support legacy versions of Node.js runtimes on a +best-efforts basis with the following warnings: + +* Legacy versions are not tested in continuous integration. +* Some security patches and features cannot be backported. +* Dependencies cannot be kept up-to-date. + +Client libraries targeting some end-of-life versions of Node.js are available, and +can be installed through npm [dist-tags](https://docs.npmjs.com/cli/dist-tag). +The dist-tags follow the naming convention `legacy-(version)`. +For example, `npm install resumable@legacy-8` installs client libraries +for versions compatible with Node.js 8. + +## Versioning + +This library follows [Semantic Versioning](http://semver.org/). + +More Information: [Google Cloud Platform Launch Stages][launch_stages] + +[launch_stages]: https://cloud.google.com/terms/launch-stages + +## Contributing + +Contributions welcome! See the [Contributing Guide](https://github.com/googleapis/google-cloud-node/blob/main/CONTRIBUTING.md). + +Please note that this `README.md` +and a variety of configuration files in this repository (including `.nycrc` and `tsconfig.json`) +are generated from a central template. + +## License + +Apache Version 2.0 + +See [LICENSE](https://github.com/googleapis/google-cloud-node/blob/main/LICENSE) + +[shell_img]: https://gstatic.com/cloudssh/images/open-btn.png +[projects]: https://console.cloud.google.com/project +[billing]: https://support.google.com/cloud/answer/6293499#enable-billing +[enable_api]: https://console.cloud.google.com/flows/enableapi?apiid=resumable.googleapis.com +[auth]: https://cloud.google.com/docs/authentication/external/set-up-adc-local +[homepage_samples]: https://github.com/googleapis/google-cloud-node/blob/main/packages/google-samples-resumable/samples +[homepage_changelog]: https://github.com/googleapis/google-cloud-node/blob/main/packages/google-samples-resumable/CHANGELOG.md +[homepage]: https://github.com/googleapis/google-cloud-node/blob/main/packages/google-samples-resumable diff --git a/core/generator/gapic-generator-typescript/baselines/resumable-upload/package.json b/core/generator/gapic-generator-typescript/baselines/resumable-upload/package.json new file mode 100644 index 000000000000..564e02d30884 --- /dev/null +++ b/core/generator/gapic-generator-typescript/baselines/resumable-upload/package.json @@ -0,0 +1,64 @@ +{ + "name": "resumable", + "version": "0.1.0", + "description": "Resumable client for Node.js", + "repository": { + "type": "git", + "directory": "packages/google-samples-resumable", + "url": "https://github.com/googleapis/google-cloud-node.git" + }, + "license": "Apache-2.0", + "homepage": "https://github.com/googleapis/google-cloud-node/tree/main/packages/google-samples-resumable", + "author": "Google LLC", + "main": "build/src/index.js", + "files": [ + "build/src", + "build/protos" + ], + "keywords": [ + "google apis client", + "google api client", + "google apis", + "google api", + "google", + "google cloud platform", + "google cloud", + "cloud", + "google resumable", + "resumable", + "resumable upload service" + ], + "scripts": { + "clean": "gts clean", + "compile": "tsc -p . && cp -r protos build/ && minifyProtoJson", + "compile-protos": "compileProtos src", + "docs": "jsdoc -c .jsdoc.js", + "fix": "gts fix", + "lint": "gts check", + "prepare": "npm run compile-protos && npm run compile", + "system-test": "c8 mocha --config ../../.mocharc.cjs --no-parallel build/system-test", + "test": "c8 mocha --config ../../.mocharc.cjs build/test" + }, + "dependencies": { + "google-gax": "^6.0.0" + }, + "devDependencies": { + "@types/mocha": "^10.0.10", + "@types/node": "^22.18.12", + "@types/sinon": "^20.0.0", + "c8": "^10.1.3", + "gapic-tools": "^2.0.0", + "gts": "^6.0.2", + "jsdoc": "^4.0.5", + "jsdoc-fresh": "^6.0.0", + "jsdoc-region-tag": "^5.0.0", + "long": "^5.3.2", + "mocha": "^11.7.4", + "pack-n-play": "^5.0.0", + "typescript": "5.8.3", + "sinon": "^20.0.0" + }, + "engines": { + "node": ">=22" + } +} diff --git a/core/generator/gapic-generator-typescript/baselines/resumable-upload/package.json.baseline b/core/generator/gapic-generator-typescript/baselines/resumable-upload/package.json.baseline new file mode 120000 index 000000000000..2ff8622f1722 --- /dev/null +++ b/core/generator/gapic-generator-typescript/baselines/resumable-upload/package.json.baseline @@ -0,0 +1 @@ +package.json \ No newline at end of file diff --git a/core/generator/gapic-generator-typescript/baselines/resumable-upload/protos/google/samples/resumable/v1/resumable.proto.baseline b/core/generator/gapic-generator-typescript/baselines/resumable-upload/protos/google/samples/resumable/v1/resumable.proto.baseline new file mode 100644 index 000000000000..c5980d756f9d --- /dev/null +++ b/core/generator/gapic-generator-typescript/baselines/resumable-upload/protos/google/samples/resumable/v1/resumable.proto.baseline @@ -0,0 +1,72 @@ +// Copyright 2026 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. + +// Synthetic fixture used by the baseline tests to exercise resumable upload +// method generation via the resumable_upload_methods generator parameter. + +syntax = "proto3"; + +package google.samples.resumable.v1; + +import "google/api/annotations.proto"; +import "google/api/client.proto"; + +option csharp_namespace = "Google.Samples.Resumable.V1"; +option go_package = "google.golang.org/genproto/googleapis/samples/resumable/v1;resumable"; +option java_multiple_files = true; +option java_outer_classname = "ResumableProto"; +option java_package = "com.google.samples.resumable.v1"; +option php_namespace = "Google\\Samples\\Resumable\\V1"; +option ruby_package = "Google::Samples::Resumable::V1"; + +service ResumableUploadService { + option (google.api.default_host) = "resumable.googleapis.com"; + option (google.api.oauth_scopes) = + "https://www.googleapis.com/auth/cloud-platform"; + + // Creates a resumable upload session. + rpc CreateResumableUpload(CreateResumableUploadRequest) + returns (CreateResumableUploadResponse) { + option (google.api.http) = { + post: "/v1/uploads" + body: "*" + }; + } + + // Returns the status of a previously created upload session. + rpc GetUploadStatus(GetUploadStatusRequest) + returns (GetUploadStatusResponse) { + option (google.api.http) = { + get: "/v1/uploads/{upload_url}" + }; + } +} + +message CreateResumableUploadRequest { + string name = 1; + string description = 2; +} + +message CreateResumableUploadResponse { + string upload_url = 1; + string status = 2; +} + +message GetUploadStatusRequest { + string upload_url = 1; +} + +message GetUploadStatusResponse { + string status = 1; +} diff --git a/core/generator/gapic-generator-typescript/baselines/resumable-upload/samples/generated/v1/resumable_upload_service.create_resumable_upload.js.baseline b/core/generator/gapic-generator-typescript/baselines/resumable-upload/samples/generated/v1/resumable_upload_service.create_resumable_upload.js.baseline new file mode 100644 index 000000000000..67188e48e219 --- /dev/null +++ b/core/generator/gapic-generator-typescript/baselines/resumable-upload/samples/generated/v1/resumable_upload_service.create_resumable_upload.js.baseline @@ -0,0 +1,65 @@ +// Copyright 2026 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 +// +// https://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. +// +// ** This file is automatically generated by gapic-generator-typescript. ** +// ** https://github.com/googleapis/gapic-generator-typescript ** +// ** All changes to this file may be overwritten. ** + + + +'use strict'; + +function main() { + // [START resumable_v1_generated_ResumableUploadService_CreateResumableUpload_async] + /** + * This snippet has been automatically generated and should be regarded as a code template only. + * It will require modifications to work. + * It may require correct/in-range values for request initialization. + * TODO(developer): Uncomment these variables before running the sample. + */ + /** + */ + // const name = 'abc123' + /** + */ + // const description = 'abc123' + + // Imports the Resumable library + const {ResumableUploadServiceClient} = require('resumable').v1; + + // Instantiates a client + const resumableClient = new ResumableUploadServiceClient(); + + async function callCreateResumableUpload() { + // Construct request + const request = { + }; + + // Run request + const session = await resumableClient.createResumableUpload(request); + const uploadSource = resumableClient.getResumableSource('/path/to/file'); + await session.start({uploadSource}); + const response = await session.finished(); + console.log(response); + } + + callCreateResumableUpload(); + // [END resumable_v1_generated_ResumableUploadService_CreateResumableUpload_async] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/core/generator/gapic-generator-typescript/baselines/resumable-upload/samples/generated/v1/resumable_upload_service.get_upload_status.js.baseline b/core/generator/gapic-generator-typescript/baselines/resumable-upload/samples/generated/v1/resumable_upload_service.get_upload_status.js.baseline new file mode 100644 index 000000000000..1bc4435b7075 --- /dev/null +++ b/core/generator/gapic-generator-typescript/baselines/resumable-upload/samples/generated/v1/resumable_upload_service.get_upload_status.js.baseline @@ -0,0 +1,59 @@ +// Copyright 2026 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 +// +// https://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. +// +// ** This file is automatically generated by gapic-generator-typescript. ** +// ** https://github.com/googleapis/gapic-generator-typescript ** +// ** All changes to this file may be overwritten. ** + + + +'use strict'; + +function main() { + // [START resumable_v1_generated_ResumableUploadService_GetUploadStatus_async] + /** + * This snippet has been automatically generated and should be regarded as a code template only. + * It will require modifications to work. + * It may require correct/in-range values for request initialization. + * TODO(developer): Uncomment these variables before running the sample. + */ + /** + */ + // const uploadUrl = 'abc123' + + // Imports the Resumable library + const {ResumableUploadServiceClient} = require('resumable').v1; + + // Instantiates a client + const resumableClient = new ResumableUploadServiceClient(); + + async function callGetUploadStatus() { + // Construct request + const request = { + }; + + // Run request + const response = await resumableClient.getUploadStatus(request); + console.log(response); + } + + callGetUploadStatus(); + // [END resumable_v1_generated_ResumableUploadService_GetUploadStatus_async] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/core/generator/gapic-generator-typescript/baselines/resumable-upload/src/index.ts.baseline b/core/generator/gapic-generator-typescript/baselines/resumable-upload/src/index.ts.baseline new file mode 100644 index 000000000000..3aac03d6ef20 --- /dev/null +++ b/core/generator/gapic-generator-typescript/baselines/resumable-upload/src/index.ts.baseline @@ -0,0 +1,25 @@ +// Copyright 2026 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 +// +// https://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. +// +// ** This file is automatically generated by gapic-generator-typescript. ** +// ** https://github.com/googleapis/gapic-generator-typescript ** +// ** All changes to this file may be overwritten. ** + +import * as v1 from './v1'; +const ResumableUploadServiceClient = v1.ResumableUploadServiceClient; +type ResumableUploadServiceClient = v1.ResumableUploadServiceClient; +export {v1, ResumableUploadServiceClient}; +export default {v1, ResumableUploadServiceClient}; +import * as protos from '../protos/protos'; +export {protos}; diff --git a/core/generator/gapic-generator-typescript/baselines/resumable-upload/src/v1/index.ts.baseline b/core/generator/gapic-generator-typescript/baselines/resumable-upload/src/v1/index.ts.baseline new file mode 100644 index 000000000000..10a2b711fd5b --- /dev/null +++ b/core/generator/gapic-generator-typescript/baselines/resumable-upload/src/v1/index.ts.baseline @@ -0,0 +1,19 @@ +// Copyright 2026 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 +// +// https://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. +// +// ** This file is automatically generated by gapic-generator-typescript. ** +// ** https://github.com/googleapis/gapic-generator-typescript ** +// ** All changes to this file may be overwritten. ** + +export {ResumableUploadServiceClient} from './resumable_upload_service_client'; diff --git a/core/generator/gapic-generator-typescript/baselines/resumable-upload/src/v1/resumable_upload_service_client.ts.baseline b/core/generator/gapic-generator-typescript/baselines/resumable-upload/src/v1/resumable_upload_service_client.ts.baseline new file mode 100644 index 000000000000..fe83ada0fc59 --- /dev/null +++ b/core/generator/gapic-generator-typescript/baselines/resumable-upload/src/v1/resumable_upload_service_client.ts.baseline @@ -0,0 +1,601 @@ +// Copyright 2026 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 +// +// https://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. +// +// ** This file is automatically generated by gapic-generator-typescript. ** +// ** https://github.com/googleapis/gapic-generator-typescript ** +// ** All changes to this file may be overwritten. ** + +/* global window */ +import type * as gax from 'google-gax'; +import type { + Callback, + CallOptions, + Descriptors, + ClientOptions, +} from 'google-gax'; + +import * as protos from '../../protos/protos'; +import jsonProtos = require('../../protos/protos.json'); +import {loggingUtils as logging, decodeAnyProtosInArray} from 'google-gax'; + +/** + * Client JSON configuration object, loaded from + * `src/v1/resumable_upload_service_client_config.json`. + * This file defines retry strategy and timeouts for all API methods in this library. + */ +import * as gapicConfig from './resumable_upload_service_client_config.json'; +const version = require('../../../package.json').version; + +/** + * @class + * @memberof v1 + */ +export class ResumableUploadServiceClient { + private _terminated = false; + private _opts: ClientOptions; + private _providedCustomServicePath: boolean; + private _gaxModule: typeof gax | typeof gax.fallback; + private _gaxGrpc: gax.GrpcClient | gax.fallback.GrpcClient; + private _fallbackRest?: gax.GrpcClient | gax.fallback.GrpcClient; + private _protos: {}; + private _defaults: {[method: string]: gax.CallSettings}; + private _universeDomain: string; + private _servicePath: string; + private _log = logging.log('resumable'); + + auth: gax.GoogleAuth; + descriptors: Descriptors = { + page: {}, + stream: {}, + longrunning: {}, + batching: {}, + resumableUpload: {}, + }; + warn: (code: string, message: string, warnType?: string) => void; + innerApiCalls: {[name: string]: Function}; + resumableUploadServiceStub?: Promise<{[name: string]: Function}>; + + /** + * Construct an instance of ResumableUploadServiceClient. + * + * @param {object} [options] - The configuration object. + * The options accepted by the constructor are described in detail + * in [this document](https://github.com/googleapis/gax-nodejs/blob/main/client-libraries.md#creating-the-client-instance). + * The common options are: + * @param {object} [options.credentials] - Credentials object. + * @param {string} [options.credentials.client_email] + * @param {string} [options.credentials.private_key] + * @param {string} [options.email] - Account email address. Required when + * using a .pem or .p12 keyFilename. + * @param {string} [options.keyFilename] - Full path to the a .json, .pem, or + * .p12 key downloaded from the Google Developers Console. If you provide + * a path to a JSON file, the projectId option below is not necessary. + * NOTE: .pem and .p12 require you to specify options.email as well. + * @param {number} [options.port] - The port on which to connect to + * the remote host. + * @param {string} [options.projectId] - The project ID from the Google + * Developer's Console, e.g. 'grape-spaceship-123'. We will also check + * the environment variable GCLOUD_PROJECT for your project ID. If your + * app is running in an environment which supports + * {@link https://cloud.google.com/docs/authentication/application-default-credentials Application Default Credentials}, + * your project ID will be detected automatically. + * @param {string} [options.apiEndpoint] - The domain name of the + * API remote host. + * @param {gax.ClientConfig} [options.clientConfig] - Client configuration override. + * Follows the structure of {@link gapicConfig}. + * @param {boolean} [options.fallback] - Use HTTP/1.1 REST mode. + * For more information, please check the + * {@link https://github.com/googleapis/gax-nodejs/blob/main/client-libraries.md#http11-rest-api-mode documentation}. + * @param {gax} [gaxInstance]: loaded instance of `google-gax`. Useful if you + * need to avoid loading the default gRPC version and want to use the fallback + * HTTP implementation. Load only fallback version and pass it to the constructor: + * ``` + * const gax = require('google-gax/build/src/fallback'); // avoids loading google-gax with gRPC + * const client = new ResumableUploadServiceClient({fallback: true}, gax); + * ``` + */ + constructor( + opts?: ClientOptions, + gaxInstance?: typeof gax | typeof gax.fallback, + ) { + // Ensure that options include all the required fields. + const staticMembers = this + .constructor as typeof ResumableUploadServiceClient; + if ( + opts?.universe_domain && + opts?.universeDomain && + opts?.universe_domain !== opts?.universeDomain + ) { + throw new Error( + 'Please set either universe_domain or universeDomain, but not both.', + ); + } + const universeDomainEnvVar = + typeof process === 'object' && typeof process.env === 'object' + ? process.env['GOOGLE_CLOUD_UNIVERSE_DOMAIN'] + : undefined; + this._universeDomain = + opts?.universeDomain ?? + opts?.universe_domain ?? + universeDomainEnvVar ?? + 'googleapis.com'; + this._servicePath = 'resumable.' + this._universeDomain; + const servicePath = + opts?.servicePath || opts?.apiEndpoint || this._servicePath; + this._providedCustomServicePath = !!( + opts?.servicePath || opts?.apiEndpoint + ); + const port = opts?.port || staticMembers.port; + const clientConfig = opts?.clientConfig ?? {}; + const fallback = + opts?.fallback ?? + (typeof window !== 'undefined' && typeof window?.fetch === 'function'); + opts = Object.assign({servicePath, port, clientConfig, fallback}, opts); + + // If scopes are unset in options and we're connecting to a non-default endpoint, set scopes just in case. + if (servicePath !== this._servicePath && !('scopes' in opts)) { + opts['scopes'] = staticMembers.scopes; + } + + // Load google-gax module synchronously if needed + if (!gaxInstance) { + gaxInstance = require('google-gax') as typeof gax; + } + + // Choose either gRPC or proto-over-HTTP implementation of google-gax. + this._gaxModule = opts.fallback ? gaxInstance.fallback : gaxInstance; + + // Create a `gaxGrpc` object, with any grpc-specific options sent to the client. + this._gaxGrpc = new this._gaxModule.GrpcClient(opts); + + // This service contains resumable upload methods, which are HTTPS-only. + // Make sure the REST transport is available even when the client was + // configured for gRPC. + this._fallbackRest = opts.fallback + ? this._gaxGrpc + : new gaxInstance.fallback.GrpcClient({...opts, fallback: true}); + + // Save options to use in initialize() method. + this._opts = opts; + + // Save the auth object to the client, for use by other methods. + this.auth = this._gaxGrpc.auth as gax.GoogleAuth; + + // Set useJWTAccessWithScope on the auth object. + this.auth.useJWTAccessWithScope = true; + + // Set defaultServicePath on the auth object. + this.auth.defaultServicePath = this._servicePath; + + // Set the default scopes in auth client if needed. + if (servicePath === this._servicePath) { + this.auth.defaultScopes = staticMembers.scopes; + } + + // Determine the client header string. + const clientHeader = [`gax/${this._gaxModule.version}`, `gapic/${version}`]; + if (typeof process === 'object' && 'versions' in process) { + clientHeader.push(`gl-node/${process.versions.node}`); + } else { + clientHeader.push(`gl-web/${this._gaxModule.version}`); + } + if (!opts.fallback) { + clientHeader.push(`grpc/${this._gaxGrpc.grpcVersion}`); + } else { + clientHeader.push(`rest/${this._gaxGrpc.grpcVersion}`); + } + if (opts.libName && opts.libVersion) { + clientHeader.push(`${opts.libName}/${opts.libVersion}`); + } + // Load the applicable protos. + this._protos = this._gaxGrpc.loadProtoJSON(jsonProtos); + + // Some methods on this API support resumable uploads; provide + // descriptors for these methods. + this.descriptors.resumableUpload = { + createResumableUpload: new this._gaxModule.ResumableUploadDescriptor( + '/resumable/upload', + ), + }; + + // Put together the default options sent with requests. + this._defaults = this._gaxGrpc.constructSettings( + 'google.samples.resumable.v1.ResumableUploadService', + gapicConfig as gax.ClientConfig, + opts.clientConfig || {}, + {'x-goog-api-client': clientHeader.join(' ')}, + ); + + // Set up a dictionary of "inner API calls"; the core implementation + // of calling the API is handled in `google-gax`, with this code + // merely providing the destination and request information. + this.innerApiCalls = {}; + + // Add a warn function to the client constructor so it can be easily tested. + this.warn = this._gaxModule.warn; + } + + /** + * Initialize the client. + * Performs asynchronous operations (such as authentication) and prepares the client. + * This function will be called automatically when any class method is called for the + * first time, but if you need to initialize it before calling an actual method, + * feel free to call initialize() directly. + * + * You can await on this method if you want to make sure the client is initialized. + * + * @returns {Promise} A promise that resolves to an authenticated service stub. + */ + initialize() { + // If the client stub promise is already initialized, return immediately. + if (this.resumableUploadServiceStub) { + return this.resumableUploadServiceStub; + } + + // Put together the "service stub" for + // google.samples.resumable.v1.ResumableUploadService. + this.resumableUploadServiceStub = this._gaxGrpc.createStub( + this._opts.fallback + ? (this._protos as protobuf.Root).lookupService( + 'google.samples.resumable.v1.ResumableUploadService', + ) + : // eslint-disable-next-line @typescript-eslint/no-explicit-any + (this._protos as any).google.samples.resumable.v1 + .ResumableUploadService, + this._opts, + this._providedCustomServicePath, + ) as Promise<{[method: string]: Function}>; + + // Iterate over each of the methods that the service provides + // and create an API call method for each. + const resumableUploadServiceStubMethods = ['getUploadStatus']; + for (const methodName of resumableUploadServiceStubMethods) { + const callPromise = this.resumableUploadServiceStub.then( + stub => + (...args: Array<{}>) => { + if (this._terminated) { + return Promise.reject('The client has already been closed.'); + } + const func = stub[methodName]; + return func.apply(stub, args); + }, + (err: Error | null | undefined) => () => { + throw err; + }, + ); + + const descriptor = undefined; + const apiCall = this._gaxModule.createApiCall( + callPromise, + this._defaults[methodName], + descriptor, + this._opts.fallback, + ); + + this.innerApiCalls[methodName] = apiCall; + } + + // Resumable upload methods do not use the gRPC/REST service stub; the + // The ResumableUploadSession performs its own HTTP requests. + this.innerApiCalls['createResumableUpload'] = this._gaxModule.createApiCall( + this._gaxModule.resumableUploadStub, + this._defaults['createResumableUpload'], + this.descriptors.resumableUpload!['createResumableUpload'], + this._opts.fallback, + ); + + return this.resumableUploadServiceStub; + } + + /** + * The DNS address for this API service. + * @deprecated Use the apiEndpoint method of the client instance. + * @returns {string} The DNS address for this service. + */ + static get servicePath() { + if ( + typeof process === 'object' && + typeof process.emitWarning === 'function' + ) { + process.emitWarning( + 'Static servicePath is deprecated, please use the instance method instead.', + 'DeprecationWarning', + ); + } + return 'resumable.googleapis.com'; + } + + /** + * The DNS address for this API service - same as servicePath. + * @deprecated Use the apiEndpoint method of the client instance. + * @returns {string} The DNS address for this service. + */ + static get apiEndpoint() { + if ( + typeof process === 'object' && + typeof process.emitWarning === 'function' + ) { + process.emitWarning( + 'Static apiEndpoint is deprecated, please use the instance method instead.', + 'DeprecationWarning', + ); + } + return 'resumable.googleapis.com'; + } + + /** + * The DNS address for this API service. + * @returns {string} The DNS address for this service. + */ + get apiEndpoint() { + return this._servicePath; + } + + get universeDomain() { + return this._universeDomain; + } + + /** + * The port for this API service. + * @returns {number} The default port for this service. + */ + static get port() { + return 443; + } + + /** + * The scopes needed to make gRPC calls for every method defined + * in this service. + * @returns {string[]} List of default scopes. + */ + static get scopes() { + return ['https://www.googleapis.com/auth/cloud-platform']; + } + + getProjectId(): Promise; + getProjectId(callback: Callback): void; + /** + * Return the project ID used by this class. + * @returns {Promise} A promise that resolves to string containing the project ID. + */ + getProjectId( + callback?: Callback, + ): Promise | void { + if (callback) { + this.auth.getProjectId(callback); + return; + } + return this.auth.getProjectId(); + } + + // ------------------- + // -- Service calls -- + // ------------------- + /** + * Returns the status of a previously created upload session. + * + * @param {Object} request + * The request object that will be sent. + * @param {string} request.uploadUrl + * @param {object} [options] + * Call options. See {@link https://googleapis.dev/nodejs/google-gax/latest/interfaces/CallOptions.html|CallOptions} for more details. + * @returns {Promise} - The promise which resolves to an array. + * The first element of the array is an object representing {@link protos.google.samples.resumable.v1.GetUploadStatusResponse|GetUploadStatusResponse}. + * Please see the {@link https://github.com/googleapis/gax-nodejs/blob/master/client-libraries.md#regular-methods | documentation } + * for more details and examples. + * @example include:samples/generated/v1/resumable_upload_service.get_upload_status.js + * region_tag:resumable_v1_generated_ResumableUploadService_GetUploadStatus_async + */ + getUploadStatus( + request?: protos.google.samples.resumable.v1.IGetUploadStatusRequest, + options?: CallOptions, + ): Promise< + [ + protos.google.samples.resumable.v1.IGetUploadStatusResponse, + protos.google.samples.resumable.v1.IGetUploadStatusRequest | undefined, + {} | undefined, + ] + >; + getUploadStatus( + request: protos.google.samples.resumable.v1.IGetUploadStatusRequest, + options: CallOptions, + callback: Callback< + protos.google.samples.resumable.v1.IGetUploadStatusResponse, + | protos.google.samples.resumable.v1.IGetUploadStatusRequest + | null + | undefined, + {} | null | undefined + >, + ): void; + getUploadStatus( + request: protos.google.samples.resumable.v1.IGetUploadStatusRequest, + callback: Callback< + protos.google.samples.resumable.v1.IGetUploadStatusResponse, + | protos.google.samples.resumable.v1.IGetUploadStatusRequest + | null + | undefined, + {} | null | undefined + >, + ): void; + getUploadStatus( + request?: protos.google.samples.resumable.v1.IGetUploadStatusRequest, + optionsOrCallback?: + | CallOptions + | Callback< + protos.google.samples.resumable.v1.IGetUploadStatusResponse, + | protos.google.samples.resumable.v1.IGetUploadStatusRequest + | null + | undefined, + {} | null | undefined + >, + callback?: Callback< + protos.google.samples.resumable.v1.IGetUploadStatusResponse, + | protos.google.samples.resumable.v1.IGetUploadStatusRequest + | null + | undefined, + {} | null | undefined + >, + ): Promise< + [ + protos.google.samples.resumable.v1.IGetUploadStatusResponse, + protos.google.samples.resumable.v1.IGetUploadStatusRequest | undefined, + {} | undefined, + ] + > | void { + request = request || {}; + let options: CallOptions; + if (typeof optionsOrCallback === 'function' && callback === undefined) { + callback = optionsOrCallback; + options = {}; + } else { + options = optionsOrCallback as CallOptions; + } + options = options || {}; + options.otherArgs = options.otherArgs || {}; + options.otherArgs.headers = options.otherArgs.headers || {}; + options.otherArgs.headers['x-goog-request-params'] = + this._gaxModule.routingHeader.fromParams({ + upload_url: request.uploadUrl ?? '', + }); + this.initialize().catch(err => { + throw err; + }); + this._log.info('getUploadStatus request %j', request); + const wrappedCallback: + | Callback< + protos.google.samples.resumable.v1.IGetUploadStatusResponse, + | protos.google.samples.resumable.v1.IGetUploadStatusRequest + | null + | undefined, + {} | null | undefined + > + | undefined = callback + ? (error, response, options, rawResponse) => { + this._log.info('getUploadStatus response %j', response); + callback!(error, response, options, rawResponse); // We verified callback above. + } + : undefined; + return this.innerApiCalls + .getUploadStatus(request, options, wrappedCallback) + ?.then( + ([response, options, rawResponse]: [ + protos.google.samples.resumable.v1.IGetUploadStatusResponse, + ( + | protos.google.samples.resumable.v1.IGetUploadStatusRequest + | undefined + ), + {} | undefined, + ]) => { + this._log.info('getUploadStatus response %j', response); + return [response, options, rawResponse]; + }, + ) + .catch((error: any) => { + if ( + error && + 'statusDetails' in error && + error.statusDetails instanceof Array + ) { + const protos = this._gaxModule.protobuf.Root.fromJSON( + jsonProtos, + ) as unknown as gax.protobuf.Type; + error.statusDetails = decodeAnyProtosInArray( + error.statusDetails, + protos, + ); + } + throw error; + }); + } + /** + * Creates a {@link gax.ResumableSource} backed by a local file. + * + * This delegates to google-gax so generated clients do not depend on the + * Node.js `fs` module at import time. The source can be passed as the + * `uploadSource` parameter of {@link gax.ResumableUploadSession#start}. + * + * @param {string} filePath - Path to the local file to upload. + * @returns {gax.ResumableSource} A seekable upload source for the file. + */ + getResumableSource(filePath: string): gax.ResumableSource { + const gaxModule = this._gaxModule as typeof gax; + return gaxModule.resumableSourceFromFile(filePath); + } + /** + * Creates a resumable upload session. + * + * @param {object} [request] - The request object. + * @param {object} [options] - Optional parameters. The upload source, chunk + * size, progress callback, and resume URL are passed to + * {@link gax.ResumableUploadSession#start} instead. + * @returns {Promise} A resumable upload session. + * Call `.start(uploadParams)` with the source to upload, then await + * `.finished()` for the final RPC response. + */ + createResumableUpload( + request?: protos.google.samples.resumable.v1.ICreateResumableUploadRequest, + options?: CallOptions, + ): Promise { + request = request || {}; + options = options || {}; + if (!this._opts.fallback && this._opts.sslCreds) { + return Promise.reject( + new this._gaxModule.GoogleError( + 'Resumable upload methods require HTTP(S) authentication and ' + + 'cannot be used with gRPC channel credentials. Configure the ' + + 'client without `sslCreds`, or use `fallback: true`.', + ), + ); + } + this.initialize().catch(err => { + throw err; + }); + this._log.info('createResumableUpload request %j', request); + return ( + this.innerApiCalls['createResumableUpload'](request, { + ...options, + resumableUpload: { + auth: this._fallbackRest!.auth as gax.GoogleAuth, + servicePath: this._opts.servicePath ?? this._servicePath, + servicePort: this._opts.port || 443, + protocol: this._opts.protocol || 'https', + rpc: this._gaxModule.protobuf.Root.fromJSON(jsonProtos).lookupService( + 'google.samples.resumable.v1.ResumableUploadService', + ).methods['CreateResumableUpload'], + request, + uploadPrefix: '/resumable/upload', + numericEnums: this._opts.numericEnums, + minifyJson: this._opts.minifyJson, + }, + }) as Promise<[gax.ResumableUploadSession]> + ).then(([session]) => session); + } + + /** + * Terminate the gRPC channel and close the client. + * + * The client will no longer be usable and all future behavior is undefined. + * @returns {Promise} A promise that resolves when the client is closed. + */ + close(): Promise { + if (this.resumableUploadServiceStub && !this._terminated) { + return this.resumableUploadServiceStub.then(stub => { + this._log.info('ending gRPC channel'); + this._terminated = true; + stub.close(); + }); + } + return Promise.resolve(); + } +} diff --git a/core/generator/gapic-generator-typescript/baselines/resumable-upload/src/v1/resumable_upload_service_client_config.json.baseline b/core/generator/gapic-generator-typescript/baselines/resumable-upload/src/v1/resumable_upload_service_client_config.json.baseline new file mode 100644 index 000000000000..de3f9fed6441 --- /dev/null +++ b/core/generator/gapic-generator-typescript/baselines/resumable-upload/src/v1/resumable_upload_service_client_config.json.baseline @@ -0,0 +1,34 @@ +{ + "interfaces": { + "google.samples.resumable.v1.ResumableUploadService": { + "retry_codes": { + "non_idempotent": [], + "idempotent": [ + "DEADLINE_EXCEEDED", + "UNAVAILABLE" + ] + }, + "retry_params": { + "default": { + "initial_retry_delay_millis": 100, + "retry_delay_multiplier": 1.3, + "max_retry_delay_millis": 60000, + "initial_rpc_timeout_millis": 60000, + "rpc_timeout_multiplier": 1, + "max_rpc_timeout_millis": 60000, + "total_timeout_millis": 600000 + } + }, + "methods": { + "CreateResumableUpload": { + "retry_codes_name": "non_idempotent", + "retry_params_name": "default" + }, + "GetUploadStatus": { + "retry_codes_name": "non_idempotent", + "retry_params_name": "default" + } + } + } + } +} diff --git a/core/generator/gapic-generator-typescript/baselines/resumable-upload/src/v1/resumable_upload_service_proto_list.json.baseline b/core/generator/gapic-generator-typescript/baselines/resumable-upload/src/v1/resumable_upload_service_proto_list.json.baseline new file mode 100644 index 000000000000..4acc3ac38ace --- /dev/null +++ b/core/generator/gapic-generator-typescript/baselines/resumable-upload/src/v1/resumable_upload_service_proto_list.json.baseline @@ -0,0 +1,3 @@ +[ + "../../protos/google/samples/resumable/v1/resumable.proto" +] diff --git a/core/generator/gapic-generator-typescript/baselines/resumable-upload/system-test/fixtures/sample/src/index.js.baseline b/core/generator/gapic-generator-typescript/baselines/resumable-upload/system-test/fixtures/sample/src/index.js.baseline new file mode 100644 index 000000000000..49a3b15d7ff1 --- /dev/null +++ b/core/generator/gapic-generator-typescript/baselines/resumable-upload/system-test/fixtures/sample/src/index.js.baseline @@ -0,0 +1,27 @@ +// Copyright 2026 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 +// +// https://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. +// +// ** This file is automatically generated by gapic-generator-typescript. ** +// ** https://github.com/googleapis/gapic-generator-typescript ** +// ** All changes to this file may be overwritten. ** + + +/* eslint-disable node/no-missing-require, no-unused-vars */ +const resumable = require('resumable'); + +function main() { + const resumableUploadServiceClient = new resumable.ResumableUploadServiceClient(); +} + +main(); diff --git a/core/generator/gapic-generator-typescript/baselines/resumable-upload/system-test/fixtures/sample/src/index.ts.baseline b/core/generator/gapic-generator-typescript/baselines/resumable-upload/system-test/fixtures/sample/src/index.ts.baseline new file mode 100644 index 000000000000..3c57d32a6bb3 --- /dev/null +++ b/core/generator/gapic-generator-typescript/baselines/resumable-upload/system-test/fixtures/sample/src/index.ts.baseline @@ -0,0 +1,34 @@ +// Copyright 2026 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 +// +// https://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. +// +// ** This file is automatically generated by gapic-generator-typescript. ** +// ** https://github.com/googleapis/gapic-generator-typescript ** +// ** All changes to this file may be overwritten. ** + +import {ResumableUploadServiceClient} from 'resumable'; + +// check that the client class type name can be used +function doStuffWithResumableUploadServiceClient( + client: ResumableUploadServiceClient, +) { + client.close(); +} + +function main() { + // check that the client instance can be created + const resumableUploadServiceClient = new ResumableUploadServiceClient(); + doStuffWithResumableUploadServiceClient(resumableUploadServiceClient); +} + +main(); diff --git a/core/generator/gapic-generator-typescript/baselines/resumable-upload/system-test/install.ts.baseline b/core/generator/gapic-generator-typescript/baselines/resumable-upload/system-test/install.ts.baseline new file mode 100644 index 000000000000..79d5bea3c93e --- /dev/null +++ b/core/generator/gapic-generator-typescript/baselines/resumable-upload/system-test/install.ts.baseline @@ -0,0 +1,51 @@ +// Copyright 2026 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 +// +// https://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. +// +// ** This file is automatically generated by gapic-generator-typescript. ** +// ** https://github.com/googleapis/gapic-generator-typescript ** +// ** All changes to this file may be overwritten. ** + +import {packNTest} from 'pack-n-play'; +import {readFileSync} from 'fs'; +import {describe, it} from 'mocha'; + +describe('📦 pack-n-play test', () => { + it('TypeScript code', async function () { + this.timeout(300000); + const options = { + packageDir: process.cwd(), + sample: { + description: 'TypeScript user can use the type definitions', + ts: readFileSync( + './system-test/fixtures/sample/src/index.ts', + ).toString(), + }, + }; + await packNTest(options); + }); + + it('JavaScript code', async function () { + this.timeout(300000); + const options = { + packageDir: process.cwd(), + sample: { + description: 'JavaScript user can use the library', + cjs: readFileSync( + './system-test/fixtures/sample/src/index.js', + ).toString(), + }, + }; + await packNTest(options); + }); +}); diff --git a/core/generator/gapic-generator-typescript/baselines/resumable-upload/test/gapic_resumable_upload_service_v1.ts.baseline b/core/generator/gapic-generator-typescript/baselines/resumable-upload/test/gapic_resumable_upload_service_v1.ts.baseline new file mode 100644 index 000000000000..f8fa88cfa884 --- /dev/null +++ b/core/generator/gapic-generator-typescript/baselines/resumable-upload/test/gapic_resumable_upload_service_v1.ts.baseline @@ -0,0 +1,408 @@ +// Copyright 2026 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 +// +// https://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. +// +// ** This file is automatically generated by gapic-generator-typescript. ** +// ** https://github.com/googleapis/gapic-generator-typescript ** +// ** All changes to this file may be overwritten. ** + +import * as protos from '../protos/protos'; +import * as assert from 'assert'; +import * as sinon from 'sinon'; +import {SinonStub} from 'sinon'; +import {describe, it} from 'mocha'; +import * as resumableuploadserviceModule from '../src'; + +import {protobuf} from 'google-gax'; + +// Dynamically loaded proto JSON is needed to get the type information +// to fill in default values for request objects +const root = protobuf.Root.fromJSON( + require('../protos/protos.json'), +).resolveAll(); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getTypeDefaultValue(typeName: string, fields: string[]) { + let type = root.lookupType(typeName) as protobuf.Type; + for (const field of fields.slice(0, -1)) { + type = type.fields[field]?.resolvedType as protobuf.Type; + } + return type.fields[fields[fields.length - 1]]?.defaultValue; +} + +function generateSampleMessage(instance: T) { + const filledObject = ( + instance.constructor as typeof protobuf.Message + ).toObject(instance as protobuf.Message, {defaults: true}); + return (instance.constructor as typeof protobuf.Message).fromObject( + filledObject, + ) as T; +} + +function stubSimpleCall(response?: ResponseType, error?: Error) { + return error + ? sinon.stub().rejects(error) + : sinon.stub().resolves([response]); +} + +function stubSimpleCallWithCallback( + response?: ResponseType, + error?: Error, +) { + return error + ? sinon.stub().callsArgWith(2, error) + : sinon.stub().callsArgWith(2, null, response); +} + +describe('v1.ResumableUploadServiceClient', () => { + describe('Common methods', () => { + it('has apiEndpoint', () => { + const client = + new resumableuploadserviceModule.v1.ResumableUploadServiceClient(); + const apiEndpoint = client.apiEndpoint; + assert.strictEqual(apiEndpoint, 'resumable.googleapis.com'); + }); + + it('has universeDomain', () => { + const client = + new resumableuploadserviceModule.v1.ResumableUploadServiceClient(); + const universeDomain = client.universeDomain; + assert.strictEqual(universeDomain, 'googleapis.com'); + }); + + if ( + typeof process === 'object' && + typeof process.emitWarning === 'function' + ) { + it('throws DeprecationWarning if static servicePath is used', () => { + const stub = sinon.stub(process, 'emitWarning'); + const servicePath = + resumableuploadserviceModule.v1.ResumableUploadServiceClient + .servicePath; + assert.strictEqual(servicePath, 'resumable.googleapis.com'); + assert(stub.called); + stub.restore(); + }); + + it('throws DeprecationWarning if static apiEndpoint is used', () => { + const stub = sinon.stub(process, 'emitWarning'); + const apiEndpoint = + resumableuploadserviceModule.v1.ResumableUploadServiceClient + .apiEndpoint; + assert.strictEqual(apiEndpoint, 'resumable.googleapis.com'); + assert(stub.called); + stub.restore(); + }); + } + it('sets apiEndpoint according to universe domain camelCase', () => { + const client = + new resumableuploadserviceModule.v1.ResumableUploadServiceClient({ + universeDomain: 'example.com', + }); + const servicePath = client.apiEndpoint; + assert.strictEqual(servicePath, 'resumable.example.com'); + }); + + it('sets apiEndpoint according to universe domain snakeCase', () => { + const client = + new resumableuploadserviceModule.v1.ResumableUploadServiceClient({ + universe_domain: 'example.com', + }); + const servicePath = client.apiEndpoint; + assert.strictEqual(servicePath, 'resumable.example.com'); + }); + + if (typeof process === 'object' && 'env' in process) { + describe('GOOGLE_CLOUD_UNIVERSE_DOMAIN environment variable', () => { + it('sets apiEndpoint from environment variable', () => { + const saved = process.env['GOOGLE_CLOUD_UNIVERSE_DOMAIN']; + process.env['GOOGLE_CLOUD_UNIVERSE_DOMAIN'] = 'example.com'; + const client = + new resumableuploadserviceModule.v1.ResumableUploadServiceClient(); + const servicePath = client.apiEndpoint; + assert.strictEqual(servicePath, 'resumable.example.com'); + if (saved) { + process.env['GOOGLE_CLOUD_UNIVERSE_DOMAIN'] = saved; + } else { + delete process.env['GOOGLE_CLOUD_UNIVERSE_DOMAIN']; + } + }); + + it('value configured in code has priority over environment variable', () => { + const saved = process.env['GOOGLE_CLOUD_UNIVERSE_DOMAIN']; + process.env['GOOGLE_CLOUD_UNIVERSE_DOMAIN'] = 'example.com'; + const client = + new resumableuploadserviceModule.v1.ResumableUploadServiceClient({ + universeDomain: 'configured.example.com', + }); + const servicePath = client.apiEndpoint; + assert.strictEqual(servicePath, 'resumable.configured.example.com'); + if (saved) { + process.env['GOOGLE_CLOUD_UNIVERSE_DOMAIN'] = saved; + } else { + delete process.env['GOOGLE_CLOUD_UNIVERSE_DOMAIN']; + } + }); + }); + } + it('does not allow setting both universeDomain and universe_domain', () => { + assert.throws(() => { + new resumableuploadserviceModule.v1.ResumableUploadServiceClient({ + universe_domain: 'example.com', + universeDomain: 'example.net', + }); + }); + }); + + it('has port', () => { + const port = + resumableuploadserviceModule.v1.ResumableUploadServiceClient.port; + assert(port); + assert(typeof port === 'number'); + }); + + it('should create a client with no option', () => { + const client = + new resumableuploadserviceModule.v1.ResumableUploadServiceClient(); + assert(client); + }); + + it('should create a client with gRPC fallback', () => { + const client = + new resumableuploadserviceModule.v1.ResumableUploadServiceClient({ + fallback: true, + }); + assert(client); + }); + + it('has initialize method and supports deferred initialization', async () => { + const client = + new resumableuploadserviceModule.v1.ResumableUploadServiceClient({ + credentials: {client_email: 'bogus', private_key: 'bogus'}, + projectId: 'bogus', + }); + assert.strictEqual(client.resumableUploadServiceStub, undefined); + await client.initialize(); + assert(client.resumableUploadServiceStub); + }); + + it('has close method for the initialized client', done => { + const client = + new resumableuploadserviceModule.v1.ResumableUploadServiceClient({ + credentials: {client_email: 'bogus', private_key: 'bogus'}, + projectId: 'bogus', + }); + client.initialize().catch(err => { + throw err; + }); + assert(client.resumableUploadServiceStub); + client + .close() + .then(() => { + done(); + }) + .catch(err => { + throw err; + }); + }); + + it('has close method for the non-initialized client', done => { + const client = + new resumableuploadserviceModule.v1.ResumableUploadServiceClient({ + credentials: {client_email: 'bogus', private_key: 'bogus'}, + projectId: 'bogus', + }); + assert.strictEqual(client.resumableUploadServiceStub, undefined); + client + .close() + .then(() => { + done(); + }) + .catch(err => { + throw err; + }); + }); + + it('has getProjectId method', async () => { + const fakeProjectId = 'fake-project-id'; + const client = + new resumableuploadserviceModule.v1.ResumableUploadServiceClient({ + credentials: {client_email: 'bogus', private_key: 'bogus'}, + projectId: 'bogus', + }); + client.auth.getProjectId = sinon.stub().resolves(fakeProjectId); + const result = await client.getProjectId(); + assert.strictEqual(result, fakeProjectId); + assert((client.auth.getProjectId as SinonStub).calledWithExactly()); + }); + + it('has getProjectId method with callback', async () => { + const fakeProjectId = 'fake-project-id'; + const client = + new resumableuploadserviceModule.v1.ResumableUploadServiceClient({ + credentials: {client_email: 'bogus', private_key: 'bogus'}, + projectId: 'bogus', + }); + client.auth.getProjectId = sinon + .stub() + .callsArgWith(0, null, fakeProjectId); + const promise = new Promise((resolve, reject) => { + client.getProjectId((err?: Error | null, projectId?: string | null) => { + if (err) { + reject(err); + } else { + resolve(projectId); + } + }); + }); + const result = await promise; + assert.strictEqual(result, fakeProjectId); + }); + }); + + describe('getUploadStatus', () => { + it('invokes getUploadStatus without error', async () => { + const client = + new resumableuploadserviceModule.v1.ResumableUploadServiceClient({ + credentials: {client_email: 'bogus', private_key: 'bogus'}, + projectId: 'bogus', + }); + await client.initialize(); + const request = generateSampleMessage( + new protos.google.samples.resumable.v1.GetUploadStatusRequest(), + ); + const defaultValue1 = getTypeDefaultValue( + '.google.samples.resumable.v1.GetUploadStatusRequest', + ['uploadUrl'], + ); + request.uploadUrl = defaultValue1; + const expectedHeaderRequestParams = `upload_url=${defaultValue1 ?? ''}`; + const expectedResponse = generateSampleMessage( + new protos.google.samples.resumable.v1.GetUploadStatusResponse(), + ); + client.innerApiCalls.getUploadStatus = stubSimpleCall(expectedResponse); + const [response] = await client.getUploadStatus(request); + assert.deepStrictEqual(response, expectedResponse); + const actualRequest = ( + client.innerApiCalls.getUploadStatus as SinonStub + ).getCall(0).args[0]; + assert.deepStrictEqual(actualRequest, request); + const actualHeaderRequestParams = ( + client.innerApiCalls.getUploadStatus as SinonStub + ).getCall(0).args[1].otherArgs.headers['x-goog-request-params']; + assert(actualHeaderRequestParams.includes(expectedHeaderRequestParams)); + }); + + it('invokes getUploadStatus without error using callback', async () => { + const client = + new resumableuploadserviceModule.v1.ResumableUploadServiceClient({ + credentials: {client_email: 'bogus', private_key: 'bogus'}, + projectId: 'bogus', + }); + await client.initialize(); + const request = generateSampleMessage( + new protos.google.samples.resumable.v1.GetUploadStatusRequest(), + ); + const defaultValue1 = getTypeDefaultValue( + '.google.samples.resumable.v1.GetUploadStatusRequest', + ['uploadUrl'], + ); + request.uploadUrl = defaultValue1; + const expectedHeaderRequestParams = `upload_url=${defaultValue1 ?? ''}`; + const expectedResponse = generateSampleMessage( + new protos.google.samples.resumable.v1.GetUploadStatusResponse(), + ); + client.innerApiCalls.getUploadStatus = + stubSimpleCallWithCallback(expectedResponse); + const promise = new Promise((resolve, reject) => { + client.getUploadStatus( + request, + ( + err?: Error | null, + result?: protos.google.samples.resumable.v1.IGetUploadStatusResponse | null, + ) => { + if (err) { + reject(err); + } else { + resolve(result); + } + }, + ); + }); + const response = await promise; + assert.deepStrictEqual(response, expectedResponse); + const actualRequest = ( + client.innerApiCalls.getUploadStatus as SinonStub + ).getCall(0).args[0]; + assert.deepStrictEqual(actualRequest, request); + const actualHeaderRequestParams = ( + client.innerApiCalls.getUploadStatus as SinonStub + ).getCall(0).args[1].otherArgs.headers['x-goog-request-params']; + assert(actualHeaderRequestParams.includes(expectedHeaderRequestParams)); + }); + + it('invokes getUploadStatus with error', async () => { + const client = + new resumableuploadserviceModule.v1.ResumableUploadServiceClient({ + credentials: {client_email: 'bogus', private_key: 'bogus'}, + projectId: 'bogus', + }); + await client.initialize(); + const request = generateSampleMessage( + new protos.google.samples.resumable.v1.GetUploadStatusRequest(), + ); + const defaultValue1 = getTypeDefaultValue( + '.google.samples.resumable.v1.GetUploadStatusRequest', + ['uploadUrl'], + ); + request.uploadUrl = defaultValue1; + const expectedHeaderRequestParams = `upload_url=${defaultValue1 ?? ''}`; + const expectedError = new Error('expected'); + client.innerApiCalls.getUploadStatus = stubSimpleCall( + undefined, + expectedError, + ); + await assert.rejects(client.getUploadStatus(request), expectedError); + const actualRequest = ( + client.innerApiCalls.getUploadStatus as SinonStub + ).getCall(0).args[0]; + assert.deepStrictEqual(actualRequest, request); + const actualHeaderRequestParams = ( + client.innerApiCalls.getUploadStatus as SinonStub + ).getCall(0).args[1].otherArgs.headers['x-goog-request-params']; + assert(actualHeaderRequestParams.includes(expectedHeaderRequestParams)); + }); + + it('invokes getUploadStatus with closed client', async () => { + const client = + new resumableuploadserviceModule.v1.ResumableUploadServiceClient({ + credentials: {client_email: 'bogus', private_key: 'bogus'}, + projectId: 'bogus', + }); + await client.initialize(); + const request = generateSampleMessage( + new protos.google.samples.resumable.v1.GetUploadStatusRequest(), + ); + const defaultValue1 = getTypeDefaultValue( + '.google.samples.resumable.v1.GetUploadStatusRequest', + ['uploadUrl'], + ); + request.uploadUrl = defaultValue1; + const expectedError = new Error('The client has already been closed.'); + client.close().catch(err => { + throw err; + }); + await assert.rejects(client.getUploadStatus(request), expectedError); + }); + }); +}); diff --git a/core/generator/gapic-generator-typescript/baselines/resumable-upload/tsconfig.json.baseline b/core/generator/gapic-generator-typescript/baselines/resumable-upload/tsconfig.json.baseline new file mode 100644 index 000000000000..ca73e7bfc824 --- /dev/null +++ b/core/generator/gapic-generator-typescript/baselines/resumable-upload/tsconfig.json.baseline @@ -0,0 +1,22 @@ +{ + "extends": "./node_modules/gts/tsconfig-google.json", + "compilerOptions": { + "rootDir": ".", + "outDir": "build", + "resolveJsonModule": true, + "lib": [ + "es2023", + "dom" + ] + }, + "include": [ + "src/*.ts", + "src/**/*.ts", + "test/*.ts", + "test/**/*.ts", + "system-test/*.ts", + "src/**/*.json", + "samples/**/*.json", + "protos/protos.json" + ] +} diff --git a/core/generator/gapic-generator-typescript/baselines/resumable-upload/webpack.config.js.baseline b/core/generator/gapic-generator-typescript/baselines/resumable-upload/webpack.config.js.baseline new file mode 100644 index 000000000000..0a553e060843 --- /dev/null +++ b/core/generator/gapic-generator-typescript/baselines/resumable-upload/webpack.config.js.baseline @@ -0,0 +1,64 @@ +// Copyright 2021 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 +// +// https://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. + +const path = require('path'); + +module.exports = { + entry: './src/index.ts', + output: { + library: 'ResumableUploadService', + filename: './resumable-upload-service.js', + }, + node: { + child_process: 'empty', + fs: 'empty', + crypto: 'empty', + }, + resolve: { + alias: { + '../../../package.json': path.resolve(__dirname, 'package.json'), + }, + extensions: ['.js', '.json', '.ts'], + }, + module: { + rules: [ + { + test: /\.tsx?$/, + use: 'ts-loader', + exclude: /node_modules/ + }, + { + test: /node_modules[\\/]@grpc[\\/]grpc-js/, + use: 'null-loader' + }, + { + test: /node_modules[\\/]grpc/, + use: 'null-loader' + }, + { + test: /node_modules[\\/]retry-request/, + use: 'null-loader' + }, + { + test: /node_modules[\\/]https?-proxy-agent/, + use: 'null-loader' + }, + { + test: /node_modules[\\/]gtoken/, + use: 'null-loader' + }, + ], + }, + mode: 'production', +}; diff --git a/core/generator/gapic-generator-typescript/rules_typescript_gapic/typescript_gapic.bzl b/core/generator/gapic-generator-typescript/rules_typescript_gapic/typescript_gapic.bzl index b76a85508c26..0385513a47a3 100644 --- a/core/generator/gapic-generator-typescript/rules_typescript_gapic/typescript_gapic.bzl +++ b/core/generator/gapic-generator-typescript/rules_typescript_gapic/typescript_gapic.bzl @@ -30,6 +30,7 @@ def typescript_gapic_library( legacy_proto_load = None, rest_numeric_enums = None, mixins = None, + resumable_upload_methods = None, format = None, extra_protoc_parameters = [], extra_protoc_file_parameters = {}, @@ -56,6 +57,8 @@ def typescript_gapic_library( plugin_args_dict["rest-numeric-enums"] = "true" if mixins: plugin_args_dict["mixins"] = mixins + if resumable_upload_methods: + plugin_args_dict["resumable-upload-methods"] = resumable_upload_methods file_args = {} # note: keys are filenames, values are parameter name, aligned with the prior art for key, value in extra_protoc_file_parameters: diff --git a/core/generator/gapic-generator-typescript/templates/cjs/typescript_gapic/src/$version/$service_client.ts.njk b/core/generator/gapic-generator-typescript/templates/cjs/typescript_gapic/src/$version/$service_client.ts.njk index 82dded752187..21eb410b98a9 100644 --- a/core/generator/gapic-generator-typescript/templates/cjs/typescript_gapic/src/$version/$service_client.ts.njk +++ b/core/generator/gapic-generator-typescript/templates/cjs/typescript_gapic/src/$version/$service_client.ts.njk @@ -78,6 +78,9 @@ export class {{ service.name }}Client { private _providedCustomServicePath: boolean; private _gaxModule: typeof gax{% if not api.legacyProtoLoad %} | typeof gax.fallback{% endif %}; private _gaxGrpc: gax.GrpcClient{% if not api.legacyProtoLoad %} | gax.fallback.GrpcClient{% endif %}; +{%- if not api.legacyProtoLoad and service.resumableUploads.length > 0 %} + private _fallbackRest?: gax.GrpcClient | gax.fallback.GrpcClient; +{%- endif %} private _protos: {}; private _defaults: {[method: string]: gax.CallSettings}; private _universeDomain: string; @@ -90,6 +93,9 @@ export class {{ service.name }}Client { stream: {}, longrunning: {}, batching: {}, +{%- if not api.legacyProtoLoad and service.resumableUploads.length > 0 %} + resumableUpload: {}, +{%- endif %} }; warn: (code: string, message: string, warnType?: string) => void; innerApiCalls: {[name: string]: Function}; @@ -202,6 +208,15 @@ export class {{ service.name }}Client { // Create a `gaxGrpc` object, with any grpc-specific options sent to the client. this._gaxGrpc = new this._gaxModule.GrpcClient(opts); +{%- if not api.legacyProtoLoad and service.resumableUploads.length > 0 %} + + // This service contains resumable upload methods, which are HTTPS-only. + // Make sure the REST transport is available even when the client was + // configured for gRPC. + this._fallbackRest = opts.fallback + ? this._gaxGrpc + : new gaxInstance.fallback.GrpcClient({...opts, fallback: true}); +{%- endif %} // Save options to use in {{ id.get("initialize") }}() method. this._opts = opts; @@ -399,6 +414,21 @@ export class {{ service.name }}Client { }; {%- endif %} +{%- if not api.legacyProtoLoad and service.resumableUploads.length > 0 %} + + // Some methods on this API support resumable uploads; provide + // descriptors for these methods. + this.descriptors.resumableUpload = { +{%- set resumableUploadJoiner = joiner() %} +{%- for method in service.resumableUploads %} + {{- resumableUploadJoiner() }} + {{ method.name.toCamelCase() }}: new this._gaxModule.ResumableUploadDescriptor( + '{{ method.resumableUpload.uploadPrefix }}' + ) +{%- endfor %} + }; +{%- endif %} + // Put together the default options sent with requests. this._defaults = this._gaxGrpc.constructSettings( '{{ api.naming.protoPackage }}.{{ service.name }}', gapicConfig as gax.ClientConfig, @@ -447,10 +477,10 @@ export class {{ service.name }}Client { (this._protos as any).{{api.naming.protoPackage}}.{{ service.name }}, this._opts, this._providedCustomServicePath) as Promise<{[method: string]: Function}>; - {%- set stubMethodsContent %} +{%- set stubMethodsContent %} {%- set stubMethodsJoiner = joiner(', ') -%} {%- for method in service.method -%} - {%- if not method.ignoreMapPagingMethod %} + {%- if not method.ignoreMapPagingMethod and not method.resumableUpload %} {{- stubMethodsJoiner() -}} '{{ method.name.toCamelCase(true) }}' {%- endif %} @@ -508,6 +538,20 @@ export class {{ service.name }}Client { this.innerApiCalls[methodName] = apiCall; } {%- endif -%} +{%- if not api.legacyProtoLoad and service.resumableUploads.length > 0 %} + + // Resumable upload methods do not use the gRPC/REST service stub; the + // The ResumableUploadSession performs its own HTTP requests. +{%- for method in service.resumableUploads %} + this.innerApiCalls['{{ method.name.toCamelCase() }}'] = + this._gaxModule.createApiCall( + this._gaxModule.resumableUploadStub, + this._defaults['{{ method.name.toCamelCase() }}'], + this.descriptors.resumableUpload!['{{ method.name.toCamelCase() }}'], + this._opts.fallback + ); +{%- endfor %} +{%- endif %} {%- if service.options and service.options.deprecated %} this.warn('DEP${{service.name}}', '{{service.name}} is deprecated and may be removed in a future version.', 'DeprecationWarning'); {%- endif %} @@ -740,6 +784,78 @@ export class {{ service.name }}Client { {%- endif %} } {%- endfor %} +{%- if not api.legacyProtoLoad and service.resumableUploads.length > 0 %} + /** + * Creates a {@link gax.ResumableSource} backed by a local file. + * + * This delegates to google-gax so generated clients do not depend on the + * Node.js `fs` module at import time. The source can be passed as the + * `uploadSource` parameter of {@link gax.ResumableUploadSession#start}. + * + * @param {string} filePath - Path to the local file to upload. + * @returns {gax.ResumableSource} A seekable upload source for the file. + */ + getResumableSource(filePath: string): gax.ResumableSource { + const gaxModule = this._gaxModule as typeof gax; + return gaxModule.resumableSourceFromFile(filePath); + } +{%- endif %} +{%- for method in service.resumableUploads %} +{%- if not api.legacyProtoLoad %} +/** + {{- internalWarning(service, method, api) }} +{{- util.printCommentsForMethod(method) }} + {%- if method.options and method.options.deprecated %} + * @deprecated {{method.name}} is deprecated and may be removed in a future version. + {%- endif %} + * @param {object} [request] - The request object. + * @param {object} [options] - Optional parameters. The upload source, chunk + * size, progress callback, and resume URL are passed to + * {@link gax.ResumableUploadSession#start} instead. + * @returns {Promise} A resumable upload session. + * Call `.start(uploadParams)` with the source to upload, then await + * `.finished()` for the final RPC response. + */ + {{ method.name.toCamelCase() }}( + request?: {{ util.toInterface(method.inputInterface) }}, + options?: CallOptions): Promise { + request = request || {}; + options = options || {}; + if (!this._opts.fallback && this._opts.sslCreds) { + return Promise.reject( + new this._gaxModule.GoogleError( + 'Resumable upload methods require HTTP(S) authentication and ' + + 'cannot be used with gRPC channel credentials. Configure the ' + + 'client without `sslCreds`, or use `fallback: true`.', + ), + ); + } + this.{{ id.get("initialize") }}().catch(err => {throw err}); + {%- if method.options and method.options.deprecated %} + this.warn('DEP${{service.name}}-${{method.name}}','{{method.name}} is deprecated and may be removed in a future version.', 'DeprecationWarning'); + {%- endif %} + this._log.info('{{ method.name.toCamelCase() }} request %j', request); + return ( + this.innerApiCalls['{{ method.name.toCamelCase() }}'](request, { + ...options, + resumableUpload: { + auth: this._fallbackRest!.auth as gax.GoogleAuth, + servicePath: this._opts.servicePath ?? this._servicePath, + servicePort: this._opts.port || 443, + protocol: this._opts.protocol || 'https', + rpc: this._gaxModule.protobuf.Root.fromJSON(jsonProtos) + .lookupService('{{api.naming.protoPackage}}.{{ service.name }}') + .methods['{{ method.name }}'], + request, + uploadPrefix: '{{ method.resumableUpload.uploadPrefix }}', + numericEnums: this._opts.numericEnums, + minifyJson: this._opts.minifyJson, + }, + }) as Promise<[gax.ResumableUploadSession]> + ).then(([session]) => session); + } +{%- endif %} +{%- endfor %} {% for method in service.streaming %} {%- if method.serverStreaming and method.clientStreaming %} /** @@ -1187,4 +1303,4 @@ export class {{ service.name }}Client { } return Promise.resolve(); } -} \ No newline at end of file +} diff --git a/core/generator/gapic-generator-typescript/templates/cjs/typescript_samples/samples/generated/$version/$service.$method.js.njk b/core/generator/gapic-generator-typescript/templates/cjs/typescript_samples/samples/generated/$version/$service.$method.js.njk index 6d0301838663..81eedb26167c 100644 --- a/core/generator/gapic-generator-typescript/templates/cjs/typescript_samples/samples/generated/$version/$service.$method.js.njk +++ b/core/generator/gapic-generator-typescript/templates/cjs/typescript_samples/samples/generated/$version/$service.$method.js.njk @@ -94,6 +94,14 @@ function main({%- for comment in filteredComments -%} stream.write(request); stream.end(); } +{% elif method.resumableUpload %} + // Run request + const session = await {{ (api.naming.productName).toCamelCase() }}Client.{{ method.name.toCamelCase() }}(request); + const uploadSource = {{ (api.naming.productName).toCamelCase() }}Client.getResumableSource('/path/to/file'); + await session.start({uploadSource}); + const response = await session.finished(); + console.log(response); + } {% else %} // Run request const response = await {{ (api.naming.productName).toCamelCase() }}Client.{{ method.name.toCamelCase() }}(request); diff --git a/core/generator/gapic-generator-typescript/templates/esm/typescript_gapic/esm/src/$version/$service_client.ts.njk b/core/generator/gapic-generator-typescript/templates/esm/typescript_gapic/esm/src/$version/$service_client.ts.njk index e61b45594482..42e7fc1ffd23 100644 --- a/core/generator/gapic-generator-typescript/templates/esm/typescript_gapic/esm/src/$version/$service_client.ts.njk +++ b/core/generator/gapic-generator-typescript/templates/esm/typescript_gapic/esm/src/$version/$service_client.ts.njk @@ -85,6 +85,9 @@ export class {{ service.name }}Client { private _providedCustomServicePath: boolean; private _gaxModule: typeof gax{% if not api.legacyProtoLoad %} | typeof gax.fallback{% endif %}; private _gaxGrpc: gax.GrpcClient{% if not api.legacyProtoLoad %} | gax.fallback.GrpcClient{% endif %}; +{%- if not api.legacyProtoLoad and service.resumableUploads.length > 0 %} + private _fallbackRest?: gax.GrpcClient | gax.fallback.GrpcClient; +{%- endif %} private _protos: {}; private _defaults: {[method: string]: gax.CallSettings}; private _universeDomain: string; @@ -96,6 +99,9 @@ export class {{ service.name }}Client { stream: {}, longrunning: {}, batching: {}, +{%- if not api.legacyProtoLoad and service.resumableUploads.length > 0 %} + resumableUpload: {}, +{%- endif %} }; warn: (code: string, message: string, warnType?: string) => void; innerApiCalls: {[name: string]: Function}; @@ -209,6 +215,15 @@ export class {{ service.name }}Client { // Create a `gaxGrpc` object, with any grpc-specific options sent to the client. this._gaxGrpc = new this._gaxModule.GrpcClient(opts); +{%- if not api.legacyProtoLoad and service.resumableUploads.length > 0 %} + + // This service contains resumable upload methods, which are HTTPS-only. + // Make sure the REST transport is available even when the client was + // configured for gRPC. + this._fallbackRest = opts.fallback + ? this._gaxGrpc + : new gaxInstance.fallback.GrpcClient({...opts, fallback: true}); +{%- endif %} // Save options to use in {{ id.get("initialize") }}() method. this._opts = opts; @@ -410,6 +425,21 @@ export class {{ service.name }}Client { }; {%- endif %} +{%- if not api.legacyProtoLoad and service.resumableUploads.length > 0 %} + + // Some methods on this API support resumable uploads; provide + // descriptors for these methods. + this.descriptors.resumableUpload = { +{%- set resumableUploadJoiner = joiner() %} +{%- for method in service.resumableUploads %} + {{- resumableUploadJoiner() }} + {{ method.name.toCamelCase() }}: new this._gaxModule.ResumableUploadDescriptor( + '{{ method.resumableUpload.uploadPrefix }}' + ) +{%- endfor %} + }; +{%- endif %} + // Put together the default options sent with requests. this._defaults = this._gaxGrpc.constructSettings( '{{ api.naming.protoPackage }}.{{ service.name }}', gapicConfig as gax.ClientConfig, @@ -458,10 +488,10 @@ export class {{ service.name }}Client { (this._protos as any).{{api.naming.protoPackage}}.{{ service.name }}, this._opts, this._providedCustomServicePath) as Promise<{[method: string]: Function}>; - {%- set stubMethodsContent %} +{%- set stubMethodsContent %} {%- set stubMethodsJoiner = joiner(', ') -%} {%- for method in service.method -%} - {%- if not method.ignoreMapPagingMethod %} + {%- if not method.ignoreMapPagingMethod and not method.resumableUpload %} {{- stubMethodsJoiner() -}} '{{ method.name.toCamelCase(true) }}' {%- endif %} @@ -519,6 +549,20 @@ export class {{ service.name }}Client { this.innerApiCalls[methodName] = apiCall; } {%- endif -%} +{%- if not api.legacyProtoLoad and service.resumableUploads.length > 0 %} + + // Resumable upload methods do not use the gRPC/REST service stub; the + // The ResumableUploadSession performs its own HTTP requests. +{%- for method in service.resumableUploads %} + this.innerApiCalls['{{ method.name.toCamelCase() }}'] = + this._gaxModule.createApiCall( + this._gaxModule.resumableUploadStub, + this._defaults['{{ method.name.toCamelCase() }}'], + this.descriptors.resumableUpload!['{{ method.name.toCamelCase() }}'], + this._opts.fallback + ); +{%- endfor %} +{%- endif %} {%- if service.options and service.options.deprecated %} this.warn('DEP${{service.name}}', '{{service.name}} is deprecated and may be removed in a future version.', 'DeprecationWarning'); {%- endif %} @@ -751,6 +795,78 @@ export class {{ service.name }}Client { {%- endif %} } {%- endfor %} +{%- if not api.legacyProtoLoad and service.resumableUploads.length > 0 %} + /** + * Creates a {@link gax.ResumableSource} backed by a local file. + * + * This delegates to google-gax so generated clients do not depend on the + * Node.js `fs` module at import time. The source can be passed as the + * `uploadSource` parameter of {@link gax.ResumableUploadSession#start}. + * + * @param {string} filePath - Path to the local file to upload. + * @returns {gax.ResumableSource} A seekable upload source for the file. + */ + getResumableSource(filePath: string): gax.ResumableSource { + const gaxModule = this._gaxModule as typeof gax; + return gaxModule.resumableSourceFromFile(filePath); + } +{%- endif %} +{%- for method in service.resumableUploads %} +{%- if not api.legacyProtoLoad %} +/** + {{- internalWarning(service, method, api) }} +{{- util.printCommentsForMethod(method) }} + {%- if method.options and method.options.deprecated %} + * @deprecated {{method.name}} is deprecated and may be removed in a future version. + {%- endif %} + * @param {object} [request] - The request object. + * @param {object} [options] - Optional parameters. The upload source, chunk + * size, progress callback, and resume URL are passed to + * {@link gax.ResumableUploadSession#start} instead. + * @returns {Promise} A resumable upload session. + * Call `.start(uploadParams)` with the source to upload, then await + * `.finished()` for the final RPC response. + */ + {{ method.name.toCamelCase() }}( + request?: {{ util.toInterface(method.inputInterface) }}, + options?: CallOptions): Promise { + request = request || {}; + options = options || {}; + if (!this._opts.fallback && this._opts.sslCreds) { + return Promise.reject( + new this._gaxModule.GoogleError( + 'Resumable upload methods require HTTP(S) authentication and ' + + 'cannot be used with gRPC channel credentials. Configure the ' + + 'client without `sslCreds`, or use `fallback: true`.', + ), + ); + } + this.{{ id.get("initialize") }}().catch(err => {throw err}); + {%- if method.options and method.options.deprecated %} + this.warn('DEP${{service.name}}-${{method.name}}','{{method.name}} is deprecated and may be removed in a future version.', 'DeprecationWarning'); + {%- endif %} + this._log.info('{{ method.name.toCamelCase() }} request %j', request); + return ( + this.innerApiCalls['{{ method.name.toCamelCase() }}'](request, { + ...options, + resumableUpload: { + auth: this._fallbackRest!.auth as gax.GoogleAuth, + servicePath: this._opts.servicePath ?? this._servicePath, + servicePort: this._opts.port || 443, + protocol: this._opts.protocol || 'https', + rpc: this._gaxModule.protobuf.Root.fromJSON(jsonProtos) + .lookupService('{{api.naming.protoPackage}}.{{ service.name }}') + .methods['{{ method.name }}'], + request, + uploadPrefix: '{{ method.resumableUpload.uploadPrefix }}', + numericEnums: this._opts.numericEnums, + minifyJson: this._opts.minifyJson, + }, + }) as Promise<[gax.ResumableUploadSession]> + ).then(([session]) => session); + } +{%- endif %} +{%- endfor %} {% for method in service.streaming %} {%- if method.serverStreaming and method.clientStreaming %} /** @@ -1194,4 +1310,4 @@ export class {{ service.name }}Client { } return Promise.resolve(); } -} \ No newline at end of file +} diff --git a/core/generator/gapic-generator-typescript/templates/esm/typescript_samples/samples/generated/$version/$service.$method.js.njk b/core/generator/gapic-generator-typescript/templates/esm/typescript_samples/samples/generated/$version/$service.$method.js.njk index 6d0301838663..81eedb26167c 100644 --- a/core/generator/gapic-generator-typescript/templates/esm/typescript_samples/samples/generated/$version/$service.$method.js.njk +++ b/core/generator/gapic-generator-typescript/templates/esm/typescript_samples/samples/generated/$version/$service.$method.js.njk @@ -94,6 +94,14 @@ function main({%- for comment in filteredComments -%} stream.write(request); stream.end(); } +{% elif method.resumableUpload %} + // Run request + const session = await {{ (api.naming.productName).toCamelCase() }}Client.{{ method.name.toCamelCase() }}(request); + const uploadSource = {{ (api.naming.productName).toCamelCase() }}Client.getResumableSource('/path/to/file'); + await session.start({uploadSource}); + const response = await session.finished(); + console.log(response); + } {% else %} // Run request const response = await {{ (api.naming.productName).toCamelCase() }}Client.{{ method.name.toCamelCase() }}(request); diff --git a/core/generator/gapic-generator-typescript/test-fixtures/protos/google/samples/resumable/v1/resumable.proto b/core/generator/gapic-generator-typescript/test-fixtures/protos/google/samples/resumable/v1/resumable.proto new file mode 100644 index 000000000000..c5980d756f9d --- /dev/null +++ b/core/generator/gapic-generator-typescript/test-fixtures/protos/google/samples/resumable/v1/resumable.proto @@ -0,0 +1,72 @@ +// Copyright 2026 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. + +// Synthetic fixture used by the baseline tests to exercise resumable upload +// method generation via the resumable_upload_methods generator parameter. + +syntax = "proto3"; + +package google.samples.resumable.v1; + +import "google/api/annotations.proto"; +import "google/api/client.proto"; + +option csharp_namespace = "Google.Samples.Resumable.V1"; +option go_package = "google.golang.org/genproto/googleapis/samples/resumable/v1;resumable"; +option java_multiple_files = true; +option java_outer_classname = "ResumableProto"; +option java_package = "com.google.samples.resumable.v1"; +option php_namespace = "Google\\Samples\\Resumable\\V1"; +option ruby_package = "Google::Samples::Resumable::V1"; + +service ResumableUploadService { + option (google.api.default_host) = "resumable.googleapis.com"; + option (google.api.oauth_scopes) = + "https://www.googleapis.com/auth/cloud-platform"; + + // Creates a resumable upload session. + rpc CreateResumableUpload(CreateResumableUploadRequest) + returns (CreateResumableUploadResponse) { + option (google.api.http) = { + post: "/v1/uploads" + body: "*" + }; + } + + // Returns the status of a previously created upload session. + rpc GetUploadStatus(GetUploadStatusRequest) + returns (GetUploadStatusResponse) { + option (google.api.http) = { + get: "/v1/uploads/{upload_url}" + }; + } +} + +message CreateResumableUploadRequest { + string name = 1; + string description = 2; +} + +message CreateResumableUploadResponse { + string upload_url = 1; + string status = 2; +} + +message GetUploadStatusRequest { + string upload_url = 1; +} + +message GetUploadStatusResponse { + string status = 1; +} diff --git a/core/generator/gapic-generator-typescript/typescript/src/gapic-generator-typescript.ts b/core/generator/gapic-generator-typescript/typescript/src/gapic-generator-typescript.ts index 86f94791dc57..4f59b199fe1c 100755 --- a/core/generator/gapic-generator-typescript/typescript/src/gapic-generator-typescript.ts +++ b/core/generator/gapic-generator-typescript/typescript/src/gapic-generator-typescript.ts @@ -136,6 +136,12 @@ async function main(processArgv: string[]) { 'Override the list of mixins to use. Semicolon-separated list of API names to mixin, e.g. google.longrunning.Operations. Use "none" to disable all mixins.', ) .string('mixins') + .describe( + 'resumable_upload_methods', + 'Semicolon-separated list of ServiceName.MethodName pairs that should be treated as resumable upload methods, e.g. ResumableUploadService.CreateResumableUpload.', + ) + .string('resumable_upload_methods') + .alias('resumable_upload_methods', 'resumable-upload-methods') .describe('protoc', 'Path to protoc binary') .usage('Usage: $0 -I /path/to/googleapis') .usage(' --output_dir /path/to/output_directory') @@ -158,6 +164,9 @@ async function main(processArgv: string[]) { const legacyProtoLoad = argv.legacyProtoLoad as boolean | undefined; const restNumericEnums = argv.restNumericEnums as boolean | undefined; const mixins = argv.mixins as string | undefined; + const resumableUploadMethods = argv.resumableUploadMethods as + | string + | undefined; // --protoc can be taken from environment or from the command line let protocParameter = argv.protoc as string | string[] | undefined; @@ -247,6 +256,11 @@ async function main(processArgv: string[]) { if (mixins) { protocCommand.push(`--typescript_gapic_opt="mixins=${mixins}"`); } + if (resumableUploadMethods) { + protocCommand.push( + `--typescript_gapic_opt="resumable-upload-methods=${resumableUploadMethods}"`, + ); + } protocCommand.push(...protoDirsArg); protocCommand.push(...protoFiles); protocCommand.push(`-I${commonProtoPath}`); diff --git a/core/generator/gapic-generator-typescript/typescript/src/generator.ts b/core/generator/gapic-generator-typescript/typescript/src/generator.ts index b66849bed378..4f0c068d2a93 100644 --- a/core/generator/gapic-generator-typescript/typescript/src/generator.ts +++ b/core/generator/gapic-generator-typescript/typescript/src/generator.ts @@ -85,6 +85,7 @@ export class Generator { legacyProtoLoad?: boolean; restNumericEnums?: boolean; mixinsOverride?: string[]; + resumableUploadMethods?: string[]; format?: string | string[]; enableTelemetryTracing?: boolean; @@ -252,6 +253,15 @@ export class Generator { } } + private readResumableUploadMethods() { + if (this.paramMap['resumable-upload-methods']) { + this.resumableUploadMethods = this.paramMap['resumable-upload-methods'] + .split(';') + .map(name => name.trim()) + .filter(name => name.length > 0); + } + } + async initializeFromStdin() { const inputBuffer = await getStdin(); const CodeGeneratorRequest = this.root.lookupType('CodeGeneratorRequest'); @@ -282,6 +292,7 @@ export class Generator { this.readRestNumericEnums(); this.readFormat(); this.readEnableTelemetryTracing(); + this.readResumableUploadMethods(); } } @@ -343,6 +354,7 @@ export class Generator { restNumericEnums: this.restNumericEnums, mixinsOverridden: this.mixinsOverride !== undefined, enableTelemetryTracing: this.enableTelemetryTracing, + resumableUploadMethods: this.resumableUploadMethods, }); return api; } diff --git a/core/generator/gapic-generator-typescript/typescript/src/schema/api.ts b/core/generator/gapic-generator-typescript/typescript/src/schema/api.ts index 2e8c02aadc69..e424cc2c36f5 100644 --- a/core/generator/gapic-generator-typescript/typescript/src/schema/api.ts +++ b/core/generator/gapic-generator-typescript/typescript/src/schema/api.ts @@ -43,6 +43,7 @@ export class API { handwrittenLayer?: boolean; legacyProtoLoad: boolean; restNumericEnums: boolean; + resumableUploadMethods: string[]; documentationUri: any; newIssueUri: string; enableTelemetryTracing?: boolean; @@ -110,6 +111,7 @@ export class API { this.diregapic = options.diregapic ?? false; this.legacyProtoLoad = options.legacyProtoLoad ?? false; this.restNumericEnums = options.restNumericEnums ?? false; + this.resumableUploadMethods = options.resumableUploadMethods ?? []; this.documentationUri = options.serviceYaml?.publishing?.documentation_uri ?? ''; this.newIssueUri = options.serviceYaml?.publishing?.new_issue_uri ?? ''; diff --git a/core/generator/gapic-generator-typescript/typescript/src/schema/naming.ts b/core/generator/gapic-generator-typescript/typescript/src/schema/naming.ts index 49c90c9f1b63..06cc8a18c473 100644 --- a/core/generator/gapic-generator-typescript/typescript/src/schema/naming.ts +++ b/core/generator/gapic-generator-typescript/typescript/src/schema/naming.ts @@ -31,6 +31,7 @@ export interface Options { restNumericEnums?: boolean; mixinsOverridden?: boolean; enableTelemetryTracing?: boolean; + resumableUploadMethods?: string[]; } export class Naming { diff --git a/core/generator/gapic-generator-typescript/typescript/src/schema/proto.ts b/core/generator/gapic-generator-typescript/typescript/src/schema/proto.ts index 60c6673b67bd..04a89800c73f 100644 --- a/core/generator/gapic-generator-typescript/typescript/src/schema/proto.ts +++ b/core/generator/gapic-generator-typescript/typescript/src/schema/proto.ts @@ -79,6 +79,9 @@ export interface MethodDescriptorProto bundleConfig?: BundleConfig; toJSON: Function | undefined; isDiregapicLRO?: boolean; + // If set, the method opts into the resumable upload protocol + // via the resumable_upload_methods generator parameter. + resumableUpload?: {uploadPrefix: string} | undefined; // if wrappers are allowed and there is a maxResultsParamter, return true maxResultsParameter?: boolean; } @@ -138,6 +141,7 @@ export interface ServiceDescriptorProto longRunningOperationsMixinFlags?: OperationsMixinConfig; protoFile: string; diregapicLRO?: MethodDescriptorProto[]; + resumableUploads: MethodDescriptorProto[]; httpRules?: protos.google.api.IHttpRule[]; selectiveGapic: SelectiveGapicConfig; } @@ -303,6 +307,24 @@ function streaming(method: MethodDescriptorProto) { return undefined; } +// Methods selected with the resumable_upload_methods generator parameter use +// the default upload prefix, since the parameter only identifies methods. +const DEFAULT_RESUMABLE_UPLOAD_PREFIX = '/resumable/upload'; + +function resumableUploadMethodNames( + serviceName: string, + resumableUploadMethods: string[] | undefined, +): Set { + const servicePrefix = `${serviceName}.`; + const methodNames = new Set(); + for (const methodName of resumableUploadMethods ?? []) { + if (methodName.startsWith(servicePrefix)) { + methodNames.add(methodName.substring(servicePrefix.length)); + } + } + return methodNames; +} + // returns true if the method has wrappers for UInt32Value enabled // and is a paginated call with a maxResults parameter instead of pageSize // as of its creation, this should only be true for BigQuery @@ -1031,9 +1053,13 @@ export function augmentService(parameters: AugmentServiceParameters) { augmentedService.bundleConfigs = parameters.options.bundleConfigs?.filter( bc => bc.serviceName === parameters.service.name, ); + const resumableUploadMethods = resumableUploadMethodNames( + parameters.service.name!, + parameters.options.resumableUploadMethods, + ); augmentedService.method = - augmentedService.method?.map(method => - augmentMethod( + augmentedService.method?.map(method => { + const augmentedMethod = augmentMethod( { allMessages: parameters.allMessages, localMessages: parameters.localMessages, @@ -1041,8 +1067,14 @@ export function augmentService(parameters: AugmentServiceParameters) { diregapic: parameters.options.diregapic, }, method, - ), - ) ?? []; + ); + if (resumableUploadMethods.has(augmentedMethod.name!)) { + augmentedMethod.resumableUpload = { + uploadPrefix: DEFAULT_RESUMABLE_UPLOAD_PREFIX, + }; + } + return augmentedMethod; + }) ?? []; /* Selective GAPIC method handling. */ augmentedService.method = augmentedService.method.filter( @@ -1076,7 +1108,13 @@ export function augmentService(parameters: AugmentServiceParameters) { ); augmentedService.simpleMethods = augmentedService.method.filter( method => - !method.longRunning && !method.streaming && !method.pagingFieldName, + !method.longRunning && + !method.streaming && + !method.pagingFieldName && + !method.resumableUpload, + ); + augmentedService.resumableUploads = augmentedService.method.filter( + method => method.resumableUpload, ); augmentedService.longRunning = augmentedService.method.filter( method => method.longRunning, diff --git a/core/generator/gapic-generator-typescript/typescript/test/unit/api.ts b/core/generator/gapic-generator-typescript/typescript/test/unit/api.ts index 0fddfb7f3e71..e0d67fe1e97a 100644 --- a/core/generator/gapic-generator-typescript/typescript/test/unit/api.ts +++ b/core/generator/gapic-generator-typescript/typescript/test/unit/api.ts @@ -36,6 +36,40 @@ describe('src/schema/api.ts', () => { ]); }); + it('should expose resumable upload methods passed as options', () => { + const fd = {} as protos.google.protobuf.FileDescriptorProto; + fd.name = 'google/cloud/test/v1/test.proto'; + fd.package = 'google.cloud.test.v1'; + fd.service = [{} as protos.google.protobuf.ServiceDescriptorProto]; + fd.service[0].name = 'ZService'; + fd.service[0].options = { + '.google.api.defaultHost': 'hostname.example.com:443', + }; + const api = new API([fd], 'google.cloud.test.v1', { + grpcServiceConfig: {} as protos.grpc.service_config.ServiceConfig, + resumableUploadMethods: ['ZService.Upload', 'ZService.Resume'], + }); + assert.deepStrictEqual(api.resumableUploadMethods, [ + 'ZService.Upload', + 'ZService.Resume', + ]); + }); + + it('should default resumable upload methods to an empty array', () => { + const fd = {} as protos.google.protobuf.FileDescriptorProto; + fd.name = 'google/cloud/test/v1/test.proto'; + fd.package = 'google.cloud.test.v1'; + fd.service = [{} as protos.google.protobuf.ServiceDescriptorProto]; + fd.service[0].name = 'ZService'; + fd.service[0].options = { + '.google.api.defaultHost': 'hostname.example.com:443', + }; + const api = new API([fd], 'google.cloud.test.v1', { + grpcServiceConfig: {} as protos.grpc.service_config.ServiceConfig, + }); + assert.deepStrictEqual(api.resumableUploadMethods, []); + }); + it('should correctly derive a valid logging name', () => { const fd = {} as protos.google.protobuf.FileDescriptorProto; fd.name = 'google/cloud/test/v1/test.proto'; diff --git a/core/generator/gapic-generator-typescript/typescript/test/unit/baselines-esm.ts b/core/generator/gapic-generator-typescript/typescript/test/unit/baselines-esm.ts index 229527063d73..e2288775ddae 100644 --- a/core/generator/gapic-generator-typescript/typescript/test/unit/baselines-esm.ts +++ b/core/generator/gapic-generator-typescript/typescript/test/unit/baselines-esm.ts @@ -32,6 +32,14 @@ describe('Baseline tests: ESM', () => { useCommonProto: true, format: 'esm', }); + runBaselineTest({ + baselineName: 'resumable-upload-esm', + outputDir: '.test-out-resumable-upload-esm', + protoPath: 'google/samples/resumable/v1/resumable.proto', + useCommonProto: false, + format: 'esm', + resumableUploadMethods: 'ResumableUploadService.CreateResumableUpload', + }); runBaselineTest({ baselineName: 'dlp-esm', outputDir: '.test-out-dlp-esm', diff --git a/core/generator/gapic-generator-typescript/typescript/test/unit/baselines.ts b/core/generator/gapic-generator-typescript/typescript/test/unit/baselines.ts index d1638762b508..b172d72596c4 100644 --- a/core/generator/gapic-generator-typescript/typescript/test/unit/baselines.ts +++ b/core/generator/gapic-generator-typescript/typescript/test/unit/baselines.ts @@ -30,6 +30,13 @@ describe('Baseline tests', () => { 'google/duplicatemethodstest/v1/duplicate_methods_test_v1.yaml', useCommonProto: true, }); + runBaselineTest({ + baselineName: 'resumable-upload', + outputDir: '.test-out-resumable-upload', + protoPath: 'google/samples/resumable/v1/resumable.proto', + useCommonProto: false, + resumableUploadMethods: 'ResumableUploadService.CreateResumableUpload', + }); runBaselineTest({ baselineName: 'dlp', outputDir: '.test-out-dlp', diff --git a/core/generator/gapic-generator-typescript/typescript/test/unit/proto.ts b/core/generator/gapic-generator-typescript/typescript/test/unit/proto.ts index fd1427cbe458..ab17a984aae0 100644 --- a/core/generator/gapic-generator-typescript/typescript/test/unit/proto.ts +++ b/core/generator/gapic-generator-typescript/typescript/test/unit/proto.ts @@ -1794,3 +1794,96 @@ describe('src/schema/proto.ts', () => { }); }); }); + +describe('src/schema/proto.ts - resumable upload methods', () => { + function augmentResumableTestService(resumableUploadMethods?: string[]) { + const fd = {} as protos.google.protobuf.FileDescriptorProto; + fd.package = 'google.samples.resumable.v1'; + fd.service = [{} as protos.google.protobuf.ServiceDescriptorProto]; + fd.service[0].name = 'ResumableUploadService'; + fd.service[0].method = [ + { + name: 'CreateResumableUpload', + inputType: '.google.samples.resumable.v1.CreateResumableUploadRequest', + outputType: + '.google.samples.resumable.v1.CreateResumableUploadResponse', + }, + { + name: 'GetUploadStatus', + inputType: '.google.samples.resumable.v1.GetUploadStatusRequest', + outputType: '.google.samples.resumable.v1.GetUploadStatusResponse', + }, + ] as protos.google.protobuf.MethodDescriptorProto[]; + + const options: Options = { + grpcServiceConfig: {} as protos.grpc.service_config.ServiceConfig, + resumableUploadMethods, + }; + return augmentService({ + allMessages: {}, + localMessages: {}, + packageName: 'google.samples.resumable.v1', + service: fd.service[0], + commentsMap: new CommentsMap([fd]), + allResourceDatabase: new ResourceDatabase(), + resourceDatabase: new ResourceDatabase(), + options, + protoFile: 'fd', + }); + } + + it('marks methods listed in resumableUploadMethods', () => { + const augmentedService = augmentResumableTestService([ + 'OtherService.DoNotEnable', + 'ResumableUploadService.CreateResumableUpload', + ]); + + assert.strictEqual(augmentedService.resumableUploads.length, 1); + assert.deepStrictEqual( + augmentedService.resumableUploads[0].resumableUpload, + {uploadPrefix: '/resumable/upload'}, + ); + assert.strictEqual( + augmentedService.method.find(m => m.name === 'GetUploadStatus') + ?.resumableUpload, + undefined, + ); + assert.strictEqual( + augmentedService.simpleMethods.some( + m => m.name === 'CreateResumableUpload', + ), + false, + ); + assert.strictEqual( + augmentedService.simpleMethods.some(m => m.name === 'GetUploadStatus'), + true, + ); + }); + + it('does not enable methods that are not listed for the service', () => { + const augmentedService = augmentResumableTestService([ + 'ResumableUploadService.CreateResumableUpload', + ]); + + assert.strictEqual( + augmentedService.method.find(m => m.name === 'GetUploadStatus') + ?.resumableUpload, + undefined, + ); + assert.strictEqual( + augmentedService.simpleMethods.some(m => m.name === 'GetUploadStatus'), + true, + ); + }); + + it('does not enable resumable uploads when the option is omitted', () => { + const augmentedService = augmentResumableTestService(); + + assert.strictEqual(augmentedService.resumableUploads.length, 0); + assert.strictEqual( + augmentedService.method.find(m => m.name === 'CreateResumableUpload') + ?.resumableUpload, + undefined, + ); + }); +}); diff --git a/core/generator/gapic-generator-typescript/typescript/test/util.ts b/core/generator/gapic-generator-typescript/typescript/test/util.ts index 617a44463c02..698dba12a4bf 100644 --- a/core/generator/gapic-generator-typescript/typescript/test/util.ts +++ b/core/generator/gapic-generator-typescript/typescript/test/util.ts @@ -46,6 +46,7 @@ export interface BaselineOptions { diregapic?: boolean; restNumericEnums?: boolean; mixins?: string; + resumableUploadMethods?: string; format?: string; } @@ -147,6 +148,9 @@ export function runBaselineTest(options: BaselineOptions) { if (options.mixins) { commandLine += ` --mixins="${options.mixins}"`; } + if (options.resumableUploadMethods) { + commandLine += ` --resumable_upload_methods="${options.resumableUploadMethods}"`; + } execSync(commandLine); assert(equalToBaseline(outputDir, baselineDir)); });