ts-proto
Version:
[](https://www.npmjs.com/package/ts-proto) [](https://github.com/stephenh/ts-proto/actions)
983 lines (976 loc) • 134 kB
JavaScript
"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: