Skip to main content
Module

x/pbkit/codegen/ts/messages.ts

Protobuf toolkit for modern web development
Latest
File
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360
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; jsonName: 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), jsonName: field.options['json_name']?.toString() ?? 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, jsonName, schema } = field; if (schema.kind === "oneof") return ""; const tsValueToJsonValueCode = getGetTsValueToJsonValueCode({ customTypeMapping, schema, messages, })({ filePath, importBuffer, field }); if (schema.kind === "repeated") { return js` result.${jsonName} = ${tsValueToJsonValueCode!};\n`; } return js` if (value.${tsName} !== undefined) result.${jsonName} = ${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.jsonName} = ${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, jsonName, 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.${jsonName} !== 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.jsonName} !== 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, jsonName } = field; if (schema.kind === "map") { const { keyTypePath, valueTypePath } = schema; if (!keyTypePath || !valueTypePath) return; const valueTypePathCode = typePathToCode("value", valueTypePath); return js`Object.fromEntries([...value.${jsonName}.entries()].map(([key, value]) => [key, ${valueTypePathCode}]))`; } if (schema.kind === "repeated") { const { typePath } = schema; if (!typePath) return; const typePathCode = typePathToCode("value", typePath); return js`value.${jsonName}?.map((value${ts`: any`}) => ${typePathCode})`; } const { typePath } = schema; return typePathToCode("value." + jsonName, 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.jsonName}`; }, }, ".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.jsonName}`; }, }, ".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.jsonName}`; }, }, ".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.jsonName}`; }, }, ".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.jsonName}`; }, }, ".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.jsonName}`; }, }, ".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.jsonName}`; }, }, ".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.jsonName}`; }, }, ".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.jsonName}`; }, }, ".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.jsonName}`; }, }, };}