Skip to main content
Module

x/pbkit/codegen/ts/messages.ts

Protobuf toolkit for modern web development
Go to Latest
File
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358
import { snakeToCamel } from "../../misc/case.ts";import * as schema from "../../core/schema/model.ts";import { unpackFns } from "../../core/runtime/wire/scalar.ts";import { ScalarValueTypePath } from "../../core/runtime/scalar.ts";import { join } from "../path.ts";import { CustomTypeMapping, GenMessagesConfig, GetFieldCodeFn,} from "./index.ts";import { AddInternalImport, CreateImportBufferFn, ImportBuffer,} from "./import-buffer.ts";import { IndexBuffer } from "./index-buffer.ts";import { CodeFragment, Export, js, Module, ModuleFragment, ts,} from "./code-fragment.ts";
export interface GenConfig { createImportBuffer: CreateImportBufferFn; indexBuffer: IndexBuffer; customTypeMapping: CustomTypeMapping; messages: GenMessagesConfig;}export default function* gen( schema: schema.Schema, config: GenConfig,): Generator<Module> { const { createImportBuffer, indexBuffer, customTypeMapping, messages, } = config; for (const [typePath, type] of Object.entries(schema.types)) { indexBuffer.reExport( getFilePath(typePath, messages.outDir, ""), "Type", typePath.split(".").pop()!, ); switch (type.kind) { case "enum": yield* genEnum({ typePath, type, messages }); continue; case "message": yield* genMessage({ schema, typePath, type, createImportBuffer, customTypeMapping, messages, }); continue; } }}
export function getFilePath( typePath: string, messagesDir: string = "messages", ext = ".ts",): string { return join( messagesDir, typePath .replace(/^\./, "") .replaceAll(".", "/") .replaceAll(/\b([A-Z][^/]*)\//g, "($1)/") + ext, );}
const getTypeDefCodeBase = ( { typePath }: { typePath: string }, getTypeDefCodeFn: (typeName: string) => CodeFragment,): ModuleFragment[] => { const fragments = typePath.split("."); const typeName = fragments.pop()!; return [ new Export( "$", ts`declare namespace $${fragments.join(".")} {\n${ getTypeDefCodeFn(typeName) }}`, ), new Export("Type", ts`type Type = $${typePath};`), ];};
interface GenEnumConfig { typePath: string; type: schema.Enum; messages: GenMessagesConfig;}function* genEnum( { typePath, type, messages }: GenEnumConfig,): Generator<Module> { const filePath = getFilePath(typePath, messages.outDir); const fields = Object.entries<schema.EnumField>({ "0": { description: { leading: [], trailing: [], leadingDetached: [] }, name: "UNSPECIFIED", options: {}, }, ...type.fields, }); yield new Module(filePath) .add(getTypeDefCodeBase({ typePath }, (typeName) => { return ts` export type ${typeName} =\n${ fields.map(([, { name }]) => ` | "${name}"`).join("\n") };\n`; })) .export( "num2name", js`const num2name = {\n${ fields.map( ([fieldNumber, { name }]) => ` ${fieldNumber}: "${name}",`, ).join("\n") }\n}${ts` as const`};`, ) .export( "name2num", js`const name2num = {\n${ fields.map( ([fieldNumber, { name }]) => ` ${name}: ${fieldNumber},`, ).join("\n") }\n}${ts` as const`};`, );}
interface Message { schema: schema.Message; collectionFieldNumbers: Set<number>; everyFieldNames: Map<number, string>; fields: Field[]; oneofFields: OneofField[];}export interface Field { schema: schema.MessageField; fieldNumber: number; tsName: string; tsType: CodeFragment; isEnum: boolean; default: CodeFragment | undefined;}interface OneofField { tsName: string; fields: Field[];}
const reservedNames = [ "Type", "Uint8Array", "getDefaultValue", "createValue", "encodeBinary", "decodeBinary", "encodeJson", "decodeJson",];
interface GenMessageConfig { schema: schema.Schema; typePath: string; type: schema.Message; createImportBuffer: CreateImportBufferFn; customTypeMapping: CustomTypeMapping; messages: GenMessagesConfig;}function* genMessage({ schema, typePath, type, createImportBuffer, customTypeMapping, messages,}: GenMessageConfig): Generator<Module> { const filePath = getFilePath(typePath, messages.outDir); const importBuffer = createImportBuffer(); type NonOneofMessageField = Exclude<schema.MessageField, schema.OneofField>; const schemaFields = Object.entries(type.fields); const schemaOneofFields = schemaFields.filter( ([, field]) => field.kind === "oneof", ) as [string, schema.OneofField][]; const schemaNonOneofFields = schemaFields.filter( ([, field]) => field.kind !== "oneof", ) as [string, NonOneofMessageField][]; const collections = schemaNonOneofFields.filter( ([, field]) => field.kind === "map" || field.kind === "repeated", ); const collectionFieldNumbers = new Set( collections.map(([fieldNumber]) => +fieldNumber), ); const everyFieldNames = new Map( schemaFields.map( ([fieldNumber, { name }]) => [+fieldNumber, snakeToCamel(name)], ), ); const oneofFieldTable: { [oneof: string]: OneofField } = {}; for (const schemaOneofField of schemaOneofFields) { const [, schemaField] = schemaOneofField; if (schemaField.kind !== "oneof") continue; const tsName = snakeToCamel(schemaField.oneof); const oneofField = oneofFieldTable[tsName] ?? { tsName, fields: [] }; oneofField.fields.push(toField(schemaOneofField)); oneofFieldTable[tsName] = oneofField; } const message: Message = { schema: type, collectionFieldNumbers, everyFieldNames, fields: schemaNonOneofFields.map(toField), oneofFields: Object.values(oneofFieldTable), }; const getCodeConfig: GetCodeConfig = { typePath, filePath, importBuffer, message, messages, customTypeMapping, }; yield new Module( filePath, importBuffer, new Set([...reservedNames, typePath.split(".").pop()!]), ) .add(getMessageTypeDefCode(getCodeConfig)) .add(getGetDefaultValueCode(getCodeConfig)) .add(getCreateValueCode(getCodeConfig)) .add(getEncodeJsonCode(getCodeConfig)) .add(getDecodeJsonCode(getCodeConfig)) .add(getEncodeBinaryCode(getCodeConfig)) .add(getDecodeBinaryCode(getCodeConfig)); function toField([fieldNumber, field]: [string, schema.MessageField]): Field { return { schema: field, fieldNumber: +fieldNumber, tsName: snakeToCamel(field.name), tsType: getFieldTypeCode(field), isEnum: getFieldValueIsEnum(field), default: getFieldDefaultCode(field), }; } function getFieldTypeCode(field: schema.MessageField): CodeFragment { if (field.kind !== "map") return toTsType(field.typePath); const keyTypeName = toTsType(field.keyTypePath); const valueTypeName = toTsType(field.valueTypePath); return ts`Map<${keyTypeName}, ${valueTypeName}>`; } function getFieldValueIsEnum(field: schema.MessageField): boolean { const typePath = (field.kind === "map") ? field.valueTypePath : field.typePath; if (!typePath) return false; return schema.types[typePath]?.kind === "enum"; } function getFieldDefaultCode( field: schema.MessageField, ): CodeFragment | undefined { if (field.kind === "repeated") return js`[]`; if (field.kind === "map") return js`new Map()`; if (field.typePath! in scalarTypeDefaultValueCodes) { return scalarTypeDefaultValueCodes[field.typePath as ScalarValueTypePath]; } const fieldType = schema.types[field.typePath!]; if (fieldType?.kind === "enum") { return js`"${fieldType.fields[0]?.name ?? "UNSPECIFIED"}"`; } } function toTsType(typePath?: string) { return pbTypeToTsType({ customTypeMapping, addInternalImport: importBuffer.addInternalImport, messages, here: filePath, typePath, }); }}
interface GetCodeConfig { typePath: string; filePath: string; importBuffer: ImportBuffer; message: Message; messages: GenMessagesConfig; customTypeMapping: CustomTypeMapping;}type GetCodeFn = (config: GetCodeConfig) => ModuleFragment[];
const getMessageTypeDefCode: GetCodeFn = (config) => { const { message } = config; const typeBodyCodes: CodeFragment[] = []; if (message.fields.length) typeBodyCodes.push(getFieldsCode()); if (message.oneofFields.length) typeBodyCodes.push(getOneofsCode()); return getTypeDefCodeBase(config, (typeName) => { if (!typeBodyCodes.length) return ts` export type ${typeName} = {}\n`; return ts` export type ${typeName} = {\n${ts(typeBodyCodes)} }\n`; }); function getFieldsCode(): CodeFragment { return ts(message.fields.map((field) => { const nullable = ( (field.default == null) || (field.schema.kind === "optional") ); const opt = nullable ? "?" : ""; const arr = (field.schema.kind === "repeated") ? "[]" : ""; const isDeprecated = field.schema.options["deprecated"] ?? false; if (isDeprecated) { return ts` /** @deprecated */\n ${field.tsName}${opt}: ${field.tsType}${arr};\n`; } return ts` ${field.tsName}${opt}: ${field.tsType}${arr};\n`; })); } function getOneofsCode(): CodeFragment { return ts(message.oneofFields.map((oneofField) => ts([ ts` ${oneofField.tsName}?: (\n`, ts(oneofField.fields.map( (field) => ts` | { field: "${field.tsName}", value: ${field.tsType} }\n`, )), ts(` );\n`), ]) )); }};
const getGetDefaultValueCode: GetCodeFn = ({ typePath, message }) => { return [ new Export( "getDefaultValue", js([ js`function getDefaultValue()${ts`: $${typePath}`} {\n`, js` return {\n`, js([ ...message.fields.map((field) => { if (!field.default || field.schema.kind === "optional") { return js` ${field.tsName}: undefined,\n`; } return js` ${field.tsName}: ${field.default},\n`; }), ...message.oneofFields.map( (field) => js` ${field.tsName}: undefined,\n`, ), ]), js` };\n`, js`}`, ]), ), ];};
const getCreateValueCode: GetCodeFn = ({ typePath }) => { return [ new Export( "createValue", js([ js`function createValue(partialValue${ts`: Partial<$${typePath}>`})${ts`: $${typePath}`} {\n`, js` return {\n`, js` ...getDefaultValue(),\n`, js` ...partialValue,\n`, js` };\n`, js`}`, ]), ), ];};
const getEncodeJsonCode: GetCodeFn = ({ typePath, filePath, importBuffer, message, messages, customTypeMapping,}) => { return [ new Export( "encodeJson", js([ js`function encodeJson(value${ts`: $${typePath}`})${ts`: unknown`} {\n`, js` const result${ts`: any`} = {};\n`, ...message.fields.map((field) => { const { tsName, schema } = field; if (schema.kind === "oneof") return ""; const tsValueToJsonValueCode = getGetTsValueToJsonValueCode({ customTypeMapping, schema, messages, })({ filePath, importBuffer, field }); if (schema.kind === "repeated") { return js` result.${tsName} = ${tsValueToJsonValueCode!};\n`; } return js` if (value.${tsName} !== undefined) result.${tsName} = ${tsValueToJsonValueCode!};\n`; }), ...message.oneofFields.map(({ tsName, fields }) => js([ js` switch (value.${tsName}?.field) {\n`, ...fields.map((field) => { const tsValueToJsonValueCode = getGetTsValueToJsonValueCode({ customTypeMapping, schema: field.schema, messages, })({ filePath, importBuffer, field: { ...field, tsName: tsName + ".value" }, }); return js([ js` case "${field.tsName}": {\n`, js` result.${field.tsName} = ${tsValueToJsonValueCode!};\n`, js` break;\n`, js` }\n`, ]); }), js` }\n`, ]) ), js` return result;\n`, js`}`, ]), ), ];};
const getDecodeJsonCode: GetCodeFn = ({ typePath, filePath, importBuffer, message, messages, customTypeMapping,}) => { return [ new Export( "decodeJson", js([ js`function decodeJson(value${ts`: any`})${ts`: $${typePath}`} {\n`, js` const result = getDefaultValue();\n`, ...message.fields .map((field) => { const { tsName, schema } = field; if (schema.kind === "oneof") return js``; // never const jsonValueToTsValueCode = getGetJsonValueToTsValueCode({ customTypeMapping, schema, messages, })({ filePath, importBuffer, field }); if (schema.kind === "repeated") { return js` result.${tsName} = ${jsonValueToTsValueCode!} ?? [];\n`; } return js` if (value.${tsName} !== undefined) result.${tsName} = ${jsonValueToTsValueCode!};\n`; }), ...message.oneofFields.map(({ tsName, fields }) => js(fields.map((field) => { const jsonValueToTsValueCode = getGetJsonValueToTsValueCode({ customTypeMapping, schema: field.schema, messages, })({ filePath, importBuffer, field }); return js` if (value.${field.tsName} !== undefined) result.${tsName} = {field: "${field.tsName}", value: ${jsonValueToTsValueCode!}};\n`; })) ), js` return result;\n`, js`}`, ]), ), ];};
const getEncodeBinaryCode: GetCodeFn = ({ typePath, filePath, importBuffer, message, messages, customTypeMapping,}) => { const WireMessage = importBuffer.addRuntimeImport({ here: filePath, from: "wire/index.ts", item: "WireMessage", type: true, }); const serialize = importBuffer.addRuntimeImport({ here: filePath, from: "wire/serialize.ts", item: "default", as: "serialize", }); return [ new Export( "encodeBinary", js([ js`function encodeBinary(value${ts`: $${typePath}`})${ts`: Uint8Array`} {\n`, js` const result${ts`: ${WireMessage}`} = [];\n`, ...message.fields.map((field) => { const { fieldNumber, tsName, schema } = field; if (schema.kind === "oneof") return js``; // never const tsValueToWireValueCode = getGetTsValueToWireValueCode({ customTypeMapping, schema, messages, })({ filePath, importBuffer, field }); if (schema.kind === "map") { return js([ js` {\n`, js` const fields = value.${tsName}.entries();\n`, js` for (const [key, value] of fields) {\n`, js` result.push(\n`, js` [${fieldNumber}, ${tsValueToWireValueCode!}],\n`, js` );\n`, js` }\n`, js` }\n`, ]); } if (schema.kind === "repeated") { return js([ js` for (const tsValue of value.${tsName}) {\n`, js` result.push(\n`, js` [${fieldNumber}, ${tsValueToWireValueCode!}],\n`, js` );\n`, js` }\n`, ]); } if (schema.kind === "optional") { return js([ js` if (value.${tsName} !== undefined) {\n`, js` const tsValue = value.${tsName};\n`, js` result.push(\n`, js` [${fieldNumber}, ${tsValueToWireValueCode!}],\n`, js` );\n`, js` }\n`, ]); } return js([ js` if (value.${tsName} !== undefined) {\n`, js` const tsValue = value.${tsName};\n`, js` result.push(\n`, js` [${fieldNumber}, ${tsValueToWireValueCode!}],\n`, js` );\n`, js` }\n`, ]); }), ...message.oneofFields.map(({ tsName, fields }) => { return js([ js` switch (value.${tsName}?.field) {\n`, ...fields.map((field) => { const tsValueToWireValueCode = getGetTsValueToWireValueCode({ customTypeMapping, schema: field.schema, messages, })({ filePath, importBuffer, field }); return js([ js` case "${field.tsName}": {\n`, js` const tsValue = value.${tsName}.value;\n`, js` result.push(\n`, js` [${field.fieldNumber}, ${tsValueToWireValueCode!}],\n`, js` );\n`, js` break;\n`, js` }\n`, ]); }), js` }\n`, ]); }), js` return ${serialize}(result);\n`, js`}`, ]), ), ];};
const getDecodeBinaryCode: GetCodeFn = ({ typePath, filePath, importBuffer, message, messages, customTypeMapping,}) => { const deserialize = importBuffer.addRuntimeImport({ here: filePath, from: "wire/deserialize.ts", item: "default", as: "deserialize", }); return [ ...(message.oneofFields.length ? [ js([ js`const oneofFieldNumbersMap${ts`: { [oneof: string]: Set<number> }`} = {\n`, ...message.oneofFields.map(({ tsName, fields }) => js([ js` ${tsName}: new Set([`, fields.map(({ fieldNumber }) => fieldNumber).join(", "), js`]),\n`, ]) ), js`};`, ]), js([ js`const oneofFieldNamesMap = {\n`, ...message.oneofFields.map(({ tsName, fields }) => js([ js` ${tsName}: new Map([\n`, ...fields.map(({ fieldNumber, tsName }) => js` [${fieldNumber}, "${tsName}"${ts` as const`}],\n` ), js` ]),\n`, ]) ), js`};`, ]), ] : []), new Export( "decodeBinary", js([ js`function decodeBinary(binary${ts`: Uint8Array`})${ts`: $${typePath}`} {\n`, js` const result = getDefaultValue();\n`, js` const wireMessage = ${deserialize}(binary);\n`, // TODO: "For embedded message fields, the parser merges multiple instances of the same field" js` const wireFields = new Map(wireMessage);\n`, message.oneofFields.length ? js` const wireFieldNumbers = Array.from(wireFields.keys()).reverse();\n` : js``, ...message.fields.map((field) => { const { fieldNumber, tsName, schema } = field; const wireValueToTsValueCode = getGetWireValueToTsValueCode({ customTypeMapping, schema, messages, })({ filePath, importBuffer, field }); if (!wireValueToTsValueCode) return ""; const isCollection = message.collectionFieldNumbers.has(fieldNumber); if (isCollection) { const typePath = (schema as schema.RepeatedField).typePath; const type = typePath?.slice(1)!; let wireValuesToTsValuesCode: CodeFragment; if (type as keyof typeof unpackFns in unpackFns) { const unpackFns = importBuffer.addRuntimeImport({ here: filePath, from: "wire/scalar.ts", item: "unpackFns", }); wireValuesToTsValuesCode = js`Array.from(${unpackFns}.${type}(wireValues))`; } else if (field.isEnum && typePath) { const unpackFns = importBuffer.addRuntimeImport({ here: filePath, from: "wire/scalar.ts", item: "unpackFns", }); const num2name = importBuffer.addInternalImport({ here: filePath, from: getFilePath(typePath, messages.outDir), item: "num2name", }); wireValuesToTsValuesCode = js`Array.from(${unpackFns}.int32(wireValues)).map(num => ${num2name}[num${ts` as keyof typeof ${num2name}`}])`; } else { wireValuesToTsValuesCode = js`wireValues.map((wireValue) => ${wireValueToTsValueCode}).filter(x => x !== undefined)`; } const value = schema.kind === "map" ? js`new Map(value${ts` as any`})` : js`value${ts` as any`}`; return js([ js` collection: {\n`, js` const wireValues = wireMessage.filter(([fieldNumber]) => fieldNumber === ${fieldNumber}).map(([, wireValue]) => wireValue);\n`, js` const value = ${wireValuesToTsValuesCode};\n`, js` if (!value.length) break collection;\n`, js` result.${tsName} = ${value};\n`, js` }\n`, ]); } else { return js([ js` field: {\n`, js` const wireValue = wireFields.get(${fieldNumber});\n`, js` if (wireValue === undefined) break field;\n`, js` const value = ${wireValueToTsValueCode};\n`, js` if (value === undefined) break field;\n`, js` result.${tsName} = value;\n`, js` }\n`, ]); } }), ...message.oneofFields.map((field) => { const { tsName, fields } = field; const Field = importBuffer.addRuntimeImport({ here: filePath, from: "wire/index.ts", item: "Field", type: true, }); const wireValueToTsValueMapCode = js([ js`{\n`, ...fields.map((field) => { const { fieldNumber, schema } = field; const wireValueToTsValueCode = getGetWireValueToTsValueCode({ customTypeMapping, schema, messages, })({ filePath, importBuffer, field }) || js`undefined`; return js` [${fieldNumber}](wireValue${ts`: ${Field}`}) { return ${wireValueToTsValueCode}; },\n`; }), js` }`, ]); return js([ js` oneof: {\n`, js` const oneofFieldNumbers = oneofFieldNumbersMap.${tsName};\n`, js` const oneofFieldNames = oneofFieldNamesMap.${tsName};\n`, js` const fieldNumber = wireFieldNumbers.find(v => oneofFieldNumbers.has(v));\n`, js` if (fieldNumber == null) break oneof;\n`, js` const wireValue = wireFields.get(fieldNumber);\n`, js` const wireValueToTsValueMap = ${wireValueToTsValueMapCode};\n`, js` const value = (wireValueToTsValueMap[fieldNumber${ts` as keyof typeof wireValueToTsValueMap`}]${ts` as any`})?.(wireValue${ts`!`});\n`, js` if (value === undefined) break oneof;\n`, js` result.${tsName} = { field: oneofFieldNames.get(fieldNumber)${ts`!`}, value: value${ts` as any`} };\n`, js` }\n`, ]); }), js` return result;\n`, js`}`, ]), ), ];};
type NonMapMessageField = Exclude<schema.MessageField, schema.MapField>;
interface GetGetTsValueToWireValueCodeConfig { customTypeMapping: CustomTypeMapping; schema: schema.MessageField; messages: GenMessagesConfig;}function getGetTsValueToWireValueCode({ customTypeMapping, schema, messages,}: GetGetTsValueToWireValueCodeConfig): GetFieldCodeFn { const customTypeMappingItem = customTypeMapping[ (schema as NonMapMessageField).typePath! ]; return ( customTypeMappingItem?.getTsValueToWireValueCode ?? ((config) => getDefaultTsValueToWireValueCode({ ...config, messages })) );}interface GetDefaultTsValueToWireValueCodeConfig { filePath: string; importBuffer: ImportBuffer; field: Field; messages: GenMessagesConfig;}function getDefaultTsValueToWireValueCode({ filePath, importBuffer, field, messages,}: GetDefaultTsValueToWireValueCodeConfig): CodeFragment { const { schema } = field; if (schema.kind === "map") { const { keyTypePath, valueTypePath } = schema; if (!keyTypePath || !valueTypePath) return js``; const serialize = importBuffer.addRuntimeImport({ here: filePath, from: "wire/serialize.ts", item: "default", as: "serialize", }); const WireType = importBuffer.addRuntimeImport({ here: filePath, from: "wire/index.ts", item: "WireType", }); const keyTypePathCode = typePathToCode("key", keyTypePath); const valueTypePathCode = typePathToCode("value", valueTypePath); const value = ( js`${serialize}([[1, ${keyTypePathCode}], [2, ${valueTypePathCode}]])` ); return js`{ type: ${WireType}.LengthDelimited${ts` as const`}, value: ${value} }`; } const { typePath } = schema; return typePathToCode("tsValue", typePath); function typePathToCode(tsValue: string, typePath?: string): CodeFragment { if (!typePath) return js``; if (typePath in scalarTypeMapping) { const tsValueToWireValueFns = importBuffer.addRuntimeImport({ here: filePath, from: "wire/scalar.ts", item: "tsValueToWireValueFns", }); return js`${tsValueToWireValueFns}.${typePath.slice(1)}(${tsValue})`; } const WireType = importBuffer.addRuntimeImport({ here: filePath, from: "wire/index.ts", item: "WireType", }); if (field.isEnum) { const Long = importBuffer.addRuntimeImport({ here: filePath, from: "Long.ts", item: "default", as: "Long", }); const name2num = importBuffer.addInternalImport({ here: filePath, from: getFilePath(typePath, messages.outDir), item: "name2num", }); return js`{ type: ${WireType}.Varint${ts` as const`}, value: new ${Long}(${name2num}[${tsValue}${ts` as keyof typeof ${name2num}`}]) }`; } const encodeBinary = importBuffer.addInternalImport({ here: filePath, from: getFilePath(typePath, messages.outDir), item: "encodeBinary", }); return js`{ type: ${WireType}.LengthDelimited${ts` as const`}, value: ${encodeBinary}(${tsValue}) }`; }}
interface GetGetTsValueToJsonValueCodeConfig { customTypeMapping: CustomTypeMapping; schema: schema.MessageField; messages: GenMessagesConfig;}function getGetTsValueToJsonValueCode({ customTypeMapping, schema, messages,}: GetGetTsValueToJsonValueCodeConfig): GetFieldCodeFn { const customTypeMappingItem = customTypeMapping[(schema as NonMapMessageField).typePath!]; return ( customTypeMappingItem?.getTsValueToJsonValueCode ?? ((config) => getDefaultTsValueToJsonValueCode({ ...config, messages })) );}
export interface GetDefaultTsValueToJsonValueCodeConfig { filePath: string; importBuffer: ImportBuffer; field: Field; messages: GenMessagesConfig;}export function getDefaultTsValueToJsonValueCode({ filePath, importBuffer, field, messages,}: GetDefaultTsValueToJsonValueCodeConfig): CodeFragment | undefined { const { schema, tsName } = field; if (schema.kind === "map") { const { keyTypePath, valueTypePath } = schema; if (!keyTypePath || !valueTypePath) return; const valueTypePathCode = typePathToCode("value", valueTypePath); return js`Object.fromEntries([...value.${tsName}.entries()].map(([key, value]) => [key, ${valueTypePathCode}]))`; } if (schema.kind === "repeated") { const { typePath } = schema; if (!typePath) return; const typePathCode = typePathToCode("value", typePath); return js`value.${tsName}.map(value => ${typePathCode})`; } const { typePath } = schema; return typePathToCode("value." + tsName, typePath); function typePathToCode(tsName: string, typePath?: string): CodeFragment { if (!typePath) return js``; if (typePath in scalarTypeMapping) { const tsValueToJsonValueFns = importBuffer.addRuntimeImport({ here: filePath, from: "json/scalar.ts", item: "tsValueToJsonValueFns", }); return js`${tsValueToJsonValueFns}.${typePath.slice(1)}(${tsName})`; } if (field.isEnum) { const tsValueToJsonValueFns = importBuffer.addRuntimeImport({ here: filePath, from: "json/scalar.ts", item: "tsValueToJsonValueFns", }); return js`${tsValueToJsonValueFns}.enum(${tsName})`; } const encodeJson = importBuffer.addInternalImport({ here: filePath, from: getFilePath(typePath, messages.outDir), item: "encodeJson", }); return js`${encodeJson}(${tsName})`; }}
interface GetGetJsonValueToTsValueCodeConfig { customTypeMapping: CustomTypeMapping; schema: schema.MessageField; messages: GenMessagesConfig;}function getGetJsonValueToTsValueCode({ customTypeMapping, schema, messages,}: GetGetJsonValueToTsValueCodeConfig): GetFieldCodeFn { const customTypeMappingItem = customTypeMapping[(schema as NonMapMessageField).typePath!]; return ( customTypeMappingItem?.getJsonValueToTsValueCode ?? ((config) => getDefaultJsonValueToTsValueCode({ ...config, messages })) );}
interface GetDefaultJsonValueToTsValueCodeConfig { filePath: string; importBuffer: ImportBuffer; field: Field; messages: GenMessagesConfig;}function getDefaultJsonValueToTsValueCode({ filePath, importBuffer, field, messages,}: GetDefaultJsonValueToTsValueCodeConfig): CodeFragment | undefined { const { schema, tsName } = field; if (schema.kind === "map") { const { keyTypePath, valueTypePath } = schema; if (!keyTypePath || !valueTypePath) return; const valueTypePathCode = typePathToCode("value", valueTypePath); return js`Object.fromEntries([...value.${tsName}.entries()].map(([key, value]) => [key, ${valueTypePathCode}]))`; } if (schema.kind === "repeated") { const { typePath } = schema; if (!typePath) return; const typePathCode = typePathToCode("value", typePath); return js`value.${tsName}?.map((value${ts`: any`}) => ${typePathCode})`; } const { typePath } = schema; return typePathToCode("value." + tsName, typePath); function typePathToCode( jsonValue: string, typePath?: string, ): CodeFragment { if (!typePath) return js``; const jsonValueToTsValueFns = importBuffer.addRuntimeImport({ here: filePath, from: "json/scalar.ts", item: "jsonValueToTsValueFns", }); if (typePath in scalarTypeMapping) { return js`${jsonValueToTsValueFns}.${typePath.slice(1)}(${jsonValue})`; } if (field.isEnum) { if (schema.kind === "map") { return js`${jsonValueToTsValueFns}.enum(${jsonValue})`; } else { return js`${jsonValueToTsValueFns}.enum(${jsonValue})${ts` as ${field.tsType}`}`; } } const decodeJson = importBuffer.addInternalImport({ here: filePath, from: getFilePath(typePath, messages.outDir), item: "decodeJson", }); return js`${decodeJson}(${jsonValue})`; }}
interface GetGetWireValueToTsValueCodeConfig { customTypeMapping: CustomTypeMapping; schema: schema.MessageField; messages: GenMessagesConfig;}function getGetWireValueToTsValueCode({ customTypeMapping, schema, messages,}: GetGetWireValueToTsValueCodeConfig): GetFieldCodeFn { const customTypeMappingItem = customTypeMapping[ (schema as NonMapMessageField).typePath! ]; return ( customTypeMappingItem?.getWireValueToTsValueCode ?? ((config) => getDefaultWireValueToTsValueCode({ ...config, messages })) );}interface GetDefaultWireValueToTsValueCodeConfig { filePath: string; importBuffer: ImportBuffer; field: Field; messages: GenMessagesConfig;}function getDefaultWireValueToTsValueCode({ filePath, importBuffer, field, messages,}: GetDefaultWireValueToTsValueCodeConfig): CodeFragment { const { schema } = field; if (schema.kind === "map") { const { keyTypePath, valueTypePath } = schema; if (!keyTypePath || !valueTypePath) return js``; const deserialize = importBuffer.addRuntimeImport({ here: filePath, from: "wire/deserialize.ts", item: "default", as: "deserialize", }); const WireType = importBuffer.addRuntimeImport({ here: filePath, from: "wire/index.ts", item: "WireType", }); const keyTypePathCode = typePathToCode("key", keyTypePath); const valueTypePathCode = typePathToCode("value", valueTypePath); return js([ js`(() => { `, js`if (wireValue.type !== ${WireType}.LengthDelimited) { return; } `, js`const { 1: key, 2: value } = Object.fromEntries(${deserialize}(wireValue.value)); `, js`if (key === undefined || value === undefined) return; `, js`return [${keyTypePathCode}, ${valueTypePathCode}]${ts` as const`};`, js`})()`, ]); } const { typePath } = schema; return typePathToCode("wireValue", typePath); function typePathToCode(wireValue: string, typePath?: string): CodeFragment { if (!typePath) return js``; if (typePath in scalarTypeMapping) { const wireValueToTsValueFns = importBuffer.addRuntimeImport({ here: filePath, from: "wire/scalar.ts", item: "wireValueToTsValueFns", }); return js`${wireValueToTsValueFns}.${typePath.slice(1)}(${wireValue})`; } const WireType = importBuffer.addRuntimeImport({ here: filePath, from: "wire/index.ts", item: "WireType", }); if (field.isEnum) { const num2name = importBuffer.addInternalImport({ here: filePath, from: getFilePath(typePath, messages.outDir), item: "num2name", }); return js`${wireValue}.type === ${WireType}.Varint ? ${num2name}[${wireValue}.value[0]${ts` as keyof typeof ${num2name}`}] : undefined`; } const decodeBinary = importBuffer.addInternalImport({ here: filePath, from: getFilePath(typePath, messages.outDir), item: "decodeBinary", }); return js`${wireValue}.type === ${WireType}.LengthDelimited ? ${decodeBinary}(${wireValue}.value) : undefined`; }}
export interface PbTypeToTsMessageTypeConfig { addInternalImport: AddInternalImport; messages: GenMessagesConfig; here: string; typePath?: string;}export function pbTypeToTsMessageType({ addInternalImport, messages, here, typePath,}: PbTypeToTsMessageTypeConfig): CodeFragment { if (!typePath) return ts`unknown`; const from = getFilePath(typePath, messages.outDir); const as = typePath.match(/[^.]+$/)?.[0]!; return ts([addInternalImport({ here, from, item: "Type", as, type: true })]);}
export interface PbTypeToTsTypeConfig { customTypeMapping: CustomTypeMapping; addInternalImport: AddInternalImport; messages: GenMessagesConfig; here: string; typePath?: string;}export function pbTypeToTsType({ customTypeMapping, addInternalImport, messages, here, typePath,}: PbTypeToTsTypeConfig): CodeFragment { if (!typePath) return ts`unknown`; if (typePath in scalarTypeMapping) { return scalarTypeMapping[typePath as keyof typeof scalarTypeMapping]; } if (typePath in customTypeMapping) { return customTypeMapping[typePath].tsType; } const from = getFilePath(typePath, messages.outDir); const as = typePath.match(/[^.]+$/)?.[0]!; return ts([addInternalImport({ here, from, item: "Type", as, type: true })]);}type ScalarToCodeTable = { [typePath in ScalarValueTypePath]: CodeFragment };const scalarTypeMapping: ScalarToCodeTable = { ".double": ts`number`, ".float": ts`number`, ".int32": ts`number`, ".int64": ts`string`, ".uint32": ts`number`, ".uint64": ts`string`, ".sint32": ts`number`, ".sint64": ts`string`, ".fixed32": ts`number`, ".fixed64": ts`string`, ".sfixed32": ts`number`, ".sfixed64": ts`string`, ".bool": ts`boolean`, ".string": ts`string`, ".bytes": ts`Uint8Array`,};const scalarTypeDefaultValueCodes: ScalarToCodeTable = { ".double": js`0`, ".float": js`0`, ".int32": js`0`, ".int64": js`"0"`, ".uint32": js`0`, ".uint64": js`"0"`, ".sint32": js`0`, ".sint64": js`"0"`, ".fixed32": js`0`, ".fixed64": js`"0"`, ".sfixed32": js`0`, ".sfixed64": js`"0"`, ".bool": js`false`, ".string": js`""`, ".bytes": js`new Uint8Array()`,};
export interface GetWellKnownTypeMappingConfig { messages: GenMessagesConfig;}export function getWellKnownTypeMapping({ messages,}: GetWellKnownTypeMappingConfig): CustomTypeMapping { return { ".google.protobuf.BoolValue": { tsType: ts`boolean`, getWireValueToTsValueCode(config) { return js`(${ getDefaultWireValueToTsValueCode({ ...config, messages }) })?.value`; }, getTsValueToWireValueCode(config) { const value = getDefaultTsValueToWireValueCode({ ...config, messages }); return js`((tsValue) => (${value}))({ value: tsValue })`; }, getTsValueToJsonValueCode(config) { const { field } = config; return js`value.${field.tsName}`; }, getJsonValueToTsValueCode(config) { const { field } = config; return js`value.${field.tsName}`; }, }, ".google.protobuf.BytesValue": { tsType: ts`Uint8Array`, getWireValueToTsValueCode(config) { return js`(${ getDefaultWireValueToTsValueCode({ ...config, messages }) })?.value`; }, getTsValueToWireValueCode(config) { const value = getDefaultTsValueToWireValueCode({ ...config, messages }); return js`((tsValue) => (${value}))({ value: tsValue })`; }, getTsValueToJsonValueCode(config) { const { field } = config; return js`value.${field.tsName}`; }, getJsonValueToTsValueCode(config) { const { field } = config; return js`value.${field.tsName}`; }, }, ".google.protobuf.DoubleValue": { tsType: ts`number`, getWireValueToTsValueCode(config) { return js`(${ getDefaultWireValueToTsValueCode({ ...config, messages }) })?.value`; }, getTsValueToWireValueCode(config) { const value = getDefaultTsValueToWireValueCode({ ...config, messages }); return js`((tsValue) => (${value}))({ value: tsValue })`; }, getTsValueToJsonValueCode(config) { const { field } = config; return js`value.${field.tsName}`; }, getJsonValueToTsValueCode(config) { const { field } = config; return js`value.${field.tsName}`; }, }, ".google.protobuf.FloatValue": { tsType: ts`number`, getWireValueToTsValueCode(config) { return js`(${ getDefaultWireValueToTsValueCode({ ...config, messages }) })?.value`; }, getTsValueToWireValueCode(config) { const value = getDefaultTsValueToWireValueCode({ ...config, messages }); return js`((tsValue) => (${value}))({ value: tsValue })`; }, getTsValueToJsonValueCode(config) { const { field } = config; return js`value.${field.tsName}`; }, getJsonValueToTsValueCode(config) { const { field } = config; return js`value.${field.tsName}`; }, }, ".google.protobuf.Int32Value": { tsType: ts`number`, getWireValueToTsValueCode(config) { return js`(${ getDefaultWireValueToTsValueCode({ ...config, messages }) })?.value`; }, getTsValueToWireValueCode(config) { const value = getDefaultTsValueToWireValueCode({ ...config, messages }); return js`((tsValue) => (${value}))({ value: tsValue })`; }, getTsValueToJsonValueCode(config) { const { field } = config; return js`value.${field.tsName}`; }, getJsonValueToTsValueCode(config) { const { field } = config; return js`value.${field.tsName}`; }, }, ".google.protobuf.Int64Value": { tsType: ts`string`, getWireValueToTsValueCode(config) { return js`(${ getDefaultWireValueToTsValueCode({ ...config, messages }) })?.value`; }, getTsValueToWireValueCode(config) { const value = getDefaultTsValueToWireValueCode({ ...config, messages }); return js`((tsValue) => (${value}))({ value: tsValue })`; }, getTsValueToJsonValueCode(config) { const { field } = config; return js`value.${field.tsName}`; }, getJsonValueToTsValueCode(config) { const { field } = config; return js`value.${field.tsName}`; }, }, ".google.protobuf.NullValue": { tsType: ts`null`, getWireValueToTsValueCode(config) { return js`(${ getDefaultWireValueToTsValueCode({ ...config, messages }) }) === "NULL_VALUE" ? null : undefined`; }, getTsValueToWireValueCode(config) { const value = getDefaultTsValueToWireValueCode({ ...config, messages }); return js`((tsValue) => (${value}))("NULL_VALUE")`; }, getTsValueToJsonValueCode(config) { const { field } = config; return js`value.${field.tsName}`; }, getJsonValueToTsValueCode(config) { const { field } = config; return js`value.${field.tsName}`; }, }, ".google.protobuf.StringValue": { tsType: ts`string`, getWireValueToTsValueCode(config) { return js`(${ getDefaultWireValueToTsValueCode({ ...config, messages }) })?.value`; }, getTsValueToWireValueCode(config) { const value = getDefaultTsValueToWireValueCode({ ...config, messages }); return js`((tsValue) => (${value}))({ value: tsValue })`; }, getTsValueToJsonValueCode(config) { const { field } = config; return js`value.${field.tsName}`; }, getJsonValueToTsValueCode(config) { const { field } = config; return js`value.${field.tsName}`; }, }, ".google.protobuf.UInt32Value": { tsType: ts`number`, getWireValueToTsValueCode(config) { return js`(${ getDefaultWireValueToTsValueCode({ ...config, messages }) })?.value`; }, getTsValueToWireValueCode(config) { const value = getDefaultTsValueToWireValueCode({ ...config, messages }); return js`((tsValue) => (${value}))({ value: tsValue })`; }, getTsValueToJsonValueCode(config) { const { field } = config; return js`value.${field.tsName}`; }, getJsonValueToTsValueCode(config) { const { field } = config; return js`value.${field.tsName}`; }, }, ".google.protobuf.UInt64Value": { tsType: ts`string`, getWireValueToTsValueCode(config) { return js`(${ getDefaultWireValueToTsValueCode({ ...config, messages }) })?.value`; }, getTsValueToWireValueCode(config) { const value = getDefaultTsValueToWireValueCode({ ...config, messages }); return js`((tsValue) => (${value}))({ value: tsValue })`; }, getTsValueToJsonValueCode(config) { const { field } = config; return js`value.${field.tsName}`; }, getJsonValueToTsValueCode(config) { const { field } = config; return js`value.${field.tsName}`; }, }, };}