typia.protobuf.message โ emit a .proto schema from a TypeScript type
If you only encode and decode within TypeScript, you donโt need this โ the encode and decode functions analyze your type internally and produce a wire-compatible binary form.
typia.protobuf.message<T>() is for the other case: when you need to share the schema with a service written in another language (Go, Python, โฆ). It returns the .proto text that any Protocol Buffer compiler can consume.
namespace protobuf {
function message<T>(): string; // returns .proto schema text
}First example
import typia, { tags } from "typia";
interface User {
id: string;
age: number & tags.Type<"uint32">;
hobbies: string[];
}
const proto: string = typia.protobuf.message<User>();
console.log(proto);
//
// syntax = "proto3";
// message User {
// required string id = 1;
// required uint32 age = 2;
// repeated string hobbies = 3;
// }TypeScript Source
import typia, { tags } from "typia";
typia.protobuf.message<IMember>();
interface IMember {
id:
| (string & tags.Sequence<11>)
| (number & tags.Type<"uint64"> & tags.Sequence<12>)
| (Uint8Array & tags.Sequence<13>);
name: (string & tags.Sequence<20>) | null;
children: Array<IMember> & tags.Sequence<30>;
keywords: Map<string, string> & tags.Sequence<40>;
thumbnail:
| (string & tags.Format<"uri"> & tags.ContentMediaType<"image/*">)
| Uint8Array;
email: string & tags.Format<"email">;
hobbies: Array<IHobby>;
}
interface IHobby {
id: string & tags.Format<"uuid">;
name: string;
valid: boolean;
}Numeric scalar types
Protocol Buffer has narrower numeric types than TypeScript (int32, uint32, int64, uint64, float, double). Use type tags to pick the protobuf scalar type explicitly:
type Score = number & tags.Type<"float">;
type Population = bigint & tags.Type<"uint64">;
type Mixed = (number & tags.Type<"int32">) | (bigint & tags.Type<"uint64">);If you donโt tag a number, typia treats it as double. If you donโt tag a bigint, typia treats it as int64.
tags.Type<"int8"> and tags.Type<"int16"> validate the smaller signed integer ranges, then emit the protobuf int32 scalar type. tags.Type<"uint8"> and tags.Type<"uint16"> validate the smaller unsigned integer ranges, then emit uint32. Protocol Buffer has no distinct 8-bit or 16-bit numeric scalar types.
The TypeScript compiler will block invalid combinations โ you canโt put
tags.Type<"uint32">on abigint, for example.
TypeScript Source
import typia, { tags } from "typia";
interface TypeTagExample {
// ATOMIC TYPES
int32: number & tags.Type<"int32">;
uint32: number & tags.Type<"uint32">;
uint64: bigint & tags.Type<"uint64">;
int64: number & tags.Type<"int64">;
float: number & tags.Type<"float">;
double: number | undefined;
string: string | null;
// UNION TYPES
uint32_or_double: number & (tags.Type<"uint32"> | tags.Type<"double">);
int32_or_uint64:
| (number & tags.Type<"int32">)
| (bigint & tags.Type<"uint64">);
int32_or_float_or_uint64:
| (number & (tags.Type<"int32"> | tags.Type<"float">))
| (bigint & tags.Type<"uint64">);
// ARRAY AND MAP
uint64_array: Array<bigint & tags.Type<"uint64">>;
int32_map?: Map<number & tags.Type<"int32">, string> | null;
}
//----
// PROTOBUF MESSAGE SCHEMA
//----
typia.protobuf.message<TypeTagExample>();
//----
// DECODE FUNCTION
//----
typia.protobuf.createDecode<TypeTagExample>();
//----
// ENCODE FUNCTION
//----
typia.protobuf.createEncode<TypeTagExample>();Comment tags
The same scalar choices are also expressible as @type comments:
interface User {
/** @type uint8 */
priority: number;
/** @type uint32 */
age: number;
}Same caveats as validators/tags: comment tags are not type-checked (a typo is ignored, so protobuf falls back to the default double scalar for an untagged number), they only work on object properties, and they canโt express union numeric types. Prefer type tags unless youโre maintaining a JSDoc-style codebase.
TypeScript Source
import typia from "typia";
export interface CommentTagExample {
/** @type int32 */
int32: number;
/** @type uint32 */
uint32?: number | null;
/** @type uint64 */
uint64?: number;
/** @type int64 */
int64: number;
/** @type float */
float: number | null;
double: number;
string: string;
}
//----
// PROTOBUF MESSAGE SCHEMA
//----
typia.protobuf.message<CommentTagExample>();
//----
// DECODE FUNCTION
//----
typia.protobuf.createDecode<CommentTagExample>();
//----
// ENCODE FUNCTION
//----
typia.protobuf.createEncode<CommentTagExample>();Restrictions
Protocol Bufferโs type system is much narrower than TypeScriptโs. The following are not representable:
Top-level type must be a single static object
number, Array<T>, Cat | Dog, Record<string, T> โ none of these are valid top-level shapes. Wrap them in an object.
TypeScript Source
import typia from "typia";
interface Cat { type: "cat"; name: string; ribbon: boolean }
interface Dog { type: "dog"; name: string; hunt: boolean }
typia.protobuf.message<bigint>();
typia.protobuf.createDecode<Record<string, number>>();
typia.protobuf.createDecode<Map<number & typia.tags.Type<"float">, Dog>>();
typia.protobuf.createEncode<boolean[]>();
typia.protobuf.createEncode<Cat | Dog>();Container nesting is limited
Array<T>, Map<K, V>, and Record<string, T> are containers. The rules:
- No two-dimensional containers.
number[][]is not allowed. Neither isMap<string, number[]>. - No unions inside container values.
Map<string, Cat | Dog>is not allowed. Mapkeys must be atomic and non-union.Map<Cat, string>andMap<string | number, Dog>are not allowed.
TypeScript Source
import typia from "typia";
interface IPointer<T> { value: T }
interface Cat { type: "cat"; name: string; ribbon: boolean }
interface Dog { type: "dog"; name: string; hunt: boolean }
typia.protobuf.message<IPointer<number[][]>>();
typia.protobuf.createEncode<IPointer<Record<string, string[]>>>();
typia.protobuf.createDecode<IPointer<Map<string, Cat | Dog>>>();
typia.protobuf.message<IPointer<Map<Cat, string>>>();
typia.protobuf.message<IPointer<Map<number | string, Dog>>>();Types with no Protocol Buffer counterpart
These are rejected outright:
any/unknown(you donโt know how to encode an unknown value)- function types
Set<T>,WeakSet<T>,WeakMap<K, V>โ useArray<T>/Map<K, V>insteadDate,Boolean,BigInt,Number,Stringโ use the primitive (string,boolean, โฆ) instead- Typed arrays except
Uint8Arrayโ useUint8Arrayfor binary data
TypeScript Source
import typia from "typia";
interface Something {
any: any;
unknown: unknown;
closure: () => void;
dict: Set<string> | WeakSet<Something> | WeakMap<Something, string>;
date: Date;
classic: String;
buffer: ArrayBuffer;
}
typia.protobuf.message<Something>();Where to go next
- Encoding a value โ
protobuf.encode - Decoding bytes โ
protobuf.decode - Numeric constraints in tags โ Special Tags