UNPKG

ts-proto

Version:

[![npm](https://img.shields.io/npm/v/ts-proto)](https://www.npmjs.com/package/ts-proto) [![build](https://github.com/stephenh/ts-proto/workflows/Build/badge.svg)](https://github.com/stephenh/ts-proto/actions)

983 lines (976 loc) 134 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.contextTypeVar = void 0; exports.generateFile = generateFile; exports.makeUtils = makeUtils; const ts_poet_1 = require("ts-poet"); const ConditionalOutput_1 = require("ts-poet/build/ConditionalOutput"); const ts_proto_descriptors_1 = require("ts-proto-descriptors"); const case_1 = require("./case"); const enums_1 = require("./enums"); const generate_async_iterable_1 = require("./generate-async-iterable"); const generate_generic_service_definition_1 = require("./generate-generic-service-definition"); const generate_grpc_js_1 = require("./generate-grpc-js"); const generate_grpc_web_1 = require("./generate-grpc-web"); const generate_nestjs_1 = require("./generate-nestjs"); const generate_nice_grpc_1 = require("./generate-nice-grpc"); const generate_services_1 = require("./generate-services"); const generate_struct_wrappers_1 = require("./generate-struct-wrappers"); const options_1 = require("./options"); const schema_1 = require("./schema"); const sourceInfo_1 = require("./sourceInfo"); const types_1 = require("./types"); const utils_1 = require("./utils"); const visit_1 = require("./visit"); function generateFile(ctx, fileDesc) { const { options, utils } = ctx; if (options.useOptionals === false) { console.warn("ts-proto: Passing useOptionals as a boolean option is deprecated and will be removed in a future version. Please pass the string 'none' instead of false."); options.useOptionals = "none"; } else if (options.useOptionals === true) { console.warn("ts-proto: Passing useOptionals as a boolean option is deprecated and will be removed in a future version. Please pass the string 'messages' instead of true."); options.useOptionals = "messages"; } // Google's protofiles are organized like Java, where package == the folder the file // is in, and file == a specific service within the package. I.e. you can have multiple // company/foo.proto and company/bar.proto files, where package would be 'company'. // // We'll match that structure by setting up the module path as: // // company/foo.proto --> company/foo.ts // company/bar.proto --> company/bar.ts // // We'll also assume that the fileDesc.name is already the `company/foo.proto` path, with // the package already implicitly in it, so we won't re-append/strip/etc. it out/back in. const suffix = `${options.fileSuffix}.ts`; const moduleName = fileDesc.name.replace(".proto", suffix); const chunks = []; // Indicate this file's source protobuf package for reflective use with google.protobuf.Any if (options.exportCommonSymbols) { chunks.push((0, ts_poet_1.code) `export const protobufPackage = '${fileDesc.package}';`); } // Syntax, unlike most fields, is not repeated and thus does not use an index const sourceInfo = sourceInfo_1.default.fromDescriptor(fileDesc); const headerComment = sourceInfo.lookup(sourceInfo_1.Fields.file.syntax, undefined); (0, utils_1.maybeAddComment)(options, headerComment, chunks, fileDesc.options?.deprecated); // Apply formatting to methods here, so they propagate globally for (let svc of fileDesc.service) { for (let i = 0; i < svc.method.length; i++) { svc.method[i] = new utils_1.FormattedMethodDescriptor(svc.method[i], options); } } // first make all the type declarations (0, visit_1.visit)(fileDesc, sourceInfo, (fullName, message, sInfo, fullProtoTypeName) => { chunks.push(generateInterfaceDeclaration(ctx, fullName, message, sInfo, (0, utils_1.maybePrefixPackage)(fileDesc, fullProtoTypeName))); }, options, (fullName, enumDesc, sInfo) => { chunks.push((0, enums_1.generateEnum)(ctx, fullName, enumDesc, sInfo)); }); // If nestJs=true export [package]_PACKAGE_NAME and [service]_SERVICE_NAME const if (options.nestJs) { if (options.exportCommonSymbols) { const prefix = (0, case_1.camelToSnake)(fileDesc.package.replace(/\./g, "_")); chunks.push((0, ts_poet_1.code) `export const ${prefix}_PACKAGE_NAME = '${fileDesc.package}';`); } if (options.useDate === options_1.DateOption.DATE && fileDesc.messageType.find((message) => message.field.find((field) => field.typeName === ".google.protobuf.Timestamp"))) { chunks.push(makeProtobufTimestampWrapper()); } } // We add `nestJs` here because enough though it doesn't use our encode/decode methods // for most/vanilla messages, we do generate static wrap/unwrap methods for the special // Struct/Value/wrapper types and use the `wrappers[...]` to have NestJS know about them. if (options.outputEncodeMethods || options.outputJsonMethods || options.outputTypeAnnotations || options.outputTypeRegistry || options.nestJs) { // then add the encoder/decoder/base instance (0, visit_1.visit)(fileDesc, sourceInfo, (fullName, message, _sInfo, fullProtoTypeName) => { const fullTypeName = (0, utils_1.maybePrefixPackage)(fileDesc, fullProtoTypeName); const outputWrapAndUnwrap = (0, generate_struct_wrappers_1.isWrapperType)(fullTypeName); // Only decode, fromPartial, and wrap use the createBase method if ((options.outputEncodeMethods && options.outputEncodeMethods !== "encode-no-creation" && options.outputEncodeMethods !== "encode-only" && (options.outputDecodeIncludeTypes === "" || new RegExp(options.outputDecodeIncludeTypes).test(fullTypeName))) || options.outputPartialMethods || outputWrapAndUnwrap) { chunks.push(generateBaseInstanceFactory(ctx, fullName, message, fullTypeName)); } const staticMembers = []; const hasTypeMember = options.outputTypeAnnotations || options.outputTypeRegistry; if (hasTypeMember) { staticMembers.push((0, ts_poet_1.code) `$type: '${fullTypeName}' as const`); } if (options.outputExtensions) { for (const extension of message.extension) { const { name, type, extensionInfo } = generateExtension(ctx, message, extension); staticMembers.push((0, ts_poet_1.code) `${name}: <${ctx.utils.Extension}<${type}>> ${extensionInfo}`); } } if (options.outputEncodeMethods) { if (options.outputEncodeMethods === true || options.outputEncodeMethods === "encode-only" || options.outputEncodeMethods === "encode-no-creation") { if (options.outputEncodeIncludeTypes === "" || new RegExp(options.outputEncodeIncludeTypes).test(fullTypeName)) { staticMembers.push(generateEncode(ctx, fullName, message)); if (options.outputExtensions && options.unknownFields && message.extensionRange.length) { staticMembers.push(generateSetExtension(ctx, fullName)); } } else if (options.outputEncodeMethods === true) { staticMembers.push(generateEmptyEncode(fullName)); } } if (options.outputEncodeMethods === true || options.outputEncodeMethods === "decode-only") { if (options.outputDecodeIncludeTypes === "" || new RegExp(options.outputDecodeIncludeTypes).test(fullTypeName)) { staticMembers.push(generateDecode(ctx, fullName, message)); if (options.outputExtensions && options.unknownFields && message.extensionRange.length) { staticMembers.push(generateGetExtension(ctx, fullName)); } } else if (options.outputEncodeMethods === true) { staticMembers.push(generateEmptyDecode(fullName)); } } } if (options.useAsyncIterable) { staticMembers.push((0, generate_async_iterable_1.generateEncodeTransform)(ctx.utils, fullName)); staticMembers.push((0, generate_async_iterable_1.generateDecodeTransform)(ctx.utils, fullName)); } if (options.outputJsonMethods) { if (options.outputJsonMethods === true || options.outputJsonMethods === "from-only") { staticMembers.push(generateFromJson(ctx, fullName, fullTypeName, message)); } if (options.outputJsonMethods === true || options.outputJsonMethods === "to-only") { staticMembers.push(generateToJson(ctx, fullName, fullTypeName, message)); } } if (options.outputPartialMethods) { staticMembers.push(generateFromPartial(ctx, fullName, message)); } const structFieldNames = { nullValue: (0, case_1.maybeSnakeToCamel)("null_value", ctx.options), numberValue: (0, case_1.maybeSnakeToCamel)("number_value", ctx.options), stringValue: (0, case_1.maybeSnakeToCamel)("string_value", ctx.options), boolValue: (0, case_1.maybeSnakeToCamel)("bool_value", ctx.options), structValue: (0, case_1.maybeSnakeToCamel)("struct_value", ctx.options), listValue: (0, case_1.maybeSnakeToCamel)("list_value", ctx.options), }; if (options.nestJs) { staticMembers.push(...(0, generate_struct_wrappers_1.generateWrapDeep)(ctx, fullTypeName, structFieldNames)); staticMembers.push(...(0, generate_struct_wrappers_1.generateUnwrapDeep)(ctx, fullTypeName, structFieldNames)); } else { staticMembers.push(...(0, generate_struct_wrappers_1.generateWrapShallow)(ctx, fullTypeName, structFieldNames)); staticMembers.push(...(0, generate_struct_wrappers_1.generateUnwrapShallow)(ctx, fullTypeName, structFieldNames)); } if (staticMembers.length > 0) { const messageFnsTypeParameters = [fullName, hasTypeMember && `'${fullTypeName}'`] .filter((p) => !!p) .join(", "); const interfaces = [(0, ts_poet_1.code) `${utils.MessageFns}<${messageFnsTypeParameters}>`]; if (options.outputEncodeMethods && options.outputExtensions && options.unknownFields && message.extensionRange.length) { interfaces.push((0, ts_poet_1.code) `${utils.ExtensionFns}<${fullName}>`); } if ((0, types_1.isStructTypeName)(fullTypeName)) { interfaces.push((0, ts_poet_1.code) `${utils.StructWrapperFns}`); } else if ((0, types_1.isAnyValueTypeName)(fullTypeName)) { interfaces.push((0, ts_poet_1.code) `${utils.AnyValueWrapperFns}`); } else if ((0, types_1.isListValueTypeName)(fullTypeName)) { interfaces.push((0, ts_poet_1.code) `${utils.ListValueWrapperFns}`); } else if ((0, types_1.isFieldMaskTypeName)(fullTypeName)) { interfaces.push((0, ts_poet_1.code) `${utils.FieldMaskWrapperFns}`); } if (options.outputExtensions) { for (const extension of message.extension) { const name = (0, case_1.maybeSnakeToCamel)(extension.name, ctx.options); const type = (0, types_1.toTypeName)(ctx, message, extension); interfaces.push((0, ts_poet_1.code) `${utils.ExtensionHolder}<"${name}", ${type}>`); } } chunks.push((0, ts_poet_1.code) ` export const ${(0, ts_poet_1.def)(fullName)}: ${(0, ts_poet_1.joinCode)(interfaces, { on: " & " })} = { ${(0, ts_poet_1.joinCode)(staticMembers, { on: ",\n\n" })} }; `); } if (options.outputTypeRegistry) { const messageTypeRegistry = (0, utils_1.impFile)(options, "messageTypeRegistry@./typeRegistry"); chunks.push((0, ts_poet_1.code) ` ${messageTypeRegistry}.set(${fullName}.$type, ${fullName}); `); } }, options); } if (options.outputExtensions) { for (const extension of fileDesc.extension) { const { name, type, extensionInfo } = generateExtension(ctx, undefined, extension); chunks.push((0, ts_poet_1.code) `export const ${name}: ${ctx.utils.Extension}<${type}> = ${extensionInfo};`); } } if (options.nestJs) { if (fileDesc.messageType.find((message) => message.field.find(types_1.isStructType))) { chunks.push(makeProtobufStructWrapper(options)); } } let hasServerStreamingMethods = false; let hasStreamingMethods = false; (0, visit_1.visitServices)(fileDesc, sourceInfo, (serviceDesc, sInfo) => { if (options.nestJs) { // NestJS is sufficiently different that we special case the client/server interfaces // generate nestjs grpc client interface chunks.push((0, generate_nestjs_1.generateNestjsServiceClient)(ctx, fileDesc, sInfo, serviceDesc)); // and the service controller interface chunks.push((0, generate_nestjs_1.generateNestjsServiceController)(ctx, fileDesc, sInfo, serviceDesc)); // generate nestjs grpc service controller decorator chunks.push((0, generate_nestjs_1.generateNestjsGrpcServiceMethodsDecorator)(ctx, serviceDesc)); let serviceConstName = `${(0, case_1.camelToSnake)(serviceDesc.name)}_NAME`; if (!serviceDesc.name.toLowerCase().endsWith("service")) { serviceConstName = `${(0, case_1.camelToSnake)(serviceDesc.name)}_SERVICE_NAME`; } chunks.push((0, ts_poet_1.code) `export const ${serviceConstName} = "${serviceDesc.name}";`); } const uniqueServices = [...new Set(options.outputServices)].sort(); uniqueServices.forEach((outputService) => { if (outputService === options_1.ServiceOption.GRPC) { chunks.push((0, generate_grpc_js_1.generateGrpcJsService)(ctx, fileDesc, sInfo, serviceDesc)); } else if (outputService === options_1.ServiceOption.NICE_GRPC) { chunks.push((0, generate_nice_grpc_1.generateNiceGrpcService)(ctx, fileDesc, sInfo, serviceDesc)); } else if (outputService === options_1.ServiceOption.GENERIC) { chunks.push((0, generate_generic_service_definition_1.generateGenericServiceDefinition)(ctx, fileDesc, sInfo, serviceDesc)); } else if (outputService === options_1.ServiceOption.DEFAULT) { // This service could be Twirp or grpc-web or JSON (maybe). So far all of their // interfaces are fairly similar so we share the same service interface. chunks.push((0, generate_services_1.generateService)(ctx, fileDesc, sInfo, serviceDesc)); if (options.outputClientImpl === true) { chunks.push((0, generate_services_1.generateServiceClientImpl)(ctx, fileDesc, serviceDesc)); } else if (options.outputClientImpl === "grpc-web") { chunks.push((0, generate_grpc_web_1.generateGrpcClientImpl)(ctx, fileDesc, serviceDesc)); chunks.push((0, generate_grpc_web_1.generateGrpcServiceDesc)(fileDesc, serviceDesc)); serviceDesc.method.forEach((method) => { if (!method.clientStreaming) { chunks.push((0, generate_grpc_web_1.generateGrpcMethodDesc)(ctx, serviceDesc, method)); } if (method.serverStreaming) { hasServerStreamingMethods = true; } }); } } }); serviceDesc.method.forEach((methodDesc, _index) => { if (methodDesc.serverStreaming || methodDesc.clientStreaming) { hasStreamingMethods = true; } }); }); if (options.outputServices.includes(options_1.ServiceOption.DEFAULT) && options.outputClientImpl && fileDesc.service.length > 0) { if (options.outputClientImpl === true) { chunks.push((0, generate_services_1.generateRpcType)(ctx, hasStreamingMethods)); } else if (options.outputClientImpl === "grpc-web") { chunks.push((0, generate_grpc_web_1.addGrpcWebMisc)(ctx, hasServerStreamingMethods)); } } if (options.context) { chunks.push((0, generate_services_1.generateDataLoaderOptionsType)()); chunks.push((0, generate_services_1.generateDataLoadersType)()); } if (options.outputSchema) { chunks.push(...(0, schema_1.generateSchema)(ctx, fileDesc, sourceInfo)); } // https://www.typescriptlang.org/docs/handbook/2/modules.html: // > In TypeScript, just as in ECMAScript 2015, any file containing a top-level import or export is considered a module. // > Conversely, a file without any top-level import or export declarations is treated as a script whose contents are available in the global scope (and therefore to modules as well). // // Thus, to mark an empty file a module, we need to add `export {}` to it. if (options.esModuleInterop && chunks.length === 0) { chunks.push((0, ts_poet_1.code) `export {};`); } chunks.push(...Object.values(utils).map((v) => { if (v instanceof ConditionalOutput_1.ConditionalOutput) { return (0, ts_poet_1.code) `${v.ifUsed}`; } else { return (0, ts_poet_1.code) ``; } })); // Finally, reset method definitions to their original state (unformatted) // This is mainly so that the `meta-typings` tests pass for (let svc of fileDesc.service) { for (let i = 0; i < svc.method.length; i++) { const methodInfo = svc.method[i]; (0, utils_1.assertInstanceOf)(methodInfo, utils_1.FormattedMethodDescriptor); svc.method[i] = methodInfo.getSource(); } } return [moduleName, (0, ts_poet_1.joinCode)(chunks, { on: "\n\n" })]; } /** These are runtime utility methods used by the generated code. */ function makeUtils(options) { const bytes = makeByteUtils(options); const longs = makeLongUtils(options, bytes); const deepPartial = makeDeepPartial(options, longs); const extension = makeExtensionClass(options); return { ...bytes, ...deepPartial, ...makeObjectIdMethods(), ...makeTimestampMethods(options, longs, bytes), ...longs, ...makeComparisonUtils(), ...makeNiceGrpcServerStreamingMethodResult(options), ...makeGrpcWebErrorClass(bytes), ...extension, ...makeAssertionUtils(bytes), ...makeMessageFns(options, deepPartial, extension), }; } function makeProtobufTimestampWrapper() { const wrappers = (0, ts_poet_1.imp)("wrappers@protobufjs"); return (0, ts_poet_1.code) ` ${wrappers}['.google.protobuf.Timestamp'] = { fromObject(value: Date) { return { seconds: value.getTime() / 1000, nanos: (value.getTime() % 1000) * 1e6, }; }, toObject(message: { seconds: number; nanos: number }) { return new Date(message.seconds * 1000 + message.nanos / 1e6); }, } as any;`; } function makeProtobufStructWrapper(options) { const wrappers = (0, ts_poet_1.imp)("wrappers@protobufjs"); const Struct = (0, utils_1.impProto)(options, "google/protobuf/struct", (0, utils_1.wrapTypeName)(options, "Struct")); return (0, ts_poet_1.code) ` ${wrappers}['.google.protobuf.Struct'] = { fromObject: ${Struct}.wrap, toObject: ${Struct}.unwrap, } as any;`; } function makeLongUtils(options, bytes) { const Long = (0, ts_poet_1.imp)("Long=long"); const numberToLong = (0, ts_poet_1.conditionalOutput)("numberToLong", (0, ts_poet_1.code) ` function numberToLong(number: number) { return ${Long}.fromNumber(number); } `); const longToNumber = (0, ts_poet_1.conditionalOutput)("longToNumber", (0, ts_poet_1.code) ` function longToNumber(int64: { toString(): string }): number { const num = ${bytes.globalThis}.Number(int64.toString()); if (num > ${bytes.globalThis}.Number.MAX_SAFE_INTEGER) { throw new ${bytes.globalThis}.Error("Value is larger than Number.MAX_SAFE_INTEGER") } if (num < ${bytes.globalThis}.Number.MIN_SAFE_INTEGER) { throw new ${bytes.globalThis}.Error("Value is smaller than Number.MIN_SAFE_INTEGER") } return num; } `); return { numberToLong, longToNumber, Long }; } function makeByteUtils(options) { const globalThisPolyfill = (0, ts_poet_1.conditionalOutput)("gt", (0, ts_poet_1.code) ` declare const self: any | undefined; declare const window: any | undefined; declare const global: any | undefined; const gt: any = (() => { if (typeof globalThis !== "undefined") return globalThis; if (typeof self !== "undefined") return self; if (typeof window !== "undefined") return window; if (typeof global !== "undefined") return global; throw "Unable to locate global object"; })(); `); const globalThis = options.globalThisPolyfill ? globalThisPolyfill : (0, ts_poet_1.conditionalOutput)("globalThis", (0, ts_poet_1.code) ``); function getBytesFromBase64Snippet() { const bytesFromBase64NodeSnippet = (0, ts_poet_1.code) ` return Uint8Array.from(${globalThis}.Buffer.from(b64, 'base64')); `; const bytesFromBase64BrowserSnippet = (0, ts_poet_1.code) ` const bin = ${globalThis}.atob(b64); const arr = new Uint8Array(bin.length); for (let i = 0; i < bin.length; ++i) { arr[i] = bin.charCodeAt(i); } return arr; `; switch (options.env) { case options_1.EnvOption.NODE: return bytesFromBase64NodeSnippet; case options_1.EnvOption.BROWSER: return bytesFromBase64BrowserSnippet; default: return (0, ts_poet_1.code) ` if ((${globalThis} as any).Buffer) { return Uint8Array.from((${globalThis} as any).Buffer.from(b64, 'base64')); } else { ${bytesFromBase64BrowserSnippet} } `; } } const bytesFromBase64 = (0, ts_poet_1.conditionalOutput)("bytesFromBase64", (0, ts_poet_1.code) ` function bytesFromBase64(b64: string): Uint8Array { ${getBytesFromBase64Snippet()} } `); function getBase64FromBytesSnippet() { const base64FromBytesNodeSnippet = (0, ts_poet_1.code) ` return ${globalThis}.Buffer.from(arr).toString('base64'); `; const base64FromBytesBrowserSnippet = (0, ts_poet_1.code) ` const bin: string[] = []; arr.forEach((byte) => { bin.push(${globalThis}.String.fromCharCode(byte)); }); return ${globalThis}.btoa(bin.join('')); `; switch (options.env) { case options_1.EnvOption.NODE: return base64FromBytesNodeSnippet; case options_1.EnvOption.BROWSER: return base64FromBytesBrowserSnippet; default: return (0, ts_poet_1.code) ` if ((${globalThis} as any).Buffer) { return (${globalThis} as any).Buffer.from(arr).toString('base64'); } else { ${base64FromBytesBrowserSnippet} } `; } } const base64FromBytes = (0, ts_poet_1.conditionalOutput)("base64FromBytes", (0, ts_poet_1.code) ` function base64FromBytes(arr: Uint8Array): string { ${getBase64FromBytesSnippet()} } `); return { globalThis, bytesFromBase64, base64FromBytes }; } function makeDeepPartial(options, longs) { let oneofCase = ""; if (options.oneof === options_1.OneofOption.UNIONS) { oneofCase = ` : T extends { ${maybeReadonly(options)}$case: string } ? { [K in keyof Omit<T, '$case'>]?: DeepPartial<T[K]> } & { ${maybeReadonly(options)}$case: T['$case'] } `; } else if (options.oneof === options_1.OneofOption.UNIONS_VALUE) { oneofCase = ` : T extends { ${maybeReadonly(options)}$case: string; value: unknown; } ? { ${maybeReadonly(options)}$case: T['$case']; value?: DeepPartial<T['value']>; } `; } const maybeExport = options.exportCommonSymbols ? "export" : ""; // Allow passing longs as numbers or strings, nad we'll convert them const maybeLong = options.forceLong === options_1.LongOption.LONG ? (0, ts_poet_1.code) ` : T extends ${longs.Long} ? string | number | Long ` : ""; const optionalBuiltins = []; if (options.forceLong === options_1.LongOption.BIGINT) { optionalBuiltins.push("bigint"); } if (options.useDate === options_1.DateOption.TEMPORAL) { optionalBuiltins.push("Temporal.Instant"); } const Builtin = (0, ts_poet_1.conditionalOutput)("Builtin", (0, ts_poet_1.code) `type Builtin = Date | Function | Uint8Array | string | number | boolean ${optionalBuiltins.length ? `| ${optionalBuiltins.join(" | ")} ` : ""}| undefined;`); // Based on https://github.com/sindresorhus/type-fest/pull/259 const maybeExcludeType = (0, options_1.addTypeToMessages)(options) ? `| '$type'` : ""; const Exact = (0, ts_poet_1.conditionalOutput)("Exact", (0, ts_poet_1.code) ` type KeysOfUnion<T> = T extends T ? keyof T : never; ${maybeExport} type Exact<P, I extends P> = P extends ${Builtin} ? P : P & { [K in keyof P]: Exact<P[K], I[K]> } & { [K in Exclude<keyof I, KeysOfUnion<P> ${maybeExcludeType}>]: never }; `); // Based on the type from ts-essentials const keys = (0, options_1.addTypeToMessages)(options) ? (0, ts_poet_1.code) `Exclude<keyof T, '$type'>` : (0, ts_poet_1.code) `keyof T`; const DeepPartial = (0, ts_poet_1.conditionalOutput)("DeepPartial", (0, ts_poet_1.code) ` ${maybeExport} type DeepPartial<T> = T extends ${Builtin} ? T ${maybeLong} : T extends globalThis.Array<infer U> ? globalThis.Array<DeepPartial<U>> : T extends ReadonlyArray<infer U> ? ReadonlyArray<DeepPartial<U>>${oneofCase} : T extends {} ? { [K in ${keys}]?: DeepPartial<T[K]> } : Partial<T>; `); return { Builtin, DeepPartial, Exact }; } function makeMessageFns(options, deepPartial, extension) { const BinaryWriter = (0, ts_poet_1.imp)("t:BinaryWriter@@bufbuild/protobuf/wire"); const BinaryReader = (0, ts_poet_1.imp)("t:BinaryReader@@bufbuild/protobuf/wire"); const { Exact, DeepPartial } = deepPartial; const { Extension } = extension; const commonStaticMembers = []; const extensionStaticMembers = []; const hasTypeMember = options.outputTypeAnnotations || options.outputTypeRegistry; if (hasTypeMember) { commonStaticMembers.push((0, ts_poet_1.code) `readonly $type: V;`); } if (options.outputEncodeMethods) { if (options.outputEncodeMethods === true || options.outputEncodeMethods === "encode-only" || options.outputEncodeMethods === "encode-no-creation") { commonStaticMembers.push((0, ts_poet_1.code) ` encode(message: T, writer?: ${BinaryWriter}): ${BinaryWriter}; `); if (options.outputExtensions && options.unknownFields) { extensionStaticMembers.push((0, ts_poet_1.code) ` setExtension<E>(message: T, extension: ${Extension}<E>, value: E): void; `); } } if (options.outputEncodeMethods === true || options.outputEncodeMethods === "decode-only") { commonStaticMembers.push((0, ts_poet_1.code) ` decode(input: ${BinaryReader} | Uint8Array, length?: number): T; `); if (options.outputExtensions && options.unknownFields) { extensionStaticMembers.push((0, ts_poet_1.code) ` getExtension<E>(message: T, extension: ${Extension}<E>): E | undefined; `); } } } if (options.useAsyncIterable) { commonStaticMembers.push((0, ts_poet_1.code) ` encodeTransform( source: AsyncIterable<T | T[]> | Iterable<T | T[]> ): AsyncIterable<Uint8Array>; `); commonStaticMembers.push((0, ts_poet_1.code) ` decodeTransform( source: AsyncIterable<Uint8Array | Uint8Array[]> | Iterable<Uint8Array | Uint8Array[]> ): AsyncIterable<T>; `); } if (options.outputJsonMethods) { if (options.outputJsonMethods === true || options.outputJsonMethods === "from-only") { commonStaticMembers.push((0, ts_poet_1.code) `fromJSON(object: any): T;`); } if (options.outputJsonMethods === true || options.outputJsonMethods === "to-only") { commonStaticMembers.push((0, ts_poet_1.code) `toJSON(message: T): unknown;`); } } if (options.outputPartialMethods) { if (options.useExactTypes) { commonStaticMembers.push((0, ts_poet_1.code) `create<I extends ${Exact}<${DeepPartial}<T>, I>>(base?: I): T;`); commonStaticMembers.push((0, ts_poet_1.code) `fromPartial<I extends ${Exact}<${DeepPartial}<T>, I>>(object: I): T;`); } else { commonStaticMembers.push((0, ts_poet_1.code) `create(base?: DeepPartial<T>): T;`); commonStaticMembers.push((0, ts_poet_1.code) `fromPartial(object: DeepPartial<T>): T;`); } } const maybeExport = options.exportCommonSymbols ? "export" : ""; const MessageFns = (0, ts_poet_1.conditionalOutput)("MessageFns", (0, ts_poet_1.code) ` ${maybeExport} interface MessageFns<T${hasTypeMember ? ", V extends string" : ""}> { ${(0, ts_poet_1.joinCode)(commonStaticMembers, { on: "\n" })} } `); const ExtensionFns = (0, ts_poet_1.conditionalOutput)("ExtensionFns", (0, ts_poet_1.code) ` ${maybeExport} interface ExtensionFns<T> { ${(0, ts_poet_1.joinCode)(extensionStaticMembers, { on: "\n" })} } `); const ExtensionHolder = (0, ts_poet_1.conditionalOutput)("ExtensionHolder", (0, ts_poet_1.code) ` ${maybeExport} type ExtensionHolder<T extends string, V> = { [key in T]: Extension<V>; } `); const StructWrapperFns = (0, ts_poet_1.conditionalOutput)("StructWrapperFns", (0, ts_poet_1.code) ` ${maybeExport} interface StructWrapperFns { wrap(object: {[key: string]: any} | undefined): ${(0, utils_1.wrapTypeName)(options, "Struct")}; unwrap(message: ${(0, utils_1.wrapTypeName)(options, "Struct")}): {[key: string]: any}; } `); const AnyValueWrapperFns = (0, ts_poet_1.conditionalOutput)("AnyValueWrapperFns", (0, ts_poet_1.code) ` ${maybeExport} interface AnyValueWrapperFns { wrap(value: any): ${(0, utils_1.wrapTypeName)(options, "Value")}; unwrap(message: any): string | number | boolean | Object | null | Array<any> | undefined; } `); const ListValueWrapperFns = (0, ts_poet_1.conditionalOutput)("ListValueWrapperFns", (0, ts_poet_1.code) ` ${maybeExport} interface ListValueWrapperFns { wrap(array: ${options.useReadonlyTypes ? "Readonly" : ""}Array<any> | undefined): ${(0, utils_1.wrapTypeName)(options, "ListValue")}; unwrap(message: ${options.useReadonlyTypes ? "any" : (0, utils_1.wrapTypeName)(options, "ListValue")}): Array<any>; } `); const FieldMaskWrapperFns = (0, ts_poet_1.conditionalOutput)("FieldMaskWrapperFns", (0, ts_poet_1.code) ` ${maybeExport} interface FieldMaskWrapperFns { wrap(paths: ${options.useReadonlyTypes ? "readonly" : ""} string[]): ${(0, utils_1.wrapTypeName)(options, "FieldMask")}; unwrap(message: ${options.useReadonlyTypes ? "any" : (0, utils_1.wrapTypeName)(options, "FieldMask")}): string[] ${options.useOptionals === "all" ? "| undefined" : ""}; } `); return { MessageFns, ExtensionFns, ExtensionHolder, StructWrapperFns, AnyValueWrapperFns, ListValueWrapperFns, FieldMaskWrapperFns, }; } function makeObjectIdMethods() { const mongodb = (0, ts_poet_1.imp)("mongodb*mongodb"); const fromProtoObjectId = (0, ts_poet_1.conditionalOutput)("fromProtoObjectId", (0, ts_poet_1.code) ` function fromProtoObjectId(oid: ObjectId): ${mongodb}.ObjectId { return new ${mongodb}.ObjectId(oid.value); } `); const fromJsonObjectId = (0, ts_poet_1.conditionalOutput)("fromJsonObjectId", (0, ts_poet_1.code) ` function fromJsonObjectId(o: any): ${mongodb}.ObjectId { if (o instanceof ${mongodb}.ObjectId) { return o; } else if (typeof o === "string") { return new ${mongodb}.ObjectId(o); } else { return ${fromProtoObjectId}(ObjectId.fromJSON(o)); } } `); const toProtoObjectId = (0, ts_poet_1.conditionalOutput)("toProtoObjectId", (0, ts_poet_1.code) ` function toProtoObjectId(oid: ${mongodb}.ObjectId): ObjectId { const value = oid.toString(); return { value }; } `); return { fromJsonObjectId, fromProtoObjectId, toProtoObjectId }; } function makeTimestampMethods(options, longs, bytes) { const Timestamp = (0, utils_1.impProto)(options, "google/protobuf/timestamp", (0, utils_1.wrapTypeName)(options, "Timestamp")); const NanoDate = (0, ts_poet_1.imp)("NanoDate=nano-date"); let seconds = "Math.trunc(date.getTime() / 1_000)"; let toNumberCode = "t.seconds"; const makeToNumberCode = (methodCall) => `t.seconds${options.useOptionals === "all" || options.noDefaultsForOptionals ? "?" : ""}.${methodCall}`; if (options.forceLong === options_1.LongOption.LONG) { toNumberCode = makeToNumberCode("toNumber()"); seconds = (0, ts_poet_1.code) `${longs.numberToLong}(${seconds})`; } else if (options.forceLong === options_1.LongOption.BIGINT) { toNumberCode = (0, ts_poet_1.code) `${bytes.globalThis}.Number(${makeToNumberCode("toString()")})`; seconds = (0, ts_poet_1.code) `BigInt(${seconds})`; } else if (options.forceLong === options_1.LongOption.STRING) { toNumberCode = (0, ts_poet_1.code) `${bytes.globalThis}.Number(t.seconds)`; seconds = (0, ts_poet_1.code) `${seconds}.toString()`; } const maybeTypeField = (0, options_1.addTypeToMessages)(options) ? `$type: 'google.protobuf.Timestamp',` : ""; const toTimestamp = (0, ts_poet_1.conditionalOutput)("toTimestamp", options.useDate === options_1.DateOption.STRING ? (0, ts_poet_1.code) ` function toTimestamp(dateStr: string): ${Timestamp} { const date = new ${bytes.globalThis}.Date(dateStr); const seconds = ${seconds}; const nanos = (date.getTime() % 1_000) * 1_000_000; return { ${maybeTypeField} seconds, nanos }; } ` : options.useDate === options_1.DateOption.STRING_NANO ? (0, ts_poet_1.code) ` function toTimestamp(dateStr: string): ${Timestamp} { const nanoDate = new ${NanoDate}(dateStr); const date = { getTime: (): number => nanoDate.valueOf(), } as const; const seconds = ${seconds}; let nanos = nanoDate.getMilliseconds() * 1_000_000; nanos += nanoDate.getMicroseconds() * 1_000; nanos += nanoDate.getNanoseconds(); return { ${maybeTypeField} seconds, nanos }; } ` : options.useDate === options_1.DateOption.TEMPORAL ? (0, ts_poet_1.code) ` function toTimestamp(instant: Temporal.Instant): ${Timestamp} { const date = { getTime: (): number => instant.epochMilliseconds, } as const; const seconds = ${seconds}; const remainder = instant.round({ smallestUnit: "seconds", roundingMode: "floor" }).until(instant); const nanos = (remainder.milliseconds * 1_000_000) + (remainder.microseconds * 1_000) + remainder.nanoseconds; return { ${maybeTypeField} seconds, nanos }; } ` : (0, ts_poet_1.code) ` function toTimestamp(date: Date): ${Timestamp} { const seconds = ${seconds}; const nanos = (date.getTime() % 1_000) * 1_000_000; return { ${maybeTypeField} seconds, nanos }; } `); const fromTimestamp = (0, ts_poet_1.conditionalOutput)("fromTimestamp", options.useDate === options_1.DateOption.STRING ? (0, ts_poet_1.code) ` function fromTimestamp(t: ${Timestamp}): string { let millis = (${toNumberCode} || 0) * 1_000; millis += (t.nanos || 0) / 1_000_000; return new ${bytes.globalThis}.Date(millis).toISOString(); } ` : options.useDate === options_1.DateOption.STRING_NANO ? (0, ts_poet_1.code) ` function fromTimestamp(t: ${Timestamp}): string { const seconds = ${toNumberCode} || 0; const nanos = (t.nanos || 0) % 1_000; const micros = Math.trunc(((t.nanos || 0) % 1_000_000) / 1_000) let millis = seconds * 1_000; millis += Math.trunc((t.nanos || 0) / 1_000_000); const nanoDate = new ${NanoDate}(millis); nanoDate.setMicroseconds(micros); nanoDate.setNanoseconds(nanos); return nanoDate.toISOStringFull(); } ` : options.useDate === options_1.DateOption.TEMPORAL ? (0, ts_poet_1.code) ` function fromTimestamp(t: ${Timestamp}): Temporal.Instant { const seconds = ${toNumberCode} || 0; return ${bytes.globalThis}.Temporal.Instant .fromEpochMilliseconds(seconds * 1_000) .add(${bytes.globalThis}.Temporal.Duration.from({ nanoseconds: t.nanos })); } ` : (0, ts_poet_1.code) ` function fromTimestamp(t: ${Timestamp}): Date { let millis = (${toNumberCode} || 0) * 1_000; millis += (t.nanos || 0) / 1_000_000; return new ${bytes.globalThis}.Date(millis); } `); const fromJsonTimestamp = (0, ts_poet_1.conditionalOutput)("fromJsonTimestamp", options.useDate === options_1.DateOption.DATE ? (0, ts_poet_1.code) ` function fromJsonTimestamp(o: any): Date { if (o instanceof ${bytes.globalThis}.Date) { return o; } else if (typeof o === "string") { return new ${bytes.globalThis}.Date(o); } else { return ${fromTimestamp}(${(0, utils_1.wrapTypeName)(options, "Timestamp")}.fromJSON(o)); } } ` : options.useDate === options_1.DateOption.TEMPORAL ? (0, ts_poet_1.code) ` function fromJsonTimestamp(o: any): Temporal.Instant { if (o instanceof ${bytes.globalThis}.Date) { return ${bytes.globalThis}.Temporal.Instant.fromEpochMilliseconds(o.getTime()); } else if (typeof o === "string") { return ${bytes.globalThis}.Temporal.Instant.from(o); } else { return ${fromTimestamp}(${(0, utils_1.wrapTypeName)(options, "Timestamp")}.fromJSON(o)); } } ` : (0, ts_poet_1.code) ` function fromJsonTimestamp(o: any): ${(0, utils_1.wrapTypeName)(options, "Timestamp")} { if (o instanceof ${bytes.globalThis}.Date) { return ${toTimestamp}(o); } else if (typeof o === "string") { return ${toTimestamp}(new ${bytes.globalThis}.Date(o)); } else { return ${options.typePrefix}Timestamp${options.typeSuffix}.fromJSON(o); } } `); return { toTimestamp, fromTimestamp, fromJsonTimestamp }; } function makeComparisonUtils() { const isObject = (0, ts_poet_1.conditionalOutput)("isObject", (0, ts_poet_1.code) ` function isObject(value: any): boolean { return typeof value === 'object' && value !== null; }`); const isSet = (0, ts_poet_1.conditionalOutput)("isSet", (0, ts_poet_1.code) ` function isSet(value: any): boolean { return value !== null && value !== undefined; }`); return { isObject, isSet }; } function makeNiceGrpcServerStreamingMethodResult(options) { const NiceGrpcServerStreamingMethodResult = (0, ts_poet_1.conditionalOutput)("ServerStreamingMethodResult", options.outputIndex ? (0, ts_poet_1.code) ` type ServerStreamingMethodResult<Response> = { [Symbol.asyncIterator](): AsyncIterator<Response, void>; }; ` : (0, ts_poet_1.code) ` export type ServerStreamingMethodResult<Response> = { [Symbol.asyncIterator](): AsyncIterator<Response, void>; }; `); return { NiceGrpcServerStreamingMethodResult }; } function makeGrpcWebErrorClass(bytes) { const GrpcWebError = (0, ts_poet_1.conditionalOutput)("GrpcWebError", (0, ts_poet_1.code) ` export class GrpcWebError extends ${bytes.globalThis}.Error { constructor(message: string, public code: grpc.Code, public metadata: grpc.Metadata) { super(message); } } `); return { GrpcWebError }; } function makeExtensionClass(options) { const Extension = (0, ts_poet_1.conditionalOutput)("Extension", (0, ts_poet_1.code) ` export interface Extension <T> { number: number; tag: number; singularTag?: number; packedTag?: number; encode?: (message: T) => Uint8Array[]; decode?: (tag: number, input: Uint8Array[]) => T; repeated: boolean; packed: boolean; } `); return { Extension }; } function makeAssertionUtils(bytes) { const fail = (0, ts_poet_1.conditionalOutput)("fail", (0, ts_poet_1.code) ` function fail(message?: string): never { throw new ${bytes.globalThis}.Error(message ?? "Failed"); } `); return { fail }; } // Create the interface with properties function generateInterfaceDeclaration(ctx, fullName, messageDesc, sourceInfo, fullTypeName) { const { options, currentFile } = ctx; const chunks = []; (0, utils_1.maybeAddComment)(options, sourceInfo, chunks, messageDesc.options?.deprecated); // interface name should be defined to avoid import collisions chunks.push((0, ts_poet_1.code) `export interface ${(0, ts_poet_1.def)(fullName)} {`); if ((0, options_1.addTypeToMessages)(options)) { chunks.push((0, ts_poet_1.code) `$type${options.outputTypeAnnotations === "optional" ? "?" : ""}: '${fullTypeName}',`); } // When oneof=unions, we generate a single property with an ADT per `oneof` clause. const processedOneofs = new Set(); messageDesc.field.forEach((fieldDesc, index) => { if ((0, types_1.isWithinOneOfThatShouldBeUnion)(options, fieldDesc)) { const { oneofIndex } = fieldDesc; if (!processedOneofs.has(oneofIndex)) { processedOneofs.add(oneofIndex); chunks.push(generateOneofProperty(ctx, messageDesc, oneofIndex, sourceInfo)); } return; } const info = sourceInfo.lookup(sourceInfo_1.Fields.message.field, index); (0, utils_1.maybeAddComment)(options, info, chunks, fieldDesc.options?.deprecated); const fieldKey = (0, utils_1.safeAccessor)((0, utils_1.getFieldName)(fieldDesc, options)); const isOptional = (0, types_1.isOptionalProperty)(fieldDesc, messageDesc.options, options, currentFile.isProto3Syntax); const type = (0, types_1.toTypeName)(ctx, messageDesc, fieldDesc, isOptional); chunks.push((0, ts_poet_1.code) `${maybeReadonly(options)}${fieldKey}${isOptional ? "?" : ""}: ${type}, `); }); if (ctx.options.unknownFields) { chunks.push((0, ts_poet_1.code) `_unknownFields?: {[key: number]: Uint8Array[]} | undefined,`); } chunks.push((0, ts_poet_1.code) `}`); return (0, ts_poet_1.joinCode)(chunks, { on: "\n" }); } function generateOneofProperty(ctx, messageDesc, oneofIndex, sourceInfo) { const { options } = ctx; const fields = messageDesc.field .map((field, index) => ({ index, field })) .filter((item) => (0, types_1.isWithinOneOf)(item.field) && item.field.oneofIndex === oneofIndex); const mbReadonly = maybeReadonly(options); const info = sourceInfo.lookup(sourceInfo_1.Fields.message.oneof_decl, oneofIndex); let outerComments = []; (0, utils_1.maybeAddComment)(options, info, outerComments); const unionType = (0, ts_poet_1.joinCode)(fields.flatMap((f) => { const fieldInfo = sourceInfo.lookup(sourceInfo_1.Fields.message.field, f.index); let fieldName = (0, case_1.maybeSnakeToCamel)(f.field.name, options); let typeName = (0, types_1.toTypeName)(ctx, messageDesc, f.field); let valueName = (0, utils_1.oneofValueName)(fieldName, options); let fieldComments = []; (0, utils_1.maybeAddComment)(options, fieldInfo, fieldComments); const combinedComments = fieldComments.join("\n"); return (0, ts_poet_1.code) `|${combinedComments ? " // " : ""}\n ${combinedComments} { ${mbReadonly}$case: '${fieldName}', ${mbReadonly}${valueName}: ${typeName} }`; })); const name = (0, case_1.maybeSnakeToCamel)(messageDesc.oneofDecl[oneofIndex].name, options); const optionalFlag = options.useOptionals === "none" ? "" : "?"; return (0, ts_poet_1.joinCode)([...outerComments, (0, ts_poet_1.code) `${mbReadonly}${name}${optionalFlag}:`, unionType, (0, ts_poet_1.code) `| ${(0, utils_1.nullOrUndefined)(options)},`], { on: "\n", }); } // Create a function that constructs 'base' instance with default values for decode to use as a prototype function generateBaseInstanceFactory(ctx, fullName, messageDesc, fullTypeName) { const { options, currentFile } = ctx; const fields = []; // When oneof=unions, we generate a single property with an ADT per `oneof` clause. const processedOneofs = new Set(); for (const field of messageDesc.field) { if ((0, types_1.isWithinOneOfThatShouldBeUnion)(ctx.options, field)) { const { oneofIndex } = field; if (!processedOneofs.has(oneofIndex)) { processedOneofs.add(oneofIndex); const name = options.useJsonName ? (0, utils_1.getFieldName)(field, options) : (0, case_1.maybeSnakeToCamel)(messageDesc.oneofDecl[oneofIndex].name, ctx.options); fields.push((0, ts_poet_1.code) `${(0, utils_1.safeAccessor)(name)}: ${(0, utils_1.nullOrUndefined)(options)}`); } continue; } if (!options.initializeFieldsAsUndefined && (0, types_1.isOptionalProperty)(field, messageDesc.options, options, currentFile.isProto3Syntax)) { continue; } const fieldKey = (0, utils_1.safeAccessor)((0, utils_1.getFieldName)(field, options)); const val = (0, types_1.isWithinOneOf)(field) ? (0, utils_1.nullOrUndefined)(options) : (0, types_1.isMapType)(ctx, messageDesc, field) ? (0, types_1.shouldGenerateJSMapType)(ctx, messageDesc, field) ? "new Map()" : "{}" : (0, types_1.isRepeated)(field) ? "[]" : (0, types_1.defaultValue)(ctx, field); fields.push((0, ts_poet_1.code) `${fieldKey}: ${val}`); } if ((0, options_1.addTypeToMessages)(options)) { fields.unshift((0, ts_poet_1.code) `$type: '${fullTypeName}'`); } if (ctx.options.unknownFields && ctx.options.initializeFieldsAsUndefined) { fields.push((0, ts_poet_1.code) `_unknownFields: {}`); } return (0, ts_poet_1.code) ` function createBase${fullName}(): ${fullName} { return { ${(0, ts_poet_1.joinCode)(fields, { on: "," })} }; } `; } function getDecodeReadSnippet(ctx, field) { const { options, utils } = ctx; let readSnippet; if ((0, types_1.isPrimitive)(field)) { readSnippet = (0, ts_poet_1.code) `reader.${(0, types_1.toReaderCall)(field)}()`; if ((0, types_1.isBytes)(field)) { if (options.env === options_1.EnvOption.NODE) { readSnippet = (0, ts_poet_1.code) `Buffer.from(${readSnippet})`; } } else if ((0, types_1.basicLongWireType)(field.type) !== undefined) { if ((0, types_1.isJsTypeFieldOption)(options, field)) { switch (field.options.jstype) { case ts_proto_descriptors_1.FieldOptions_JSType.JS_NUMBER: readSnippet = (0, ts_poet_1.code) `${utils.longToNumber}(${readSnippet})`; break; case ts_proto_descriptors_1.FieldOptions_JSType.JS_STRING: