From d7e9de953a3086d64dcf84cb40aad7b33107d48d Mon Sep 17 00:00:00 2001 From: feywind <57276408+feywind@users.noreply.github.com> Date: Thu, 10 Sep 2026 15:16:32 -0400 Subject: [PATCH 1/3] feat(generator): select resumable upload methods Adds the `resumable_upload_methods` generator parameter: a semicolon-separated list of `Service.Method` pairs. Selected methods are augmented with a resumable upload descriptor, removed from the simple methods list, and exposed on the service as `resumableUploads`, which the templates use to generate the resumable upload clients. Plumbs the parameter through the CLI, the bazel rule and the naming options, and refreshes the pubsub api dump baselines for the new `resumableUploads` field. --- .../pubsub-api-dump-esm/api.json.baseline | 3 + .../pubsub-api-dump/api.json.baseline | 3 + .../typescript_gapic.bzl | 3 + .../src/gapic-generator-typescript.ts | 14 +++ .../typescript/src/generator.ts | 12 +++ .../typescript/src/schema/api.ts | 2 + .../typescript/src/schema/naming.ts | 1 + .../typescript/src/schema/proto.ts | 48 +++++++++- .../typescript/test/unit/api.ts | 34 +++++++ .../typescript/test/unit/proto.ts | 93 +++++++++++++++++++ 10 files changed, 208 insertions(+), 5 deletions(-) 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/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/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/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, + ); + }); +}); From d81f6da8c28d67bd90aaa9679be905bf8e5efff3 Mon Sep 17 00:00:00 2001 From: feywind <57276408+feywind@users.noreply.github.com> Date: Thu, 10 Sep 2026 15:19:18 -0400 Subject: [PATCH 2/3] feat(generator): generate resumable upload client methods Templates for the cjs and esm flavors now turn methods selected with `resumable_upload_methods` into resumable upload methods: - expose a ResumableUploadDescriptor for each selected method and wire the gax resumableUploadStub into the client's inner API calls - keep the methods out of the service stub used for unary/paging calls - add the getResumableSource() helper and the session-returning method, which require an HTTP(S) transport and reject gRPC channel credentials - generate sample snippets for resumable upload methods Adds a synthetic resumable.proto fixture, the resumable-upload and resumable-upload-esm baseline tests, and baselines generated with the current templates. --- .../.OwlBot.yaml.baseline | 19 + .../.babelrc.json.baseline | 19 + .../resumable-upload-esm/.gitignore.baseline | 15 + .../resumable-upload-esm/.jsdoc.cjs.baseline | 54 ++ .../resumable-upload-esm/.nycrc.baseline | 24 + .../resumable-upload-esm/README.md.baseline | 108 +++ .../esm/src/index.ts.baseline | 26 + .../esm/src/json-helper.cjs.baseline | 20 + .../esm/src/v1/index.ts.baseline | 19 + ...esumable_upload_service_client.ts.baseline | 623 ++++++++++++++++++ ...upload_service_client_config.json.baseline | 34 + ...le_upload_service_proto_list.json.baseline | 3 + .../fixtures/sample/src/index.cjs.baseline | 27 + .../fixtures/sample/src/index.js.baseline | 27 + .../fixtures/sample/src/index.ts.baseline | 35 + .../esm/system-test/install.ts.baseline | 60 ++ ...ic_resumable_upload_service_v1.ts.baseline | 419 ++++++++++++ .../resumable-upload-esm/package.json | 108 +++ .../package.json.baseline | 1 + .../resumable/v1/resumable.proto.baseline | 72 ++ ...ervice.create_resumable_upload.js.baseline | 66 ++ ...load_service.get_upload_status.js.baseline | 59 ++ .../tsconfig.esm.json.baseline | 27 + .../tsconfig.json.baseline | 32 + .../webpack.config.cjs.baseline | 64 ++ .../resumable-upload/.OwlBot.yaml.baseline | 19 + .../resumable-upload/.gitignore.baseline | 14 + .../resumable-upload/.jsdoc.js.baseline | 55 ++ .../resumable-upload/.nycrc.baseline | 24 + .../resumable-upload/README.md.baseline | 108 +++ .../baselines/resumable-upload/package.json | 64 ++ .../resumable-upload/package.json.baseline | 1 + .../resumable/v1/resumable.proto.baseline | 72 ++ ...ervice.create_resumable_upload.js.baseline | 66 ++ ...load_service.get_upload_status.js.baseline | 59 ++ .../resumable-upload/src/index.ts.baseline | 25 + .../resumable-upload/src/v1/index.ts.baseline | 19 + ...esumable_upload_service_client.ts.baseline | 601 +++++++++++++++++ ...upload_service_client_config.json.baseline | 34 + ...le_upload_service_proto_list.json.baseline | 3 + .../fixtures/sample/src/index.js.baseline | 27 + .../fixtures/sample/src/index.ts.baseline | 34 + .../system-test/install.ts.baseline | 51 ++ ...ic_resumable_upload_service_v1.ts.baseline | 408 ++++++++++++ .../resumable-upload/tsconfig.json.baseline | 22 + .../webpack.config.js.baseline | 64 ++ .../src/$version/$service_client.ts.njk | 122 +++- .../$version/$service.$method.js.njk | 11 +- .../esm/src/$version/$service_client.ts.njk | 122 +++- .../$version/$service.$method.js.njk | 11 +- .../samples/resumable/v1/resumable.proto | 72 ++ .../typescript/test/unit/baselines-esm.ts | 8 + .../typescript/test/unit/baselines.ts | 7 + .../typescript/test/util.ts | 4 + 54 files changed, 4080 insertions(+), 8 deletions(-) create mode 100644 core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/.OwlBot.yaml.baseline create mode 100644 core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/.babelrc.json.baseline create mode 100644 core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/.gitignore.baseline create mode 100644 core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/.jsdoc.cjs.baseline create mode 100644 core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/.nycrc.baseline create mode 100644 core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/README.md.baseline create mode 100644 core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/esm/src/index.ts.baseline create mode 100644 core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/esm/src/json-helper.cjs.baseline create mode 100644 core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/esm/src/v1/index.ts.baseline create mode 100644 core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/esm/src/v1/resumable_upload_service_client.ts.baseline create mode 100644 core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/esm/src/v1/resumable_upload_service_client_config.json.baseline create mode 100644 core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/esm/src/v1/resumable_upload_service_proto_list.json.baseline create mode 100644 core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/esm/system-test/fixtures/sample/src/index.cjs.baseline create mode 100644 core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/esm/system-test/fixtures/sample/src/index.js.baseline create mode 100644 core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/esm/system-test/fixtures/sample/src/index.ts.baseline create mode 100644 core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/esm/system-test/install.ts.baseline create mode 100644 core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/esm/test/gapic_resumable_upload_service_v1.ts.baseline create mode 100644 core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/package.json create mode 120000 core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/package.json.baseline create mode 100644 core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/protos/google/samples/resumable/v1/resumable.proto.baseline create mode 100644 core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/samples/generated/v1/resumable_upload_service.create_resumable_upload.js.baseline create mode 100644 core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/samples/generated/v1/resumable_upload_service.get_upload_status.js.baseline create mode 100644 core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/tsconfig.esm.json.baseline create mode 100644 core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/tsconfig.json.baseline create mode 100644 core/generator/gapic-generator-typescript/baselines/resumable-upload-esm/webpack.config.cjs.baseline create mode 100644 core/generator/gapic-generator-typescript/baselines/resumable-upload/.OwlBot.yaml.baseline create mode 100644 core/generator/gapic-generator-typescript/baselines/resumable-upload/.gitignore.baseline create mode 100644 core/generator/gapic-generator-typescript/baselines/resumable-upload/.jsdoc.js.baseline create mode 100644 core/generator/gapic-generator-typescript/baselines/resumable-upload/.nycrc.baseline create mode 100644 core/generator/gapic-generator-typescript/baselines/resumable-upload/README.md.baseline create mode 100644 core/generator/gapic-generator-typescript/baselines/resumable-upload/package.json create mode 120000 core/generator/gapic-generator-typescript/baselines/resumable-upload/package.json.baseline create mode 100644 core/generator/gapic-generator-typescript/baselines/resumable-upload/protos/google/samples/resumable/v1/resumable.proto.baseline create mode 100644 core/generator/gapic-generator-typescript/baselines/resumable-upload/samples/generated/v1/resumable_upload_service.create_resumable_upload.js.baseline create mode 100644 core/generator/gapic-generator-typescript/baselines/resumable-upload/samples/generated/v1/resumable_upload_service.get_upload_status.js.baseline create mode 100644 core/generator/gapic-generator-typescript/baselines/resumable-upload/src/index.ts.baseline create mode 100644 core/generator/gapic-generator-typescript/baselines/resumable-upload/src/v1/index.ts.baseline create mode 100644 core/generator/gapic-generator-typescript/baselines/resumable-upload/src/v1/resumable_upload_service_client.ts.baseline create mode 100644 core/generator/gapic-generator-typescript/baselines/resumable-upload/src/v1/resumable_upload_service_client_config.json.baseline create mode 100644 core/generator/gapic-generator-typescript/baselines/resumable-upload/src/v1/resumable_upload_service_proto_list.json.baseline create mode 100644 core/generator/gapic-generator-typescript/baselines/resumable-upload/system-test/fixtures/sample/src/index.js.baseline create mode 100644 core/generator/gapic-generator-typescript/baselines/resumable-upload/system-test/fixtures/sample/src/index.ts.baseline create mode 100644 core/generator/gapic-generator-typescript/baselines/resumable-upload/system-test/install.ts.baseline create mode 100644 core/generator/gapic-generator-typescript/baselines/resumable-upload/test/gapic_resumable_upload_service_v1.ts.baseline create mode 100644 core/generator/gapic-generator-typescript/baselines/resumable-upload/tsconfig.json.baseline create mode 100644 core/generator/gapic-generator-typescript/baselines/resumable-upload/webpack.config.js.baseline create mode 100644 core/generator/gapic-generator-typescript/test-fixtures/protos/google/samples/resumable/v1/resumable.proto 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..f8f01c010da4 --- /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,66 @@ +// 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..f8f01c010da4 --- /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,66 @@ +// 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/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..5ef0c1278baa 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 @@ -41,7 +41,8 @@ function main({%- for comment in filteredComments -%} {{ "\n" }} // Imports the {{ api.naming.productName }} library const { {{- service.name.toPascalCase() }}Client} = require('{{ api.publishName }}').{{ api.naming.version }}; - +{% if method.resumableUpload %} +{% endif %} // Instantiates a client const {{ (api.naming.productName).toCamelCase() }}Client = new {{ service.name.toPascalCase() }}Client(); @@ -94,6 +95,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..5ef0c1278baa 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 @@ -41,7 +41,8 @@ function main({%- for comment in filteredComments -%} {{ "\n" }} // Imports the {{ api.naming.productName }} library const { {{- service.name.toPascalCase() }}Client} = require('{{ api.publishName }}').{{ api.naming.version }}; - +{% if method.resumableUpload %} +{% endif %} // Instantiates a client const {{ (api.naming.productName).toCamelCase() }}Client = new {{ service.name.toPascalCase() }}Client(); @@ -94,6 +95,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/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/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)); }); From 96d292b046be382dab0dbb09e76471b435ce9b96 Mon Sep 17 00:00:00 2001 From: feywind <57276408+feywind@users.noreply.github.com> Date: Thu, 10 Sep 2026 18:55:05 -0400 Subject: [PATCH 3/3] fix(generator): drop the empty conditional in the sample template `{% if method.resumableUpload %}` had an empty body, so its only effect was an extra newline: resumable upload samples rendered two blank lines after the require, where every other generated sample has one. Remove the block and regenerate the affected create_resumable_upload baselines for both the cjs and esm variants. Found by the Gemini review on #9285. --- ...esumable_upload_service.create_resumable_upload.js.baseline | 1 - ...esumable_upload_service.create_resumable_upload.js.baseline | 1 - .../samples/generated/$version/$service.$method.js.njk | 3 +-- .../samples/generated/$version/$service.$method.js.njk | 3 +-- 4 files changed, 2 insertions(+), 6 deletions(-) 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 index f8f01c010da4..67188e48e219 100644 --- 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 @@ -38,7 +38,6 @@ function main() { // Imports the Resumable library const {ResumableUploadServiceClient} = require('resumable').v1; - // Instantiates a client const resumableClient = new ResumableUploadServiceClient(); 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 index f8f01c010da4..67188e48e219 100644 --- 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 @@ -38,7 +38,6 @@ function main() { // Imports the Resumable library const {ResumableUploadServiceClient} = require('resumable').v1; - // Instantiates a client const resumableClient = new ResumableUploadServiceClient(); 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 5ef0c1278baa..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 @@ -41,8 +41,7 @@ function main({%- for comment in filteredComments -%} {{ "\n" }} // Imports the {{ api.naming.productName }} library const { {{- service.name.toPascalCase() }}Client} = require('{{ api.publishName }}').{{ api.naming.version }}; -{% if method.resumableUpload %} -{% endif %} + // Instantiates a client const {{ (api.naming.productName).toCamelCase() }}Client = new {{ service.name.toPascalCase() }}Client(); 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 5ef0c1278baa..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 @@ -41,8 +41,7 @@ function main({%- for comment in filteredComments -%} {{ "\n" }} // Imports the {{ api.naming.productName }} library const { {{- service.name.toPascalCase() }}Client} = require('{{ api.publishName }}').{{ api.naming.version }}; -{% if method.resumableUpload %} -{% endif %} + // Instantiates a client const {{ (api.naming.productName).toCamelCase() }}Client = new {{ service.name.toPascalCase() }}Client();